diff --git a/homeconnect/homeconnect.h b/homeconnect/homeconnect.h index ae2f757f..486bb651 100644 --- a/homeconnect/homeconnect.h +++ b/homeconnect/homeconnect.h @@ -166,6 +166,7 @@ private: bool m_connected = false; bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData); + private slots: void onRefreshTimeout(); diff --git a/tempo/integrationplugintempo.cpp b/tempo/integrationplugintempo.cpp index 9cc92cb4..f4621b0b 100644 --- a/tempo/integrationplugintempo.cpp +++ b/tempo/integrationplugintempo.cpp @@ -62,15 +62,26 @@ void IntegrationPluginTempo::startPairing(ThingPairingInfo *info) return; } - QString jiraCloudInstanceName = info->params().paramValue(tempoConnectionAtlassianAccountParamTypeId).toString(); - QUrl url = Tempo::getLoginUrl(QUrl("https://127.0.0.1:8888"), jiraCloudInstanceName, clientId); - qCDebug(dcTempo()) << "Checking if the Tempo server is reachable"; - QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + QString jiraCloudInstanceName = info->params().paramValue(tempoConnectionThingAtlassianAccountNameParamTypeId).toString(); + Tempo *tempo = new Tempo(hardwareManager()->networkManager(), clientId, clientSecret, this); + + QUrl url = tempo->getLoginUrl(QUrl("https://127.0.0.1:8888"), jiraCloudInstanceName); + qCDebug(dcTempo()) << "Checking if the Tempo server is reachable: https://api.tempo.io/core/3"; + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(QUrl("https://api.tempo.io/core/3"))); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, info, [reply, info, url, this] { + connect(reply, &QNetworkReply::finished, info, [reply, info, tempo, url, this] { if (reply->error() != QNetworkReply::NetworkError::HostNotFoundError) { qCDebug(dcTempo()) << "Tempo server is reachable"; + ThingId thingId = info->thingId(); + m_setupTempoConnections.insert(info->thingId(), tempo); + connect(info, &ThingPairingInfo::aborted, this, [thingId, this] { + qCWarning(dcTempo()) << "ThingPairingInfo aborted, cleaning up"; + Tempo *tempo = m_setupTempoConnections.take(thingId); + if (tempo) + tempo->deleteLater(); + }); + qCDebug(dcTempo()) << "OAuthUrl" << url.toString(); info->setOAuthUrl(url); info->finish(Thing::ThingErrorNoError); } else { @@ -87,9 +98,9 @@ void IntegrationPluginTempo::startPairing(ThingPairingInfo *info) void IntegrationPluginTempo::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) { Q_UNUSED(username); - qCDebug(dcTempo()) << "Confirm pairing"; - if (info->thingClassId() == tempoConnectionThingClassId) { + if (info->thingClassId() == tempoConnectionThingClassId) { + qCDebug(dcTempo()) << "Confirm pairing" << info->thingName(); QUrl url(secret); QUrlQuery query(url); QByteArray authorizationCode = query.queryItemValue("code").toLocal8Bit(); @@ -100,7 +111,7 @@ void IntegrationPluginTempo::confirmPairing(ThingPairingInfo *info, const QStrin Tempo *tempo = m_setupTempoConnections.value(info->thingId()); if (!tempo) { - qWarning(dcTempo()) << "No Tempo connection found for device:" << info->thingName(); + qWarning(dcTempo()) << "No tempo connection found for device:" << info->thingName(); m_setupTempoConnections.remove(info->thingId()); return info->finish(Thing::ThingErrorHardwareFailure); } @@ -127,6 +138,99 @@ void IntegrationPluginTempo::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == tempoConnectionThingClassId) { + Tempo *tempo; + if (m_tempoConnections.contains(thing)) { + qCDebug(dcTempo()) << "Setup after reconfiguration, cleaning up"; + m_tempoConnections.take(thing)->deleteLater(); + } + if (m_setupTempoConnections.keys().contains(thing->id())) { + // This thing setup is after a pairing process + qCDebug(dcTempo()) << "Tempo OAuth setup complete"; + tempo = m_setupTempoConnections.take(thing->id()); + if (!tempo) { + qCWarning(dcTempo()) << "Tempo connection object not found for thing" << thing->name(); + } + m_tempoConnections.insert(thing, tempo); + info->finish(Thing::ThingErrorNoError); + } else { + //device loaded from the device database, needs a new access token; + pluginStorage()->beginGroup(thing->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); + if (refreshToken.isEmpty()) { + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Refresh token is not available.")); + return; + } + + QByteArray clientId = configValue(tempoPluginCustomClientIdParamTypeId).toByteArray(); + QByteArray clientSecret = configValue(tempoPluginCustomClientSecretParamTypeId).toByteArray(); + if (clientId.isEmpty() || clientSecret.isEmpty()) { + clientId = apiKeyStorage()->requestKey("tempo").data("clientId"); + clientSecret = apiKeyStorage()->requestKey("tempo").data("clientSecret"); + } else { + qCDebug(dcTempo()) << "Using custom API id and secret."; + } + if (clientId.isEmpty() || clientSecret.isEmpty()) { + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client id and/or secret is not available.")); + return; + } + Tempo *tempo = new Tempo(hardwareManager()->networkManager(), clientId, clientSecret, this); + tempo->getAccessTokenFromRefreshToken(refreshToken); + connect(tempo, &Tempo::receivedAccessToken, info, [info] { + info->finish(Thing::ThingErrorNoError); + }); + connect(info, &ThingSetupInfo::aborted, tempo, &Tempo::deleteLater); + } + connect(tempo, &Tempo::connectionChanged, this, &IntegrationPluginTempo::onConnectionChanged); + connect(tempo, &Tempo::authenticationStatusChanged, this, &IntegrationPluginTempo::onAuthenticationStatusChanged); + connect(tempo, &Tempo::accountsReceived, this, &IntegrationPluginTempo::onReceivedAccounts); + + } else if (thing->thingClassId() == accountThingClassId) { + Thing *parentThing = myThings().findById(thing->parentId()); + if (parentThing->setupComplete()) { + info->finish(Thing::ThingErrorNoError); + } else { + connect(parentThing, &Thing::setupStatusChanged, info, [parentThing, info]{ + if (parentThing->setupComplete()) { + info->finish(Thing::ThingErrorNoError); + } + }); + } + } else { + Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginTempo::postSetupThing(Thing *thing) +{ + qCDebug(dcTempo()) << "Post setup thing" << thing->name(); + + if (!m_pluginTimer15min) { + m_pluginTimer15min = hardwareManager()->pluginTimerManager()->registerTimer(60*15); + connect(m_pluginTimer15min, &PluginTimer::timeout, this, [this]() { + qCDebug(dcTempo()) << "Refresh timer timout, polling all Tempo accounts."; + Q_FOREACH (Thing *thing, myThings().filterByThingClassId(tempoConnectionThingClassId)) { + Tempo *tempo = m_tempoConnections.value(thing); + if (!tempo) { + qWarning(dcTempo()) << "No Tempo connection found for" << thing->name(); + continue; + } + tempo->getAccounts(); + Q_FOREACH (Thing *childThing, myThings().filterByParentId(thing->id())) { + QString key = childThing->paramValue(accountThingKeyParamTypeId).toString(); + QDate from(1970, 1, 1); + tempo->getWorkloadByAccount(key, from, QDate::currentDate()); + } + } + }); + } + + if (thing->thingClassId() == tempoConnectionThingClassId) { + Tempo *tempo = m_tempoConnections.value(thing); + tempo->getAccounts(); + + } else if (thing->thingClassId() == accountThingClassId) { + } } @@ -147,6 +251,41 @@ void IntegrationPluginTempo::thingRemoved(Thing *thing) qCDebug(dcTempo()) << "Thing removed" << thing->name(); } +void IntegrationPluginTempo::onConnectionChanged(bool connected) +{ + Tempo *tempo = static_cast(sender()); + Thing *thing = m_tempoConnections.key(tempo); + if (!thing) + return; + thing->setStateValue(tempoConnectionConnectedStateTypeId, connected); + if (!connected) { + Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) { + child->setStateValue(accountConnectedStateTypeId, connected); + } + } +} + +void IntegrationPluginTempo::onAuthenticationStatusChanged(bool authenticated) +{ + qCDebug(dcTempo()) << "Authentication changed" << authenticated; + + Tempo *tempoConnection = static_cast(sender()); + + Thing *thing = m_tempoConnections.key(tempoConnection); + if (!thing) + return; + + thing->setStateValue(tempoConnectionLoggedInStateTypeId, authenticated); + if (!authenticated) { + //refresh access token needs to be refreshed + pluginStorage()->beginGroup(thing->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); + tempoConnection->getAccessTokenFromRefreshToken(refreshToken); + } +} + + void IntegrationPluginTempo::onReceivedAccounts(const QList &accounts) { qCDebug(dcTempo()) << "Received" << accounts.count() << "accounts"; @@ -160,13 +299,13 @@ void IntegrationPluginTempo::onReceivedAccounts(const QList &acc Q_FOREACH(Tempo::Account account, accounts) { ThingClassId thingClassId; - Thing * existingThing = myThings().findByParams(ParamList() << Param(m_idParamTypeIds.value(thingClassId), appliance.homeApplianceId)); - if (existingThing) { - qCDebug(dcTempo()) << "Thing is already added to system" << existingThing->name(); - //Set connected state; - //existingThing->setStateValue(m_connectedStateTypeIds.value(thingClassId), appliance.connected); - continue; - } + //Thing * existingThing = myThings().findByParams(ParamList() << Param(m_idParamTypeIds.value(thingClassId), appliance.homeApplianceId)); + //if (existingThing) { + // qCDebug(dcTempo()) << "Thing is already added to system" << existingThing->name(); + //Set connected state; + //existingThing->setStateValue(m_connectedStateTypeIds.value(thingClassId), appliance.connected); + // continue; + // } qCDebug(dcTempo()) << "Found new account:" << account.name << "key:" << account.key << "id:" << account.id; ThingDescriptor descriptor(thingClassId, account.name, account.key, parentThing->id()); @@ -179,3 +318,16 @@ void IntegrationPluginTempo::onReceivedAccounts(const QList &acc emit autoThingsAppeared(desciptors); } +void IntegrationPluginTempo::onAccountWorkloadReceived(const QString &accountKey, QList workloads) +{ + Thing *thing = myThings().findByParams(ParamList() << Param(accountThingKeyParamTypeId, accountKey)); + if (!thing) + return; + + uint totalTimeSpentSeconds = 0; + Q_FOREACH(Tempo::Worklog workload, workloads) { + totalTimeSpentSeconds += workload.timeSpentSeconds; + } + thing->setStateValue(accountTotalTimeSpentStateTypeId, totalTimeSpentSeconds/60); +} + diff --git a/tempo/integrationplugintempo.h b/tempo/integrationplugintempo.h index 5ad3bd88..4139452e 100644 --- a/tempo/integrationplugintempo.h +++ b/tempo/integrationplugintempo.h @@ -50,6 +50,7 @@ public: void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override; void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; void executeAction(ThingActionInfo *info) override; void thingRemoved(Thing *thing) override; @@ -62,7 +63,8 @@ private: private slots: void onConnectionChanged(bool connected); void onAuthenticationStatusChanged(bool authenticated); - void onRequestExecuted(QUuid requestId, bool success); void onReceivedAccounts(const QList &accounts); + + void onAccountWorkloadReceived(const QString &accountKey, QList workloads); }; #endif // INTEGRATIONPLUGINTEMPO_H diff --git a/tempo/integrationplugintempo.json b/tempo/integrationplugintempo.json index 1aaede7d..c20b4cc7 100644 --- a/tempo/integrationplugintempo.json +++ b/tempo/integrationplugintempo.json @@ -87,9 +87,9 @@ "paramTypes": [ { "id": "c6aeddae-56af-496d-a419-1635ff9bae50", - "name": "id", - "displayName": "ID", - "defaultValue": "-", + "name": "key", + "displayName": "Key", + "defaultValue": "", "type": "QString" } ], @@ -163,6 +163,24 @@ "displayNameEvent": "Customer changed", "defaultValue": "", "type": "QString" + }, + { + "id": "1ac39002-56a1-4911-aa68-9d14e142edae", + "name": "totalTimeSpent", + "displayName": "Total time spent", + "displayNameEvent": "Total time spent changed", + "defaultValue": 0, + "type": "uint", + "unit": "Minutes" + }, + { + "id": "81bec4e8-9fd3-43d1-b339-2a7fdd83e8cb", + "name": "monthTimeSpent", + "displayName": "This month time spent", + "displayNameEvent": "This month time spent changed", + "defaultValue": 0, + "type": "uint", + "unit": "Minutes" } ] } diff --git a/tempo/tempo.cpp b/tempo/tempo.cpp index fc497ce9..adc5af28 100644 --- a/tempo/tempo.cpp +++ b/tempo/tempo.cpp @@ -35,15 +35,15 @@ #include "extern-plugininfo.h" Tempo::Tempo(NetworkAccessManager *networkmanager, const QByteArray &clientId, const QByteArray &clientSecret, QObject *parent) : -QObject(parent), - m_clientId(clientId), - m_clientSecret(clientSecret), - m_networkManager(networkmanager) + QObject(parent), + m_clientId(clientId), + m_clientSecret(clientSecret), + m_networkManager(networkmanager) { - m_tokenRefreshTimer = new QTimer(this); - m_tokenRefreshTimer->setSingleShot(true); - connect(m_tokenRefreshTimer, &QTimer::timeout, this, &Tempo::onRefreshTimer); + m_tokenRefreshTimer = new QTimer(this); + m_tokenRefreshTimer->setSingleShot(true); + connect(m_tokenRefreshTimer, &QTimer::timeout, this, &Tempo::onRefreshTimer); } QByteArray Tempo::accessToken() @@ -56,20 +56,140 @@ QByteArray Tempo::refreshToken() return m_refreshToken; } -QUrl Tempo::getLoginUrl(const QUrl &redirectUrl, const QString &jiraCloudInstanceName, const QByteArray &clientId) +QUrl Tempo::getLoginUrl(const QUrl &redirectUrl, const QString &jiraCloudInstanceName) { + if (m_clientId.isEmpty()) { + qWarning(dcTempo) << "Client Id not defined!"; + return QUrl(""); + } + + if (redirectUrl.isEmpty()){ + qWarning(dcTempo) << "No redirect uri defined!"; + } + m_redirectUri = QUrl::toPercentEncoding(redirectUrl.toString()); + QUrl url; url.setScheme("https"); - url.setHost(jiraCloudInstanceName+"atlassian.net"); + url.setHost(jiraCloudInstanceName+".atlassian.net"); url.setPath("/plugins/servlet/ac/io.tempo.jira/oauth-authorize/"); QUrlQuery query; - query.addQueryItem("client_id", clientId); - query.addQueryItem("redirect_uri", redirectUrl.toString()); + query.addQueryItem("client_id", m_clientId); + query.addQueryItem("redirect_uri", m_redirectUri); query.addQueryItem("access_type", "tenant_user"); url.setQuery(query); return url; } +void Tempo::getAccessTokenFromRefreshToken(const QByteArray &refreshToken) +{ + if (refreshToken.isEmpty()) { + qWarning(dcTempo) << "No refresh token given!"; + setAuthenticated(false); + return; + } + + QUrl url(m_baseTokenUrl); + QUrlQuery query; + query.clear(); + query.addQueryItem("grant_type", "refresh_token"); + query.addQueryItem("refresh_token", refreshToken); + query.addQueryItem("client_secret", m_clientSecret); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8()); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QJsonDocument data = QJsonDocument::fromJson(rawData); + + if(!data.toVariant().toMap().contains("access_token")) { + setAuthenticated(false); + return; + } + m_accessToken = data.toVariant().toMap().value("access_token").toByteArray(); + emit receivedAccessToken(m_accessToken); + + if (data.toVariant().toMap().contains("expires_in")) { + int expireTime = data.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcTempo) << "Access token expires int" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcTempo()) << "Access token refresh timer not initialized"; + return; + } + if (expireTime < 20) { + qCWarning(dcTempo()) << "Expire time too short"; + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + }); +} + +void Tempo::getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode) +{ + // Obtaining access token + if(authorizationCode.isEmpty()) + qWarning(dcTempo()) << "No authorization code given!"; + if(m_clientId.isEmpty()) + qWarning(dcTempo()) << "Client key not set!"; + if(m_clientSecret.isEmpty()) + qWarning(dcTempo()) << "Client secret not set!"; + + QUrl url = QUrl(m_baseTokenUrl); + QUrlQuery query; url.setQuery(query); + + query.clear(); + query.addQueryItem("client_id", m_clientId); + query.addQueryItem("client_secret", m_clientSecret); + query.addQueryItem("redirect_uri", m_redirectUri); + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", authorizationCode); + // query.addQueryItem("code_verifier", m_codeChallenge); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8()); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData); + if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) { + setAuthenticated(false); + return; + } + m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); + receivedAccessToken(m_accessToken); + m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray(); + receivedRefreshToken(m_refreshToken); + + if (jsonDoc.toVariant().toMap().contains("expires_in")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcTempo()) << "Token expires in" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcTempo()) << "Token refresh timer not initialized"; + setAuthenticated(false); + return; + } + if (expireTime < 20) { + qCWarning(dcTempo()) << "Expire time too short"; + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + }); +} + void Tempo::getAccounts() { QUrl url = QUrl(m_baseControlUrl+"/accounts"); @@ -130,12 +250,52 @@ void Tempo::getAccounts() void Tempo::getWorkloadByAccount(const QString &accountKey, QDate from, QDate to) { + QUrl url = QUrl(m_baseControlUrl+"/worklogs/account/"+accountKey); + QUrlQuery query; + query.addQueryItem("from", from.toString(Qt::DateFormat::ISODate)); + query.addQueryItem("to", to.toString(Qt::DateFormat::ISODate)); + url.setQuery(query); + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer "+m_accessToken); + + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, accountKey, reply]{ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap(); + QVariantList worklogList = dataMap.value("results").toList(); + QList worklogs; + Q_FOREACH(QVariant var, worklogList) { + QVariantMap map = var.toMap(); + Worklog worklog; + worklog.self = map["self"].toString(); + worklog.tempoWorklogId = map["tempoWorklogId"].toInt(); + worklog.jiraWorklogId = map["jiraWorklogId"].toInt(); + worklog.issue = map["issue"].toMap().value("key").toString(); + worklog.timeSpentSeconds = map["timeSpentSeconds"].toInt(); + //TODO startDate: required (date-only) + //TODO startTime: required (time-only) + worklog.description = map["description"].toString(); + //TODO createdAt: required (datetime) + //TODO updatedAt: required (datetime) + worklog.authorAccountId = map["author"].toMap().value("accountId").toString(); + worklog.authorDisplayName = map["author"].toMap().value("displayName").toString(); + worklogs.append(worklog); + } + if (!worklogs.isEmpty()) + emit accountWorklogsReceived(accountKey, worklogs); + }); } void Tempo::onRefreshTimer() { - + qCDebug(dcTempo()) << "Refresh authentication token"; + getAccessTokenFromRefreshToken(m_refreshToken); } void Tempo::setAuthenticated(bool state) @@ -153,3 +313,87 @@ void Tempo::setConnected(bool state) emit connectionChanged(state); } } + +bool Tempo::checkStatusCode(QNetworkReply *reply, const QByteArray &rawData) +{ + // Check for the internet connection + if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError || + reply->error() == QNetworkReply::NetworkError::UnknownNetworkError || + reply->error() == QNetworkReply::NetworkError::TemporaryNetworkFailureError) { + qCWarning(dcTempo()) << "Connection error" << reply->errorString(); + setConnected(false); + setAuthenticated(false); + return false; + } else { + setConnected(true); + } + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData, &error); + + switch (status){ + case 200: //The request was successful. Typically returned for successful GET requests. + case 204: //The request was successful. Typically returned for successful PUT/DELETE requests with no payload. + break; + case 400: //Error occurred (e.g. validation error - value is out of range) + if(!jsonDoc.toVariant().toMap().contains("error")) { + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_client") { + qWarning(dcTempo()) << "Client token provided doesn’t correspond to client that generated auth code."; + } + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_redirect_uri") { + qWarning(dcTempo()) << "Missing redirect_uri parameter."; + } + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_code") { + qWarning(dcTempo()) << "Expired authorization code."; + } + } + setAuthenticated(false); + return false; + case 401: + qWarning(dcTempo()) << "Client does not have permission to use this API."; + setAuthenticated(false); + return false; + case 403: + qCWarning(dcTempo()) << "Forbidden, Scope has not been granted or home appliance is not assigned to HC account"; + setAuthenticated(false); + return false; + case 404: + qCWarning(dcTempo()) << "Not Found. This resource is not available (e.g. no images on washing machine)"; + return false; + case 405: + qWarning(dcTempo()) << "Wrong HTTP method used."; + setAuthenticated(false); + return false; + case 408: + qCWarning(dcTempo())<< "Request Timeout, API Server failed to produce an answer or has no connection to backend service"; + return false; + case 409: + qCWarning(dcTempo()) << "Conflict - Command/Query cannot be executed for the home appliance, the error response contains the error details"; + qCWarning(dcTempo()) << "Error" << jsonDoc; + return false; + case 415: + qCWarning(dcTempo())<< "Unsupported Media Type. The request's Content-Type is not supported"; + return false; + case 429: + qCWarning(dcTempo())<< "Too Many Requests, the number of requests for a specific endpoint exceeded the quota of the client"; + return false; + case 500: + qCWarning(dcTempo())<< "Internal Server Error, in case of a server configuration error or any errors in resource files"; + return false; + case 503: + qCWarning(dcTempo())<< "Service Unavailable,if a required backend service is not available"; + return false; + default: + break; + } + + if (error.error != QJsonParseError::NoError) { + qCWarning(dcTempo()) << "Received invalide JSON object" << rawData; + qCWarning(dcTempo()) << "Status" << status; + setAuthenticated(false); + return false; + } + + setAuthenticated(true); + return true; +} diff --git a/tempo/tempo.h b/tempo/tempo.h index c6316c1f..9bfd7d51 100644 --- a/tempo/tempo.h +++ b/tempo/tempo.h @@ -88,11 +88,25 @@ public: Customer customer; }; + struct Worklog { + QUrl self; + int tempoWorklogId; + int jiraWorklogId; + QString issue; + int timeSpentSeconds; + QDateTime startedAt; + QString description; + QDateTime createdAt; + QDateTime updatedAt; + QString authorAccountId; + QString authorDisplayName; + }; + explicit Tempo(NetworkAccessManager *networkmanager, const QByteArray &clientId, const QByteArray &clientSecret, QObject *parent = nullptr); QByteArray accessToken(); QByteArray refreshToken(); - static QUrl getLoginUrl(const QUrl &redirectUrl, const QString &jiraCloudInstanceName, const QByteArray &clientId); + QUrl getLoginUrl(const QUrl &redirectUrl, const QString &jiraCloudInstanceName); void getAccessTokenFromRefreshToken(const QByteArray &refreshToken); void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode); @@ -130,6 +144,8 @@ signals: void receivedRefreshToken(const QByteArray &refreshToken); void receivedAccessToken(const QByteArray &accessToken); void accountsReceived(const QList accounts); + + void accountWorklogsReceived(const QString &accountKey, QList worklogs); }; #endif // TEMPO_H