fix(editor): When cloud users click on "How to update your n8n version" auto-login them before redirecting to the dashboard (no-changelog) (#11467)

This commit is contained in:
Ricardo Espinoza 2024-11-01 11:01:54 -04:00 committed by GitHub
parent 602355a5c1
commit 78e7d8dc8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 503 additions and 205 deletions

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import ProjectSharing from '@/components/Projects/ProjectSharing.vue';
import { useI18n } from '@/composables/useI18n';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
import { EnterpriseEditionFeature } from '@/constants';
import type { ICredentialsDecryptedResponse, ICredentialsResponse } from '@/Interface';
import type { PermissionsRecord } from '@/permissions';
@ -39,6 +40,8 @@ const settingsStore = useSettingsStore();
const projectsStore = useProjectsStore();
const rolesStore = useRolesStore();
const pageRedirectionHelper = usePageRedirectionHelper();
const sharedWithProjects = ref([...(props.credential?.sharedWithProjects ?? [])]);
const isSharingEnabled = computed(
@ -107,7 +110,7 @@ onMounted(async () => {
});
function goToUpgrade() {
void uiStore.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing');
void pageRedirectionHelper.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing');
}
</script>

View file

@ -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');
},
},
});

View file

@ -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<void
}
function goToUpgrade() {
void uiStore.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
void pageRedirectionHelper.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
}
function goToWorkflowHistoryUpgrade() {
void uiStore.goToUpgrade('workflow-history', 'upgrade-workflow-history');
void pageRedirectionHelper.goToUpgrade('workflow-history', 'upgrade-workflow-history');
}
function showCreateWorkflowSuccessToast(id?: string) {

View file

@ -21,6 +21,7 @@ import { useUserHelpers } from '@/composables/useUserHelpers';
import { ABOUT_MODAL_KEY, VERSIONS_MODAL_KEY, VIEWS } from '@/constants';
import { useBugReporting } from '@/composables/useBugReporting';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const becomeTemplateCreatorStore = useBecomeTemplateCreatorStore();
const cloudPlanStore = useCloudPlanStore();
@ -38,6 +39,7 @@ const locale = useI18n();
const route = useRoute();
const router = useRouter();
const telemetry = useTelemetry();
const pageRedirectionHelper = usePageRedirectionHelper();
const { getReportingURL } = useBugReporting();
useUserHelpers(router, route);
@ -260,7 +262,7 @@ const handleSelect = (key: string) => {
break;
}
case 'cloud-admin': {
void cloudPlanStore.redirectToDashboard();
void pageRedirectionHelper.goToDashboard();
break;
}
case 'quickstart':

View file

@ -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<typeof useRouter>;
let toast: ReturnType<typeof useToast>;
let projectsStore: ReturnType<typeof mockedStore<typeof useProjectsStore>>;
let uiStore: ReturnType<typeof mockedStore<typeof useUIStore>>;
let pageRedirectionHelper: ReturnType<typeof usePageRedirectionHelper>;
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,7 @@ 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');
expect(pageRedirectionHelper.goToUpgrade).toHaveBeenCalledWith('rbac', 'upgrade-rbac');
});
it('should show "Projects" title and Personal project when the feature is enabled', async () => {

View file

@ -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 () => {

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useI18n } from '@/composables/useI18n';
import { useUIStore } from '@/stores/ui.store';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
type Props = {
limit: number;
@ -9,11 +9,11 @@ type Props = {
const props = defineProps<Props>();
const visible = defineModel<boolean>();
const uiStore = useUIStore();
const pageRedirectionHelper = usePageRedirectionHelper();
const locale = useI18n();
const goToUpgrade = async () => {
await uiStore.goToUpgrade('rbac', 'upgrade-rbac');
await pageRedirectionHelper.goToUpgrade('rbac', 'upgrade-rbac');
visible.value = false;
};
</script>

View file

@ -1,27 +1,16 @@
<script setup lang="ts">
import { computed } from 'vue';
import ModalDrawer from './ModalDrawer.vue';
import TimeAgo from './TimeAgo.vue';
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();
const nextVersions = computed(() => {
return versionsStore.nextVersions;
});
const currentVersion = computed(() => {
return versionsStore.currentVersion;
});
const infoUrl = computed(() => {
return versionsStore.infoUrl;
});
</script>
<template>
@ -38,22 +27,22 @@ const infoUrl = computed(() => {
</template>
<template #content>
<section :class="$style['description']">
<p v-if="currentVersion">
<p v-if="versionsStore.currentVersion">
{{
i18n.baseText('updatesPanel.youReOnVersion', {
interpolate: { currentVersionName: currentVersion.name },
interpolate: { currentVersionName: versionsStore.currentVersion.name },
})
}}
<strong>
<TimeAgo :date="currentVersion.createdAt" />
<TimeAgo :date="versionsStore.currentVersion.createdAt" />
</strong>
{{ i18n.baseText('updatesPanel.andIs') }}
<strong>
{{
i18n.baseText('updatesPanel.version', {
interpolate: {
numberOfVersions: nextVersions.length,
howManySuffix: nextVersions.length > 1 ? 's' : '',
numberOfVersions: versionsStore.nextVersions.length,
howManySuffix: versionsStore.nextVersions.length > 1 ? 's' : '',
},
})
}}
@ -61,15 +50,27 @@ const infoUrl = computed(() => {
{{ i18n.baseText('updatesPanel.behindTheLatest') }}
</p>
<n8n-link v-if="infoUrl" :to="infoUrl" :bold="true">
<n8n-button
v-if="versionsStore.infoUrl"
:text="true"
type="primary"
size="large"
:class="$style['link']"
:bold="true"
@click="pageRedirectionHelper.goToVersions()"
>
<font-awesome-icon icon="info-circle" class="mr-2xs" />
<span>
{{ i18n.baseText('updatesPanel.howToUpdateYourN8nVersion') }}
</span>
</n8n-link>
</n8n-button>
</section>
<section :class="$style.versions">
<div v-for="version in nextVersions" :key="version.name" :class="$style['versions-card']">
<div
v-for="version in versionsStore.nextVersions"
:key="version.name"
:class="$style['versions-card']"
>
<VersionCard :version="version" />
</div>
</section>
@ -102,6 +103,15 @@ const infoUrl = computed(() => {
div {
padding-top: 20px;
}
.link {
padding-left: 0px;
}
.link:hover {
color: var(--prim-color-primary);
text-decoration: none;
}
}
.versions {

View file

@ -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) {

View file

@ -3,13 +3,15 @@ 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';
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 +51,7 @@ const currentExecutions = computed(() => {
});
function onUpdatePlanClick() {
void useUIStore().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
void pageRedirectionHelper.goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
}
</script>

View file

@ -1,10 +1,10 @@
<script lang="ts" setup>
import BaseBanner from '@/components/banners/BaseBanner.vue';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
import { i18n as locale } from '@/plugins/i18n';
import { useUIStore } from '@/stores/ui.store';
function onUpdatePlanClick() {
void useUIStore().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
void usePageRedirectionHelper().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
}
</script>

View file

@ -10,11 +10,11 @@ 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';
import AnnotationTagsDropdown from '@/components/AnnotationTagsDropdown.ee.vue';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
export type ExecutionFilterProps = {
workflows?: Array<IWorkflowDb | IWorkflowShortResponse>;
@ -25,10 +25,10 @@ export type ExecutionFilterProps = {
const DATE_TIME_MASK = 'YYYY-MM-DD HH:mm';
const settingsStore = useSettingsStore();
const uiStore = useUIStore();
const { debounce } = useDebounce();
const telemetry = useTelemetry();
const pageRedirectionHelper = usePageRedirectionHelper();
const props = withDefaults(defineProps<ExecutionFilterProps>(), {
workflows: () => [] as Array<IWorkflowDb | IWorkflowShortResponse>,
@ -149,7 +149,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(() => {

View file

@ -0,0 +1,231 @@
import { ROLE } from '@/constants';
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<typeof useSettingsStore>;
let usersStore: ReturnType<typeof useUsersStore>;
let versionStore: ReturnType<typeof useVersionsStore>;
let pageRedirectionHelper: ReturnType<typeof usePageRedirectionHelper>;
vi.mock('@/composables/useTelemetry', () => {
const track = vi.fn();
return {
useTelemetry: () => {
return {
track,
};
},
};
});
describe('usePageRedirectionHelper', () => {
afterEach(() => {
vi.clearAllMocks();
});
beforeEach(() => {
setActivePinia(createPinia());
settingsStore = useSettingsStore();
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);
},
);
});

View file

@ -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');
},
},
});

View file

@ -0,0 +1,105 @@
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.isInstanceOwner && settingsStore.isCloudDeployment) {
versionsLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
redirectionPath: '/manage',
});
}
location.href = versionsLink;
};
const goToDashboard = async () => {
if (usersStore.isInstanceOwner && settingsStore.isCloudDeployment) {
const dashboardLink = await cloudPlanStore.generateCloudDashboardAutoLoginLink({
redirectionPath: '/dashboard',
});
location.href = dashboardLink;
}
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,
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.isInstanceOwner && settingsStore.isCloudDeployment) {
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,
};
}

View file

@ -1,5 +1,5 @@
import { createPinia, setActivePinia } from 'pinia';
import { generateUpgradeLinkUrl, 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',
],
])(
'"generateUpgradeLinkUrl" 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 generateUpgradeLinkUrl('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, {

View file

@ -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;
@ -173,11 +167,22 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
state.initialized = true;
};
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,
@ -185,10 +190,13 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
currentUsageData,
trialExpired,
allExecutionsUsed,
generateCloudDashboardAutoLoginLink,
initialize,
getOwnerCurrentPlan,
getInstanceCurrentUsage,
reset,
checkForCloudPlanData,
fetchUserCloudAccount,
getAutoLoginCode,
redirectToDashboard,
};
});

View file

@ -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,
);

View file

@ -30,7 +30,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,
@ -39,9 +38,7 @@ import {
COMMUNITY_PLUS_ENROLLMENT_MODAL,
} from '@/constants';
import type {
CloudUpdateLinkSourceType,
INodeUi,
UTMCampaign,
XYPosition,
Modals,
NewCredentialsModal,
@ -53,10 +50,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';
@ -69,9 +64,9 @@ import {
} from './ui.utils';
import { computed, ref } from 'vue';
import type { Connection } from '@vue-flow/core';
import { useTelemetry } from '@/composables/useTelemetry';
let savedTheme: ThemeOption = 'system';
try {
const value = getThemeOverride();
if (isValidTheme(value)) {
@ -189,8 +184,6 @@ 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 appliedTheme = computed(() => {
@ -525,33 +518,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 generateUpgradeLinkUrl(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);
};
@ -633,6 +599,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
currentView,
isAnyModalOpen,
pendingNotificationsForViews,
activeModals,
setTheme,
setMode,
setActiveId,
@ -659,7 +626,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
setCurlCommand,
toggleSidebarMenuCollapse,
getCurlToJson,
goToUpgrade,
removeBannerFromStack,
dismissBanner,
updateBannersHeight,
@ -668,7 +634,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
setNotificationsForView,
deleteNotificationsForView,
resetLastInteractedWith,
activeModals,
};
});
@ -712,34 +677,3 @@ export const listenForModalChanges = (opts: {
});
});
};
export const generateUpgradeLinkUrl = async (
source: string,
utm_campaign: string,
deploymentType: string,
) => {
let linkUrl = '';
const searchParams = new URLSearchParams();
const cloudPlanStore = useCloudPlanStore();
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;
}
if (utm_campaign) {
searchParams.set('utm_campaign', utm_campaign);
}
if (source) {
searchParams.set('source', source);
}
return `${linkUrl}?${searchParams.toString()}`;
};

View file

@ -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,6 +28,7 @@ import * as invitationsApi from '@/api/invitation';
import { useNpsSurveyStore } from './npsSurvey.store';
import { computed, ref } from 'vue';
import { useTelemetry } from '@/composables/useTelemetry';
import { useSettingsStore } from '@/stores/settings.store';
const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner;
@ -49,6 +49,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const cloudPlanStore = useCloudPlanStore();
const telemetry = useTelemetry();
// Composables

View file

@ -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(),
pageRedirectionHelper: usePageRedirectionHelper(),
documentTitle: useDocumentTitle(),
};
},
@ -70,7 +72,7 @@ export default defineComponent({
},
methods: {
onUpgrade() {
void this.uiStore.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
void this.pageRedirectionHelper.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
},
async showDeleteModal() {
const confirmed = await this.confirm(

View file

@ -1,5 +1,4 @@
<script lang="ts" setup>
import { useUIStore } from '@/stores/ui.store';
import { useI18n } from '@/composables/useI18n';
import { useToast } from '@/composables/useToast';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
@ -7,12 +6,13 @@ import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
import { computed, onMounted } from 'vue';
import ExternalSecretsProviderCard from '@/components/ExternalSecretsProviderCard.ee.vue';
import type { ExternalSecretsProvider } from '@/Interface';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const i18n = useI18n();
const uiStore = useUIStore();
const externalSecretsStore = useExternalSecretsStore();
const toast = useToast();
const documentTitle = useDocumentTitle();
const pageRedirectionHelper = usePageRedirectionHelper();
const sortedProviders = computed(() => {
return ([...externalSecretsStore.providers] as ExternalSecretsProvider[]).sort((a, b) => {
@ -32,7 +32,7 @@ onMounted(() => {
});
function goToUpgrade() {
void uiStore.goToUpgrade('external-secrets', 'upgrade-external-secrets');
void pageRedirectionHelper.goToUpgrade('external-secrets', 'upgrade-external-secrets');
}
</script>

View file

@ -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<ILdapSyncTable[]>([]);
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<TableRow>): CSSProperties => {
if (column.property === 'status') {

View file

@ -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;

View file

@ -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 () => {

View file

@ -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 () => {

View file

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { computed, ref, onMounted } from 'vue';
import { useSSOStore } from '@/stores/sso.store';
import { useUIStore } from '@/stores/ui.store';
import CopyInput from '@/components/CopyInput.vue';
import { useI18n } from '@/composables/useI18n';
import { useMessage } from '@/composables/useMessage';
@ -9,6 +8,7 @@ import { useToast } from '@/composables/useToast';
import { useTelemetry } from '@/composables/useTelemetry';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import { useRootStore } from '@/stores/root.store';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const IdentityProviderSettingsType = {
URL: 'url',
@ -19,10 +19,10 @@ const i18n = useI18n();
const telemetry = useTelemetry();
const rootStore = useRootStore();
const ssoStore = useSSOStore();
const uiStore = useUIStore();
const message = useMessage();
const toast = useToast();
const documentTitle = useDocumentTitle();
const pageRedirectionHelper = usePageRedirectionHelper();
const ssoActivatedLabel = computed(() =>
ssoStore.isSamlLoginEnabled
@ -133,7 +133,7 @@ const onTest = async () => {
};
const goToUpgrade = () => {
void uiStore.goToUpgrade('sso', 'upgrade-sso');
void pageRedirectionHelper.goToUpgrade('sso', 'upgrade-sso');
};
const isToggleSsoDisabled = computed(() => {

View file

@ -13,6 +13,7 @@ import N8nInfoTip from 'n8n-design-system/components/N8nInfoTip';
import { COMMUNITY_PLUS_ENROLLMENT_MODAL } from '@/constants';
import { useUsersStore } from '@/stores/users.store';
import { getResourcePermissions } from '@/permissions';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const usageStore = useUsageStore();
const route = useRoute();
@ -21,6 +22,7 @@ const uiStore = useUIStore();
const usersStore = useUsersStore();
const toast = useToast();
const documentTitle = useDocumentTitle();
const pageRedirectionHelper = usePageRedirectionHelper();
const queryParamCallback = ref<string>(
`callback=${encodeURIComponent(`${window.location.origin}${window.location.pathname}`)}`,
@ -129,7 +131,7 @@ const onAddActivationKey = () => {
};
const onViewPlans = () => {
void uiStore.goToUpgrade('usage_page', 'open');
void pageRedirectionHelper.goToUpgrade('usage_page', 'open');
sendUsageTelemetry('view_plans');
};

View file

@ -12,6 +12,7 @@ import { merge } from 'lodash-es';
import { useUIStore } from '@/stores/ui.store';
import { useSSOStore } from '@/stores/sso.store';
import { STORES } from '@/constants';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const loggedInUser = createUser();
const invitedUser = createUser({
@ -72,6 +73,15 @@ vi.mock('@/composables/useToast', () => ({
}),
}));
vi.mock('@/composables/usePageRedirectionHelper', () => {
const goToUpgrade = vi.fn();
return {
usePageRedirectionHelper: () => ({
goToUpgrade,
}),
};
});
describe('SettingsUsersView', () => {
afterEach(() => {
copy.mockReset();
@ -105,14 +115,17 @@ describe('SettingsUsersView', () => {
it('allows the user to upgrade', async () => {
const { getByTestId } = renderView({ pinia });
const uiStore = useUIStore();
const pageRedirectionHelper = usePageRedirectionHelper();
const actionBox = getByTestId('action-box');
expect(actionBox).toBeInTheDocument();
await userEvent.click(await within(actionBox).findByText('View plans'));
expect(uiStore.goToUpgrade).toHaveBeenCalledWith('settings-users', 'upgrade-users');
expect(pageRedirectionHelper.goToUpgrade).toHaveBeenCalledWith(
'settings-users',
'upgrade-users',
);
});
});

View file

@ -13,6 +13,7 @@ import type { UpdateGlobalRolePayload } from '@/api/users';
import { computed, onMounted } from 'vue';
import { useI18n } from '@/composables/useI18n';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const clipboard = useClipboard();
const { showToast, showError } = useToast();
@ -22,6 +23,7 @@ const uiStore = useUIStore();
const usersStore = useUsersStore();
const ssoStore = useSSOStore();
const documentTitle = useDocumentTitle();
const pageRedirectionHelper = usePageRedirectionHelper();
const i18n = useI18n();
@ -209,10 +211,10 @@ async function onDisallowSSOManualLogin(userId: string) {
}
}
function goToUpgrade() {
void uiStore.goToUpgrade('settings-users', 'upgrade-users');
void pageRedirectionHelper.goToUpgrade('settings-users', 'upgrade-users');
}
function goToUpgradeAdvancedPermissions() {
void uiStore.goToUpgrade('settings-users', 'upgrade-advanced-permissions');
void pageRedirectionHelper.goToUpgrade('settings-users', 'upgrade-advanced-permissions');
}
async function onRoleChange(user: IUser, newRoleName: UpdateGlobalRolePayload['newRoleName']) {
try {

View file

@ -20,6 +20,7 @@ import type { DatatableColumn, EnvironmentVariable } from '@/Interface';
import { uid } from 'n8n-design-system/utils';
import { getResourcePermissions } from '@/permissions';
import type { BaseTextKey } from '@/plugins/i18n';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const settingsStore = useSettingsStore();
const environmentsStore = useEnvironmentsStore();
@ -30,6 +31,7 @@ const i18n = useI18n();
const message = useMessage();
const sourceControlStore = useSourceControlStore();
const documentTitle = useDocumentTitle();
const pageRedirectionHelper = usePageRedirectionHelper();
let sourceControlStoreUnsubscribe = () => {};
const layoutRef = ref<InstanceType<typeof ResourcesListLayout> | null>(null);
@ -230,7 +232,7 @@ async function deleteVariable(data: IResource) {
}
function goToUpgrade() {
void uiStore.goToUpgrade('variables', 'upgrade-variables');
void pageRedirectionHelper.goToUpgrade('variables', 'upgrade-variables');
}
function displayName(resource: IResource) {

View file

@ -1,13 +1,13 @@
<script setup lang="ts">
import WorkerList from '@/components/WorkerList.ee.vue';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
const settingsStore = useSettingsStore();
const uiStore = useUIStore();
const pageRedirectionHelper = usePageRedirectionHelper();
const goToUpgrade = () => {
void uiStore.goToUpgrade('worker-view', 'upgrade-worker-view');
void pageRedirectionHelper.goToUpgrade('worker-view', 'upgrade-worker-view');
};
</script>

View file

@ -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<WorkflowHistoryActionTypes[number]>]: Lowercase<K>;
@ -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 () => {