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 "axivionplugin.h"
|
|
|
|
|
|
|
|
|
|
#include "axivionoutputpane.h"
|
2022-12-13 11:17:12 +01:00
|
|
|
#include "axivionprojectsettings.h"
|
2023-09-15 12:25:04 +02:00
|
|
|
#include "axivionsettings.h"
|
2022-12-13 11:17:12 +01:00
|
|
|
#include "axiviontr.h"
|
2024-02-11 18:22:39 +01:00
|
|
|
#include "credentialquery.h"
|
2023-07-25 18:48:18 +02:00
|
|
|
#include "dashboard/dto.h"
|
2023-11-24 12:29:17 +01:00
|
|
|
#include "dashboard/error.h"
|
2022-11-28 09:48:11 +01:00
|
|
|
|
2022-12-16 15:12:39 +01:00
|
|
|
#include <coreplugin/editormanager/documentmodel.h>
|
2022-12-16 23:11:46 +01:00
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
2023-09-15 12:25:04 +02:00
|
|
|
#include <coreplugin/icore.h>
|
2024-02-19 13:37:20 +01:00
|
|
|
#include <coreplugin/inavigationwidgetfactory.h>
|
2022-12-14 12:11:03 +01:00
|
|
|
#include <coreplugin/messagemanager.h>
|
2024-02-19 13:37:20 +01:00
|
|
|
#include <coreplugin/navigationwidget.h>
|
2023-03-01 17:54:39 +01:00
|
|
|
|
2024-01-12 08:38:31 +01:00
|
|
|
#include <extensionsystem/iplugin.h>
|
2022-12-12 16:45:31 +01:00
|
|
|
#include <extensionsystem/pluginmanager.h>
|
2023-03-01 17:54:39 +01:00
|
|
|
|
2022-12-16 15:12:39 +01:00
|
|
|
#include <projectexplorer/buildsystem.h>
|
2022-12-13 11:17:12 +01:00
|
|
|
#include <projectexplorer/project.h>
|
2023-03-01 17:54:39 +01:00
|
|
|
#include <projectexplorer/projectmanager.h>
|
|
|
|
|
|
2023-11-24 12:29:17 +01:00
|
|
|
#include <solutions/tasking/networkquery.h>
|
|
|
|
|
#include <solutions/tasking/tasktreerunner.h>
|
|
|
|
|
|
2022-12-16 23:11:46 +01:00
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
#include <texteditor/texteditor.h>
|
|
|
|
|
#include <texteditor/textmark.h>
|
2023-03-01 17:54:39 +01:00
|
|
|
|
2023-09-15 12:25:04 +02:00
|
|
|
#include <utils/algorithm.h>
|
2023-11-24 12:29:17 +01:00
|
|
|
#include <utils/async.h>
|
2024-02-21 10:40:02 +01:00
|
|
|
#include <utils/checkablemessagebox.h>
|
2024-02-11 18:22:39 +01:00
|
|
|
#include <utils/environment.h>
|
2023-08-29 15:42:08 +02:00
|
|
|
#include <utils/networkaccessmanager.h>
|
2022-12-13 11:17:12 +01:00
|
|
|
#include <utils/qtcassert.h>
|
2023-01-13 14:38:39 +01:00
|
|
|
#include <utils/utilsicons.h>
|
2022-11-28 09:48:11 +01:00
|
|
|
|
2023-01-13 14:38:39 +01:00
|
|
|
#include <QAction>
|
2024-02-21 10:40:02 +01:00
|
|
|
#include <QDesktopServices>
|
2024-02-11 18:22:39 +01:00
|
|
|
#include <QInputDialog>
|
2023-09-15 12:25:04 +02:00
|
|
|
#include <QMessageBox>
|
|
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
|
#include <QNetworkReply>
|
2024-02-19 13:37:20 +01:00
|
|
|
#include <QTextBrowser>
|
2022-12-14 12:11:03 +01:00
|
|
|
#include <QTimer>
|
2024-02-21 09:54:31 +01:00
|
|
|
#include <QUrlQuery>
|
2022-12-13 15:13:13 +01:00
|
|
|
|
2023-07-25 18:48:18 +02:00
|
|
|
#include <memory>
|
|
|
|
|
|
2024-02-11 18:22:39 +01:00
|
|
|
constexpr char s_axivionTextMarkId[] = "AxivionTextMark";
|
|
|
|
|
constexpr char s_axivionKeychainService[] = "keychain.axivion.qtcreator";
|
2023-01-09 07:31:50 +01:00
|
|
|
|
2024-01-30 14:01:58 +01:00
|
|
|
using namespace Core;
|
2024-02-03 21:05:01 +01:00
|
|
|
using namespace ProjectExplorer;
|
2023-11-24 12:29:17 +01:00
|
|
|
using namespace Tasking;
|
2024-02-02 11:24:48 +01:00
|
|
|
using namespace TextEditor;
|
2023-11-24 12:29:17 +01:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2022-11-28 09:48:11 +01:00
|
|
|
namespace Axivion::Internal {
|
|
|
|
|
|
2024-02-27 13:51:54 +01:00
|
|
|
QIcon iconForIssue(const std::optional<Dto::IssueKind> &issueKind)
|
2024-01-19 13:47:03 +01:00
|
|
|
{
|
2024-02-27 13:51:54 +01:00
|
|
|
if (!issueKind)
|
|
|
|
|
return {};
|
2024-01-19 13:47:03 +01:00
|
|
|
|
2024-02-27 13:51:54 +01:00
|
|
|
static QHash<Dto::IssueKind, QIcon> prefixToIcon;
|
2024-02-27 15:34:32 +01:00
|
|
|
|
2024-02-27 13:51:54 +01:00
|
|
|
auto it = prefixToIcon.constFind(*issueKind);
|
2024-02-27 15:34:32 +01:00
|
|
|
if (it != prefixToIcon.constEnd())
|
|
|
|
|
return *it;
|
|
|
|
|
|
|
|
|
|
const QLatin1String prefix = Dto::IssueKindMeta::enumToStr(*issueKind);
|
|
|
|
|
const Icon icon({{FilePath::fromString(":/axivion/images/button-" + prefix + ".png"),
|
|
|
|
|
Theme::PaletteButtonText}}, Icon::Tint);
|
|
|
|
|
return prefixToIcon.insert(*issueKind, icon.icon()).value();
|
2024-01-19 13:47:03 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 15:15:50 +01:00
|
|
|
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 {};
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 18:22:39 +01:00
|
|
|
static QString apiTokenDescription()
|
|
|
|
|
{
|
|
|
|
|
const QString ua = "Axivion" + QCoreApplication::applicationName() + "Plugin/"
|
|
|
|
|
+ QCoreApplication::applicationVersion();
|
|
|
|
|
QString user = Utils::qtcEnvironmentVariable("USERNAME");
|
|
|
|
|
if (user.isEmpty())
|
|
|
|
|
user = Utils::qtcEnvironmentVariable("USER");
|
|
|
|
|
return "Automatically created by " + ua + " on " + user + "@" + QSysInfo::machineHostName();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-11 15:05:38 +01:00
|
|
|
static QString escapeKey(const QString &string)
|
|
|
|
|
{
|
|
|
|
|
QString escaped = string;
|
|
|
|
|
return escaped.replace('\\', "\\\\").replace('@', "\\@");
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 18:22:39 +01:00
|
|
|
static QString credentialKey()
|
|
|
|
|
{
|
2024-03-11 15:05:38 +01:00
|
|
|
return escapeKey(settings().server.username) + '@' + escapeKey(settings().server.dashboard);
|
2024-02-11 18:22:39 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 01:17:41 +01:00
|
|
|
template <typename DtoType>
|
|
|
|
|
struct GetDtoStorage
|
|
|
|
|
{
|
|
|
|
|
QUrl url;
|
|
|
|
|
std::optional<QByteArray> credential;
|
|
|
|
|
std::optional<DtoType> dtoData;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <typename DtoType>
|
|
|
|
|
struct PostDtoStorage
|
|
|
|
|
{
|
|
|
|
|
QUrl url;
|
|
|
|
|
std::optional<QByteArray> credential;
|
|
|
|
|
QByteArray csrfToken;
|
|
|
|
|
QByteArray writeData;
|
|
|
|
|
std::optional<DtoType> dtoData;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static DashboardInfo toDashboardInfo(const GetDtoStorage<Dto::DashboardInfoDto> &dashboardStorage)
|
2024-02-11 18:22:39 +01:00
|
|
|
{
|
2024-02-28 01:17:41 +01:00
|
|
|
const Dto::DashboardInfoDto &infoDto = *dashboardStorage.dtoData;
|
2024-02-11 18:22:39 +01:00
|
|
|
const QVersionNumber versionNumber = infoDto.dashboardVersionNumber
|
|
|
|
|
? QVersionNumber::fromString(*infoDto.dashboardVersionNumber) : QVersionNumber();
|
|
|
|
|
|
|
|
|
|
QStringList projects;
|
|
|
|
|
QHash<QString, QUrl> projectUrls;
|
|
|
|
|
|
|
|
|
|
if (infoDto.projects) {
|
|
|
|
|
for (const Dto::ProjectReferenceDto &project : *infoDto.projects) {
|
|
|
|
|
projects.push_back(project.name);
|
|
|
|
|
projectUrls.insert(project.name, project.url);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-28 01:17:41 +01:00
|
|
|
return {dashboardStorage.url, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
|
2024-02-11 18:22:39 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-19 15:06:34 +01:00
|
|
|
QString IssueListSearch::toQuery() const
|
|
|
|
|
{
|
|
|
|
|
if (kind.isEmpty())
|
|
|
|
|
return {};
|
|
|
|
|
QString result;
|
2024-01-30 18:26:21 +01:00
|
|
|
result.append(QString("?kind=%1&offset=%2").arg(kind).arg(offset));
|
|
|
|
|
if (limit)
|
|
|
|
|
result.append(QString("&limit=%1").arg(limit));
|
2024-01-19 15:06:34 +01:00
|
|
|
// TODO other params
|
2024-01-29 11:34:42 +01:00
|
|
|
if (!versionStart.isEmpty()) {
|
|
|
|
|
result.append(QString("&start=%1").arg(
|
2024-01-30 15:15:50 +01:00
|
|
|
QString::fromUtf8(QUrl::toPercentEncoding(versionStart))));
|
2024-01-29 11:34:42 +01:00
|
|
|
}
|
|
|
|
|
if (!versionEnd.isEmpty()) {
|
|
|
|
|
result.append(QString("&end=%1").arg(
|
2024-01-30 15:15:50 +01:00
|
|
|
QString::fromUtf8(QUrl::toPercentEncoding(versionEnd))));
|
|
|
|
|
}
|
2024-01-31 10:53:15 +01:00
|
|
|
if (!owner.isEmpty()) {
|
|
|
|
|
result.append(QString("&user=%1").arg(
|
|
|
|
|
QString::fromUtf8((QUrl::toPercentEncoding(owner)))));
|
|
|
|
|
}
|
2024-01-30 15:15:50 +01:00
|
|
|
if (!filter_path.isEmpty()) {
|
2024-02-22 10:33:40 +01:00
|
|
|
result.append(QString("&filter_any path=%1").arg(
|
2024-01-30 15:15:50 +01:00
|
|
|
QString::fromUtf8(QUrl::toPercentEncoding(filter_path))));
|
2024-01-29 11:34:42 +01:00
|
|
|
}
|
2024-01-31 11:41:01 +01:00
|
|
|
if (!state.isEmpty())
|
|
|
|
|
result.append(QString("&state=%1").arg(state));
|
2024-01-25 11:16:42 +01:00
|
|
|
if (computeTotalRowCount)
|
|
|
|
|
result.append("&computeTotalRowCount=true");
|
2024-02-21 17:35:18 +01:00
|
|
|
if (!sort.isEmpty())
|
|
|
|
|
result.append(QString("&sort=%1").arg(
|
|
|
|
|
QString::fromUtf8(QUrl::toPercentEncoding(sort))));
|
2024-01-19 15:06:34 +01:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 16:17:27 +01:00
|
|
|
enum class ServerAccess { Unknown, NoAuthorization, WithAuthorization };
|
|
|
|
|
|
2022-12-14 12:11:03 +01:00
|
|
|
class AxivionPluginPrivate : public QObject
|
2022-11-28 09:48:11 +01:00
|
|
|
{
|
2024-02-19 13:37:20 +01:00
|
|
|
Q_OBJECT
|
2022-11-28 09:48:11 +01:00
|
|
|
public:
|
2023-09-15 12:25:04 +02:00
|
|
|
AxivionPluginPrivate();
|
|
|
|
|
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
2024-02-03 21:06:15 +01:00
|
|
|
void onStartupProjectChanged(Project *project);
|
2022-12-14 12:11:03 +01:00
|
|
|
void fetchProjectInfo(const QString &projectName);
|
2024-02-03 21:06:15 +01:00
|
|
|
void handleOpenedDocs();
|
2024-01-30 14:01:58 +01:00
|
|
|
void onDocumentOpened(IDocument *doc);
|
|
|
|
|
void onDocumentClosed(IDocument * doc);
|
2022-12-16 22:34:56 +01:00
|
|
|
void clearAllMarks();
|
2024-02-12 13:55:56 +01:00
|
|
|
void handleIssuesForFile(const Dto::FileViewDto &fileView);
|
2024-01-30 21:19:28 +01:00
|
|
|
void fetchIssueInfo(const QString &id);
|
2024-02-19 13:37:20 +01:00
|
|
|
void setIssueDetails(const QString &issueDetailsHtml);
|
2024-02-21 09:54:31 +01:00
|
|
|
void handleAnchorClicked(const QUrl &url);
|
2024-02-19 13:37:20 +01:00
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void issueDetailsChanged(const QString &issueDetailsHtml);
|
2022-12-14 12:11:03 +01:00
|
|
|
|
2024-02-19 13:37:20 +01:00
|
|
|
public:
|
2024-02-16 16:17:27 +01:00
|
|
|
// TODO: Should be set to Unknown on server address change in settings.
|
|
|
|
|
ServerAccess m_serverAccess = ServerAccess::Unknown;
|
|
|
|
|
// TODO: Should be cleared on username change in settings.
|
|
|
|
|
std::optional<QByteArray> m_apiToken;
|
2023-11-24 12:29:17 +01:00
|
|
|
NetworkAccessManager m_networkAccessManager;
|
2024-01-23 22:29:59 +01:00
|
|
|
std::optional<DashboardInfo> m_dashboardInfo;
|
2023-11-24 12:29:17 +01:00
|
|
|
std::optional<Dto::ProjectInfoDto> m_currentProjectInfo;
|
2024-02-03 21:06:15 +01:00
|
|
|
Project *m_project = nullptr;
|
2023-01-09 07:41:22 +01:00
|
|
|
bool m_runningQuery = false;
|
2023-11-24 12:29:17 +01:00
|
|
|
TaskTreeRunner m_taskTreeRunner;
|
2024-01-30 15:15:50 +01:00
|
|
|
std::unordered_map<IDocument *, std::unique_ptr<TaskTree>> m_docMarksTrees;
|
2024-01-30 21:19:28 +01:00
|
|
|
TaskTreeRunner m_issueInfoRunner;
|
2022-11-28 09:48:11 +01:00
|
|
|
};
|
|
|
|
|
|
2022-12-13 11:17:12 +01:00
|
|
|
static AxivionPluginPrivate *dd = nullptr;
|
|
|
|
|
|
2024-02-02 11:24:48 +01:00
|
|
|
class AxivionTextMark : public TextMark
|
2023-01-13 13:49:40 +01:00
|
|
|
{
|
|
|
|
|
public:
|
2024-02-13 14:20:23 +01:00
|
|
|
AxivionTextMark(const FilePath &filePath, const Dto::LineMarkerDto &issue,
|
|
|
|
|
std::optional<Theme::Color> color)
|
2024-02-19 22:16:36 +01:00
|
|
|
: TextMark(filePath, issue.startLine, {"Axivion", s_axivionTextMarkId})
|
2024-02-02 11:23:18 +01:00
|
|
|
{
|
2024-02-12 13:55:56 +01:00
|
|
|
const QString markText = issue.description;
|
|
|
|
|
const QString id = issue.kind + QString::number(issue.id.value_or(-1));
|
2024-02-13 14:20:23 +01:00
|
|
|
setToolTip(id + '\n' + markText);
|
2024-02-27 13:51:54 +01:00
|
|
|
setIcon(iconForIssue(issue.getOptionalKindEnum()));
|
2024-02-13 14:20:23 +01:00
|
|
|
if (color)
|
|
|
|
|
setColor(*color);
|
2024-02-02 11:24:48 +01:00
|
|
|
setPriority(TextMark::NormalPriority);
|
2024-02-02 11:23:18 +01:00
|
|
|
setLineAnnotation(markText);
|
2024-02-12 13:55:56 +01:00
|
|
|
setActionsProvider([id] {
|
2024-02-02 11:23:18 +01:00
|
|
|
auto action = new QAction;
|
2024-02-11 12:53:52 +01:00
|
|
|
action->setIcon(Icons::INFO.icon());
|
2024-02-02 11:23:18 +01:00
|
|
|
action->setToolTip(Tr::tr("Show rule details"));
|
|
|
|
|
QObject::connect(action, &QAction::triggered, dd, [id] { dd->fetchIssueInfo(id); });
|
|
|
|
|
return QList{action};
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-01-13 13:49:40 +01:00
|
|
|
};
|
|
|
|
|
|
2024-01-12 08:38:31 +01:00
|
|
|
void fetchProjectInfo(const QString &projectName)
|
2022-12-14 12:11:03 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd, return);
|
|
|
|
|
dd->fetchProjectInfo(projectName);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 12:29:17 +01:00
|
|
|
std::optional<Dto::ProjectInfoDto> projectInfo()
|
2022-12-14 13:53:00 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd, return {});
|
2023-01-09 07:41:22 +01:00
|
|
|
return dd->m_currentProjectInfo;
|
2022-12-14 13:53:00 +01:00
|
|
|
}
|
|
|
|
|
|
2023-09-15 12:25:04 +02:00
|
|
|
// FIXME: extend to give some details?
|
|
|
|
|
// FIXME: move when curl is no more in use?
|
2024-01-12 08:38:31 +01:00
|
|
|
bool handleCertificateIssue()
|
2023-09-15 12:25:04 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd, return false);
|
|
|
|
|
const QString serverHost = QUrl(settings().server.dashboard).host();
|
2024-01-30 14:01:58 +01:00
|
|
|
if (QMessageBox::question(ICore::dialogParent(), Tr::tr("Certificate Error"),
|
2023-09-15 12:25:04 +02:00
|
|
|
Tr::tr("Server certificate for %1 cannot be authenticated.\n"
|
|
|
|
|
"Do you want to disable SSL verification for this server?\n"
|
|
|
|
|
"Note: This can expose you to man-in-the-middle attack.")
|
|
|
|
|
.arg(serverHost))
|
|
|
|
|
!= QMessageBox::Yes) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
settings().server.validateCert = false;
|
|
|
|
|
settings().apply();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AxivionPluginPrivate::AxivionPluginPrivate()
|
|
|
|
|
{
|
2023-10-24 13:54:55 +02:00
|
|
|
#if QT_CONFIG(ssl)
|
2023-09-15 12:25:04 +02:00
|
|
|
connect(&m_networkAccessManager, &QNetworkAccessManager::sslErrors,
|
|
|
|
|
this, &AxivionPluginPrivate::handleSslErrors);
|
2023-10-24 13:54:55 +02:00
|
|
|
#endif // ssl
|
2023-09-15 12:25:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AxivionPluginPrivate::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
|
|
|
|
|
{
|
2023-10-24 13:54:55 +02:00
|
|
|
#if QT_CONFIG(ssl)
|
2023-09-15 12:25:04 +02:00
|
|
|
const QList<QSslError::SslError> accepted{
|
|
|
|
|
QSslError::CertificateNotYetValid, QSslError::CertificateExpired,
|
|
|
|
|
QSslError::InvalidCaCertificate, QSslError::CertificateUntrusted,
|
|
|
|
|
QSslError::HostNameMismatch
|
|
|
|
|
};
|
|
|
|
|
if (Utils::allOf(errors,
|
|
|
|
|
[&accepted](const QSslError &e) { return accepted.contains(e.error()); })) {
|
2024-01-12 08:38:31 +01:00
|
|
|
if (!settings().server.validateCert || handleCertificateIssue())
|
2023-09-15 12:25:04 +02:00
|
|
|
reply->ignoreSslErrors(errors);
|
|
|
|
|
}
|
2023-10-24 13:54:55 +02:00
|
|
|
#else // ssl
|
|
|
|
|
Q_UNUSED(reply)
|
|
|
|
|
Q_UNUSED(errors)
|
|
|
|
|
#endif // ssl
|
2023-09-15 12:25:04 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-03 21:06:15 +01:00
|
|
|
void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
|
2022-12-16 23:01:01 +01:00
|
|
|
{
|
2024-02-03 21:06:15 +01:00
|
|
|
if (project == m_project)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (m_project)
|
|
|
|
|
disconnect(m_project, &Project::fileListChanged, this, &AxivionPluginPrivate::handleOpenedDocs);
|
|
|
|
|
|
|
|
|
|
m_project = project;
|
|
|
|
|
clearAllMarks();
|
|
|
|
|
m_currentProjectInfo = {};
|
2024-02-22 15:35:03 +01:00
|
|
|
updateDashboard();
|
2024-02-03 21:06:15 +01:00
|
|
|
|
|
|
|
|
if (!m_project)
|
2022-12-16 23:01:01 +01:00
|
|
|
return;
|
|
|
|
|
|
2024-02-03 21:06:15 +01:00
|
|
|
connect(m_project, &Project::fileListChanged, this, &AxivionPluginPrivate::handleOpenedDocs);
|
|
|
|
|
const AxivionProjectSettings *projSettings = AxivionProjectSettings::projectSettings(m_project);
|
2022-12-16 23:01:01 +01:00
|
|
|
fetchProjectInfo(projSettings->dashboardProjectName());
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 12:29:17 +01:00
|
|
|
static QUrl urlForProject(const QString &projectName)
|
|
|
|
|
{
|
2024-02-28 01:25:36 +01:00
|
|
|
if (!dd->m_dashboardInfo)
|
|
|
|
|
return {};
|
|
|
|
|
return dd->m_dashboardInfo->source.resolved(QString("api/projects/")).resolved(projectName);
|
2023-11-24 12:29:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static constexpr int httpStatusCodeOk = 200;
|
2024-02-11 17:10:08 +01:00
|
|
|
constexpr char s_htmlContentType[] = "text/html";
|
|
|
|
|
constexpr char s_jsonContentType[] = "application/json";
|
2024-01-30 21:19:28 +01:00
|
|
|
|
2024-02-27 15:51:46 +01:00
|
|
|
static bool isServerAccessEstablished()
|
|
|
|
|
{
|
|
|
|
|
return dd->m_serverAccess == ServerAccess::NoAuthorization
|
|
|
|
|
|| (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 21:19:28 +01:00
|
|
|
static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QByteArray &)> &handler)
|
|
|
|
|
{
|
2024-02-11 18:22:39 +01:00
|
|
|
// TODO: Refactor so that it's a common code with fetchDataRecipe().
|
2024-02-15 00:08:16 +01:00
|
|
|
const auto onQuerySetup = [url](NetworkQuery &query) {
|
2024-02-27 15:51:46 +01:00
|
|
|
if (!isServerAccessEstablished())
|
2024-02-15 00:08:16 +01:00
|
|
|
return SetupResult::StopWithError; // TODO: start authorizationRecipe()?
|
2024-01-30 21:19:28 +01:00
|
|
|
|
|
|
|
|
QNetworkRequest request(url);
|
2024-02-11 17:10:08 +01:00
|
|
|
request.setRawHeader("Accept", s_htmlContentType);
|
2024-02-15 00:08:16 +01:00
|
|
|
if (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken)
|
|
|
|
|
request.setRawHeader("Authorization", "AxToken " + *dd->m_apiToken);
|
2024-02-11 17:10:08 +01:00
|
|
|
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
|
|
|
|
|
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
|
|
|
|
|
request.setRawHeader("X-Axivion-User-Agent", ua);
|
2024-01-30 21:19:28 +01:00
|
|
|
query.setRequest(request);
|
|
|
|
|
query.setNetworkAccessManager(&dd->m_networkAccessManager);
|
2024-02-15 00:08:16 +01:00
|
|
|
return SetupResult::Continue;
|
2024-01-30 21:19:28 +01:00
|
|
|
};
|
|
|
|
|
const auto onQueryDone = [url, handler](const NetworkQuery &query, DoneWith doneWith) {
|
|
|
|
|
QNetworkReply *reply = query.reply();
|
|
|
|
|
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
|
const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader)
|
|
|
|
|
.toString()
|
|
|
|
|
.split(';')
|
|
|
|
|
.constFirst()
|
|
|
|
|
.trimmed()
|
|
|
|
|
.toLower();
|
|
|
|
|
if (doneWith == DoneWith::Success && statusCode == httpStatusCodeOk
|
2024-02-11 17:10:08 +01:00
|
|
|
&& contentType == s_htmlContentType) {
|
2024-01-30 21:19:28 +01:00
|
|
|
handler(reply->readAll());
|
|
|
|
|
return DoneResult::Success;
|
|
|
|
|
}
|
|
|
|
|
return DoneResult::Error;
|
|
|
|
|
};
|
2024-02-15 00:08:16 +01:00
|
|
|
return {NetworkQueryTask(onQuerySetup, onQueryDone)};
|
2024-01-30 21:19:28 +01:00
|
|
|
}
|
2023-11-24 12:29:17 +01:00
|
|
|
|
2024-02-16 19:21:38 +01:00
|
|
|
template <typename DtoType, template <typename> typename DtoStorageType>
|
|
|
|
|
static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
|
2024-02-11 18:01:48 +01:00
|
|
|
{
|
2024-03-11 11:01:55 +01:00
|
|
|
const Storage<std::optional<QByteArray>> storage;
|
2023-11-24 12:29:17 +01:00
|
|
|
|
2024-02-11 18:01:48 +01:00
|
|
|
const auto onNetworkQuerySetup = [dtoStorage](NetworkQuery &query) {
|
|
|
|
|
QNetworkRequest request(dtoStorage->url);
|
2024-02-11 17:10:08 +01:00
|
|
|
request.setRawHeader("Accept", s_jsonContentType);
|
2024-02-16 16:17:27 +01:00
|
|
|
if (dtoStorage->credential) // Unauthorized access otherwise
|
|
|
|
|
request.setRawHeader("Authorization", *dtoStorage->credential);
|
2024-02-11 17:10:08 +01:00
|
|
|
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
|
|
|
|
|
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
|
|
|
|
|
request.setRawHeader("X-Axivion-User-Agent", ua);
|
2024-02-16 19:21:38 +01:00
|
|
|
|
|
|
|
|
if constexpr (std::is_same_v<DtoStorageType<DtoType>, PostDtoStorage<DtoType>>) {
|
|
|
|
|
request.setRawHeader("Content-Type", "application/json");
|
|
|
|
|
request.setRawHeader("AX-CSRF-Token", dtoStorage->csrfToken);
|
|
|
|
|
query.setWriteData(dtoStorage->writeData);
|
|
|
|
|
query.setOperation(NetworkOperation::Post);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 12:29:17 +01:00
|
|
|
query.setRequest(request);
|
2024-01-19 19:46:40 +01:00
|
|
|
query.setNetworkAccessManager(&dd->m_networkAccessManager);
|
2023-11-24 12:29:17 +01:00
|
|
|
};
|
|
|
|
|
|
2024-02-28 01:17:41 +01:00
|
|
|
const auto onNetworkQueryDone = [storage, dtoStorage](const NetworkQuery &query,
|
|
|
|
|
DoneWith doneWith) {
|
2024-02-14 08:18:49 +01:00
|
|
|
QNetworkReply *reply = query.reply();
|
|
|
|
|
const QNetworkReply::NetworkError error = reply->error();
|
|
|
|
|
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
|
const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader)
|
|
|
|
|
.toString()
|
|
|
|
|
.split(';')
|
|
|
|
|
.constFirst()
|
|
|
|
|
.trimmed()
|
|
|
|
|
.toLower();
|
|
|
|
|
if (doneWith == DoneWith::Success && statusCode == httpStatusCodeOk
|
|
|
|
|
&& contentType == s_jsonContentType) {
|
|
|
|
|
*storage = reply->readAll();
|
2024-02-28 01:17:41 +01:00
|
|
|
dtoStorage->url = reply->url();
|
2024-02-14 08:18:49 +01:00
|
|
|
return DoneResult::Success;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-28 16:40:10 +01:00
|
|
|
QString errorString;
|
|
|
|
|
if (contentType == s_jsonContentType) {
|
|
|
|
|
const Utils::expected_str<Dto::ErrorDto> error
|
|
|
|
|
= Dto::ErrorDto::deserializeExpected(reply->readAll());
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
if constexpr (std::is_same_v<DtoType, Dto::DashboardInfoDto>) {
|
|
|
|
|
// Suppress logging error on unauthorized dashboard fetch
|
2024-03-19 14:04:55 +01:00
|
|
|
if (!dtoStorage->credential && error->type == "UnauthenticatedException") {
|
|
|
|
|
dtoStorage->url = reply->url();
|
2024-03-11 11:01:55 +01:00
|
|
|
return DoneResult::Success;
|
2024-03-19 14:04:55 +01:00
|
|
|
}
|
2024-02-14 08:18:49 +01:00
|
|
|
}
|
2024-02-28 16:40:10 +01:00
|
|
|
|
|
|
|
|
errorString = Error(DashboardError(reply->url(), statusCode,
|
|
|
|
|
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
|
|
|
|
|
*error)).message();
|
2024-03-11 10:25:55 +01:00
|
|
|
} else {
|
|
|
|
|
errorString = error.error();
|
2024-02-14 08:18:49 +01:00
|
|
|
}
|
2024-02-28 16:40:10 +01:00
|
|
|
} else if (statusCode != 0) {
|
|
|
|
|
errorString = Error(HttpError(reply->url(), statusCode,
|
|
|
|
|
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
|
|
|
|
|
QString::fromUtf8(reply->readAll()))).message(); // encoding?
|
|
|
|
|
} else {
|
|
|
|
|
errorString = Error(NetworkError(reply->url(), error, reply->errorString())).message();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MessageManager::writeDisrupting(QString("Axivion: %1").arg(errorString));
|
2024-02-14 08:18:49 +01:00
|
|
|
return DoneResult::Error;
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-20 18:45:03 +01:00
|
|
|
const auto onDeserializeSetup = [storage](Async<expected_str<DtoType>> &task) {
|
2024-03-11 11:01:55 +01:00
|
|
|
if (!*storage)
|
|
|
|
|
return SetupResult::StopWithSuccess;
|
|
|
|
|
|
2024-02-20 18:45:03 +01:00
|
|
|
const auto deserialize = [](QPromise<expected_str<DtoType>> &promise, const QByteArray &input) {
|
|
|
|
|
promise.addResult(DtoType::deserializeExpected(input));
|
2024-02-14 08:18:49 +01:00
|
|
|
};
|
|
|
|
|
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
|
2024-03-11 11:01:55 +01:00
|
|
|
task.setConcurrentCallData(deserialize, **storage);
|
|
|
|
|
return SetupResult::Continue;
|
2024-02-14 08:18:49 +01:00
|
|
|
};
|
|
|
|
|
|
2024-02-20 18:45:03 +01:00
|
|
|
const auto onDeserializeDone = [dtoStorage](const Async<expected_str<DtoType>> &task,
|
|
|
|
|
DoneWith doneWith) {
|
2024-02-11 18:22:39 +01:00
|
|
|
if (doneWith == DoneWith::Success && task.isResultAvailable()) {
|
2024-02-20 18:45:03 +01:00
|
|
|
const auto result = task.result();
|
|
|
|
|
if (result) {
|
|
|
|
|
dtoStorage->dtoData = *result;
|
|
|
|
|
return DoneResult::Success;
|
|
|
|
|
}
|
|
|
|
|
MessageManager::writeFlashing(QString("Axivion: %1").arg(result.error()));
|
2024-02-11 18:22:39 +01:00
|
|
|
} else {
|
|
|
|
|
MessageManager::writeFlashing(QString("Axivion: %1")
|
2024-02-20 18:45:03 +01:00
|
|
|
.arg(Tr::tr("Unknown Dto structure deserialization error.")));
|
2024-02-11 18:22:39 +01:00
|
|
|
}
|
2024-02-20 18:45:03 +01:00
|
|
|
return DoneResult::Error;
|
2024-02-14 08:18:49 +01:00
|
|
|
};
|
|
|
|
|
|
2024-02-16 19:21:38 +01:00
|
|
|
return {
|
2023-11-24 12:29:17 +01:00
|
|
|
storage,
|
2024-02-11 17:10:08 +01:00
|
|
|
NetworkQueryTask(onNetworkQuerySetup, onNetworkQueryDone),
|
2024-02-20 18:45:03 +01:00
|
|
|
AsyncTask<expected_str<DtoType>>(onDeserializeSetup, onDeserializeDone)
|
2024-02-11 18:01:48 +01:00
|
|
|
};
|
2024-02-16 19:21:38 +01:00
|
|
|
}
|
2024-02-11 18:01:48 +01:00
|
|
|
|
2024-02-21 18:45:16 +01:00
|
|
|
static QString credentialOperationMessage(CredentialOperation operation)
|
|
|
|
|
{
|
|
|
|
|
switch (operation) {
|
|
|
|
|
case CredentialOperation::Get:
|
|
|
|
|
return Tr::tr("The ApiToken cannot be read in a secure way.");
|
|
|
|
|
case CredentialOperation::Set:
|
|
|
|
|
return Tr::tr("The ApiToken cannot be stored in a secure way.");
|
|
|
|
|
case CredentialOperation::Delete:
|
|
|
|
|
return Tr::tr("The ApiToken cannot be deleted in a secure way.");
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handleCredentialError(const CredentialQuery &credential)
|
|
|
|
|
{
|
|
|
|
|
const QString keyChainMessage = credential.errorString().isEmpty() ? QString()
|
|
|
|
|
: QString(" %1").arg(Tr::tr("Key chain message: \"%1\".").arg(credential.errorString()));
|
|
|
|
|
MessageManager::writeFlashing(QString("Axivion: %1")
|
|
|
|
|
.arg(credentialOperationMessage(credential.operation()) + keyChainMessage));
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 15:22:41 +01:00
|
|
|
static Group authorizationRecipe()
|
2024-02-11 18:01:48 +01:00
|
|
|
{
|
2024-03-19 14:04:55 +01:00
|
|
|
const Storage<QUrl> serverUrlStorage;
|
2024-02-16 16:17:27 +01:00
|
|
|
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> unauthorizedDashboardStorage;
|
2024-03-19 14:04:55 +01:00
|
|
|
const auto onUnauthorizedGroupSetup = [serverUrlStorage, unauthorizedDashboardStorage] {
|
|
|
|
|
unauthorizedDashboardStorage->url = *serverUrlStorage;
|
|
|
|
|
return isServerAccessEstablished() ? SetupResult::StopWithSuccess : SetupResult::Continue;
|
2024-02-16 16:17:27 +01:00
|
|
|
};
|
2024-03-11 11:01:55 +01:00
|
|
|
const auto onUnauthorizedDashboard = [unauthorizedDashboardStorage] {
|
2024-02-16 16:17:27 +01:00
|
|
|
if (unauthorizedDashboardStorage->dtoData) {
|
2024-02-28 15:07:00 +01:00
|
|
|
const Dto::DashboardInfoDto &dashboardInfo = *unauthorizedDashboardStorage->dtoData;
|
|
|
|
|
const QString &username = settings().server.username;
|
|
|
|
|
if (username.isEmpty()
|
|
|
|
|
|| (dashboardInfo.username && *dashboardInfo.username == username)) {
|
|
|
|
|
dd->m_serverAccess = ServerAccess::NoAuthorization;
|
|
|
|
|
dd->m_dashboardInfo = toDashboardInfo(*unauthorizedDashboardStorage);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
MessageManager::writeFlashing(QString("Axivion: %1")
|
|
|
|
|
.arg(Tr::tr("Unauthenticated access failed (wrong user), "
|
|
|
|
|
"using authenticated access...")));
|
2024-02-16 16:17:27 +01:00
|
|
|
}
|
2024-02-28 15:07:00 +01:00
|
|
|
dd->m_serverAccess = ServerAccess::WithAuthorization;
|
2024-02-16 16:17:27 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto onCredentialLoopCondition = [](int) {
|
|
|
|
|
return dd->m_serverAccess == ServerAccess::WithAuthorization && !dd->m_apiToken;
|
|
|
|
|
};
|
2024-02-11 18:22:39 +01:00
|
|
|
const auto onGetCredentialSetup = [](CredentialQuery &credential) {
|
|
|
|
|
credential.setOperation(CredentialOperation::Get);
|
|
|
|
|
credential.setService(s_axivionKeychainService);
|
|
|
|
|
credential.setKey(credentialKey());
|
2024-02-11 18:01:48 +01:00
|
|
|
};
|
2024-02-11 18:22:39 +01:00
|
|
|
const auto onGetCredentialDone = [](const CredentialQuery &credential, DoneWith result) {
|
|
|
|
|
if (result == DoneWith::Success)
|
|
|
|
|
dd->m_apiToken = credential.data();
|
2024-02-17 10:10:30 +01:00
|
|
|
else
|
2024-02-21 18:45:16 +01:00
|
|
|
handleCredentialError(credential);
|
|
|
|
|
// TODO: In case of an error we are multiplying the ApiTokens on Axivion server for each
|
|
|
|
|
// Creator run, but at least things should continue to work OK in the current session.
|
|
|
|
|
return DoneResult::Success;
|
2024-02-11 18:22:39 +01:00
|
|
|
};
|
2024-02-16 16:17:27 +01:00
|
|
|
|
|
|
|
|
const Storage<QString> passwordStorage;
|
|
|
|
|
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> dashboardStorage;
|
2024-03-19 14:04:55 +01:00
|
|
|
const auto onPasswordGroupSetup = [serverUrlStorage, passwordStorage, dashboardStorage] {
|
2024-02-11 18:22:39 +01:00
|
|
|
if (dd->m_apiToken)
|
|
|
|
|
return SetupResult::StopWithSuccess;
|
|
|
|
|
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const QString text(Tr::tr("Enter the password for:\nDashboard: %1\nUser: %2")
|
|
|
|
|
.arg(settings().server.dashboard, settings().server.username));
|
|
|
|
|
*passwordStorage = QInputDialog::getText(ICore::mainWindow(),
|
|
|
|
|
Tr::tr("Axivion Server Password"), text, QLineEdit::Password, {}, &ok);
|
|
|
|
|
if (!ok)
|
|
|
|
|
return SetupResult::StopWithError;
|
|
|
|
|
|
|
|
|
|
const QString credential = settings().server.username + ':' + *passwordStorage;
|
|
|
|
|
dashboardStorage->credential = "Basic " + credential.toUtf8().toBase64();
|
2024-03-19 14:04:55 +01:00
|
|
|
dashboardStorage->url = *serverUrlStorage;
|
2024-02-11 18:22:39 +01:00
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-16 16:17:27 +01:00
|
|
|
const Storage<PostDtoStorage<Dto::ApiTokenInfoDto>> apiTokenStorage;
|
2024-02-11 18:22:39 +01:00
|
|
|
const auto onApiTokenGroupSetup = [passwordStorage, dashboardStorage, apiTokenStorage] {
|
|
|
|
|
if (!dashboardStorage->dtoData)
|
|
|
|
|
return SetupResult::StopWithSuccess;
|
2024-02-11 18:01:48 +01:00
|
|
|
|
2024-02-28 01:17:41 +01:00
|
|
|
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
|
2024-02-11 18:22:39 +01:00
|
|
|
|
|
|
|
|
const Dto::DashboardInfoDto &dashboardDto = *dashboardStorage->dtoData;
|
|
|
|
|
if (!dashboardDto.userApiTokenUrl)
|
|
|
|
|
return SetupResult::StopWithError;
|
|
|
|
|
|
|
|
|
|
apiTokenStorage->credential = dashboardStorage->credential;
|
|
|
|
|
apiTokenStorage->url
|
2024-02-28 01:25:36 +01:00
|
|
|
= dd->m_dashboardInfo->source.resolved(*dashboardDto.userApiTokenUrl);
|
2024-02-11 18:22:39 +01:00
|
|
|
apiTokenStorage->csrfToken = dashboardDto.csrfToken.toUtf8();
|
|
|
|
|
const Dto::ApiTokenCreationRequestDto requestDto{*passwordStorage, "IdePlugin",
|
|
|
|
|
apiTokenDescription(), 0};
|
|
|
|
|
apiTokenStorage->writeData = requestDto.serialize();
|
|
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto onSetCredentialSetup = [apiTokenStorage](CredentialQuery &credential) {
|
|
|
|
|
if (!apiTokenStorage->dtoData || !apiTokenStorage->dtoData->token)
|
|
|
|
|
return SetupResult::StopWithSuccess;
|
|
|
|
|
|
|
|
|
|
dd->m_apiToken = apiTokenStorage->dtoData->token->toUtf8();
|
|
|
|
|
credential.setOperation(CredentialOperation::Set);
|
|
|
|
|
credential.setService(s_axivionKeychainService);
|
|
|
|
|
credential.setKey(credentialKey());
|
|
|
|
|
credential.setData(*dd->m_apiToken);
|
|
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
2024-02-17 10:10:30 +01:00
|
|
|
const auto onSetCredentialDone = [](const CredentialQuery &credential) {
|
2024-02-21 18:45:16 +01:00
|
|
|
handleCredentialError(credential);
|
|
|
|
|
// TODO: In case of an error we are multiplying the ApiTokens on Axivion server for each
|
|
|
|
|
// Creator run, but at least things should continue to work OK in the current session.
|
2024-02-21 16:50:34 +01:00
|
|
|
return DoneResult::Success;
|
2024-02-17 10:10:30 +01:00
|
|
|
};
|
2024-02-11 18:22:39 +01:00
|
|
|
|
2024-03-19 14:04:55 +01:00
|
|
|
const auto onDashboardGroupSetup = [serverUrlStorage, dashboardStorage] {
|
2024-02-28 01:17:41 +01:00
|
|
|
if (dd->m_dashboardInfo || dd->m_serverAccess != ServerAccess::WithAuthorization
|
|
|
|
|
|| !dd->m_apiToken) {
|
|
|
|
|
return SetupResult::StopWithSuccess; // Unauthorized access should have collect dashboard before
|
|
|
|
|
}
|
|
|
|
|
dashboardStorage->credential = "AxToken " + *dd->m_apiToken;
|
2024-03-19 14:04:55 +01:00
|
|
|
dashboardStorage->url = *serverUrlStorage;
|
2024-02-28 01:17:41 +01:00
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
|
|
|
|
const auto onDeleteCredentialSetup = [dashboardStorage](CredentialQuery &credential) {
|
|
|
|
|
if (dashboardStorage->dtoData) {
|
|
|
|
|
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
|
|
|
|
|
return SetupResult::StopWithSuccess;
|
|
|
|
|
}
|
|
|
|
|
dd->m_apiToken = {};
|
|
|
|
|
MessageManager::writeFlashing(QString("Axivion: %1")
|
|
|
|
|
.arg(Tr::tr("The stored ApiToken is not valid anymore, removing it.")));
|
|
|
|
|
credential.setOperation(CredentialOperation::Delete);
|
|
|
|
|
credential.setService(s_axivionKeychainService);
|
|
|
|
|
credential.setKey(credentialKey());
|
|
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-16 15:22:41 +01:00
|
|
|
return {
|
2024-03-19 14:04:55 +01:00
|
|
|
serverUrlStorage,
|
|
|
|
|
onGroupSetup([serverUrlStorage] { *serverUrlStorage = QUrl(settings().server.dashboard); }),
|
2024-02-11 18:01:48 +01:00
|
|
|
Group {
|
2024-02-16 16:17:27 +01:00
|
|
|
unauthorizedDashboardStorage,
|
|
|
|
|
onGroupSetup(onUnauthorizedGroupSetup),
|
2024-02-16 19:21:38 +01:00
|
|
|
dtoRecipe(unauthorizedDashboardStorage),
|
2024-03-19 14:04:55 +01:00
|
|
|
Sync(onUnauthorizedDashboard),
|
|
|
|
|
onGroupDone([serverUrlStorage, unauthorizedDashboardStorage] {
|
|
|
|
|
*serverUrlStorage = unauthorizedDashboardStorage->url;
|
|
|
|
|
}),
|
2024-02-16 16:17:27 +01:00
|
|
|
},
|
|
|
|
|
Group {
|
|
|
|
|
LoopUntil(onCredentialLoopCondition),
|
2024-02-11 18:22:39 +01:00
|
|
|
CredentialQueryTask(onGetCredentialSetup, onGetCredentialDone),
|
|
|
|
|
Group {
|
|
|
|
|
passwordStorage,
|
|
|
|
|
dashboardStorage,
|
2024-02-28 01:17:41 +01:00
|
|
|
onGroupSetup(onPasswordGroupSetup),
|
2024-02-11 18:22:39 +01:00
|
|
|
Group { // GET DashboardInfoDto
|
|
|
|
|
finishAllAndSuccess,
|
2024-02-16 19:21:38 +01:00
|
|
|
dtoRecipe(dashboardStorage)
|
2024-02-11 18:22:39 +01:00
|
|
|
},
|
|
|
|
|
Group { // POST ApiTokenCreationRequestDto, GET ApiTokenInfoDto.
|
|
|
|
|
apiTokenStorage,
|
|
|
|
|
onGroupSetup(onApiTokenGroupSetup),
|
2024-02-16 19:21:38 +01:00
|
|
|
dtoRecipe(apiTokenStorage),
|
2024-02-17 10:10:30 +01:00
|
|
|
CredentialQueryTask(onSetCredentialSetup, onSetCredentialDone, CallDoneIf::Error)
|
2024-02-11 18:22:39 +01:00
|
|
|
}
|
2024-02-28 01:17:41 +01:00
|
|
|
},
|
|
|
|
|
Group {
|
|
|
|
|
finishAllAndSuccess,
|
|
|
|
|
dashboardStorage,
|
|
|
|
|
onGroupSetup(onDashboardGroupSetup),
|
|
|
|
|
dtoRecipe(dashboardStorage),
|
|
|
|
|
CredentialQueryTask(onDeleteCredentialSetup)
|
2024-02-11 18:22:39 +01:00
|
|
|
}
|
2024-02-16 15:22:41 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename DtoType>
|
|
|
|
|
static Group fetchDataRecipe(const QUrl &url, const std::function<void(const DtoType &)> &handler)
|
|
|
|
|
{
|
|
|
|
|
const Storage<GetDtoStorage<DtoType>> dtoStorage;
|
|
|
|
|
|
|
|
|
|
const auto onDtoSetup = [dtoStorage, url] {
|
2024-02-27 15:51:46 +01:00
|
|
|
if (!isServerAccessEstablished())
|
2024-02-16 15:22:41 +01:00
|
|
|
return SetupResult::StopWithError;
|
|
|
|
|
|
2024-02-27 15:51:46 +01:00
|
|
|
if (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken)
|
|
|
|
|
dtoStorage->credential = "AxToken " + *dd->m_apiToken;
|
2024-02-16 15:22:41 +01:00
|
|
|
dtoStorage->url = url;
|
|
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
|
|
|
|
const auto onDtoDone = [dtoStorage, handler] {
|
|
|
|
|
if (dtoStorage->dtoData)
|
|
|
|
|
handler(*dtoStorage->dtoData);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Group recipe {
|
|
|
|
|
authorizationRecipe(),
|
2024-02-11 18:22:39 +01:00
|
|
|
Group {
|
|
|
|
|
dtoStorage,
|
|
|
|
|
onGroupSetup(onDtoSetup),
|
2024-02-16 19:21:38 +01:00
|
|
|
dtoRecipe(dtoStorage),
|
2024-02-11 18:01:48 +01:00
|
|
|
onGroupDone(onDtoDone)
|
|
|
|
|
}
|
2024-01-19 19:46:40 +01:00
|
|
|
};
|
|
|
|
|
return recipe;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-23 22:29:59 +01:00
|
|
|
Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
|
|
|
|
|
{
|
|
|
|
|
const auto onSetup = [handler] {
|
|
|
|
|
if (dd->m_dashboardInfo) {
|
2024-02-28 01:17:41 +01:00
|
|
|
handler(*dd->m_dashboardInfo);
|
2024-01-23 22:29:59 +01:00
|
|
|
return SetupResult::StopWithSuccess;
|
|
|
|
|
}
|
|
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
2024-02-28 01:17:41 +01:00
|
|
|
const auto onDone = [handler](DoneWith result) {
|
|
|
|
|
if (result == DoneWith::Success && dd->m_dashboardInfo)
|
2024-01-23 22:29:59 +01:00
|
|
|
handler(*dd->m_dashboardInfo);
|
2024-02-28 01:17:41 +01:00
|
|
|
else
|
|
|
|
|
handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
|
2024-01-23 22:29:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Group root {
|
|
|
|
|
onGroupSetup(onSetup), // Stops if cache exists.
|
2024-02-28 01:17:41 +01:00
|
|
|
authorizationRecipe(),
|
|
|
|
|
onGroupDone(onDone)
|
2024-01-23 22:29:59 +01:00
|
|
|
};
|
|
|
|
|
return root;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-27 13:33:45 +01:00
|
|
|
Group issueTableRecipe(const IssueListSearch &search, const IssueTableHandler &handler)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd->m_currentProjectInfo, return {}); // TODO: Call handler with unexpected?
|
|
|
|
|
|
|
|
|
|
const QString query = search.toQuery();
|
|
|
|
|
if (query.isEmpty())
|
|
|
|
|
return {}; // TODO: Call handler with unexpected?
|
|
|
|
|
|
|
|
|
|
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
|
|
|
|
.resolved(QString("issues" + query));
|
|
|
|
|
return fetchDataRecipe<Dto::IssueTableDto>(url, handler);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 12:53:52 +01:00
|
|
|
Group lineMarkerRecipe(const FilePath &filePath, const LineMarkerHandler &handler)
|
2024-02-12 13:55:56 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd->m_currentProjectInfo, return {}); // TODO: Call handler with unexpected?
|
|
|
|
|
QTC_ASSERT(!filePath.isEmpty(), return {}); // TODO: Call handler with unexpected?
|
|
|
|
|
|
|
|
|
|
const QString fileName = QString::fromUtf8(QUrl::toPercentEncoding(filePath.path()));
|
|
|
|
|
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
|
|
|
|
.resolved(QString("files?filename=" + fileName));
|
|
|
|
|
return fetchDataRecipe<Dto::FileViewDto>(url, handler);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 21:19:28 +01:00
|
|
|
Group issueHtmlRecipe(const QString &issueId, const HtmlHandler &handler)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd->m_currentProjectInfo, return {}); // TODO: Call handler with unexpected?
|
|
|
|
|
|
|
|
|
|
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
|
|
|
|
.resolved(QString("issues/"))
|
|
|
|
|
.resolved(QString(issueId + '/'))
|
|
|
|
|
.resolved(QString("properties"));
|
|
|
|
|
|
|
|
|
|
return fetchHtmlRecipe(url, handler);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 19:46:40 +01:00
|
|
|
void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
|
|
|
|
|
{
|
2024-02-03 21:06:15 +01:00
|
|
|
if (!m_project)
|
2024-01-19 19:46:40 +01:00
|
|
|
return;
|
2024-02-03 21:06:15 +01:00
|
|
|
|
2024-01-19 19:46:40 +01:00
|
|
|
clearAllMarks();
|
|
|
|
|
if (projectName.isEmpty()) {
|
|
|
|
|
m_currentProjectInfo = {};
|
2024-02-22 15:35:03 +01:00
|
|
|
updateDashboard();
|
2024-01-19 19:46:40 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 00:06:55 +01:00
|
|
|
const auto onTaskTreeSetup = [this, projectName](TaskTree &taskTree) {
|
2024-02-21 15:32:30 +01:00
|
|
|
if (!m_dashboardInfo) {
|
|
|
|
|
MessageManager::writeDisrupting(QString("Axivion: %1")
|
|
|
|
|
.arg(Tr::tr("Fetching DashboardInfo error.")));
|
2024-01-24 00:06:55 +01:00
|
|
|
return SetupResult::StopWithError;
|
2024-02-21 15:32:30 +01:00
|
|
|
}
|
2024-01-24 00:06:55 +01:00
|
|
|
|
|
|
|
|
const auto it = m_dashboardInfo->projectUrls.constFind(projectName);
|
2024-02-21 15:32:30 +01:00
|
|
|
if (it == m_dashboardInfo->projectUrls.constEnd()) {
|
|
|
|
|
MessageManager::writeDisrupting(QString("Axivion: %1")
|
|
|
|
|
.arg(Tr::tr("The DashboardInfo doesn't contain project \"%1\".").arg(projectName)));
|
2024-01-24 00:06:55 +01:00
|
|
|
return SetupResult::StopWithError;
|
2024-02-21 15:32:30 +01:00
|
|
|
}
|
2024-01-24 00:06:55 +01:00
|
|
|
|
|
|
|
|
const auto handler = [this](const Dto::ProjectInfoDto &data) {
|
|
|
|
|
m_currentProjectInfo = data;
|
2024-02-22 15:35:03 +01:00
|
|
|
updateDashboard();
|
2024-02-03 21:06:15 +01:00
|
|
|
handleOpenedDocs();
|
2024-01-24 00:06:55 +01:00
|
|
|
};
|
2024-01-19 19:46:40 +01:00
|
|
|
|
2024-02-28 01:25:36 +01:00
|
|
|
taskTree.setRecipe(
|
|
|
|
|
fetchDataRecipe<Dto::ProjectInfoDto>(m_dashboardInfo->source.resolved(*it), handler));
|
2024-01-24 00:06:55 +01:00
|
|
|
return SetupResult::Continue;
|
2023-11-24 12:29:17 +01:00
|
|
|
};
|
|
|
|
|
|
2024-01-24 00:06:55 +01:00
|
|
|
const Group root {
|
2024-02-28 01:17:41 +01:00
|
|
|
authorizationRecipe(),
|
2024-01-24 00:06:55 +01:00
|
|
|
TaskTreeTask(onTaskTreeSetup)
|
|
|
|
|
};
|
|
|
|
|
m_taskTreeRunner.start(root);
|
2022-12-14 12:11:03 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-27 14:23:39 +01:00
|
|
|
Group tableInfoRecipe(const QString &prefix, const TableInfoHandler &handler)
|
2024-01-19 15:06:34 +01:00
|
|
|
{
|
2024-01-27 14:23:39 +01:00
|
|
|
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
|
|
|
|
.resolved(QString("issues_meta?kind=" + prefix));
|
|
|
|
|
return fetchDataRecipe<Dto::TableInfoDto>(url, handler);
|
2024-01-19 15:06:34 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 21:19:28 +01:00
|
|
|
void AxivionPluginPrivate::fetchIssueInfo(const QString &id)
|
2023-01-13 14:38:39 +01:00
|
|
|
{
|
2024-01-30 15:15:50 +01:00
|
|
|
if (!m_currentProjectInfo)
|
|
|
|
|
return;
|
|
|
|
|
|
2024-01-30 21:19:28 +01:00
|
|
|
const auto ruleHandler = [](const QByteArray &htmlText) {
|
2024-02-02 15:24:31 +01:00
|
|
|
QByteArray fixedHtml = htmlText;
|
|
|
|
|
const int idx = htmlText.indexOf("<div class=\"ax-issuedetails-table-container\">");
|
|
|
|
|
if (idx >= 0)
|
|
|
|
|
fixedHtml = "<html><body>" + htmlText.mid(idx);
|
2024-02-19 13:37:20 +01:00
|
|
|
|
|
|
|
|
NavigationWidget::activateSubWidget("Axivion.Issue", Side::Right);
|
|
|
|
|
dd->setIssueDetails(QString::fromUtf8(fixedHtml));
|
2024-01-30 21:19:28 +01:00
|
|
|
};
|
2023-01-13 14:38:39 +01:00
|
|
|
|
2024-02-12 13:55:56 +01:00
|
|
|
m_issueInfoRunner.start(issueHtmlRecipe(id, ruleHandler));
|
2023-01-13 14:38:39 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-19 13:37:20 +01:00
|
|
|
void AxivionPluginPrivate::setIssueDetails(const QString &issueDetailsHtml)
|
|
|
|
|
{
|
|
|
|
|
emit issueDetailsChanged(issueDetailsHtml);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-03 21:06:15 +01:00
|
|
|
void AxivionPluginPrivate::handleOpenedDocs()
|
2022-12-16 15:12:39 +01:00
|
|
|
{
|
2024-01-30 14:01:58 +01:00
|
|
|
const QList<IDocument *> openDocuments = DocumentModel::openedDocuments();
|
|
|
|
|
for (IDocument *doc : openDocuments)
|
2022-12-16 15:12:39 +01:00
|
|
|
onDocumentOpened(doc);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-16 22:34:56 +01:00
|
|
|
void AxivionPluginPrivate::clearAllMarks()
|
|
|
|
|
{
|
2024-01-30 14:01:58 +01:00
|
|
|
const QList<IDocument *> openDocuments = DocumentModel::openedDocuments();
|
|
|
|
|
for (IDocument *doc : openDocuments)
|
2022-12-16 22:34:56 +01:00
|
|
|
onDocumentClosed(doc);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 14:01:58 +01:00
|
|
|
void AxivionPluginPrivate::onDocumentOpened(IDocument *doc)
|
2022-12-16 23:11:46 +01:00
|
|
|
{
|
2024-02-03 21:06:15 +01:00
|
|
|
if (!doc || !m_currentProjectInfo || !m_project || !m_project->isKnownFile(doc->filePath()))
|
2022-12-16 23:11:46 +01:00
|
|
|
return;
|
|
|
|
|
|
2024-02-12 13:55:56 +01:00
|
|
|
const FilePath filePath = doc->filePath().relativeChildPath(m_project->projectDirectory());
|
2024-02-26 14:54:41 +01:00
|
|
|
if (filePath.isEmpty())
|
|
|
|
|
return; // Empty is fine
|
2024-01-30 15:15:50 +01:00
|
|
|
|
2024-02-12 13:55:56 +01:00
|
|
|
const auto handler = [this](const Dto::FileViewDto &data) {
|
|
|
|
|
if (data.lineMarkers.empty())
|
|
|
|
|
return;
|
|
|
|
|
handleIssuesForFile(data);
|
|
|
|
|
};
|
2024-01-30 15:15:50 +01:00
|
|
|
TaskTree *taskTree = new TaskTree;
|
2024-02-12 13:55:56 +01:00
|
|
|
taskTree->setRecipe(lineMarkerRecipe(filePath, handler));
|
2024-01-30 15:15:50 +01:00
|
|
|
m_docMarksTrees.insert_or_assign(doc, std::unique_ptr<TaskTree>(taskTree));
|
|
|
|
|
connect(taskTree, &TaskTree::done, this, [this, doc] {
|
|
|
|
|
const auto it = m_docMarksTrees.find(doc);
|
|
|
|
|
QTC_ASSERT(it != m_docMarksTrees.end(), return);
|
|
|
|
|
it->second.release()->deleteLater();
|
|
|
|
|
m_docMarksTrees.erase(it);
|
2022-12-16 23:11:46 +01:00
|
|
|
});
|
2024-01-30 15:15:50 +01:00
|
|
|
taskTree->start();
|
2022-12-16 23:11:46 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 14:01:58 +01:00
|
|
|
void AxivionPluginPrivate::onDocumentClosed(IDocument *doc)
|
2022-12-16 23:11:46 +01:00
|
|
|
{
|
2024-02-02 11:24:48 +01:00
|
|
|
const auto document = qobject_cast<TextDocument *>(doc);
|
2022-12-16 23:11:46 +01:00
|
|
|
if (!document)
|
|
|
|
|
return;
|
|
|
|
|
|
2024-01-30 15:15:50 +01:00
|
|
|
const auto it = m_docMarksTrees.find(doc);
|
|
|
|
|
if (it != m_docMarksTrees.end())
|
|
|
|
|
m_docMarksTrees.erase(it);
|
|
|
|
|
|
2024-02-02 11:26:14 +01:00
|
|
|
const TextMarks &marks = document->marks();
|
|
|
|
|
for (TextMark *mark : marks) {
|
2024-02-11 18:22:39 +01:00
|
|
|
if (mark->category().id == s_axivionTextMarkId)
|
2024-02-02 11:26:14 +01:00
|
|
|
delete mark;
|
2022-12-16 23:11:46 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 13:55:56 +01:00
|
|
|
void AxivionPluginPrivate::handleIssuesForFile(const Dto::FileViewDto &fileView)
|
2022-12-16 23:11:46 +01:00
|
|
|
{
|
2024-02-12 13:55:56 +01:00
|
|
|
if (fileView.lineMarkers.empty())
|
2022-12-16 23:11:46 +01:00
|
|
|
return;
|
|
|
|
|
|
2024-02-03 21:05:01 +01:00
|
|
|
Project *project = ProjectManager::startupProject();
|
2022-12-16 23:11:46 +01:00
|
|
|
if (!project)
|
|
|
|
|
return;
|
|
|
|
|
|
2024-02-12 13:55:56 +01:00
|
|
|
const FilePath filePath = project->projectDirectory().pathAppended(fileView.fileName);
|
2024-02-13 14:20:23 +01:00
|
|
|
std::optional<Theme::Color> color = std::nullopt;
|
|
|
|
|
if (settings().highlightMarks())
|
|
|
|
|
color.emplace(Theme::Color(Theme::Bookmarks_TextMarkColor)); // FIXME!
|
2024-02-12 13:55:56 +01:00
|
|
|
for (const Dto::LineMarkerDto &marker : std::as_const(fileView.lineMarkers)) {
|
2022-12-16 23:11:46 +01:00
|
|
|
// FIXME the line location can be wrong (even the whole issue could be wrong)
|
|
|
|
|
// depending on whether this line has been changed since the last axivion run and the
|
|
|
|
|
// current state of the file - some magic has to happen here
|
2024-02-13 14:20:23 +01:00
|
|
|
new AxivionTextMark(filePath, marker, color);
|
2022-12-16 23:11:46 +01:00
|
|
|
}
|
2022-12-14 12:11:03 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-21 09:54:31 +01:00
|
|
|
void AxivionPluginPrivate::handleAnchorClicked(const QUrl &url)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd, return);
|
|
|
|
|
QTC_ASSERT(dd->m_project, return);
|
2024-02-21 10:40:02 +01:00
|
|
|
if (!url.scheme().isEmpty()) {
|
|
|
|
|
const QString detail = Tr::tr("The activated link appears to be external.\n"
|
|
|
|
|
"Do you want to open \"%1\" with its default application?")
|
|
|
|
|
.arg(url.toString());
|
|
|
|
|
const QMessageBox::StandardButton pressed
|
|
|
|
|
= CheckableMessageBox::question(Core::ICore::dialogParent(),
|
|
|
|
|
Tr::tr("Open External Links"),
|
|
|
|
|
detail,
|
|
|
|
|
Key("AxivionOpenExternalLinks"));
|
|
|
|
|
if (pressed == QMessageBox::Yes)
|
|
|
|
|
QDesktopServices::openUrl(url);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-02-21 09:54:31 +01:00
|
|
|
const QUrlQuery query(url);
|
|
|
|
|
if (query.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
Link link;
|
|
|
|
|
if (const QString path = query.queryItemValue("filename", QUrl::FullyDecoded); !path.isEmpty())
|
|
|
|
|
link.targetFilePath = m_project->projectDirectory().pathAppended(path);
|
|
|
|
|
if (const QString line = query.queryItemValue("line"); !line.isEmpty())
|
|
|
|
|
link.targetLine = line.toInt();
|
|
|
|
|
// column entry is wrong - so, ignore it
|
|
|
|
|
if (link.hasValidTarget() && link.targetFilePath.exists())
|
|
|
|
|
EditorManager::openEditorAt(link);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-19 13:37:20 +01:00
|
|
|
class AxivionIssueWidgetFactory final : public INavigationWidgetFactory
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
AxivionIssueWidgetFactory()
|
|
|
|
|
{
|
|
|
|
|
setDisplayName(Tr::tr("Axivion"));
|
|
|
|
|
setId("Axivion.Issue");
|
|
|
|
|
setPriority(555);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NavigationView createWidget() final
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd, return {});
|
|
|
|
|
QTextBrowser *browser = new QTextBrowser;
|
|
|
|
|
browser->setOpenLinks(false);
|
|
|
|
|
NavigationView view;
|
|
|
|
|
view.widget = browser;
|
|
|
|
|
connect(dd, &AxivionPluginPrivate::issueDetailsChanged, browser, &QTextBrowser::setHtml);
|
2024-02-21 09:54:31 +01:00
|
|
|
connect(browser, &QTextBrowser::anchorClicked,
|
|
|
|
|
dd, &AxivionPluginPrivate::handleAnchorClicked);
|
2024-02-19 13:37:20 +01:00
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void setupAxivionIssueWidgetFactory()
|
|
|
|
|
{
|
|
|
|
|
static AxivionIssueWidgetFactory issueWidgetFactory;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-12 08:38:31 +01:00
|
|
|
class AxivionPlugin final : public ExtensionSystem::IPlugin
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Axivion.json")
|
|
|
|
|
|
|
|
|
|
~AxivionPlugin() final
|
|
|
|
|
{
|
|
|
|
|
AxivionProjectSettings::destroyProjectSettings();
|
|
|
|
|
delete dd;
|
|
|
|
|
dd = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void initialize() final
|
|
|
|
|
{
|
2024-02-22 15:35:03 +01:00
|
|
|
setupAxivionOutputPane(this);
|
|
|
|
|
|
2024-01-12 08:38:31 +01:00
|
|
|
dd = new AxivionPluginPrivate;
|
|
|
|
|
|
|
|
|
|
AxivionProjectSettings::setupProjectPanel();
|
2024-02-19 13:37:20 +01:00
|
|
|
setupAxivionIssueWidgetFactory();
|
2024-01-12 08:38:31 +01:00
|
|
|
|
2024-02-03 21:05:01 +01:00
|
|
|
connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
|
2024-01-12 08:38:31 +01:00
|
|
|
dd, &AxivionPluginPrivate::onStartupProjectChanged);
|
2024-01-30 14:01:58 +01:00
|
|
|
connect(EditorManager::instance(), &EditorManager::documentOpened,
|
2024-01-12 08:38:31 +01:00
|
|
|
dd, &AxivionPluginPrivate::onDocumentOpened);
|
2024-01-30 14:01:58 +01:00
|
|
|
connect(EditorManager::instance(), &EditorManager::documentClosed,
|
2024-01-12 08:38:31 +01:00
|
|
|
dd, &AxivionPluginPrivate::onDocumentClosed);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-21 09:14:39 +01:00
|
|
|
void fetchIssueInfo(const QString &id)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd, return);
|
|
|
|
|
dd->fetchIssueInfo(id);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 15:52:26 +01:00
|
|
|
const std::optional<DashboardInfo> currentDashboardInfo()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(dd, return std::nullopt);
|
|
|
|
|
return dd->m_dashboardInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 09:48:11 +01:00
|
|
|
} // Axivion::Internal
|
2024-01-12 08:38:31 +01:00
|
|
|
|
|
|
|
|
#include "axivionplugin.moc"
|