-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.ts
156 lines (137 loc) · 7.07 KB
/
main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import * as core from '@actions/core';
import * as github from '@actions/github';
import { components } from '@octokit/openapi-types';
import {
TEAM_LABEL_PREFIX,
LINKING_CHECK_RETRIES,
LINKING_CHECK_DELAY_MILLIS,
TEAMS_NOT_USING_ZENHUB,
ORGANIZATION,
TESTED_LABEL_NAME,
SKIP_MILESTONES_AND_ESTIMATES_FOR_TEAMS,
} from './consts';
import {
assignPrCreator,
fillCurrentMilestone,
findUsersTeamName,
addTeamLabel,
ensureCorrectLinkingAndEstimates,
isPullRequestTested,
isRepoIncludedInZenHubWorkspace,
retry,
} from './helpers';
type Assignee = components['schemas']['simple-user'];
type Label = components['schemas']['label'];
async function run(): Promise<void> {
try {
// This skips the action when run on a PR from external fork, i.e., when the fork is not a part of the organization.
// Do not use pull_request?.base but pull_request?.head because the former one does not container the forked repo name.
if (!github.context.payload.pull_request?.head.repo.full_name.startsWith(`${ORGANIZATION}/`)) {
core.warning(`Skipping toolkit action for PR from external fork: ${github.context.payload.pull_request?.head.repo.full_name}`);
return;
}
core.info('Pull request is from an apify organization, not from an external fork.');
// Skip when PR is not into the default branch. We only want to run this on PRs to develop or main when develop is not used but we
// don't want to run this on releases or PR chains.
const defaultBranch = github.context.payload.pull_request.head.repo.default_branch;
const targetBranch = github.context.payload.pull_request.base.ref;
if (defaultBranch !== targetBranch) {
core.info(`Skipping toolkit action for PR not into the default branch "${defaultBranch}" but "${targetBranch}" instead.`);
return;
}
core.info(`Pull request is into the default branch "${defaultBranch}".`);
// Octokit configured with repository token - this can be used to modify pull-request.
const repoToken = core.getInput('repo-token');
const repoOctokit = github.getOctokit(repoToken);
// Organization token providing read-only access to the organization.
const orgToken = core.getInput('org-token');
const orgOctokit = github.getOctokit(orgToken);
const pullRequestContext = github.context.payload.pull_request;
if (!pullRequestContext) throw new Error('Action works only for PRs!');
const { data: pullRequest } = await repoOctokit.rest.pulls.get({
owner: pullRequestContext.base.repo.owner.login,
repo: pullRequestContext.base.repo.name,
pull_number: pullRequestContext.number,
});
// Skip the PR if not a member of one of the product teams.
const teamName = await findUsersTeamName(orgOctokit, pullRequestContext.user.login);
if (!teamName) {
core.warning(`User ${pullRequestContext.user.login} is not a member of team. Skipping toolkit action.`);
return;
}
core.info(`User ${pullRequestContext.user.login} belongs to a ${teamName} team.`);
// Skip if the repository is not connected to the ZenHub workspace.
const belongsToZenhub = await isRepoIncludedInZenHubWorkspace(pullRequest.base.repo.name);
if (!belongsToZenhub) {
core.warning(`Repository ${pullRequest.base.repo.name} is not included in ZenHub workspace. Skipping toolkit action.`);
return;
}
core.info(`Repository ${pullRequest.base.repo.name} is included in ZenHub workspace.`);
// Skip if the team is listed in TEAMS_NOT_USING_ZENHUB.
const isTeamUsingZenhub = !TEAMS_NOT_USING_ZENHUB.includes(teamName);
if (!isTeamUsingZenhub) {
core.info(`Team ${teamName} is listed in TEAMS_NOT_USING_ZENHUB. Skipping toolkit action.`);
return;
}
core.info(`Team ${teamName} uses a ZenHub.`);
// All these 4 actions below are idempotent, so they can be run on every PR update.
// Also, these actions do not require any action from a PR author.
// 1. Assigns PR creator if not already assigned.
const isCreatorAssigned = pullRequestContext.assignees.find((u: Assignee) => u?.login === pullRequestContext.user.login);
if (!isCreatorAssigned) {
await assignPrCreator(github.context, repoOctokit, pullRequest);
core.info('Creator successfully assigned.');
} else {
core.info('Creator already assigned.');
}
// 2. Assigns current milestone if not already assigned.
if (!pullRequestContext.milestone && !SKIP_MILESTONES_AND_ESTIMATES_FOR_TEAMS.includes(teamName)) {
const milestoneTitle = await fillCurrentMilestone(github.context, repoOctokit, pullRequest, teamName);
core.info(`Milestone successfully filled with ${milestoneTitle}.`);
} else {
core.info('Milestone already assigned or team is skipped.');
}
// 3. Adds team label if not already there.
const teamLabel = pullRequestContext.labels.find((label: Label) => label.name.startsWith(TEAM_LABEL_PREFIX));
if (!teamLabel) {
await addTeamLabel(github.context, repoOctokit, pullRequest, teamName);
core.info(`Team label for team ${teamName} successfully added`);
} else {
core.info(`Team label ${teamLabel.name} already present`);
}
// 4. Checks if PR is tested and adds a `tested` label if so.
const isTested = await isPullRequestTested(repoOctokit, pullRequest);
if (isTested) {
core.info('PR is tested.');
await repoOctokit.rest.issues.addLabels({
owner: ORGANIZATION,
repo: pullRequest.base.repo.name,
issue_number: pullRequest.number,
labels: [TESTED_LABEL_NAME],
});
core.info(`Label ${TESTED_LABEL_NAME} successfully added`);
} else {
core.info('PR is not tested.');
}
if (SKIP_MILESTONES_AND_ESTIMATES_FOR_TEAMS.includes(teamName)) {
core.info(`Team ${teamName} is listed in SKIP_MILESTONES_AND_ESTIMATES_FOR_TEAMS. Skipping the linking and estimate check.`);
return;
}
// On the other hand, this is a check that author of the PR correctly filled in the details.
// I.e., that the PR is linked to the ZenHub issue and that the estimate is set either on issue or on the PR.
await retry(
async () => ensureCorrectLinkingAndEstimates(pullRequest),
LINKING_CHECK_RETRIES,
LINKING_CHECK_DELAY_MILLIS,
);
core.info('Pull request is correctly linked to ZenHub issue, epic, or is adhoc and has an estimate.');
core.info('All checks passed!');
} catch (error) {
if (error instanceof Error) {
core.error(error);
console.error(error); // eslint-disable-line no-console
core.setFailed(error.message);
}
}
}
void run();