mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
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:
parent
602355a5c1
commit
78e7d8dc8c
|
@ -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>
|
||||
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
|
@ -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');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
105
packages/editor-ui/src/composables/usePageRedirectionHelper.ts
Normal file
105
packages/editor-ui/src/composables/usePageRedirectionHelper.ts
Normal 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,
|
||||
};
|
||||
}
|
|
@ -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, {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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()}`;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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');
|
||||
};
|
||||
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
Loading…
Reference in a new issue