Repository navigation
Expand file tree
/
Copy pathpull_request_workflow.ts
More file actions
143 lines (132 loc) · 6.31 KB
/
Copy pathpull_request_workflow.ts
File metadata and controls
143 lines (132 loc) · 6.31 KB
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
import { Result } from "../../data/model/result";
import { logDebugInfo, logError } from "../ports/logging_ports";
import type { ParamUseCase } from "./base/param_usecase";
import type { PullRequestWorkflowSteps } from "./pull_request_workflow_steps";
import { ApplicationError } from '../errors/application_error';
import type { BoundActorAuthorizationPort } from '../ports/actor_authorization_ports';
import type { BugbotReviewOperationContext } from './steps/commit/bugbot/bugbot_review_operation_context';
import type { UpdateTitleContext } from './steps/common/update_title_workflow';
import type { ProjectContentLinkContext } from './steps/common/project_content_link_workflow';
import type {
PullRequestDescriptionRequest,
PullRequestWorkflowStepContexts,
} from './pull_request_workflow_context';
export interface PullRequestWorkflowRouteContext {
readonly actor: string;
readonly membersOnly: boolean;
readonly action: string;
readonly opened: boolean;
readonly synchronize: boolean;
readonly closed: boolean;
readonly merged: boolean;
readonly automaticDescription: boolean;
readonly reviewable: boolean;
readonly review: BugbotReviewOperationContext;
}
export interface PullRequestSharedStepContexts {
readonly title: UpdateTitleContext;
readonly projectLink: ProjectContentLinkContext;
readonly steps: PullRequestWorkflowStepContexts;
}
export interface PullRequestWorkflowPorts {
updatePullRequestDescriptionUseCase: ParamUseCase<PullRequestDescriptionRequest, Result[]>;
reviewPotentialProblemsUseCase?: ParamUseCase<BugbotReviewOperationContext, Result[]>;
workflowSteps: PullRequestWorkflowSteps;
actorAuthorizationPort?: BoundActorAuthorizationPort;
sharedContexts: PullRequestSharedStepContexts;
}
/** Coordinates pull-request lifecycle actions while preserving their sequential order. */
export async function runPullRequestWorkflow(
context: PullRequestWorkflowRouteContext,
taskId: string,
ports: PullRequestWorkflowPorts,
): Promise<Result[]> {
try {
logPullRequestState(context);
// Even synchronize/ready/edited must repair a previous interrupted link
// before any ordinary writer or review can observe the temporary target.
const recovery = !context.closed && (context.opened || context.synchronize
|| context.action === 'ready_for_review' || context.action === 'edited')
? await ports.workflowSteps.linkPullRequestIssue.invoke({ ...ports.sharedContexts.steps.linkIssue, recoveryOnly: true })
: [];
if (recovery.some(result => !result.success)) return recovery;
const agentAllowed = await canUseAgent(context, ports.actorAuthorizationPort);
if (context.opened) {
const results = await ports.workflowSteps.updateTitle.invoke(ports.sharedContexts.title);
results.push(...(await ports.workflowSteps.assignMemberToIssue.invoke(ports.sharedContexts.steps.assignment)));
results.push(...(await ports.workflowSteps.assignReviewersToIssue.invoke(ports.sharedContexts.steps.reviewers)));
results.push(...(await ports.workflowSteps.linkPullRequestProject.invoke(ports.sharedContexts.projectLink)));
// Successful pending repair already verified this relationship. Do not
// restart its observation window during the same opened invocation.
const linkage = recovery.some(result => result.executed)
? [] : await ports.workflowSteps.linkPullRequestIssue.invoke(ports.sharedContexts.steps.linkIssue);
results.push(...linkage);
if (linkage.some(result => !result.success)) return results;
results.push(...(await ports.workflowSteps.syncSizeAndProgressLabels.invoke(ports.sharedContexts.steps.syncLabels)));
results.push(...(await ports.workflowSteps.checkPriorityPullRequestSize.invoke(ports.sharedContexts.steps.priority)));
if (agentAllowed && context.automaticDescription) {
results.push(...(await ports.updatePullRequestDescriptionUseCase.invoke({
context: ports.sharedContexts.steps.description,
trigger: 'automatic',
})));
}
if (agentAllowed) results.push(...(await runPullRequestReview(context, ports)));
return results;
}
if (context.synchronize) {
const results = agentAllowed && context.automaticDescription
? await ports.updatePullRequestDescriptionUseCase.invoke({
context: ports.sharedContexts.steps.description,
trigger: 'automatic',
})
: [];
if (agentAllowed) results.push(...(await runPullRequestReview(context, ports)));
return results;
}
if (context.action === 'ready_for_review') {
return agentAllowed ? await runPullRequestReview(context, ports) : [];
}
if (context.action === 'edited') {
return ports.workflowSteps.updateTitle.invoke(ports.sharedContexts.title);
}
if (context.closed && context.merged) {
const results = await ports.workflowSteps.closeIssueAfterMerging.invoke(ports.sharedContexts.steps.closeIssue);
if (ports.workflowSteps.retireBranchSync) results.push(...await ports.workflowSteps.retireBranchSync.invoke(ports.sharedContexts.steps.retireBranchSync));
return results;
}
} catch (cause) {
const semanticError = new ApplicationError('workflow.failed', "Unable to process the pull request.", { cause });
logError(semanticError);
return [
new Result({
id: taskId,
success: false,
executed: true,
steps: ["Unable to process the pull request."],
errors: [semanticError],
}),
];
}
return [];
}
async function canUseAgent(
context: PullRequestWorkflowRouteContext,
authorization: BoundActorAuthorizationPort | undefined,
): Promise<boolean> {
if (!context.membersOnly) return true;
if (!authorization) return false;
return authorization.isActorAllowedToUseMemberOnlyAutomation(context.actor);
}
async function runPullRequestReview(
context: PullRequestWorkflowRouteContext,
ports: PullRequestWorkflowPorts,
): Promise<Result[]> {
if (!ports.reviewPotentialProblemsUseCase || !context.reviewable) return [];
return ports.reviewPotentialProblemsUseCase.invoke(context.review);
}
function logPullRequestState(context: PullRequestWorkflowRouteContext): void {
logDebugInfo(`PR action ${context.action}`);
logDebugInfo(`PR isOpened ${context.opened}`);
logDebugInfo(`PR isMerged ${context.merged}`);
logDebugInfo(`PR isClosed ${context.closed}`);
}