diff --git a/README.md b/README.md
index fe76ad68ecda5..54965230a5843 100644
--- a/README.md
+++ b/README.md
@@ -293,7 +293,7 @@ Yes. All features are free to use on all repos, **except** for `Pro` features, w
While GitLens offers a remarkable set of free features, a subset of `Pro` features tailored for professional developers and teams, require a trial or paid plan for use on privately-hosted repos — use on local or publicly-hosted repos is free for everyone. Additionally `Preview` features may require a paid plan in the future and some, if cloud-backed, require a GitKraken account.
-Preview `Pro` 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 `Pro` features to experience the full power of GitLens.
+Preview `Pro` features instantly for free for 3 days without an account, or start a free GitLens Pro trial to get an additional 14 days and gain access to `Pro` features to experience the full power of GitLens.
## Are `Pro` and `Preview` features free to use?
diff --git a/package.json b/package.json
index 26ed4f07a2973..4b16f76472ca1 100644
--- a/package.json
+++ b/package.json
@@ -5726,11 +5726,6 @@
"title": "Sign Up for GitKraken...",
"category": "GitLens"
},
- {
- "command": "gitlens.plus.startPreviewTrial",
- "title": "Preview Pro",
- "category": "GitLens"
- },
{
"command": "gitlens.plus.reactivateProTrial",
"title": "Reactivate Pro Trial",
@@ -10276,10 +10271,6 @@
"command": "gitlens.plus.signUp",
"when": "!gitlens:plus"
},
- {
- "command": "gitlens.plus.startPreviewTrial",
- "when": "!gitlens:plus"
- },
{
"command": "gitlens.plus.reactivateProTrial",
"when": "gitlens:plus:state == 5"
@@ -19322,7 +19313,7 @@
},
{
"view": "gitlens.views.drafts",
- "contents": "Cloud Patches ᴘʀᴇᴠɪᴇᴡ — easily and securely share code with your teammates or other developers, accessible from anywhere, streamlining your workflow with better collaboration."
+ "contents": "Cloud Patches ᴘʀᴇᴠɪᴇᴡ — easily and securely share code with your teammates and other developers without adding noise to your repository."
},
{
"view": "gitlens.views.drafts",
@@ -19331,16 +19322,17 @@
},
{
"view": "gitlens.views.drafts",
- "contents": "[Start Pro Trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22cloud-patches%22%7D)\n\nStart your free 7-day Pro trial to try Cloud Patches and other Pro features, or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22cloud-patches%22%7D).",
+ "contents": "Preview feature - requires an account and may require [GitLens Pro](https://help.gitkraken.com/gitlens/gitlens-community-vs-gitlens-pro/) in the future.\n[Try GitLens Pro](command:gitlens.plus.signUp?%7B%22source%22%3A%22cloud-patches%22%7D)\n\nGet 14 days of GitLens Pro for free - no credit card required. Or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22cloud-patches%22%7D).",
"when": "!gitlens:plus"
},
{
- "view": "gitlens.views.drafts",
- "contents": "Preview feature ☁️ — requires an account and may require a paid plan in the future."
+ "view": "gitlens.views.launchpad",
+ "contents": "[Launchpad](command:gitlens.views.launchpad.info \"Learn about Launchpad\") — organizes your pull requests into actionable groups to help you focus and keep your team unblocked."
},
{
"view": "gitlens.views.launchpad",
- "contents": "[Launchpad](command:gitlens.views.launchpad.info \"Learn about Launchpad\") — organizes your pull requests into actionable groups to help you focus and keep your team unblocked."
+ "contents": "Pro feature — requires a paid plan for use on privately-hosted repos.",
+ "when": "!gitlens:plus"
},
{
"view": "gitlens.views.scm.grouped",
@@ -19369,23 +19361,13 @@
},
{
"view": "gitlens.views.launchpad",
- "contents": "[Continue](command:gitlens.plus.startPreviewTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nContinuing gives you 3 days to preview Launchpad and other local Pro features for 3 days. [Start 7-day Pro trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22launchpad-view%22%7D) or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22launchpad-view%22%7D) for full access to Pro features.",
- "when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 0"
- },
- {
- "view": "gitlens.views.scm.grouped",
- "contents": "[Continue](command:gitlens.plus.startPreviewTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nContinuing gives you 3 days to preview Launchpad and other local Pro features for 3 days. [Start 7-day Pro trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22launchpad-view%22%7D) or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22launchpad-view%22%7D) for full access to Pro features.",
- "when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 0 && gitlens:views:scm:grouped:view == launchpad"
- },
- {
- "view": "gitlens.views.launchpad",
- "contents": "[Start Pro Trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nStart your free 7-day Pro trial to try Launchpad and other Pro features, or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22launchpad-view%22%7D).",
- "when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 2"
+ "contents": "[Try GitLens Pro](command:gitlens.plus.signUp?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nGet 14 days of GitLens Pro for free - no credit card required. Or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22launchpad-view%22%7D).",
+ "when": "!gitlens:launchpad:connect && gitlens:plus:required && (gitlens:plus:state == 2 || gitlens:plus:state == 0)"
},
{
"view": "gitlens.views.scm.grouped",
- "contents": "[Start Pro Trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nStart your free 7-day Pro trial to try Launchpad and other Pro features, or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22launchpad-view%22%7D).",
- "when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 2 && gitlens:views:scm:grouped:view == launchpad"
+ "contents": "[Try GitLens Pro](command:gitlens.plus.signUp?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nGet 14 days of GitLens Pro for free - no credit card required. Or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22launchpad-view%22%7D).",
+ "when": "!gitlens:launchpad:connect && gitlens:plus:required && (gitlens:plus:state == 0 || gitlens:plus:state == 2) && gitlens:views:scm:grouped:view == launchpad"
},
{
"view": "gitlens.views.launchpad",
@@ -19429,19 +19411,14 @@
},
{
"view": "gitlens.views.launchpad",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 14 days!",
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 5"
},
{
"view": "gitlens.views.scm.grouped",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22launchpad-view%22%7D)\n\nReactivate your Pro trial and experience Launchpad and all the new Pro features — free for another 14 days!",
"when": "!gitlens:launchpad:connect && gitlens:plus:required && gitlens:plus:state == 5 && gitlens:views:scm:grouped:view == launchpad"
},
- {
- "view": "gitlens.views.launchpad",
- "contents": "Pro feature — requires a paid plan for use on privately-hosted repos.",
- "when": "!gitlens:launchpad:connect"
- },
{
"view": "gitlens.views.scm.grouped",
"contents": "Pro feature — requires a paid plan for use on privately-hosted repos.",
@@ -19458,7 +19435,7 @@
},
{
"view": "gitlens.views.workspaces",
- "contents": "[Start Pro Trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22workspaces%22%7D)\n\nStart your free 7-day Pro trial to try GitKraken (GK) Workspaces and other Pro features, or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22workspaces%22%7D).",
+ "contents": "Use on privately-hosted repos requires [GitLens Pro](https://help.gitkraken.com/gitlens/gitlens-community-vs-gitlens-pro/).\n[Try GitLens Pro](command:gitlens.plus.signUp?%7B%22source%22%3A%22workspaces%22%7D)\n\nGet 14 days of GitLens Pro for free - no credit card required. Or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22workspaces%22%7D).",
"when": "!gitlens:plus"
},
{
@@ -19467,7 +19444,7 @@
},
{
"view": "gitlens.views.worktrees",
- "contents": "[Worktrees](https://help.gitkraken.com/gitlens/side-bar/#worktrees-view-pro) ᴾᴿᴼ — minimize context switching by allowing you to work on multiple branches simultaneously."
+ "contents": "[Worktrees](https://help.gitkraken.com/gitlens/side-bar/#worktrees-view-pro) ᴾᴿᴼ — minimize context switching by working on multiple branches simultaneously."
},
{
"view": "gitlens.views.scm.grouped",
@@ -19496,27 +19473,17 @@
},
{
"view": "gitlens.views.worktrees",
- "contents": "[Continue](command:gitlens.plus.startPreviewTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nContinuing gives you 3 days to preview Worktrees and other local Pro features for 3 days. [Start 7-day Pro trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22worktrees%22%7D) or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22worktrees%22%7D) for full access to Pro features.",
- "when": "gitlens:plus:required && gitlens:plus:state == 0"
- },
- {
- "view": "gitlens.views.scm.grouped",
- "contents": "[Continue](command:gitlens.plus.startPreviewTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nContinuing gives you 3 days to preview Worktrees and other local Pro features for 3 days. [Start 7-day Pro trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22worktrees%22%7D) or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22worktrees%22%7D) for full access to Pro features.",
- "when": "gitlens:plus:required && gitlens:plus:state == 0 && gitlens:views:scm:grouped:view == worktrees"
- },
- {
- "view": "gitlens.views.worktrees",
- "contents": "[Start Pro Trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22worktrees%22%7D)\n\nStart your free 7-day Pro trial to try Worktrees and other Pro features, or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22worktrees%22%7D).",
- "when": "gitlens:plus:required && gitlens:plus:state == 2"
+ "contents": "Use on privately-hosted repos requires [GitLens Pro](https://help.gitkraken.com/gitlens/gitlens-community-vs-gitlens-pro/).\n[Try GitLens Pro](command:gitlens.plus.signUp?%7B%22source%22%3A%22worktrees%22%7D)\n\nGet 14 days of GitLens Pro for free - no credit card required. Or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22worktrees%22%7D).",
+ "when": "gitlens:plus:required && (gitlens:plus:state == 2 || gitlens:plus:state == 0)"
},
{
"view": "gitlens.views.scm.grouped",
- "contents": "[Start Pro Trial](command:gitlens.plus.signUp?%7B%22source%22%3A%22worktrees%22%7D)\n\nStart your free 7-day Pro trial to try Worktrees and other Pro features, or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22worktrees%22%7D).",
- "when": "gitlens:plus:required && gitlens:plus:state == 2 && gitlens:views:scm:grouped:view == worktrees"
+ "contents": "[Try GitLens Pro](command:gitlens.plus.signUp?%7B%22source%22%3A%22worktrees%22%7D)\n\nGet 14 days of GitLens Pro for free - no credit card required. Or [sign in](command:gitlens.plus.login?%7B%22source%22%3A%22worktrees%22%7D).",
+ "when": "gitlens:plus:required && (gitlens:plus:state == 0 || gitlens:plus:state == 2) && gitlens:views:scm:grouped:view == worktrees"
},
{
"view": "gitlens.views.worktrees",
- "contents": "[Upgrade to Pro](command:gitlens.plus.upgrade?%7B%22source%22%3A%22worktrees%22%7D)",
+ "contents": "Use on privately-hosted repos requires [GitLens Pro](https://help.gitkraken.com/gitlens/gitlens-community-vs-gitlens-pro?%7B%22source%22%3A%22worktrees%22%7D).\n\n[Upgrade to Pro](command:gitlens.plus.upgrade?%7B%22source%22%3A%22worktrees%22%7D)",
"when": "gitlens:plus:required && gitlens:plus:state == 4"
},
{
@@ -19544,11 +19511,6 @@
"contents": "Save more than 55% during our GitLens 16 sale!",
"when": "gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == gitlens16 && gitlens:views:scm:grouped:view == worktrees"
},
- {
- "view": "gitlens.views.worktrees",
- "contents": "Your Pro trial has ended. Please upgrade for full access to Worktrees and other Pro features.",
- "when": "gitlens:plus:required && gitlens:plus:state == 4"
- },
{
"view": "gitlens.views.scm.grouped",
"contents": "Your Pro trial has ended. Please upgrade for full access to Worktrees and other Pro features.",
@@ -19556,18 +19518,14 @@
},
{
"view": "gitlens.views.worktrees",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 14 days!",
"when": "gitlens:plus:required && gitlens:plus:state == 5"
},
{
"view": "gitlens.views.scm.grouped",
- "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 7 days!",
+ "contents": "[Continue](command:gitlens.plus.reactivateProTrial?%7B%22source%22%3A%22worktrees%22%7D)\n\nReactivate your Pro trial and experience Worktrees and all the new Pro features — free for another 14 days!",
"when": "gitlens:plus:required && gitlens:plus:state == 5 && gitlens:views:scm:grouped:view == worktrees"
},
- {
- "view": "gitlens.views.worktrees",
- "contents": "Pro feature — requires a paid plan for use on privately-hosted repos."
- },
{
"view": "gitlens.views.scm.grouped",
"contents": "Pro feature — requires a paid plan for use on privately-hosted repos.",
diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts
index 72729c473ebfc..01168508eb927 100644
--- a/src/commands/quickCommand.steps.ts
+++ b/src/commands/quickCommand.steps.ts
@@ -2637,9 +2637,9 @@ export async function* ensureAccessStep<
const promo = getApplicablePromo(access.subscription.current.state, 'gate');
const detail = promo?.quickpick.detail;
- placeholder = 'Pro feature — requires a trial or paid plan for use on privately-hosted repos';
+ placeholder = 'Pro feature — requires a trial or GitLens Pro for use on privately-hosted repos';
if (isSubscriptionPaidPlan(access.subscription.required) && access.subscription.current.account != null) {
- placeholder = 'Pro feature — requires a paid plan for use on privately-hosted repos';
+ placeholder = 'Pro feature — requires GitLens Pro for use on privately-hosted repos';
directives.push(
createDirectiveQuickPickItem(Directive.RequiresPaidSubscription, true, { detail: detail }),
createQuickPickSeparator(),
diff --git a/src/constants.storage.ts b/src/constants.storage.ts
index faada399620b4..ad8a0bcca327a 100644
--- a/src/constants.storage.ts
+++ b/src/constants.storage.ts
@@ -1,7 +1,7 @@
import type { GraphBranchesVisibility, ViewShowBranchComparison } from './config';
import type { AIProviders } from './constants.ai';
import type { IntegrationId } from './constants.integrations';
-import type { TrackedUsage, TrackedUsageKeys } from './constants.telemetry';
+import type { Sources, TrackedUsage, TrackedUsageKeys } from './constants.telemetry';
import type { GroupableTreeViewTypes } from './constants.views';
import type { Environment } from './container';
import type { Subscription } from './plus/gk/account/subscription';
@@ -78,6 +78,10 @@ export type GlobalStorage = {
'graph:searchMode': StoredGraphSearchMode;
'views:scm:grouped:welcome:dismissed': boolean;
} & { [key in `confirm:ai:tos:${AIProviders}`]: boolean } & {
+ [key in `plus:featurePreviewTrial:${Sources}:consumedDays`]: { startedOn: string; expiresOn: string }[];
+} & {
+ [key in `confirm:ai:tos:${AIProviders}`]: boolean;
+} & {
[key in `provider:authentication:skip:${string}`]: boolean;
} & { [key in `gk:${string}:checkin`]: Stored } & {
[key in `gk:${string}:organizations`]: Stored;
diff --git a/src/constants.subscription.ts b/src/constants.subscription.ts
index a7fc5e5349e36..1dcef837048aa 100644
--- a/src/constants.subscription.ts
+++ b/src/constants.subscription.ts
@@ -1,5 +1,5 @@
export const proPreviewLengthInDays = 3;
-export const proTrialLengthInDays = 7;
+export const proTrialLengthInDays = 14;
export type PromoKeys = 'gitlens16' | 'pro50';
diff --git a/src/constants.telemetry.ts b/src/constants.telemetry.ts
index f9483f00af88d..71b2365e3d8d4 100644
--- a/src/constants.telemetry.ts
+++ b/src/constants.telemetry.ts
@@ -413,7 +413,8 @@ export type TelemetryEvents = {
| 'resend-verification'
| 'pricing'
| 'start-preview-trial'
- | 'upgrade';
+ | 'upgrade'
+ | `start-${Sources}-preview-trial`;
}
| {
action: 'visibility';
diff --git a/src/constants.ts b/src/constants.ts
index 8ea0def7fd01d..f1d326c4dc8d3 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -178,6 +178,7 @@ export const urls = Object.freeze({
interactiveCodeHistory: `https://help.gitkraken.com/gitlens/gitlens-start-here/?${utm}#interactive-code-history`,
startIntegrations: `https://help.gitkraken.com/gitlens/gitlens-start-here/?${utm}#improve-workflows-with-integrations`,
streamlineCollaboration: `https://help.gitkraken.com/gitlens/gitlens-start-here/?${utm}#streamline-collaboration`,
+ gitlensProVsCommunity: `https://help.gitkraken.com/gitlens/gitlens-community-vs-gitlens-pro/?${utm}`,
});
export type WalkthroughSteps =
diff --git a/src/plus/gk/account/subscriptionService.ts b/src/plus/gk/account/subscriptionService.ts
index b5cd8912ed1aa..0ad5f9894ea9a 100644
--- a/src/plus/gk/account/subscriptionService.ts
+++ b/src/plus/gk/account/subscriptionService.ts
@@ -315,46 +315,27 @@ export class SubscriptionService implements Disposable {
} = this._subscription;
if (account?.verified === false) {
- const days = getSubscriptionTimeRemaining(this._subscription, 'days') ?? proTrialLengthInDays;
-
const verify: MessageItem = { title: 'Resend Email' };
- const learn: MessageItem = { title: 'See Pro Features' };
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
+
const result = await window.showInformationMessage(
- isSubscriptionPaid(this._subscription)
- ? `You are now on the ${actual.name} plan. \n\nYou must first verify your email. Once verified, you will have full access to Pro features.`
- : `Welcome to your ${
- effective.name
- } Trial.\n\nYou must first verify your email. Once verified, you will have full access to Pro features for ${
- days < 1 ? '<1 more day' : pluralize('day', days, { infix: ' more ' })
- }.`,
- {
- modal: true,
- detail: `Your ${
- isSubscriptionPaid(this._subscription) ? 'plan' : 'trial'
- } also includes access to the GitKraken DevEx platform, unleashing powerful Git visualization & productivity capabilities everywhere you work: IDE, desktop, browser, and terminal.`,
- },
+ 'Welcome to GitLens',
+ { modal: true, detail: 'Verify the email we just sent you to start your Pro trial.' },
verify,
- learn,
confirm,
);
if (result === verify) {
void this.resendVerification(source);
- } else if (result === learn) {
- void this.learnAboutPro({ source: 'prompt', detail: { action: 'trial-started-verify-email' } }, source);
}
} else if (isSubscriptionPaid(this._subscription)) {
- const learn: MessageItem = { title: 'See Pro Features' };
+ const learn: MessageItem = { title: 'Learn More' };
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
const result = await window.showInformationMessage(
`You are now on the ${actual.name} plan and have full access to Pro features.`,
- {
- modal: true,
- detail: 'Your plan also includes access to the GitKraken DevEx platform, unleashing powerful Git visualization & productivity capabilities everywhere you work: IDE, desktop, browser, and terminal.',
- },
- learn,
+ { modal: true },
confirm,
+ learn,
);
if (result === learn) {
@@ -363,7 +344,7 @@ export class SubscriptionService implements Disposable {
} else if (isSubscriptionTrial(this._subscription)) {
const days = getSubscriptionTimeRemaining(this._subscription, 'days') ?? 0;
- const learn: MessageItem = { title: 'See Pro Features' };
+ const learn: MessageItem = { title: 'Learn More' };
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
const result = await window.showInformationMessage(
`Welcome to your ${effective.name} Trial.\n\nYou now have full access to Pro features for ${
@@ -382,13 +363,13 @@ export class SubscriptionService implements Disposable {
}
} else {
const upgrade: MessageItem = { title: 'Upgrade to Pro' };
- const learn: MessageItem = { title: 'See Pro Features' };
+ const learn: MessageItem = { title: 'Community vs. Pro' };
const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true };
const result = await window.showInformationMessage(
`You are now on the ${actual.name} plan.`,
{
modal: true,
- detail: 'You only have access to Pro features on publicly-hosted repos. For full access to Pro features, please upgrade to a paid plan.\nA paid plan also includes access to the GitKraken DevEx platform, unleashing powerful Git visualization & productivity capabilities everywhere you work: IDE, desktop, browser, and terminal.',
+ detail: 'You only have access to Pro features on publicly-hosted repos. For full access to Pro features, please upgrade to a paid plan.',
},
upgrade,
learn,
@@ -711,19 +692,14 @@ export class SubscriptionService implements Disposable {
this.changeSubscription(subscription);
setTimeout(async () => {
- const confirm: MessageItem = { title: 'Continue' };
- const learn: MessageItem = { title: 'See Pro Features' };
- const result = await window.showInformationMessage(
- `You can now preview local Pro features for ${
+ await window.showInformationMessage(
+ `You can now preview the Commit Graph on privately-hosted repos for ${
days < 1 ? '1 day' : pluralize('day', days)
- }, or [start your free ${proTrialLengthInDays}-day Pro trial](command:gitlens.plus.signUp "Start Pro Trial") for full access to Pro features.`,
- confirm,
- learn,
+ }, or [start your free ${proTrialLengthInDays}-day Pro trial](command:gitlens.plus.signUp "Start Pro Trial") for full access to all [GitLens Pro](${
+ urls.gitlensProVsCommunity
+ }) features.`,
+ { title: 'Continue' },
);
-
- if (result === learn) {
- void this.learnAboutPro({ source: 'notification', detail: { action: 'preview-started' } }, source);
- }
}, 1);
}
diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts
index 4a69e82a51d0d..3036f1b4b2514 100644
--- a/src/plus/webviews/graph/graphWebview.ts
+++ b/src/plus/webviews/graph/graphWebview.ts
@@ -20,6 +20,7 @@ import type {
import { GlyphChars } from '../../../constants';
import { Commands } from '../../../constants.commands';
import type { StoredGraphFilters, StoredGraphRefType } from '../../../constants.storage';
+import { proPreviewLengthInDays } from '../../../constants.subscription';
import type { GraphShownTelemetryContext, GraphTelemetryContext, TelemetryEvents } from '../../../constants.telemetry';
import type { Container } from '../../../container';
import { CancellationError } from '../../../errors';
@@ -97,6 +98,7 @@ import { getSearchQueryComparisonKey, parseSearchQuery } from '../../../git/sear
import { splitGitCommitMessage } from '../../../git/utils/commit-utils';
import { ReferencesQuickPickIncludes, showReferencePicker } from '../../../quickpicks/referencePicker';
import { showRepositoryPicker } from '../../../quickpicks/repositoryPicker';
+import { createFromDateDelta } from '../../../system/date';
import { gate } from '../../../system/decorators/gate';
import { debug, log } from '../../../system/decorators/log';
import type { Deferrable } from '../../../system/function';
@@ -136,6 +138,7 @@ import type {
DidGetCountParams,
DidGetRowHoverParams,
DidSearchParams,
+ DidSetFeaturePreviewTrialParams,
DoubleClickedParams,
GetMissingAvatarsParams,
GetMissingRefsMetadataParams,
@@ -206,6 +209,7 @@ import {
DidChangeWorkingTreeNotification,
DidFetchNotification,
DidSearchNotification,
+ DidSetFeaturePreviewTrialNotification,
DoubleClickedCommandType,
EnsureRowRequest,
GetCountsRequest,
@@ -294,6 +298,7 @@ export class GraphWebviewProvider implements WebviewProvider | null | undefined;
private _search: GitSearch | undefined;
@@ -681,11 +686,50 @@ export class GraphWebviewProvider implements WebviewProvider 0 && new Date(consumedDays[consumedDays.length - 1].expiresOn) > timestamp) {
+ return;
+ }
+
+ if (consumedDays.length >= proPreviewLengthInDays) {
+ void window.showInformationMessage(
+ `You have already used your ${proPreviewLengthInDays} days of previewing local Pro features.`,
+ );
+ return;
+ }
+
+ await this.container.storage.store(`plus:featurePreviewTrial:graph:consumedDays`, [
+ ...(consumedDays ?? []),
+ {
+ startedOn: timestamp.toISOString(),
+ expiresOn: createFromDateDelta(timestamp, { days: 1 }).toISOString(),
+ },
+ ]);
+
+ if (this.container.telemetry.enabled) {
+ this.container.telemetry.sendEvent(
+ 'subscription/action',
+ { action: `start-graph-preview-trial` },
+ { source: 'graph' },
+ );
+ }
+
+ void this.notifyDidSetFeaturePreviewTrial();
+ }
+
onWindowFocusChanged(focused: boolean): void {
this.isWindowFocused = focused;
}
@@ -1874,6 +1918,16 @@ export class GraphWebviewProvider implements WebviewProvider 0 &&
+ storedValue.length <= proPreviewLengthInDays &&
+ new Date(storedValue[storedValue.length - 1].expiresOn) > new Date(),
+ };
+ }
+
private updateIncludeOnlyRefs(
repoPath: string | undefined,
{ branchesVisibility, refs }: UpdateIncludedRefsParams,
diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts
index f0cfd83c1b5e4..d946565c6a04e 100644
--- a/src/plus/webviews/graph/protocol.ts
+++ b/src/plus/webviews/graph/protocol.ts
@@ -25,6 +25,7 @@ import type {
import type { Config, DateStyle, GraphBranchesVisibility } from '../../../config';
import type { SupportedCloudIntegrationIds } from '../../../constants.integrations';
import type { SearchQuery } from '../../../constants.search';
+import type { Sources } from '../../../constants.telemetry';
import type { RepositoryVisibility } from '../../../git/gitProvider';
import type { GitTrackingState } from '../../../git/models/branch';
import type { GitGraphRowType } from '../../../git/models/graph';
@@ -92,6 +93,7 @@ export const supportedRefMetadataTypes: GraphRefMetadataType[] = ['upstream', 'p
export interface State extends WebviewState {
windowFocused?: boolean;
+ webroot?: string;
repositories?: GraphRepository[];
selectedRepository?: string;
selectedRepositoryVisibility?: RepositoryVisibility;
@@ -129,6 +131,7 @@ export interface State extends WebviewState {
bottom: number;
};
theming?: { cssVariables: CssVariables; themeOpacityFactor: number };
+ graphPreviewTrial?: { consumedDays: { startedOn: string; expiresOn: string }[]; isActive: boolean };
}
export interface BranchState extends GitTrackingState {
@@ -380,6 +383,16 @@ export interface DidSearchParams {
export const SearchRequest = new IpcRequest(scope, 'search');
// NOTIFICATIONS
+export interface DidSetFeaturePreviewTrialParams {
+ feature: Sources;
+ consumedDays: { startedOn: string; expiresOn: string }[];
+ isActive: boolean;
+}
+
+export const DidSetFeaturePreviewTrialNotification = new IpcNotification(
+ scope,
+ 'featurePreviewTrial/didSet',
+);
export interface DidChangeRepoConnectionParams {
repositories?: GraphRepository[];
diff --git a/src/quickpicks/items/directive.ts b/src/quickpicks/items/directive.ts
index ba5b010f0dc62..773560aa02344 100644
--- a/src/quickpicks/items/directive.ts
+++ b/src/quickpicks/items/directive.ts
@@ -1,5 +1,6 @@
import type { QuickPickItem, ThemeIcon, Uri } from 'vscode';
import { proPreviewLengthInDays, proTrialLengthInDays } from '../../constants.subscription';
+import { pluralize } from '../../system/string';
export enum Directive {
Back,
@@ -64,8 +65,11 @@ export function createDirectiveQuickPickItem(
detail = `Continuing gives you ${proPreviewLengthInDays} days to preview this and other local Pro features`;
break;
case Directive.StartProTrial:
- label = 'Start Pro Trial';
- detail = `Start your free ${proTrialLengthInDays}-day Pro trial for full access to Pro features`;
+ label = 'Try GitLens Pro';
+ detail = `Get ${pluralize(
+ 'day',
+ proTrialLengthInDays,
+ )} of GitLens Pro trial for free - no credit card required.`;
break;
case Directive.RequiresVerification:
label = 'Resend Email';
@@ -74,9 +78,9 @@ export function createDirectiveQuickPickItem(
case Directive.RequiresPaidSubscription:
label = 'Upgrade to Pro';
if (detail != null) {
- description ??= ' \u2014\u00a0\u00a0 a paid plan is required to use this Pro feature';
+ description ??= ' \u2014\u00a0\u00a0 GitLens Pro is required to use this feature';
} else {
- detail = 'Upgrading to a paid plan is required to use this Pro feature';
+ detail = 'Upgrading to GitLens Pro is required to use this feature';
}
break;
}
diff --git a/src/webviews/apps/media/graph-commit-search.png b/src/webviews/apps/media/graph-commit-search.png
new file mode 100644
index 0000000000000..b2cf3e00fc1be
Binary files /dev/null and b/src/webviews/apps/media/graph-commit-search.png differ
diff --git a/src/webviews/apps/media/graph-minimap.png b/src/webviews/apps/media/graph-minimap.png
new file mode 100644
index 0000000000000..216f32274379c
Binary files /dev/null and b/src/webviews/apps/media/graph-minimap.png differ
diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx
index 9f95da0833fca..1ee78e65c799f 100644
--- a/src/webviews/apps/plus/graph/GraphWrapper.tsx
+++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx
@@ -64,6 +64,7 @@ import {
DidChangeWorkingTreeNotification,
DidFetchNotification,
DidSearchNotification,
+ DidSetFeaturePreviewTrialNotification,
} from '../../../../plus/webviews/graph/protocol';
import { createCommandLink } from '../../../../system/commands';
import { filterMap, first, groupByFilterMap, join } from '../../../../system/iterable';
@@ -282,6 +283,8 @@ export function GraphWrapper({
const [windowFocused, setWindowFocused] = useState(state.windowFocused);
const [allowed, setAllowed] = useState(state.allowed ?? false);
const [subscription, setSubscription] = useState(state.subscription);
+ const [graphPreviewTrial, setGraphPreviewTrial] = useState(state.graphPreviewTrial);
+
// search state
const searchEl = useRef(null);
const [searchQuery, setSearchQuery] = useState(undefined);
@@ -318,6 +321,10 @@ export function GraphWrapper({
setStyleProps(state.theming);
}
break;
+ case DidSetFeaturePreviewTrialNotification:
+ setGraphPreviewTrial(state.graphPreviewTrial);
+ setAllowed(state.graphPreviewTrial?.isActive || allowed);
+ break;
case DidChangeAvatarsNotification:
setAvatars(state.avatars);
break;
@@ -1530,10 +1537,17 @@ export function GraphWrapper({
diff --git a/src/webviews/apps/plus/graph/graph.tsx b/src/webviews/apps/plus/graph/graph.tsx
index f215c20b40da0..e10d3bee29ec1 100644
--- a/src/webviews/apps/plus/graph/graph.tsx
+++ b/src/webviews/apps/plus/graph/graph.tsx
@@ -38,6 +38,7 @@ import {
DidChangeWorkingTreeNotification,
DidFetchNotification,
DidSearchNotification,
+ DidSetFeaturePreviewTrialNotification,
DoubleClickedCommandType,
EnsureRowRequest,
GetMissingAvatarsCommand,
@@ -165,7 +166,11 @@ export class GraphApp extends App {
this.state.avatars = msg.params.avatars;
this.setState(this.state, DidChangeAvatarsNotification);
break;
-
+ case DidSetFeaturePreviewTrialNotification.is(msg):
+ this.state.graphPreviewTrial = { consumedDays: msg.params.consumedDays, isActive: msg.params.isActive };
+ this.state.allowed = msg.params.isActive || this.state.allowed;
+ this.setState(this.state, DidSetFeaturePreviewTrialNotification);
+ break;
case DidChangeBranchStateNotification.is(msg):
this.state.branchState = msg.params.branchState;
this.setState(this.state, DidChangeBranchStateNotification);
diff --git a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
index 0f995ad28fc07..1aeb0f1210a02 100644
--- a/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
+++ b/src/webviews/apps/plus/shared/components/feature-gate-plus-state.ts
@@ -1,8 +1,10 @@
+import type { TemplateResult } from 'lit';
import { css, html, LitElement, nothing } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
+import { urls } from '../../../../../constants';
import { Commands } from '../../../../../constants.commands';
-import { proTrialLengthInDays, SubscriptionState } from '../../../../../constants.subscription';
-import type { Source } from '../../../../../constants.telemetry';
+import { proPreviewLengthInDays, proTrialLengthInDays, SubscriptionState } from '../../../../../constants.subscription';
+import type { Source, Sources } from '../../../../../constants.telemetry';
import type { Promo } from '../../../../../plus/gk/account/promos';
import { getApplicablePromo } from '../../../../../plus/gk/account/promos';
import { pluralize } from '../../../../../system/string';
@@ -69,6 +71,14 @@ export class GlFeatureGatePlusState extends LitElement {
@query('gl-button')
private readonly button!: GlButton;
+ @property({ type: Object })
+ featureInPreviewTrial?: {
+ [key in Sources]?: { consumedDays: { startedOn: string; expiresOn: string }[]; isActive: boolean };
+ };
+
+ @property({ type: String })
+ featurePreviewTrialCommandLink?: string;
+
@property({ type: String })
appearance?: 'alert' | 'welcome';
@@ -81,6 +91,9 @@ export class GlFeatureGatePlusState extends LitElement {
@property({ attribute: false, type: Number })
state?: SubscriptionState;
+ @property({ type: String })
+ webroot?: string;
+
protected override firstUpdated() {
if (this.appearance === 'alert') {
queueMicrotask(() => this.button.focus());
@@ -96,6 +109,11 @@ export class GlFeatureGatePlusState extends LitElement {
this.hidden = false;
const appearance = (this.appearance ?? 'alert') === 'alert' ? 'alert' : nothing;
const promo = this.state ? getApplicablePromo(this.state, 'gate') : undefined;
+ let consumedDaysCount = 0;
+ const feature = this.source?.source;
+ if (feature) {
+ consumedDaysCount = this.featureInPreviewTrial?.[feature]?.consumedDays?.length ?? 0;
+ }
switch (this.state) {
case SubscriptionState.VerificationRequired:
@@ -118,52 +136,42 @@ export class GlFeatureGatePlusState extends LitElement {
`;
case SubscriptionState.Community:
- return html`
- Continue
-
- Continuing gives you 3 days to preview
- ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} and other ` : ''}local
- Pro features.
- ${appearance !== 'alert' ? html`
` : ''} For full access to Pro features
- start your free ${proTrialLengthInDays}-day Pro trial
- or
- sign in.
-
- `;
-
case SubscriptionState.ProPreviewExpired:
- return html`
+ if (
+ this.state === SubscriptionState.Community &&
+ feature &&
+ this.featureInPreviewTrial?.[feature] &&
+ proPreviewLengthInDays - consumedDaysCount > 0
+ ) {
+ return html`
+ ${this.getFeaturePreviewModalFor(feature, proPreviewLengthInDays - consumedDaysCount)}
+ `;
+ }
+
+ return html`
+ Use on privately-hosted repos requires
+ GitLens Pro.
+
Start Pro Trial Try GitLens Pro
- Start your free ${proTrialLengthInDays}-day Pro trial to try
- ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} and other ` : ''}Pro
- features, or
+ Get ${proTrialLengthInDays} days of GitLens Pro for free - no credit card required. Or
sign in.
-
- `;
+
`;
case SubscriptionState.ProTrialExpired:
- return html`
+ Use on privately-hosted repos requires GitLens Pro.
+
+ Upgrade to Pro
- ${this.renderPromo(promo)}
-
- Your Pro trial has ended. Please upgrade for full access to
- ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} and other ` : ''}Pro
- features.
-
`;
+ ${this.renderPromo(promo)}`;
case SubscriptionState.ProTrialReactivationEligible:
return html`
@@ -186,6 +194,80 @@ export class GlFeatureGatePlusState extends LitElement {
private renderPromo(promo: Promo | undefined) {
return html``;
}
+
+ private getFeaturePreviewModalFor(feature: Sources, daysLeft: number) {
+ const appearance = (this.appearance ?? 'alert') === 'alert' ? 'alert' : nothing;
+ let partial: TemplateResult<1> | undefined;
+ switch (feature) {
+ case 'graph':
+ switch (daysLeft) {
+ case 2:
+ partial = html`Try Commit Search
+
+ Search for commits in your repo by author, commit message, SHA, file, change, or type.
+ Turn on the commit filter to show only commits that match your query.
+
+
+
+
`;
+ break;
+ case 1:
+ partial = html`
+ Try the Graph Minimap
+
+ Visualize the amount of changes to a repository over time, and inspect specific points
+ in the history to locate branches, stashes, tags and pull requests.
+
+
+
+
+ `;
+ break;
+ }
+ return html`
+ ${partial}
+ Continue
+
+ Continuing gives you ${pluralize('day', daysLeft)} to preview
+ ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} on` : ''}
+ privately-hosted repositories.
+ ${appearance !== 'alert' ? html`
` : ''} For full access to all GitLens Pro features,
+ start your free ${proTrialLengthInDays}-day Pro trial
+ - no credit card required. Or
+ sign in.
+
+ `;
+ default:
+ return html`
+ Continue
+
+ Continuing gives you ${pluralize('day', daysLeft)} to preview
+ ${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} on` : ''}
+ privately-hosted repositories.
+ ${appearance !== 'alert' ? html`
` : ''} For full access to all GitLens Pro features,
+ start your free ${proTrialLengthInDays}-day Pro trial
+ - no credit card required. Or
+ sign in.
+
+ `;
+ }
+ }
}
function generateCommandLink(command: Commands, source: Source | undefined) {
diff --git a/src/webviews/apps/plus/shared/components/home-account-content.ts b/src/webviews/apps/plus/shared/components/home-account-content.ts
index 43e14d99f2e28..fa916c4f971f8 100644
--- a/src/webviews/apps/plus/shared/components/home-account-content.ts
+++ b/src/webviews/apps/plus/shared/components/home-account-content.ts
@@ -361,11 +361,14 @@ export class GLHomeAccountContent extends LitElement {
case SubscriptionState.ProTrialExpired:
return html`
-
Your Pro trial has ended. You can now only use Pro features on publicly-hosted repos.
+
+ Thank you for trying GitLens Pro.
+ Continue leveraging Pro features and workflows on privately-hosted repos by upgrading today.
+
Upgrade to Pro
- ${this.renderPromo(promo)} ${this.renderIncludesDevEx()}
+ ${this.renderPromo(promo)}
`;
@@ -395,15 +398,16 @@ export class GLHomeAccountContent extends LitElement {
return html`
- Sign up for access to Pro features and the
- GitKraken DevEx platform, or
- sign in.
+ Unlock advanced workflows and professional developer features with
+ GitLens Pro.
- Sign Up
+ Try GitLens Pro
-
Signing up starts your free ${proTrialLengthInDays}-day Pro trial.
- ${this.renderIncludesDevEx()}
+
+ Get ${proTrialLengthInDays} days of GitLens Pro for free - no credit card required. Or
+ sign in.
+
`;
}
diff --git a/src/webviews/apps/plus/shared/components/vscode.css.ts b/src/webviews/apps/plus/shared/components/vscode.css.ts
index c06e6459adb31..b33ad6693330c 100644
--- a/src/webviews/apps/plus/shared/components/vscode.css.ts
+++ b/src/webviews/apps/plus/shared/components/vscode.css.ts
@@ -2,14 +2,18 @@ import { css } from 'lit';
export const linkStyles = css`
a {
- color: var(--link-foreground);
- text-decoration: var(--link-decoration-default, none);
+ border: 0;
+ color: var(--color-link-foreground);
+ font-weight: 400;
+ outline: none;
+ text-decoration: none;
}
- a:focus {
- outline-color: var(--focus-border);
+ a:not([href]):not([tabindex]):focus,
+ a:not([href]):not([tabindex]):hover {
+ color: inherit;
+ text-decoration: none;
}
- a:hover {
- color: var(--link-foreground-active);
- text-decoration: underline;
+ a:focus {
+ outline-color: var(--color-focus-border);
}
`;
diff --git a/src/webviews/apps/shared/components/feature-gate.ts b/src/webviews/apps/shared/components/feature-gate.ts
index 9bba2a5095de6..9f0f87e0e912a 100644
--- a/src/webviews/apps/shared/components/feature-gate.ts
+++ b/src/webviews/apps/shared/components/feature-gate.ts
@@ -1,7 +1,7 @@
import { css, html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import type { SubscriptionState } from '../../../../constants.subscription';
-import type { Source } from '../../../../constants.telemetry';
+import type { Source, Sources } from '../../../../constants.telemetry';
import { isSubscriptionStatePaidOrTrial } from '../../../../plus/gk/account/subscription';
import '../../plus/shared/components/feature-gate-plus-state';
@@ -90,6 +90,14 @@ export class GlFeatureGate extends LitElement {
}
`;
+ @property({ type: Object })
+ featureInPreviewTrial?: {
+ [key in Sources]?: { consumedDays: { startedOn: string; expiresOn: string }[]; isActive: boolean };
+ };
+
+ @property({ type: String })
+ featurePreviewTrialCommandLink?: string;
+
@property({ reflect: true })
appearance?: 'alert' | 'welcome';
@@ -105,6 +113,9 @@ export class GlFeatureGate extends LitElement {
@property({ type: Boolean })
visible?: boolean;
+ @property({ type: String })
+ webroot?: string;
+
override render() {
if (!this.visible || (this.state != null && isSubscriptionStatePaidOrTrial(this.state))) {
this.hidden = true;
@@ -117,15 +128,26 @@ export class GlFeatureGate extends LitElement {
: 'welcome';
this.hidden = false;
+
+ const featureInTrial = this.source?.source;
+ const featureInTrialInfo = featureInTrial ? this.featureInPreviewTrial?.[featureInTrial] : undefined;
+ const shouldHideFeature =
+ featureInTrial === 'graph' &&
+ featureInTrialInfo &&
+ featureInTrialInfo.consumedDays.length > 0 &&
+ featureInTrialInfo.consumedDays.length < 3;
return html`
`;