mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
chore(editor): Add trial upgrade button to main sidebar experiment (no-changelog) (#22151)
Co-authored-by: Giulio Andreini <g.andreini@gmail.com> Co-authored-by: Romeo Balta <7095569+romeobalta@users.noreply.github.com>
This commit is contained in:
co-authored by
Giulio Andreini
Romeo Balta
parent
bc557ec9e9
commit
8364430471
@@ -49,11 +49,11 @@ describe('OwnerController', () => {
|
||||
});
|
||||
|
||||
it('should call dismissBanner with the correct banner name', async () => {
|
||||
const payload = mock<DismissBannerRequestDto>({ banner: 'TRIAL' });
|
||||
const payload = mock<DismissBannerRequestDto>({ banner: 'TRIAL_OVER' });
|
||||
|
||||
await controller.dismissBanner(mock(), mock(), payload);
|
||||
|
||||
expect(bannerService.dismissBanner).toHaveBeenCalledWith('TRIAL');
|
||||
expect(bannerService.dismissBanner).toHaveBeenCalledWith('TRIAL_OVER');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { BannerName } from '@n8n/api-types';
|
||||
import type { SettingsRepository } from '@n8n/db';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { ErrorReporter } from 'n8n-core';
|
||||
@@ -17,15 +16,14 @@ describe('BannerService', () => {
|
||||
|
||||
describe('dismissBanner', () => {
|
||||
const key = 'ui.banners.dismissed';
|
||||
const bannerName: BannerName = 'TRIAL';
|
||||
|
||||
it('should save the banner name to settings if no banners are dismissed yet', async () => {
|
||||
settingsRepo.findOneBy.mockResolvedValue(null);
|
||||
|
||||
await bannerService.dismissBanner(bannerName);
|
||||
await bannerService.dismissBanner('TRIAL');
|
||||
|
||||
expect(settingsRepo.save).toHaveBeenCalledWith(
|
||||
{ key, value: JSON.stringify([bannerName]), loadOnStartup: true },
|
||||
{ key, value: JSON.stringify(['TRIAL']), loadOnStartup: true },
|
||||
{ transaction: false },
|
||||
);
|
||||
});
|
||||
@@ -38,11 +36,27 @@ describe('BannerService', () => {
|
||||
loadOnStartup: false,
|
||||
});
|
||||
|
||||
await bannerService.dismissBanner(bannerName);
|
||||
await bannerService.dismissBanner('TRIAL');
|
||||
|
||||
expect(settingsRepo.update).toHaveBeenCalledWith(
|
||||
{ key },
|
||||
{ value: JSON.stringify([bannerName, 'TRIAL_OVER']), loadOnStartup: true },
|
||||
{ value: JSON.stringify(['TRIAL', 'TRIAL_OVER']), loadOnStartup: true },
|
||||
);
|
||||
});
|
||||
|
||||
it('should not create duplicate entries if already dismissed', async () => {
|
||||
const dismissedBanners = ['TRIAL', 'TRIAL_OVER'];
|
||||
settingsRepo.findOneBy.mockResolvedValue({
|
||||
key,
|
||||
value: JSON.stringify(dismissedBanners),
|
||||
loadOnStartup: false,
|
||||
});
|
||||
|
||||
await bannerService.dismissBanner('TRIAL');
|
||||
|
||||
expect(settingsRepo.update).toHaveBeenCalledWith(
|
||||
{ key },
|
||||
{ value: JSON.stringify(['TRIAL', 'TRIAL_OVER']), loadOnStartup: true },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -51,7 +65,7 @@ describe('BannerService', () => {
|
||||
settingsRepo.findOneBy.mockResolvedValue(null);
|
||||
settingsRepo.save.mockRejectedValue(error);
|
||||
|
||||
await bannerService.dismissBanner(bannerName);
|
||||
await bannerService.dismissBanner('TRIAL');
|
||||
|
||||
expect(errorReporter.error).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
"generic.unsavedWork.confirmMessage.message": "If you don't save, you will lose your changes.",
|
||||
"generic.unsavedWork.confirmMessage.confirmButtonText": "Save",
|
||||
"generic.unsavedWork.confirmMessage.cancelButtonText": "Leave without saving",
|
||||
"generic.trial.message": "1 day left | {count} days left",
|
||||
"generic.trial.tooltip": "1 day left in your trial. Upgrade to keep using n8n. | {count} days left in your trial. Upgrade to keep using n8n.",
|
||||
"generic.upgrade": "Upgrade",
|
||||
"generic.upgradeNow": "Upgrade now",
|
||||
"generic.update": "Update",
|
||||
|
||||
@@ -847,11 +847,11 @@ export type CloudUpdateLinkSourceType =
|
||||
| 'evaluations'
|
||||
| 'ai-builder-sidebar'
|
||||
| 'ai-builder-canvas'
|
||||
| 'custom-roles';
|
||||
| 'custom-roles'
|
||||
| 'main-sidebar';
|
||||
|
||||
export type UTMCampaign =
|
||||
| 'upgrade-custom-data-filter'
|
||||
| 'upgrade-canvas-nav'
|
||||
| 'upgrade-concurrency'
|
||||
| 'upgrade-workflow-sharing'
|
||||
| 'upgrade-credentials-sharing'
|
||||
@@ -874,7 +874,9 @@ export type UTMCampaign =
|
||||
| 'upgrade-insights'
|
||||
| 'upgrade-evaluations'
|
||||
| 'upgrade-builder'
|
||||
| 'upgrade-custom-roles';
|
||||
| 'upgrade-custom-roles'
|
||||
| 'upgrade-canvas-nav'
|
||||
| 'upgrade-main-sidebar';
|
||||
|
||||
export type AddedNode = {
|
||||
type: string;
|
||||
|
||||
@@ -57,6 +57,7 @@ import { useKeybindings } from '@/app/composables/useKeybindings';
|
||||
import { useCalloutHelpers } from '@/app/composables/useCalloutHelpers';
|
||||
import ProjectNavigation from '@/features/collaboration/projects/components/ProjectNavigation.vue';
|
||||
import MainSidebarSourceControl from './MainSidebarSourceControl.vue';
|
||||
import MainSidebarTrialUpgrade from './MainSidebarTrialUpgrade.vue';
|
||||
import MainSidebarUserArea from '@/app/components/MainSidebarUserArea.vue';
|
||||
|
||||
const becomeTemplateCreatorStore = useBecomeTemplateCreatorStore();
|
||||
@@ -580,6 +581,7 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
|
||||
</N8nScrollArea>
|
||||
|
||||
<MainSidebarSourceControl :is-collapsed="isCollapsed" />
|
||||
<MainSidebarTrialUpgrade />
|
||||
<MainSidebarUserArea
|
||||
v-if="showUserArea"
|
||||
ref="user"
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { N8nButton, N8nTooltip } from '@n8n/design-system';
|
||||
import { computed } from 'vue';
|
||||
import { useCloudPlanStore } from '@/app/stores/cloudPlan.store';
|
||||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { i18n as locale } from '@n8n/i18n';
|
||||
import { usePageRedirectionHelper } from '@/app/composables/usePageRedirectionHelper';
|
||||
|
||||
const cloudPlanStore = useCloudPlanStore();
|
||||
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||
const uiStore = useUIStore();
|
||||
|
||||
const isCollapsed = computed(() => uiStore.sidebarMenuCollapsed);
|
||||
|
||||
const isVisible = computed(() => {
|
||||
return cloudPlanStore.userIsTrialing && cloudPlanStore.isTrialUpgradeOnSidebar;
|
||||
});
|
||||
|
||||
const trialDaysLeft = computed(() => -1 * cloudPlanStore.trialDaysLeft);
|
||||
|
||||
const trialMessage = computed(() => {
|
||||
return locale.baseText('generic.trial.message', {
|
||||
adjustToNumber: trialDaysLeft.value,
|
||||
interpolate: { count: String(trialDaysLeft.value) },
|
||||
});
|
||||
});
|
||||
|
||||
const tooltipContent = computed(() => {
|
||||
return locale.baseText('generic.trial.tooltip', {
|
||||
adjustToNumber: trialDaysLeft.value,
|
||||
interpolate: { count: String(trialDaysLeft.value) },
|
||||
});
|
||||
});
|
||||
|
||||
const onUpgradeClick = () => {
|
||||
void pageRedirectionHelper.goToUpgrade('main-sidebar', 'upgrade-main-sidebar', 'redirect');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="isVisible"
|
||||
:class="[$style.mainSidebarTrialUpgrade, { [$style.collapsed]: isCollapsed }]"
|
||||
data-test-id="main-sidebar-trial-upgrade"
|
||||
>
|
||||
<N8nTooltip :placement="isCollapsed ? 'right' : 'top'">
|
||||
<template #content>
|
||||
{{ tooltipContent }}
|
||||
</template>
|
||||
<N8nButton
|
||||
type="success"
|
||||
size="mini"
|
||||
icon="zap"
|
||||
:label="isCollapsed ? '' : locale.baseText('generic.upgrade')"
|
||||
:square="isCollapsed"
|
||||
data-test-id="trial-upgrade-button"
|
||||
@click="onUpgradeClick"
|
||||
/>
|
||||
</N8nTooltip>
|
||||
|
||||
<div v-if="!isCollapsed" :class="$style.mainSidebarTrialMessage">
|
||||
{{ trialMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.mainSidebarTrialUpgrade {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing--sm) var(--spacing--xs);
|
||||
border-top: var(--border-width) var(--border-style) var(--color--foreground);
|
||||
gap: var(--spacing--2xs);
|
||||
|
||||
&.collapsed {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mainSidebarTrialMessage {
|
||||
font-size: var(--font-size--2xs);
|
||||
color: var(--color--text);
|
||||
line-height: var(--line-height--sm);
|
||||
}
|
||||
</style>
|
||||
@@ -55,6 +55,12 @@ export const TEMPLATE_RECO_V2 = {
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const UPGRADE_PLAN_CTA_EXPERIMENT = {
|
||||
name: '054_upgrade_plan_cta',
|
||||
control: 'control',
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const TEMPLATES_DATA_QUALITY_EXPERIMENT = {
|
||||
name: '046_templates_data_quality',
|
||||
control: 'control',
|
||||
@@ -114,6 +120,7 @@ export const EXPERIMENTS_TO_TRACK = [
|
||||
TEMPLATE_RECO_V2.name,
|
||||
TEMPLATES_DATA_QUALITY_EXPERIMENT.name,
|
||||
READY_TO_RUN_V2_PART2_EXPERIMENT.name,
|
||||
UPGRADE_PLAN_CTA_EXPERIMENT.name,
|
||||
AI_BUILDER_TEMPLATE_EXAMPLES_EXPERIMENT.name,
|
||||
AI_BUILDER_MULTI_AGENT_EXPERIMENT.name,
|
||||
TIME_SAVED_NODE_EXPERIMENT.name,
|
||||
|
||||
@@ -20,6 +20,7 @@ import merge from 'lodash/merge';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { telemetry } from '@/app/plugins/telemetry';
|
||||
import { usePostHog } from './stores/posthog.store';
|
||||
|
||||
const showMessage = vi.fn();
|
||||
const showToast = vi.fn();
|
||||
@@ -47,6 +48,7 @@ describe('Init', () => {
|
||||
let ssoStore: ReturnType<typeof mockedStore<typeof useSSOStore>>;
|
||||
let rootStore: ReturnType<typeof mockedStore<typeof useRootStore>>;
|
||||
let bannersStore: ReturnType<typeof mockedStore<typeof useBannersStore>>;
|
||||
let posthogStore: ReturnType<typeof mockedStore<typeof usePostHog>>;
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(
|
||||
@@ -67,6 +69,7 @@ describe('Init', () => {
|
||||
ssoStore = mockedStore(useSSOStore);
|
||||
rootStore = mockedStore(useRootStore);
|
||||
bannersStore = mockedStore(useBannersStore);
|
||||
posthogStore = mockedStore(usePostHog);
|
||||
});
|
||||
|
||||
describe('initializeCore()', () => {
|
||||
@@ -291,7 +294,7 @@ describe('Init', () => {
|
||||
expect(bannersStore.pushBannerToStack).toHaveBeenCalledWith('TRIAL_OVER');
|
||||
});
|
||||
|
||||
it('should push TRIAL banner if trial is active', async () => {
|
||||
it('should push TRIAL banner if trial is active and does not have feature flag set', async () => {
|
||||
settingsStore.settings.deployment.type = 'cloud';
|
||||
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||
usersStore.currentUserId = '123';
|
||||
@@ -307,6 +310,42 @@ describe('Init', () => {
|
||||
expect(bannersStore.pushBannerToStack).toHaveBeenCalledWith('TRIAL');
|
||||
});
|
||||
|
||||
it('should push TRIAL banner if trial is active and has feature flag control', async () => {
|
||||
settingsStore.settings.deployment.type = 'cloud';
|
||||
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||
usersStore.currentUserId = '123';
|
||||
|
||||
cloudPlanStore.userIsTrialing = true;
|
||||
cloudPlanStore.trialExpired = false;
|
||||
|
||||
const posthogStoreSpy = vi.spyOn(posthogStore, 'getVariant').mockReturnValueOnce('control');
|
||||
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||
|
||||
await initializeAuthenticatedFeatures(false);
|
||||
|
||||
expect(posthogStoreSpy).toHaveBeenCalled();
|
||||
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||
expect(bannersStore.pushBannerToStack).toHaveBeenCalledWith('TRIAL');
|
||||
});
|
||||
|
||||
it('should not push TRIAL banner if trial is active and has feature flag variant', async () => {
|
||||
settingsStore.settings.deployment.type = 'cloud';
|
||||
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||
usersStore.currentUserId = '123';
|
||||
|
||||
cloudPlanStore.userIsTrialing = true;
|
||||
cloudPlanStore.trialExpired = false;
|
||||
|
||||
const posthogStoreSpy = vi.spyOn(posthogStore, 'getVariant').mockReturnValueOnce('variant');
|
||||
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||
|
||||
await initializeAuthenticatedFeatures(false);
|
||||
|
||||
expect(posthogStoreSpy).toHaveBeenCalled();
|
||||
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||
expect(bannersStore.pushBannerToStack).not.toHaveBeenCalledWith('TRIAL');
|
||||
});
|
||||
|
||||
it('should push EMAIL_CONFIRMATION banner if user cloud info is not confirmed', async () => {
|
||||
settingsStore.settings.deployment.type = 'cloud';
|
||||
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||
|
||||
@@ -163,7 +163,9 @@ export async function initializeAuthenticatedFeatures(
|
||||
if (cloudPlanStore.trialExpired) {
|
||||
bannersStore.pushBannerToStack('TRIAL_OVER');
|
||||
} else {
|
||||
bannersStore.pushBannerToStack('TRIAL');
|
||||
if (!cloudPlanStore.isTrialUpgradeOnSidebar) {
|
||||
bannersStore.pushBannerToStack('TRIAL');
|
||||
}
|
||||
}
|
||||
} else if (cloudPlanStore.currentUserCloudInfo?.confirmed === false) {
|
||||
bannersStore.pushBannerToStack('EMAIL_CONFIRMATION');
|
||||
|
||||
@@ -10,10 +10,11 @@ import {
|
||||
getCurrentUsage,
|
||||
} from '@n8n/rest-api-client/api/cloudPlans';
|
||||
import { DateTime } from 'luxon';
|
||||
import { CLOUD_TRIAL_CHECK_INTERVAL } from '@/app/constants';
|
||||
import { CLOUD_TRIAL_CHECK_INTERVAL, UPGRADE_PLAN_CTA_EXPERIMENT } from '@/app/constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { hasPermission } from '@/app/utils/rbac/permissions';
|
||||
import * as cloudApi from '@n8n/rest-api-client/api/cloudPlans';
|
||||
import { usePostHog } from './posthog.store';
|
||||
|
||||
const DEFAULT_STATE: CloudPlanState = {
|
||||
initialized: false,
|
||||
@@ -27,6 +28,7 @@ const DYNAMIC_TRIAL_BANNER_DISMISSED_KEY = 'n8n-dynamic-trial-banner-dismissed';
|
||||
export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const posthogStore = usePostHog();
|
||||
|
||||
const state = reactive<CloudPlanState>(DEFAULT_STATE);
|
||||
const currentUserCloudInfo = ref<Cloud.UserAccount | null>(null);
|
||||
@@ -114,6 +116,13 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
||||
return await getAdminPanelLoginCode(rootStore.restApiContext);
|
||||
};
|
||||
|
||||
const isTrialUpgradeOnSidebar = computed(() => {
|
||||
return (
|
||||
posthogStore.getVariant(UPGRADE_PLAN_CTA_EXPERIMENT.name) ===
|
||||
UPGRADE_PLAN_CTA_EXPERIMENT.variant
|
||||
);
|
||||
});
|
||||
|
||||
const getOwnerCurrentPlan = async () => {
|
||||
if (!hasCloudPlan.value) throw new Error('User does not have a cloud plan');
|
||||
state.loadingPlan = true;
|
||||
@@ -242,5 +251,6 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
||||
dynamicTrialBannerText,
|
||||
shouldShowDynamicTrialBanner,
|
||||
dismissDynamicTrialBanner,
|
||||
isTrialUpgradeOnSidebar,
|
||||
};
|
||||
});
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ function onUpdatePlanClick() {
|
||||
:bold="true"
|
||||
>{{ trialBannerText }}</N8nBadge
|
||||
>
|
||||
<N8nButton type="success" icon="gem" size="small" @click="onUpdatePlanClick">{{
|
||||
<N8nButton type="success" icon="zap" size="small" @click="onUpdatePlanClick">{{
|
||||
locale.baseText('generic.upgradeNow')
|
||||
}}</N8nButton>
|
||||
</div>
|
||||
|
||||
+6
-3
@@ -4,8 +4,11 @@ import { usePageRedirectionHelper } from '@/app/composables/usePageRedirectionHe
|
||||
import { i18n as locale } from '@n8n/i18n';
|
||||
|
||||
import { N8nButton } from '@n8n/design-system';
|
||||
function onUpdatePlanClick() {
|
||||
void usePageRedirectionHelper().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
||||
|
||||
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||
|
||||
function onUpgradePlanClick() {
|
||||
void pageRedirectionHelper.goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,7 +18,7 @@ function onUpdatePlanClick() {
|
||||
<span>{{ locale.baseText('banners.trialOver.message') }}</span>
|
||||
</template>
|
||||
<template #trailingContent>
|
||||
<N8nButton type="success" icon="gem" size="small" @click="onUpdatePlanClick">{{
|
||||
<N8nButton type="success" icon="zap" size="small" @click="onUpgradePlanClick">{{
|
||||
locale.baseText('generic.upgradeNow')
|
||||
}}</N8nButton>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"id": 200,
|
||||
"planId": 1,
|
||||
"pruneExecutionsInterval": 168,
|
||||
"monthlyExecutionsLimit": 1000,
|
||||
"activeWorkflowsLimit": 20,
|
||||
"credentialsLimit": 100,
|
||||
"supportTier": "community",
|
||||
"displayName": "Starter",
|
||||
"enabledFeatures": ["userManagement", "advancedExecutionFilters", "sharing"],
|
||||
"licenseFeatures": {
|
||||
"feat:sharing": true,
|
||||
"feat:advancedExecutionFilters": true,
|
||||
"quota:users": -1,
|
||||
"quota:maxVariables": -1,
|
||||
"feat:variables": true
|
||||
},
|
||||
"metadata": {
|
||||
"version": "v1",
|
||||
"group": "starter",
|
||||
"slug": "starter"
|
||||
},
|
||||
"expirationDate": "2023-08-30T15:47:27.611Z"
|
||||
}
|
||||
@@ -98,6 +98,10 @@ export class SidebarPage {
|
||||
return this.page.getByTestId('banners-TRIAL');
|
||||
}
|
||||
|
||||
getMainSidebarTrialUpgrade(): Locator {
|
||||
return this.page.getByTestId('main-sidebar-trial-upgrade');
|
||||
}
|
||||
|
||||
getTemplatesLink(): Locator {
|
||||
return this.page.getByTestId('templates').locator('a');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { test, expect } from '../../../fixtures/base';
|
||||
import nonTrialPlanData from '../../../fixtures/plan-data-non-trial.json';
|
||||
import basePlanData from '../../../fixtures/plan-data-trial.json';
|
||||
import type { n8nPage } from '../../../pages/n8nPage';
|
||||
import type { TestRequirements } from '../../../Types';
|
||||
@@ -6,7 +7,7 @@ import type { TestRequirements } from '../../../Types';
|
||||
const fiveDaysFromNow = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000);
|
||||
const planData = { ...basePlanData, expirationDate: fiveDaysFromNow.toJSON() };
|
||||
|
||||
const cloudTrialRequirements = {
|
||||
const cloudTrialRequirements: TestRequirements = {
|
||||
config: {
|
||||
settings: {
|
||||
deployment: { type: 'cloud' },
|
||||
@@ -32,6 +33,32 @@ const cloudTrialRequirements = {
|
||||
},
|
||||
};
|
||||
|
||||
const cloudNonTrialRequirements: TestRequirements = {
|
||||
config: {
|
||||
settings: {
|
||||
deployment: { type: 'cloud' },
|
||||
n8nMetadata: { userId: '1' },
|
||||
aiCredits: {
|
||||
enabled: true,
|
||||
credits: 100,
|
||||
},
|
||||
banners: {
|
||||
dismissed: ['V1'], // Prevent V1 banner interference
|
||||
},
|
||||
},
|
||||
},
|
||||
intercepts: {
|
||||
'cloud-plan': {
|
||||
url: '**/rest/admin/cloud-plan',
|
||||
response: { ...nonTrialPlanData, expirationDate: fiveDaysFromNow.toJSON() },
|
||||
},
|
||||
'cloud-user': {
|
||||
url: '**/rest/cloud/proxy/user/me',
|
||||
response: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const setupCloudTest = async (
|
||||
n8n: n8nPage,
|
||||
setupRequirements: (requirements: TestRequirements) => Promise<void>,
|
||||
@@ -48,14 +75,72 @@ const createProjectAndNavigate = async (n8n: n8nPage) => {
|
||||
};
|
||||
|
||||
test.describe('Cloud @db:reset @auth:owner', () => {
|
||||
test.describe('Trial Banner', () => {
|
||||
test('should render trial banner for opt-in cloud user', async ({ n8n, setupRequirements }) => {
|
||||
test.describe('Trial Upgrade', () => {
|
||||
test('should render trial banner for opt-in cloud user and has feature flag control', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupCloudTest(n8n, setupRequirements, cloudTrialRequirements);
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.sideBar.expand();
|
||||
|
||||
await n8n.page.evaluate(() => {
|
||||
(
|
||||
window as unknown as { featureFlags: { override: (name: string, value: string) => void } }
|
||||
).featureFlags?.override('054_upgrade_plan_cta', 'control');
|
||||
});
|
||||
|
||||
await expect(n8n.sideBar.getTrialBanner()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should render trial banner for opt-in cloud user and has no feature flag set', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupCloudTest(n8n, setupRequirements, cloudTrialRequirements);
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.sideBar.expand();
|
||||
|
||||
await expect(n8n.sideBar.getTrialBanner()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show trial upgrade in the main sidebar if user is trialing and has feature flag enabled', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupCloudTest(n8n, setupRequirements, cloudTrialRequirements);
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.sideBar.expand();
|
||||
|
||||
await n8n.page.evaluate(() => {
|
||||
(
|
||||
window as unknown as { featureFlags: { override: (name: string, value: string) => void } }
|
||||
).featureFlags?.override('054_upgrade_plan_cta', 'variant');
|
||||
});
|
||||
|
||||
await n8n.page.reload();
|
||||
|
||||
await expect(n8n.sideBar.getMainSidebarTrialUpgrade()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should not show trial upgrade in the main sidebar if user is not trialing and feature flag is enabled', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupCloudTest(n8n, setupRequirements, cloudNonTrialRequirements);
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.sideBar.expand();
|
||||
|
||||
await n8n.page.evaluate(() => {
|
||||
(
|
||||
window as unknown as { featureFlags: { override: (name: string, value: string) => void } }
|
||||
).featureFlags?.override('054_upgrade_plan_cta', 'variant');
|
||||
});
|
||||
|
||||
await n8n.page.reload();
|
||||
|
||||
await expect(n8n.sideBar.getMainSidebarTrialUpgrade()).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Admin Home', () => {
|
||||
|
||||
Reference in New Issue
Block a user