@@ -102,6 +111,15 @@ const infoUrl = computed(() => {
div {
padding-top: 20px;
}
+
+ .link {
+ padding-left: 0px;
+ }
+
+ .link:hover {
+ color: var(--prim-color-primary);
+ text-decoration: none;
+ }
}
.versions {
diff --git a/packages/editor-ui/src/stores/__tests__/ui.test.ts b/packages/editor-ui/src/stores/__tests__/ui.test.ts
index ace9ab3bca474..995f87d9f3228 100644
--- a/packages/editor-ui/src/stores/__tests__/ui.test.ts
+++ b/packages/editor-ui/src/stores/__tests__/ui.test.ts
@@ -1,5 +1,5 @@
import { createPinia, setActivePinia } from 'pinia';
-import { generateUpgradeLinkUrl, useUIStore } from '@/stores/ui.store';
+import { generateUpgradeLink, useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store';
import { merge } from 'lodash-es';
@@ -98,7 +98,7 @@ describe('UI store', () => {
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
],
])(
- '"generateUpgradeLinkUrl" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"',
+ '"generateUpgradeLink" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"',
async (type, environment, role, expectation) => {
setUser(role as IRole);
@@ -115,7 +115,7 @@ describe('UI store', () => {
}),
);
- const updateLinkUrl = await generateUpgradeLinkUrl('test_source', 'utm-test-campaign', type);
+ const updateLinkUrl = await generateUpgradeLink('test_source', 'utm-test-campaign', type);
expect(updateLinkUrl).toBe(expectation);
},
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index 4121099291044..7311c7d5e1294 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -73,6 +73,7 @@ import {
import { computed, ref } from 'vue';
import type { Connection } from '@vue-flow/core';
import { useTelemetry } from '@/composables/useTelemetry';
+import { useVersionsStore } from '@/stores/versions.store';
let savedTheme: ThemeOption = 'system';
try {
@@ -208,6 +209,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
const telemetry = useTelemetry();
const cloudPlanStore = useCloudPlanStore();
const userStore = useUsersStore();
+ const versionsStore = useVersionsStore();
const appliedTheme = computed(() => {
return theme.value === 'system' ? getPreferredTheme() : theme.value;
@@ -575,7 +577,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
workflowsLeft,
});
- const upgradeLink = await generateUpgradeLinkUrl(source, utm_campaign, deploymentType);
+ const upgradeLink = await generateUpgradeLink(source, utm_campaign, deploymentType);
if (mode === 'open') {
window.open(upgradeLink, '_blank');
@@ -628,6 +630,19 @@ export const useUIStore = defineStore(STORES.UI, () => {
lastCancelledConnectionPosition.value = undefined;
}
+ const goToVersions = async () => {
+ const deploymentType = settingsStore.deploymentType;
+ let versionsLink = versionsStore.infoUrl;
+
+ if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) {
+ versionsLink = await generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/manage',
+ });
+ }
+
+ location.href = versionsLink;
+ };
+
return {
appGridWidth,
appliedTheme,
@@ -669,6 +684,8 @@ export const useUIStore = defineStore(STORES.UI, () => {
isAnyModalOpen,
fakeDoorsById,
pendingNotificationsForViews,
+ activeModals,
+ goToVersions,
setTheme,
setMode,
setActiveId,
@@ -704,7 +721,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
setNotificationsForView,
deleteNotificationsForView,
resetLastInteractedWith,
- activeModals,
};
});
@@ -749,33 +765,43 @@ export const listenForModalChanges = (opts: {
});
};
-export const generateUpgradeLinkUrl = async (
+export const generateUpgradeLink = async (
source: string,
utm_campaign: string,
deploymentType: string,
) => {
- let linkUrl = '';
-
- const searchParams = new URLSearchParams();
-
- const cloudPlanStore = useCloudPlanStore();
-
+ let upgradeLink = N8N_PRICING_PAGE_URL;
if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) {
- const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.');
- const { code } = await cloudPlanStore.getAutoLoginCode();
- linkUrl = `https://${adminPanelHost}/login`;
- searchParams.set('code', code);
- searchParams.set('returnPath', '/account/change-plan');
- } else {
- linkUrl = N8N_PRICING_PAGE_URL;
+ upgradeLink = await generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/account/change-plan',
+ });
}
+ const url = new URL(upgradeLink);
+
if (utm_campaign) {
- searchParams.set('utm_campaign', utm_campaign);
+ url.searchParams.set('utm_campaign', utm_campaign);
}
if (source) {
- searchParams.set('source', source);
+ url.searchParams.set('source', source);
}
+
+ return url.toString();
+};
+
+export const generateCloudDashboardAutoLoginLink = async (data: {
+ redirectionPath: string;
+}) => {
+ const searchParams = new URLSearchParams();
+
+ const cloudPlanStore = useCloudPlanStore();
+
+ const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.');
+ const { code } = await cloudPlanStore.getAutoLoginCode();
+ const linkUrl = `https://${adminPanelHost}/login`;
+ searchParams.set('code', code);
+ searchParams.set('returnPath', data.redirectionPath);
+
return `${linkUrl}?${searchParams.toString()}`;
};
From abb68a67f5b40f1bdd8209baf4247953132d7a82 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Tue, 29 Oct 2024 19:32:29 -0400
Subject: [PATCH 03/15] remove `redirectTodashboard` method from
`cloudPlan.store.ts`
This helps consolidate all logic to redirect to owner to the cloud dashboard in one place, the uiStore.
---
.../editor-ui/src/components/MainSidebar.vue | 2 +-
.../editor-ui/src/stores/cloudPlan.store.ts | 7 -----
packages/editor-ui/src/stores/ui.store.ts | 26 +++++++++++++++++--
3 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue
index 8e483cb79ce5b..2b71c3d52a63a 100644
--- a/packages/editor-ui/src/components/MainSidebar.vue
+++ b/packages/editor-ui/src/components/MainSidebar.vue
@@ -260,7 +260,7 @@ const handleSelect = (key: string) => {
break;
}
case 'cloud-admin': {
- void cloudPlanStore.redirectToDashboard();
+ void uiStore.goToDashboard();
break;
}
case 'quickstart':
diff --git a/packages/editor-ui/src/stores/cloudPlan.store.ts b/packages/editor-ui/src/stores/cloudPlan.store.ts
index 6d14383fb43e3..9dec6546b128b 100644
--- a/packages/editor-ui/src/stores/cloudPlan.store.ts
+++ b/packages/editor-ui/src/stores/cloudPlan.store.ts
@@ -147,12 +147,6 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
}
};
- const redirectToDashboard = async () => {
- const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.');
- const { code } = await getAutoLoginCode();
- window.location.href = `https://${adminPanelHost}/login?code=${code}`;
- };
-
const initialize = async () => {
if (state.initialized) {
return;
@@ -189,6 +183,5 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
checkForCloudPlanData,
fetchUserCloudAccount,
getAutoLoginCode,
- redirectToDashboard,
};
});
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index 7311c7d5e1294..5e0480be5d619 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -630,11 +630,20 @@ export const useUIStore = defineStore(STORES.UI, () => {
lastCancelledConnectionPosition.value = undefined;
}
- const goToVersions = async () => {
+ const isInstanceOwnerInCloud = () => {
const deploymentType = settingsStore.deploymentType;
+ return hasPermission(['instanceOwner']) && deploymentType === 'cloud';
+ };
+
+ /**
+ * If the user is an instance owner in the cloud, it generates an auto-login link to the
+ * cloud dashboard that redirects the user to the manage page where they can upgrade to a new n8n version.
+ * Otherwise, it redirect them to our docs.
+ */
+ const goToVersions = async () => {
let versionsLink = versionsStore.infoUrl;
- if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) {
+ if (isInstanceOwnerInCloud()) {
versionsLink = await generateCloudDashboardAutoLoginLink({
redirectionPath: '/manage',
});
@@ -643,6 +652,18 @@ export const useUIStore = defineStore(STORES.UI, () => {
location.href = versionsLink;
};
+ const goToDashboard = async () => {
+ if (isInstanceOwnerInCloud()) {
+ const dashboardLink = await generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/dashboard',
+ });
+
+ location.href = dashboardLink;
+ }
+
+ return;
+ };
+
return {
appGridWidth,
appliedTheme,
@@ -686,6 +707,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
pendingNotificationsForViews,
activeModals,
goToVersions,
+ goToDashboard,
setTheme,
setMode,
setActiveId,
From 1c5c6e10ae5783ab3c87646b8fcb968198420d07 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 09:44:59 -0400
Subject: [PATCH 04/15] use values from stores directly
---
.../editor-ui/src/components/UpdatesPanel.vue | 32 +++++++------------
1 file changed, 12 insertions(+), 20 deletions(-)
diff --git a/packages/editor-ui/src/components/UpdatesPanel.vue b/packages/editor-ui/src/components/UpdatesPanel.vue
index 38d61824dcef7..a710e5589ed9e 100644
--- a/packages/editor-ui/src/components/UpdatesPanel.vue
+++ b/packages/editor-ui/src/components/UpdatesPanel.vue
@@ -1,6 +1,4 @@
@@ -39,22 +27,22 @@ const onInfoUrlClick = async () => {
-
+
{{
i18n.baseText('updatesPanel.youReOnVersion', {
- interpolate: { currentVersionName: currentVersion.name },
+ interpolate: { currentVersionName: versionsStore.currentVersion.name },
})
}}
-
+
{{ i18n.baseText('updatesPanel.andIs') }}
{{
i18n.baseText('updatesPanel.version', {
interpolate: {
- numberOfVersions: nextVersions.length,
- howManySuffix: nextVersions.length > 1 ? 's' : '',
+ numberOfVersions: versionsStore.nextVersions.length,
+ howManySuffix: versionsStore.nextVersions.length > 1 ? 's' : '',
},
})
}}
@@ -63,13 +51,13 @@ const onInfoUrlClick = async () => {
@@ -78,7 +66,11 @@ const onInfoUrlClick = async () => {
-
+
From f2e0a8d79c178f62f257dcb03784fa7df967172d Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 09:56:31 -0400
Subject: [PATCH 05/15] move `isInstanceOwnerInCloud` method to users's store
---
packages/editor-ui/src/stores/ui.store.ts | 10 +++-------
packages/editor-ui/src/stores/users.store.ts | 6 ++++++
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index 5e0480be5d619..5153ee9802211 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -210,6 +210,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
const cloudPlanStore = useCloudPlanStore();
const userStore = useUsersStore();
const versionsStore = useVersionsStore();
+ const usersStore = useUsersStore();
const appliedTheme = computed(() => {
return theme.value === 'system' ? getPreferredTheme() : theme.value;
@@ -630,11 +631,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
lastCancelledConnectionPosition.value = undefined;
}
- const isInstanceOwnerInCloud = () => {
- const deploymentType = settingsStore.deploymentType;
- return hasPermission(['instanceOwner']) && deploymentType === 'cloud';
- };
-
/**
* If the user is an instance owner in the cloud, it generates an auto-login link to the
* cloud dashboard that redirects the user to the manage page where they can upgrade to a new n8n version.
@@ -643,7 +639,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
const goToVersions = async () => {
let versionsLink = versionsStore.infoUrl;
- if (isInstanceOwnerInCloud()) {
+ if (usersStore.userIsOwnerInCloudDeployment) {
versionsLink = await generateCloudDashboardAutoLoginLink({
redirectionPath: '/manage',
});
@@ -653,7 +649,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
};
const goToDashboard = async () => {
- if (isInstanceOwnerInCloud()) {
+ if (usersStore.userIsOwnerInCloudDeployment) {
const dashboardLink = await generateCloudDashboardAutoLoginLink({
redirectionPath: '/dashboard',
});
diff --git a/packages/editor-ui/src/stores/users.store.ts b/packages/editor-ui/src/stores/users.store.ts
index 4f17aa3f76765..bc07a0f416b65 100644
--- a/packages/editor-ui/src/stores/users.store.ts
+++ b/packages/editor-ui/src/stores/users.store.ts
@@ -29,6 +29,7 @@ import * as invitationsApi from '@/api/invitation';
import { useNpsSurveyStore } from './npsSurvey.store';
import { computed, ref } from 'vue';
import { useTelemetry } from '@/composables/useTelemetry';
+import { hasPermission } from '@/utils/rbac/permissions';
const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner;
@@ -73,6 +74,10 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const globalRoleName = computed(() => currentUser.value?.role ?? 'default');
+ const userIsOwnerInCloudDeployment = computed(
+ () => hasPermission(['instanceOwner']) && settingsStore.isCloudDeployment,
+ );
+
const personalizedNodeTypes = computed(() => {
const user = currentUser.value;
if (!user) {
@@ -379,6 +384,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
mfaEnabled,
globalRoleName,
personalizedNodeTypes,
+ userIsOwnerInCloudDeployment,
addUsers,
loginWithCookie,
initialize,
From c2c427f04e6b99b3f47aba12c24ab73985034c44 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 10:07:03 -0400
Subject: [PATCH 06/15] move goToVersions to the versions store and move
goToDashboard to the cloud store
---
.../editor-ui/src/components/MainSidebar.vue | 2 +-
.../editor-ui/src/components/UpdatesPanel.vue | 4 +-
.../editor-ui/src/stores/cloudPlan.store.ts | 34 ++++++++++++--
packages/editor-ui/src/stores/ui.store.ts | 47 -------------------
.../editor-ui/src/stores/versions.store.ts | 22 +++++++++
5 files changed, 55 insertions(+), 54 deletions(-)
diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue
index 2b71c3d52a63a..e2dd53209c236 100644
--- a/packages/editor-ui/src/components/MainSidebar.vue
+++ b/packages/editor-ui/src/components/MainSidebar.vue
@@ -260,7 +260,7 @@ const handleSelect = (key: string) => {
break;
}
case 'cloud-admin': {
- void uiStore.goToDashboard();
+ void cloudPlanStore.goToDashboardPage();
break;
}
case 'quickstart':
diff --git a/packages/editor-ui/src/components/UpdatesPanel.vue b/packages/editor-ui/src/components/UpdatesPanel.vue
index a710e5589ed9e..a45c09905c9ba 100644
--- a/packages/editor-ui/src/components/UpdatesPanel.vue
+++ b/packages/editor-ui/src/components/UpdatesPanel.vue
@@ -5,10 +5,8 @@ import VersionCard from './VersionCard.vue';
import { VERSIONS_MODAL_KEY } from '../constants';
import { useVersionsStore } from '@/stores/versions.store';
import { useI18n } from '@/composables/useI18n';
-import { useUIStore } from '@/stores/ui.store';
const versionsStore = useVersionsStore();
-const uiStore = useUIStore();
const i18n = useI18n();
@@ -57,7 +55,7 @@ const i18n = useI18n();
size="large"
:class="$style['link']"
:bold="true"
- @click="uiStore.goToVersions()"
+ @click="versionsStore.goToVersionsPage()"
>
diff --git a/packages/editor-ui/src/stores/cloudPlan.store.ts b/packages/editor-ui/src/stores/cloudPlan.store.ts
index 9dec6546b128b..e51de0317109b 100644
--- a/packages/editor-ui/src/stores/cloudPlan.store.ts
+++ b/packages/editor-ui/src/stores/cloudPlan.store.ts
@@ -167,11 +167,34 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
state.initialized = true;
};
+ const goToDashboardPage = async () => {
+ if (usersStore.userIsOwnerInCloudDeployment) {
+ const dashboardLink = await generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/dashboard',
+ });
+
+ location.href = dashboardLink;
+ }
+
+ return;
+ };
+
+ const generateCloudDashboardAutoLoginLink = async (data: {
+ redirectionPath: string;
+ }) => {
+ const searchParams = new URLSearchParams();
+
+ const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.');
+ const { code } = await getAutoLoginCode();
+ const linkUrl = `https://${adminPanelHost}/login`;
+ searchParams.set('code', code);
+ searchParams.set('returnPath', data.redirectionPath);
+
+ return `${linkUrl}?${searchParams.toString()}`;
+ };
+
return {
state,
- initialize,
- getOwnerCurrentPlan,
- getInstanceCurrentUsage,
usageLeft,
trialDaysLeft,
userIsTrialing,
@@ -179,6 +202,11 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
currentUsageData,
trialExpired,
allExecutionsUsed,
+ goToDashboardPage,
+ generateCloudDashboardAutoLoginLink,
+ initialize,
+ getOwnerCurrentPlan,
+ getInstanceCurrentUsage,
reset,
checkForCloudPlanData,
fetchUserCloudAccount,
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index 5153ee9802211..f2c33ca5bba0d 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -631,35 +631,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
lastCancelledConnectionPosition.value = undefined;
}
- /**
- * If the user is an instance owner in the cloud, it generates an auto-login link to the
- * cloud dashboard that redirects the user to the manage page where they can upgrade to a new n8n version.
- * Otherwise, it redirect them to our docs.
- */
- const goToVersions = async () => {
- let versionsLink = versionsStore.infoUrl;
-
- if (usersStore.userIsOwnerInCloudDeployment) {
- versionsLink = await generateCloudDashboardAutoLoginLink({
- redirectionPath: '/manage',
- });
- }
-
- location.href = versionsLink;
- };
-
- const goToDashboard = async () => {
- if (usersStore.userIsOwnerInCloudDeployment) {
- const dashboardLink = await generateCloudDashboardAutoLoginLink({
- redirectionPath: '/dashboard',
- });
-
- location.href = dashboardLink;
- }
-
- return;
- };
-
return {
appGridWidth,
appliedTheme,
@@ -702,8 +673,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
fakeDoorsById,
pendingNotificationsForViews,
activeModals,
- goToVersions,
- goToDashboard,
setTheme,
setMode,
setActiveId,
@@ -807,19 +776,3 @@ export const generateUpgradeLink = async (
return url.toString();
};
-
-export const generateCloudDashboardAutoLoginLink = async (data: {
- redirectionPath: string;
-}) => {
- const searchParams = new URLSearchParams();
-
- const cloudPlanStore = useCloudPlanStore();
-
- const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.');
- const { code } = await cloudPlanStore.getAutoLoginCode();
- const linkUrl = `https://${adminPanelHost}/login`;
- searchParams.set('code', code);
- searchParams.set('returnPath', data.redirectionPath);
-
- return `${linkUrl}?${searchParams.toString()}`;
-};
diff --git a/packages/editor-ui/src/stores/versions.store.ts b/packages/editor-ui/src/stores/versions.store.ts
index 8455d5d15f515..2999f86582313 100644
--- a/packages/editor-ui/src/stores/versions.store.ts
+++ b/packages/editor-ui/src/stores/versions.store.ts
@@ -7,6 +7,8 @@ import { useRootStore } from './root.store';
import { useToast } from '@/composables/useToast';
import { useUIStore } from '@/stores/ui.store';
import { computed, ref } from 'vue';
+import { useCloudPlanStore } from '@/stores/cloudPlan.store';
+import { useUsersStore } from './users.store';
type SetVersionParams = { versions: IVersion[]; currentVersion: string };
@@ -17,6 +19,8 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
const { showToast } = useToast();
const uiStore = useUIStore();
+ const usersStore = useUsersStore();
+ const cloudPlanStore = useCloudPlanStore();
// ---------------------------------------------------------------------------
// #region Computed
@@ -96,6 +100,23 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
}
};
+ /**
+ * If the user is an instance owner in the cloud, it generates an auto-login link to the
+ * cloud dashboard that redirects the user to the manage page where they can upgrade to a new n8n version.
+ * Otherwise, it redirect them to our docs.
+ */
+ const goToVersionsPage = async () => {
+ let versionsLink = infoUrl.value;
+
+ if (usersStore.userIsOwnerInCloudDeployment) {
+ versionsLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/manage',
+ });
+ }
+
+ location.href = versionsLink;
+ };
+
// #endregion
return {
@@ -104,6 +125,7 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
hasVersionUpdates,
areNotificationsEnabled,
infoUrl,
+ goToVersionsPage,
fetchVersions,
setVersions,
setVersionNotificationSettings,
From 02d7e9790b9a7f6139675c3d2876b01d68766a52 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 16:58:19 -0400
Subject: [PATCH 07/15] move logic to its own composable
---
.../CredentialEdit/CredentialSharing.ee.vue | 5 +-
.../src/components/InviteUsersModal.vue | 4 +-
.../components/MainHeader/WorkflowDetails.vue | 6 +-
.../editor-ui/src/components/MainSidebar.vue | 4 +-
.../Projects/ProjectNavigation.test.ts | 19 +++-
.../components/Projects/ProjectNavigation.vue | 6 +-
.../Projects/ProjectRoleUpgradeDialog.vue | 6 +-
.../editor-ui/src/components/UpdatesPanel.vue | 4 +-
.../src/components/WorkflowShareModal.ee.vue | 4 +-
.../src/components/banners/TrialBanner.vue | 5 +-
.../components/banners/TrialOverBanner.vue | 4 +-
.../executions/ExecutionsFilter.vue | 4 +-
.../src/composables/useExecutionDebugging.ts | 5 +-
.../composables/usePageRedirectionHelper.ts | 99 +++++++++++++++++++
.../editor-ui/src/stores/cloudPlan.store.ts | 13 ---
packages/editor-ui/src/stores/ui.store.ts | 53 ----------
.../editor-ui/src/stores/versions.store.ts | 22 -----
.../editor-ui/src/views/SettingsApiView.vue | 4 +-
.../src/views/SettingsExternalSecrets.vue | 6 +-
.../editor-ui/src/views/SettingsLdapView.vue | 6 +-
.../src/views/SettingsLogStreamingView.vue | 4 +-
.../src/views/SettingsSourceControl.vue | 6 +-
.../editor-ui/src/views/SettingsSso.test.ts | 15 ++-
packages/editor-ui/src/views/SettingsSso.vue | 6 +-
.../src/views/SettingsUsageAndPlan.vue | 4 +-
.../src/views/SettingsUsersView.test.ts | 17 +++-
.../editor-ui/src/views/SettingsUsersView.vue | 6 +-
.../editor-ui/src/views/VariablesView.vue | 4 +-
packages/editor-ui/src/views/WorkerView.vue | 6 +-
.../editor-ui/src/views/WorkflowHistory.vue | 5 +-
30 files changed, 215 insertions(+), 137 deletions(-)
create mode 100644 packages/editor-ui/src/composables/usePageRedirectionHelper.ts
diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
index 1c2242195c713..e54f247cea88b 100644
--- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
+++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
@@ -1,6 +1,7 @@
diff --git a/packages/editor-ui/src/components/InviteUsersModal.vue b/packages/editor-ui/src/components/InviteUsersModal.vue
index aa698371e62fb..e43fc7ab4a6f5 100644
--- a/packages/editor-ui/src/components/InviteUsersModal.vue
+++ b/packages/editor-ui/src/components/InviteUsersModal.vue
@@ -15,6 +15,7 @@ import { useSettingsStore } from '@/stores/settings.store';
import { useUIStore } from '@/stores/ui.store';
import { createFormEventBus, createEventBus } from 'n8n-design-system/utils';
import { useClipboard } from '@/composables/useClipboard';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
@@ -43,6 +44,7 @@ export default defineComponent({
return {
clipboard,
...useToast(),
+ ...usePageRedirectionHelper(),
};
},
data() {
@@ -277,7 +279,7 @@ export default defineComponent({
}
},
goToUpgradeAdvancedPermissions() {
- void this.uiStore.goToUpgrade('advanced-permissions', 'upgrade-advanced-permissions');
+ void this.goToUpgrade('advanced-permissions', 'upgrade-advanced-permissions');
},
},
});
diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue
index 33a830af92440..e13f47b3ebdc4 100644
--- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue
+++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue
@@ -56,6 +56,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
import type { BaseTextKey } from '@/plugins/i18n';
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
import { useNodeViewVersionSwitcher } from '@/composables/useNodeViewVersionSwitcher';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const props = defineProps<{
readOnly?: boolean;
@@ -89,6 +90,7 @@ const message = useMessage();
const toast = useToast();
const documentTitle = useDocumentTitle();
const workflowHelpers = useWorkflowHelpers({ router });
+const pageRedirectionHelper = usePageRedirectionHelper();
const isTagsEditEnabled = ref(false);
const isNameEditEnabled = ref(false);
@@ -584,11 +586,11 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise {
break;
}
case 'cloud-admin': {
- void cloudPlanStore.goToDashboardPage();
+ void pageRedirectionHelper.goToDashboard();
break;
}
case 'quickstart':
diff --git a/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts b/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
index d6a760b1d9240..856a922dfbdef 100644
--- a/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
@@ -5,11 +5,11 @@ import { createRouter, createMemoryHistory, useRouter } from 'vue-router';
import { createProjectListItem } from '@/__tests__/data/projects';
import ProjectsNavigation from '@/components/Projects//ProjectNavigation.vue';
import { useProjectsStore } from '@/stores/projects.store';
-import { useUIStore } from '@/stores/ui.store';
import { mockedStore } from '@/__tests__/utils';
import type { Project } from '@/types/projects.types';
import { VIEWS } from '@/constants';
import { useToast } from '@/composables/useToast';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
vi.mock('vue-router', async () => {
const actual = await vi.importActual('vue-router');
@@ -36,6 +36,15 @@ vi.mock('@/composables/useToast', () => {
};
});
+vi.mock('@/composables/usePageRedirectionHelper', () => {
+ const goToUpgrade = vi.fn();
+ return {
+ usePageRedirectionHelper: () => ({
+ goToUpgrade,
+ }),
+ };
+});
+
const renderComponent = createComponentRenderer(ProjectsNavigation, {
global: {
plugins: [
@@ -56,7 +65,7 @@ const renderComponent = createComponentRenderer(ProjectsNavigation, {
let router: ReturnType;
let toast: ReturnType;
let projectsStore: ReturnType>;
-let uiStore: ReturnType>;
+let pageRedirectionHelper: ReturnType;
const personalProjects = Array.from({ length: 3 }, createProjectListItem);
const teamProjects = Array.from({ length: 3 }, () => createProjectListItem('team'));
@@ -67,9 +76,9 @@ describe('ProjectsNavigation', () => {
router = useRouter();
toast = useToast();
+ pageRedirectionHelper = usePageRedirectionHelper();
projectsStore = mockedStore(useProjectsStore);
- uiStore = mockedStore(useUIStore);
});
it('should not throw an error', () => {
@@ -144,7 +153,9 @@ describe('ProjectsNavigation', () => {
expect(getByText(/You have reached the Free plan limit of 3/)).toBeVisible();
await userEvent.click(getByText('View plans'));
- expect(uiStore.goToUpgrade).toHaveBeenCalledWith('rbac', 'upgrade-rbac');
+ console.log(pageRedirectionHelper);
+
+ expect(pageRedirectionHelper.goToUpgrade).toHaveBeenCalledWith('rbac', 'upgrade-rbac');
});
it('should show "Projects" title and Personal project when the feature is enabled', async () => {
diff --git a/packages/editor-ui/src/components/Projects/ProjectNavigation.vue b/packages/editor-ui/src/components/Projects/ProjectNavigation.vue
index aa4fa6ab7c6dc..47513ea1d6b64 100644
--- a/packages/editor-ui/src/components/Projects/ProjectNavigation.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectNavigation.vue
@@ -7,8 +7,8 @@ import { VIEWS } from '@/constants';
import { useProjectsStore } from '@/stores/projects.store';
import type { ProjectListItem } from '@/types/projects.types';
import { useToast } from '@/composables/useToast';
-import { useUIStore } from '@/stores/ui.store';
import { sortByProperty } from '@/utils/sortUtils';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
type Props = {
collapsed: boolean;
@@ -21,7 +21,7 @@ const router = useRouter();
const locale = useI18n();
const toast = useToast();
const projectsStore = useProjectsStore();
-const uiStore = useUIStore();
+const pageRedirectionHelper = usePageRedirectionHelper();
const isCreatingProject = ref(false);
const isComponentMounted = ref(false);
@@ -99,7 +99,7 @@ const canCreateProjects = computed(
);
const goToUpgrade = async () => {
- await uiStore.goToUpgrade('rbac', 'upgrade-rbac');
+ await pageRedirectionHelper.goToUpgrade('rbac', 'upgrade-rbac');
};
onMounted(async () => {
diff --git a/packages/editor-ui/src/components/Projects/ProjectRoleUpgradeDialog.vue b/packages/editor-ui/src/components/Projects/ProjectRoleUpgradeDialog.vue
index 6a125181eb23a..4176c2e0c0ca5 100644
--- a/packages/editor-ui/src/components/Projects/ProjectRoleUpgradeDialog.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectRoleUpgradeDialog.vue
@@ -1,6 +1,6 @@
diff --git a/packages/editor-ui/src/components/UpdatesPanel.vue b/packages/editor-ui/src/components/UpdatesPanel.vue
index a45c09905c9ba..a5f1332bc433e 100644
--- a/packages/editor-ui/src/components/UpdatesPanel.vue
+++ b/packages/editor-ui/src/components/UpdatesPanel.vue
@@ -5,8 +5,10 @@ import VersionCard from './VersionCard.vue';
import { VERSIONS_MODAL_KEY } from '../constants';
import { useVersionsStore } from '@/stores/versions.store';
import { useI18n } from '@/composables/useI18n';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const versionsStore = useVersionsStore();
+const pageRedirectionHelper = usePageRedirectionHelper();
const i18n = useI18n();
@@ -55,7 +57,7 @@ const i18n = useI18n();
size="large"
:class="$style['link']"
:bold="true"
- @click="versionsStore.goToVersionsPage()"
+ @click="pageRedirectionHelper.goToVersions()"
>
diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
index 886bd90003e11..d46c1df3b14d7 100644
--- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
+++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
@@ -30,6 +30,7 @@ import type { ProjectListItem, ProjectSharingData, Project } from '@/types/proje
import { ProjectTypes } from '@/types/projects.types';
import { useRolesStore } from '@/stores/roles.store';
import type { RoleMap } from '@/types/roles.types';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
export default defineComponent({
name: 'WorkflowShareModal',
@@ -47,6 +48,7 @@ export default defineComponent({
return {
...useToast(),
...useMessage(),
+ ...usePageRedirectionHelper(),
};
},
data() {
@@ -235,7 +237,7 @@ export default defineComponent({
});
},
goToUpgrade() {
- void this.uiStore.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
+ void this.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
},
async initialize() {
if (this.isSharingEnabled) {
diff --git a/packages/editor-ui/src/components/banners/TrialBanner.vue b/packages/editor-ui/src/components/banners/TrialBanner.vue
index f09a561df5c37..074bf6ee2d5ae 100644
--- a/packages/editor-ui/src/components/banners/TrialBanner.vue
+++ b/packages/editor-ui/src/components/banners/TrialBanner.vue
@@ -5,11 +5,14 @@ import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { computed } from 'vue';
import { useUIStore } from '@/stores/ui.store';
import type { CloudPlanAndUsageData } from '@/Interface';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const PROGRESS_BAR_MINIMUM_THRESHOLD = 8;
const cloudPlanStore = useCloudPlanStore();
+const pageRedirectionHelper = usePageRedirectionHelper();
+
const trialDaysLeft = computed(() => -1 * cloudPlanStore.trialDaysLeft);
const messageText = computed(() => {
return locale.baseText('banners.trial.message', {
@@ -49,7 +52,7 @@ const currentExecutions = computed(() => {
});
function onUpdatePlanClick() {
- void useUIStore().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
+ void pageRedirectionHelper.goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
}
diff --git a/packages/editor-ui/src/components/banners/TrialOverBanner.vue b/packages/editor-ui/src/components/banners/TrialOverBanner.vue
index 9080ad0fed9d3..45eedf9c14205 100644
--- a/packages/editor-ui/src/components/banners/TrialOverBanner.vue
+++ b/packages/editor-ui/src/components/banners/TrialOverBanner.vue
@@ -1,10 +1,10 @@
diff --git a/packages/editor-ui/src/components/executions/ExecutionsFilter.vue b/packages/editor-ui/src/components/executions/ExecutionsFilter.vue
index b76b51f24defb..11cf5b8cc08d4 100644
--- a/packages/editor-ui/src/components/executions/ExecutionsFilter.vue
+++ b/packages/editor-ui/src/components/executions/ExecutionsFilter.vue
@@ -15,6 +15,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
import type { Placement } from '@floating-ui/core';
import { useDebounce } from '@/composables/useDebounce';
import AnnotationTagsDropdown from '@/components/AnnotationTagsDropdown.ee.vue';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
export type ExecutionFilterProps = {
workflows?: Array;
@@ -29,6 +30,7 @@ const uiStore = useUIStore();
const { debounce } = useDebounce();
const telemetry = useTelemetry();
+const pageRedirectionHelper = usePageRedirectionHelper();
const props = withDefaults(defineProps(), {
workflows: () => [] as Array,
@@ -149,7 +151,7 @@ const onFilterReset = () => {
};
const goToUpgrade = () => {
- void uiStore.goToUpgrade('custom-data-filter', 'upgrade-custom-data-filter');
+ void pageRedirectionHelper.goToUpgrade('custom-data-filter', 'upgrade-custom-data-filter');
};
onBeforeMount(() => {
diff --git a/packages/editor-ui/src/composables/useExecutionDebugging.ts b/packages/editor-ui/src/composables/useExecutionDebugging.ts
index cf59964bb8706..90f2a32cae907 100644
--- a/packages/editor-ui/src/composables/useExecutionDebugging.ts
+++ b/packages/editor-ui/src/composables/useExecutionDebugging.ts
@@ -17,6 +17,7 @@ import { useTelemetry } from './useTelemetry';
import { useRootStore } from '@/stores/root.store';
import { isFullExecutionResponse } from '@/utils/typeGuards';
import { sanitizeHtml } from '@/utils/htmlUtils';
+import { usePageRedirectionHelper } from './usePageRedirectionHelper';
export const useExecutionDebugging = () => {
const telemetry = useTelemetry();
@@ -29,6 +30,8 @@ export const useExecutionDebugging = () => {
const settingsStore = useSettingsStore();
const uiStore = useUIStore();
+ const pageRedirectionHelper = usePageRedirectionHelper();
+
const isDebugEnabled = computed(
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DebugInEditor],
);
@@ -147,7 +150,7 @@ export const useExecutionDebugging = () => {
title: i18n.baseText(uiStore.contextBasedTranslationKeys.feature.unavailable.title),
footerButtonAction: () => {
uiStore.closeModal(DEBUG_PAYWALL_MODAL_KEY);
- void uiStore.goToUpgrade('debug', 'upgrade-debug');
+ void pageRedirectionHelper.goToUpgrade('debug', 'upgrade-debug');
},
},
});
diff --git a/packages/editor-ui/src/composables/usePageRedirectionHelper.ts b/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
new file mode 100644
index 0000000000000..5515369afbba7
--- /dev/null
+++ b/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
@@ -0,0 +1,99 @@
+import { useUsersStore } from '@/stores/users.store';
+import { useCloudPlanStore } from '@/stores/cloudPlan.store';
+import { useVersionsStore } from '@/stores/versions.store';
+import { useTelemetry } from './useTelemetry';
+import { useSettingsStore } from '@/stores/settings.store';
+import type { CloudUpdateLinkSourceType, UTMCampaign } from '@/Interface';
+import { N8N_PRICING_PAGE_URL } from '@/constants';
+
+export function usePageRedirectionHelper() {
+ const usersStore = useUsersStore();
+ const cloudPlanStore = useCloudPlanStore();
+ const versionsStore = useVersionsStore();
+ const telemetry = useTelemetry();
+ const settingsStore = useSettingsStore();
+
+ /**
+ * If the user is an instance owner in the cloud, it generates an auto-login link to the
+ * cloud dashboard that redirects the user to the manage page where they can upgrade to a new n8n version.
+ * Otherwise, it redirect them to our docs.
+ */
+ const goToVersions = async () => {
+ let versionsLink = versionsStore.infoUrl;
+
+ if (usersStore.userIsOwnerInCloudDeployment) {
+ versionsLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/manage',
+ });
+ }
+
+ location.href = versionsLink;
+ };
+
+ const goToDashboard = async () => {
+ if (usersStore.userIsOwnerInCloudDeployment) {
+ const dashboardLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/dashboard',
+ });
+
+ location.href = dashboardLink;
+ }
+
+ return;
+ };
+
+ const goToUpgrade = async (
+ source: CloudUpdateLinkSourceType,
+ utm_campaign: UTMCampaign,
+ mode: 'open' | 'redirect' = 'open',
+ ) => {
+ const { usageLeft, trialDaysLeft, userIsTrialing } = cloudPlanStore;
+ const { executionsLeft, workflowsLeft } = usageLeft;
+ const deploymentType = settingsStore.deploymentType;
+
+ telemetry.track('User clicked upgrade CTA', {
+ source,
+ isTrial: userIsTrialing,
+ deploymentType,
+ trialDaysLeft,
+ executionsLeft,
+ workflowsLeft,
+ });
+
+ const upgradeLink = await generateUpgradeLink(source, utm_campaign);
+
+ if (mode === 'open') {
+ window.open(upgradeLink, '_blank');
+ } else {
+ location.href = upgradeLink;
+ }
+ };
+
+ const generateUpgradeLink = async (source: string, utm_campaign: string) => {
+ let upgradeLink = N8N_PRICING_PAGE_URL;
+
+ if (usersStore.userIsOwnerInCloudDeployment) {
+ upgradeLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
+ redirectionPath: '/account/change-plan',
+ });
+ }
+
+ const url = new URL(upgradeLink);
+
+ if (utm_campaign) {
+ url.searchParams.set('utm_campaign', utm_campaign);
+ }
+
+ if (source) {
+ url.searchParams.set('source', source);
+ }
+
+ return url.toString();
+ };
+
+ return {
+ goToDashboard,
+ goToVersions,
+ goToUpgrade,
+ };
+}
diff --git a/packages/editor-ui/src/stores/cloudPlan.store.ts b/packages/editor-ui/src/stores/cloudPlan.store.ts
index e51de0317109b..4f8c29ebef2d7 100644
--- a/packages/editor-ui/src/stores/cloudPlan.store.ts
+++ b/packages/editor-ui/src/stores/cloudPlan.store.ts
@@ -167,18 +167,6 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
state.initialized = true;
};
- const goToDashboardPage = async () => {
- if (usersStore.userIsOwnerInCloudDeployment) {
- const dashboardLink = await generateCloudDashboardAutoLoginLink({
- redirectionPath: '/dashboard',
- });
-
- location.href = dashboardLink;
- }
-
- return;
- };
-
const generateCloudDashboardAutoLoginLink = async (data: {
redirectionPath: string;
}) => {
@@ -202,7 +190,6 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
currentUsageData,
trialExpired,
allExecutionsUsed,
- goToDashboardPage,
generateCloudDashboardAutoLoginLink,
initialize,
getOwnerCurrentPlan,
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index f2c33ca5bba0d..f526292d31405 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -560,33 +560,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
return parameters;
};
- const goToUpgrade = async (
- source: CloudUpdateLinkSourceType,
- utm_campaign: UTMCampaign,
- mode: 'open' | 'redirect' = 'open',
- ) => {
- const { usageLeft, trialDaysLeft, userIsTrialing } = cloudPlanStore;
- const { executionsLeft, workflowsLeft } = usageLeft;
- const deploymentType = settingsStore.deploymentType;
-
- telemetry.track('User clicked upgrade CTA', {
- source,
- isTrial: userIsTrialing,
- deploymentType,
- trialDaysLeft,
- executionsLeft,
- workflowsLeft,
- });
-
- const upgradeLink = await generateUpgradeLink(source, utm_campaign, deploymentType);
-
- if (mode === 'open') {
- window.open(upgradeLink, '_blank');
- } else {
- location.href = upgradeLink;
- }
- };
-
const removeBannerFromStack = (name: BannerName) => {
bannerStack.value = bannerStack.value.filter((bannerName) => bannerName !== name);
};
@@ -699,7 +672,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
setCurlCommand,
toggleSidebarMenuCollapse,
getCurlToJson,
- goToUpgrade,
removeBannerFromStack,
dismissBanner,
updateBannersHeight,
@@ -751,28 +723,3 @@ export const listenForModalChanges = (opts: {
});
});
};
-
-export const generateUpgradeLink = async (
- source: string,
- utm_campaign: string,
- deploymentType: string,
-) => {
- let upgradeLink = N8N_PRICING_PAGE_URL;
- if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) {
- upgradeLink = await generateCloudDashboardAutoLoginLink({
- redirectionPath: '/account/change-plan',
- });
- }
-
- const url = new URL(upgradeLink);
-
- if (utm_campaign) {
- url.searchParams.set('utm_campaign', utm_campaign);
- }
-
- if (source) {
- url.searchParams.set('source', source);
- }
-
- return url.toString();
-};
diff --git a/packages/editor-ui/src/stores/versions.store.ts b/packages/editor-ui/src/stores/versions.store.ts
index 2999f86582313..8455d5d15f515 100644
--- a/packages/editor-ui/src/stores/versions.store.ts
+++ b/packages/editor-ui/src/stores/versions.store.ts
@@ -7,8 +7,6 @@ import { useRootStore } from './root.store';
import { useToast } from '@/composables/useToast';
import { useUIStore } from '@/stores/ui.store';
import { computed, ref } from 'vue';
-import { useCloudPlanStore } from '@/stores/cloudPlan.store';
-import { useUsersStore } from './users.store';
type SetVersionParams = { versions: IVersion[]; currentVersion: string };
@@ -19,8 +17,6 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
const { showToast } = useToast();
const uiStore = useUIStore();
- const usersStore = useUsersStore();
- const cloudPlanStore = useCloudPlanStore();
// ---------------------------------------------------------------------------
// #region Computed
@@ -100,23 +96,6 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
}
};
- /**
- * If the user is an instance owner in the cloud, it generates an auto-login link to the
- * cloud dashboard that redirects the user to the manage page where they can upgrade to a new n8n version.
- * Otherwise, it redirect them to our docs.
- */
- const goToVersionsPage = async () => {
- let versionsLink = infoUrl.value;
-
- if (usersStore.userIsOwnerInCloudDeployment) {
- versionsLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
- redirectionPath: '/manage',
- });
- }
-
- location.href = versionsLink;
- };
-
// #endregion
return {
@@ -125,7 +104,6 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
hasVersionUpdates,
areNotificationsEnabled,
infoUrl,
- goToVersionsPage,
fetchVersions,
setVersions,
setVersionNotificationSettings,
diff --git a/packages/editor-ui/src/views/SettingsApiView.vue b/packages/editor-ui/src/views/SettingsApiView.vue
index 2bb67d04400ac..3d912900105eb 100644
--- a/packages/editor-ui/src/views/SettingsApiView.vue
+++ b/packages/editor-ui/src/views/SettingsApiView.vue
@@ -13,6 +13,7 @@ import { useUIStore } from '@/stores/ui.store';
import { useUsersStore } from '@/stores/users.store';
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { DOCS_DOMAIN, MODAL_CONFIRM } from '@/constants';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
export default defineComponent({
name: 'SettingsApiView',
@@ -24,6 +25,7 @@ export default defineComponent({
...useToast(),
...useMessage(),
...useUIStore(),
+ ...usePageRedirectionHelper(),
documentTitle: useDocumentTitle(),
};
},
@@ -70,7 +72,7 @@ export default defineComponent({
},
methods: {
onUpgrade() {
- void this.uiStore.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
+ void this.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
},
async showDeleteModal() {
const confirmed = await this.confirm(
diff --git a/packages/editor-ui/src/views/SettingsExternalSecrets.vue b/packages/editor-ui/src/views/SettingsExternalSecrets.vue
index 30aaebeae1b7e..0d05bf60a2287 100644
--- a/packages/editor-ui/src/views/SettingsExternalSecrets.vue
+++ b/packages/editor-ui/src/views/SettingsExternalSecrets.vue
@@ -1,5 +1,4 @@
diff --git a/packages/editor-ui/src/views/SettingsLdapView.vue b/packages/editor-ui/src/views/SettingsLdapView.vue
index 41227c854e122..c1e71cd857699 100644
--- a/packages/editor-ui/src/views/SettingsLdapView.vue
+++ b/packages/editor-ui/src/views/SettingsLdapView.vue
@@ -20,10 +20,10 @@ import { ElTable, ElTableColumn } from 'element-plus';
import type { Events } from 'v3-infinite-loading';
import InfiniteLoading from 'v3-infinite-loading';
import { useSettingsStore } from '@/stores/settings.store';
-import { useUIStore } from '@/stores/ui.store';
import { createFormEventBus } from 'n8n-design-system/utils';
import type { TableColumnCtx } from 'element-plus';
import { useI18n } from '@/composables/useI18n';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
type TableRow = {
status: string;
@@ -67,9 +67,9 @@ const toast = useToast();
const i18n = useI18n();
const message = useMessage();
const documentTitle = useDocumentTitle();
+const pageRedirectionHelper = usePageRedirectionHelper();
const settingsStore = useSettingsStore();
-const uiStore = useUIStore();
const dataTable = ref([]);
const tableKey = ref(0);
@@ -89,7 +89,7 @@ const ldapConfigFormRef = ref<{ getValues: () => LDAPConfigForm }>();
const isLDAPFeatureEnabled = computed(() => settingsStore.settings.enterprise.ldap);
-const goToUpgrade = async () => await uiStore.goToUpgrade('ldap', 'upgrade-ldap');
+const goToUpgrade = async () => await pageRedirectionHelper.goToUpgrade('ldap', 'upgrade-ldap');
const cellClassStyle = ({ row, column }: CellClassStyleMethodParams): CSSProperties => {
if (column.property === 'status') {
diff --git a/packages/editor-ui/src/views/SettingsLogStreamingView.vue b/packages/editor-ui/src/views/SettingsLogStreamingView.vue
index fae75b2604c21..5cc79c009d442 100644
--- a/packages/editor-ui/src/views/SettingsLogStreamingView.vue
+++ b/packages/editor-ui/src/views/SettingsLogStreamingView.vue
@@ -14,6 +14,7 @@ import { deepCopy, defaultMessageEventBusDestinationOptions } from 'n8n-workflow
import EventDestinationCard from '@/components/SettingsLogStreaming/EventDestinationCard.ee.vue';
import { createEventBus } from 'n8n-design-system/utils';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
export default defineComponent({
name: 'SettingsLogStreamingView',
@@ -28,6 +29,7 @@ export default defineComponent({
disableLicense: false,
allDestinations: [] as MessageEventBusDestinationOptions[],
documentTitle: useDocumentTitle(),
+ pageRedirectionHelper: usePageRedirectionHelper(),
};
},
async mounted() {
@@ -117,7 +119,7 @@ export default defineComponent({
this.$forceUpdate();
},
goToUpgrade() {
- void this.uiStore.goToUpgrade('log-streaming', 'upgrade-log-streaming');
+ void this.pageRedirectionHelper.goToUpgrade('log-streaming', 'upgrade-log-streaming');
},
storeHasItems(): boolean {
return this.logStreamingStore.items && Object.keys(this.logStreamingStore.items).length > 0;
diff --git a/packages/editor-ui/src/views/SettingsSourceControl.vue b/packages/editor-ui/src/views/SettingsSourceControl.vue
index 3928556104a8a..eb29042230f96 100644
--- a/packages/editor-ui/src/views/SettingsSourceControl.vue
+++ b/packages/editor-ui/src/views/SettingsSourceControl.vue
@@ -3,7 +3,6 @@ import { computed, reactive, ref, onMounted } from 'vue';
import type { Rule, RuleGroup } from 'n8n-design-system/types';
import { MODAL_CONFIRM } from '@/constants';
import { useSourceControlStore } from '@/stores/sourceControl.store';
-import { useUIStore } from '@/stores/ui.store';
import { useToast } from '@/composables/useToast';
import { useLoadingService } from '@/composables/useLoadingService';
import { useI18n } from '@/composables/useI18n';
@@ -12,10 +11,11 @@ import { useDocumentTitle } from '@/composables/useDocumentTitle';
import CopyInput from '@/components/CopyInput.vue';
import type { TupleToUnion } from '@/utils/typeHelpers';
import type { SshKeyTypes } from '@/Interface';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const locale = useI18n();
const sourceControlStore = useSourceControlStore();
-const uiStore = useUIStore();
+const pageRedirectionHelper = usePageRedirectionHelper();
const toast = useToast();
const message = useMessage();
const documentTitle = useDocumentTitle();
@@ -102,7 +102,7 @@ const onSelect = async (b: string) => {
};
const goToUpgrade = () => {
- void uiStore.goToUpgrade('source-control', 'upgrade-source-control');
+ void pageRedirectionHelper.goToUpgrade('source-control', 'upgrade-source-control');
};
const initialize = async () => {
diff --git a/packages/editor-ui/src/views/SettingsSso.test.ts b/packages/editor-ui/src/views/SettingsSso.test.ts
index 793f28bda267a..0306bde35f210 100644
--- a/packages/editor-ui/src/views/SettingsSso.test.ts
+++ b/packages/editor-ui/src/views/SettingsSso.test.ts
@@ -2,10 +2,10 @@ import { createTestingPinia } from '@pinia/testing';
import { createComponentRenderer } from '@/__tests__/render';
import SettingsSso from './SettingsSso.vue';
import { useSSOStore } from '@/stores/sso.store';
-import { useUIStore } from '@/stores/ui.store';
import { within, waitFor } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { mockedStore } from '@/__tests__/utils';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const renderView = createComponentRenderer(SettingsSso);
@@ -38,6 +38,15 @@ vi.mock('@/composables/useMessage', () => ({
}),
}));
+vi.mock('@/composables/usePageRedirectionHelper', () => {
+ const goToUpgrade = vi.fn();
+ return {
+ usePageRedirectionHelper: () => ({
+ goToUpgrade,
+ }),
+ };
+});
+
describe('SettingsSso View', () => {
beforeEach(() => {
telemetryTrack.mockReset();
@@ -50,7 +59,7 @@ describe('SettingsSso View', () => {
const ssoStore = mockedStore(useSSOStore);
ssoStore.isEnterpriseSamlEnabled = false;
- const uiStore = useUIStore();
+ const pageRedirectionHelper = usePageRedirectionHelper();
const { getByTestId } = renderView({ pinia });
@@ -58,7 +67,7 @@ describe('SettingsSso View', () => {
expect(actionBox).toBeInTheDocument();
await userEvent.click(await within(actionBox).findByText('See plans'));
- expect(uiStore.goToUpgrade).toHaveBeenCalledWith('sso', 'upgrade-sso');
+ expect(pageRedirectionHelper.goToUpgrade).toHaveBeenCalledWith('sso', 'upgrade-sso');
});
it('should show user SSO config', async () => {
diff --git a/packages/editor-ui/src/views/SettingsSso.vue b/packages/editor-ui/src/views/SettingsSso.vue
index a9043c41aef35..64107ccba4286 100644
--- a/packages/editor-ui/src/views/SettingsSso.vue
+++ b/packages/editor-ui/src/views/SettingsSso.vue
@@ -1,7 +1,6 @@
diff --git a/packages/editor-ui/src/views/WorkflowHistory.vue b/packages/editor-ui/src/views/WorkflowHistory.vue
index 567cae9a41cd6..792f6b9ef51e3 100644
--- a/packages/editor-ui/src/views/WorkflowHistory.vue
+++ b/packages/editor-ui/src/views/WorkflowHistory.vue
@@ -20,6 +20,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { telemetry } from '@/plugins/telemetry';
import { useRootStore } from '@/stores/root.store';
import { getResourcePermissions } from '@/permissions';
+import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
type WorkflowHistoryActionRecord = {
[K in Uppercase]: Lowercase;
@@ -46,6 +47,8 @@ const route = useRoute();
const router = useRouter();
const i18n = useI18n();
const toast = useToast();
+const pageRedirectionHelper = usePageRedirectionHelper();
+
const workflowHistoryStore = useWorkflowHistoryStore();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
@@ -296,7 +299,7 @@ const onPreview = async ({ event, id }: { event: MouseEvent; id: WorkflowVersion
};
const onUpgrade = () => {
- void uiStore.goToUpgrade('workflow-history', 'upgrade-workflow-history');
+ void pageRedirectionHelper.goToUpgrade('workflow-history', 'upgrade-workflow-history');
};
watchEffect(async () => {
From 1f14e5c1eb4ab26477b3a4afad102fe8208a9d70 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 17:04:00 -0400
Subject: [PATCH 08/15] improve comments
---
.../editor-ui/src/composables/usePageRedirectionHelper.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/packages/editor-ui/src/composables/usePageRedirectionHelper.ts b/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
index 5515369afbba7..39f5d58823bc9 100644
--- a/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
+++ b/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
@@ -15,7 +15,7 @@ export function usePageRedirectionHelper() {
/**
* If the user is an instance owner in the cloud, it generates an auto-login link to the
- * cloud dashboard that redirects the user to the manage page where they can upgrade to a new n8n version.
+ * cloud dashboard that redirects the user to the /manage page where they can upgrade to a new n8n version.
* Otherwise, it redirect them to our docs.
*/
const goToVersions = async () => {
@@ -42,6 +42,12 @@ export function usePageRedirectionHelper() {
return;
};
+ /**
+ * If the user is an instance owner in the cloud, it generates an auto-login link to the
+ * cloud dashboard that redirects the user to the /account/change-plan page where they upgrade/downgrade the current plan.
+ * Otherwise, it redirect them our website.
+ */
+
const goToUpgrade = async (
source: CloudUpdateLinkSourceType,
utm_campaign: UTMCampaign,
From c9daab42863d3dfbb779d3663bf3c71d6f2d8a38 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 19:57:24 -0400
Subject: [PATCH 09/15] add tests
---
.../usePageRedirectionHelper.test.ts | 234 ++++++++++++++++++
.../editor-ui/src/stores/__tests__/ui.test.ts | 53 +---
packages/editor-ui/src/stores/ui.store.ts | 12 +-
3 files changed, 236 insertions(+), 63 deletions(-)
create mode 100644 packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
diff --git a/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
new file mode 100644
index 0000000000000..3292c45f090f7
--- /dev/null
+++ b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
@@ -0,0 +1,234 @@
+import { ROLE } from '@/constants';
+import { useCloudPlanStore } from '@/stores/cloudPlan.store';
+import { useSettingsStore } from '@/stores/settings.store';
+import { merge } from 'lodash-es';
+import { usePageRedirectionHelper } from '../usePageRedirectionHelper';
+import { defaultSettings } from '@/__tests__/defaults';
+import { useUsersStore } from '@/stores/users.store';
+import { createPinia, setActivePinia } from 'pinia';
+import * as cloudPlanApi from '@/api/cloudPlans';
+import { useVersionsStore } from '@/stores/versions.store';
+import { useTelemetry } from '../useTelemetry';
+
+let settingsStore: ReturnType;
+let cloudPlanStore: ReturnType;
+let usersStore: ReturnType;
+let versionStore: ReturnType;
+let pageRedirectionHelper: ReturnType;
+
+vi.mock('@/composables/useTelemetry', () => {
+ const track = vi.fn();
+ return {
+ useTelemetry: () => {
+ return {
+ track,
+ };
+ },
+ };
+});
+
+describe('usePageRedirectionHelper', () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ beforeEach(() => {
+ setActivePinia(createPinia());
+ settingsStore = useSettingsStore();
+ cloudPlanStore = useCloudPlanStore();
+ usersStore = useUsersStore();
+ versionStore = useVersionsStore();
+
+ pageRedirectionHelper = usePageRedirectionHelper();
+
+ vi.spyOn(cloudPlanApi, 'getAdminPanelLoginCode').mockResolvedValue({
+ code: '123',
+ });
+
+ const url = 'https://test.app.n8n.cloud';
+
+ Object.defineProperty(window, 'location', {
+ value: {
+ href: url,
+ },
+ writable: true,
+ });
+
+ versionStore.setVersionNotificationSettings({
+ enabled: true,
+ endpoint: '',
+ infoUrl:
+ 'https://docs.n8n.io/release-notes/#n8n1652?utm_source=n8n_app&utm_medium=instance_upgrade_releases',
+ });
+ });
+
+ test.each([
+ [
+ 'default',
+ 'production',
+ ROLE.Owner,
+ 'https://n8n.io/pricing?utm_campaign=upgrade-api&source=advanced-permissions',
+ ],
+ [
+ 'default',
+ 'development',
+ ROLE.Owner,
+ 'https://n8n.io/pricing?utm_campaign=upgrade-api&source=advanced-permissions',
+ ],
+ [
+ 'cloud',
+ 'production',
+ ROLE.Owner,
+ `https://app.n8n.cloud/login?code=123&returnPath=${encodeURIComponent(
+ '/account/change-plan',
+ )}&utm_campaign=upgrade-api&source=advanced-permissions`,
+ ],
+ [
+ 'cloud',
+ 'production',
+ ROLE.Member,
+ 'https://n8n.io/pricing?utm_campaign=upgrade-api&source=advanced-permissions',
+ ],
+ ])(
+ '"goToUpgrade" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"',
+ async (type, environment, role, expectation) => {
+ // Arrange
+
+ usersStore.addUsers([
+ {
+ id: '1',
+ isPending: false,
+ role,
+ },
+ ]);
+
+ usersStore.currentUserId = '1';
+
+ const telemetry = useTelemetry();
+
+ settingsStore.setSettings(
+ merge({}, defaultSettings, {
+ deployment: {
+ type,
+ },
+ license: {
+ environment,
+ },
+ }),
+ );
+
+ // Act
+
+ await pageRedirectionHelper.goToUpgrade('advanced-permissions', 'upgrade-api', 'redirect');
+
+ // Assert
+
+ expect(telemetry.track).toHaveBeenCalledWith(
+ 'User clicked upgrade CTA',
+ expect.objectContaining({
+ source: 'advanced-permissions',
+ isTrial: false,
+ deploymentType: type,
+ trialDaysLeft: expect.any(Number),
+ executionsLeft: expect.any(Number),
+ workflowsLeft: expect.any(Number),
+ }),
+ );
+
+ expect(location.href).toBe(expectation);
+ },
+ );
+
+ test.each([
+ [
+ 'cloud',
+ 'production',
+ ROLE.Owner,
+ `https://app.n8n.cloud/login?code=123&returnPath=${encodeURIComponent('/manage')}`,
+ ],
+ [
+ 'cloud',
+ 'production',
+ ROLE.Member,
+ 'https://docs.n8n.io/release-notes/#n8n1652?utm_source=n8n_app&utm_medium=instance_upgrade_releases',
+ ],
+ ])(
+ '"goToVersions" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"',
+ async (type, environment, role, expectation) => {
+ // Arrange
+
+ usersStore.addUsers([
+ {
+ id: '1',
+ isPending: false,
+ role,
+ },
+ ]);
+
+ usersStore.currentUserId = '1';
+
+ settingsStore.setSettings(
+ merge({}, defaultSettings, {
+ deployment: {
+ type,
+ },
+ license: {
+ environment,
+ },
+ }),
+ );
+
+ // Act
+
+ await pageRedirectionHelper.goToVersions();
+
+ // Assert
+
+ expect(location.href).toBe(expectation);
+ },
+ );
+
+ test.each([
+ [
+ 'cloud',
+ 'production',
+ ROLE.Owner,
+ `https://app.n8n.cloud/login?code=123&returnPath=${encodeURIComponent('/dashboard')}`,
+ ],
+ ['cloud', 'production', ROLE.Member, 'https://test.app.n8n.cloud'],
+ ])(
+ '"goToDashboard" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"',
+ async (type, environment, role, expectation) => {
+ // Arrange
+
+ usersStore.addUsers([
+ {
+ id: '1',
+ isPending: false,
+ role,
+ },
+ ]);
+
+ usersStore.currentUserId = '1';
+
+ settingsStore.setSettings(
+ merge({}, defaultSettings, {
+ deployment: {
+ type,
+ },
+ license: {
+ environment,
+ },
+ }),
+ );
+
+ // Act
+
+ await pageRedirectionHelper.goToDashboard();
+
+ // Assert
+
+ expect(location.href).toBe(expectation);
+ },
+ );
+});
diff --git a/packages/editor-ui/src/stores/__tests__/ui.test.ts b/packages/editor-ui/src/stores/__tests__/ui.test.ts
index 995f87d9f3228..7de20d2502594 100644
--- a/packages/editor-ui/src/stores/__tests__/ui.test.ts
+++ b/packages/editor-ui/src/stores/__tests__/ui.test.ts
@@ -1,5 +1,5 @@
import { createPinia, setActivePinia } from 'pinia';
-import { generateUpgradeLink, 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-es';
@@ -70,57 +70,6 @@ describe('UI store', () => {
});
});
- test.each([
- [
- 'default',
- 'production',
- ROLE.Owner,
- 'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
- ],
- [
- 'default',
- 'development',
- ROLE.Owner,
- 'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
- ],
- [
- 'cloud',
- 'production',
- ROLE.Owner,
- `https://app.n8n.cloud/login?code=123&returnPath=${encodeURIComponent(
- '/account/change-plan',
- )}&utm_campaign=utm-test-campaign&source=test_source`,
- ],
- [
- 'cloud',
- 'production',
- ROLE.Member,
- 'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
- ],
- ])(
- '"generateUpgradeLink" should generate the correct URL for "%s" deployment and "%s" license environment and user role "%s"',
- async (type, environment, role, expectation) => {
- setUser(role as IRole);
-
- settingsStore.setSettings(
- merge({}, defaultSettings, {
- deployment: {
- type,
- },
- license: {
- environment,
- },
- instanceId: '123abc',
- versionCli: '0.223.0',
- }),
- );
-
- const updateLinkUrl = await generateUpgradeLink('test_source', 'utm-test-campaign', type);
-
- expect(updateLinkUrl).toBe(expectation);
- },
- );
-
it('should add non-production license banner to stack based on enterprise settings', () => {
settingsStore.setSettings(
merge({}, defaultSettings, {
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index f526292d31405..747f43e4b7a87 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -31,7 +31,6 @@ import {
SOURCE_CONTROL_PUSH_MODAL_KEY,
SOURCE_CONTROL_PULL_MODAL_KEY,
DEBUG_PAYWALL_MODAL_KEY,
- N8N_PRICING_PAGE_URL,
WORKFLOW_HISTORY_VERSION_RESTORE,
SETUP_CREDENTIALS_MODAL_KEY,
PROJECT_MOVE_RESOURCE_MODAL,
@@ -40,10 +39,8 @@ import {
COMMUNITY_PLUS_ENROLLMENT_MODAL,
} from '@/constants';
import type {
- CloudUpdateLinkSourceType,
IFakeDoorLocation,
INodeUi,
- UTMCampaign,
XYPosition,
Modals,
NewCredentialsModal,
@@ -56,10 +53,8 @@ import type {
import { defineStore } from 'pinia';
import { useRootStore } from '@/stores/root.store';
import * as curlParserApi from '@/api/curlHelper';
-import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useSettingsStore } from '@/stores/settings.store';
-import { hasPermission } from '@/utils/rbac/permissions';
import { useUsersStore } from '@/stores/users.store';
import { dismissBannerPermanently } from '@/api/ui';
import type { BannerName } from 'n8n-workflow';
@@ -72,10 +67,9 @@ import {
} from './ui.utils';
import { computed, ref } from 'vue';
import type { Connection } from '@vue-flow/core';
-import { useTelemetry } from '@/composables/useTelemetry';
-import { useVersionsStore } from '@/stores/versions.store';
let savedTheme: ThemeOption = 'system';
+
try {
const value = getThemeOverride();
if (isValidTheme(value)) {
@@ -206,11 +200,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
const settingsStore = useSettingsStore();
const workflowsStore = useWorkflowsStore();
const rootStore = useRootStore();
- const telemetry = useTelemetry();
- const cloudPlanStore = useCloudPlanStore();
const userStore = useUsersStore();
- const versionsStore = useVersionsStore();
- const usersStore = useUsersStore();
const appliedTheme = computed(() => {
return theme.value === 'system' ? getPreferredTheme() : theme.value;
From 2f904647612824ac763fccdf8ffae9e6a793116a Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 20:08:50 -0400
Subject: [PATCH 10/15] small fixes
---
.../editor-ui/src/components/banners/TrialBanner.vue | 1 -
.../src/components/executions/ExecutionsFilter.vue | 2 --
.../__tests__/usePageRedirectionHelper.test.ts | 9 +++++----
3 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/packages/editor-ui/src/components/banners/TrialBanner.vue b/packages/editor-ui/src/components/banners/TrialBanner.vue
index 074bf6ee2d5ae..25c0f3d802748 100644
--- a/packages/editor-ui/src/components/banners/TrialBanner.vue
+++ b/packages/editor-ui/src/components/banners/TrialBanner.vue
@@ -3,7 +3,6 @@ import BaseBanner from '@/components/banners/BaseBanner.vue';
import { i18n as locale } from '@/plugins/i18n';
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { computed } from 'vue';
-import { useUIStore } from '@/stores/ui.store';
import type { CloudPlanAndUsageData } from '@/Interface';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
diff --git a/packages/editor-ui/src/components/executions/ExecutionsFilter.vue b/packages/editor-ui/src/components/executions/ExecutionsFilter.vue
index 11cf5b8cc08d4..4325c9b94fad1 100644
--- a/packages/editor-ui/src/components/executions/ExecutionsFilter.vue
+++ b/packages/editor-ui/src/components/executions/ExecutionsFilter.vue
@@ -10,7 +10,6 @@ import { i18n as locale } from '@/plugins/i18n';
import { getObjectKeys, isEmpty } from '@/utils/typesUtils';
import { EnterpriseEditionFeature } from '@/constants';
import { useSettingsStore } from '@/stores/settings.store';
-import { useUIStore } from '@/stores/ui.store';
import { useTelemetry } from '@/composables/useTelemetry';
import type { Placement } from '@floating-ui/core';
import { useDebounce } from '@/composables/useDebounce';
@@ -26,7 +25,6 @@ export type ExecutionFilterProps = {
const DATE_TIME_MASK = 'YYYY-MM-DD HH:mm';
const settingsStore = useSettingsStore();
-const uiStore = useUIStore();
const { debounce } = useDebounce();
const telemetry = useTelemetry();
diff --git a/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
index 3292c45f090f7..39188a151bf8f 100644
--- a/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
+++ b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
@@ -10,10 +10,11 @@ import * as cloudPlanApi from '@/api/cloudPlans';
import { useVersionsStore } from '@/stores/versions.store';
import { useTelemetry } from '../useTelemetry';
-let settingsStore: ReturnType;
-let cloudPlanStore: ReturnType;
-let usersStore: ReturnType;
-let versionStore: ReturnType;
+let settingsStore: ReturnType;
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+let cloudPlanStore: ReturnType;
+let usersStore: ReturnType;
+let versionStore: ReturnType;
let pageRedirectionHelper: ReturnType;
vi.mock('@/composables/useTelemetry', () => {
From 225beb69c04d9a92254d4de94a8f6cc119040f02 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 20:14:48 -0400
Subject: [PATCH 11/15] remove console.log
---
.../editor-ui/src/components/Projects/ProjectNavigation.test.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts b/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
index 856a922dfbdef..6b20301d66bc2 100644
--- a/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
@@ -153,8 +153,6 @@ describe('ProjectsNavigation', () => {
expect(getByText(/You have reached the Free plan limit of 3/)).toBeVisible();
await userEvent.click(getByText('View plans'));
- console.log(pageRedirectionHelper);
-
expect(pageRedirectionHelper.goToUpgrade).toHaveBeenCalledWith('rbac', 'upgrade-rbac');
});
From 9c5bb84e19c0d254e8b09b159d663f83a870cc48 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 20:25:41 -0400
Subject: [PATCH 12/15] fix issues
---
.../composables/__tests__/usePageRedirectionHelper.test.ts | 3 ---
packages/editor-ui/src/views/SettingsApiView.vue | 4 ++--
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
index 39188a151bf8f..83e990d602fd9 100644
--- a/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
+++ b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
@@ -11,8 +11,6 @@ import { useVersionsStore } from '@/stores/versions.store';
import { useTelemetry } from '../useTelemetry';
let settingsStore: ReturnType;
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-let cloudPlanStore: ReturnType;
let usersStore: ReturnType;
let versionStore: ReturnType;
let pageRedirectionHelper: ReturnType;
@@ -36,7 +34,6 @@ describe('usePageRedirectionHelper', () => {
beforeEach(() => {
setActivePinia(createPinia());
settingsStore = useSettingsStore();
- cloudPlanStore = useCloudPlanStore();
usersStore = useUsersStore();
versionStore = useVersionsStore();
diff --git a/packages/editor-ui/src/views/SettingsApiView.vue b/packages/editor-ui/src/views/SettingsApiView.vue
index 3d912900105eb..c8fc8bef4e841 100644
--- a/packages/editor-ui/src/views/SettingsApiView.vue
+++ b/packages/editor-ui/src/views/SettingsApiView.vue
@@ -25,7 +25,7 @@ export default defineComponent({
...useToast(),
...useMessage(),
...useUIStore(),
- ...usePageRedirectionHelper(),
+ pageRedirectionHelper: usePageRedirectionHelper(),
documentTitle: useDocumentTitle(),
};
},
@@ -72,7 +72,7 @@ export default defineComponent({
},
methods: {
onUpgrade() {
- void this.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
+ void this.pageRedirectionHelper.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
},
async showDeleteModal() {
const confirmed = await this.confirm(
From f22e0b30c50377d5d8283d5637e9c58c752c6473 Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 20:42:15 -0400
Subject: [PATCH 13/15] fix problem
---
.../src/composables/usePageRedirectionHelper.ts | 6 +++---
packages/editor-ui/src/stores/settings.store.ts | 2 +-
packages/editor-ui/src/stores/users.store.ts | 9 ++-------
3 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/packages/editor-ui/src/composables/usePageRedirectionHelper.ts b/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
index 39f5d58823bc9..42a1616dd98ae 100644
--- a/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
+++ b/packages/editor-ui/src/composables/usePageRedirectionHelper.ts
@@ -21,7 +21,7 @@ export function usePageRedirectionHelper() {
const goToVersions = async () => {
let versionsLink = versionsStore.infoUrl;
- if (usersStore.userIsOwnerInCloudDeployment) {
+ if (usersStore.isInstanceOwner && settingsStore.isCloudDeployment) {
versionsLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
redirectionPath: '/manage',
});
@@ -31,7 +31,7 @@ export function usePageRedirectionHelper() {
};
const goToDashboard = async () => {
- if (usersStore.userIsOwnerInCloudDeployment) {
+ if (usersStore.isInstanceOwner && settingsStore.isCloudDeployment) {
const dashboardLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
redirectionPath: '/dashboard',
});
@@ -78,7 +78,7 @@ export function usePageRedirectionHelper() {
const generateUpgradeLink = async (source: string, utm_campaign: string) => {
let upgradeLink = N8N_PRICING_PAGE_URL;
- if (usersStore.userIsOwnerInCloudDeployment) {
+ if (usersStore.isInstanceOwner && settingsStore.isCloudDeployment) {
upgradeLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
redirectionPath: '/account/change-plan',
});
diff --git a/packages/editor-ui/src/stores/settings.store.ts b/packages/editor-ui/src/stores/settings.store.ts
index 119fc20ac1039..7d881b74d4d9d 100644
--- a/packages/editor-ui/src/stores/settings.store.ts
+++ b/packages/editor-ui/src/stores/settings.store.ts
@@ -147,7 +147,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []);
const isBelowUserQuota = computed(
- () =>
+ (): boolean =>
userManagement.value.quota === -1 ||
userManagement.value.quota > useUsersStore().allUsers.length,
);
diff --git a/packages/editor-ui/src/stores/users.store.ts b/packages/editor-ui/src/stores/users.store.ts
index bc07a0f416b65..4ba79024a27f5 100644
--- a/packages/editor-ui/src/stores/users.store.ts
+++ b/packages/editor-ui/src/stores/users.store.ts
@@ -18,7 +18,6 @@ import { getPersonalizedNodeTypes } from '@/utils/userUtils';
import { defineStore } from 'pinia';
import { useRootStore } from '@/stores/root.store';
import { usePostHog } from './posthog.store';
-import { useSettingsStore } from './settings.store';
import { useUIStore } from './ui.store';
import { useCloudPlanStore } from './cloudPlan.store';
import * as mfaApi from '@/api/mfa';
@@ -29,7 +28,7 @@ import * as invitationsApi from '@/api/invitation';
import { useNpsSurveyStore } from './npsSurvey.store';
import { computed, ref } from 'vue';
import { useTelemetry } from '@/composables/useTelemetry';
-import { hasPermission } from '@/utils/rbac/permissions';
+import { useSettingsStore } from '@/stores/settings.store';
const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner;
@@ -50,6 +49,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const cloudPlanStore = useCloudPlanStore();
+
const telemetry = useTelemetry();
// Composables
@@ -74,10 +74,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const globalRoleName = computed(() => currentUser.value?.role ?? 'default');
- const userIsOwnerInCloudDeployment = computed(
- () => hasPermission(['instanceOwner']) && settingsStore.isCloudDeployment,
- );
-
const personalizedNodeTypes = computed(() => {
const user = currentUser.value;
if (!user) {
@@ -384,7 +380,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
mfaEnabled,
globalRoleName,
personalizedNodeTypes,
- userIsOwnerInCloudDeployment,
addUsers,
loginWithCookie,
initialize,
From 2cf3ebb50f27651f2ac93b77643004c9e3a8156b Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Thu, 31 Oct 2024 20:43:36 -0400
Subject: [PATCH 14/15] remove unused import
---
.../src/composables/__tests__/usePageRedirectionHelper.test.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
index 83e990d602fd9..ee080531dea11 100644
--- a/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
+++ b/packages/editor-ui/src/composables/__tests__/usePageRedirectionHelper.test.ts
@@ -1,5 +1,4 @@
import { ROLE } from '@/constants';
-import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { useSettingsStore } from '@/stores/settings.store';
import { merge } from 'lodash-es';
import { usePageRedirectionHelper } from '../usePageRedirectionHelper';
From 55f6a0d64039925c9e3008b747bc2e662c3d95cf Mon Sep 17 00:00:00 2001
From: Ricardo Espinoza
Date: Fri, 1 Nov 2024 10:35:14 -0400
Subject: [PATCH 15/15] remove unused types
---
packages/editor-ui/src/stores/ui.store.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts
index d7f23af2281ed..6a12495543764 100644
--- a/packages/editor-ui/src/stores/ui.store.ts
+++ b/packages/editor-ui/src/stores/ui.store.ts
@@ -38,8 +38,6 @@ import {
COMMUNITY_PLUS_ENROLLMENT_MODAL,
} from '@/constants';
import type {
- IFakeDoorLocation,
- CloudUpdateLinkSourceType,
INodeUi,
XYPosition,
Modals,