added tempo integration
parent
8150258814
commit
7a30f648a5
|
|
@ -166,6 +166,7 @@ private:
|
|||
bool m_connected = false;
|
||||
|
||||
bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData);
|
||||
|
||||
private slots:
|
||||
void onRefreshTimeout();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Tempo *>(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<Tempo *>(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<Tempo::Account> &accounts)
|
||||
{
|
||||
qCDebug(dcTempo()) << "Received" << accounts.count() << "accounts";
|
||||
|
|
@ -160,13 +299,13 @@ void IntegrationPluginTempo::onReceivedAccounts(const QList<Tempo::Account> &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();
|
||||
//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;
|
||||
}
|
||||
// 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<Tempo::Account> &acc
|
|||
emit autoThingsAppeared(desciptors);
|
||||
}
|
||||
|
||||
void IntegrationPluginTempo::onAccountWorkloadReceived(const QString &accountKey, QList<Tempo::Worklog> 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Tempo::Account> &accounts);
|
||||
|
||||
void onAccountWorkloadReceived(const QString &accountKey, QList<Tempo::Worklog> workloads);
|
||||
};
|
||||
#endif // INTEGRATIONPLUGINTEMPO_H
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
256
tempo/tempo.cpp
256
tempo/tempo.cpp
|
|
@ -35,7 +35,7 @@
|
|||
#include "extern-plugininfo.h"
|
||||
|
||||
Tempo::Tempo(NetworkAccessManager *networkmanager, const QByteArray &clientId, const QByteArray &clientSecret, QObject *parent) :
|
||||
QObject(parent),
|
||||
QObject(parent),
|
||||
m_clientId(clientId),
|
||||
m_clientSecret(clientSecret),
|
||||
m_networkManager(networkmanager)
|
||||
|
|
@ -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<Worklog> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Account> accounts);
|
||||
|
||||
void accountWorklogsReceived(const QString &accountKey, QList<Worklog> worklogs);
|
||||
};
|
||||
|
||||
#endif // TEMPO_H
|
||||
|
|
|
|||
Loading…
Reference in New Issue