Browse Source

Ungates GitLens+ features for local & public repos

main
Eric Amodio 2 years ago
parent
commit
bca9be09e3
16 changed files with 164 additions and 206 deletions
  1. +19
    -67
      src/git/gitProviderService.ts
  2. +1
    -1
      src/git/remotes/github.ts
  3. +1
    -1
      src/git/remotes/gitlab.ts
  4. +5
    -5
      src/subscription.ts
  5. +2
    -2
      src/views/worktreesView.ts
  6. +2
    -2
      src/webviews/apps/home/home.html
  7. +4
    -4
      src/webviews/apps/home/home.ts
  8. +1
    -1
      src/webviews/apps/home/partials/state.free-preview-trial-expired.html
  9. +1
    -1
      src/webviews/apps/home/partials/state.free-preview-trial.html
  10. +10
    -4
      src/webviews/apps/plus/graph/GraphWrapper.tsx
  11. +1
    -1
      src/webviews/apps/plus/timeline/partials/state.free-preview-trial-expired.html
  12. +1
    -1
      src/webviews/apps/plus/timeline/timeline.html
  13. +2
    -2
      src/webviews/apps/plus/timeline/timeline.ts

+ 19
- 67
src/git/gitProviderService.ts View File

@ -16,20 +16,14 @@ import { ContextKeys, CoreGitConfiguration, GlyphChars, Schemes } from '../const
import type { Container } from '../container';
import { setContext } from '../context';
import { AccessDeniedError, ProviderNotFoundError } from '../errors';
import type { FeatureAccess, Features } from '../features';
import { PlusFeatures } from '../features';
import type { FeatureAccess, Features, PlusFeatures } from '../features';
import type { RemoteProvider } from '../git/remotes/remoteProvider';
import { Logger } from '../logger';
import type { SubscriptionChangeEvent } from '../plus/subscription/subscriptionService';
import type { RepoComparisonKey } from '../repositories';
import { asRepoComparisonKey, Repositories } from '../repositories';
import type { FreeSubscriptionPlans, RequiredSubscriptionPlans, Subscription } from '../subscription';
import {
getSubscriptionPlan,
getSubscriptionPlanPriority,
isSubscriptionPaidPlan,
SubscriptionPlanId,
} from '../subscription';
import type { RequiredSubscriptionPlans, Subscription } from '../subscription';
import { getSubscriptionPlanPriority, isSubscriptionPaidPlan, SubscriptionPlanId } from '../subscription';
import { groupByFilterMap, groupByMap } from '../system/array';
import { gate } from '../system/decorators/gate';
import { debug, getLogScope, log } from '../system/decorators/log';
@ -111,8 +105,6 @@ export const enum RepositoriesVisibility {
}
export class GitProviderService implements Disposable {
static readonly previewFeatures: Map<PlusFeatures | undefined, boolean> | undefined; // = new Map();
private readonly _onDidChangeProviders = new EventEmitter<GitProvidersChangeEvent>();
get onDidChangeProviders(): Event<GitProvidersChangeEvent> {
return this._onDidChangeProviders.event;
@ -526,40 +518,10 @@ export class GitProviderService implements Disposable {
cacheKey = path;
}
let accessPromise = this._accessCache.get(cacheKey);
if (accessPromise == null) {
accessPromise = this.accessCore(feature, repoPath);
this._accessCache.set(cacheKey, accessPromise);
}
const access = await accessPromise;
if (feature === PlusFeatures.Graph) {
if (access.visibility == null && repoPath != null) {
access.visibility = await this.visibility(repoPath);
}
if (
(access.visibility !== RepositoryVisibility.Private &&
access.subscription.current.plan.effective.id === SubscriptionPlanId.Free) ||
(access.visibility === RepositoryVisibility.Private && access.subscription.current.previewTrial == null)
) {
return {
allowed: !(
access.visibility === RepositoryVisibility.Private &&
access.subscription.current.previewTrial == null
),
subscription: {
current: {
...access.subscription.current,
plan: {
...access.subscription.current.plan,
effective: getSubscriptionPlan(SubscriptionPlanId.Pro, undefined),
},
},
},
visibility: access.visibility,
};
}
let access = this._accessCache.get(cacheKey);
if (access == null) {
access = this.accessCore(feature, repoPath);
this._accessCache.set(cacheKey, access);
}
return access;
@ -573,14 +535,13 @@ export class GitProviderService implements Disposable {
}
const plan = subscription.plan.effective.id;
if (isSubscriptionPaidPlan(plan) || GitProviderService.previewFeatures?.get(feature)) {
if (isSubscriptionPaidPlan(plan)) {
return { allowed: true, subscription: { current: subscription } };
}
function getRepoAccess(
this: GitProviderService,
repoPath: string | Uri,
plan: FreeSubscriptionPlans,
force: boolean = false,
): Promise<FeatureAccess> {
const { path: cacheKey } = this.getProvider(repoPath);
@ -588,26 +549,17 @@ export class GitProviderService implements Disposable {
let access = force ? undefined : this._accessCache.get(cacheKey);
if (access == null) {
access = this.visibility(repoPath).then(visibility => {
if (visibility !== RepositoryVisibility.Private) {
switch (plan) {
case SubscriptionPlanId.Free:
return {
allowed: false,
subscription: { current: subscription, required: SubscriptionPlanId.FreePlus },
visibility: visibility,
};
case SubscriptionPlanId.FreePlus:
return {
allowed: true,
subscription: { current: subscription },
visibility: visibility,
};
}
if (visibility === RepositoryVisibility.Private) {
return {
allowed: false,
subscription: { current: subscription, required: SubscriptionPlanId.Pro },
visibility: visibility,
};
}
return {
allowed: false,
subscription: { current: subscription, required: SubscriptionPlanId.Pro },
allowed: true,
subscription: { current: subscription },
visibility: visibility,
};
});
@ -625,7 +577,7 @@ export class GitProviderService implements Disposable {
}
if (repositories.length === 1) {
return getRepoAccess.call(this, repositories[0].path, plan);
return getRepoAccess.call(this, repositories[0].path);
}
let allowed = true;
@ -634,7 +586,7 @@ export class GitProviderService implements Disposable {
const maxPriority = getSubscriptionPlanPriority(SubscriptionPlanId.Pro);
for await (const result of fastestSettled(repositories.map(r => getRepoAccess.call(this, r.path, plan)))) {
for await (const result of fastestSettled(repositories.map(r => getRepoAccess.call(this, r.path)))) {
if (result.status !== 'fulfilled' || result.value.allowed) continue;
allowed = false;
@ -653,7 +605,7 @@ export class GitProviderService implements Disposable {
}
// Pass force = true to bypass the cache and avoid a promise loop (where we used the cached promise we just created to try to resolve itself 🤦)
return getRepoAccess.call(this, repoPath, plan, true);
return getRepoAccess.call(this, repoPath, true);
}
async ensureAccess(feature: PlusFeatures, repoPath?: string): Promise<void> {

+ 1
- 1
src/git/remotes/github.ts View File

@ -155,7 +155,7 @@ export class GitHubRemote extends RichRemoteProvider {
const startTrial = { title: 'Try GitLens+' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
const result = await window.showWarningMessage(
`${title}\n\nDo you want to try GitLens+ free for 3 days?`,
`${title}\n\nDo you want to try GitLens+ for free for 3 days?`,
{ modal: true },
startTrial,
cancel,

+ 1
- 1
src/git/remotes/gitlab.ts View File

@ -177,7 +177,7 @@ export class GitLabRemote extends RichRemoteProvider {
const startTrial = { title: 'Try GitLens+' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
const result = await window.showWarningMessage(
`${title}\n\nDo you want to try GitLens+ free for 3 days?`,
`${title}\n\nDo you want to try GitLens+ for free for 3 days?`,
{ modal: true },
startTrial,
cancel,

+ 5
- 5
src/subscription.ts View File

@ -49,9 +49,9 @@ export const enum SubscriptionState {
/** Indicates a Free user who hasn't yet started the preview trial */
Free = 0,
/** Indicates a Free user who is in preview trial */
FreeInPreview,
FreeInPreviewTrial,
/** Indicates a Free user who's preview has expired trial */
FreePreviewExpired,
FreePreviewTrialExpired,
/** Indicates a Free+ user with a completed trial */
FreePlusInTrial,
/** Indicates a Free+ user who's trial has expired */
@ -72,7 +72,7 @@ export function computeSubscriptionState(subscription: Optional
if (actual.id === effective.id) {
switch (effective.id) {
case SubscriptionPlanId.Free:
return preview == null ? SubscriptionState.Free : SubscriptionState.FreePreviewExpired;
return preview == null ? SubscriptionState.Free : SubscriptionState.FreePreviewTrialExpired;
case SubscriptionPlanId.FreePlus:
return SubscriptionState.FreePlusTrialExpired;
@ -86,14 +86,14 @@ export function computeSubscriptionState(subscription: Optional
switch (effective.id) {
case SubscriptionPlanId.Free:
return preview == null ? SubscriptionState.Free : SubscriptionState.FreeInPreview;
return preview == null ? SubscriptionState.Free : SubscriptionState.FreeInPreviewTrial;
case SubscriptionPlanId.FreePlus:
return SubscriptionState.FreePlusTrialExpired;
case SubscriptionPlanId.Pro:
return actual.id === SubscriptionPlanId.Free
? SubscriptionState.FreeInPreview
? SubscriptionState.FreeInPreviewTrial
: SubscriptionState.FreePlusInTrial;
case SubscriptionPlanId.Teams:

+ 2
- 2
src/views/worktreesView.ts View File

@ -147,11 +147,11 @@ export class WorktreesView extends ViewBase
switch (subscription.state) {
case SubscriptionState.Free:
case SubscriptionState.FreePreviewExpired:
case SubscriptionState.FreePreviewTrialExpired:
case SubscriptionState.VerificationRequired:
this.description = '✨ GitLens+ feature';
break;
case SubscriptionState.FreeInPreview: {
case SubscriptionState.FreeInPreviewTrial: {
const days = getSubscriptionTimeRemaining(subscription, 'days')!;
this.description = `✨⏳ ${pluralize('more day', days)} to try worktrees on public and private repos`;
break;

+ 2
- 2
src/webviews/apps/home/home.html View File

@ -37,8 +37,8 @@
<%= require('html-loader?{"esModule":false}!./partials/views.html') %>
<%= require('html-loader?{"esModule":false}!./partials/links.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.free.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.free-preview.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.free-preview-expired.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.free-preview-trial.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.free-preview-trial-expired.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.plus-trial.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.plus-trial-expired.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.paid.html') %>

+ 4
- 4
src/webviews/apps/home/home.ts View File

@ -93,13 +93,13 @@ export class HomeApp extends App {
DOM.insertTemplate('state:free', this.$slots[index++]);
break;
case SubscriptionState.FreeInPreview: {
case SubscriptionState.FreeInPreviewTrial: {
if (viewsVisible) {
DOM.insertTemplate('views', this.$slots[index++]);
}
const remaining = getSubscriptionTimeRemaining(subscription, 'days') ?? 0;
DOM.insertTemplate('state:free-preview', this.$slots[index++], {
DOM.insertTemplate('state:free-preview-trial', this.$slots[index++], {
bindings: {
previewDays: `${
remaining < 1
@ -113,12 +113,12 @@ export class HomeApp extends App {
break;
}
case SubscriptionState.FreePreviewExpired:
case SubscriptionState.FreePreviewTrialExpired:
if (viewsVisible) {
DOM.insertTemplate('views', this.$slots[index++]);
}
DOM.insertTemplate('state:free-preview-expired', this.$slots[index++]);
DOM.insertTemplate('state:free-preview-trial-expired', this.$slots[index++]);
break;
case SubscriptionState.FreePlusInTrial: {

src/webviews/apps/home/partials/state.free-preview-expired.html → src/webviews/apps/home/partials/state.free-preview-trial-expired.html View File

@ -1,4 +1,4 @@
<template id="state:free-preview-expired">
<template id="state:free-preview-trial-expired">
<h3>Continue using GitLens+ Features</h3>
<p>
Your trial of

src/webviews/apps/home/partials/state.free-preview.html → src/webviews/apps/home/partials/state.free-preview-trial.html View File

@ -1,4 +1,4 @@
<template id="state:free-preview">
<template id="state:free-preview-trial">
<h3>Trying GitLens+ Features</h3>
<p>
You have <span data-bind="previewDays">3 days</span> left in your

+ 10
- 4
src/webviews/apps/plus/graph/GraphWrapper.tsx View File

@ -259,7 +259,9 @@ export function GraphWrapper({
const renderTrialDays = () => {
if (
!subscriptionSnapshot ||
![SubscriptionState.FreeInPreview, SubscriptionState.FreePlusInTrial].includes(subscriptionSnapshot.state)
![SubscriptionState.FreeInPreviewTrial, SubscriptionState.FreePlusInTrial].includes(
subscriptionSnapshot.state,
)
) {
return;
}
@ -280,7 +282,11 @@ export function GraphWrapper({
let content;
let actions;
let days = 0;
if ([SubscriptionState.FreeInPreview, SubscriptionState.FreePlusInTrial].includes(subscriptionSnapshot.state)) {
if (
[SubscriptionState.FreeInPreviewTrial, SubscriptionState.FreePlusInTrial].includes(
subscriptionSnapshot.state,
)
) {
days = getSubscriptionTimeRemaining(subscriptionSnapshot, 'days') ?? 0;
}
@ -288,7 +294,7 @@ export function GraphWrapper({
case SubscriptionState.Free:
case SubscriptionState.Paid:
return;
case SubscriptionState.FreeInPreview:
case SubscriptionState.FreeInPreviewTrial:
case SubscriptionState.FreePlusInTrial:
icon = 'calendar';
modifier = 'neutral';
@ -306,7 +312,7 @@ export function GraphWrapper({
</>
);
break;
case SubscriptionState.FreePreviewExpired:
case SubscriptionState.FreePreviewTrialExpired:
icon = 'warning';
modifier = 'warning';
content = (

src/webviews/apps/plus/timeline/partials/state.free-preview-expired.html → src/webviews/apps/plus/timeline/partials/state.free-preview-trial-expired.html View File

@ -1,4 +1,4 @@
<template id="state:free-preview-expired">
<template id="state:free-preview-trial-expired">
<section>
<p>
Sign in to use the Visual File History and other GitLens+ features on public repos and get an additional

+ 1
- 1
src/webviews/apps/plus/timeline/timeline.html View File

@ -66,7 +66,7 @@
<!-- prettier-ignore -->
<%= require('html-loader?{"esModule":false}!./partials/state.free.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.free-preview-expired.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.free-preview-trial-expired.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.plus-trial-expired.html') %>
<%= require('html-loader?{"esModule":false}!./partials/state.verify-email.html') %>
</html>

+ 2
- 2
src/webviews/apps/plus/timeline/timeline.ts View File

@ -108,8 +108,8 @@ export class TimelineApp extends App {
case SubscriptionState.Free:
DOM.insertTemplate('state:free', $slot, options);
break;
case SubscriptionState.FreePreviewExpired:
DOM.insertTemplate('state:free-preview-expired', $slot, options);
case SubscriptionState.FreePreviewTrialExpired:
DOM.insertTemplate('state:free-preview-trial-expired', $slot, options);
break;
case SubscriptionState.FreePlusTrialExpired:
DOM.insertTemplate('state:plus-trial-expired', $slot, options);

Loading…
Cancel
Save