Skip to content

Commit f563e21

Browse files
Adds trial reactivation UX and command (#3147)
* Adds trial reactivation UX and command * Updates wording on all trial states and references * Fixes button text in Worktrees view
1 parent dad7c0f commit f563e21

File tree

19 files changed

+235
-71
lines changed

19 files changed

+235
-71
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ See the [FAQ](#is-gitlens-free-to-use 'Jump to FAQ') for more details.
2929

3030
[Features](#discover-powerful-features 'Jump to Discover Powerful Features')
3131
| [Labs](#gitkraken-labs 'Jump to GitKraken Labs')
32-
| [Pro](#ready-for-gitlens-pro 'Jump to Ready for GitKraken Pro?')
32+
| [Pro](#ready-for-gitlens-pro 'Jump to Ready for GitLens Pro?')
3333
| [FAQ](#faq 'Jump to FAQ')
3434
| [Support and Community](#support-and-community 'Jump to Support and Community')
3535
| [Contributing](#contributing 'Jump to Contributing')
@@ -257,11 +257,11 @@ Use the Explain panel on the **Commit Details** view to leverage AI to help you
257257

258258
Use the `Generate Commit Message` command from the Source Control view's context menu to automatically generate a commit message for your staged changes by leveraging AI.
259259

260-
# Ready for GitKraken Pro?
260+
# Ready for GitLens Pro?
261261

262-
When you're ready to unlock the full potential of GitLens and enjoy all the benefits on your privately hosted repos, consider upgrading to GitKraken Pro. With GitKraken Pro, you'll gain access to ✨ features on privately hosted repos and ☁️ features based on the Pro plan.
262+
When you're ready to unlock the full potential of GitLens and enjoy all the benefits on your privately hosted repos, consider upgrading to GitLens Pro. With GitLens Pro, you'll gain access to ✨ features on privately hosted repos and ☁️ features based on the Pro plan.
263263

264-
To learn more about the pricing and the additional ✨ and ☁️ features offered with GitKraken Pro, visit the [GitLens Pricing page](https://www.gitkraken.com/gitlens/pricing). Upgrade to GitKraken Pro today and take your Git workflow to the next level!
264+
To learn more about the pricing and the additional ✨ and ☁️ features offered with GitLens Pro, visit the [GitLens Pricing page](https://www.gitkraken.com/gitlens/pricing). Upgrade to GitLens Pro today and take your Git workflow to the next level!
265265

266266
# FAQ
267267

@@ -274,7 +274,7 @@ Yes. All features are free to use on all repos, **except** for features,
274274

275275
While GitLens offers a remarkable set of free features, a subset of features tailored for professional developers and teams, marked with a ✨, require a trial or paid plan for use on privately hosted repos — use on local or publicly hosted repos is free for everyone. Additionally some features marked with a ☁️, rely on GitKraken Dev Services which requires a GitKraken account and access is based on your plan, e.g. Free, Pro, etc.
276276

277-
Preview ✨ features instantly for free for 3 days without an account, or start a free GitKraken trial to get an additional 7 days and gain access to ☁️ features to experience the full power of GitLens.
277+
Preview ✨ features instantly for free for 3 days without an account, or start a free GitLens Pro trial to get an additional 7 days and gain access to ☁️ features to experience the full power of GitLens.
278278

279279
## Are ✨ and ☁️ features free to use?
280280

@@ -300,7 +300,7 @@ Join the GitLens community on [GitHub Discussions](https://github.com/gitkraken/
300300

301301
For any issues or inquiries related to GitLens, you can reach out to the GitKraken support team via the [official support page](https://support.gitkraken.com/). They will be happy to assist you with any problems you may encounter.
302302

303-
With GitKraken Pro, you gain access to priority email support from our customer success team, ensuring higher priority and faster response times. Custom onboarding and training are also available to help you and your team quickly get up and running with a GitKraken Pro plan.
303+
With GitLens Pro, you gain access to priority email support from our customer success team, ensuring higher priority and faster response times. Custom onboarding and training are also available to help you and your team quickly get up and running with a GitLens Pro plan.
304304

305305
# Contributing
306306

package.json

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5123,6 +5123,11 @@
51235123
"title": "Preview Pro",
51245124
"category": "GitLens"
51255125
},
5126+
{
5127+
"command": "gitlens.plus.reactivateProTrial",
5128+
"title": "Reactivate Pro Trial",
5129+
"category": "GitLens"
5130+
},
51265131
{
51275132
"command": "gitlens.plus.manage",
51285133
"title": "Manage Your Account...",
@@ -8929,6 +8934,10 @@
89298934
"command": "gitlens.plus.startPreviewTrial",
89308935
"when": "!gitlens:plus"
89318936
},
8937+
{
8938+
"command": "gitlens.plus.reactivateProTrial",
8939+
"when": "gitlens:plus:state == 5"
8940+
},
89328941
{
89338942
"command": "gitlens.plus.manage",
89348943
"when": "gitlens:plus"
@@ -16123,7 +16132,7 @@
1612316132
},
1612416133
{
1612516134
"view": "gitlens.views.drafts",
16126-
"contents": "[Start Free Pro Trial](command:gitlens.plus.signUp)\n\nStart a free 7-day Pro trial to use Cloud Patches, or [sign in](command:gitlens.plus.login).\n☁️ Requires a GitKraken account and access is based on your plan, e.g. Free, Pro, etc",
16135+
"contents": "[Start Pro Trial](command:gitlens.plus.signUp)\n\nStart a free 7-day GitLens Pro trial to use Cloud Patches, or [sign in](command:gitlens.plus.login).\n☁️ Requires a GitKraken account and access is based on your plan, e.g. Free, Pro, etc",
1612716136
"when": "!gitlens:plus"
1612816137
},
1612916138
{
@@ -16137,7 +16146,7 @@
1613716146
},
1613816147
{
1613916148
"view": "gitlens.views.workspaces",
16140-
"contents": "[Start Free GitKraken Trial](command:gitlens.plus.signUp)\n\nStart a free 7-day GitKraken trial to use GitKraken Workspaces, or [sign in](command:gitlens.plus.login).\n☁️ Requires a GitKraken account and access is based on your plan, e.g. Free, Pro, etc",
16149+
"contents": "[Start Pro Trial](command:gitlens.plus.signUp)\n\nStart a free 7-day GitLens Pro trial to use GitKraken Workspaces, or [sign in](command:gitlens.plus.login).\n☁️ Requires a GitKraken account and access is based on your plan, e.g. Free, Pro, etc",
1614116150
"when": "!gitlens:plus"
1614216151
},
1614316152
{
@@ -16157,18 +16166,23 @@
1615716166
},
1615816167
{
1615916168
"view": "gitlens.views.worktrees",
16160-
"contents": "[Preview Pro](command:gitlens.plus.startPreviewTrial)\n\nPreview Pro for 3 days, or [sign up](command:gitlens.plus.signUp) to start a full 7-day GitKraken trial.\n✨ A trial or paid plan is required to use this on privately hosted repos.",
16169+
"contents": "[Preview Pro](command:gitlens.plus.startPreviewTrial)\n\nPreview Pro for 3 days, or [sign up](command:gitlens.plus.signUp) to start a full 7-day GitLens Pro trial.\n[✨ Worktrees](https://help.gitkraken.com/gitlens/side-bar/#worktrees-view%e2%9c%a8) — seamlessly work on multiple branches simultaneously.",
1616116170
"when": "gitlens:plus:required && gitlens:plus:state == 0"
1616216171
},
1616316172
{
1616416173
"view": "gitlens.views.worktrees",
16165-
"contents": "Your 3-day Pro preview has ended, start a free GitKraken trial to get an additional 7 days, or [sign in](command:gitlens.plus.login).\n\n[Start Free GitKraken Trial](command:gitlens.plus.signUp)\n✨ A trial or paid plan is required to use this on privately hosted repos.",
16174+
"contents": "Your 3-day preview has ended. Start a free GitLens Pro trial to get an additional 7 days, or [sign in](command:gitlens.plus.login).\n\n[Start Pro Trial](command:gitlens.plus.signUp)\n[✨ Worktrees](https://help.gitkraken.com/gitlens/side-bar/#worktrees-view%e2%9c%a8) — seamlessly work on multiple branches simultaneously.",
1616616175
"when": "gitlens:plus:required && gitlens:plus:state == 2"
1616716176
},
1616816177
{
1616916178
"view": "gitlens.views.worktrees",
16170-
"contents": "Your GitKraken trial has ended, please upgrade to continue to use this on privately hosted repos.\n\n[Upgrade to Pro](command:gitlens.plus.purchase)\n✨ A paid plan is required to use this on privately hosted repos.",
16179+
"contents": "Your GitLens Pro trial has ended. Please upgrade to continue to use this on privately hosted repos.\n\n[Get GitLens Pro](command:gitlens.plus.purchase)\n[✨ Worktrees](https://help.gitkraken.com/gitlens/side-bar/#worktrees-view%e2%9c%a8) — seamlessly work on multiple branches simultaneously.",
1617116180
"when": "gitlens:plus:required && gitlens:plus:state == 4"
16181+
},
16182+
{
16183+
"view": "gitlens.views.worktrees",
16184+
"contents": "You're eligible to reactivate your GitLens Pro trial and experience all the new Pro features — free for another 7 days!\n\n[Try Pro](command:gitlens.plus.reactivateProTrial)\n[✨ Worktrees](https://help.gitkraken.com/gitlens/side-bar/#worktrees-view%e2%9c%a8) — seamlessly work on multiple branches simultaneously.",
16185+
"when": "gitlens:plus:required && gitlens:plus:state == 5"
1617216186
}
1617316187
],
1617416188
"views": {
@@ -16441,16 +16455,16 @@
1644116455
},
1644216456
{
1644316457
"id": "gitlens.welcome.preview",
16444-
"title": "Previewing GitKraken Pro",
16445-
"description": "During your preview, you have access to ✨ features on privately hosted repos. [Learn more](https://www.gitkraken.com/gitlens/pro-features)\n\n[Start Free GitKraken Trial](command:gitlens.plus.signUp)\n\nStart a free GitKraken trial to get an additional 7 days.",
16458+
"title": "Previewing GitLens Pro",
16459+
"description": "During your preview, you have access to ✨ features on privately hosted repos. [Learn more](https://www.gitkraken.com/gitlens/pro-features)\n\n[Start Free Pro Trial](command:gitlens.plus.signUp)\n\nStart a free Pro trial to get an additional 7 days.",
1644616460
"media": {
1644716461
"markdown": "walkthroughs/welcome/preview.md"
1644816462
},
1644916463
"when": "gitlens:plus:state == 1"
1645016464
},
1645116465
{
1645216466
"id": "gitlens.welcome.trial",
16453-
"title": "Trialing GitKraken Pro",
16467+
"title": "Trialing GitLens Pro",
1645416468
"description": "During your trial, you have access to ✨ features on privately hosted repos and ☁️ features based on the Pro plan. [Learn more](https://www.gitkraken.com/gitlens/pro-features)\n\n[Upgrade to Pro](command:gitlens.plus.purchase)",
1645516469
"media": {
1645616470
"markdown": "walkthroughs/welcome/trial.md"

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ export const enum Commands {
252252
PlusLogout = 'gitlens.plus.logout',
253253
PlusManage = 'gitlens.plus.manage',
254254
PlusPurchase = 'gitlens.plus.purchase',
255+
PlusReactivateProTrial = 'gitlens.plus.reactivateProTrial',
255256
PlusResendVerification = 'gitlens.plus.resendVerification',
256257
PlusRestore = 'gitlens.plus.restore',
257258
PlusShowPlans = 'gitlens.plus.showPlans',

src/plus/gk/account/subscription.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface SubscriptionPlan {
3434
readonly name: string;
3535
readonly bundle: boolean;
3636
readonly trialReactivationCount: number;
37+
readonly nextTrialOptInDate?: string | undefined;
3738
readonly cancelled: boolean;
3839
readonly startedOn: string;
3940
readonly expiresOn?: string | undefined;
@@ -53,6 +54,7 @@ export interface SubscriptionPreviewTrial {
5354
readonly expiresOn: string;
5455
}
5556

57+
// Note: Pay attention to gitlens:plus:state in package.json when modifying this enum
5658
export const enum SubscriptionState {
5759
/** Indicates a user who hasn't verified their email address yet */
5860
VerificationRequired = -1,
@@ -64,8 +66,10 @@ export const enum SubscriptionState {
6466
FreePreviewTrialExpired,
6567
/** Indicates a Free+ user with a completed trial */
6668
FreePlusInTrial,
67-
/** Indicates a Free+ user who's trial has expired */
69+
/** Indicates a Free+ user who's trial has expired and is not yet eligible for reactivation */
6870
FreePlusTrialExpired,
71+
/** Indicated a Free+ user who's trial has expired and is eligible for reactivation */
72+
FreePlusTrialReactivationEligible,
6973
/** Indicates a Paid user */
7074
Paid,
7175
}
@@ -84,8 +88,13 @@ export function computeSubscriptionState(subscription: Optional<Subscription, 's
8488
case SubscriptionPlanId.Free:
8589
return preview == null ? SubscriptionState.Free : SubscriptionState.FreePreviewTrialExpired;
8690

87-
case SubscriptionPlanId.FreePlus:
91+
case SubscriptionPlanId.FreePlus: {
92+
if (effective.nextTrialOptInDate != null && new Date(effective.nextTrialOptInDate) < new Date()) {
93+
return SubscriptionState.FreePlusTrialReactivationEligible;
94+
}
95+
8896
return SubscriptionState.FreePlusTrialExpired;
97+
}
8998

9099
case SubscriptionPlanId.Pro:
91100
case SubscriptionPlanId.Teams:
@@ -98,8 +107,13 @@ export function computeSubscriptionState(subscription: Optional<Subscription, 's
98107
case SubscriptionPlanId.Free:
99108
return preview == null ? SubscriptionState.Free : SubscriptionState.FreeInPreviewTrial;
100109

101-
case SubscriptionPlanId.FreePlus:
110+
case SubscriptionPlanId.FreePlus: {
111+
if (effective.nextTrialOptInDate != null && new Date(effective.nextTrialOptInDate) < new Date()) {
112+
return SubscriptionState.FreePlusTrialReactivationEligible;
113+
}
114+
102115
return SubscriptionState.FreePlusTrialExpired;
116+
}
103117

104118
case SubscriptionPlanId.Pro:
105119
return actual.id === SubscriptionPlanId.Free
@@ -120,6 +134,7 @@ export function getSubscriptionPlan(
120134
startedOn?: Date,
121135
expiresOn?: Date,
122136
cancelled: boolean = false,
137+
nextTrialOptInDate?: string,
123138
): SubscriptionPlan {
124139
return {
125140
id: id,
@@ -128,6 +143,7 @@ export function getSubscriptionPlan(
128143
cancelled: cancelled,
129144
organizationId: organizationId,
130145
trialReactivationCount: trialReactivationCount,
146+
nextTrialOptInDate: nextTrialOptInDate,
131147
startedOn: (startedOn ?? new Date()).toISOString(),
132148
expiresOn: expiresOn != null ? expiresOn.toISOString() : undefined,
133149
};
@@ -152,6 +168,7 @@ export function getSubscriptionPlanName(id: SubscriptionPlanId) {
152168
export function getSubscriptionStatePlanName(state: SubscriptionState | undefined, id: SubscriptionPlanId | undefined) {
153169
switch (state) {
154170
case SubscriptionState.FreePlusTrialExpired:
171+
case SubscriptionState.FreePlusTrialReactivationEligible:
155172
return getSubscriptionPlanName(SubscriptionPlanId.FreePlus);
156173
case SubscriptionState.FreeInPreviewTrial:
157174
return `${getSubscriptionPlanName(SubscriptionPlanId.Pro)} (Trial)`;
@@ -193,7 +210,7 @@ export function getTimeRemaining(
193210
expiresOn: string | undefined,
194211
unit?: 'days' | 'hours' | 'minutes' | 'seconds',
195212
): number | undefined {
196-
return expiresOn != null ? getDateDifference(Date.now(), new Date(expiresOn), unit) : undefined;
213+
return expiresOn != null ? getDateDifference(Date.now(), new Date(expiresOn), unit, Math.round) : undefined;
197214
}
198215

199216
export function isSubscriptionPaid(subscription: Optional<Subscription, 'state'>): boolean {

src/plus/gk/account/subscriptionService.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
ProgressLocation,
1818
StatusBarAlignment,
1919
ThemeColor,
20+
Uri,
2021
window,
2122
} from 'vscode';
2223
import { getPlatform } from '@env/platform';
@@ -173,6 +174,7 @@ export class SubscriptionService implements Disposable {
173174
registerCommand(Commands.PlusLogout, () => this.logout()),
174175

175176
registerCommand(Commands.PlusStartPreviewTrial, () => this.startPreviewTrial()),
177+
registerCommand(Commands.PlusReactivateProTrial, () => this.reactivateProTrial()),
176178
registerCommand(Commands.PlusManage, () => this.manage()),
177179
registerCommand(Commands.PlusPurchase, () => this.purchase()),
178180

@@ -436,10 +438,10 @@ export class SubscriptionService implements Disposable {
436438
void this.showAccountView();
437439

438440
if (!silent && plan.effective.id === SubscriptionPlanId.Free) {
439-
const confirm: MessageItem = { title: 'Start Free GitKraken Trial', isCloseAffordance: true };
441+
const confirm: MessageItem = { title: 'Start Pro Trial', isCloseAffordance: true };
440442
const cancel: MessageItem = { title: 'Cancel' };
441443
const result = await window.showInformationMessage(
442-
'Your 3-day Pro preview has ended, start a free GitKraken trial to get an additional 7 days.\n\n✨ A trial or paid plan is required to use Pro features on privately hosted repos.',
444+
'Your 3-day preview has ended. Start a free GitLens Pro trial to get an additional 7 days.\n\n✨ A trial or paid plan is required to use Pro features on privately hosted repos.',
443445
{ modal: true },
444446
confirm,
445447
cancel,
@@ -492,7 +494,7 @@ export class SubscriptionService implements Disposable {
492494
`You can now preview Pro features for ${pluralize(
493495
'day',
494496
days,
495-
)}. After which, you can start a free GitKraken trial for an additional 7 days.`,
497+
)}. After which, you can start a free GitLens Pro trial for an additional 7 days.`,
496498
confirm,
497499
learn,
498500
);
@@ -504,6 +506,66 @@ export class SubscriptionService implements Disposable {
504506
}
505507
}
506508

509+
@gate()
510+
@log()
511+
async reactivateProTrial(): Promise<void> {
512+
if (!(await ensurePlusFeaturesEnabled())) return;
513+
const scope = getLogScope();
514+
515+
const session = await this.ensureSession(false);
516+
if (session == null) return;
517+
518+
const rsp = await this.connection.fetchApi('user/reactivate-trial', {
519+
method: 'POST',
520+
body: JSON.stringify({ client: 'gitlens' }),
521+
});
522+
523+
if (!rsp.ok) {
524+
if (rsp.status === 409) {
525+
void window.showErrorMessage(
526+
'Unable to reactivate trial: User not eligible. Please try again. If this issue persists, please contact support.',
527+
'OK',
528+
);
529+
return;
530+
}
531+
532+
void window.showErrorMessage(
533+
`Unable to reactivate trial: (${rsp.status}) ${rsp.statusText}. Please try again. If this issue persists, please contact support.`,
534+
'OK',
535+
);
536+
return;
537+
}
538+
539+
// Trial was reactivated. Do a check-in to update, and show a message if successful.
540+
try {
541+
await this.checkInAndValidate(session, { force: true });
542+
if (isSubscriptionTrial(this._subscription)) {
543+
const remaining = getSubscriptionTimeRemaining(this._subscription, 'days');
544+
545+
const confirm: MessageItem = { title: 'OK', isCloseAffordance: true };
546+
const learn: MessageItem = { title: "See What's New" };
547+
const result = await window.showInformationMessage(
548+
`Your new trial has been activated! Enjoy access to Pro features on privately hosted repos for another ${pluralize(
549+
'day',
550+
remaining ?? 0,
551+
)}.`,
552+
{ modal: true },
553+
confirm,
554+
learn,
555+
);
556+
557+
if (result === learn) {
558+
void env.openExternal(
559+
Uri.parse('https://help.gitkraken.com/gitlens/gitlens-release-notes-current/'),
560+
);
561+
}
562+
}
563+
} catch (ex) {
564+
Logger.error(ex, scope);
565+
debugger;
566+
}
567+
}
568+
507569
@gate<SubscriptionService['validate']>(o => `${o?.force ?? false}`)
508570
@log()
509571
async validate(options?: { force?: boolean }): Promise<void> {

src/plus/gk/checkin.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface GKCheckInResponse {
88
readonly paidLicenses: Record<GKLicenseType, GKLicense>;
99
readonly effectiveLicenses: Record<GKLicenseType, GKLicense>;
1010
};
11+
readonly nextOptInDate?: string;
1112
}
1213

1314
export interface GKUser {
@@ -25,6 +26,7 @@ export interface GKLicense {
2526
readonly latestEndDate: string;
2627
readonly organizationId: string | undefined;
2728
readonly reactivationCount?: number;
29+
readonly nextOptInDate?: string;
2830
}
2931

3032
export type GKLicenseType =
@@ -144,6 +146,9 @@ export function getSubscriptionFromCheckIn(
144146
: data.user.createdDate != null
145147
? new Date(data.user.createdDate)
146148
: undefined,
149+
undefined,
150+
undefined,
151+
data.nextOptInDate,
147152
);
148153
}
149154

@@ -162,6 +167,7 @@ export function getSubscriptionFromCheckIn(
162167
new Date(license.latestStartDate),
163168
new Date(license.latestEndDate),
164169
license.latestStatus === 'cancelled',
170+
license.nextOptInDate ?? data.nextOptInDate,
165171
);
166172
}
167173

src/plus/integrations/providerIntegration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ export async function ensurePaidPlan(providerName: string, container: Container)
934934
void container.subscription.startPreviewTrial();
935935
break;
936936
} else if (subscription.account == null) {
937-
const signIn = { title: 'Start Free GitKraken Trial' };
937+
const signIn = { title: 'Start Pro Trial' };
938938
const cancel = { title: 'Cancel', isCloseAffordance: true };
939939
const result = await window.showWarningMessage(
940940
`${title}\n\nDo you want to continue to use ✨ features on privately hosted repos, free for an additional 7 days?`,

src/plus/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export async function ensurePaidPlan(title: string, container: Container): Promi
2929
if (isSubscriptionPaidPlan(plan)) break;
3030

3131
if (subscription.account == null) {
32-
const signIn = { title: 'Start Free GitKraken Trial' };
32+
const signIn = { title: 'Start Pro Trial' };
3333
const cancel = { title: 'Cancel', isCloseAffordance: true };
3434
const result = await window.showWarningMessage(
3535
`${title}\n\nTry our developer productivity and collaboration services free for 7 days.`,

src/plus/workspaces/workspacesService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export class WorkspacesService implements Disposable {
163163
cloudWorkspaces: cloudWorkspaces,
164164
cloudWorkspaceInfo:
165165
filteredSharedWorkspaceCount > 0
166-
? `${filteredSharedWorkspaceCount} shared workspaces hidden - upgrade to GitKraken Pro to access.`
166+
? `${filteredSharedWorkspaceCount} shared workspaces hidden - upgrade to GitLens Pro to access.`
167167
: undefined,
168168
};
169169
}

0 commit comments

Comments
 (0)