mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-07 14:02:48 +00:00
189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
/*
|
|
* Copyright (C) 2019-2022 Open StApps
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
import {
|
|
Api,
|
|
AccessLevel,
|
|
MembershipScope,
|
|
MergeRequestMergeStatus,
|
|
MergeRequestState,
|
|
Scope,
|
|
User,
|
|
} from '@openstapps/gitlab-api';
|
|
import {Logger} from '@openstapps/logger';
|
|
import {CONCURRENCY, GROUPS, MAX_DEPTH_FOR_REMINDER, NOTE_PREFIX, SLACK_CHANNEL} from '../configuration.js';
|
|
import {mapAsyncLimit} from '@openstapps/collection-utils';
|
|
|
|
/**
|
|
* Remind people of open merge requests
|
|
* @param api GitLab API to make requests with
|
|
*/
|
|
export async function remind(api: Api): Promise<void> {
|
|
// get a list of open merge requests
|
|
const allMergeRequests = await api.getMergeRequests(
|
|
MembershipScope.GROUPS,
|
|
GROUPS[0],
|
|
MergeRequestState.OPENED,
|
|
);
|
|
|
|
const mergeRequests = allMergeRequests.filter(mergeRequest => {
|
|
const parts = mergeRequest.web_url.split('/');
|
|
|
|
// remove protocol, server name and main group
|
|
parts.splice(0, parts.indexOf('gitlab.com') + 1 + 1);
|
|
|
|
// remove merge_requests and INDEX parts
|
|
parts.splice(-1 - 1);
|
|
|
|
return parts.length <= MAX_DEPTH_FOR_REMINDER;
|
|
});
|
|
|
|
Logger.info(`Found ${mergeRequests.length} open merge requests.`);
|
|
|
|
// instantiate slack client
|
|
const client = undefined;
|
|
|
|
// get members of the main group
|
|
const members = await api.getMembers(MembershipScope.GROUPS, GROUPS[0]);
|
|
|
|
// filter members with at least maintainer status
|
|
const maintainers = members.filter(member => member.access_level >= AccessLevel.Maintainer);
|
|
|
|
// extract maintainer's usernames
|
|
const maintainerUsernames = maintainers.map(maintainer => maintainer.username);
|
|
|
|
// sort maintainer's usernames alphabetically
|
|
maintainerUsernames.sort((a, b) => {
|
|
return a.localeCompare(b);
|
|
});
|
|
|
|
Logger.info(`Found ${maintainers.length} maintainer(s).`);
|
|
|
|
await mapAsyncLimit(
|
|
mergeRequests,
|
|
async mergeRequest => {
|
|
// check if merge request is WIP
|
|
if (mergeRequest.work_in_progress) {
|
|
Logger.info(`Merge request '${mergeRequest.title}' is WIP.`);
|
|
|
|
return;
|
|
}
|
|
|
|
// get merge request approval
|
|
const approval = await api.getMergeRequestApproval(mergeRequest.project_id, mergeRequest.iid);
|
|
|
|
// get merge request discussions
|
|
const discussions = await api.getMergeRequestDiscussions(mergeRequest.project_id, mergeRequest.iid);
|
|
|
|
// check if at least one of the discussions is unresolved
|
|
const hasUnresolvedDiscussions = discussions.some(discussion => {
|
|
return discussion.notes.some(note => {
|
|
return note.resolvable && (note.resolved === undefined || !note.resolved);
|
|
});
|
|
});
|
|
|
|
if (hasUnresolvedDiscussions) {
|
|
let recipient = mergeRequest.author.username;
|
|
|
|
if (mergeRequest.assignee !== undefined && mergeRequest.assignee !== null) {
|
|
recipient = mergeRequest.assignee.username;
|
|
}
|
|
|
|
// create note in merge request
|
|
await api.createNote(
|
|
mergeRequest.project_id,
|
|
Scope.MERGE_REQUESTS,
|
|
mergeRequest.iid,
|
|
`${NOTE_PREFIX} Please resolve pending discussions, @${recipient}!`,
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
if (approval.merge_status === MergeRequestMergeStatus.CAN_BE_MERGED) {
|
|
if (approval.approvals_left > 0) {
|
|
Logger.warn(`Merge request '${mergeRequest.title}' needs more approvals!`);
|
|
|
|
// get possible appropers, prefixed with '@' and joined with commas
|
|
const possibleApprovers = maintainerUsernames
|
|
.filter(username => {
|
|
if (mergeRequest!.assignee.username === username) {
|
|
return false;
|
|
}
|
|
if (username.includes('openstapps') || username.includes('kphilipp')) {
|
|
return false;
|
|
}
|
|
|
|
if (approval.approved_by.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
return approval.approved_by.find(
|
|
(approver: {
|
|
/**
|
|
* Possible approver
|
|
*/
|
|
user: User;
|
|
}) => {
|
|
return approver.user.username !== username;
|
|
},
|
|
);
|
|
})
|
|
.map(username => `@${username}`)
|
|
.join(' ');
|
|
|
|
// send message to slack
|
|
await client?.chat.postMessage({
|
|
channel: SLACK_CHANNEL,
|
|
text: `Merge request '${mergeRequest.title}' needs more approvals! See ${mergeRequest.web_url}!`,
|
|
});
|
|
|
|
// assign reviewers
|
|
await api.createNote(
|
|
mergeRequest.project_id,
|
|
Scope.MERGE_REQUESTS,
|
|
mergeRequest.iid,
|
|
`/assign_reviewer ${possibleApprovers}`,
|
|
);
|
|
} else {
|
|
Logger.log(`Merge request '${mergeRequest.title}' is ready to be merged!`);
|
|
|
|
// send message to slack
|
|
await client?.chat.postMessage({
|
|
channel: SLACK_CHANNEL,
|
|
text: `Merge request '${mergeRequest.title}' is ready to be merged! See ${mergeRequest.web_url}!`,
|
|
});
|
|
|
|
// prefix maintainers with '@' and join with commas
|
|
const possibleMergers = maintainerUsernames
|
|
.filter(username => {
|
|
return mergeRequest!.assignee.username !== username;
|
|
})
|
|
.map(username => `@${username}`)
|
|
.join(', ');
|
|
|
|
// create note in merge request
|
|
await api.createNote(
|
|
mergeRequest.project_id,
|
|
Scope.MERGE_REQUESTS,
|
|
mergeRequest.iid,
|
|
`${NOTE_PREFIX} Merge request is ready to be merged, ${possibleMergers}!`,
|
|
);
|
|
}
|
|
}
|
|
},
|
|
CONCURRENCY,
|
|
);
|
|
}
|