Skip to content

Commit c31d910

Browse files
sergeibbbchivorotkiv
authored andcommitted
Connects integrations if they are not connected
(#3621, #3698)
1 parent 92e485d commit c31d910

File tree

3 files changed

+91
-2
lines changed

3 files changed

+91
-2
lines changed

docs/telemetry-events.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1320,7 +1320,7 @@ void
13201320
'repository.visibility': 'private' | 'public' | 'local',
13211321
'repoPrivacy': 'private' | 'public' | 'local',
13221322
'filesChanged': number,
1323-
'source': 'graph' | 'patchDetails' | 'settings' | 'timeline' | 'home' | 'code-suggest' | 'account' | 'cloud-patches' | 'commandPalette' | 'deeplink' | 'inspect' | 'inspect-overview' | 'integrations' | 'launchpad' | 'launchpad-indicator' | 'launchpad-view' | 'notification' | 'prompt' | 'quick-wizard' | 'remoteProvider' | 'trial-indicator' | 'scm-input' | 'subscription' | 'walkthrough' | 'worktrees'
1323+
'source': 'graph' | 'patchDetails' | 'settings' | 'timeline' | 'home' | 'code-suggest' | 'account' | 'cloud-patches' | 'commandPalette' | 'deeplink' | 'inspect' | 'inspect-overview' | 'integrations' | 'launchpad' | 'launchpad-indicator' | 'launchpad-view' | 'notification' | 'prompt' | 'quick-wizard' | 'remoteProvider' | 'startWork' | 'trial-indicator' | 'scm-input' | 'subscription' | 'walkthrough' | 'worktrees'
13241324
}
13251325
```
13261326

src/constants.telemetry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ export type Sources =
610610
| 'quick-wizard'
611611
| 'remoteProvider'
612612
| 'settings'
613+
| 'startWork'
613614
| 'timeline'
614615
| 'trial-indicator'
615616
| 'scm-input'

src/plus/startWork/startWork.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type { QuickInputButton } from 'vscode';
1+
import type { QuickInputButton, QuickPick } from 'vscode';
22
import { ThemeIcon, Uri } from 'vscode';
33
import type {
4+
AsyncStepResultGenerator,
45
PartialStepState,
56
StepGenerator,
67
StepResultGenerator,
@@ -11,16 +12,20 @@ import {
1112
canPickStepContinue,
1213
createPickStep,
1314
endSteps,
15+
freezeStep,
1416
QuickCommand,
1517
StepResultBreak,
1618
} from '../../commands/quickCommand';
1719
import { proBadge } from '../../constants';
20+
import type { IntegrationId } from '../../constants.integrations';
1821
import { HostingIntegrationId } from '../../constants.integrations';
1922
import type { Container } from '../../container';
2023
import type { SearchedIssue } from '../../git/models/issue';
2124
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
25+
import { createQuickPickItemOfT } from '../../quickpicks/items/common';
2226
import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive';
2327
import { fromNow } from '../../system/date';
28+
import { some } from '../../system/iterable';
2429

2530
export type StartWorkItem = {
2631
item: SearchedIssue;
@@ -36,6 +41,7 @@ export type StartWorkResult = { items: StartWorkItem[] };
3641
interface Context {
3742
result: StartWorkResult;
3843
title: string;
44+
connectedIntegrations: Map<IntegrationId, boolean>;
3945
}
4046

4147
interface State {
@@ -58,6 +64,8 @@ function assertsStartWorkStepState(state: StepState<State>): asserts state is St
5864
throw new Error('Missing item');
5965
}
6066

67+
export const supportedStartWorkIntegrations = [HostingIntegrationId.GitHub];
68+
6169
export class StartWorkCommand extends QuickCommand<State> {
6270
constructor(container: Container) {
6371
super(container, 'startWork', 'startWork', `Start Work\u00a0\u00a0${proBadge}`, {
@@ -77,11 +85,20 @@ export class StartWorkCommand extends QuickCommand<State> {
7785
const context: Context = {
7886
result: { items: [] },
7987
title: this.title,
88+
connectedIntegrations: await this.getConnectedIntegrations(),
8089
};
8190

8291
while (this.canStepsContinue(state)) {
8392
context.title = this.title;
8493

94+
const hasConnectedIntegrations = [...context.connectedIntegrations.values()].some(c => c);
95+
if (!hasConnectedIntegrations) {
96+
const result = yield* this.confirmCloudIntegrationsConnectStep(state, context);
97+
if (result === StepResultBreak) {
98+
return result;
99+
}
100+
}
101+
85102
await updateContextItems(this.container, context);
86103

87104
if (state.counter < 1 || state.item == null) {
@@ -107,6 +124,65 @@ export class StartWorkCommand extends QuickCommand<State> {
107124
return state.counter < 0 ? StepResultBreak : undefined;
108125
}
109126

127+
private async *confirmCloudIntegrationsConnectStep(
128+
state: StepState<State>,
129+
context: Context,
130+
): AsyncStepResultGenerator<{ connected: boolean | IntegrationId; resume: () => void }> {
131+
// TODO: This step is almost an exact copy of the similar one from launchpad.ts. Do we want to do anything about it? Maybe to move it to an util function with ability to parameterize labels?
132+
const hasConnectedIntegration = some(context.connectedIntegrations.values(), c => c);
133+
const step = this.createConfirmStep(
134+
`${this.title} \u00a0\u2022\u00a0 Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration`,
135+
[
136+
createQuickPickItemOfT(
137+
{
138+
label: `Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration...`,
139+
detail: hasConnectedIntegration
140+
? 'Connect additional integrations to view their issues in Start Work'
141+
: 'Connect an integration to accelerate your work',
142+
picked: true,
143+
},
144+
true,
145+
),
146+
],
147+
createDirectiveQuickPickItem(Directive.Cancel, false, { label: 'Cancel' }),
148+
{
149+
placeholder: hasConnectedIntegration
150+
? 'Connect additional integrations to Start Work'
151+
: 'Connect an integration to get started with Start Work',
152+
buttons: [],
153+
ignoreFocusOut: true,
154+
},
155+
);
156+
157+
// Note: This is a hack to allow the quickpick to stay alive after the user finishes connecting the integration.
158+
// Otherwise it disappears.
159+
let freeze!: () => Disposable;
160+
let quickpick!: QuickPick<any>;
161+
step.onDidActivate = qp => {
162+
quickpick = qp;
163+
freeze = () => freezeStep(step, qp);
164+
};
165+
166+
const selection: StepSelection<typeof step> = yield step;
167+
168+
if (canPickStepContinue(step, state, selection)) {
169+
const previousPlaceholder = quickpick.placeholder;
170+
quickpick.placeholder = 'Connecting integrations...';
171+
quickpick.ignoreFocusOut = true;
172+
const resume = freeze();
173+
const connected = await this.container.integrations.connectCloudIntegrations(
174+
{ integrationIds: supportedStartWorkIntegrations },
175+
{
176+
source: 'startWork',
177+
},
178+
);
179+
quickpick.placeholder = previousPlaceholder;
180+
return { connected: connected, resume: () => resume[Symbol.dispose]() };
181+
}
182+
183+
return StepResultBreak;
184+
}
185+
110186
private *pickIssueStep(state: StepState<State>, context: Context): StepResultGenerator<StartWorkItem> {
111187
const buildIssueItem = (i: StartWorkItem) => {
112188
const buttons = [StartWorkQuickInputButton];
@@ -171,6 +247,18 @@ export class StartWorkCommand extends QuickCommand<State> {
171247
const element = selection[0];
172248
return { ...element.item };
173249
}
250+
251+
private async getConnectedIntegrations(): Promise<Map<IntegrationId, boolean>> {
252+
const connected = new Map<IntegrationId, boolean>();
253+
await Promise.allSettled(
254+
supportedStartWorkIntegrations.map(async integrationId => {
255+
const integration = await this.container.integrations.get(integrationId);
256+
connected.set(integrationId, integration.maybeConnected ?? (await integration.isConnected()));
257+
}),
258+
);
259+
260+
return connected;
261+
}
174262
}
175263

176264
async function updateContextItems(container: Container, context: Context) {

0 commit comments

Comments
 (0)