Skip to content

Commit 2c442a1

Browse files
committed
Adds pr entity id to draft models
Fixes draft create/archive/view tracking Adds strong-typing to our tracking data Adds strong-typing to the flatten method
1 parent 6f2a0a6 commit 2c442a1

File tree

13 files changed

+583
-223
lines changed

13 files changed

+583
-223
lines changed

src/constants.ts

Lines changed: 340 additions & 41 deletions
Large diffs are not rendered by default.

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export async function activate(context: ExtensionContext): Promise<GitLensApi |
206206
await container.ready();
207207

208208
// TODO@eamodio do we want to capture any vscode settings that are relevant to GitLens?
209-
const flatCfg = flatten(configuration.getAll(true), { prefix: 'config', stringify: 'all' });
209+
const flatCfg = flatten(configuration.getAll(true), 'config', { joinArrays: true });
210210

211211
container.telemetry.setGlobalAttributes({
212212
debugging: container.debugging,

src/git/gitProviderService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,9 @@ export class GitProviderService implements Disposable {
923923
visibility = await this.visibilityCore();
924924
if (this.container.telemetry.enabled) {
925925
this.container.telemetry.setGlobalAttribute('repositories.visibility', visibility);
926-
this.container.telemetry.sendEvent('repositories/visibility');
926+
this.container.telemetry.sendEvent('repositories/visibility', {
927+
'repositories.visibility': visibility,
928+
});
927929
}
928930
this._reposVisibilityCache = visibility;
929931
}

src/gk/models/drafts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export interface Draft {
4444
readonly archivedReason?: DraftArchiveReason;
4545
readonly archivedAt?: Date;
4646

47+
readonly prEntityId?: string;
48+
4749
readonly latestChangesetId: string;
4850
changesets?: DraftChangeset[];
4951

@@ -78,6 +80,7 @@ export interface DraftPatch {
7880
readonly draftId: string;
7981
readonly changesetId: string;
8082
readonly userId: string;
83+
readonly prEntityId?: string;
8184

8285
readonly baseBranchName: string;
8386
/*readonly*/ baseRef: string;

src/plus/drafts/draftsService.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { EntityIdentifier } from '@gitkraken/provider-apis';
12
import { EntityIdentifierUtils } from '@gitkraken/provider-apis';
23
import type { Disposable } from 'vscode';
34
import type { HeadersInit } from '@env/fetch';
@@ -1039,3 +1040,15 @@ function formatPatch(
10391040
repository: options?.repository,
10401041
};
10411042
}
1043+
1044+
export function getDraftEntityIdentifier(draft: Draft, patch?: DraftPatch): EntityIdentifier | undefined {
1045+
if (draft.prEntityId != null) {
1046+
return EntityIdentifierUtils.decode(draft.prEntityId);
1047+
}
1048+
1049+
if (patch?.prEntityId != null) {
1050+
return EntityIdentifierUtils.decode(patch.prEntityId);
1051+
}
1052+
1053+
return undefined;
1054+
}

src/plus/focus/focus.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { AttributeValue } from '@opentelemetry/api';
21
import type { QuickInputButton } from 'vscode';
32
import { commands, Uri } from 'vscode';
43
import { getAvatarUri } from '../../avatars';
@@ -29,7 +28,7 @@ import {
2928
UnpinQuickInputButton,
3029
UnsnoozeQuickInputButton,
3130
} from '../../commands/quickCommand.buttons';
32-
import type { Source, Sources } from '../../constants';
31+
import type { LaunchpadTelemetryContext, Source, Sources } from '../../constants';
3332
import { Commands, previewBadge } from '../../constants';
3433
import type { Container } from '../../container';
3534
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
@@ -91,7 +90,7 @@ interface Context {
9190
items: FocusItem[];
9291
title: string;
9392
collapsed: Map<FocusGroup, boolean>;
94-
telemetryContext: Record<string, AttributeValue | null | undefined> | undefined;
93+
telemetryContext: LaunchpadTelemetryContext | undefined;
9594
}
9695

9796
interface GroupedFocusItem extends FocusItem {
@@ -126,7 +125,7 @@ const instanceCounter = getScopedCounter();
126125
@command()
127126
export class FocusCommand extends QuickCommand<State> {
128127
private readonly source: Source;
129-
private readonly telemetryContext: Record<string, any> | undefined;
128+
private readonly telemetryContext: LaunchpadTelemetryContext | undefined;
130129

131130
// TODO: Hidden is a hack for now to avoid telemetry when this gets loaded in the hidden group of the git commands
132131
constructor(container: Container, args?: FocusCommandArgs, hidden: boolean = false) {
@@ -145,7 +144,7 @@ export class FocusCommand extends QuickCommand<State> {
145144
if (this.container.telemetry.enabled && !hidden) {
146145
this.telemetryContext = {
147146
instance: instanceCounter.next(),
148-
'initialState.group': args?.state?.initialGroup ?? null,
147+
'initialState.group': args?.state?.initialGroup,
149148
'initialState.selectTopItem': args?.state?.selectTopItem ?? false,
150149
};
151150

@@ -205,7 +204,7 @@ export class FocusCommand extends QuickCommand<State> {
205204
this.container.telemetry.sendEvent(
206205
opened ? 'launchpad/steps/connect' : 'launchpad/opened',
207206
{
208-
...context.telemetryContext,
207+
...context.telemetryContext!,
209208
connected: false,
210209
},
211210
this.source,
@@ -236,7 +235,7 @@ export class FocusCommand extends QuickCommand<State> {
236235
this.container.telemetry.sendEvent(
237236
opened ? 'launchpad/steps/main' : 'launchpad/opened',
238237
{
239-
...context.telemetryContext,
238+
...context.telemetryContext!,
240239
connected: true,
241240
},
242241
this.source,
@@ -349,7 +348,7 @@ export class FocusCommand extends QuickCommand<State> {
349348
this.container.telemetry.sendEvent(
350349
'launchpad/groupToggled',
351350
{
352-
...context.telemetryContext,
351+
...context.telemetryContext!,
353352
group: ui,
354353
collapsed: collapsed,
355354
},
@@ -915,7 +914,7 @@ export class FocusCommand extends QuickCommand<State> {
915914
this.container.telemetry.sendEvent(
916915
action === 'select' ? 'launchpad/steps/details' : 'launchpad/action',
917916
{
918-
...context.telemetryContext,
917+
...context.telemetryContext!,
919918
action: action,
920919
'item.id': getFocusItemIdHash(item),
921920
'item.type': item.type,
@@ -927,8 +926,8 @@ export class FocusCommand extends QuickCommand<State> {
927926
'item.updatedDate': item.updatedDate.getTime(),
928927
'item.isNew': item.isNew,
929928

930-
'item.comments.count': item.commentCount,
931-
'item.upvotes.count': item.upvoteCount,
929+
'item.comments.count': item.commentCount ?? undefined,
930+
'item.upvotes.count': item.upvoteCount ?? undefined,
932931

933932
'item.pr.codeSuggestionCount': item.codeSuggestionsCount,
934933
'item.pr.isDraft': item.isDraft,
@@ -944,7 +943,7 @@ export class FocusCommand extends QuickCommand<State> {
944943
'item.pr.hasConflicts': item.hasConflicts,
945944

946945
'item.pr.reviews.count': item.reviews?.length ?? undefined,
947-
'item.pr.reviews.decision': item.reviewDecision,
946+
'item.pr.reviews.decision': item.reviewDecision ?? undefined,
948947
'item.pr.reviews.changeRequestCount': item.changeRequestReviewCount ?? undefined,
949948

950949
'item.viewer.isAuthor': item.viewer.isAuthor,
@@ -971,18 +970,22 @@ async function updateContextItems(container: Container, context: Context, option
971970
}
972971

973972
function updateTelemetryContext(context: Context) {
973+
if (context.telemetryContext == null) return;
974+
974975
const grouped = countFocusItemGroups(context.items);
975976

976-
context.telemetryContext = {
977+
const updatedContext: NonNullable<(typeof context)['telemetryContext']> = {
977978
...context.telemetryContext,
978979
'items.count': context.items.length,
979980
'groups.count': grouped.size,
980981
};
981982

982983
for (const [group, count] of grouped) {
983-
context.telemetryContext[`groups.${group}.count`] = count;
984-
context.telemetryContext[`groups.${group}.collapsed`] = context.collapsed.get(group);
984+
updatedContext[`groups.${group}.count`] = count;
985+
updatedContext[`groups.${group}.collapsed`] = context.collapsed.get(group);
985986
}
987+
988+
context.telemetryContext = updatedContext;
986989
}
987990

988991
function isFocusTargetActionQuickPickItem(item: any): item is QuickPickItemOfT<FocusTargetAction> {

src/plus/focus/focusIndicator.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ export class FocusIndicator implements Disposable {
6060
private async onConfigurationChanged(e: ConfigurationChangeEvent) {
6161
if (!configuration.changed(e, 'launchpad.indicator')) return;
6262

63+
if (
64+
configuration.changed(e, 'launchpad.indicator.enabled') &&
65+
!configuration.get('launchpad.indicator.enabled')
66+
) {
67+
this.container.telemetry.sendEvent('launchpad/indicator/hidden');
68+
}
69+
6370
if (configuration.changed(e, 'launchpad.indicator.openInEditor')) {
6471
this.updateStatusBarCommand();
6572
}

src/plus/focus/focusProvider.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -670,24 +670,21 @@ export class FocusProvider implements Disposable {
670670

671671
private onConfigurationChanged(e: ConfigurationChangeEvent) {
672672
if (!configuration.changed(e, 'launchpad')) return;
673-
const launchpadConfig = configuration.get('launchpad');
673+
674+
const cfg = configuration.get('launchpad');
674675
this.container.telemetry.sendEvent('launchpad/configurationChanged', {
675-
staleThreshold: launchpadConfig.staleThreshold,
676-
ignoredRepositories: launchpadConfig.ignoredRepositories,
677-
indicatorEnabled: launchpadConfig.indicator.enabled,
678-
indicatorOpenInEditor: launchpadConfig.indicator.openInEditor,
679-
indicatorIcon: launchpadConfig.indicator.icon,
680-
indicatorLabel: launchpadConfig.indicator.label,
681-
indicatorUseColors: launchpadConfig.indicator.useColors,
682-
indicatorGroups: launchpadConfig.indicator.groups,
683-
indicatorPollingEnabled: launchpadConfig.indicator.polling.enabled,
684-
indicatorPollingInterval: launchpadConfig.indicator.polling.interval,
676+
'config.launchpad.staleThreshold': cfg.staleThreshold,
677+
'config.launchpad.ignoredRepositories': cfg.ignoredRepositories?.length ?? 0,
678+
'config.launchpad.indicator.enabled': cfg.indicator.enabled,
679+
'config.launchpad.indicator.openInEditor': cfg.indicator.openInEditor,
680+
'config.launchpad.indicator.icon': cfg.indicator.icon,
681+
'config.launchpad.indicator.label': cfg.indicator.label,
682+
'config.launchpad.indicator.useColors': cfg.indicator.useColors,
683+
'config.launchpad.indicator.groups': cfg.indicator.groups.join(','),
684+
'config.launchpad.indicator.polling.enabled': cfg.indicator.polling.enabled,
685+
'config.launchpad.indicator.polling.interval': cfg.indicator.polling.interval,
685686
});
686687

687-
if (configuration.changed(e, 'launchpad.indicator.enabled') && !launchpadConfig.indicator.enabled) {
688-
this.container.telemetry.sendEvent('launchpad/indicator/hidden');
689-
}
690-
691688
if (
692689
configuration.changed(e, 'launchpad.ignoredRepositories') ||
693690
configuration.changed(e, 'launchpad.staleThreshold')

src/plus/gk/account/subscriptionService.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export class SubscriptionService implements Disposable {
215215
if (originalSource != null) {
216216
source.detail = {
217217
...(typeof source.detail === 'string' ? { action: source.detail } : source.detail),
218-
...flatten(originalSource, { prefix: 'original' }),
218+
...flatten(originalSource, 'original'),
219219
};
220220
}
221221

@@ -1335,28 +1335,43 @@ export class SubscriptionService implements Disposable {
13351335
}
13361336
}
13371337

1338-
function flattenSubscription(subscription: Optional<Subscription, 'state'> | undefined, prefix?: string) {
1338+
type FlattenedSubscription = {
1339+
'subscription.state'?: SubscriptionState;
1340+
'subscription.status'?:
1341+
| 'verification'
1342+
| 'free'
1343+
| 'preview'
1344+
| 'preview-expired'
1345+
| 'trial'
1346+
| 'trial-expired'
1347+
| 'trial-reactivation-eligible'
1348+
| 'paid'
1349+
| 'unknown';
1350+
} & Partial<
1351+
Record<`account.${string}`, string | number | boolean | undefined> &
1352+
Record<`subscription.${string}`, string | number | boolean | undefined> &
1353+
Record<`subscription.previewTrial.${string}`, string | number | boolean | undefined> &
1354+
Record<`previous.account.${string}`, string | number | boolean | undefined> &
1355+
Record<`previous.subscription.${string}`, string | number | boolean | undefined> &
1356+
Record<`previous.subscription.previewTrial.${string}`, string | number | boolean | undefined>
1357+
>;
1358+
1359+
function flattenSubscription(
1360+
subscription: Optional<Subscription, 'state'> | undefined,
1361+
prefix?: string,
1362+
): FlattenedSubscription {
13391363
if (subscription == null) return {};
13401364

13411365
return {
1342-
...flatten(subscription.account, {
1343-
arrays: 'join',
1344-
prefix: `${prefix ? `${prefix}.` : ''}account`,
1366+
...flatten(subscription.account, `${prefix ? `${prefix}.` : ''}account`, {
1367+
joinArrays: true,
13451368
skipPaths: ['name', 'email'],
1346-
skipNulls: true,
1347-
stringify: true,
13481369
}),
1349-
...flatten(subscription.plan, {
1350-
prefix: `${prefix ? `${prefix}.` : ''}subscription`,
1370+
...flatten(subscription.plan, `${prefix ? `${prefix}.` : ''}subscription`, {
13511371
skipPaths: ['actual.name', 'effective.name'],
1352-
skipNulls: true,
1353-
stringify: true,
13541372
}),
1355-
...flatten(subscription.previewTrial, {
1356-
prefix: `${prefix ? `${prefix}.` : ''}subscription.previewTrial`,
1373+
...flatten(subscription.previewTrial, `${prefix ? `${prefix}.` : ''}subscription.previewTrial`, {
13571374
skipPaths: ['actual.name', 'effective.name'],
1358-
skipNulls: true,
1359-
stringify: true,
13601375
}),
13611376
'subscription.state': subscription.state,
13621377
'subscription.stateString': getSubscriptionStateString(subscription.state),

src/plus/webviews/patchDetails/patchDetailsWebview.ts

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ConfigurationChangeEvent } from 'vscode';
22
import { Disposable, env, Uri, window } from 'vscode';
33
import { getAvatarUri } from '../../../avatars';
4-
import type { ContextKeys } from '../../../constants';
4+
import type { ContextKeys, Sources } from '../../../constants';
55
import { Commands, GlyphChars, previewBadge } from '../../../constants';
66
import type { Container } from '../../../container';
77
import { CancellationError } from '../../../errors';
@@ -46,6 +46,7 @@ import type { IpcCallMessageType, IpcMessage } from '../../../webviews/protocol'
4646
import type { WebviewHost, WebviewProvider } from '../../../webviews/webviewProvider';
4747
import type { WebviewShowOptions } from '../../../webviews/webviewsController';
4848
import { showPatchesView } from '../../drafts/actions';
49+
import { getDraftEntityIdentifier } from '../../drafts/draftsService';
4950
import type { OrganizationMember } from '../../gk/account/organization';
5051
import { confirmDraftStorage, ensureAccount } from '../../utils';
5152
import type { ShowInCommitGraphCommandArgs } from '../graph/protocol';
@@ -769,19 +770,25 @@ export class PatchDetailsWebviewProvider
769770
private async trackArchiveDraft(draft: Draft) {
770771
draft = await this.ensureDraftContent(draft);
771772
const patch = draft.changesets?.[0].patches.find(p => isRepoLocated(p.repository));
772-
if (patch == null) return;
773773

774-
const repo = patch.repository as Repository;
775-
const remote = await this.container.git.getBestRemoteWithProvider(repo.uri);
776-
if (remote == null) return;
774+
let repoPrivacy;
775+
if (isRepository(patch?.repository)) {
776+
repoPrivacy = await this.container.git.visibility(patch.repository.uri);
777+
}
777778

778-
const provider = remote.provider.id;
779+
const entity = getDraftEntityIdentifier(draft, patch);
779780

780-
this.container.telemetry.sendEvent('codeSuggestionArchived', {
781-
provider: provider,
782-
draftId: draft.id,
783-
reason: draft.archivedReason!,
784-
});
781+
this.container.telemetry.sendEvent(
782+
'codeSuggestionArchived',
783+
{
784+
provider: entity?.provider,
785+
'repository.visibility': repoPrivacy,
786+
repoPrivacy: repoPrivacy,
787+
draftId: draft.id,
788+
reason: draft.archivedReason!,
789+
},
790+
{ source: 'patchDetails' },
791+
);
785792
}
786793

787794
private async explainRequest<T extends typeof ExplainRequest>(requestType: T, msg: IpcCallMessageType<T>) {
@@ -1077,27 +1084,31 @@ export class PatchDetailsWebviewProvider
10771084
});
10781085
}
10791086

1080-
private async trackViewDraft(draft: Draft | LocalDraft | undefined, source?: string) {
1087+
private async trackViewDraft(draft: Draft | LocalDraft | undefined, source?: Sources | undefined) {
10811088
if (draft?.draftType !== 'cloud' || draft.type !== 'suggested_pr_change') return;
10821089

10831090
draft = await this.ensureDraftContent(draft);
10841091
const patch = draft.changesets?.[0].patches.find(p => isRepoLocated(p.repository));
1085-
if (patch == null) return;
10861092

1087-
const repo = patch.repository as Repository;
1088-
const remote = await this.container.git.getBestRemoteWithProvider(repo.uri);
1089-
if (remote == null) return;
1093+
let repoPrivacy;
1094+
if (isRepository(patch?.repository)) {
1095+
repoPrivacy = await this.container.git.visibility(patch.repository.uri);
1096+
}
10901097

1091-
const provider = remote.provider.id;
1092-
const repoPrivacy = await this.container.git.visibility(repo.path);
1098+
const entity = getDraftEntityIdentifier(draft, patch);
10931099

1094-
this.container.telemetry.sendEvent('codeSuggestionViewed', {
1095-
provider: provider,
1096-
source: source,
1097-
repoPrivacy: repoPrivacy,
1098-
draftPrivacy: draft.visibility,
1099-
draftId: draft.id,
1100-
});
1100+
this.container.telemetry.sendEvent(
1101+
'codeSuggestionViewed',
1102+
{
1103+
provider: entity?.provider,
1104+
'repository.visibility': repoPrivacy,
1105+
repoPrivacy: repoPrivacy,
1106+
draftId: draft.id,
1107+
draftPrivacy: draft.visibility,
1108+
source: source,
1109+
},
1110+
{ source: source ?? 'patchDetails' },
1111+
);
11011112
}
11021113

11031114
private async updateViewDraftState(draft: LocalDraft | Draft | undefined) {

0 commit comments

Comments
 (0)