diff options
author | Christian Stenger <christian.stenger@qt.io> | 2022-05-12 13:54:00 +0200 |
---|---|---|
committer | Christian Stenger <christian.stenger@qt.io> | 2022-05-31 10:47:11 +0000 |
commit | dd27901759e3f03edfb7926fbd63fc821d509760 (patch) | |
tree | 22bd4bb11e05997f4c75ec4cdc9fd30b7e249a53 /src/plugins/gitlab | |
parent | cd1af2864bf79c4cc8c7b79c4dae0d1ca51402c0 (diff) | |
download | qt-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.cpp | 181 | ||||
-rw-r--r-- | src/plugins/gitlab/gitlabplugin.h | 3 | ||||
-rw-r--r-- | src/plugins/gitlab/gitlabprojectsettings.cpp | 7 | ||||
-rw-r--r-- | src/plugins/gitlab/gitlabprojectsettings.h | 5 | ||||
-rw-r--r-- | src/plugins/gitlab/queryrunner.cpp | 8 | ||||
-rw-r--r-- | src/plugins/gitlab/queryrunner.h | 3 | ||||
-rw-r--r-- | src/plugins/gitlab/resultparser.cpp | 66 | ||||
-rw-r--r-- | src/plugins/gitlab/resultparser.h | 24 |
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{¶meters}; 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(¬ificationTimer, &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 ¶meter) : 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 ¶meters = {}); 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 |