summaryrefslogtreecommitdiff
path: root/src/plugins/gitlab
diff options
context:
space:
mode:
authorChristian Stenger <christian.stenger@qt.io>2022-05-12 13:54:00 +0200
committerChristian Stenger <christian.stenger@qt.io>2022-05-31 10:47:11 +0000
commitdd27901759e3f03edfb7926fbd63fc821d509760 (patch)
tree22bd4bb11e05997f4c75ec4cdc9fd30b7e249a53 /src/plugins/gitlab
parentcd1af2864bf79c4cc8c7b79c4dae0d1ca51402c0 (diff)
downloadqt-creator-dd27901759e3f03edfb7926fbd63fc821d509760.tar.gz
GitLab: Allow fetching events
Projects that are linked to a GitLab instance will now fetch notifications for this project and print them to the vcs output pane. Change-Id: Ifb960e64b30a260327efb28a3dfd26f6457503a0 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: David Schulz <david.schulz@qt.io>
Diffstat (limited to 'src/plugins/gitlab')
-rw-r--r--src/plugins/gitlab/gitlabplugin.cpp181
-rw-r--r--src/plugins/gitlab/gitlabplugin.h3
-rw-r--r--src/plugins/gitlab/gitlabprojectsettings.cpp7
-rw-r--r--src/plugins/gitlab/gitlabprojectsettings.h5
-rw-r--r--src/plugins/gitlab/queryrunner.cpp8
-rw-r--r--src/plugins/gitlab/queryrunner.h3
-rw-r--r--src/plugins/gitlab/resultparser.cpp66
-rw-r--r--src/plugins/gitlab/resultparser.h24
8 files changed, 294 insertions, 3 deletions
diff --git a/src/plugins/gitlab/gitlabplugin.cpp b/src/plugins/gitlab/gitlabplugin.cpp
index 2a4dd7c69d..5df45c8e68 100644
--- a/src/plugins/gitlab/gitlabplugin.cpp
+++ b/src/plugins/gitlab/gitlabplugin.cpp
@@ -29,6 +29,8 @@
#include "gitlaboptionspage.h"
#include "gitlabparameters.h"
#include "gitlabprojectsettings.h"
+#include "queryrunner.h"
+#include "resultparser.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
@@ -36,24 +38,39 @@
#include <git/gitplugin.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectpanelfactory.h>
+#include <projectexplorer/session.h>
#include <utils/qtcassert.h>
+#include <vcsbase/vcsoutputwindow.h>
#include <QAction>
#include <QMessageBox>
#include <QPointer>
+#include <QTimer>
namespace GitLab {
namespace Constants {
const char GITLAB_OPEN_VIEW[] = "GitLab.OpenView";
} // namespace Constants
-class GitLabPluginPrivate
+class GitLabPluginPrivate : public QObject
{
public:
GitLabParameters parameters;
GitLabOptionsPage optionsPage{&parameters};
QHash<ProjectExplorer::Project *, GitLabProjectSettings *> projectSettings;
QPointer<GitLabDialog> dialog;
+
+ QTimer notificationTimer;
+ QString projectName;
+ Utils::Id serverId;
+ bool runningQuery = false;
+
+ void setupNotificationTimer();
+ void fetchEvents();
+ void fetchUser();
+ void createAndSendEventsRequest(const QDateTime timeStamp, int page = -1);
+ void handleUser(const User &user);
+ void handleEvents(const Events &events, const QDateTime &timeStamp);
};
static GitLabPluginPrivate *dd = nullptr;
@@ -93,6 +110,9 @@ bool GitLabPlugin::initialize(const QStringList & /*arguments*/, QString * /*err
if (dd->dialog)
dd->dialog->updateRemotes();
});
+ connect(ProjectExplorer::SessionManager::instance(),
+ &ProjectExplorer::SessionManager::startupProjectChanged,
+ this, &GitLabPlugin::onStartupProjectChanged);
return true;
}
@@ -119,6 +139,141 @@ void GitLabPlugin::openView()
dd->dialog->raise();
}
+void GitLabPlugin::onStartupProjectChanged()
+{
+ QTC_ASSERT(dd, return);
+ disconnect(&dd->notificationTimer);
+ ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ if (!project) {
+ dd->notificationTimer.stop();
+ return;
+ }
+
+ const GitLabProjectSettings *projSettings = projectSettings(project);
+ if (!projSettings->isLinked()) {
+ dd->notificationTimer.stop();
+ return;
+ }
+
+ dd->fetchEvents();
+ dd->setupNotificationTimer();
+}
+
+void GitLabPluginPrivate::setupNotificationTimer()
+{
+ // make interval configurable?
+ notificationTimer.setInterval(15 * 60 * 1000);
+ QObject::connect(&notificationTimer, &QTimer::timeout, this, &GitLabPluginPrivate::fetchEvents);
+ notificationTimer.start();
+}
+
+void GitLabPluginPrivate::fetchEvents()
+{
+ ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ QTC_ASSERT(project, return);
+
+ if (runningQuery)
+ return;
+
+ const GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
+ projectName = projSettings->currentProject();
+ serverId = projSettings->currentServer();
+
+ const QDateTime lastRequest = projSettings->lastRequest();
+ if (!lastRequest.isValid()) { // we haven't queried events for this project yet
+ fetchUser();
+ return;
+ }
+ createAndSendEventsRequest(lastRequest);
+}
+
+void GitLabPluginPrivate::fetchUser()
+{
+ if (runningQuery)
+ return;
+
+ const Query query(Query::User);
+ QueryRunner *runner = new QueryRunner(query, serverId, this);
+ QObject::connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) {
+ handleUser(ResultParser::parseUser(result));
+ });
+ QObject::connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
+ runningQuery = true;
+ runner->start();
+}
+
+void GitLabPluginPrivate::createAndSendEventsRequest(const QDateTime timeStamp, int page)
+{
+ if (runningQuery)
+ return;
+
+ Query query(Query::Events, {projectName});
+ QStringList additional = {"sort=asc"};
+
+ QDateTime after = timeStamp.addDays(-1);
+ additional.append(QLatin1String("after=%1").arg(after.toString("yyyy-MM-dd")));
+ query.setAdditionalParameters(additional);
+
+ if (page > 1)
+ query.setPageParameter(page);
+
+ QueryRunner *runner = new QueryRunner(query, serverId, this);
+ QObject::connect(runner, &QueryRunner::resultRetrieved, this,
+ [this, timeStamp](const QByteArray &result) {
+ handleEvents(ResultParser::parseEvents(result), timeStamp);
+ });
+ QObject::connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
+ runningQuery = true;
+ runner->start();
+}
+
+void GitLabPluginPrivate::handleUser(const User &user)
+{
+ runningQuery = false;
+
+ QTC_ASSERT(user.error.message.isEmpty(), return);
+ const QDateTime timeStamp = QDateTime::fromString(user.lastLogin, Qt::ISODateWithMs);
+ createAndSendEventsRequest(timeStamp);
+}
+
+void GitLabPluginPrivate::handleEvents(const Events &events, const QDateTime &timeStamp)
+{
+ runningQuery = false;
+
+ ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ QTC_ASSERT(project, return);
+
+ GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
+ QTC_ASSERT(projSettings->currentProject() == projectName, return);
+
+ if (!projSettings->isLinked()) // link state has changed meanwhile - ignore the request
+ return;
+
+ if (!events.error.message.isEmpty()) {
+ VcsBase::VcsOutputWindow::appendError("GitLab: Error while fetching events. "
+ + events.error.message + '\n');
+ return;
+ }
+
+ QDateTime lastTimeStamp;
+ for (const Event &event : events.events) {
+ const QDateTime eventTimeStamp = QDateTime::fromString(event.timeStamp, Qt::ISODateWithMs);
+ if (!timeStamp.isValid() || timeStamp < eventTimeStamp) {
+ VcsBase::VcsOutputWindow::appendMessage("GitLab: " + event.toMessage());
+ if (!lastTimeStamp.isValid() || lastTimeStamp < eventTimeStamp)
+ lastTimeStamp = eventTimeStamp;
+ }
+ }
+ if (lastTimeStamp.isValid()) {
+ if (auto outputWindow = VcsBase::VcsOutputWindow::instance())
+ outputWindow->flash();
+ projSettings->setLastRequest(lastTimeStamp);
+ }
+
+ if (events.pageInfo.currentPage < events.pageInfo.totalPages)
+ createAndSendEventsRequest(timeStamp, events.pageInfo.currentPage + 1);
+}
+
QList<GitLabServer> GitLabPlugin::allGitLabServers()
{
QTC_ASSERT(dd, return {});
@@ -152,4 +307,28 @@ GitLabOptionsPage *GitLabPlugin::optionsPage()
return &dd->optionsPage;
}
+void GitLabPlugin::linkedStateChanged(bool enabled)
+{
+ QTC_ASSERT(dd, return);
+
+ ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ if (project) {
+ const GitLabProjectSettings *pSettings = projectSettings(project);
+ dd->serverId = pSettings->currentServer();
+ dd->projectName = pSettings->currentProject();
+ } else {
+ dd->serverId = Utils::Id();
+ dd->projectName = QString();
+ }
+
+ if (enabled) {
+ dd->fetchEvents();
+ dd->setupNotificationTimer();
+ } else {
+ QObject::disconnect(&dd->notificationTimer, &QTimer::timeout,
+ dd, &GitLabPluginPrivate::fetchEvents);
+ dd->notificationTimer.stop();
+ }
+}
+
} // namespace GitLab
diff --git a/src/plugins/gitlab/gitlabplugin.h b/src/plugins/gitlab/gitlabplugin.h
index 14368ef29f..f1abf1bd1e 100644
--- a/src/plugins/gitlab/gitlabplugin.h
+++ b/src/plugins/gitlab/gitlabplugin.h
@@ -33,6 +33,7 @@ namespace ProjectExplorer { class Project; }
namespace GitLab {
+class Events;
class GitLabProjectSettings;
class GitLabOptionsPage;
@@ -53,8 +54,10 @@ public:
static GitLabProjectSettings *projectSettings(ProjectExplorer::Project *project);
static GitLabOptionsPage *optionsPage();
+ static void linkedStateChanged(bool enabled);
private:
void openView();
+ void onStartupProjectChanged();
};
} // namespace GitLab
diff --git a/src/plugins/gitlab/gitlabprojectsettings.cpp b/src/plugins/gitlab/gitlabprojectsettings.cpp
index 22921e229d..e24b469982 100644
--- a/src/plugins/gitlab/gitlabprojectsettings.cpp
+++ b/src/plugins/gitlab/gitlabprojectsettings.cpp
@@ -49,6 +49,7 @@ namespace GitLab {
const char PSK_LINKED_ID[] = "GitLab.LinkedId";
const char PSK_SERVER[] = "GitLab.Server";
const char PSK_PROJECT[] = "GitLab.Project";
+const char PSK_LAST_REQ[] = "GitLab.LastRequest";
static QString accessLevelString(int accessLevel)
{
@@ -106,6 +107,7 @@ void GitLabProjectSettings::load()
m_id = Utils::Id::fromSetting(m_project->namedSettings(PSK_LINKED_ID));
m_host = m_project->namedSettings(PSK_SERVER).toString();
m_currentProject = m_project->namedSettings(PSK_PROJECT).toString();
+ m_lastRequest = m_project->namedSettings(PSK_LAST_REQ).toDateTime();
// may still be wrong, but we avoid an additional request by just doing sanity check here
if (!m_id.isValid() || m_host.isEmpty())
@@ -124,6 +126,7 @@ void GitLabProjectSettings::save()
m_project->setNamedSettings(PSK_SERVER, QString());
}
m_project->setNamedSettings(PSK_PROJECT, m_currentProject);
+ m_project->setNamedSettings(PSK_LAST_REQ, m_lastRequest);
}
GitLabProjectSettingsWidget::GitLabProjectSettingsWidget(ProjectExplorer::Project *project,
@@ -184,6 +187,7 @@ void GitLabProjectSettingsWidget::unlink()
m_projectSettings->setLinked(false);
m_projectSettings->setCurrentProject({});
updateEnabledStates();
+ GitLabPlugin::linkedStateChanged(false);
}
void GitLabProjectSettingsWidget::checkConnection(CheckMode mode)
@@ -245,6 +249,7 @@ void GitLabProjectSettingsWidget::onConnectionChecked(const Project &project,
m_projectSettings->setCurrentServerHost(remote);
m_projectSettings->setLinked(true);
m_projectSettings->setCurrentProject(projectName);
+ GitLabPlugin::linkedStateChanged(true);
}
updateEnabledStates();
}
@@ -282,8 +287,10 @@ void GitLabProjectSettingsWidget::updateUi()
m_hostCB->setCurrentIndex(m_hostCB->findData(QVariant::fromValue(serverHost)));
m_linkedGitLabServer->setCurrentIndex(
m_linkedGitLabServer->findData(QVariant::fromValue(server)));
+ GitLabPlugin::linkedStateChanged(true);
} else {
m_projectSettings->setLinked(false);
+ GitLabPlugin::linkedStateChanged(false);
}
}
updateEnabledStates();
diff --git a/src/plugins/gitlab/gitlabprojectsettings.h b/src/plugins/gitlab/gitlabprojectsettings.h
index de8bdb851d..5a9081adc7 100644
--- a/src/plugins/gitlab/gitlabprojectsettings.h
+++ b/src/plugins/gitlab/gitlabprojectsettings.h
@@ -28,6 +28,7 @@
#include <projectexplorer/projectsettingswidget.h>
#include <utils/id.h>
+#include <QDateTime>
#include <QObject>
#include <QWidget>
@@ -59,9 +60,12 @@ public:
QString currentProject() const { return m_currentProject; }
bool isLinked() const { return m_linked; }
void setLinked(bool linked);
+ QDateTime lastRequest() const { return m_lastRequest; }
+ void setLastRequest(const QDateTime &lastRequest) { m_lastRequest = lastRequest; }
ProjectExplorer::Project *project() const { return m_project; }
static std::tuple<QString, QString, int> remotePartsFromRemote(const QString &remote);
+
private:
void load();
void save();
@@ -69,6 +73,7 @@ private:
ProjectExplorer::Project *m_project = nullptr;
QString m_host;
Utils::Id m_id;
+ QDateTime m_lastRequest;
QString m_currentProject;
bool m_linked = false;
};
diff --git a/src/plugins/gitlab/queryrunner.cpp b/src/plugins/gitlab/queryrunner.cpp
index e0369d78c5..86ad6b303a 100644
--- a/src/plugins/gitlab/queryrunner.cpp
+++ b/src/plugins/gitlab/queryrunner.cpp
@@ -43,6 +43,7 @@ const char API_PREFIX[] = "/api/v4";
const char QUERY_PROJECT[] = "/projects/%1";
const char QUERY_PROJECTS[] = "/projects?simple=true";
const char QUERY_USER[] = "/user";
+const char QUERY_EVENTS[] = "/projects/%1/events";
Query::Query(Type type, const QStringList &parameter)
: m_type(type)
@@ -62,7 +63,7 @@ void Query::setAdditionalParameters(const QStringList &additional)
bool Query::hasPaginatedResults() const
{
- return m_type == Query::Projects;
+ return m_type == Query::Projects || m_type == Query::Events;
}
QString Query::toString() const
@@ -82,6 +83,11 @@ QString Query::toString() const
case Query::User:
query += QUERY_USER;
break;
+ case Query::Events:
+ QTC_ASSERT(!m_parameter.isEmpty(), return {});
+ query += QLatin1String(QUERY_EVENTS).arg(QLatin1String(
+ QUrl::toPercentEncoding(m_parameter.at(0))));
+ break;
}
if (m_pageParameter > 0) {
query.append(m_type == Query::Projects ? '&' : '?');
diff --git a/src/plugins/gitlab/queryrunner.h b/src/plugins/gitlab/queryrunner.h
index a431921ab7..ead892f55d 100644
--- a/src/plugins/gitlab/queryrunner.h
+++ b/src/plugins/gitlab/queryrunner.h
@@ -40,7 +40,8 @@ public:
NoQuery,
User,
Project,
- Projects
+ Projects,
+ Events
};
explicit Query(Type type, const QStringList &parameters = {});
diff --git a/src/plugins/gitlab/resultparser.cpp b/src/plugins/gitlab/resultparser.cpp
index 7fb5db8d2f..e9ce0d7083 100644
--- a/src/plugins/gitlab/resultparser.cpp
+++ b/src/plugins/gitlab/resultparser.cpp
@@ -32,6 +32,25 @@
#include <utility>
namespace GitLab {
+
+
+QString Event::toMessage() const
+{
+ QString message;
+ if (author.realname.isEmpty())
+ message.append(author.name);
+ else
+ message.append(author.realname + " (" + author.name + ')');
+ message.append(' ');
+ if (!pushData.isEmpty())
+ message.append(pushData);
+ else if (!targetTitle.isEmpty())
+ message.append(action + ' ' + targetType + " '" + targetTitle +'\'');
+ else
+ message.append(action + ' ' + targetType);
+ return message;
+}
+
namespace ResultParser {
static PageInformation paginationInformation(const QByteArray &header)
@@ -133,6 +152,7 @@ static User userFromJson(const QJsonObject &jsonObj)
user.realname = jsonObj.value("name").toString();
user.id = jsonObj.value("id").toInt(-1);
user.email = jsonObj.value("email").toString();
+ user.lastLogin = jsonObj.value("last_sign_in_at").toString();
user.bot = jsonObj.value("bot").toBool();
return user;
}
@@ -162,6 +182,31 @@ static Project projectFromJson(const QJsonObject &jsonObj)
return project;
}
+static Event eventFromJson(const QJsonObject &jsonObj)
+{
+ Event event;
+ event.action = jsonObj.value("action_name").toString();
+ const QJsonValue value = jsonObj.value("target_type");
+ event.targetType = value.isNull() ? "project" : jsonObj.value("target_type").toString();
+ if (event.targetType == "DiffNote") {
+ const QJsonObject noteObject = jsonObj.value("note").toObject();
+ event.targetType = noteObject.value("noteable_type").toString();
+ }
+ event.targetTitle = jsonObj.value("target_title").toString();
+ event.author = userFromJson(jsonObj.value("author").toObject());
+ event.timeStamp = jsonObj.value("created_at").toString();
+ if (jsonObj.contains("push_data")) {
+ const QJsonObject pushDataObj = jsonObj.value("push_data").toObject();
+ if (!pushDataObj.isEmpty()) {
+ const QString action = pushDataObj.value("action").toString();
+ const QString ref = pushDataObj.value("ref").toString();
+ const QString refType = pushDataObj.value("ref_type").toString();
+ event.pushData = action + ' ' + refType + " '" + ref + '\'';
+ }
+ }
+ return event;
+}
+
User parseUser(const QByteArray &input)
{
auto [error, userObj] = preHandleSingle(input);
@@ -204,6 +249,27 @@ Projects parseProjects(const QByteArray &input)
return result;
}
+Events parseEvents(const QByteArray &input)
+{
+ auto [header, json] = splitHeaderAndBody(input);
+ auto [error, jsonDoc] = preHandleHeaderAndBody(header, json);
+ Events result;
+ if (!error.message.isEmpty()) {
+ result.error = error;
+ return result;
+ }
+ result.pageInfo = paginationInformation(header);
+ const QJsonArray eventsArray = jsonDoc.array();
+ for (const QJsonValue &value : eventsArray) {
+ if (!value.isObject())
+ continue;
+ const QJsonObject eventObj = value.toObject();
+ result.events.append(eventFromJson(eventObj));
+ }
+ return result;
+
+}
+
Error parseErrorMessage(const QString &message)
{
Error error;
diff --git a/src/plugins/gitlab/resultparser.h b/src/plugins/gitlab/resultparser.h
index 7c71b5c6d1..aeaeeadcc5 100644
--- a/src/plugins/gitlab/resultparser.h
+++ b/src/plugins/gitlab/resultparser.h
@@ -52,6 +52,7 @@ public:
QString name;
QString realname;
QString email;
+ QString lastLogin;
Error error;
int id = -1;
bool bot = false;
@@ -82,11 +83,34 @@ public:
PageInformation pageInfo;
};
+class Event
+{
+public:
+ QString action;
+ QString targetType;
+ QString targetTitle;
+ QString timeStamp;
+ QString pushData;
+ User author;
+ Error error;
+
+ QString toMessage() const;
+};
+
+class Events
+{
+public:
+ QList<Event> events;
+ Error error;
+ PageInformation pageInfo;
+};
+
namespace ResultParser {
User parseUser(const QByteArray &input);
Project parseProject(const QByteArray &input);
Projects parseProjects(const QByteArray &input);
+Events parseEvents(const QByteArray &input);
Error parseErrorMessage(const QString &message);
} // namespace ResultParser