Skip to content

Commit 1dbb1af

Browse files
committed
Add diagnostic of error response. WIP optimize GUI logic
1 parent f4f791a commit 1dbb1af

13 files changed

Lines changed: 646 additions & 15 deletions

src/plugins/synth/internal/addon/SynthesisServicePanelAddOn.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
#include "SynthesisServicePanelAddOn.h"
22

33
#include <QAbstractItemModel>
4+
#include <QClipboard>
5+
#include <QFile>
6+
#include <QGuiApplication>
47
#include <QQmlComponent>
8+
#include <QSaveFile>
59
#include <QVariant>
610

711
#include <CoreApi/runtimeinterface.h>
@@ -10,6 +14,9 @@
1014

1115
#include <coreplugin/ProjectWindowInterface.h>
1216

17+
#include <synth/SynthInterface.h>
18+
#include <synth/SynthesisTask.h>
19+
#include <synth/SynthesisTaskManager.h>
1320
#include <synth/internal/ServiceStatusModel.h>
1421
#include <synth/internal/SynthService.h>
1522

@@ -18,6 +25,7 @@ namespace Synth::Internal {
1825
SynthesisServicePanelAddOn::SynthesisServicePanelAddOn(QObject *parent)
1926
: Core::WindowInterfaceAddOn(parent),
2027
m_service(SynthService::instance()),
28+
m_taskManager(SynthInterface::instance()->taskManager()),
2129
m_model(new ServiceStatusModel(m_service, this)) {
2230
connect(m_service, &SynthService::refreshingChanged,
2331
this, &SynthesisServicePanelAddOn::refreshingChanged);
@@ -58,10 +66,55 @@ namespace Synth::Internal {
5866
return m_service->refreshing();
5967
}
6068

69+
QUrl SynthesisServicePanelAddOn::diagnosticsDirectoryUrl() const {
70+
return QUrl::fromLocalFile(m_taskManager->diagnosticsDirectory());
71+
}
72+
6173
void SynthesisServicePanelAddOn::refreshAll() {
6274
m_service->refreshAll();
6375
}
6476

77+
bool SynthesisServicePanelAddOn::copyDiagnosticRequest(QObject *taskObject, int exchangeIndex) {
78+
auto task = qobject_cast<SynthesisTask *>(taskObject);
79+
if (!task || exchangeIndex < 0 || exchangeIndex >= task->diagnostics().size()) {
80+
return false;
81+
}
82+
const auto diagnostics = task->diagnostics();
83+
QGuiApplication::clipboard()->setText(diagnostics.at(exchangeIndex).toMap().value(QStringLiteral("requestBody")).toString());
84+
return true;
85+
}
86+
87+
bool SynthesisServicePanelAddOn::copyDiagnosticResponse(QObject *taskObject, int exchangeIndex) {
88+
auto task = qobject_cast<SynthesisTask *>(taskObject);
89+
if (!task || exchangeIndex < 0 || exchangeIndex >= task->diagnostics().size()) {
90+
return false;
91+
}
92+
const auto diagnostics = task->diagnostics();
93+
QGuiApplication::clipboard()->setText(diagnostics.at(exchangeIndex).toMap().value(QStringLiteral("responseBody")).toString());
94+
return true;
95+
}
96+
97+
bool SynthesisServicePanelAddOn::exportDiagnostics(QObject *taskObject, const QUrl &fileUrl) {
98+
auto task = qobject_cast<SynthesisTask *>(taskObject);
99+
if (!task || task->diagnosticFilePath().isEmpty() || !fileUrl.isLocalFile()) {
100+
return false;
101+
}
102+
QFile source(task->diagnosticFilePath());
103+
if (!source.open(QIODevice::ReadOnly)) {
104+
return false;
105+
}
106+
QSaveFile destination(fileUrl.toLocalFile());
107+
if (!destination.open(QIODevice::WriteOnly)) {
108+
return false;
109+
}
110+
const auto bytes = source.readAll();
111+
return destination.write(bytes) == bytes.size() && destination.commit();
112+
}
113+
114+
void SynthesisServicePanelAddOn::clearDiagnostics() {
115+
m_taskManager->clearDiagnostics();
116+
}
117+
65118
}
66119

67120
#include "moc_SynthesisServicePanelAddOn.cpp"

src/plugins/synth/internal/addon/SynthesisServicePanelAddOn.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef DIFFSCOPE_SYNTH_SYNTHESISSERVICEPANELADDON_H
22
#define DIFFSCOPE_SYNTH_SYNTHESISSERVICEPANELADDON_H
33

4+
#include <QUrl>
5+
46
#include <CoreApi/windowinterface.h>
57

68
class QAbstractItemModel;
@@ -10,10 +12,21 @@ namespace Synth::Internal {
1012
class ServiceStatusModel;
1113
class SynthService;
1214

15+
}
16+
17+
namespace Synth {
18+
19+
class SynthesisTaskManager;
20+
21+
}
22+
23+
namespace Synth::Internal {
24+
1325
class SynthesisServicePanelAddOn final : public Core::WindowInterfaceAddOn {
1426
Q_OBJECT
1527
Q_PROPERTY(QAbstractItemModel *serviceModel READ serviceModel CONSTANT)
1628
Q_PROPERTY(bool refreshing READ refreshing NOTIFY refreshingChanged)
29+
Q_PROPERTY(QUrl diagnosticsDirectoryUrl READ diagnosticsDirectoryUrl CONSTANT)
1730
public:
1831
explicit SynthesisServicePanelAddOn(QObject *parent = nullptr);
1932
~SynthesisServicePanelAddOn() override;
@@ -24,14 +37,20 @@ namespace Synth::Internal {
2437

2538
QAbstractItemModel *serviceModel() const;
2639
bool refreshing() const;
40+
QUrl diagnosticsDirectoryUrl() const;
2741

2842
Q_INVOKABLE void refreshAll();
43+
Q_INVOKABLE bool copyDiagnosticRequest(QObject *taskObject, int exchangeIndex);
44+
Q_INVOKABLE bool copyDiagnosticResponse(QObject *taskObject, int exchangeIndex);
45+
Q_INVOKABLE bool exportDiagnostics(QObject *taskObject, const QUrl &fileUrl);
46+
Q_INVOKABLE void clearDiagnostics();
2947

3048
Q_SIGNALS:
3149
void refreshingChanged();
3250

3351
private:
3452
SynthService *m_service{};
53+
SynthesisTaskManager *m_taskManager{};
3554
ServiceStatusModel *m_model{};
3655
};
3756

src/plugins/synth/internal/api/ApiClient.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ namespace {
7575
QByteArray rawResponse;
7676
QJsonValue json{QJsonValue::Undefined};
7777
std::optional<ApiError> error;
78+
QList<ApiExchange> exchanges;
7879
};
7980

8081
struct RequestTask {
@@ -87,6 +88,7 @@ namespace {
8788
SynthesisCategory category{SynthesisCategory::None};
8889
ApiVersion apiVersion{ApiVersion::V1};
8990
int attempt{};
91+
QList<ApiExchange> exchanges;
9092
std::function<bool()> isCanceled;
9193
std::function<void()> reportStarted;
9294
std::function<void(const RawResponse &)> reportResponse;
@@ -239,6 +241,7 @@ namespace {
239241
QPointer<QNetworkReply> reply;
240242
QPointer<QTimer> timeoutTimer;
241243
QPointer<QTimer> retryTimer;
244+
QDateTime attemptStartedAt;
242245
bool timedOut{};
243246
};
244247

@@ -364,6 +367,7 @@ namespace {
364367
}
365368

366369
active.timedOut = false;
370+
active.attemptStartedAt = QDateTime::currentDateTimeUtc();
367371
auto request = networkRequest(*task);
368372
qCDebug(lcDsspApiClient) << "Sending DSSP API request"
369373
<< "requestId=" << task->id
@@ -426,6 +430,7 @@ namespace {
426430
.toString()
427431
.trimmed();
428432
const auto retryAfter = reply->rawHeader(QByteArrayLiteral("Retry-After"));
433+
const auto url = reply->url();
429434
const auto body = reply->readAll();
430435
const auto rawJson = parseJsonBestEffort(body);
431436
reply->deleteLater();
@@ -504,6 +509,23 @@ namespace {
504509
}
505510
}
506511

512+
ApiExchange exchange;
513+
exchange.requestId = task->id;
514+
exchange.serviceInstanceId = task->service.id;
515+
exchange.method = task->method;
516+
exchange.url = url;
517+
exchange.attempt = task->attempt + 1;
518+
exchange.httpStatusCode = status;
519+
exchange.networkErrorCode = static_cast<int>(networkError);
520+
exchange.startedAt = active.attemptStartedAt;
521+
exchange.finishedAt = QDateTime::currentDateTimeUtc();
522+
exchange.requestBody = task->body;
523+
exchange.responseBody = body;
524+
if (response.error)
525+
exchange.errorMessage = response.error->message;
526+
task->exchanges.append(std::move(exchange));
527+
response.exchanges = task->exchanges;
528+
507529
const bool retryable = response.error
508530
&& ((response.error->isNetworkError()
509531
&& isRetryableNetworkError(static_cast<QNetworkReply::NetworkError>(
@@ -701,21 +723,24 @@ class ApiClient::Private {
701723

702724
ApiResult<T> result;
703725
if (response.error) {
704-
result = ApiResult<T>::failure(*response.error);
726+
result = ApiResult<T>::failure(*response.error, response.exchanges);
705727
} else {
706728
T value;
707729
QString parseError;
708730
if (!T::fromJson(response.json, value, &parseError)) {
709731
ApiError error;
710732
error.kind = ApiError::ResponseError;
711733
error.httpStatusCode = response.httpStatusCode;
712-
error.message = QObject::tr("The synthesis service returned data that does not match the DSSP schema: %1.")
734+
error.message = QObject::tr("The synthesis service returned data that does not match the DSSP schema: %1.")
713735
.arg(parseError);
714736
error.rawResponse = response.rawResponse;
715737
error.rawJsonResponse = response.json;
716-
result = ApiResult<T>::failure(std::move(error));
738+
auto exchanges = response.exchanges;
739+
if (!exchanges.isEmpty())
740+
exchanges.last().errorMessage = error.message;
741+
result = ApiResult<T>::failure(std::move(error), std::move(exchanges));
717742
} else {
718-
result = ApiResult<T>::success(std::move(value));
743+
result = ApiResult<T>::success(std::move(value), response.exchanges);
719744
}
720745
}
721746
futureInterface->reportResult(std::move(result));

src/plugins/synth/internal/api/ApiTypes.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,32 @@
55
#include <utility>
66

77
#include <QByteArray>
8+
#include <QDateTime>
89
#include <QFuture>
910
#include <QJsonValue>
11+
#include <QList>
1012
#include <QMetaType>
1113
#include <QString>
14+
#include <QUrl>
15+
#include <QUuid>
1216

1317
namespace Synth::Internal::Api {
1418

19+
struct ApiExchange {
20+
quint64 requestId{};
21+
QUuid serviceInstanceId;
22+
QByteArray method;
23+
QUrl url;
24+
int attempt{};
25+
int httpStatusCode{};
26+
int networkErrorCode{};
27+
QDateTime startedAt;
28+
QDateTime finishedAt;
29+
QByteArray requestBody;
30+
QByteArray responseBody;
31+
QString errorMessage;
32+
};
33+
1534
struct ApiError {
1635
Q_GADGET
1736
public:
@@ -35,15 +54,17 @@ namespace Synth::Internal::Api {
3554
template<typename T>
3655
class ApiResult {
3756
public:
38-
static ApiResult success(T value) {
57+
static ApiResult success(T value, QList<ApiExchange> exchanges = {}) {
3958
ApiResult result;
4059
result.m_value = std::move(value);
60+
result.m_exchanges = std::move(exchanges);
4161
return result;
4262
}
4363

44-
static ApiResult failure(ApiError error) {
64+
static ApiResult failure(ApiError error, QList<ApiExchange> exchanges = {}) {
4565
ApiResult result;
4666
result.m_error = std::move(error);
67+
result.m_exchanges = std::move(exchanges);
4768
return result;
4869
}
4970

@@ -56,10 +77,12 @@ namespace Synth::Internal::Api {
5677
T takeValue() { return std::move(m_value).value(); }
5778

5879
const ApiError &error() const { return m_error.value(); }
80+
const QList<ApiExchange> &exchanges() const { return m_exchanges; }
5981

6082
private:
6183
std::optional<T> m_value;
6284
std::optional<ApiError> m_error;
85+
QList<ApiExchange> m_exchanges;
6386
};
6487

6588
enum class AsyncRequestState {
@@ -83,5 +106,6 @@ namespace Synth::Internal::Api {
83106
} // namespace Synth::Internal::Api
84107

85108
Q_DECLARE_METATYPE(Synth::Internal::Api::ApiError)
109+
Q_DECLARE_METATYPE(Synth::Internal::Api::ApiExchange)
86110

87111
#endif // DIFFSCOPE_SYNTH_INTERNAL_APITYPES_H

src/plugins/synth/internal/panel/ServiceStatusModel.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ namespace Synth::Internal {
2121
connect(m_service, &SynthService::serviceConfigurationsChanged, this, &ServiceStatusModel::rebuild);
2222
connect(m_service, &SynthService::serviceDetailsChanged, this, &ServiceStatusModel::updateService);
2323
connect(m_taskManager, &SynthesisTaskManager::serviceTaskCountsChanged, this, &ServiceStatusModel::updateServiceTasks);
24+
connect(m_taskManager, &SynthesisTaskManager::taskRemoved, this, [this](SynthesisTask *task) {
25+
updateServiceTasks(task->serviceInstanceId());
26+
});
2427
rebuild();
2528
}
2629

@@ -90,8 +93,16 @@ namespace Synth::Internal {
9093
));
9194
case TasksRole: {
9295
QVariantList result;
93-
for (auto task : m_taskManager->tasksForService(configuration.id()))
94-
result.append(QVariant::fromValue(task));
96+
for (auto task : m_taskManager->tasks()) {
97+
if (task->serviceInstanceId() != configuration.id()) {
98+
continue;
99+
}
100+
if (task->state() == SynthesisTask::Queued ||
101+
task->state() == SynthesisTask::Running ||
102+
task->state() == SynthesisTask::Failed) {
103+
result.append(QVariant::fromValue(task));
104+
}
105+
}
95106
return result;
96107
}
97108
default:

src/plugins/synth/internal/scheduler/SynthesisProjectInput.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ namespace Synth::Internal::ProjectInput {
493493
if (id == QStringLiteral("pitch")) {
494494
value = std::clamp(value + documentCentShift, 0.0, 12800.0);
495495
}
496-
parameter.values.append(SynthesisParameterEvaluator::normalize(configuration, value));
496+
parameter.values.append(value);
497497
}
498498
result.score.parameters.insert(id, parameter);
499499
}

0 commit comments

Comments
 (0)