mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
refactor(editor): Detangle cloudPlan store (no-changelog) (#16706)
This commit is contained in:
@@ -252,7 +252,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
const trackTemplatesClick = () => {
|
const trackTemplatesClick = () => {
|
||||||
telemetry.track('User clicked on templates', {
|
telemetry.track('User clicked on templates', {
|
||||||
role: usersStore.currentUserCloudInfo?.role,
|
role: cloudPlanStore.currentUserCloudInfo?.role,
|
||||||
active_workflow_count: workflowsStore.activeWorkflows.length,
|
active_workflow_count: workflowsStore.activeWorkflows.length,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import { useToast } from '@/composables/useToast';
|
|||||||
import { i18n as locale } from '@n8n/i18n';
|
import { i18n as locale } from '@n8n/i18n';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
|
|
||||||
const userEmail = computed(() => {
|
const userEmail = computed(() => {
|
||||||
const { currentUserCloudInfo } = useUsersStore();
|
return cloudPlanStore.currentUserCloudInfo?.email ?? '';
|
||||||
return currentUserCloudInfo?.email ?? '';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onConfirmEmailClick() {
|
async function onConfirmEmailClick() {
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ import { useSettingsStore } from '@/stores/settings.store';
|
|||||||
import { useVersionsStore } from '@/stores/versions.store';
|
import { useVersionsStore } from '@/stores/versions.store';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
import { mockedStore, SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
||||||
import { STORES } from '@n8n/stores';
|
import { STORES } from '@n8n/stores';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import { UserManagementAuthenticationMethod } from '@/Interface';
|
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
|
import type { IUser } from '@/Interface';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
import type { Cloud } from '@n8n/rest-api-client';
|
||||||
|
|
||||||
const showMessage = vi.fn();
|
const showMessage = vi.fn();
|
||||||
const showToast = vi.fn();
|
const showToast = vi.fn();
|
||||||
@@ -38,7 +40,7 @@ vi.mock('@n8n/stores/useRootStore', () => ({
|
|||||||
|
|
||||||
describe('Init', () => {
|
describe('Init', () => {
|
||||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
let settingsStore: ReturnType<typeof useSettingsStore>;
|
||||||
let cloudPlanStore: ReturnType<typeof useCloudPlanStore>;
|
let cloudPlanStore: ReturnType<typeof mockedStore<typeof useCloudPlanStore>>;
|
||||||
let sourceControlStore: ReturnType<typeof useSourceControlStore>;
|
let sourceControlStore: ReturnType<typeof useSourceControlStore>;
|
||||||
let usersStore: ReturnType<typeof useUsersStore>;
|
let usersStore: ReturnType<typeof useUsersStore>;
|
||||||
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
|
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
|
||||||
@@ -56,7 +58,7 @@ describe('Init', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
settingsStore = useSettingsStore();
|
settingsStore = useSettingsStore();
|
||||||
cloudPlanStore = useCloudPlanStore();
|
cloudPlanStore = mockedStore(useCloudPlanStore);
|
||||||
sourceControlStore = useSourceControlStore();
|
sourceControlStore = useSourceControlStore();
|
||||||
nodeTypesStore = useNodeTypesStore();
|
nodeTypesStore = useNodeTypesStore();
|
||||||
usersStore = useUsersStore();
|
usersStore = useUsersStore();
|
||||||
@@ -215,5 +217,67 @@ describe('Init', () => {
|
|||||||
expect.anything(),
|
expect.anything(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('cloudPlanStore', () => {
|
||||||
|
it('should initialize cloudPlanStore correctly', async () => {
|
||||||
|
settingsStore.settings.deployment.type = 'cloud';
|
||||||
|
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||||
|
usersStore.currentUserId = '123';
|
||||||
|
|
||||||
|
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||||
|
|
||||||
|
await initializeAuthenticatedFeatures(false);
|
||||||
|
|
||||||
|
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should push TRIAL_OVER banner if trial is expired', async () => {
|
||||||
|
settingsStore.settings.deployment.type = 'cloud';
|
||||||
|
usersStore.usersById = { '123': { id: '123', email: '' } as IUser };
|
||||||
|
usersStore.currentUserId = '123';
|
||||||
|
|
||||||
|
cloudPlanStore.userIsTrialing = true;
|
||||||
|
cloudPlanStore.trialExpired = true;
|
||||||
|
|
||||||
|
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||||
|
|
||||||
|
await initializeAuthenticatedFeatures(false);
|
||||||
|
|
||||||
|
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||||
|
expect(uiStore.pushBannerToStack).toHaveBeenCalledWith('TRIAL_OVER');
|
||||||
|
});
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
cloudPlanStore.userIsTrialing = true;
|
||||||
|
cloudPlanStore.trialExpired = false;
|
||||||
|
|
||||||
|
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||||
|
|
||||||
|
await initializeAuthenticatedFeatures(false);
|
||||||
|
|
||||||
|
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||||
|
expect(uiStore.pushBannerToStack).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 };
|
||||||
|
usersStore.currentUserId = '123';
|
||||||
|
|
||||||
|
cloudPlanStore.userIsTrialing = false;
|
||||||
|
cloudPlanStore.currentUserCloudInfo = { confirmed: false } as Cloud.UserAccount;
|
||||||
|
|
||||||
|
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize').mockResolvedValueOnce();
|
||||||
|
|
||||||
|
await initializeAuthenticatedFeatures(false);
|
||||||
|
|
||||||
|
expect(cloudStoreSpy).toHaveBeenCalled();
|
||||||
|
expect(uiStore.pushBannerToStack).toHaveBeenCalledWith('EMAIL_CONFIRMATION');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export async function initializeAuthenticatedFeatures(
|
|||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
const rolesStore = useRolesStore();
|
const rolesStore = useRolesStore();
|
||||||
const insightsStore = useInsightsStore();
|
const insightsStore = useInsightsStore();
|
||||||
|
const uiStore = useUIStore();
|
||||||
|
|
||||||
if (sourceControlStore.isEnterpriseSourceControlEnabled) {
|
if (sourceControlStore.isEnterpriseSourceControlEnabled) {
|
||||||
try {
|
try {
|
||||||
@@ -150,6 +151,16 @@ export async function initializeAuthenticatedFeatures(
|
|||||||
if (settingsStore.isCloudDeployment) {
|
if (settingsStore.isCloudDeployment) {
|
||||||
try {
|
try {
|
||||||
await cloudPlanStore.initialize();
|
await cloudPlanStore.initialize();
|
||||||
|
|
||||||
|
if (cloudPlanStore.userIsTrialing) {
|
||||||
|
if (cloudPlanStore.trialExpired) {
|
||||||
|
uiStore.pushBannerToStack('TRIAL_OVER');
|
||||||
|
} else {
|
||||||
|
uiStore.pushBannerToStack('TRIAL');
|
||||||
|
}
|
||||||
|
} else if (!cloudPlanStore.currentUserCloudInfo?.confirmed) {
|
||||||
|
uiStore.pushBannerToStack('EMAIL_CONFIRMATION');
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to initialize cloud plan store:', e);
|
console.error('Failed to initialize cloud plan store:', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { computed, reactive } from 'vue';
|
import { computed, reactive, ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import type { CloudPlanState } from '@/Interface';
|
import type { CloudPlanState } from '@/Interface';
|
||||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import type { Cloud } from '@n8n/rest-api-client/api/cloudPlans';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
|
||||||
import {
|
import {
|
||||||
getAdminPanelLoginCode,
|
getAdminPanelLoginCode,
|
||||||
getCurrentPlan,
|
getCurrentPlan,
|
||||||
@@ -14,6 +13,7 @@ import { DateTime } from 'luxon';
|
|||||||
import { CLOUD_TRIAL_CHECK_INTERVAL } from '@/constants';
|
import { CLOUD_TRIAL_CHECK_INTERVAL } from '@/constants';
|
||||||
import { STORES } from '@n8n/stores';
|
import { STORES } from '@n8n/stores';
|
||||||
import { hasPermission } from '@/utils/rbac/permissions';
|
import { hasPermission } from '@/utils/rbac/permissions';
|
||||||
|
import * as cloudApi from '@n8n/rest-api-client/api/cloudPlans';
|
||||||
|
|
||||||
const DEFAULT_STATE: CloudPlanState = {
|
const DEFAULT_STATE: CloudPlanState = {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
@@ -25,11 +25,12 @@ const DEFAULT_STATE: CloudPlanState = {
|
|||||||
export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const usersStore = useUsersStore();
|
|
||||||
|
|
||||||
const state = reactive<CloudPlanState>(DEFAULT_STATE);
|
const state = reactive<CloudPlanState>(DEFAULT_STATE);
|
||||||
|
const currentUserCloudInfo = ref<Cloud.UserAccount | null>(null);
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
|
currentUserCloudInfo.value = null;
|
||||||
state.data = null;
|
state.data = null;
|
||||||
state.usage = null;
|
state.usage = null;
|
||||||
};
|
};
|
||||||
@@ -58,11 +59,10 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
|||||||
|
|
||||||
const getUserCloudAccount = async () => {
|
const getUserCloudAccount = async () => {
|
||||||
if (!hasCloudPlan.value) throw new Error('User does not have a cloud plan');
|
if (!hasCloudPlan.value) throw new Error('User does not have a cloud plan');
|
||||||
|
let cloudUser: Cloud.UserAccount | null = null;
|
||||||
try {
|
try {
|
||||||
await usersStore.fetchUserCloudAccount();
|
cloudUser = await cloudApi.getCloudUserInfo(rootStore.restApiContext);
|
||||||
if (!usersStore.currentUserCloudInfo?.confirmed && !userIsTrialing.value) {
|
currentUserCloudInfo.value = cloudUser;
|
||||||
useUIStore().pushBannerToStack('EMAIL_CONFIRMATION');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error.message);
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
@@ -80,14 +80,6 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
|||||||
plan = await getCurrentPlan(rootStore.restApiContext);
|
plan = await getCurrentPlan(rootStore.restApiContext);
|
||||||
state.data = plan;
|
state.data = plan;
|
||||||
state.loadingPlan = false;
|
state.loadingPlan = false;
|
||||||
|
|
||||||
if (userIsTrialing.value) {
|
|
||||||
if (trialExpired.value) {
|
|
||||||
useUIStore().pushBannerToStack('TRIAL_OVER');
|
|
||||||
} else {
|
|
||||||
useUIStore().pushBannerToStack('TRIAL');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.loadingPlan = false;
|
state.loadingPlan = false;
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@@ -194,6 +186,7 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
|||||||
trialExpired,
|
trialExpired,
|
||||||
allExecutionsUsed,
|
allExecutionsUsed,
|
||||||
hasCloudPlan,
|
hasCloudPlan,
|
||||||
|
currentUserCloudInfo,
|
||||||
generateCloudDashboardAutoLoginLink,
|
generateCloudDashboardAutoLoginLink,
|
||||||
initialize,
|
initialize,
|
||||||
getOwnerCurrentPlan,
|
getOwnerCurrentPlan,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants';
|
|||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { defaultSettings } from '../__tests__/defaults';
|
import { defaultSettings } from '../__tests__/defaults';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
|
|
||||||
export const DEFAULT_POSTHOG_SETTINGS: FrontendSettings['posthog'] = {
|
export const DEFAULT_POSTHOG_SETTINGS: FrontendSettings['posthog'] = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -49,7 +50,9 @@ function resetStores() {
|
|||||||
usersStore.initialized = false;
|
usersStore.initialized = false;
|
||||||
usersStore.currentUserId = null;
|
usersStore.currentUserId = null;
|
||||||
usersStore.usersById = {};
|
usersStore.usersById = {};
|
||||||
usersStore.currentUserCloudInfo = null;
|
|
||||||
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
|
cloudPlanStore.currentUserCloudInfo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
|
|||||||
import { useUsersStore } from './users.store';
|
import { useUsersStore } from './users.store';
|
||||||
import { useWorkflowsStore } from './workflows.store';
|
import { useWorkflowsStore } from './workflows.store';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
|
|
||||||
export interface ITemplateState {
|
export interface ITemplateState {
|
||||||
categories: ITemplatesCategory[];
|
categories: ITemplatesCategory[];
|
||||||
@@ -81,6 +82,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
|||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const userStore = useUsersStore();
|
const userStore = useUsersStore();
|
||||||
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
const allCategories = computed(() => {
|
const allCategories = computed(() => {
|
||||||
@@ -171,7 +173,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
|||||||
utm_awc: String(workflowsStore.activeWorkflows.length),
|
utm_awc: String(workflowsStore.activeWorkflows.length),
|
||||||
};
|
};
|
||||||
const userRole: string | null | undefined =
|
const userRole: string | null | undefined =
|
||||||
userStore.currentUserCloudInfo?.role ??
|
cloudPlanStore.currentUserCloudInfo?.role ??
|
||||||
(userStore.currentUser?.personalizationAnswers &&
|
(userStore.currentUser?.personalizationAnswers &&
|
||||||
'role' in userStore.currentUser.personalizationAnswers
|
'role' in userStore.currentUser.personalizationAnswers
|
||||||
? userStore.currentUser.personalizationAnswers.role
|
? userStore.currentUser.personalizationAnswers.role
|
||||||
|
|||||||
@@ -1,54 +1,16 @@
|
|||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
|
||||||
import merge from 'lodash/merge';
|
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
import * as cloudPlanApi from '@n8n/rest-api-client/api/cloudPlans';
|
|
||||||
import { defaultSettings } from '../__tests__/defaults';
|
|
||||||
import {
|
|
||||||
getTrialExpiredUserResponse,
|
|
||||||
getTrialingUserResponse,
|
|
||||||
getUserCloudInfo,
|
|
||||||
getNotTrialingUserResponse,
|
|
||||||
} from './__tests__/utils/cloudStoreUtils';
|
|
||||||
import { ROLE, type Role } from '@n8n/api-types';
|
|
||||||
|
|
||||||
let uiStore: ReturnType<typeof useUIStore>;
|
let uiStore: ReturnType<typeof useUIStore>;
|
||||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
|
||||||
let cloudPlanStore: ReturnType<typeof useCloudPlanStore>;
|
let cloudPlanStore: ReturnType<typeof useCloudPlanStore>;
|
||||||
|
|
||||||
function setUser(role: Role) {
|
|
||||||
useUsersStore().addUsers([
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
isPending: false,
|
|
||||||
role,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
useUsersStore().currentUserId = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupOwnerAndCloudDeployment() {
|
|
||||||
setUser(ROLE.Owner);
|
|
||||||
settingsStore.setSettings(
|
|
||||||
merge({}, defaultSettings, {
|
|
||||||
n8nMetadata: {
|
|
||||||
userId: '1',
|
|
||||||
},
|
|
||||||
deployment: { type: 'cloud' },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('UI store', () => {
|
describe('UI store', () => {
|
||||||
let mockedCloudStore;
|
let mockedCloudStore;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createPinia());
|
setActivePinia(createPinia());
|
||||||
uiStore = useUIStore();
|
uiStore = useUIStore();
|
||||||
settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
cloudPlanStore = useCloudPlanStore();
|
cloudPlanStore = useCloudPlanStore();
|
||||||
|
|
||||||
@@ -90,62 +52,4 @@ describe('UI store', () => {
|
|||||||
|
|
||||||
expect(uiStore.bannerStack).not.toContain('V1');
|
expect(uiStore.bannerStack).not.toContain('V1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add trial banner to the the stack', async () => {
|
|
||||||
const fetchCloudSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCurrentPlan')
|
|
||||||
.mockResolvedValue(getTrialingUserResponse());
|
|
||||||
const fetchUserCloudAccountSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCloudUserInfo')
|
|
||||||
.mockResolvedValue(getUserCloudInfo(true));
|
|
||||||
const getCurrentUsageSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCurrentUsage')
|
|
||||||
.mockResolvedValue({ executions: 1000, activeWorkflows: 100 });
|
|
||||||
setupOwnerAndCloudDeployment();
|
|
||||||
await cloudPlanStore.checkForCloudPlanData();
|
|
||||||
await cloudPlanStore.fetchUserCloudAccount();
|
|
||||||
expect(fetchCloudSpy).toHaveBeenCalled();
|
|
||||||
expect(fetchUserCloudAccountSpy).toHaveBeenCalled();
|
|
||||||
expect(getCurrentUsageSpy).toHaveBeenCalled();
|
|
||||||
expect(uiStore.bannerStack).toContain('TRIAL');
|
|
||||||
// There should be no email confirmation banner for trialing users
|
|
||||||
expect(uiStore.bannerStack).not.toContain('EMAIL_CONFIRMATION');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add trial over banner to the the stack', async () => {
|
|
||||||
const fetchCloudSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCurrentPlan')
|
|
||||||
.mockResolvedValue(getTrialExpiredUserResponse());
|
|
||||||
const fetchUserCloudAccountSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCloudUserInfo')
|
|
||||||
.mockResolvedValue(getUserCloudInfo(true));
|
|
||||||
setupOwnerAndCloudDeployment();
|
|
||||||
const getCurrentUsageSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCurrentUsage')
|
|
||||||
.mockResolvedValue({ executions: 1000, activeWorkflows: 100 });
|
|
||||||
setupOwnerAndCloudDeployment();
|
|
||||||
await cloudPlanStore.checkForCloudPlanData();
|
|
||||||
await cloudPlanStore.fetchUserCloudAccount();
|
|
||||||
expect(fetchCloudSpy).toHaveBeenCalled();
|
|
||||||
expect(fetchUserCloudAccountSpy).toHaveBeenCalled();
|
|
||||||
expect(getCurrentUsageSpy).toHaveBeenCalled();
|
|
||||||
expect(uiStore.bannerStack).toContain('TRIAL_OVER');
|
|
||||||
// There should be no email confirmation banner for trialing users
|
|
||||||
expect(uiStore.bannerStack).not.toContain('EMAIL_CONFIRMATION');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add email confirmation banner to the the stack', async () => {
|
|
||||||
const fetchCloudSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCurrentPlan')
|
|
||||||
.mockResolvedValue(getNotTrialingUserResponse());
|
|
||||||
const fetchUserCloudAccountSpy = vi
|
|
||||||
.spyOn(cloudPlanApi, 'getCloudUserInfo')
|
|
||||||
.mockResolvedValue(getUserCloudInfo(false));
|
|
||||||
setupOwnerAndCloudDeployment();
|
|
||||||
await cloudPlanStore.checkForCloudPlanData();
|
|
||||||
await cloudPlanStore.fetchUserCloudAccount();
|
|
||||||
expect(fetchCloudSpy).toHaveBeenCalled();
|
|
||||||
expect(fetchUserCloudAccountSpy).toHaveBeenCalled();
|
|
||||||
expect(uiStore.bannerStack).toContain('EMAIL_CONFIRMATION');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import type {
|
|||||||
CurrentUserResponse,
|
CurrentUserResponse,
|
||||||
InvitableRoleName,
|
InvitableRoleName,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import type { Cloud } from '@n8n/rest-api-client/api/cloudPlans';
|
|
||||||
import { getPersonalizedNodeTypes } from '@/utils/userUtils';
|
import { getPersonalizedNodeTypes } from '@/utils/userUtils';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||||
@@ -43,7 +42,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
|||||||
const initialized = ref(false);
|
const initialized = ref(false);
|
||||||
const currentUserId = ref<string | null>(null);
|
const currentUserId = ref<string | null>(null);
|
||||||
const usersById = ref<Record<string, IUser>>({});
|
const usersById = ref<Record<string, IUser>>({});
|
||||||
const currentUserCloudInfo = ref<Cloud.UserAccount | null>(null);
|
|
||||||
const userQuota = ref<number>(-1);
|
const userQuota = ref<number>(-1);
|
||||||
|
|
||||||
const loginHooks = ref<LoginHook[]>([]);
|
const loginHooks = ref<LoginHook[]>([]);
|
||||||
@@ -179,7 +177,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
|||||||
|
|
||||||
const unsetCurrentUser = () => {
|
const unsetCurrentUser = () => {
|
||||||
currentUserId.value = null;
|
currentUserId.value = null;
|
||||||
currentUserCloudInfo.value = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUserById = (userId: string) => {
|
const deleteUserById = (userId: string) => {
|
||||||
@@ -391,16 +388,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchUserCloudAccount = async () => {
|
|
||||||
let cloudUser: Cloud.UserAccount | null = null;
|
|
||||||
try {
|
|
||||||
cloudUser = await cloudApi.getCloudUserInfo(rootStore.restApiContext);
|
|
||||||
currentUserCloudInfo.value = cloudUser;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendConfirmationEmail = async () => {
|
const sendConfirmationEmail = async () => {
|
||||||
await cloudApi.sendConfirmationEmail(rootStore.restApiContext);
|
await cloudApi.sendConfirmationEmail(rootStore.restApiContext);
|
||||||
};
|
};
|
||||||
@@ -438,7 +425,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
|||||||
initialized,
|
initialized,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
usersById,
|
usersById,
|
||||||
currentUserCloudInfo,
|
|
||||||
allUsers,
|
allUsers,
|
||||||
currentUser,
|
currentUser,
|
||||||
userActivated,
|
userActivated,
|
||||||
@@ -481,7 +467,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
|||||||
enableMfa,
|
enableMfa,
|
||||||
disableMfa,
|
disableMfa,
|
||||||
canEnableMFA,
|
canEnableMFA,
|
||||||
fetchUserCloudAccount,
|
|
||||||
sendConfirmationEmail,
|
sendConfirmationEmail,
|
||||||
updateGlobalRole,
|
updateGlobalRole,
|
||||||
setEasyAIWorkflowOnboardingDone,
|
setEasyAIWorkflowOnboardingDone,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
|||||||
|
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
@@ -27,7 +26,6 @@ export type MfaCodeOrMfaRecoveryCode = Pick<LoginRequestDto, 'mfaCode' | 'mfaRec
|
|||||||
|
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const cloudPlanStore = useCloudPlanStore();
|
|
||||||
const ssoStore = useSSOStore();
|
const ssoStore = useSSOStore();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -136,13 +134,6 @@ const login = async (form: LoginRequestDto) => {
|
|||||||
mfaRecoveryCode: form.mfaRecoveryCode,
|
mfaRecoveryCode: form.mfaRecoveryCode,
|
||||||
});
|
});
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
if (settingsStore.isCloudDeployment) {
|
|
||||||
try {
|
|
||||||
await cloudPlanStore.checkForCloudPlanData();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to check for cloud plan data', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await settingsStore.getSettings();
|
await settingsStore.getSettings();
|
||||||
|
|
||||||
if (settingsStore.activeModules.length > 0) {
|
if (settingsStore.activeModules.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user