mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-27 14:57:21 +02:00
feat(editor): Implement part 2 of ready to run v2 (no-changelog) (#20353)
This commit is contained in:
parent
0602018d9a
commit
476bfe58b7
|
|
@ -844,6 +844,13 @@ export const READY_TO_RUN_V2_EXPERIMENT = {
|
|||
variant2: 'variant-2-twoboxes',
|
||||
};
|
||||
|
||||
export const READY_TO_RUN_V2_PART2_EXPERIMENT = {
|
||||
name: '045_ready-to-run-worfklow_v2-2',
|
||||
control: 'control',
|
||||
variant3: 'variant-3',
|
||||
variant4: 'variant-4',
|
||||
};
|
||||
|
||||
export const PERSONALIZED_TEMPLATES_V3 = {
|
||||
name: '044_template_reco_v3',
|
||||
control: 'control',
|
||||
|
|
@ -861,6 +868,7 @@ export const EXPERIMENTS_TO_TRACK = [
|
|||
TEMPLATE_RECO_V2.name,
|
||||
READY_TO_RUN_V2_EXPERIMENT.name,
|
||||
PERSONALIZED_TEMPLATES_V3.name,
|
||||
READY_TO_RUN_V2_PART2_EXPERIMENT.name,
|
||||
];
|
||||
|
||||
export const MFA_FORM = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { N8nCard, N8nHeading, N8nText, N8nIcon } from '@n8n/design-system';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
|
|
@ -21,6 +21,8 @@ const sourceControlStore = useSourceControlStore();
|
|||
const projectPages = useProjectPages();
|
||||
const readyToRunWorkflowsV2Store = useReadyToRunWorkflowsV2Store();
|
||||
|
||||
const isLoadingReadyToRun = ref(false);
|
||||
|
||||
const currentUser = computed(() => usersStore.currentUser ?? ({} as IUser));
|
||||
const personalProject = computed(() => projectsStore.personalProject);
|
||||
const readOnlyEnv = computed(() => sourceControlStore.preferences.branchReadOnly);
|
||||
|
|
@ -42,14 +44,20 @@ const emptyListDescription = computed(() => {
|
|||
});
|
||||
|
||||
const showReadyToRunV2Card = computed(() => {
|
||||
return readyToRunWorkflowsV2Store.getCardVisibility(
|
||||
projectPermissions.value.workflow.create,
|
||||
readOnlyEnv.value,
|
||||
false, // loading is false in simplified layout
|
||||
return (
|
||||
isLoadingReadyToRun.value ||
|
||||
readyToRunWorkflowsV2Store.getCardVisibility(
|
||||
projectPermissions.value.workflow.create,
|
||||
readOnlyEnv.value,
|
||||
false, // loading is false in simplified layout
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const handleReadyToRunV2Click = async () => {
|
||||
if (isLoadingReadyToRun.value) return;
|
||||
|
||||
isLoadingReadyToRun.value = true;
|
||||
const projectId = projectPages.isOverviewSubPage
|
||||
? personalProject.value?.id
|
||||
: (route.params.projectId as string);
|
||||
|
|
@ -61,6 +69,7 @@ const handleReadyToRunV2Click = async () => {
|
|||
projectId,
|
||||
);
|
||||
} catch (error) {
|
||||
isLoadingReadyToRun.value = false;
|
||||
toast.showError(error, i18n.baseText('generic.error'));
|
||||
}
|
||||
};
|
||||
|
|
@ -98,17 +107,18 @@ const emit = defineEmits<{
|
|||
>
|
||||
<N8nCard
|
||||
v-if="showReadyToRunV2Card"
|
||||
:class="$style.actionCard"
|
||||
hoverable
|
||||
:class="[$style.actionCard, { [$style.loading]: isLoadingReadyToRun }]"
|
||||
:hoverable="!isLoadingReadyToRun"
|
||||
data-test-id="ready-to-run-v2-card"
|
||||
@click="handleReadyToRunV2Click"
|
||||
>
|
||||
<div :class="$style.cardContent">
|
||||
<N8nIcon
|
||||
:class="$style.cardIcon"
|
||||
icon="sparkles"
|
||||
:icon="isLoadingReadyToRun ? 'spinner' : 'sparkles'"
|
||||
color="foreground-dark"
|
||||
:stroke-width="1.5"
|
||||
:spin="isLoadingReadyToRun"
|
||||
/>
|
||||
<N8nText size="large" class="mt-xs">
|
||||
{{ i18n.baseText('workflows.empty.readyToRunV2') }}
|
||||
|
|
@ -201,6 +211,11 @@ const emit = defineEmits<{
|
|||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { READY_TO_RUN_V2_EXPERIMENT, VIEWS } from '@/constants';
|
||||
import { READY_TO_RUN_V2_PART2_EXPERIMENT, VIEWS } from '@/constants';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
|
|
@ -15,8 +15,8 @@ import type { WorkflowDataCreate } from '@n8n/rest-api-client';
|
|||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter, type RouteLocationNormalized } from 'vue-router';
|
||||
import { READY_TO_RUN_WORKFLOW_V1 } from '../workflows/ai-workflow';
|
||||
import { READY_TO_RUN_WORKFLOW_V2 } from '../workflows/ai-workflow-v2';
|
||||
import { READY_TO_RUN_WORKFLOW_V3 } from '../workflows/ai-workflow-v3';
|
||||
import { READY_TO_RUN_WORKFLOW_V4 } from '../workflows/ai-workflow-v4';
|
||||
import { useEmptyStateDetection } from '../composables/useEmptyStateDetection';
|
||||
|
||||
const LOCAL_STORAGE_CREDENTIAL_KEY = 'N8N_READY_TO_RUN_V2_OPENAI_CREDENTIAL_ID';
|
||||
|
|
@ -36,10 +36,10 @@ export const useReadyToRunWorkflowsV2Store = defineStore(
|
|||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
const isFeatureEnabled = computed(() => {
|
||||
const variant = posthogStore.getVariant(READY_TO_RUN_V2_EXPERIMENT.name);
|
||||
const variant = posthogStore.getVariant(READY_TO_RUN_V2_PART2_EXPERIMENT.name);
|
||||
return (
|
||||
(variant === READY_TO_RUN_V2_EXPERIMENT.variant1 ||
|
||||
variant === READY_TO_RUN_V2_EXPERIMENT.variant2) &&
|
||||
(variant === READY_TO_RUN_V2_PART2_EXPERIMENT.variant3 ||
|
||||
variant === READY_TO_RUN_V2_PART2_EXPERIMENT.variant4) &&
|
||||
cloudPlanStore.userIsTrialing
|
||||
);
|
||||
});
|
||||
|
|
@ -68,7 +68,7 @@ export const useReadyToRunWorkflowsV2Store = defineStore(
|
|||
});
|
||||
|
||||
const getCurrentVariant = () => {
|
||||
return posthogStore.getVariant(READY_TO_RUN_V2_EXPERIMENT.name);
|
||||
return posthogStore.getVariant(READY_TO_RUN_V2_PART2_EXPERIMENT.name);
|
||||
};
|
||||
|
||||
const trackExecuteAiWorkflow = (status: string) => {
|
||||
|
|
@ -92,10 +92,6 @@ export const useReadyToRunWorkflowsV2Store = defineStore(
|
|||
try {
|
||||
const credential = await credentialsStore.claimFreeAiCredits(projectId);
|
||||
|
||||
if (usersStore?.currentUser?.settings) {
|
||||
usersStore.currentUser.settings.userClaimedAiCredits = true;
|
||||
}
|
||||
|
||||
claimedCredentialIdRef.value = credential.id;
|
||||
|
||||
telemetry.track('User claimed OpenAI credits');
|
||||
|
|
@ -120,9 +116,9 @@ export const useReadyToRunWorkflowsV2Store = defineStore(
|
|||
});
|
||||
|
||||
const workflowTemplate =
|
||||
variant === READY_TO_RUN_V2_EXPERIMENT.variant2
|
||||
? READY_TO_RUN_WORKFLOW_V2
|
||||
: READY_TO_RUN_WORKFLOW_V1;
|
||||
variant === READY_TO_RUN_V2_PART2_EXPERIMENT.variant3
|
||||
? READY_TO_RUN_WORKFLOW_V3
|
||||
: READY_TO_RUN_WORKFLOW_V4;
|
||||
|
||||
try {
|
||||
let workflowToCreate: WorkflowDataCreate = {
|
||||
|
|
@ -165,6 +161,10 @@ export const useReadyToRunWorkflowsV2Store = defineStore(
|
|||
) => {
|
||||
await claimFreeAiCredits(projectId);
|
||||
await createAndOpenAiWorkflow(source, parentFolderId);
|
||||
|
||||
if (usersStore?.currentUser?.settings) {
|
||||
usersStore.currentUser.settings.userClaimedAiCredits = true;
|
||||
}
|
||||
};
|
||||
|
||||
const getCardVisibility = (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
import type { WorkflowDataCreate } from '@n8n/rest-api-client';
|
||||
|
||||
export const READY_TO_RUN_WORKFLOW_V3: WorkflowDataCreate = {
|
||||
name: 'AI Agent workflow',
|
||||
meta: { templateId: 'ready-to-run-ai-workflow-v3' },
|
||||
nodes: [
|
||||
{
|
||||
parameters: {
|
||||
url: 'https://www.theverge.com/rss/index.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [128, 448],
|
||||
id: '7febc10d-90ce-4329-90fb-a9a2ca0185c4',
|
||||
name: 'Get Tech News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
toolDescription: 'Reads the news',
|
||||
url: '=https://feeds.bbci.co.uk/news/world/rss.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [272, 448],
|
||||
id: '9424428d-45e2-4085-99f6-ee223802ba5a',
|
||||
name: 'Get World News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
model: {
|
||||
__rl: true,
|
||||
mode: 'list',
|
||||
value: 'gpt-4.1-mini',
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
||||
typeVersion: 1.2,
|
||||
position: [-144, 448],
|
||||
id: 'a4fcf631-d3d9-4c4d-9e7b-02c93e70b23f',
|
||||
name: 'OpenAI Model',
|
||||
notesInFlow: true,
|
||||
credentials: {},
|
||||
notes: 'Free n8n credits ',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
promptType: 'define',
|
||||
text: '=Summarize world news and tech news from the last 24 hours. \nSkip your comments. \nThe titles should be "World news:" and "Tech news:" \nLimit to 10 bullet points. \nToday is {{ $today }}',
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.agent',
|
||||
typeVersion: 2.2,
|
||||
position: [-144, 192],
|
||||
id: '99bed296-3855-4d89-b983-f30539cfa775',
|
||||
name: 'AI Summary Agent',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content:
|
||||
'### ✅ This test workflow is ready to use:\nHover over here and click the orange "Execute workflow" button below.\n',
|
||||
height: 240,
|
||||
width: 400,
|
||||
color: 5,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-672, 112],
|
||||
id: 'e664273f-63c6-4f12-804a-0fcd99c294cb',
|
||||
name: 'Sticky Note2',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
subject: 'Your news daily summary',
|
||||
emailType: 'text',
|
||||
message: '={{ $json.output }}',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.gmail',
|
||||
typeVersion: 2.1,
|
||||
position: [688, 192],
|
||||
id: 'd0e843dc-c398-4d32-8c56-0bf83176add3',
|
||||
name: 'Send summary with Gmail',
|
||||
webhookId: '99bdd654-5c17-4ba1-b091-3d726e56f88d',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {},
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [-432, 192],
|
||||
id: 'e6618880-9281-4d92-91ff-c9a000429b7d',
|
||||
name: 'Manual execution',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content:
|
||||
'### Bonus (optional)\nConnect the `Output (News Summary)` to the node below, add your Google account info, and send the News summary by email.',
|
||||
height: 112,
|
||||
width: 384,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [544, 16],
|
||||
id: '5e33fbe1-1971-48f8-81c7-08bd32e24aca',
|
||||
name: 'Sticky Note',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
assignments: {
|
||||
assignments: [
|
||||
{
|
||||
id: '85b5c530-2c13-4424-ab83-05979bc879a5',
|
||||
name: 'output',
|
||||
value: '={{ $json.output }}',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 3.4,
|
||||
position: [256, 192],
|
||||
id: 'f0a79856-ddcf-404b-95a7-a9bf882697ff',
|
||||
name: 'Output (News summary)',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
'Get Tech News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Get World News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'OpenAI Model': {
|
||||
ai_languageModel: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_languageModel',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'AI Summary Agent': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'Output (News summary)',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Manual execution': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Output (News summary)': {
|
||||
main: [[]],
|
||||
},
|
||||
},
|
||||
pinData: {},
|
||||
};
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
import type { WorkflowDataCreate } from '@n8n/rest-api-client';
|
||||
|
||||
export const READY_TO_RUN_WORKFLOW_V4: WorkflowDataCreate = {
|
||||
name: 'AI Agent workflow',
|
||||
meta: { templateId: 'ready-to-run-ai-workflow-v4' },
|
||||
nodes: [
|
||||
{
|
||||
parameters: {
|
||||
url: 'https://www.theverge.com/rss/index.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [288, 160],
|
||||
id: '6160830b-4f20-437c-b1a2-586bffe62d66',
|
||||
name: 'Get Tech News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
toolDescription: 'Reads the news',
|
||||
url: '=https://feeds.bbci.co.uk/news/world/rss.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [416, 160],
|
||||
id: '4f8ae14c-8c6a-4cf8-b51b-99af6bd23ed1',
|
||||
name: 'Get World News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
model: {
|
||||
__rl: true,
|
||||
mode: 'list',
|
||||
value: 'gpt-4.1-mini',
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
||||
typeVersion: 1.2,
|
||||
position: [32, 160],
|
||||
id: '95986360-8ca1-4b8a-af7e-f101e89e3654',
|
||||
name: 'OpenAI Model',
|
||||
notesInFlow: true,
|
||||
credentials: {},
|
||||
notes: 'Free n8n credits ',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
promptType: 'define',
|
||||
text: '=Summarize world news and tech news from the last 24 hours. \nSkip your comments. \nThe titles should be "World news:" and "Tech news:" \nLimit to 10 bullet points. \nToday is {{ $today }}',
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.agent',
|
||||
typeVersion: 2.2,
|
||||
position: [32, -64],
|
||||
id: 'd36975bc-d51f-472f-a51f-f6c745b29a8d',
|
||||
name: 'AI Summary Agent',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content:
|
||||
'### ✅ This test workflow is ready to use \n\n1. Click the orange "Execute workflow" button\n\n2. Watch the workflow get the latest news and summarize it with AI \n\n3. (Bonus) Connect the `Gmail node` to the workflow to send the summary via email\n\n',
|
||||
height: 256,
|
||||
width: 352,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-832, -128],
|
||||
id: '13abc1af-da4a-427d-8cc4-e260dff43307',
|
||||
name: 'Sticky Note2',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content:
|
||||
'[](https://www.youtube.com/watch?v=cMyOkQ4N-5M "Watch on YouTube")\n',
|
||||
height: 208,
|
||||
width: 352,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-832, 160],
|
||||
id: 'e0e15104-1954-43b9-b748-0ff8441f6aeb',
|
||||
name: 'Sticky Note',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
assignments: {
|
||||
assignments: [
|
||||
{
|
||||
id: '85b5c530-2c13-4424-ab83-05979bc879a5',
|
||||
name: 'output',
|
||||
value: '={{ $json.output }}',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 3.4,
|
||||
position: [464, -64],
|
||||
id: 'bef94b0a-b2aa-42f6-85bb-2e23f530d799',
|
||||
name: 'Output (News Summary)',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {},
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [-256, -64],
|
||||
id: '55cb3e43-b73c-48cb-b420-dd618de68a58',
|
||||
name: 'Execute workflow',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
subject: 'Your news daily summary',
|
||||
emailType: 'text',
|
||||
message: '={{ $json.output }}',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.gmail',
|
||||
typeVersion: 2.1,
|
||||
position: [768, -64],
|
||||
id: 'e74f8dac-d766-4f4d-91f3-36604a2d4e7a',
|
||||
name: 'Send summary with Gmail',
|
||||
webhookId: '093b04f1-5e78-4926-9863-1b100d6f2ead',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
'Get Tech News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Get World News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'OpenAI Model': {
|
||||
ai_languageModel: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_languageModel',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'AI Summary Agent': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'Output (News Summary)',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Output (News Summary)': {
|
||||
main: [[]],
|
||||
},
|
||||
'Execute workflow': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
pinData: {},
|
||||
};
|
||||
|
|
@ -289,7 +289,7 @@ describe('Init', () => {
|
|||
expect(uiStore.pushBannerToStack).toHaveBeenCalledWith('TRIAL_OVER');
|
||||
});
|
||||
|
||||
it('should not push TRIAL banner if trial is active but user is visiting for the first time', async () => {
|
||||
it('should push TRIAL banner if trial is active', async () => {
|
||||
settingsStore.settings.deployment.type = 'cloud';
|
||||
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||
usersStore.currentUserId = '123';
|
||||
|
|
@ -297,44 +297,12 @@ describe('Init', () => {
|
|||
cloudPlanStore.userIsTrialing = true;
|
||||
cloudPlanStore.trialExpired = false;
|
||||
|
||||
// Mock localStorage to simulate first visit
|
||||
const getItemSpy = vi.spyOn(Storage.prototype, 'getItem').mockReturnValue(null);
|
||||
const setItemSpy = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {});
|
||||
|
||||
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||
|
||||
await initializeAuthenticatedFeatures(false);
|
||||
|
||||
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||
expect(setItemSpy).toHaveBeenCalledWith('n8n-trial-visit-count', '1');
|
||||
expect(uiStore.pushBannerToStack).not.toHaveBeenCalledWith('TRIAL');
|
||||
|
||||
getItemSpy.mockRestore();
|
||||
setItemSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should push TRIAL banner if trial is active and user is returning', async () => {
|
||||
settingsStore.settings.deployment.type = 'cloud';
|
||||
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||
usersStore.currentUserId = '123';
|
||||
|
||||
cloudPlanStore.userIsTrialing = true;
|
||||
cloudPlanStore.trialExpired = false;
|
||||
|
||||
// Mock localStorage to simulate return visit
|
||||
const getItemSpy = vi.spyOn(Storage.prototype, 'getItem').mockReturnValue('1');
|
||||
const setItemSpy = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {});
|
||||
|
||||
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||
|
||||
await initializeAuthenticatedFeatures(false);
|
||||
|
||||
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||
expect(setItemSpy).toHaveBeenCalledWith('n8n-trial-visit-count', '2');
|
||||
expect(uiStore.pushBannerToStack).toHaveBeenCalledWith('TRIAL');
|
||||
|
||||
getItemSpy.mockRestore();
|
||||
setItemSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should push EMAIL_CONFIRMATION banner if user cloud info is not confirmed', async () => {
|
||||
|
|
|
|||
|
|
@ -35,22 +35,6 @@ export const state = {
|
|||
};
|
||||
let authenticatedFeaturesInitialized = false;
|
||||
|
||||
/**
|
||||
* EXP: Ready to run V2
|
||||
* Tracks user visits and determines if trial banner should show
|
||||
* Returns true if this is not the user's first visit
|
||||
*/
|
||||
function shouldShowTrialBanner(): boolean {
|
||||
const VISIT_COUNT_KEY = 'n8n-trial-visit-count';
|
||||
const currentCount = parseInt(localStorage.getItem(VISIT_COUNT_KEY) ?? '0', 10);
|
||||
const newCount = currentCount + 1;
|
||||
|
||||
localStorage.setItem(VISIT_COUNT_KEY, newCount.toString());
|
||||
|
||||
// Don't show banner on first visit
|
||||
return newCount > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the core application stores and hooks
|
||||
* This is called once, when the first route is loaded.
|
||||
|
|
@ -179,7 +163,7 @@ export async function initializeAuthenticatedFeatures(
|
|||
if (cloudPlanStore.userIsTrialing) {
|
||||
if (cloudPlanStore.trialExpired) {
|
||||
uiStore.pushBannerToStack('TRIAL_OVER');
|
||||
} else if (shouldShowTrialBanner()) {
|
||||
} else {
|
||||
uiStore.pushBannerToStack('TRIAL');
|
||||
}
|
||||
} else if (cloudPlanStore.currentUserCloudInfo?.confirmed === false) {
|
||||
|
|
|
|||
|
|
@ -32,17 +32,6 @@ const cloudTrialRequirements = {
|
|||
},
|
||||
};
|
||||
|
||||
const firstVisitRequirements: TestRequirements = {
|
||||
...cloudTrialRequirements,
|
||||
};
|
||||
|
||||
const subsequentVisitRequirements: TestRequirements = {
|
||||
...cloudTrialRequirements,
|
||||
storage: {
|
||||
'n8n-trial-visit-count': '1',
|
||||
},
|
||||
};
|
||||
|
||||
const setupCloudTest = async (
|
||||
n8n: n8nPage,
|
||||
setupRequirements: (requirements: TestRequirements) => Promise<void>,
|
||||
|
|
@ -54,14 +43,8 @@ const setupCloudTest = async (
|
|||
|
||||
test.describe('Cloud @db:reset @auth:owner', () => {
|
||||
test.describe('Trial Banner', () => {
|
||||
test('should not render trial banner on first visit', async ({ n8n, setupRequirements }) => {
|
||||
await setupCloudTest(n8n, setupRequirements, firstVisitRequirements);
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await expect(n8n.sideBar.getTrialBanner()).toBeHidden();
|
||||
});
|
||||
|
||||
test('should render trial banner on subsequent visits', async ({ n8n, setupRequirements }) => {
|
||||
await setupCloudTest(n8n, setupRequirements, subsequentVisitRequirements);
|
||||
test('should render trial banner for opt-in cloud user', async ({ n8n, setupRequirements }) => {
|
||||
await setupCloudTest(n8n, setupRequirements, cloudTrialRequirements);
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.sideBar.expand();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user