Skip to content

Commit cb9b565

Browse files
committed
Adds async loaded product config
Refactors promo handling via product config Moves all required content into promo model Adds percentile support to promos
1 parent d1389c5 commit cb9b565

40 files changed

+621
-218
lines changed

contributions.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12658,7 +12658,11 @@
1265812658
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4"
1265912659
},
1266012660
{
12661-
"contents": "Save 55% or more on your 1st seat of Pro.",
12661+
"contents": "Limited-time sale on GitLens Pro.",
12662+
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
12663+
},
12664+
{
12665+
"contents": "Save 33% or more on GitLens Pro.",
1266212666
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1266312667
},
1266412668
{
@@ -12761,7 +12765,11 @@
1276112765
"when": "gitlens:views:scm:grouped:view == launchpad && !gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4"
1276212766
},
1276312767
{
12764-
"contents": "Save 55% or more on your 1st seat of Pro.",
12768+
"contents": "Limited-time sale on GitLens Pro.",
12769+
"when": "gitlens:views:scm:grouped:view == launchpad && !gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
12770+
},
12771+
{
12772+
"contents": "Save 33% or more on GitLens Pro.",
1276512773
"when": "gitlens:views:scm:grouped:view == launchpad && !gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1276612774
},
1276712775
{
@@ -12809,7 +12817,11 @@
1280912817
"when": "gitlens:views:scm:grouped:view == worktrees && gitlens:plus:required && gitlens:plus:state == 4"
1281012818
},
1281112819
{
12812-
"contents": "Save 55% or more on your 1st seat of Pro.",
12820+
"contents": "Limited-time sale on GitLens Pro.",
12821+
"when": "gitlens:views:scm:grouped:view == worktrees && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
12822+
},
12823+
{
12824+
"contents": "Save 33% or more on GitLens Pro.",
1281312825
"when": "gitlens:views:scm:grouped:view == worktrees && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1281412826
},
1281512827
{
@@ -12939,7 +12951,11 @@
1293912951
"when": "gitlens:plus:required && gitlens:plus:state == 4"
1294012952
},
1294112953
{
12942-
"contents": "Save 55% or more on your 1st seat of Pro.",
12954+
"contents": "Limited-time sale on GitLens Pro.",
12955+
"when": "gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
12956+
},
12957+
{
12958+
"contents": "Save 33% or more on GitLens Pro.",
1294312959
"when": "gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1294412960
},
1294512961
{

docs/telemetry-events.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@
6767
'global.subscription.featurePreviews.graph.status': 'eligible' | 'active' | 'expired',
6868
'global.subscription.previewTrial.expiresOn': string,
6969
'global.subscription.previewTrial.startedOn': string,
70+
'global.subscription.promo.code': string,
71+
'global.subscription.promo.key': string,
7072
'global.subscription.state': -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6,
7173
'global.subscription.stateString': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown',
72-
'global.subscription.status': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown',
7374
'global.upgrade': boolean,
7475
'global.upgradedFrom': string,
7576
'global.workspace.isTrusted': boolean
@@ -87,7 +88,7 @@
8788
'account.id': string,
8889
'code': string,
8990
'exception': string,
90-
'statusCode': string
91+
'statusCode': number
9192
}
9293
```
9394

@@ -1457,6 +1458,19 @@ void
14571458
}
14581459
```
14591460

1461+
### productConfig/failed
1462+
1463+
> Sent when fetching the product config fails
1464+
1465+
```typescript
1466+
{
1467+
'exception': string,
1468+
'json': string,
1469+
'reason': 'fetch' | 'validation',
1470+
'statusCode': number
1471+
}
1472+
```
1473+
14601474
### providers/context
14611475

14621476
> Sent when the "context" of the workspace changes (e.g. repo added, integration connected, etc)
@@ -1719,9 +1733,10 @@ void
17191733
'subscription.featurePreviews.graph.status': 'eligible' | 'active' | 'expired',
17201734
'subscription.previewTrial.expiresOn': string,
17211735
'subscription.previewTrial.startedOn': string,
1736+
'subscription.promo.code': string,
1737+
'subscription.promo.key': string,
17221738
'subscription.state': -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6,
1723-
'subscription.stateString': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown',
1724-
'subscription.status': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown'
1739+
'subscription.stateString': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown'
17251740
}
17261741
```
17271742

@@ -1740,7 +1755,9 @@ or
17401755
```typescript
17411756
{
17421757
'aborted': boolean,
1743-
'action': 'upgrade'
1758+
'action': 'upgrade',
1759+
'promo.code': string,
1760+
'promo.key': string
17441761
}
17451762
```
17461763

@@ -1818,9 +1835,10 @@ or
18181835
'subscription.featurePreviews.graph.status': 'eligible' | 'active' | 'expired',
18191836
'subscription.previewTrial.expiresOn': string,
18201837
'subscription.previewTrial.startedOn': string,
1838+
'subscription.promo.code': string,
1839+
'subscription.promo.key': string,
18211840
'subscription.state': -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6,
1822-
'subscription.stateString': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown',
1823-
'subscription.status': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown'
1841+
'subscription.stateString': 'verification' | 'free' | 'preview' | 'preview-expired' | 'trial' | 'trial-expired' | 'trial-reactivation-eligible' | 'paid' | 'unknown'
18241842
}
18251843
```
18261844

package.json

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19526,7 +19526,12 @@
1952619526
},
1952719527
{
1952819528
"view": "gitlens.views.launchpad",
19529-
"contents": "Save 55% or more on your 1st seat of Pro.",
19529+
"contents": "Limited-time sale on GitLens Pro.",
19530+
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
19531+
},
19532+
{
19533+
"view": "gitlens.views.launchpad",
19534+
"contents": "Save 33% or more on GitLens Pro.",
1953019535
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1953119536
},
1953219537
{
@@ -19581,7 +19586,12 @@
1958119586
},
1958219587
{
1958319588
"view": "gitlens.views.scm.grouped",
19584-
"contents": "Save 55% or more on your 1st seat of Pro.",
19589+
"contents": "Limited-time sale on GitLens Pro.",
19590+
"when": "gitlens:views:scm:grouped:view == launchpad && !gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
19591+
},
19592+
{
19593+
"view": "gitlens.views.scm.grouped",
19594+
"contents": "Save 33% or more on GitLens Pro.",
1958519595
"when": "gitlens:views:scm:grouped:view == launchpad && !gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1958619596
},
1958719597
{
@@ -19641,7 +19651,12 @@
1964119651
},
1964219652
{
1964319653
"view": "gitlens.views.scm.grouped",
19644-
"contents": "Save 55% or more on your 1st seat of Pro.",
19654+
"contents": "Limited-time sale on GitLens Pro.",
19655+
"when": "gitlens:views:scm:grouped:view == worktrees && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
19656+
},
19657+
{
19658+
"view": "gitlens.views.scm.grouped",
19659+
"contents": "Save 33% or more on GitLens Pro.",
1964519660
"when": "gitlens:views:scm:grouped:view == worktrees && gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1964619661
},
1964719662
{
@@ -19724,7 +19739,12 @@
1972419739
},
1972519740
{
1972619741
"view": "gitlens.views.worktrees",
19727-
"contents": "Save 55% or more on your 1st seat of Pro.",
19742+
"contents": "Limited-time sale on GitLens Pro.",
19743+
"when": "gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo && gitlens:promo != pro50"
19744+
},
19745+
{
19746+
"view": "gitlens.views.worktrees",
19747+
"contents": "Save 33% or more on GitLens Pro.",
1972819748
"when": "gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == pro50"
1972919749
},
1973019750
{

src/commands/git/worktree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ export class WorktreeGitCommand extends QuickCommand<State> {
309309
}
310310
assertStateStepRepository(state);
311311

312-
const result = yield* ensureAccessStep(state, context, PlusFeatures.Worktrees);
312+
const result = yield* ensureAccessStep(this.container, state, context, PlusFeatures.Worktrees);
313313
if (result === StepResultBreak) continue;
314314

315315
switch (state.subcommand) {

src/commands/quickCommand.steps.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import {
4545
} from '../git/utils/reference.utils';
4646
import { getHighlanderProviderName } from '../git/utils/remote.utils';
4747
import { createRevisionRange, isRevisionRange } from '../git/utils/revision.utils';
48-
import { getApplicablePromo } from '../plus/gk/utils/promo.utils';
4948
import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../plus/gk/utils/subscription.utils';
5049
import type { LaunchpadCommandArgs } from '../plus/launchpad/launchpad';
5150
import {
@@ -2635,8 +2634,13 @@ function getShowRepositoryStatusStepItems<
26352634
export async function* ensureAccessStep<
26362635
State extends PartialStepState & { repo?: Repository },
26372636
Context extends { title: string },
2638-
>(state: State, context: Context, feature: PlusFeatures): AsyncStepResultGenerator<FeatureAccess | RepoFeatureAccess> {
2639-
const access = await Container.instance.git.access(feature, state.repo?.path);
2637+
>(
2638+
container: Container,
2639+
state: State,
2640+
context: Context,
2641+
feature: PlusFeatures,
2642+
): AsyncStepResultGenerator<FeatureAccess | RepoFeatureAccess> {
2643+
const access = await container.git.access(feature, state.repo?.path);
26402644
if (access.allowed) return access;
26412645

26422646
const directives: DirectiveQuickPickItem[] = [];
@@ -2651,8 +2655,8 @@ export async function* ensureAccessStep<
26512655
} else {
26522656
if (access.subscription.required == null) return access;
26532657

2654-
const promo = getApplicablePromo(access.subscription.current.state, 'gate');
2655-
const detail = promo?.quickpick.detail;
2658+
const promo = await container.productConfig.getApplicablePromo(access.subscription.current.state, 'gate');
2659+
const detail = promo?.content?.quickpick.detail;
26562660

26572661
placeholder = 'Pro feature — requires a trial or GitLens Pro for use on privately-hosted repos';
26582662
if (isSubscriptionPaidPlan(access.subscription.required) && access.subscription.current.account != null) {

src/constants.context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { Uri } from 'vscode';
22
import type { AnnotationStatus, Keys } from './constants';
3-
import type { PromoKeys } from './constants.promos';
43
import type { SubscriptionPlanId, SubscriptionState } from './constants.subscription';
54
import type { CustomEditorTypes, GroupableTreeViewTypes, WebviewTypes, WebviewViewTypes } from './constants.views';
5+
import type { PromoKeys } from './plus/gk/models/promo';
66
import type { WalkthroughContextKeys } from './telemetry/walkthroughStateProvider';
77

88
export type ContextKeys = {

src/constants.promos.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/constants.storage.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { GraphBranchesVisibility, ViewShowBranchComparison } from './config';
22
import type { AIProviders } from './constants.ai';
33
import type { IntegrationId } from './constants.integrations';
4+
import type { SubscriptionState } from './constants.subscription';
45
import type { TrackedUsage, TrackedUsageKeys } from './constants.telemetry';
56
import type { GroupableTreeViewTypes } from './constants.views';
67
import type { Environment } from './container';
@@ -69,6 +70,7 @@ export type GlobalStorage = {
6970
version: string;
7071
// Keep the pre-release version separate from the released version
7172
preVersion: string;
73+
'product:config': Stored<StoredProductConfig>;
7274
'confirm:draft:storage': boolean;
7375
'home:sections:collapsed': string[];
7476
'home:walkthrough:dismissed': boolean;
@@ -103,6 +105,20 @@ export interface StoredConfiguredIntegrationDescriptor {
103105
scopes: string;
104106
}
105107

108+
export interface StoredProductConfig {
109+
promos: StoredPromo[];
110+
}
111+
112+
export interface StoredPromo {
113+
key: string;
114+
code?: string;
115+
locations?: ('account' | 'badge' | 'gate' | 'home')[];
116+
states?: SubscriptionState[];
117+
expiresOn?: number;
118+
startsOn?: number;
119+
percentile?: number;
120+
}
121+
106122
export type DeprecatedWorkspaceStorage = {
107123
/** @deprecated use `confirm:ai:tos:${AIProviders}` */
108124
'confirm:sendToOpenAI': boolean;

src/constants.telemetry.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ export interface TelemetryEvents extends WebviewShowAbortedEvents, WebviewShownE
182182
/** Sent when a PR review was started in the inspect overview */
183183
openReviewMode: OpenReviewModeEvent;
184184

185+
/** Sent when fetching the product config fails */
186+
'productConfig/failed': ProductConfigFailedEvent;
187+
185188
/** Sent when the "context" of the workspace changes (e.g. repo added, integration connected, etc) */
186189
'providers/context': void;
187190

@@ -288,7 +291,7 @@ interface AccountValidationFailedEvent {
288291
'account.id': string;
289292
exception: string;
290293
code: string | undefined;
291-
statusCode: string | undefined;
294+
statusCode: number | undefined;
292295
}
293296

294297
interface ActivateEvent extends ConfigEventData {
@@ -658,6 +661,13 @@ interface OpenReviewModeEvent {
658661
source: Sources;
659662
}
660663

664+
interface ProductConfigFailedEvent {
665+
reason: 'fetch' | 'validation';
666+
json: string | undefined;
667+
exception?: string;
668+
statusCode?: number | undefined;
669+
}
670+
661671
interface ProvidersRegistrationCompleteEvent {
662672
'config.git.autoRepositoryDetection': boolean | 'subFolders' | 'openEditors' | undefined;
663673
}
@@ -769,9 +779,10 @@ export interface SubscriptionPreviousEventData
769779
Partial<Flatten<NonNullable<Subscription['previewTrial']>, 'previous.subscription.previewTrial', true>> {}
770780

771781
export interface SubscriptionEventData extends Partial<SubscriptionCurrentEventData> {
782+
'subscription.promo.key'?: string;
783+
'subscription.promo.code'?: string;
772784
'subscription.state'?: SubscriptionState;
773785
'subscription.stateString'?: SubscriptionStateString;
774-
'subscription.status'?: SubscriptionStateString;
775786
}
776787

777788
type SubscriptionActionEventData =
@@ -789,6 +800,8 @@ type SubscriptionActionEventData =
789800
| {
790801
action: 'upgrade';
791802
aborted: boolean;
803+
'promo.key'?: string;
804+
'promo.code'?: string;
792805
}
793806
| {
794807
action: 'visibility';

src/container.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { LineHoverController } from './hovers/lineHoverController';
2828
import { DraftService } from './plus/drafts/draftsService';
2929
import { AccountAuthenticationProvider } from './plus/gk/authenticationProvider';
3030
import { OrganizationService } from './plus/gk/organizationService';
31+
import { ProductConfigProvider } from './plus/gk/productConfigProvider';
3132
import { ServerConnection } from './plus/gk/serverConnection';
3233
import { SubscriptionService } from './plus/gk/subscriptionService';
3334
import { GraphStatusBarController } from './plus/graph/statusbar';
@@ -609,6 +610,12 @@ export class Container {
609610
return this._prerelease || this.debugging;
610611
}
611612

613+
private _productConfig: ProductConfigProvider | undefined;
614+
get productConfig(): ProductConfigProvider {
615+
this._productConfig ??= new ProductConfigProvider(this, this._connection);
616+
return this._productConfig;
617+
}
618+
612619
private readonly _rebaseEditor: RebaseEditorProvider;
613620
get rebaseEditor(): RebaseEditorProvider {
614621
return this._rebaseEditor;

0 commit comments

Comments
 (0)