diff --git a/src/plugins/axivion/axivionoutputpane.cpp b/src/plugins/axivion/axivionoutputpane.cpp index c2443223cdd..4e8326da0b5 100644 --- a/src/plugins/axivion/axivionoutputpane.cpp +++ b/src/plugins/axivion/axivionoutputpane.cpp @@ -4,8 +4,8 @@ #include "axivionoutputpane.h" #include "axivionplugin.h" -#include "axivionresultparser.h" #include "axiviontr.h" +#include "dashboard/dto.h" #include #include @@ -18,6 +18,9 @@ #include #include +#include +#include + namespace Axivion::Internal { class DashboardWidget : public QScrollArea @@ -58,7 +61,7 @@ DashboardWidget::DashboardWidget(QWidget *parent) setWidgetResizable(true); } -static QPixmap trendIcon(int added, int removed) +static QPixmap trendIcon(qint64 added, qint64 removed) { static const QPixmap unchanged = Utils::Icons::NEXT.pixmap(); static const QPixmap increased = Utils::Icon( @@ -70,10 +73,20 @@ static QPixmap trendIcon(int added, int removed) return added < removed ? decreased : increased; } +static qint64 extract_value(const std::map &map, const QString &key) +{ + const auto search = map.find(key); + if (search == map.end()) + return 0; + const Dto::Any &value = search->second; + if (!value.isDouble()) + return 0; + return static_cast(value.getDouble()); +} + void DashboardWidget::updateUi() { - const ProjectInfo &info = AxivionPlugin::projectInfo(); - m_project->setText(info.name); + m_project->setText({}); m_loc->setText({}); m_timestamp->setText({}); QLayoutItem *child; @@ -81,60 +94,77 @@ void DashboardWidget::updateUi() delete child->widget(); delete child; } - - if (info.versions.isEmpty()) + std::shared_ptr info = AxivionPlugin::projectInfo(); + if (!info) + return; + m_project->setText(info->name); + if (info->versions.empty()) return; - const ResultVersion &last = info.versions.last(); - m_loc->setText(QString::number(last.linesOfCode)); - const QDateTime timeStamp = QDateTime::fromString(last.timeStamp, Qt::ISODate); - m_timestamp->setText(timeStamp.isValid() ? timeStamp.toString("yyyy-MM-dd HH:mm:ss") + const Dto::AnalysisVersionDto &last = info->versions.back(); + if (last.linesOfCode.has_value()) + m_loc->setText(QString::number(last.linesOfCode.value())); + const QDateTime timeStamp = QDateTime::fromString(last.date, Qt::ISODate); + m_timestamp->setText(timeStamp.isValid() ? timeStamp.toString("yyyy-MM-dd HH:mm:ss t") : Tr::tr("unknown")); - const QList &issueKinds = info.issueKinds; + const std::vector &issueKinds = info->issueKinds; auto toolTip = [issueKinds](const QString &prefix){ - for (const IssueKind &kind : issueKinds) { + for (const Dto::IssueKindInfoDto &kind : issueKinds) { if (kind.prefix == prefix) - return kind.nicePlural; + return kind.nicePluralName; } return prefix; }; - auto addValuesWidgets = [this, &toolTip](const IssueCount &issueCount, int row){ - const QString currentToolTip = toolTip(issueCount.issueKind); - QLabel *label = new QLabel(issueCount.issueKind, this); + auto addValuesWidgets = [this, &toolTip](const QString &issueKind, qint64 total, qint64 added, qint64 removed, int row) { + const QString currentToolTip = toolTip(issueKind); + QLabel *label = new QLabel(issueKind, this); label->setToolTip(currentToolTip); m_gridLayout->addWidget(label, row, 0); - label = new QLabel(QString::number(issueCount.total), this); + label = new QLabel(QString::number(total), this); label->setToolTip(currentToolTip); label->setAlignment(Qt::AlignRight); m_gridLayout->addWidget(label, row, 1); label = new QLabel(this); - label->setPixmap(trendIcon(issueCount.added, issueCount.removed)); + label->setPixmap(trendIcon(added, removed)); label->setToolTip(currentToolTip); m_gridLayout->addWidget(label, row, 2); - label = new QLabel('+' + QString::number(issueCount.added)); + label = new QLabel('+' + QString::number(added)); label->setAlignment(Qt::AlignRight); label->setToolTip(currentToolTip); m_gridLayout->addWidget(label, row, 3); label = new QLabel("/"); label->setToolTip(currentToolTip); m_gridLayout->addWidget(label, row, 4); - label = new QLabel('-' + QString::number(issueCount.removed)); + label = new QLabel('-' + QString::number(removed)); label->setAlignment(Qt::AlignRight); label->setToolTip(currentToolTip); m_gridLayout->addWidget(label, row, 5); }; - int allTotal = 0, allAdded = 0, allRemoved = 0, row = 0; - for (auto issueCount : std::as_const(last.issueCounts)) { - allTotal += issueCount.total; - allAdded += issueCount.added; - allRemoved += issueCount.removed; - addValuesWidgets(issueCount, row); - ++row; + qint64 allTotal = 0; + qint64 allAdded = 0; + qint64 allRemoved = 0; + qint64 row = 0; + // This code is overly complex because of a heedlessness in the + // Axivion Dashboard API definition. Other Axivion IDE plugins do + // not use the issue counts, thus the QtCreator Axivion Plugin + // is going to stop using them, too. + if (last.issueCounts.isMap()) { + for (const auto &issueCount : last.issueCounts.getMap()) { + if (issueCount.second.isMap()) { + const auto &counts = issueCount.second.getMap(); + qint64 total = extract_value(counts, QStringLiteral(u"Total")); + allTotal += total; + qint64 added = extract_value(counts, QStringLiteral(u"Added")); + allAdded += added; + qint64 removed = extract_value(counts, QStringLiteral(u"Removed")); + allRemoved += removed; + addValuesWidgets(issueCount.first, total, added, removed, row); + ++row; + } + } } - - const IssueCount total{{}, Tr::tr("Total:"), allTotal, allAdded, allRemoved}; - addValuesWidgets(total, row); + addValuesWidgets(Tr::tr("Total:"), allTotal, allAdded, allRemoved, row); } AxivionOutputPane::AxivionOutputPane(QObject *parent) diff --git a/src/plugins/axivion/axivionplugin.cpp b/src/plugins/axivion/axivionplugin.cpp index ef253e9c0d5..3d89c8ba957 100644 --- a/src/plugins/axivion/axivionplugin.cpp +++ b/src/plugins/axivion/axivionplugin.cpp @@ -8,6 +8,7 @@ #include "axivionquery.h" #include "axivionresultparser.h" #include "axiviontr.h" +#include "dashboard/dto.h" #include #include @@ -24,12 +25,16 @@ #include #include +#include #include #include #include #include +#include +#include + constexpr char AxivionTextMarkId[] = "AxivionTextMark"; namespace Axivion::Internal { @@ -39,7 +44,7 @@ class AxivionPluginPrivate : public QObject public: void onStartupProjectChanged(); void fetchProjectInfo(const QString &projectName); - void handleProjectInfo(const ProjectInfo &info); + void handleProjectInfo(const QByteArray &result); void handleOpenedDocs(ProjectExplorer::Project *project); void onDocumentOpened(Core::IDocument *doc); void onDocumentClosed(Core::IDocument * doc); @@ -48,7 +53,7 @@ public: void fetchRuleInfo(const QString &id); AxivionOutputPane m_axivionOutputPane; - ProjectInfo m_currentProjectInfo; + std::shared_ptr m_currentProjectInfo; bool m_runningQuery = false; }; @@ -113,7 +118,7 @@ void AxivionPlugin::fetchProjectInfo(const QString &projectName) dd->fetchProjectInfo(projectName); } -ProjectInfo AxivionPlugin::projectInfo() +std::shared_ptr AxivionPlugin::projectInfo() { QTC_ASSERT(dd, return {}); return dd->m_currentProjectInfo; @@ -124,7 +129,7 @@ void AxivionPluginPrivate::onStartupProjectChanged() ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project) { clearAllMarks(); - m_currentProjectInfo = ProjectInfo(); + m_currentProjectInfo = {}; m_axivionOutputPane.updateDashboard(); return; } @@ -141,7 +146,7 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName) } clearAllMarks(); if (projectName.isEmpty()) { - m_currentProjectInfo = ProjectInfo(); + m_currentProjectInfo = {}; m_axivionOutputPane.updateDashboard(); return; } @@ -150,7 +155,7 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName) AxivionQuery query(AxivionQuery::ProjectInfo, {projectName}); AxivionQueryRunner *runner = new AxivionQueryRunner(query, this); connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){ - handleProjectInfo(ResultParser::parseProjectInfo(result)); + handleProjectInfo(result); }); connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); }); runner->start(); @@ -196,20 +201,16 @@ void AxivionPluginPrivate::clearAllMarks() onDocumentClosed(doc); } -void AxivionPluginPrivate::handleProjectInfo(const ProjectInfo &info) +void AxivionPluginPrivate::handleProjectInfo(const QByteArray &result) { + Utils::expected_str raw_info = ResultParser::parseProjectInfo(result); m_runningQuery = false; - if (!info.error.isEmpty()) { - Core::MessageManager::writeFlashing("Axivion: " + info.error); + if (!raw_info) { + Core::MessageManager::writeFlashing(QStringLiteral(u"Axivion: ") + raw_info.error()); return; } - - m_currentProjectInfo = info; + m_currentProjectInfo = std::make_shared(std::move(raw_info.value())); m_axivionOutputPane.updateDashboard(); - - if (m_currentProjectInfo.name.isEmpty()) - return; - // handle already opened documents if (auto buildSystem = ProjectExplorer::ProjectManager::startupBuildSystem(); !buildSystem || !buildSystem->isParsing()) { @@ -223,7 +224,7 @@ void AxivionPluginPrivate::handleProjectInfo(const ProjectInfo &info) void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc) { - if (m_currentProjectInfo.name.isEmpty()) // we do not have a project info (yet) + if (!m_currentProjectInfo) // we do not have a project info (yet) return; ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); @@ -232,7 +233,7 @@ void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc) Utils::FilePath relative = doc->filePath().relativeChildPath(project->projectDirectory()); // for now only style violations - AxivionQuery query(AxivionQuery::IssuesForFileList, {m_currentProjectInfo.name, "SV", + AxivionQuery query(AxivionQuery::IssuesForFileList, {m_currentProjectInfo->name, "SV", relative.path() } ); AxivionQueryRunner *runner = new AxivionQueryRunner(query, this); connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){ diff --git a/src/plugins/axivion/axivionplugin.h b/src/plugins/axivion/axivionplugin.h index 9d71366fc45..ff235d393ce 100644 --- a/src/plugins/axivion/axivionplugin.h +++ b/src/plugins/axivion/axivionplugin.h @@ -3,8 +3,12 @@ #pragma once +#include "dashboard/dto.h" + #include +#include + namespace ProjectExplorer { class Project; } namespace Axivion::Internal { @@ -22,7 +26,7 @@ public: ~AxivionPlugin() final; static void fetchProjectInfo(const QString &projectName); - static ProjectInfo projectInfo(); + static std::shared_ptr projectInfo(); private: void initialize() final; diff --git a/src/plugins/axivion/axivionresultparser.cpp b/src/plugins/axivion/axivionresultparser.cpp index 0d8bb27b7db..81ae9787170 100644 --- a/src/plugins/axivion/axivionresultparser.cpp +++ b/src/plugins/axivion/axivionresultparser.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "axivionresultparser.h" +#include "dashboard/dto.h" #include @@ -10,6 +11,7 @@ #include #include +#include #include namespace Axivion::Internal { @@ -79,124 +81,6 @@ static std::pair prehandleHeaderAndBody(const QByteAr return {result, doc}; } -static User::UserType userTypeForString(const QString &type) -{ - if (type == "DASHBOARD_USER") - return User::Dashboard; - if (type == "VIRTUAL_USER") - return User::Virtual; - return User::Unknown; -} - -static User userFromJson(const QJsonObject &object) -{ - User result; - if (object.isEmpty()) { - result.error = "Not a user object."; - return result; - } - result.name = object.value("name").toString(); - result.displayName = object.value("displayName").toString(); - result.type = userTypeForString(object.value("type").toString()); - return result; -} - -static QList usersFromJson(const QJsonArray &array) -{ - QList result; - for (const QJsonValue &value : array) { - User user = userFromJson(value.toObject()); - if (!user.error.isEmpty()) // add this error to result.error? - continue; - result.append(user); - } - return result; -} - -static IssueCount issueCountFromJson(const QJsonObject &object) -{ - IssueCount result; - if (object.isEmpty()) { - result.error = "Not an issue count object."; - return result; - } - result.added = object.value("Added").toInt(); - result.removed = object.value("Removed").toInt(); - result.total = object.value("Total").toInt(); - return result; -} - -static QList issueCountsFromJson(const QJsonObject &object) -{ - QList result; - - const QStringList keys = object.keys(); - for (const QString &k : keys) { - IssueCount issue = issueCountFromJson(object.value(k).toObject()); - if (!issue.error.isEmpty()) // add this error to result.error? - continue; - issue.issueKind = k; - result.append(issue); - } - return result; -} - -static ResultVersion versionFromJson(const QJsonObject &object) -{ - ResultVersion result; - if (object.isEmpty()) { - result.error = "Not a version object."; - return result; - } - const QJsonValue issuesValue = object.value("issueCounts"); - if (!issuesValue.isObject()) { - result.error = "Not an object (issueCounts)."; - return result; - } - result.issueCounts = issueCountsFromJson(issuesValue.toObject()); - result.timeStamp = object.value("date").toString(); - result.name = object.value("name").toString(); - result.linesOfCode = object.value("linesOfCode").toInt(); - return result; -} - -static QList versionsFromJson(const QJsonArray &array) -{ - QList result; - for (const QJsonValue &value : array) { - ResultVersion version = versionFromJson(value.toObject()); - if (!version.error.isEmpty()) // add this error to result.error? - continue; - result.append(version); - } - return result; -} - -static IssueKind issueKindFromJson(const QJsonObject &object) -{ - IssueKind result; - if (object.isEmpty()) { - result.error = "Not an issue kind object."; - return result; - } - result.prefix = object.value("prefix").toString(); - result.niceSingular = object.value("niceSingularName").toString(); - result.nicePlural = object.value("nicePluralName").toString(); - return result; -} - -static QList issueKindsFromJson(const QJsonArray &array) -{ - QList result; - for (const QJsonValue &value : array) { - IssueKind kind = issueKindFromJson(value.toObject()); - if (!kind.error.isEmpty()) // add this error to result.error? - continue; - result.append(kind); - } - return result; -} - namespace ResultParser { DashboardInfo parseDashboardInfo(const QByteArray &input) @@ -236,41 +120,18 @@ DashboardInfo parseDashboardInfo(const QByteArray &input) return result; } -ProjectInfo parseProjectInfo(const QByteArray &input) +Utils::expected_str parseProjectInfo(const QByteArray &input) { - ProjectInfo result; - auto [header, body] = splitHeaderAndBody(input); auto [error, doc] = prehandleHeaderAndBody(header, body); - if (!error.error.isEmpty()) { - result.error = error.error; - return result; + if (!error.error.isEmpty()) + return tl::make_unexpected(std::move(error.error)); + try + { + return { Dto::ProjectInfoDto::deserialize(body) }; + } catch (const Dto::invalid_dto_exception &e) { + return tl::make_unexpected(QString::fromUtf8(e.what())); } - - const QJsonObject object = doc.object(); - result.name = object.value("name").toString(); - - const QJsonValue usersValue = object.value("users"); - if (!usersValue.isArray()) { - result.error = "Malformed json response (users)."; - return result; - } - result.users = usersFromJson(usersValue.toArray()); - - const QJsonValue versionsValue = object.value("versions"); - if (!versionsValue.isArray()) { - result.error = "Malformed json response (versions)."; - return result; - } - result.versions = versionsFromJson(versionsValue.toArray()); - - const QJsonValue issueKindsValue = object.value("issueKinds"); - if (!issueKindsValue.isArray()) { - result.error = "Malformed json response (issueKinds)."; - return result; - } - result.issueKinds = issueKindsFromJson(issueKindsValue.toArray()); - return result; } static QRegularExpression issueCsvLineRegex(const QByteArray &firstCsvLine) diff --git a/src/plugins/axivion/axivionresultparser.h b/src/plugins/axivion/axivionresultparser.h index 84c5a6b8ca8..d8d4a39a72c 100644 --- a/src/plugins/axivion/axivionresultparser.h +++ b/src/plugins/axivion/axivionresultparser.h @@ -3,6 +3,10 @@ #pragma once +#include "dashboard/dto.h" + +#include + #include namespace Axivion::Internal { @@ -27,49 +31,6 @@ public: QList projects; }; -class User : public BaseResult -{ -public: - QString name; - QString displayName; - enum UserType { Dashboard, Virtual, Unknown } type; -}; - -class IssueKind : public BaseResult -{ -public: - QString prefix; - QString niceSingular; - QString nicePlural; -}; - -class IssueCount : public BaseResult -{ -public: - QString issueKind; - int total = 0; - int added = 0; - int removed = 0; -}; - -class ResultVersion : public BaseResult -{ -public: - QString name; - QString timeStamp; - QList issueCounts; - int linesOfCode = 0; -}; - -class ProjectInfo : public BaseResult -{ -public: - QString name; - QList users; - QList versions; - QList issueKinds; -}; - class ShortIssue : public BaseResult { public: @@ -92,7 +53,7 @@ public: namespace ResultParser { DashboardInfo parseDashboardInfo(const QByteArray &input); -ProjectInfo parseProjectInfo(const QByteArray &input); +Utils::expected_str parseProjectInfo(const QByteArray &input); IssuesList parseIssuesList(const QByteArray &input); QString parseRuleInfo(const QByteArray &input);