Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/changes/browser/checksActions.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Codicon } from '../../../../base/common/codicons.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { derived } from '../../../../base/common/observable.js';
9
import { localize, localize2 } from '../../../../nls.js';
10
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
11
import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
12
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
13
import { ILogService } from '../../../../platform/log/common/log.js';
14
import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js';
15
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
16
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
17
import { ChatViewPaneTarget, IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
18
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
19
import { CHAT_CATEGORY } from '../../../../workbench/contrib/chat/browser/actions/chatActions.js';
20
import { IGitHubService } from '../../github/browser/githubService.js';
21
import { GitHubCheckConclusion, GitHubCheckStatus, IGitHubCICheck } from '../../github/common/types.js';
22
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
23
24
export const hasActiveSessionFailedCIChecks = new RawContextKey<boolean>('sessions.hasActiveSessionFailedCIChecks', false);
25
26
// --- Shared CI check utilities ------------------------------------------------
27
28
export const enum CICheckGroup {
29
Running,
30
Pending,
31
Failed,
32
Successful,
33
}
34
35
export function isFailedConclusion(conclusion: GitHubCheckConclusion | undefined): boolean {
36
return conclusion === GitHubCheckConclusion.Failure
37
|| conclusion === GitHubCheckConclusion.TimedOut
38
|| conclusion === GitHubCheckConclusion.ActionRequired;
39
}
40
41
export function getCheckGroup(check: IGitHubCICheck): CICheckGroup {
42
switch (check.status) {
43
case GitHubCheckStatus.InProgress:
44
return CICheckGroup.Running;
45
case GitHubCheckStatus.Queued:
46
return CICheckGroup.Pending;
47
case GitHubCheckStatus.Completed:
48
return isFailedConclusion(check.conclusion) ? CICheckGroup.Failed : CICheckGroup.Successful;
49
}
50
}
51
52
export function getCheckStateLabel(check: IGitHubCICheck): string {
53
switch (getCheckGroup(check)) {
54
case CICheckGroup.Running:
55
return localize('ci.runningState', "running");
56
case CICheckGroup.Pending:
57
return localize('ci.pendingState', "pending");
58
case CICheckGroup.Failed:
59
return localize('ci.failedState', "failed");
60
case CICheckGroup.Successful:
61
return localize('ci.successfulState', "successful");
62
}
63
}
64
65
export function getFailedChecks(checks: readonly IGitHubCICheck[]): readonly IGitHubCICheck[] {
66
return checks.filter(check => getCheckGroup(check) === CICheckGroup.Failed);
67
}
68
69
export function buildFixChecksPrompt(failedChecks: ReadonlyArray<{ check: IGitHubCICheck; annotations: string }>): string {
70
const sections = failedChecks.map(({ check, annotations }) => {
71
const parts = [
72
`Check: ${check.name}`,
73
`Status: ${getCheckStateLabel(check)}`,
74
`Conclusion: ${check.conclusion ?? 'unknown'}`,
75
];
76
77
if (check.detailsUrl) {
78
parts.push(`Details: ${check.detailsUrl}`);
79
}
80
81
parts.push('', 'Annotations and output:', annotations || 'No output available for this check run.');
82
return parts.join('\n');
83
});
84
85
return [
86
'Please fix the failed CI checks for this session immediately.',
87
'Use the failed check information below, including annotations and check output, to identify the root causes and make the necessary code changes.',
88
'Focus on resolving these CI failures. Avoid unrelated changes unless they are required to fix the checks.',
89
'',
90
'Failed CI checks:',
91
'',
92
sections.join('\n\n---\n\n'),
93
].join('\n');
94
}
95
96
/**
97
* Sets the `hasActiveSessionFailedCIChecks` context key to true when the
98
* active session has a PR with CI checks and at least one has failed.
99
*/
100
class ActiveSessionFailedCIChecksContextContribution extends Disposable implements IWorkbenchContribution {
101
102
static readonly ID = 'workbench.contrib.activeSessionFailedCIChecksContext';
103
104
constructor(
105
@IContextKeyService contextKeyService: IContextKeyService,
106
@ISessionsManagementService sessionManagementService: ISessionsManagementService,
107
@IGitHubService gitHubService: IGitHubService,
108
) {
109
super();
110
111
const ciModelObs = derived(this, reader => {
112
const session = sessionManagementService.activeSession.read(reader);
113
if (!session) {
114
return undefined;
115
}
116
const gitHubInfo = session.gitHubInfo.read(reader);
117
if (!gitHubInfo?.pullRequest) {
118
return undefined;
119
}
120
const prModel = gitHubService.getPullRequest(gitHubInfo.owner, gitHubInfo.repo, gitHubInfo.pullRequest.number);
121
const pr = prModel.pullRequest.read(reader);
122
if (!pr) {
123
return undefined;
124
}
125
return gitHubService.getPullRequestCI(gitHubInfo.owner, gitHubInfo.repo, gitHubInfo.pullRequest.number, pr.headSha);
126
});
127
128
this._register(bindContextKey(hasActiveSessionFailedCIChecks, contextKeyService, reader => {
129
const ciModel = ciModelObs.read(reader);
130
if (!ciModel) {
131
return false;
132
}
133
const checks = ciModel.checks.read(reader);
134
return getFailedChecks(checks).length > 0;
135
}));
136
}
137
}
138
139
class FixCIChecksAction extends Action2 {
140
141
static readonly ID = 'sessions.action.fixCIChecks';
142
143
constructor() {
144
super({
145
id: FixCIChecksAction.ID,
146
title: localize2('fixCIChecks', 'Fix CI Checks'),
147
icon: Codicon.lightbulbAutofix,
148
category: CHAT_CATEGORY,
149
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, hasActiveSessionFailedCIChecks),
150
menu: [{
151
id: MenuId.ChatEditingSessionApplySubmenu,
152
group: 'navigation',
153
order: 4,
154
when: ContextKeyExpr.and(IsSessionsWindowContext, hasActiveSessionFailedCIChecks),
155
}],
156
});
157
}
158
159
override async run(accessor: ServicesAccessor): Promise<void> {
160
const sessionManagementService = accessor.get(ISessionsManagementService);
161
const gitHubService = accessor.get(IGitHubService);
162
const chatWidgetService = accessor.get(IChatWidgetService);
163
const logService = accessor.get(ILogService);
164
165
const activeSession = sessionManagementService.activeSession.get();
166
if (!activeSession) {
167
return;
168
}
169
170
const gitHubInfo = activeSession.gitHubInfo.get();
171
if (!gitHubInfo?.pullRequest) {
172
return;
173
}
174
175
const prModel = gitHubService.getPullRequest(gitHubInfo.owner, gitHubInfo.repo, gitHubInfo.pullRequest.number);
176
const pr = prModel.pullRequest.get();
177
if (!pr) {
178
return;
179
}
180
181
const ciModel = gitHubService.getPullRequestCI(gitHubInfo.owner, gitHubInfo.repo, gitHubInfo.pullRequest.number, pr.headSha);
182
const checks = ciModel.checks.get();
183
const failedChecks = getFailedChecks(checks);
184
if (failedChecks.length === 0) {
185
return;
186
}
187
188
const failedCheckDetails = await Promise.all(failedChecks.map(async check => {
189
const annotations = await ciModel.getCheckRunAnnotations(check.id);
190
return { check, annotations };
191
}));
192
193
const prompt = buildFixChecksPrompt(failedCheckDetails);
194
const sessionResource = activeSession.resource;
195
const chatWidget = chatWidgetService.getWidgetBySessionResource(sessionResource)
196
?? await chatWidgetService.openSession(sessionResource, ChatViewPaneTarget);
197
if (!chatWidget) {
198
logService.error('[FixCIChecks] Cannot fix CI checks: no chat widget found for session', sessionResource.toString());
199
return;
200
}
201
202
await chatWidget.acceptInput(prompt, { noCommandDetection: true });
203
}
204
}
205
206
registerWorkbenchContribution2(ActiveSessionFailedCIChecksContextContribution.ID, ActiveSessionFailedCIChecksContextContribution, WorkbenchPhase.AfterRestored);
207
registerAction2(FixCIChecksAction);
208
209