Axivion: Employ TaskTree

Change-Id: I55678f67d990c569073571055dbfe820b2fd324e
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Jarek Kobus
2023-11-24 12:29:17 +01:00
parent d77f1b09c5
commit 40a0d1db88
8 changed files with 135 additions and 376 deletions

View File

@@ -13,6 +13,5 @@ add_qtc_plugin(Axivion
axiviontr.h axiviontr.h
dashboard/dto.cpp dashboard/dto.h dashboard/dto.cpp dashboard/dto.h
dashboard/concat.cpp dashboard/concat.h dashboard/concat.cpp dashboard/concat.h
dashboard/dashboardclient.cpp dashboard/dashboardclient.h
dashboard/error.h dashboard/error.cpp dashboard/error.h dashboard/error.cpp
) )

View File

@@ -39,8 +39,6 @@ QtcPlugin {
"concat.h", "concat.h",
"dto.cpp", "dto.cpp",
"dto.h", "dto.h",
"dashboardclient.cpp",
"dashboardclient.h",
"error.cpp", "error.cpp",
"error.h", "error.h",
] ]

View File

@@ -94,10 +94,10 @@ void DashboardWidget::updateUi()
delete child->widget(); delete child->widget();
delete child; delete child;
} }
std::shared_ptr<const DashboardClient::ProjectInfo> projectInfo = Internal::projectInfo(); std::optional<Dto::ProjectInfoDto> projectInfo = Internal::projectInfo();
if (!projectInfo) if (!projectInfo)
return; return;
const Dto::ProjectInfoDto &info = projectInfo->data; const Dto::ProjectInfoDto &info = *projectInfo;
m_project->setText(info.name); m_project->setText(info.name);
if (info.versions.empty()) if (info.versions.empty())
return; return;
@@ -154,11 +154,11 @@ void DashboardWidget::updateUi()
for (const Dto::Any::MapEntry &issueCount : last.issueCounts.getMap()) { for (const Dto::Any::MapEntry &issueCount : last.issueCounts.getMap()) {
if (issueCount.second.isMap()) { if (issueCount.second.isMap()) {
const Dto::Any::Map &counts = issueCount.second.getMap(); const Dto::Any::Map &counts = issueCount.second.getMap();
qint64 total = extract_value(counts, QStringLiteral(u"Total")); qint64 total = extract_value(counts, QStringLiteral("Total"));
allTotal += total; allTotal += total;
qint64 added = extract_value(counts, QStringLiteral(u"Added")); qint64 added = extract_value(counts, QStringLiteral("Added"));
allAdded += added; allAdded += added;
qint64 removed = extract_value(counts, QStringLiteral(u"Removed")); qint64 removed = extract_value(counts, QStringLiteral("Removed"));
allRemoved += removed; allRemoved += removed;
addValuesWidgets(issueCount.first, total, added, removed, row); addValuesWidgets(issueCount.first, total, added, removed, row);
++row; ++row;

View File

@@ -9,8 +9,8 @@
#include "axivionresultparser.h" #include "axivionresultparser.h"
#include "axivionsettings.h" #include "axivionsettings.h"
#include "axiviontr.h" #include "axiviontr.h"
#include "dashboard/dashboardclient.h"
#include "dashboard/dto.h" #include "dashboard/dto.h"
#include "dashboard/error.h"
#include <coreplugin/editormanager/documentmodel.h> #include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
@@ -24,11 +24,15 @@
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h> #include <projectexplorer/projectmanager.h>
#include <solutions/tasking/networkquery.h>
#include <solutions/tasking/tasktreerunner.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <texteditor/textmark.h> #include <texteditor/textmark.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/expected.h> #include <utils/expected.h>
#include <utils/networkaccessmanager.h> #include <utils/networkaccessmanager.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -46,6 +50,9 @@
constexpr char AxivionTextMarkId[] = "AxivionTextMark"; constexpr char AxivionTextMarkId[] = "AxivionTextMark";
using namespace Tasking;
using namespace Utils;
namespace Axivion::Internal { namespace Axivion::Internal {
class AxivionPluginPrivate : public QObject class AxivionPluginPrivate : public QObject
@@ -55,7 +62,6 @@ public:
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors); void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
void onStartupProjectChanged(); void onStartupProjectChanged();
void fetchProjectInfo(const QString &projectName); void fetchProjectInfo(const QString &projectName);
void handleProjectInfo(DashboardClient::RawProjectInfo rawInfo);
void handleOpenedDocs(ProjectExplorer::Project *project); void handleOpenedDocs(ProjectExplorer::Project *project);
void onDocumentOpened(Core::IDocument *doc); void onDocumentOpened(Core::IDocument *doc);
void onDocumentClosed(Core::IDocument * doc); void onDocumentClosed(Core::IDocument * doc);
@@ -63,10 +69,11 @@ public:
void handleIssuesForFile(const IssuesList &issues); void handleIssuesForFile(const IssuesList &issues);
void fetchRuleInfo(const QString &id); void fetchRuleInfo(const QString &id);
Utils::NetworkAccessManager m_networkAccessManager; NetworkAccessManager m_networkAccessManager;
AxivionOutputPane m_axivionOutputPane; AxivionOutputPane m_axivionOutputPane;
std::shared_ptr<const DashboardClient::ProjectInfo> m_currentProjectInfo; std::optional<Dto::ProjectInfoDto> m_currentProjectInfo;
bool m_runningQuery = false; bool m_runningQuery = false;
TaskTreeRunner m_taskTreeRunner;
}; };
static AxivionPluginPrivate *dd = nullptr; static AxivionPluginPrivate *dd = nullptr;
@@ -74,13 +81,13 @@ static AxivionPluginPrivate *dd = nullptr;
class AxivionTextMark : public TextEditor::TextMark class AxivionTextMark : public TextEditor::TextMark
{ {
public: public:
AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue); AxivionTextMark(const FilePath &filePath, const ShortIssue &issue);
private: private:
QString m_id; QString m_id;
}; };
AxivionTextMark::AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue) AxivionTextMark::AxivionTextMark(const FilePath &filePath, const ShortIssue &issue)
: TextEditor::TextMark(filePath, issue.lineNumber, {Tr::tr("Axivion"), AxivionTextMarkId}) : TextEditor::TextMark(filePath, issue.lineNumber, {Tr::tr("Axivion"), AxivionTextMarkId})
, m_id(issue.id) , m_id(issue.id)
{ {
@@ -105,7 +112,7 @@ void fetchProjectInfo(const QString &projectName)
dd->fetchProjectInfo(projectName); dd->fetchProjectInfo(projectName);
} }
std::shared_ptr<const DashboardClient::ProjectInfo> projectInfo() std::optional<Dto::ProjectInfoDto> projectInfo()
{ {
QTC_ASSERT(dd, return {}); QTC_ASSERT(dd, return {});
return dd->m_currentProjectInfo; return dd->m_currentProjectInfo;
@@ -172,9 +179,25 @@ void AxivionPluginPrivate::onStartupProjectChanged()
fetchProjectInfo(projSettings->dashboardProjectName()); fetchProjectInfo(projSettings->dashboardProjectName());
} }
static QUrl urlForProject(const QString &projectName)
{
QString dashboard = settings().server.dashboard;
if (!dashboard.endsWith(QLatin1Char('/')))
dashboard += QLatin1Char('/');
return QUrl(dashboard).resolved(QStringLiteral("api/projects/")).resolved(projectName);
}
static constexpr int httpStatusCodeOk = 200;
static const QLatin1String jsonContentType{ "application/json" };
static void deserialize(QPromise<Dto::ProjectInfoDto> &promise, const QByteArray &input)
{
promise.addResult(Dto::ProjectInfoDto::deserialize(input));
}
void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName) void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
{ {
if (m_runningQuery) { // re-schedule if (m_taskTreeRunner.isRunning()) { // TODO: cache in queue and run when task tree finished
QTimer::singleShot(3000, this, [this, projectName] { fetchProjectInfo(projectName); }); QTimer::singleShot(3000, this, [this, projectName] { fetchProjectInfo(projectName); });
return; return;
} }
@@ -184,17 +207,94 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
m_axivionOutputPane.updateDashboard(); m_axivionOutputPane.updateDashboard();
return; return;
} }
m_runningQuery = true;
DashboardClient client { this->m_networkAccessManager }; const QUrl url = urlForProject(projectName);
QFuture<DashboardClient::RawProjectInfo> response = client.fetchProjectInfo(projectName);
auto responseWatcher = std::make_shared<QFutureWatcher<DashboardClient::RawProjectInfo>>(); const Storage<QByteArray> storage;
connect(responseWatcher.get(),
&QFutureWatcher<DashboardClient::RawProjectInfo>::finished, const auto onQuerySetup = [this, url](NetworkQuery &query) {
this, QNetworkRequest request(url);
[this, responseWatcher]() { request.setRawHeader(QByteArrayLiteral("Accept"),
handleProjectInfo(responseWatcher->result()); QByteArray(jsonContentType.data(), jsonContentType.size()));
}); request.setRawHeader(QByteArrayLiteral("Authorization"),
responseWatcher->setFuture(response); QByteArrayLiteral("AxToken ") + settings().server.token.toUtf8());
const QByteArray ua = QByteArrayLiteral("Axivion")
+ QCoreApplication::applicationName().toUtf8()
+ QByteArrayLiteral("Plugin/")
+ QCoreApplication::applicationVersion().toUtf8();
request.setRawHeader(QByteArrayLiteral("X-Axivion-User-Agent"), ua);
query.setRequest(request);
query.setNetworkAccessManager(&m_networkAccessManager);
};
const auto onQueryDone = [storage, url](const NetworkQuery &query, DoneWith doneWith) {
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 == jsonContentType) {
*storage = reply->readAll();
return DoneResult::Success;
}
const auto getError = [&]() -> Error {
if (contentType == jsonContentType) {
try {
return DashboardError(reply->url(), statusCode,
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
Dto::ErrorDto::deserialize(reply->readAll()));
} catch (const Dto::invalid_dto_exception &) {
// ignore
}
}
if (statusCode != 0) {
return HttpError(reply->url(), statusCode,
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
QString::fromUtf8(reply->readAll())); // encoding?
}
return NetworkError(reply->url(), error, reply->errorString());
};
Core::MessageManager::writeFlashing(
QStringLiteral("Axivion: %1").arg(getError().message()));
return DoneResult::Error;
};
const auto onDeserializeSetup = [storage](Async<Dto::ProjectInfoDto> &task) {
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
task.setConcurrentCallData(deserialize, *storage);
};
const auto onDeserializeDone = [this, url](const Async<Dto::ProjectInfoDto> &task,
DoneWith doneWith) {
if (doneWith == DoneWith::Success) {
m_currentProjectInfo = task.future().result();
m_axivionOutputPane.updateDashboard();
// handle already opened documents
if (auto buildSystem = ProjectExplorer::ProjectManager::startupBuildSystem();
!buildSystem || !buildSystem->isParsing()) {
handleOpenedDocs(nullptr);
} else {
connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::projectFinishedParsing,
this, &AxivionPluginPrivate::handleOpenedDocs);
}
}
};
const Group recipe {
storage,
NetworkQueryTask(onQuerySetup, onQueryDone),
AsyncTask<Dto::ProjectInfoDto>(onDeserializeSetup, onDeserializeDone)
};
m_taskTreeRunner.start(recipe);
} }
void AxivionPluginPrivate::fetchRuleInfo(const QString &id) void AxivionPluginPrivate::fetchRuleInfo(const QString &id)
@@ -237,27 +337,6 @@ void AxivionPluginPrivate::clearAllMarks()
onDocumentClosed(doc); onDocumentClosed(doc);
} }
void AxivionPluginPrivate::handleProjectInfo(DashboardClient::RawProjectInfo rawInfo)
{
m_runningQuery = false;
if (!rawInfo) {
Core::MessageManager::writeFlashing(
QStringLiteral(u"Axivion: %1").arg(rawInfo.error().message()));
return;
}
m_currentProjectInfo = std::make_shared<const DashboardClient::ProjectInfo>(std::move(rawInfo.value()));
m_axivionOutputPane.updateDashboard();
// handle already opened documents
if (auto buildSystem = ProjectExplorer::ProjectManager::startupBuildSystem();
!buildSystem || !buildSystem->isParsing()) {
handleOpenedDocs(nullptr);
} else {
connect(ProjectExplorer::ProjectManager::instance(),
&ProjectExplorer::ProjectManager::projectFinishedParsing,
this, &AxivionPluginPrivate::handleOpenedDocs);
}
}
void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc) void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc)
{ {
if (!m_currentProjectInfo) // we do not have a project info (yet) if (!m_currentProjectInfo) // we do not have a project info (yet)
@@ -267,9 +346,9 @@ void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc)
if (!doc || !project->isKnownFile(doc->filePath())) if (!doc || !project->isKnownFile(doc->filePath()))
return; return;
Utils::FilePath relative = doc->filePath().relativeChildPath(project->projectDirectory()); const FilePath relative = doc->filePath().relativeChildPath(project->projectDirectory());
// for now only style violations // for now only style violations
AxivionQuery query(AxivionQuery::IssuesForFileList, {m_currentProjectInfo->data.name, "SV", const AxivionQuery query(AxivionQuery::IssuesForFileList, {m_currentProjectInfo->name, "SV",
relative.path()}); relative.path()});
AxivionQueryRunner *runner = new AxivionQueryRunner(query, this); AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){ connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){
@@ -301,10 +380,10 @@ void AxivionPluginPrivate::handleIssuesForFile(const IssuesList &issues)
if (!project) if (!project)
return; return;
const Utils::FilePath filePath = project->projectDirectory() const FilePath filePath = project->projectDirectory()
.pathAppended(issues.issues.first().filePath); .pathAppended(issues.issues.first().filePath);
const Utils::Id axivionId(AxivionTextMarkId); const Id axivionId(AxivionTextMarkId);
for (const ShortIssue &issue : std::as_const(issues.issues)) { for (const ShortIssue &issue : std::as_const(issues.issues)) {
// FIXME the line location can be wrong (even the whole issue could be wrong) // 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 // depending on whether this line has been changed since the last axivion run and the

View File

@@ -3,7 +3,7 @@
#pragma once #pragma once
#include "dashboard/dashboardclient.h" #include "dashboard/dto.h"
#include <memory> #include <memory>
@@ -12,7 +12,7 @@ namespace ProjectExplorer { class Project; }
namespace Axivion::Internal { namespace Axivion::Internal {
void fetchProjectInfo(const QString &projectName); void fetchProjectInfo(const QString &projectName);
std::shared_ptr<const DashboardClient::ProjectInfo> projectInfo(); std::optional<Dto::ProjectInfoDto> projectInfo();
bool handleCertificateIssue(); bool handleCertificateIssue();
} // Axivion::Internal } // Axivion::Internal

View File

@@ -1,239 +0,0 @@
/*
* Copyright (C) 2022-current by Axivion GmbH
* https://www.axivion.com/
*
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
*/
#include "dashboardclient.h"
#include "axivionsettings.h"
#include <QByteArray>
#include <QCoreApplication>
#include <QFutureWatcher>
#include <QLatin1String>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QPromise>
#include <memory>
namespace Axivion::Internal
{
Credential::Credential(const QString &apiToken)
: m_authorizationValue(QByteArrayLiteral("AxToken ") + apiToken.toUtf8())
{
}
const QByteArray &Credential::authorizationValue() const
{
return m_authorizationValue;
}
QFuture<Credential> CredentialProvider::getCredential()
{
return QtFuture::makeReadyFuture(Credential(settings().server.token));
}
QFuture<void> CredentialProvider::authenticationFailure(const Credential &credential)
{
Q_UNUSED(credential);
// ToDo: invalidate stored credential to prevent further accesses with it.
// This is to prevent account locking on password change day due to to many
// authentication failuers caused by automated requests.
return QtFuture::makeReadyFuture();
}
QFuture<void> CredentialProvider::authenticationSuccess(const Credential &credential)
{
Q_UNUSED(credential);
// ToDo: store (now verified) credential on disk if not already happened.
return QtFuture::makeReadyFuture();
}
bool CredentialProvider::canReRequestPasswordOnAuthenticationFailure()
{
// ToDo: support on-demand password input dialog.
return false;
}
ClientData::ClientData(Utils::NetworkAccessManager &networkAccessManager)
: networkAccessManager(networkAccessManager),
credentialProvider(std::make_unique<CredentialProvider>())
{
}
DashboardClient::DashboardClient(Utils::NetworkAccessManager &networkAccessManager)
: m_clientData(std::make_shared<ClientData>(networkAccessManager))
{
}
using ResponseData = Utils::expected<DataWithOrigin<QByteArray>, Error>;
static constexpr int httpStatusCodeOk = 200;
static const QLatin1String jsonContentType{ "application/json" };
static ResponseData readResponse(QNetworkReply &reply, QAnyStringView expectedContentType)
{
QNetworkReply::NetworkError error = reply.error();
int statusCode = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QString contentType = reply.header(QNetworkRequest::ContentTypeHeader)
.toString()
.split(';')
.constFirst()
.trimmed()
.toLower();
if (error == QNetworkReply::NetworkError::NoError
&& statusCode == httpStatusCodeOk
&& contentType == expectedContentType) {
return DataWithOrigin(reply.url(), reply.readAll());
}
if (contentType == jsonContentType) {
try {
return tl::make_unexpected(DashboardError(
reply.url(),
statusCode,
reply.attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
Dto::ErrorDto::deserialize(reply.readAll())));
} catch (const Dto::invalid_dto_exception &) {
// ignore
}
}
if (statusCode != 0) {
return tl::make_unexpected(HttpError(
reply.url(),
statusCode,
reply.attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
QString::fromUtf8(reply.readAll()))); // encoding?
}
return tl::make_unexpected(
NetworkError(reply.url(), error, reply.errorString()));
}
template<typename T>
static Utils::expected<DataWithOrigin<T>, Error> parseResponse(ResponseData rawBody)
{
if (!rawBody)
return tl::make_unexpected(std::move(rawBody.error()));
try {
T data = T::deserialize(rawBody.value().data);
return DataWithOrigin(std::move(rawBody.value().origin),
std::move(data));
} catch (const Dto::invalid_dto_exception &e) {
return tl::make_unexpected(GeneralError(std::move(rawBody.value().origin),
QString::fromUtf8(e.what())));
}
}
static void fetch(QPromise<ResponseData> promise, std::shared_ptr<ClientData> clientData, QUrl url);
static void processResponse(QPromise<ResponseData> promise,
std::shared_ptr<ClientData> clientData,
QNetworkReply *reply,
Credential credential)
{
ResponseData response = readResponse(*reply, jsonContentType);
if (!response
&& response.error().isInvalidCredentialsError()
&& clientData->credentialProvider->canReRequestPasswordOnAuthenticationFailure()) {
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(&clientData->networkAccessManager);
QObject::connect(watcher,
&QFutureWatcher<void>::finished,
&clientData->networkAccessManager,
[promise = std::move(promise),
clientData,
url = reply->url(),
watcher]() mutable {
fetch(std::move(promise), std::move(clientData), std::move(url));
watcher->deleteLater();
});
watcher->setFuture(clientData->credentialProvider->authenticationFailure(credential));
return;
}
if (response) {
clientData->credentialProvider->authenticationSuccess(credential);
} else if (response.error().isInvalidCredentialsError()) {
clientData->credentialProvider->authenticationFailure(credential);
}
promise.addResult(std::move(response));
promise.finish();
}
static void fetch(QPromise<ResponseData> promise,
std::shared_ptr<ClientData> clientData,
const QUrl &url,
Credential credential)
{
QNetworkRequest request{ url };
request.setRawHeader(QByteArrayLiteral("Accept"),
QByteArray(jsonContentType.data(), jsonContentType.size()));
request.setRawHeader(QByteArrayLiteral("Authorization"),
credential.authorizationValue());
QByteArray ua = QByteArrayLiteral("Axivion")
+ QCoreApplication::applicationName().toUtf8()
+ QByteArrayLiteral("Plugin/")
+ QCoreApplication::applicationVersion().toUtf8();
request.setRawHeader(QByteArrayLiteral("X-Axivion-User-Agent"), ua);
QNetworkReply *reply = clientData->networkAccessManager.get(request);
QObject::connect(reply,
&QNetworkReply::finished,
reply,
[promise = std::move(promise),
clientData = std::move(clientData),
reply,
credential = std::move(credential)]() mutable {
processResponse(std::move(promise),
std::move(clientData),
reply,
std::move(credential));
reply->deleteLater();
});
}
static void fetch(QPromise<ResponseData> promise,
std::shared_ptr<ClientData> clientData,
QUrl url)
{
QFutureWatcher<Credential> *watcher = new QFutureWatcher<Credential>(&clientData->networkAccessManager);
QObject::connect(watcher,
&QFutureWatcher<Credential>::finished,
&clientData->networkAccessManager,
[promise = std::move(promise), clientData, url = std::move(url), watcher]() mutable {
fetch(std::move(promise),
std::move(clientData),
url,
watcher->result());
watcher->deleteLater();;
});
watcher->setFuture(clientData->credentialProvider->getCredential());
}
static QFuture<ResponseData> fetch(std::shared_ptr<ClientData> clientData,
const std::optional<QUrl> &base,
const QUrl &target)
{
QPromise<ResponseData> promise;
promise.start();
QFuture<ResponseData> future = promise.future();
fetch(std::move(promise),
std::move(clientData),
base ? base->resolved(target) : target);
return future;
}
QFuture<DashboardClient::RawProjectInfo> DashboardClient::fetchProjectInfo(const QString &projectName)
{
const AxivionServer &server = settings().server;
QString dashboard = server.dashboard;
if (!dashboard.endsWith(QLatin1Char('/')))
dashboard += QLatin1Char('/');
QUrl url = QUrl(dashboard)
.resolved(QUrl(QStringLiteral(u"api/projects/")))
.resolved(QUrl(projectName));
return fetch(this->m_clientData, std::nullopt, url)
.then(QtFuture::Launch::Async, &parseResponse<Dto::ProjectInfoDto>);
}
} // namespace Axivion::Internal

View File

@@ -1,78 +0,0 @@
#pragma once
/*
* Copyright (C) 2022-current by Axivion GmbH
* https://www.axivion.com/
*
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
*/
#include "dashboard/dto.h"
#include "dashboard/error.h"
#include <utils/expected.h>
#include <utils/networkaccessmanager.h>
#include <QFuture>
#include <QNetworkReply>
namespace Axivion::Internal
{
template<typename T>
class DataWithOrigin
{
public:
QUrl origin;
T data;
DataWithOrigin(QUrl origin, T data) : origin(std::move(origin)), data(std::move(data)) { }
};
class Credential
{
public:
Credential(const QString &apiToken);
const QByteArray &authorizationValue() const;
private:
QByteArray m_authorizationValue;
};
class CredentialProvider
{
public:
QFuture<Credential> getCredential();
QFuture<void> authenticationFailure(const Credential &credential);
QFuture<void> authenticationSuccess(const Credential &credential);
bool canReRequestPasswordOnAuthenticationFailure();
};
class ClientData
{
public:
Utils::NetworkAccessManager &networkAccessManager;
std::unique_ptr<CredentialProvider> credentialProvider;
ClientData(Utils::NetworkAccessManager &networkAccessManager);
};
class DashboardClient
{
public:
using ProjectInfo = DataWithOrigin<Dto::ProjectInfoDto>;
using RawProjectInfo = Utils::expected<ProjectInfo, Error>;
DashboardClient(Utils::NetworkAccessManager &networkAccessManager);
QFuture<RawProjectInfo> fetchProjectInfo(const QString &projectName);
private:
std::shared_ptr<ClientData> m_clientData;
};
} // namespace Axivion::Internal

View File

@@ -76,25 +76,25 @@ QString Error::message() const
return std::visit( return std::visit(
overloaded{ overloaded{
[](const GeneralError &error) { [](const GeneralError &error) {
return QStringLiteral(u"GeneralError (%1) %2") return QStringLiteral("GeneralError (%1) %2")
.arg(error.replyUrl.toString(), .arg(error.replyUrl.toString(),
error.message); error.message);
}, },
[](const NetworkError &error) { [](const NetworkError &error) {
return QStringLiteral(u"NetworkError (%1) %2: %3") return QStringLiteral("NetworkError (%1) %2: %3")
.arg(error.replyUrl.toString(), .arg(error.replyUrl.toString(),
QString::number(error.networkError), QString::number(error.networkError),
error.networkErrorString); error.networkErrorString);
}, },
[](const HttpError &error) { [](const HttpError &error) {
return QStringLiteral(u"HttpError (%1) %2: %3\n%4") return QStringLiteral("HttpError (%1) %2: %3\n%4")
.arg(error.replyUrl.toString(), .arg(error.replyUrl.toString(),
QString::number(error.httpStatusCode), QString::number(error.httpStatusCode),
error.httpReasonPhrase, error.httpReasonPhrase,
error.body); error.body);
}, },
[](const DashboardError &error) { [](const DashboardError &error) {
return QStringLiteral(u"DashboardError (%1) [%2 %3] %4: %5") return QStringLiteral("DashboardError (%1) [%2 %3] %4: %5")
.arg(error.replyUrl.toString(), .arg(error.replyUrl.toString(),
QString::number(error.httpStatusCode), QString::number(error.httpStatusCode),
error.httpReasonPhrase, error.httpReasonPhrase,