diff --git a/analyzer/CMakeLists.txt b/analyzer/CMakeLists.txt index 63bff24a19ae185e6395562b42aca2a0ad89a568..236ae878d4230f0215124bddebe298fd94d71807 100644 --- a/analyzer/CMakeLists.txt +++ b/analyzer/CMakeLists.txt @@ -29,6 +29,7 @@ set(analyzer_lib_srcs add_library(UserFeedbackAnalyzer STATIC ${analyzer_lib_srcs}) target_link_libraries(UserFeedbackAnalyzer Qt5::Network) target_include_directories(UserFeedbackAnalyzer PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};>") +target_compile_features(UserFeedbackAnalyzer PRIVATE cxx_generic_lambdas) set(analyzer_srcs aggregateddatamodel.cpp diff --git a/analyzer/core/aggregation.cpp b/analyzer/core/aggregation.cpp index bb4b3b680a1db53baa5b1045ad19ecf1a6e893db..6769fb0d552f0d304aa23304328dc073cb6c8039 100644 --- a/analyzer/core/aggregation.cpp +++ b/analyzer/core/aggregation.cpp @@ -16,9 +16,24 @@ */ #include "aggregation.h" +#include "util.h" + +#include <QJsonArray> +#include <QJsonObject> using namespace UserFeedback::Analyzer; +static const struct { + Aggregation::Type type; + const char *name; +} aggregation_types_table[] { + { Aggregation::None, "none" }, + { Aggregation::Category, "category" }, + { Aggregation::RatioSet, "ratio_set" }, + { Aggregation::Numeric, "numeric" }, + { Aggregation::XY, "xy" } +}; + Aggregation::Aggregation() = default; Aggregation::~Aggregation() = default; @@ -41,3 +56,31 @@ void Aggregation::setElements(const QVector<AggregationElement>& elements) { m_elements = elements; } + +QJsonObject Aggregation::toJsonObject() const +{ + QJsonObject obj; + obj.insert(QStringLiteral("type"), QLatin1String(aggregation_types_table[m_type].name)); + QJsonArray elems; + for (const auto &e : m_elements) + elems.push_back(e.toJsonObject()); + obj.insert(QStringLiteral("elements"), elems); + return obj; +} + +QVector<Aggregation> Aggregation::fromJson(const Product &product, const QJsonArray& a) +{ + QVector<Aggregation> aggrs; + aggrs.reserve(a.size()); + for (const auto &v : a) { + if (!v.isObject()) + continue; + const auto obj = v.toObject(); + + Aggregation aggr; + aggr.setType(Util::stringToEnum<Aggregation::Type>(obj.value(QLatin1String("type")).toString(), aggregation_types_table)); + aggr.setElements(AggregationElement::fromJson(product, obj.value(QLatin1String("elements")).toArray())); + aggrs.push_back(aggr); + } + return aggrs; +} diff --git a/analyzer/core/aggregation.h b/analyzer/core/aggregation.h index b2332a594de90f63b93ce8f06f32a6e3941f62b6..61f6fef0833c0d48bfc88bce97bc03ec57f9d309 100644 --- a/analyzer/core/aggregation.h +++ b/analyzer/core/aggregation.h @@ -22,9 +22,14 @@ #include <QTypeInfo> +class QJsonArray; +class QJsonObject; + namespace UserFeedback { namespace Analyzer { +class Product; + class Aggregation { Q_GADGET @@ -47,6 +52,9 @@ public: QVector<AggregationElement> elements() const; void setElements(const QVector<AggregationElement> &elements); + QJsonObject toJsonObject() const; + static QVector<Aggregation> fromJson(const Product &product, const QJsonArray &a); + private: Type m_type = None; QVector<AggregationElement> m_elements; diff --git a/analyzer/core/aggregationelement.cpp b/analyzer/core/aggregationelement.cpp index 88304bfa9b37ddc43c04c72f36bd01c57055fd37..c7f80c6c0252e980587cf033c1f363955bce2ab2 100644 --- a/analyzer/core/aggregationelement.cpp +++ b/analyzer/core/aggregationelement.cpp @@ -16,9 +16,22 @@ */ #include "aggregationelement.h" +#include "product.h" +#include "util.h" + +#include <QJsonArray> +#include <QJsonObject> using namespace UserFeedback::Analyzer; +static const struct { + AggregationElement::Type type; + const char *name; +} aggregation_element_types_table[] { + { AggregationElement::Value, "value" }, + { AggregationElement::Size, "size" } +}; + AggregationElement::AggregationElement() = default; AggregationElement::~AggregationElement() = default; @@ -76,3 +89,44 @@ bool AggregationElement::operator==(const AggregationElement &other) const } Q_UNREACHABLE(); } + +QJsonObject AggregationElement::toJsonObject() const +{ + QJsonObject obj; + obj.insert(QStringLiteral("type"), QLatin1String(aggregation_element_types_table[m_type].name)); + switch (m_type) { + case Value: + obj.insert(QStringLiteral("schemaEntry"), m_entry.name()); + obj.insert(QStringLiteral("schemaEntryElement"), m_element.name()); + break; + case Size: + obj.insert(QStringLiteral("schemaEntry"), m_entry.name()); + break; + } + return obj; +} + +QVector<AggregationElement> AggregationElement::fromJson(const Product &product, const QJsonArray& a) +{ + QVector<AggregationElement> elems; + elems.reserve(a.size()); + for (const auto &v : a) { + if (!v.isObject()) + continue; + const auto obj = v.toObject(); + + AggregationElement e; + e.setType(Util::stringToEnum<AggregationElement::Type>(obj.value(QLatin1String("type")).toString(), aggregation_element_types_table)); + switch (e.type()) { + case Value: + e.setSchemaEntry(product.schemaEntry(obj.value(QLatin1String("schemaEntry")).toString())); + e.setSchemaEntryElement(e.schemaEntry().element(obj.value(QLatin1String("schemaEntryElement")).toString())); + break; + case Size: + e.setSchemaEntry(product.schemaEntry(obj.value(QLatin1String("schemaEntry")).toString())); + break; + } + elems.push_back(e); + } + return elems; +} diff --git a/analyzer/core/aggregationelement.h b/analyzer/core/aggregationelement.h index f037d10416a3ec8ce1fe6de0df15a7621d5b17b5..8889ad8d3e9937f8fc51d3517f41490fc6410e0c 100644 --- a/analyzer/core/aggregationelement.h +++ b/analyzer/core/aggregationelement.h @@ -21,9 +21,14 @@ #include "schemaentry.h" #include "schemaentryelement.h" +class QJsonArray; +class QJsonObject; + namespace UserFeedback { namespace Analyzer { +class Product; + class AggregationElement { public: @@ -47,6 +52,9 @@ public: QString displayString() const; + QJsonObject toJsonObject() const; + static QVector<AggregationElement> fromJson(const Product &product, const QJsonArray &a); + private: SchemaEntry m_entry; SchemaEntryElement m_element; diff --git a/analyzer/core/product.cpp b/analyzer/core/product.cpp index d3fae729a65e1f78d68c12c9fcb9d8e57dcceb08..3f72130b7d2d181b89cb635aad02519bd8239e2c 100644 --- a/analyzer/core/product.cpp +++ b/analyzer/core/product.cpp @@ -64,6 +64,16 @@ QVector<SchemaEntry> Product::schema() const return d->schema; } +SchemaEntry Product::schemaEntry(const QString& name) const +{ + const auto it = std::find_if(d->schema.cbegin(), d->schema.cend(), [name](const auto &entry) { + return entry.name() == name; + }); + if (it == d->schema.cend()) + return {}; + return *it; +} + void Product::setSchema(const QVector<SchemaEntry> &schema) { d->schema = schema; @@ -83,10 +93,19 @@ QByteArray Product::toJson() const { QJsonObject obj; obj.insert(QStringLiteral("name"), name()); - QJsonArray schema; - foreach (const auto &s, d->schema) - schema.push_back(s.toJsonObject()); - obj.insert(QStringLiteral("schema"), schema); + { + QJsonArray schema; + foreach (const auto &s, d->schema) + schema.push_back(s.toJsonObject()); + obj.insert(QStringLiteral("schema"), schema); + } + + { + QJsonArray aggrs; + foreach (const auto &a, d->aggregations) + aggrs.push_back(a.toJsonObject()); + obj.insert(QStringLiteral("aggregation"), aggrs); + } QJsonDocument doc(obj); return doc.toJson(); } @@ -96,21 +115,24 @@ static Product productFromJsonObject(const QJsonObject &obj) Product product; product.setName(obj.value(QStringLiteral("name")).toString()); product.setSchema(SchemaEntry::fromJson(obj.value(QStringLiteral("schema")).toArray())); + product.setAggregations(Aggregation::fromJson(product, obj.value(QLatin1String("aggregation")).toArray())); // ### temporary HACK - QVector<Aggregation> aggrs; - for (const auto &entry : product.schema()) { - for (const auto &elem : entry.elements()) { - Aggregation aggr; - aggr.setType(Aggregation::Category); - AggregationElement e; - e.setSchemaEntry(entry); - e.setSchemaEntryElement(elem); - aggr.setElements({e}); - aggrs.push_back(aggr); + if (product.aggregations().isEmpty()) { + QVector<Aggregation> aggrs; + for (const auto &entry : product.schema()) { + for (const auto &elem : entry.elements()) { + Aggregation aggr; + aggr.setType(Aggregation::Category); + AggregationElement e; + e.setSchemaEntry(entry); + e.setSchemaEntryElement(elem); + aggr.setElements({e}); + aggrs.push_back(aggr); + } } + product.setAggregations(aggrs); } - product.setAggregations(aggrs); return product; } diff --git a/analyzer/core/product.h b/analyzer/core/product.h index 209cd46003c26a149fad12ceb687647f6deb947a..be76595f43cd72c20d7ed443ab97d4d0e5d3342c 100644 --- a/analyzer/core/product.h +++ b/analyzer/core/product.h @@ -48,6 +48,7 @@ public: QVector<SchemaEntry> schema() const; void setSchema(const QVector<SchemaEntry>& schema); + SchemaEntry schemaEntry(const QString &name) const; QVector<Aggregation> aggregations() const; void setAggregations(const QVector<Aggregation> &aggregations); diff --git a/analyzer/core/schemaentry.cpp b/analyzer/core/schemaentry.cpp index 9c00e8091047f56fe7a907654f72b7eb615ed0d7..bb1916cd250046b1c82f47e62e35968383087288 100644 --- a/analyzer/core/schemaentry.cpp +++ b/analyzer/core/schemaentry.cpp @@ -127,6 +127,16 @@ void SchemaEntry::setElements(const QVector<SchemaEntryElement> &elements) d->elements = elements; } +SchemaEntryElement SchemaEntry::element(const QString& name) const +{ + const auto it = std::find_if(d->elements.cbegin(), d->elements.cend(), [name](const auto &entry) { + return entry.name() == name; + }); + if (it == d->elements.cend()) + return {}; + return *it; +} + QJsonObject SchemaEntry::toJsonObject() const { QJsonObject obj; diff --git a/analyzer/core/schemaentry.h b/analyzer/core/schemaentry.h index d01b4a445d378d0ce734bbb53b2e71072d589da2..80917b2f97f793c0e91b5c6c5a8984aad5419ad8 100644 --- a/analyzer/core/schemaentry.h +++ b/analyzer/core/schemaentry.h @@ -96,6 +96,7 @@ public: QVector<SchemaEntryElement> elements() const; void setElements(const QVector<SchemaEntryElement> &elements); + SchemaEntryElement element(const QString &name) const; QJsonObject toJsonObject() const; static QVector<SchemaEntry> fromJson(const QJsonArray &array); diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 8e4571a2b5d454c906292ef19e00b39aeb271ea6..edd27876a9d80947657b99a7cdc576ef8302d38f 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -12,6 +12,7 @@ function(uf_add_test _file) add_test(NAME ${_name} COMMAND ${_name}) endfunction() +uf_add_test(producttest UserFeedbackAnalyzer) uf_add_test(productapitest UserFeedbackTestUtils) uf_add_test(productmodeltest UserFeedbackTestUtils) uf_add_test(schemamodeltest UserFeedbackTestUtils) diff --git a/tests/auto/producttest.cpp b/tests/auto/producttest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..768dd5d97353799ee5b78f7aedf3e984c137d910 --- /dev/null +++ b/tests/auto/producttest.cpp @@ -0,0 +1,98 @@ +/* + 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 <analyzer/core/aggregation.h> +#include <analyzer/core/product.h> + +#include <3rdparty/qt/modeltest.h> + +#include <QDebug> +#include <QtTest/qtest.h> +#include <QObject> +#include <QStandardPaths> + +using namespace UserFeedback::Analyzer; + +class ProductTest : public QObject +{ + Q_OBJECT +private slots: + void initTestCase() + { + Q_INIT_RESOURCE(schemaentrytemplates); + QStandardPaths::setTestModeEnabled(true); + } + + void testFromJson() + { + const auto ps = Product::fromJson(R"({ + "name": "org.kde.TestProduct", + "schema": [{ + "name": "entry1", + "type": "scalar", + "elements": [{ "name": "elem11", "type": "string" }] + }, { + "name": "entry2", + "type": "list", + "elements": [{ "name": "elem21", "type": "number" }] + }], + "aggregation": [{ + "type": "ratio_set", + "elements": [{ "type": "value", "schemaEntry": "entry1", "schemaEntryElement": "elem11" }] + }, { + "type": "numeric", + "elements": [{ "type": "size", "schemaEntry": "entry2" }] + }] + })"); + + QCOMPARE(ps.size(), 1); + + const auto p = ps.at(0); + QCOMPARE(p.name(), QLatin1String("org.kde.TestProduct")); + + QCOMPARE(p.schema().size(), 2); + + const auto aggrs = p.aggregations(); + QCOMPARE(aggrs.size(), 2); + { + const auto a1 = aggrs.at(0); + QCOMPARE(a1.type(), Aggregation::RatioSet); + const auto a1elems = a1.elements(); + QCOMPARE(a1elems.size(), 1); + QCOMPARE(a1elems.at(0).type(), AggregationElement::Value); + QCOMPARE(a1elems.at(0).schemaEntry().name(), QLatin1String("entry1")); + QCOMPARE(a1elems.at(0).schemaEntry().dataType(), SchemaEntry::Scalar); + QCOMPARE(a1elems.at(0).schemaEntryElement().name(), QLatin1String("elem11")); + } + + { + const auto a2 = aggrs.at(1); + QCOMPARE(a2.type(), Aggregation::Numeric); + const auto a2elems = a2.elements(); + QCOMPARE(a2elems.size(), 1); + QCOMPARE(a2elems.at(0).type(), AggregationElement::Size); + QCOMPARE(a2elems.at(0).schemaEntry().name(), QLatin1String("entry2")); + QCOMPARE(a2elems.at(0).schemaEntry().dataType(), SchemaEntry::List); + QVERIFY(a2elems.at(0).schemaEntryElement().name().isEmpty()); + } + } + +}; + +QTEST_MAIN(ProductTest) + +#include "producttest.moc"