Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/telemetry-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,19 @@ void
}
```

### startWork/issue/action

> Sent when the user takes an action on a StartWork issue

```typescript
{
'instance': number,
'type': 'branch' | 'branch-worktree' | 'issue' | 'issue-worktree',
'items.count': number,
'action': 'soft-open'
}
```

### startWork/issue/chosen

> Sent when the user chooses an issue to start work in the second step
Expand Down
5 changes: 5 additions & 0 deletions src/commands/quickCommand.buttons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ export const MergeQuickInputButton: QuickInputButton = {
tooltip: 'Merge...',
};

export const OpenOnJiraQuickInputButton: QuickInputButton = {
iconPath: new ThemeIcon('globe'),
tooltip: 'Open on Jira',
};

export const OpenOnGitHubQuickInputButton: QuickInputButton = {
iconPath: new ThemeIcon('globe'),
tooltip: 'Open on GitHub',
Expand Down
6 changes: 6 additions & 0 deletions src/constants.telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ export type TelemetryEvents = {
connected: boolean;
type: StartWorkType;
};
/** Sent when the user takes an action on a StartWork issue */
'startWork/issue/action': StartWorkEventData & {
action: 'soft-open';
connected: boolean;
type: StartWorkType;
} & Partial<Record<`item.${string}`, string | number | boolean>>;
/** Sent when the user chooses an issue to start work in the second step */
'startWork/issue/chosen': StartWorkEventData & {
connected: boolean;
Expand Down
92 changes: 73 additions & 19 deletions src/plus/startWork/startWork.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { md5 } from '@env/crypto';
import slug from 'slug';
import type { QuickPick } from 'vscode';
import type { QuickInputButton, QuickPick } from 'vscode';
import { Uri } from 'vscode';
import type {
AsyncStepResultGenerator,
Expand All @@ -18,7 +18,7 @@ import {
QuickCommand,
StepResultBreak,
} from '../../commands/quickCommand';
import { OpenOnGitHubQuickInputButton } from '../../commands/quickCommand.buttons';
import { OpenOnGitHubQuickInputButton, OpenOnJiraQuickInputButton } from '../../commands/quickCommand.buttons';
import { getSteps } from '../../commands/quickWizard.utils';
import { proBadge } from '../../constants';
import type { IntegrationId } from '../../constants.integrations';
Expand Down Expand Up @@ -56,6 +56,9 @@ interface State {
type?: StartWorkType;
inWorktree?: boolean;
}
interface StateWithType extends State {
type: StartWorkType;
}

export type StartWorkType = 'branch' | 'branch-worktree' | 'issue' | 'issue-worktree';
type StartWorkTypeItem = { type: StartWorkType; inWorktree?: boolean };
Expand Down Expand Up @@ -166,6 +169,7 @@ export class StartWorkCommand extends QuickCommand<State> {
}
}

assertsTypeStepState(state);
const result = yield* this.pickIssueStep(state, context, opened);
opened = true;
if (result === StepResultBreak) continue;
Expand All @@ -176,19 +180,9 @@ export class StartWorkCommand extends QuickCommand<State> {
'startWork/issue/chosen',
{
...context.telemetryContext!,
...buildItemTelemetryData(result),
connected: true,
type: state.type,
'item.id': getStartWorkItemIdHash(result),
'item.type': result.item.issue.type,
'item.provider': result.item.issue.provider.id,
'item.assignees.count': result.item.issue.assignees?.length ?? undefined,
'item.createdDate': result.item.issue.createdDate.getTime(),
'item.updatedDate': result.item.issue.updatedDate.getTime(),

'item.comments.count': result.item.issue.commentsCount ?? undefined,
'item.upvotes.count': result.item.issue.thumbsUpCount ?? undefined,

'item.issue.state': result.item.issue.state,
},
this.source,
);
Expand Down Expand Up @@ -409,12 +403,13 @@ export class StartWorkCommand extends QuickCommand<State> {
}

private *pickIssueStep(
state: StepState<State>,
state: StepState<StateWithType>,
context: Context,
opened: boolean,
): StepResultGenerator<StartWorkItem | StartWorkTypeItem> {
const buildIssueItem = (i: StartWorkItem) => {
const buttons = i.item.issue.url ? [OpenOnGitHubQuickInputButton] : [];
const onWebbButton = i.item.issue.url ? getOpenOnWebQuickInputButton(i.item.issue.provider.id) : undefined;
const buttons = onWebbButton ? [onWebbButton] : [];
const hoverContent = i.item.issue.body ? `${repeatSpaces(200)}\n\n${i.item.issue.body}` : '';
return {
label:
Expand Down Expand Up @@ -499,11 +494,19 @@ export class StartWorkCommand extends QuickCommand<State> {
items: [],
onDidActivate: updateItems,
onDidClickItemButton: (_quickpick, button, { item }) => {
if (button === OpenOnGitHubQuickInputButton && !isStartWorkTypeItem(item)) {
this.open(item);
return undefined;
if (isStartWorkTypeItem(item)) {
return false;
}

switch (button) {
case OpenOnGitHubQuickInputButton:
case OpenOnJiraQuickInputButton:
this.sendItemActionTelemetry('soft-open', item, state, context);
this.open(item);
return undefined;
default:
return false;
}
return false;
},
onDidChangeValue: () => true,
});
Expand All @@ -521,6 +524,21 @@ export class StartWorkCommand extends QuickCommand<State> {
void openUrl(item.item.issue.url);
}

private sendItemActionTelemetry(
action: 'soft-open',
item: StartWorkItem,
state: StepState<StateWithType>,
context: Context,
) {
this.container.telemetry.sendEvent('startWork/issue/action', {
...context.telemetryContext!,
...buildItemTelemetryData(item),
action: action,
connected: true,
type: state.type,
});
}

private async getConnectedIntegrations(): Promise<Map<SupportedStartWorkIntegrationIds, boolean>> {
const connected = new Map<SupportedStartWorkIntegrationIds, boolean>();
await Promise.allSettled(
Expand Down Expand Up @@ -576,3 +594,39 @@ function repeatSpaces(count: number) {
export function getStartWorkItemIdHash(item: StartWorkItem) {
return md5(item.item.issue.id);
}

function buildItemTelemetryData(item: StartWorkItem) {
return {
'item.id': getStartWorkItemIdHash(item),
'item.type': item.item.issue.type,
'item.provider': item.item.issue.provider.id,
'item.assignees.count': item.item.issue.assignees?.length ?? undefined,
'item.createdDate': item.item.issue.createdDate.getTime(),
'item.updatedDate': item.item.issue.updatedDate.getTime(),

'item.comments.count': item.item.issue.commentsCount ?? undefined,
'item.upvotes.count': item.item.issue.thumbsUpCount ?? undefined,

'item.issue.state': item.item.issue.state,
};
}

function getOpenOnWebQuickInputButton(integrationId: string): QuickInputButton | undefined {
switch (integrationId) {
case HostingIntegrationId.GitHub:
return OpenOnGitHubQuickInputButton;
case IssueIntegrationId.Jira:
return OpenOnJiraQuickInputButton;
default:
return undefined;
}
}

function assertsTypeStepState(state: StepState<State>): asserts state is StepState<StateWithType> {
if (state.type != null) {
return;
}

debugger;
throw new Error('Missing `item` field in state of StartWork');
}
Loading