Commit 85b81bd8 authored by Volker Krause's avatar Volker Krause
Browse files

First draft of the new schema API/data model

Mostly non-functional still though.
parent 95df1d41
set(analyzer_lib_srcs
core/product.cpp
core/schemaentry.cpp
core/schemaentryelement.cpp
rest/restapi.cpp
rest/restclient.cpp
......
......@@ -16,7 +16,10 @@
*/
#include "schemaentry.h"
#include "schemaentryelement.h"
#include "util.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonObject>
#include <QObject>
......@@ -30,10 +33,23 @@ namespace Analyzer {
class SchemaEntryData : public QSharedData
{
public:
QString name;
int internalId = -1;
SchemaEntry::Type type = SchemaEntry::StringType;
SchemaEntry::DataType dataType = SchemaEntry::Scalar;
SchemaEntry::AggregationType aggregation = SchemaEntry::None;
QVector<SchemaEntryElement> elements;
};
static const struct {
SchemaEntry::AggregationType type;
const char *name;
} aggregation_types_table[] {
{ SchemaEntry::None, "none" },
{ SchemaEntry::Category, "category" },
{ SchemaEntry::RatioSet, "ratio_set" },
{ SchemaEntry::Numeric, "numeric" },
{ SchemaEntry::XY, "xy" }
};
}
......@@ -48,7 +64,10 @@ bool SchemaEntry::operator==(const SchemaEntry &other) const
{
return d->name == other.d->name
&& d->internalId == other.d->internalId
&& d->type == other.d->internalId;
&& d->type == other.d->type
&& d->dataType == other.d->dataType
&& d->aggregation == other.d->aggregation
&& d->elements == other.d->elements;
}
int SchemaEntry::internalId() const
......@@ -81,6 +100,36 @@ void SchemaEntry::setType(SchemaEntry::Type type)
d->type = type;
}
SchemaEntry::DataType SchemaEntry::dataType() const
{
return d->dataType;
}
void SchemaEntry::setDataType(SchemaEntry::DataType type)
{
d->dataType = type;
}
SchemaEntry::AggregationType SchemaEntry::aggregationType() const
{
return d->aggregation;
}
void SchemaEntry::setAggregationType(SchemaEntry::AggregationType type)
{
d->aggregation = type;
}
QVector<SchemaEntryElement> SchemaEntry::elements() const
{
return d->elements;
}
void SchemaEntry::setElements(const QVector<SchemaEntryElement> &elements)
{
d->elements = elements;
}
QString SchemaEntry::displayString(SchemaEntry::Type type)
{
switch (type) {
......@@ -110,6 +159,12 @@ QJsonObject SchemaEntry::toJsonObject() const
case RatioSetType: t = QStringLiteral("ratio_set"); break;
}
obj.insert(QStringLiteral("type"), t);
obj.insert(QStringLiteral("aggregation"), QLatin1String(aggregation_types_table[d->aggregation].name));
QJsonArray array;
for (const auto &element : qAsConst(d->elements))
array.push_back(element.toJsonObject());
obj.insert(QStringLiteral("elements"), array);
return obj;
}
......@@ -122,7 +177,7 @@ QVector<SchemaEntry> SchemaEntry::fromJson(const QJsonArray &array)
const auto obj = v.toObject();
SchemaEntry entry;
entry.setName(obj.value(QStringLiteral("name")).toString());
const auto t = obj.value(QStringLiteral("type")).toString();
auto t = obj.value(QStringLiteral("type")).toString();
if (t == QStringLiteral("string"))
entry.setType(StringType);
else if (t == QStringLiteral("int"))
......@@ -131,6 +186,9 @@ QVector<SchemaEntry> SchemaEntry::fromJson(const QJsonArray &array)
entry.setType(StringListType);
else if (t == QStringLiteral("ratio_set"))
entry.setType(RatioSetType);
entry.setAggregationType(Util::stringToEnum<AggregationType>(obj.value(QLatin1String("aggregation")).toString(), aggregation_types_table));
entry.setElements(SchemaEntryElement::fromJson(obj.value(QLatin1String("elements")).toArray()));
res.push_back(entry);
}
......
......@@ -29,6 +29,7 @@ namespace UserFeedback {
namespace Analyzer {
class SchemaEntryData;
class SchemaEntryElement;
/** Represents one schema entry.
* A schema entry can be a scalar, a list or a map, and consists of one or
......@@ -58,6 +59,22 @@ class SchemaEntry
{
Q_GADGET
public:
enum DataType {
Scalar,
List,
Map
};
Q_ENUM(DataType)
enum AggregationType {
None,
Category,
RatioSet,
Numeric,
XY
};
Q_ENUM(AggregationType)
enum Type {
InvalidType,
IntegerType,
......@@ -83,6 +100,15 @@ public:
Type type() const;
void setType(Type type);
AggregationType aggregationType() const;
void setAggregationType(AggregationType type);
DataType dataType() const;
void setDataType(DataType type);
QVector<SchemaEntryElement> elements() const;
void setElements(const QVector<SchemaEntryElement> &elements);
static QString displayString(Type type);
QJsonObject toJsonObject() const;
......
/*
Copyright (C) 2016 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "schemaentryelement.h"
#include "util.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QSharedData>
#include <QVector>
using namespace UserFeedback::Analyzer;
namespace UserFeedback {
namespace Analyzer {
class SchemaEntryElementData : public QSharedData
{
public:
QString name;
SchemaEntryElement::Type type = SchemaEntryElement::String;
};
static const struct {
SchemaEntryElement::Type type;
const char *name;
} element_type_table[] {
{ SchemaEntryElement::Integer, "int" },
{ SchemaEntryElement::Number, "number" },
{ SchemaEntryElement::String, "string" },
{ SchemaEntryElement::Boolean, "bool" }
};
}
}
SchemaEntryElement::SchemaEntryElement() :
d(new SchemaEntryElementData)
{
}
SchemaEntryElement::SchemaEntryElement(const SchemaEntryElement&) = default;
SchemaEntryElement::~SchemaEntryElement() = default;
SchemaEntryElement& SchemaEntryElement::operator=(const SchemaEntryElement&) = default;
bool SchemaEntryElement::operator==(const SchemaEntryElement& other) const
{
return d->name == other.d->name
&& d->type == other.d->type;
}
QString SchemaEntryElement::name() const
{
return d->name;
}
void SchemaEntryElement::setName(const QString& name)
{
d->name = name;
}
SchemaEntryElement::Type SchemaEntryElement::type() const
{
return d->type;
}
void SchemaEntryElement::setType(SchemaEntryElement::Type type)
{
d->type = type;
}
QJsonObject SchemaEntryElement::toJsonObject() const
{
QJsonObject obj;
obj.insert(QStringLiteral("name"), d->name);
obj.insert(QStringLiteral("type"), QLatin1String(element_type_table[d->type].name));
return obj;
}
QVector<SchemaEntryElement> SchemaEntryElement::fromJson(const QJsonArray& array)
{
QVector<SchemaEntryElement> res;
res.reserve(array.size());
foreach (const auto &v, array) {
const auto obj = v.toObject();
SchemaEntryElement e;
e.setName(obj.value(QLatin1String("name")).toString());
e.setType(Util::stringToEnum<SchemaEntryElement::Type>(obj.value(QLatin1String("type")).toString(), element_type_table));
res.push_back(e);
}
return res;
}
/*
Copyright (C) 2016 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef USERFEEDBACK_ANALYZER_SCHEMAENTRYELEMENT_H
#define USERFEEDBACK_ANALYZER_SCHEMAENTRYELEMENT_H
#include <qobjectdefs.h>
#include <QMetaType>
#include <QSharedDataPointer>
QT_BEGIN_NAMESPACE
class QJsonArray;
class QJsonObject;
template <typename T> class QVector;
QT_END_NAMESPACE
namespace UserFeedback {
namespace Analyzer {
class SchemaEntryElementData;
/** One element in a SchemaEntry. */
class SchemaEntryElement
{
Q_GADGET
public:
enum Type {
Integer,
Number,
String,
Boolean
};
Q_ENUM(Type)
SchemaEntryElement();
SchemaEntryElement(const SchemaEntryElement &other);
~SchemaEntryElement();
SchemaEntryElement& operator=(const SchemaEntryElement &other);
bool operator==(const SchemaEntryElement &other) const;
QString name() const;
void setName(const QString& name);
Type type() const;
void setType(Type type);
QJsonObject toJsonObject() const;
static QVector<SchemaEntryElement> fromJson(const QJsonArray &array);
private:
QSharedDataPointer<SchemaEntryElementData> d;
};
}
}
Q_DECLARE_METATYPE(UserFeedback::Analyzer::SchemaEntryElement)
Q_DECLARE_METATYPE(UserFeedback::Analyzer::SchemaEntryElement::Type)
Q_DECLARE_TYPEINFO(UserFeedback::Analyzer::SchemaEntryElement, Q_MOVABLE_TYPE);
#endif // USERFEEDBACK_ANALYZER_SCHEMAENTRYELEMENT_H
/*
Copyright (C) 2016 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef USERFEEDBACK_ANALYZER_UTIL_H
#define USERFEEDBACK_ANALYZER_UTIL_H
class QString;
namespace UserFeedback {
namespace Analyzer {
namespace Util
{
template<typename Enum, typename Table, std::size_t N>
Enum stringToEnum(const QString &s, const Table(&lookupTable)[N])
{
for (std::size_t i = 0; i < N; ++i) {
if (s == QLatin1String(lookupTable[i].name))
return lookupTable[i].type;
}
return Enum();
}
}
}
}
#endif // USERFEEDBACK_ANALYZER_UTIL_H
......@@ -17,10 +17,16 @@
#include "schemamodel.h"
#include <core/schemaentryelement.h>
#include <limits>
using namespace UserFeedback::Analyzer;
static const auto TOPLEVEL = std::numeric_limits<quintptr>::max();
SchemaModel::SchemaModel(QObject *parent) :
QAbstractTableModel(parent)
QAbstractItemModel(parent)
{
}
......@@ -61,19 +67,21 @@ void SchemaModel::deleteEntry(int row)
int SchemaModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
return 3;
}
int SchemaModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return m_product.schema().size();
if (!parent.isValid())
return m_product.schema().size();
if (parent.internalId() == TOPLEVEL)
return m_product.schema().at(parent.row()).elements().size();
return 0;
}
QVariant SchemaModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
if (!index.isValid() || index.internalId() != TOPLEVEL)
return {};
switch (index.column()) {
......@@ -87,6 +95,9 @@ QVariant SchemaModel::data(const QModelIndex& index, int role) const
if (role == Qt::EditRole)
return QVariant::fromValue(m_product.schema().at(index.row()).type());
break;
case 2:
if (role == Qt::DisplayRole)
return m_product.schema().at(index.row()).aggregationType();
}
return {};
......@@ -98,14 +109,15 @@ QVariant SchemaModel::headerData(int section, Qt::Orientation orientation, int r
switch (section) {
case 0: return tr("Name");
case 1: return tr("Type");
case 2: return tr("Aggregation");
}
}
return QAbstractTableModel::headerData(section, orientation, role);
return QAbstractItemModel::headerData(section, orientation, role);
}
Qt::ItemFlags SchemaModel::flags(const QModelIndex &index) const
{
const auto baseFlags = QAbstractTableModel::flags(index);
const auto baseFlags = QAbstractItemModel::flags(index);
if (index.isValid())
return baseFlags | Qt::ItemIsEditable;
return baseFlags;
......@@ -133,3 +145,21 @@ bool SchemaModel::setData(const QModelIndex &index, const QVariant &value, int r
emit dataChanged(index, index);
return false;
}
QModelIndex SchemaModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return {};
if (!parent.isValid())
return createIndex(row, column, TOPLEVEL);
if (parent.internalId() == TOPLEVEL)
return createIndex(row, column, parent.row());
return {};
}
QModelIndex SchemaModel::parent(const QModelIndex &index) const
{
if (!index.isValid() || index.internalId() == TOPLEVEL)
return {};
return createIndex(index.internalId(), 0);
}
......@@ -27,7 +27,7 @@ namespace Analyzer {
class Product;
class SchemaModel : public QAbstractTableModel
class SchemaModel : public QAbstractItemModel
{
Q_OBJECT
public:
......@@ -47,6 +47,9 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
private:
Product m_product;
};
......
......@@ -24,7 +24,7 @@
<number>0</number>
</property>
<item>
<widget class="QTableView" name="schemaView">
<widget class="QTreeView" name="schemaView">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
......
......@@ -171,6 +171,7 @@ public function productSchema($productId)
$entry = array();
$entry['name'] = $row['name'];
$entry['type'] = $row['type'];
$entry['aggregation'] = $row['aggregation'];
array_push($schema, $entry);
}
return $schema;
......@@ -192,17 +193,18 @@ public function updateProductSchema($product, $schema)
die('Cannot change data type of entry ' . $entry['name'] . '!');
$res = $this->db->exec('UPDATE product_schema SET ' .
'type = ' . $this->db->quote($entry['type']) . ' WHERE ' .
'aggregation = ' . $this->db->quote($entry['aggregation']) . ' WHERE ' .
'productId = ' . intval($product['id']) . ' AND ' .
'name = ' . $this->db->quote($entry['name'])
);
$this->checkError($res);
} else {
// insert
$res = $this->db->exec('INSERT INTO product_schema (productId, name, type) VALUES (' .
$res = $this->db->exec('INSERT INTO product_schema (productId, name, type, aggregation) VALUES (' .
intval($product['id']) . ', ' .
$this->db->quote($entry['name']) . ', ' .
$this->db->quote($entry['type']) . ')'
$this->db->quote($entry['type']) . ', ' .
$this->db->quote($entry['aggregation']) . ')'
);
$this->checkError($res);
......
......@@ -23,6 +23,11 @@
{
"version": 5,
"sql": ["CREATE TABLE product_schema (id INTEGER PRIMARY KEY AUTOINCREMENT, productId INTEGER REFERENCES products (id), name VARCHAR, type VARCHAR)" ]
},
{
"version": 6,
"sql": ["ALTER TABLE product_schema ADD COLUMN aggregation VARCHAR"]
}
]
}
......@@ -85,17 +85,26 @@ private slots:
RESTClient client;
client.connectToServer(testServer());
QVERIFY(client.isConnected());
Product newProduct;
newProduct.setName(QStringLiteral("org.kde.UserFeedback.UnitTestProduct"));
// clean up from previous failed runs, if needed
auto reply = RESTApi::deleteProduct(&client, newProduct);
waitForFinished(reply);
// list existing products
auto reply = RESTApi::getProducts(&client);
reply = RESTApi::getProducts(&client);
QVERIFY(waitForFinished(reply));
QCOMPARE(reply->error(), QNetworkReply::NoError);
const auto products = Product::fromJson(reply->readAll());
QVERIFY(!findProduct(products, QLatin1String("org.kde.UserFeedback.UnitTestProduct")).isValid());
// add new product
Product newProduct;
newProduct.setName(QStringLiteral("org.kde.UserFeedback.UnitTestProduct"));
SchemaEntry entry;
entry.setName(QStringLiteral("entry1"));
entry.setType(SchemaEntry::StringType);
entry.setAggregationType(SchemaEntry::Category);
newProduct.setSchema({entry});
QVERIFY(newProduct.isValid());
reply = RESTApi::createProduct(&client, newProduct);
QVERIFY(waitForFinished(reply));
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment