2022-12-12 16:45:31 +01:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
2023-05-24 10:27:35 +02:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2022-11-28 09:48:11 +01:00
|
|
|
|
|
|
|
|
#include "axivionoutputpane.h"
|
|
|
|
|
|
2022-12-14 13:53:00 +01:00
|
|
|
#include "axivionplugin.h"
|
2022-11-28 09:48:11 +01:00
|
|
|
#include "axiviontr.h"
|
2023-07-25 18:48:18 +02:00
|
|
|
#include "dashboard/dto.h"
|
2022-11-28 09:48:11 +01:00
|
|
|
|
2024-01-25 15:39:32 +01:00
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
|
|
|
|
|
|
|
|
#include <projectexplorer/project.h>
|
|
|
|
|
#include <projectexplorer/projectmanager.h>
|
|
|
|
|
|
2024-01-27 13:33:45 +01:00
|
|
|
#include <solutions/tasking/tasktreerunner.h>
|
|
|
|
|
|
2024-01-25 15:39:32 +01:00
|
|
|
#include <utils/link.h>
|
2022-11-28 09:48:11 +01:00
|
|
|
#include <utils/qtcassert.h>
|
2024-01-19 15:06:34 +01:00
|
|
|
#include <utils/treemodel.h>
|
|
|
|
|
#include <utils/basetreeview.h>
|
2023-01-13 15:43:01 +01:00
|
|
|
#include <utils/utilsicons.h>
|
2022-11-28 09:48:11 +01:00
|
|
|
|
2024-01-19 13:47:03 +01:00
|
|
|
#include <QComboBox>
|
2022-12-14 13:53:00 +01:00
|
|
|
#include <QFormLayout>
|
2023-06-19 10:22:03 +02:00
|
|
|
#include <QGridLayout>
|
2024-01-19 13:47:03 +01:00
|
|
|
#include <QHeaderView>
|
2022-12-14 13:53:00 +01:00
|
|
|
#include <QLabel>
|
2024-01-19 13:47:03 +01:00
|
|
|
#include <QPushButton>
|
2023-01-11 14:44:27 +01:00
|
|
|
#include <QScrollArea>
|
2024-01-26 14:58:49 +01:00
|
|
|
#include <QScrollBar>
|
2022-11-28 09:48:11 +01:00
|
|
|
#include <QStackedWidget>
|
2023-01-13 14:38:39 +01:00
|
|
|
#include <QTextBrowser>
|
2023-01-13 15:43:01 +01:00
|
|
|
#include <QToolButton>
|
2022-11-28 09:48:11 +01:00
|
|
|
|
2023-07-25 18:48:18 +02:00
|
|
|
#include <map>
|
2024-01-26 09:01:11 +01:00
|
|
|
|
2024-01-27 13:33:45 +01:00
|
|
|
using namespace Tasking;
|
2024-01-26 09:01:11 +01:00
|
|
|
using namespace Utils;
|
2023-07-25 18:48:18 +02:00
|
|
|
|
2022-11-28 09:48:11 +01:00
|
|
|
namespace Axivion::Internal {
|
|
|
|
|
|
2023-01-11 14:44:27 +01:00
|
|
|
class DashboardWidget : public QScrollArea
|
2022-12-14 13:53:00 +01:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit DashboardWidget(QWidget *parent = nullptr);
|
|
|
|
|
void updateUi();
|
|
|
|
|
bool hasProject() const { return !m_project->text().isEmpty(); }
|
|
|
|
|
private:
|
|
|
|
|
QLabel *m_project = nullptr;
|
|
|
|
|
QLabel *m_loc = nullptr;
|
2023-06-16 10:11:41 +02:00
|
|
|
QLabel *m_timestamp = nullptr;
|
2023-06-19 10:22:03 +02:00
|
|
|
QGridLayout *m_gridLayout = nullptr;
|
2022-12-14 13:53:00 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
DashboardWidget::DashboardWidget(QWidget *parent)
|
2023-01-11 14:44:27 +01:00
|
|
|
: QScrollArea(parent)
|
2022-12-14 13:53:00 +01:00
|
|
|
{
|
2023-01-11 14:44:27 +01:00
|
|
|
QWidget *widget = new QWidget(this);
|
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(widget);
|
2022-12-14 13:53:00 +01:00
|
|
|
QFormLayout *projectLayout = new QFormLayout;
|
|
|
|
|
m_project = new QLabel(this);
|
|
|
|
|
projectLayout->addRow(Tr::tr("Project:"), m_project);
|
|
|
|
|
m_loc = new QLabel(this);
|
2023-06-19 10:55:58 +02:00
|
|
|
projectLayout->addRow(Tr::tr("Lines of code:"), m_loc);
|
2023-06-16 10:11:41 +02:00
|
|
|
m_timestamp = new QLabel(this);
|
|
|
|
|
projectLayout->addRow(Tr::tr("Analysis timestamp:"), m_timestamp);
|
2022-12-14 13:53:00 +01:00
|
|
|
layout->addLayout(projectLayout);
|
2023-06-16 10:11:41 +02:00
|
|
|
layout->addSpacing(10);
|
2023-06-19 10:22:03 +02:00
|
|
|
auto row = new QHBoxLayout;
|
|
|
|
|
m_gridLayout = new QGridLayout;
|
|
|
|
|
row->addLayout(m_gridLayout);
|
|
|
|
|
row->addStretch(1);
|
|
|
|
|
layout->addLayout(row);
|
2023-06-16 10:11:41 +02:00
|
|
|
layout->addStretch(1);
|
2023-01-11 14:44:27 +01:00
|
|
|
setWidget(widget);
|
|
|
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
|
|
|
setWidgetResizable(true);
|
2022-12-14 13:53:00 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-25 18:48:18 +02:00
|
|
|
static QPixmap trendIcon(qint64 added, qint64 removed)
|
2023-06-19 10:22:03 +02:00
|
|
|
{
|
2024-01-26 09:01:11 +01:00
|
|
|
static const QPixmap unchanged = Icons::NEXT.pixmap();
|
|
|
|
|
static const QPixmap increased = Icon(
|
|
|
|
|
{ {":/utils/images/arrowup.png", Theme::IconsErrorColor} }).pixmap();
|
|
|
|
|
static const QPixmap decreased = Icon(
|
|
|
|
|
{ {":/utils/images/arrowdown.png", Theme::IconsRunColor} }).pixmap();
|
2023-06-19 10:22:03 +02:00
|
|
|
if (added == removed)
|
|
|
|
|
return unchanged;
|
|
|
|
|
return added < removed ? decreased : increased;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-25 18:48:18 +02:00
|
|
|
static qint64 extract_value(const std::map<QString, Dto::Any> &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<qint64>(value.getDouble());
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 13:53:00 +01:00
|
|
|
void DashboardWidget::updateUi()
|
|
|
|
|
{
|
2023-07-25 18:48:18 +02:00
|
|
|
m_project->setText({});
|
2022-12-14 13:53:00 +01:00
|
|
|
m_loc->setText({});
|
2023-06-16 10:11:41 +02:00
|
|
|
m_timestamp->setText({});
|
2023-06-19 10:22:03 +02:00
|
|
|
QLayoutItem *child;
|
|
|
|
|
while ((child = m_gridLayout->takeAt(0)) != nullptr) {
|
|
|
|
|
delete child->widget();
|
|
|
|
|
delete child;
|
|
|
|
|
}
|
2023-11-24 12:29:17 +01:00
|
|
|
std::optional<Dto::ProjectInfoDto> projectInfo = Internal::projectInfo();
|
2023-09-15 14:38:48 +02:00
|
|
|
if (!projectInfo)
|
2023-07-25 18:48:18 +02:00
|
|
|
return;
|
2023-11-24 12:29:17 +01:00
|
|
|
const Dto::ProjectInfoDto &info = *projectInfo;
|
2023-09-15 14:38:48 +02:00
|
|
|
m_project->setText(info.name);
|
|
|
|
|
if (info.versions.empty())
|
2022-12-14 13:53:00 +01:00
|
|
|
return;
|
|
|
|
|
|
2023-09-15 14:38:48 +02:00
|
|
|
const Dto::AnalysisVersionDto &last = info.versions.back();
|
2023-07-25 18:48:18 +02:00
|
|
|
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")
|
2023-06-16 10:11:41 +02:00
|
|
|
: Tr::tr("unknown"));
|
2022-12-14 13:53:00 +01:00
|
|
|
|
2023-09-15 14:38:48 +02:00
|
|
|
const std::vector<Dto::IssueKindInfoDto> &issueKinds = info.issueKinds;
|
2022-12-14 13:53:00 +01:00
|
|
|
auto toolTip = [issueKinds](const QString &prefix){
|
2023-07-25 18:48:18 +02:00
|
|
|
for (const Dto::IssueKindInfoDto &kind : issueKinds) {
|
2022-12-14 13:53:00 +01:00
|
|
|
if (kind.prefix == prefix)
|
2023-07-25 18:48:18 +02:00
|
|
|
return kind.nicePluralName;
|
2022-12-14 13:53:00 +01:00
|
|
|
}
|
2023-06-19 10:22:03 +02:00
|
|
|
return prefix;
|
2022-12-14 13:53:00 +01:00
|
|
|
};
|
2023-07-25 18:48:18 +02:00
|
|
|
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);
|
2023-06-19 10:22:03 +02:00
|
|
|
label->setToolTip(currentToolTip);
|
|
|
|
|
m_gridLayout->addWidget(label, row, 0);
|
2023-07-25 18:48:18 +02:00
|
|
|
label = new QLabel(QString::number(total), this);
|
2023-06-19 10:22:03 +02:00
|
|
|
label->setToolTip(currentToolTip);
|
|
|
|
|
label->setAlignment(Qt::AlignRight);
|
|
|
|
|
m_gridLayout->addWidget(label, row, 1);
|
|
|
|
|
label = new QLabel(this);
|
2023-07-25 18:48:18 +02:00
|
|
|
label->setPixmap(trendIcon(added, removed));
|
2023-06-19 10:22:03 +02:00
|
|
|
label->setToolTip(currentToolTip);
|
|
|
|
|
m_gridLayout->addWidget(label, row, 2);
|
2023-07-25 18:48:18 +02:00
|
|
|
label = new QLabel('+' + QString::number(added));
|
2023-06-19 10:22:03 +02:00
|
|
|
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);
|
2023-07-25 18:48:18 +02:00
|
|
|
label = new QLabel('-' + QString::number(removed));
|
2023-06-19 10:22:03 +02:00
|
|
|
label->setAlignment(Qt::AlignRight);
|
|
|
|
|
label->setToolTip(currentToolTip);
|
|
|
|
|
m_gridLayout->addWidget(label, row, 5);
|
|
|
|
|
};
|
2023-07-25 18:48:18 +02:00
|
|
|
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()) {
|
2023-08-22 15:55:28 +02:00
|
|
|
for (const Dto::Any::MapEntry &issueCount : last.issueCounts.getMap()) {
|
2023-07-25 18:48:18 +02:00
|
|
|
if (issueCount.second.isMap()) {
|
2023-08-22 15:55:28 +02:00
|
|
|
const Dto::Any::Map &counts = issueCount.second.getMap();
|
2023-11-24 12:29:17 +01:00
|
|
|
qint64 total = extract_value(counts, QStringLiteral("Total"));
|
2023-07-25 18:48:18 +02:00
|
|
|
allTotal += total;
|
2023-11-24 12:29:17 +01:00
|
|
|
qint64 added = extract_value(counts, QStringLiteral("Added"));
|
2023-07-25 18:48:18 +02:00
|
|
|
allAdded += added;
|
2023-11-24 12:29:17 +01:00
|
|
|
qint64 removed = extract_value(counts, QStringLiteral("Removed"));
|
2023-07-25 18:48:18 +02:00
|
|
|
allRemoved += removed;
|
|
|
|
|
addValuesWidgets(issueCount.first, total, added, removed, row);
|
|
|
|
|
++row;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-14 13:53:00 +01:00
|
|
|
}
|
2023-07-25 18:48:18 +02:00
|
|
|
addValuesWidgets(Tr::tr("Total:"), allTotal, allAdded, allRemoved, row);
|
2022-12-14 13:53:00 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-26 09:01:11 +01:00
|
|
|
class IssueTreeItem final : public StaticTreeItem
|
2024-01-25 15:39:32 +01:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
IssueTreeItem(const QStringList &data, const QStringList &toolTips)
|
2024-01-26 09:01:11 +01:00
|
|
|
: StaticTreeItem(data, toolTips)
|
2024-01-25 15:39:32 +01:00
|
|
|
{}
|
|
|
|
|
|
2024-01-26 09:01:11 +01:00
|
|
|
void setLinks(const Links &links) { m_links = links; }
|
2024-01-25 15:39:32 +01:00
|
|
|
|
2024-01-26 09:01:11 +01:00
|
|
|
bool setData(int column, const QVariant &value, int role) final
|
2024-01-25 15:39:32 +01:00
|
|
|
{
|
2024-01-26 09:01:11 +01:00
|
|
|
if (role == BaseTreeView::ItemActivatedRole && !m_links.isEmpty()) {
|
2024-01-25 15:39:32 +01:00
|
|
|
// TODO for now only simple - just the first..
|
2024-01-26 09:01:11 +01:00
|
|
|
Link link = m_links.first();
|
2024-01-26 08:59:06 +01:00
|
|
|
ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
|
2024-01-26 09:01:11 +01:00
|
|
|
FilePath baseDir = project ? project->projectDirectory() : FilePath{};
|
2024-01-26 08:59:06 +01:00
|
|
|
link.targetFilePath = baseDir.resolvePath(link.targetFilePath);
|
2024-01-25 15:39:32 +01:00
|
|
|
if (link.targetFilePath.exists())
|
|
|
|
|
Core::EditorManager::openEditorAt(link);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-01-26 09:01:11 +01:00
|
|
|
return StaticTreeItem::setData(column, value, role);
|
2024-01-25 15:39:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2024-01-26 09:01:11 +01:00
|
|
|
Links m_links;
|
2024-01-25 15:39:32 +01:00
|
|
|
};
|
|
|
|
|
|
2024-01-19 13:47:03 +01:00
|
|
|
class IssuesWidget : public QScrollArea
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit IssuesWidget(QWidget *parent = nullptr);
|
|
|
|
|
void updateUi();
|
2024-01-19 15:06:34 +01:00
|
|
|
void setTableDto(const Dto::TableInfoDto &dto);
|
|
|
|
|
void addIssues(const Dto::IssueTableDto &dto);
|
2024-01-19 13:47:03 +01:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void updateTableView();
|
2024-01-27 13:33:45 +01:00
|
|
|
void fetchIssues(const IssueListSearch &search);
|
2024-01-26 14:58:49 +01:00
|
|
|
void fetchMoreIssues();
|
2024-01-19 13:47:03 +01:00
|
|
|
|
|
|
|
|
QString m_currentPrefix;
|
|
|
|
|
std::optional<Dto::TableInfoDto> m_currentTableInfo;
|
|
|
|
|
QHBoxLayout *m_typesLayout = nullptr;
|
|
|
|
|
QHBoxLayout *m_filtersLayout = nullptr;
|
|
|
|
|
QPushButton *m_addedFilter = nullptr;
|
|
|
|
|
QPushButton *m_removedFilter = nullptr;
|
|
|
|
|
QComboBox *m_ownerFilter = nullptr;
|
|
|
|
|
QComboBox *m_versionStart = nullptr;
|
|
|
|
|
QComboBox *m_versionEnd = nullptr;
|
|
|
|
|
QLineEdit *m_pathGlobFilter = nullptr; // FancyLineEdit instead?
|
2024-01-25 11:16:42 +01:00
|
|
|
QLabel *m_totalRows = nullptr;
|
2024-01-26 09:01:11 +01:00
|
|
|
BaseTreeView *m_issuesView = nullptr;
|
|
|
|
|
TreeModel<> *m_issuesModel = nullptr;
|
2024-01-26 14:58:49 +01:00
|
|
|
int m_totalRowCount = 0;
|
|
|
|
|
int m_lastRequestedOffset = 0;
|
2024-01-27 13:33:45 +01:00
|
|
|
TaskTreeRunner m_issuesRunner;
|
2024-01-19 13:47:03 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
IssuesWidget::IssuesWidget(QWidget *parent)
|
|
|
|
|
: QScrollArea(parent)
|
|
|
|
|
{
|
|
|
|
|
QWidget *widget = new QWidget(this);
|
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(widget);
|
|
|
|
|
// row with issue types (-> depending on choice, tables below change)
|
|
|
|
|
// and a selectable range (start version, end version)
|
|
|
|
|
// row with added/removed and some filters (assignee, path glob, (named filter))
|
|
|
|
|
// table, columns depend on chosen issue type
|
|
|
|
|
QHBoxLayout *top = new QHBoxLayout;
|
|
|
|
|
layout->addLayout(top);
|
|
|
|
|
m_typesLayout = new QHBoxLayout;
|
|
|
|
|
top->addLayout(m_typesLayout);
|
|
|
|
|
top->addStretch(1);
|
|
|
|
|
m_versionStart = new QComboBox(this);
|
|
|
|
|
m_versionStart->setMinimumContentsLength(25);
|
|
|
|
|
top->addWidget(m_versionStart);
|
|
|
|
|
m_versionEnd = new QComboBox(this);
|
|
|
|
|
m_versionEnd->setMinimumContentsLength(25);
|
|
|
|
|
top->addWidget(m_versionEnd);
|
|
|
|
|
top->addStretch(1);
|
|
|
|
|
m_filtersLayout = new QHBoxLayout;
|
|
|
|
|
m_addedFilter = new QPushButton(this);
|
|
|
|
|
m_addedFilter->setIcon(trendIcon(1, 0));
|
|
|
|
|
m_addedFilter->setText("0");
|
|
|
|
|
m_filtersLayout->addWidget(m_addedFilter);
|
|
|
|
|
m_removedFilter = new QPushButton(this);
|
|
|
|
|
m_removedFilter->setIcon(trendIcon(0, 1));
|
|
|
|
|
m_removedFilter->setText("0");
|
|
|
|
|
m_filtersLayout->addWidget(m_removedFilter);
|
|
|
|
|
m_filtersLayout->addSpacing(1);
|
|
|
|
|
m_ownerFilter = new QComboBox(this);
|
|
|
|
|
m_ownerFilter->setToolTip(Tr::tr("Owner"));
|
|
|
|
|
m_ownerFilter->setMinimumContentsLength(25);
|
|
|
|
|
m_filtersLayout->addWidget(m_ownerFilter);
|
|
|
|
|
m_pathGlobFilter = new QLineEdit(this);
|
|
|
|
|
m_pathGlobFilter->setPlaceholderText(Tr::tr("Path globbing"));
|
|
|
|
|
m_filtersLayout->addWidget(m_pathGlobFilter);
|
|
|
|
|
layout->addLayout(m_filtersLayout);
|
2024-01-26 09:01:11 +01:00
|
|
|
m_issuesView = new BaseTreeView(this);
|
2024-01-19 15:06:34 +01:00
|
|
|
m_issuesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
|
|
|
m_issuesView->enableColumnHiding();
|
2024-01-26 09:01:11 +01:00
|
|
|
m_issuesModel = new TreeModel;
|
2024-01-19 15:06:34 +01:00
|
|
|
m_issuesView->setModel(m_issuesModel);
|
2024-01-26 14:58:49 +01:00
|
|
|
auto sb = m_issuesView->verticalScrollBar();
|
|
|
|
|
if (QTC_GUARD(sb)) {
|
|
|
|
|
connect(sb, &QAbstractSlider::valueChanged, sb, [this, sb](int value) {
|
|
|
|
|
if (value >= sb->maximum() - 10) {
|
|
|
|
|
if (m_issuesModel->rowCount() < m_totalRowCount)
|
|
|
|
|
fetchMoreIssues();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-01-19 15:06:34 +01:00
|
|
|
layout->addWidget(m_issuesView);
|
2024-01-25 11:16:42 +01:00
|
|
|
m_totalRows = new QLabel(Tr::tr("Total rows:"), this);
|
|
|
|
|
QHBoxLayout *bottom = new QHBoxLayout;
|
|
|
|
|
layout->addLayout(bottom);
|
|
|
|
|
bottom->addStretch(1);
|
|
|
|
|
bottom->addWidget(m_totalRows);
|
2024-01-19 13:47:03 +01:00
|
|
|
setWidget(widget);
|
|
|
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
|
|
|
setWidgetResizable(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IssuesWidget::updateUi()
|
|
|
|
|
{
|
|
|
|
|
m_filtersLayout->setEnabled(false);
|
|
|
|
|
// TODO extract parts of it and do them only when necessary
|
|
|
|
|
QLayoutItem *child;
|
|
|
|
|
while ((child = m_typesLayout->takeAt(0)) != nullptr) {
|
|
|
|
|
delete child->widget();
|
|
|
|
|
delete child;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<Dto::ProjectInfoDto> projectInfo = Internal::projectInfo();
|
|
|
|
|
if (!projectInfo)
|
|
|
|
|
return;
|
|
|
|
|
const Dto::ProjectInfoDto &info = *projectInfo;
|
|
|
|
|
if (info.versions.empty()) // add some warning/information?
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// for now just a start..
|
|
|
|
|
|
|
|
|
|
const std::vector<Dto::IssueKindInfoDto> &issueKinds = info.issueKinds;
|
|
|
|
|
for (const Dto::IssueKindInfoDto &kind : issueKinds) {
|
|
|
|
|
auto button = new QToolButton(this);
|
|
|
|
|
button->setIcon(iconForIssue(kind.prefix));
|
|
|
|
|
button->setToolTip(kind.nicePluralName);
|
|
|
|
|
connect(button, &QToolButton::clicked, this, [this, prefix = kind.prefix]{
|
|
|
|
|
m_currentPrefix = prefix;
|
|
|
|
|
updateTableView();
|
|
|
|
|
});
|
|
|
|
|
m_typesLayout->addWidget(button);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-25 10:07:26 +01:00
|
|
|
m_ownerFilter->clear();
|
|
|
|
|
for (const Dto::UserRefDto &user : projectInfo->users)
|
|
|
|
|
m_ownerFilter->addItem(user.displayName, user.name);
|
|
|
|
|
|
|
|
|
|
m_versionStart->clear();
|
|
|
|
|
m_versionEnd->clear();
|
|
|
|
|
const std::vector<Dto::AnalysisVersionDto> &versions = info.versions;
|
|
|
|
|
for (const Dto::AnalysisVersionDto &version : versions) {
|
|
|
|
|
const QString label = version.label.value_or(version.name);
|
|
|
|
|
m_versionStart->insertItem(0, label);
|
|
|
|
|
m_versionEnd->insertItem(0, label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_versionEnd->setCurrentText(versions.back().label.value_or(versions.back().name));
|
|
|
|
|
|
2024-01-19 13:47:03 +01:00
|
|
|
m_filtersLayout->setEnabled(true);
|
2024-01-19 15:06:34 +01:00
|
|
|
if (info.issueKinds.size())
|
|
|
|
|
m_currentPrefix = info.issueKinds.front().prefix;
|
|
|
|
|
updateTableView();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IssuesWidget::setTableDto(const Dto::TableInfoDto &dto)
|
|
|
|
|
{
|
|
|
|
|
m_currentTableInfo.emplace(dto);
|
|
|
|
|
|
|
|
|
|
// update issues table layout - for now just simple approach
|
2024-01-26 09:01:11 +01:00
|
|
|
TreeModel<> *issuesModel = new TreeModel;
|
2024-01-19 15:06:34 +01:00
|
|
|
QStringList columnHeaders;
|
|
|
|
|
QStringList hiddenColumns;
|
|
|
|
|
for (const Dto::ColumnInfoDto &column : dto.columns) {
|
2024-01-25 10:07:26 +01:00
|
|
|
columnHeaders << column.header.value_or(column.key);
|
2024-01-19 15:06:34 +01:00
|
|
|
if (!column.showByDefault)
|
|
|
|
|
hiddenColumns << column.key;
|
|
|
|
|
}
|
2024-01-25 11:16:42 +01:00
|
|
|
m_addedFilter->setText("0");
|
|
|
|
|
m_removedFilter->setText("0");
|
|
|
|
|
m_totalRows->setText(Tr::tr("Total rows:"));
|
2024-01-19 15:06:34 +01:00
|
|
|
|
|
|
|
|
issuesModel->setHeader(columnHeaders);
|
|
|
|
|
|
|
|
|
|
auto oldModel = m_issuesModel;
|
|
|
|
|
m_issuesModel = issuesModel;
|
|
|
|
|
m_issuesView->setModel(issuesModel);
|
|
|
|
|
delete oldModel;
|
|
|
|
|
int counter = 0;
|
|
|
|
|
for (const QString &header : std::as_const(columnHeaders))
|
|
|
|
|
m_issuesView->setColumnHidden(counter++, hiddenColumns.contains(header));
|
|
|
|
|
|
|
|
|
|
// first time lookup... should we cache and maybe represent old data?
|
2024-01-26 14:58:49 +01:00
|
|
|
m_totalRowCount = 0;
|
|
|
|
|
m_lastRequestedOffset = 0;
|
2024-01-19 15:06:34 +01:00
|
|
|
IssueListSearch search;
|
|
|
|
|
search.kind = m_currentPrefix;
|
2024-01-25 11:16:42 +01:00
|
|
|
search.computeTotalRowCount = true;
|
2024-01-25 10:07:26 +01:00
|
|
|
m_issuesView->showProgressIndicator();
|
2024-01-19 15:06:34 +01:00
|
|
|
fetchIssues(search);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-25 10:07:26 +01:00
|
|
|
static QString anyToSimpleString(const Dto::Any &any)
|
|
|
|
|
{
|
|
|
|
|
if (any.isString())
|
|
|
|
|
return any.getString();
|
|
|
|
|
if (any.isBool())
|
|
|
|
|
return QString("%1").arg(any.getBool());
|
|
|
|
|
if (any.isDouble())
|
|
|
|
|
return QString::number(any.getDouble());
|
|
|
|
|
if (any.isNull())
|
|
|
|
|
return QString(); // or NULL??
|
|
|
|
|
if (any.isList()) {
|
|
|
|
|
const std::vector<Dto::Any> anyList = any.getList();
|
|
|
|
|
QStringList list;
|
|
|
|
|
for (const Dto::Any &inner : anyList)
|
|
|
|
|
list << anyToSimpleString(inner);
|
|
|
|
|
return list.join(',');
|
|
|
|
|
}
|
|
|
|
|
if (any.isMap()) { // TODO
|
|
|
|
|
const std::map<QString, Dto::Any> anyMap = any.getMap();
|
|
|
|
|
auto value = anyMap.find("displayName");
|
|
|
|
|
if (value != anyMap.end())
|
|
|
|
|
return anyToSimpleString(value->second);
|
|
|
|
|
value = anyMap.find("name");
|
|
|
|
|
if (value != anyMap.end())
|
|
|
|
|
return anyToSimpleString(value->second);
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-26 09:01:11 +01:00
|
|
|
static Links linksForIssue(const std::map<QString, Dto::Any> &issueRow)
|
2024-01-25 15:39:32 +01:00
|
|
|
{
|
2024-01-26 09:01:11 +01:00
|
|
|
Links links;
|
2024-01-25 15:39:32 +01:00
|
|
|
|
|
|
|
|
auto end = issueRow.end();
|
2024-01-26 08:59:06 +01:00
|
|
|
auto findAndAppend = [&links, &issueRow, &end](const QString &path, const QString &line) {
|
2024-01-25 15:39:32 +01:00
|
|
|
auto it = issueRow.find(path);
|
|
|
|
|
if (it != end) {
|
2024-01-26 09:01:11 +01:00
|
|
|
Link link{ FilePath::fromUserInput(it->second.getString()) };
|
2024-01-25 15:39:32 +01:00
|
|
|
it = issueRow.find(line);
|
|
|
|
|
if (it != end)
|
|
|
|
|
link.targetLine = it->second.getDouble();
|
|
|
|
|
links.append(link);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// do these always? or just for their "expected" kind
|
|
|
|
|
findAndAppend("path", "line");
|
|
|
|
|
findAndAppend("sourcePath", "sourceLine");
|
|
|
|
|
findAndAppend("targetPath", "targetLine");
|
|
|
|
|
findAndAppend("leftPath", "leftLine");
|
|
|
|
|
findAndAppend("rightPath", "rightLine");
|
|
|
|
|
|
|
|
|
|
return links;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 15:06:34 +01:00
|
|
|
void IssuesWidget::addIssues(const Dto::IssueTableDto &dto)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_currentTableInfo.has_value(), return);
|
2024-01-26 14:58:49 +01:00
|
|
|
if (dto.totalRowCount.has_value()) {
|
|
|
|
|
m_totalRowCount = dto.totalRowCount.value();
|
|
|
|
|
m_totalRows->setText(Tr::tr("Total rows:") + ' ' + QString::number(m_totalRowCount));
|
|
|
|
|
}
|
2024-01-25 11:16:42 +01:00
|
|
|
if (dto.totalAddedCount.has_value())
|
|
|
|
|
m_addedFilter->setText(QString::number(dto.totalAddedCount.value()));
|
|
|
|
|
if (dto.totalRemovedCount.has_value())
|
|
|
|
|
m_removedFilter->setText(QString::number(dto.totalRemovedCount.value()));
|
2024-01-19 15:06:34 +01:00
|
|
|
|
|
|
|
|
const std::vector<Dto::ColumnInfoDto> tableColumns = m_currentTableInfo->columns;
|
|
|
|
|
const std::vector<std::map<QString, Dto::Any>> rows = dto.rows;
|
|
|
|
|
for (auto row : rows) {
|
|
|
|
|
QStringList data;
|
|
|
|
|
for (auto column : tableColumns) {
|
|
|
|
|
auto it = row.find(column.key);
|
|
|
|
|
if (it != row.end()) {
|
2024-01-25 10:07:26 +01:00
|
|
|
QString value = anyToSimpleString(it->second);
|
|
|
|
|
if (column.key == "id")
|
|
|
|
|
value.prepend(m_currentPrefix);
|
|
|
|
|
data << value;
|
2024-01-19 15:06:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-25 15:39:32 +01:00
|
|
|
IssueTreeItem *it = new IssueTreeItem(data, data);
|
2024-01-26 08:59:06 +01:00
|
|
|
it->setLinks(linksForIssue(row));
|
2024-01-19 15:06:34 +01:00
|
|
|
m_issuesModel->rootItem()->appendChild(it);
|
|
|
|
|
}
|
2024-01-25 10:07:26 +01:00
|
|
|
m_issuesView->hideProgressIndicator();
|
2024-01-19 13:47:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IssuesWidget::updateTableView()
|
|
|
|
|
{
|
2024-01-19 15:06:34 +01:00
|
|
|
QTC_ASSERT(!m_currentPrefix.isEmpty(), return);
|
|
|
|
|
// fetch table dto and apply, on done fetch first data for the selected issues
|
|
|
|
|
fetchIssueTableLayout(m_currentPrefix);
|
2024-01-19 13:47:03 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-27 13:33:45 +01:00
|
|
|
void IssuesWidget::fetchIssues(const IssueListSearch &search)
|
|
|
|
|
{
|
|
|
|
|
const auto issuesHandler = [this](const Dto::IssueTableDto &dto) { addIssues(dto); };
|
|
|
|
|
m_issuesRunner.start(issueTableRecipe(search, issuesHandler));
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-26 14:58:49 +01:00
|
|
|
void IssuesWidget::fetchMoreIssues()
|
|
|
|
|
{
|
|
|
|
|
if (m_lastRequestedOffset == m_issuesModel->rowCount())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
IssueListSearch search;
|
|
|
|
|
search.kind = m_currentPrefix;
|
|
|
|
|
m_lastRequestedOffset = m_issuesModel->rowCount();
|
|
|
|
|
search.offset = m_lastRequestedOffset;
|
|
|
|
|
m_issuesView->showProgressIndicator();
|
|
|
|
|
fetchIssues(search);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 09:48:11 +01:00
|
|
|
AxivionOutputPane::AxivionOutputPane(QObject *parent)
|
|
|
|
|
: Core::IOutputPane(parent)
|
|
|
|
|
{
|
2023-09-13 15:36:07 +02:00
|
|
|
setId("Axivion");
|
2023-09-13 11:46:00 +02:00
|
|
|
setDisplayName(Tr::tr("Axivion"));
|
2023-09-14 09:40:56 +02:00
|
|
|
setPriorityInStatusBar(-50);
|
2023-09-13 11:46:00 +02:00
|
|
|
|
2022-11-28 09:48:11 +01:00
|
|
|
m_outputWidget = new QStackedWidget;
|
2022-12-14 13:53:00 +01:00
|
|
|
DashboardWidget *dashboardWidget = new DashboardWidget(m_outputWidget);
|
|
|
|
|
m_outputWidget->addWidget(dashboardWidget);
|
2024-01-19 13:47:03 +01:00
|
|
|
IssuesWidget *issuesWidget = new IssuesWidget(m_outputWidget);
|
|
|
|
|
m_outputWidget->addWidget(issuesWidget);
|
2023-01-13 14:38:39 +01:00
|
|
|
QTextBrowser *browser = new QTextBrowser(m_outputWidget);
|
|
|
|
|
m_outputWidget->addWidget(browser);
|
2022-11-28 09:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AxivionOutputPane::~AxivionOutputPane()
|
|
|
|
|
{
|
|
|
|
|
if (!m_outputWidget->parent())
|
|
|
|
|
delete m_outputWidget;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QWidget *AxivionOutputPane::outputWidget(QWidget *parent)
|
|
|
|
|
{
|
|
|
|
|
if (m_outputWidget)
|
|
|
|
|
m_outputWidget->setParent(parent);
|
|
|
|
|
else
|
|
|
|
|
QTC_CHECK(false);
|
|
|
|
|
return m_outputWidget;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<QWidget *> AxivionOutputPane::toolBarWidgets() const
|
|
|
|
|
{
|
|
|
|
|
QList<QWidget *> buttons;
|
2023-01-13 15:43:01 +01:00
|
|
|
auto showDashboard = new QToolButton(m_outputWidget);
|
2024-01-26 09:01:11 +01:00
|
|
|
showDashboard->setIcon(Icons::HOME_TOOLBAR.icon());
|
2023-01-13 15:43:01 +01:00
|
|
|
showDashboard->setToolTip(Tr::tr("Show dashboard"));
|
|
|
|
|
connect(showDashboard, &QToolButton::clicked, this, [this]{
|
|
|
|
|
QTC_ASSERT(m_outputWidget, return);
|
|
|
|
|
m_outputWidget->setCurrentIndex(0);
|
|
|
|
|
});
|
|
|
|
|
buttons.append(showDashboard);
|
2024-01-19 13:47:03 +01:00
|
|
|
auto showIssues = new QToolButton(m_outputWidget);
|
2024-01-26 09:01:11 +01:00
|
|
|
showIssues->setIcon(Icons::ZOOM_TOOLBAR.icon());
|
2024-01-19 13:47:03 +01:00
|
|
|
showIssues->setToolTip(Tr::tr("Search for issues"));
|
|
|
|
|
connect(showIssues, &QToolButton::clicked, this, [this]{
|
|
|
|
|
QTC_ASSERT(m_outputWidget, return);
|
|
|
|
|
m_outputWidget->setCurrentIndex(1);
|
|
|
|
|
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
|
|
|
|
|
issues->updateUi();
|
|
|
|
|
});
|
|
|
|
|
buttons.append(showIssues);
|
2022-11-28 09:48:11 +01:00
|
|
|
return buttons;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AxivionOutputPane::clearContents()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AxivionOutputPane::setFocus()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AxivionOutputPane::hasFocus() const
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AxivionOutputPane::canFocus() const
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AxivionOutputPane::canNavigate() const
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AxivionOutputPane::canNext() const
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AxivionOutputPane::canPrevious() const
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AxivionOutputPane::goToNext()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AxivionOutputPane::goToPrev()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 12:11:03 +01:00
|
|
|
void AxivionOutputPane::updateDashboard()
|
|
|
|
|
{
|
2022-12-14 13:53:00 +01:00
|
|
|
if (auto dashboard = static_cast<DashboardWidget *>(m_outputWidget->widget(0))) {
|
|
|
|
|
dashboard->updateUi();
|
|
|
|
|
m_outputWidget->setCurrentIndex(0);
|
|
|
|
|
if (dashboard->hasProject())
|
|
|
|
|
flash();
|
|
|
|
|
}
|
2022-12-14 12:11:03 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-19 15:06:34 +01:00
|
|
|
void AxivionOutputPane::setTableDto(const Dto::TableInfoDto &dto)
|
|
|
|
|
{
|
|
|
|
|
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
|
|
|
|
|
issues->setTableDto(dto);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-13 14:38:39 +01:00
|
|
|
void AxivionOutputPane::updateAndShowRule(const QString &ruleHtml)
|
|
|
|
|
{
|
2024-01-19 13:47:03 +01:00
|
|
|
if (auto browser = static_cast<QTextBrowser *>(m_outputWidget->widget(2))) {
|
2023-01-13 14:38:39 +01:00
|
|
|
browser->setText(ruleHtml);
|
|
|
|
|
if (!ruleHtml.isEmpty()) {
|
2024-01-19 13:47:03 +01:00
|
|
|
m_outputWidget->setCurrentIndex(2);
|
2023-01-13 14:38:39 +01:00
|
|
|
popup(Core::IOutputPane::NoModeSwitch);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-12 16:45:31 +01:00
|
|
|
} // Axivion::Internal
|