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">
|
<script setup lang="ts">
|
||||||
import ProjectSharing from '@/components/Projects/ProjectSharing.vue';
|
import ProjectSharing from '@/components/Projects/ProjectSharing.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
import type { ICredentialsDecryptedResponse, ICredentialsResponse } from '@/Interface';
|
import type { ICredentialsDecryptedResponse, ICredentialsResponse } from '@/Interface';
|
||||||
import type { PermissionsRecord } from '@/permissions';
|
import type { PermissionsRecord } from '@/permissions';
|
||||||
|
@ -39,6 +40,8 @@ const settingsStore = useSettingsStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
const rolesStore = useRolesStore();
|
const rolesStore = useRolesStore();
|
||||||
|
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const sharedWithProjects = ref([...(props.credential?.sharedWithProjects ?? [])]);
|
const sharedWithProjects = ref([...(props.credential?.sharedWithProjects ?? [])]);
|
||||||
|
|
||||||
const isSharingEnabled = computed(
|
const isSharingEnabled = computed(
|
||||||
|
@ -107,7 +110,7 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function goToUpgrade() {
|
function goToUpgrade() {
|
||||||
void uiStore.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing');
|
void pageRedirectionHelper.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { createFormEventBus, createEventBus } from 'n8n-design-system/utils';
|
import { createFormEventBus, createEventBus } from 'n8n-design-system/utils';
|
||||||
import { useClipboard } from '@/composables/useClipboard';
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
|
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
clipboard,
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
|
...usePageRedirectionHelper(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -277,7 +279,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToUpgradeAdvancedPermissions() {
|
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 type { BaseTextKey } from '@/plugins/i18n';
|
||||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||||
import { useNodeViewVersionSwitcher } from '@/composables/useNodeViewVersionSwitcher';
|
import { useNodeViewVersionSwitcher } from '@/composables/useNodeViewVersionSwitcher';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
@ -89,6 +90,7 @@ const message = useMessage();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
const workflowHelpers = useWorkflowHelpers({ router });
|
const workflowHelpers = useWorkflowHelpers({ router });
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const isTagsEditEnabled = ref(false);
|
const isTagsEditEnabled = ref(false);
|
||||||
const isNameEditEnabled = ref(false);
|
const isNameEditEnabled = ref(false);
|
||||||
|
@ -584,11 +586,11 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToUpgrade() {
|
function goToUpgrade() {
|
||||||
void uiStore.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
|
void pageRedirectionHelper.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToWorkflowHistoryUpgrade() {
|
function goToWorkflowHistoryUpgrade() {
|
||||||
void uiStore.goToUpgrade('workflow-history', 'upgrade-workflow-history');
|
void pageRedirectionHelper.goToUpgrade('workflow-history', 'upgrade-workflow-history');
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCreateWorkflowSuccessToast(id?: string) {
|
function showCreateWorkflowSuccessToast(id?: string) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { useUserHelpers } from '@/composables/useUserHelpers';
|
||||||
|
|
||||||
import { ABOUT_MODAL_KEY, VERSIONS_MODAL_KEY, VIEWS } from '@/constants';
|
import { ABOUT_MODAL_KEY, VERSIONS_MODAL_KEY, VIEWS } from '@/constants';
|
||||||
import { useBugReporting } from '@/composables/useBugReporting';
|
import { useBugReporting } from '@/composables/useBugReporting';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const becomeTemplateCreatorStore = useBecomeTemplateCreatorStore();
|
const becomeTemplateCreatorStore = useBecomeTemplateCreatorStore();
|
||||||
const cloudPlanStore = useCloudPlanStore();
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
|
@ -38,6 +39,7 @@ const locale = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
const { getReportingURL } = useBugReporting();
|
const { getReportingURL } = useBugReporting();
|
||||||
|
|
||||||
useUserHelpers(router, route);
|
useUserHelpers(router, route);
|
||||||
|
@ -260,7 +262,7 @@ const handleSelect = (key: string) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'cloud-admin': {
|
case 'cloud-admin': {
|
||||||
void cloudPlanStore.redirectToDashboard();
|
void pageRedirectionHelper.goToDashboard();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'quickstart':
|
case 'quickstart':
|
||||||
|
|
|
@ -5,11 +5,11 @@ import { createRouter, createMemoryHistory, useRouter } from 'vue-router';
|
||||||
import { createProjectListItem } from '@/__tests__/data/projects';
|
import { createProjectListItem } from '@/__tests__/data/projects';
|
||||||
import ProjectsNavigation from '@/components/Projects//ProjectNavigation.vue';
|
import ProjectsNavigation from '@/components/Projects//ProjectNavigation.vue';
|
||||||
import { useProjectsStore } from '@/stores/projects.store';
|
import { useProjectsStore } from '@/stores/projects.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { mockedStore } from '@/__tests__/utils';
|
import { mockedStore } from '@/__tests__/utils';
|
||||||
import type { Project } from '@/types/projects.types';
|
import type { Project } from '@/types/projects.types';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
vi.mock('vue-router', async () => {
|
vi.mock('vue-router', async () => {
|
||||||
const actual = await vi.importActual('vue-router');
|
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, {
|
const renderComponent = createComponentRenderer(ProjectsNavigation, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -56,7 +65,7 @@ const renderComponent = createComponentRenderer(ProjectsNavigation, {
|
||||||
let router: ReturnType<typeof useRouter>;
|
let router: ReturnType<typeof useRouter>;
|
||||||
let toast: ReturnType<typeof useToast>;
|
let toast: ReturnType<typeof useToast>;
|
||||||
let projectsStore: ReturnType<typeof mockedStore<typeof useProjectsStore>>;
|
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 personalProjects = Array.from({ length: 3 }, createProjectListItem);
|
||||||
const teamProjects = Array.from({ length: 3 }, () => createProjectListItem('team'));
|
const teamProjects = Array.from({ length: 3 }, () => createProjectListItem('team'));
|
||||||
|
@ -67,9 +76,9 @@ describe('ProjectsNavigation', () => {
|
||||||
|
|
||||||
router = useRouter();
|
router = useRouter();
|
||||||
toast = useToast();
|
toast = useToast();
|
||||||
|
pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
projectsStore = mockedStore(useProjectsStore);
|
projectsStore = mockedStore(useProjectsStore);
|
||||||
uiStore = mockedStore(useUIStore);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw an error', () => {
|
it('should not throw an error', () => {
|
||||||
|
@ -144,7 +153,7 @@ describe('ProjectsNavigation', () => {
|
||||||
expect(getByText(/You have reached the Free plan limit of 3/)).toBeVisible();
|
expect(getByText(/You have reached the Free plan limit of 3/)).toBeVisible();
|
||||||
await userEvent.click(getByText('View plans'));
|
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 () => {
|
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 { useProjectsStore } from '@/stores/projects.store';
|
||||||
import type { ProjectListItem } from '@/types/projects.types';
|
import type { ProjectListItem } from '@/types/projects.types';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { sortByProperty } from '@/utils/sortUtils';
|
import { sortByProperty } from '@/utils/sortUtils';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
|
@ -21,7 +21,7 @@ const router = useRouter();
|
||||||
const locale = useI18n();
|
const locale = useI18n();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
const uiStore = useUIStore();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const isCreatingProject = ref(false);
|
const isCreatingProject = ref(false);
|
||||||
const isComponentMounted = ref(false);
|
const isComponentMounted = ref(false);
|
||||||
|
@ -99,7 +99,7 @@ const canCreateProjects = computed(
|
||||||
);
|
);
|
||||||
|
|
||||||
const goToUpgrade = async () => {
|
const goToUpgrade = async () => {
|
||||||
await uiStore.goToUpgrade('rbac', 'upgrade-rbac');
|
await pageRedirectionHelper.goToUpgrade('rbac', 'upgrade-rbac');
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
limit: number;
|
limit: number;
|
||||||
|
@ -9,11 +9,11 @@ type Props = {
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const visible = defineModel<boolean>();
|
const visible = defineModel<boolean>();
|
||||||
const uiStore = useUIStore();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
const locale = useI18n();
|
const locale = useI18n();
|
||||||
|
|
||||||
const goToUpgrade = async () => {
|
const goToUpgrade = async () => {
|
||||||
await uiStore.goToUpgrade('rbac', 'upgrade-rbac');
|
await pageRedirectionHelper.goToUpgrade('rbac', 'upgrade-rbac');
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,27 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
import ModalDrawer from './ModalDrawer.vue';
|
import ModalDrawer from './ModalDrawer.vue';
|
||||||
import TimeAgo from './TimeAgo.vue';
|
import TimeAgo from './TimeAgo.vue';
|
||||||
import VersionCard from './VersionCard.vue';
|
import VersionCard from './VersionCard.vue';
|
||||||
import { VERSIONS_MODAL_KEY } from '../constants';
|
import { VERSIONS_MODAL_KEY } from '../constants';
|
||||||
import { useVersionsStore } from '@/stores/versions.store';
|
import { useVersionsStore } from '@/stores/versions.store';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const versionsStore = useVersionsStore();
|
const versionsStore = useVersionsStore();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const nextVersions = computed(() => {
|
|
||||||
return versionsStore.nextVersions;
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentVersion = computed(() => {
|
|
||||||
return versionsStore.currentVersion;
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoUrl = computed(() => {
|
|
||||||
return versionsStore.infoUrl;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -38,22 +27,22 @@ const infoUrl = computed(() => {
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<section :class="$style['description']">
|
<section :class="$style['description']">
|
||||||
<p v-if="currentVersion">
|
<p v-if="versionsStore.currentVersion">
|
||||||
{{
|
{{
|
||||||
i18n.baseText('updatesPanel.youReOnVersion', {
|
i18n.baseText('updatesPanel.youReOnVersion', {
|
||||||
interpolate: { currentVersionName: currentVersion.name },
|
interpolate: { currentVersionName: versionsStore.currentVersion.name },
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
<strong>
|
<strong>
|
||||||
<TimeAgo :date="currentVersion.createdAt" />
|
<TimeAgo :date="versionsStore.currentVersion.createdAt" />
|
||||||
</strong>
|
</strong>
|
||||||
{{ i18n.baseText('updatesPanel.andIs') }}
|
{{ i18n.baseText('updatesPanel.andIs') }}
|
||||||
<strong>
|
<strong>
|
||||||
{{
|
{{
|
||||||
i18n.baseText('updatesPanel.version', {
|
i18n.baseText('updatesPanel.version', {
|
||||||
interpolate: {
|
interpolate: {
|
||||||
numberOfVersions: nextVersions.length,
|
numberOfVersions: versionsStore.nextVersions.length,
|
||||||
howManySuffix: nextVersions.length > 1 ? 's' : '',
|
howManySuffix: versionsStore.nextVersions.length > 1 ? 's' : '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
@ -61,15 +50,27 @@ const infoUrl = computed(() => {
|
||||||
{{ i18n.baseText('updatesPanel.behindTheLatest') }}
|
{{ i18n.baseText('updatesPanel.behindTheLatest') }}
|
||||||
</p>
|
</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" />
|
<font-awesome-icon icon="info-circle" class="mr-2xs" />
|
||||||
<span>
|
<span>
|
||||||
{{ i18n.baseText('updatesPanel.howToUpdateYourN8nVersion') }}
|
{{ i18n.baseText('updatesPanel.howToUpdateYourN8nVersion') }}
|
||||||
</span>
|
</span>
|
||||||
</n8n-link>
|
</n8n-button>
|
||||||
</section>
|
</section>
|
||||||
<section :class="$style.versions">
|
<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" />
|
<VersionCard :version="version" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -102,6 +103,15 @@ const infoUrl = computed(() => {
|
||||||
div {
|
div {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
color: var(--prim-color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.versions {
|
.versions {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import type { ProjectListItem, ProjectSharingData, Project } from '@/types/proje
|
||||||
import { ProjectTypes } from '@/types/projects.types';
|
import { ProjectTypes } from '@/types/projects.types';
|
||||||
import { useRolesStore } from '@/stores/roles.store';
|
import { useRolesStore } from '@/stores/roles.store';
|
||||||
import type { RoleMap } from '@/types/roles.types';
|
import type { RoleMap } from '@/types/roles.types';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'WorkflowShareModal',
|
name: 'WorkflowShareModal',
|
||||||
|
@ -47,6 +48,7 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
...useToast(),
|
...useToast(),
|
||||||
...useMessage(),
|
...useMessage(),
|
||||||
|
...usePageRedirectionHelper(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -235,7 +237,7 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
goToUpgrade() {
|
goToUpgrade() {
|
||||||
void this.uiStore.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
|
void this.goToUpgrade('workflow_sharing', 'upgrade-workflow-sharing');
|
||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
if (this.isSharingEnabled) {
|
if (this.isSharingEnabled) {
|
||||||
|
|
|
@ -3,13 +3,15 @@ import BaseBanner from '@/components/banners/BaseBanner.vue';
|
||||||
import { i18n as locale } from '@/plugins/i18n';
|
import { i18n as locale } from '@/plugins/i18n';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import type { CloudPlanAndUsageData } from '@/Interface';
|
import type { CloudPlanAndUsageData } from '@/Interface';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const PROGRESS_BAR_MINIMUM_THRESHOLD = 8;
|
const PROGRESS_BAR_MINIMUM_THRESHOLD = 8;
|
||||||
|
|
||||||
const cloudPlanStore = useCloudPlanStore();
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
|
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const trialDaysLeft = computed(() => -1 * cloudPlanStore.trialDaysLeft);
|
const trialDaysLeft = computed(() => -1 * cloudPlanStore.trialDaysLeft);
|
||||||
const messageText = computed(() => {
|
const messageText = computed(() => {
|
||||||
return locale.baseText('banners.trial.message', {
|
return locale.baseText('banners.trial.message', {
|
||||||
|
@ -49,7 +51,7 @@ const currentExecutions = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function onUpdatePlanClick() {
|
function onUpdatePlanClick() {
|
||||||
void useUIStore().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
void pageRedirectionHelper.goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import BaseBanner from '@/components/banners/BaseBanner.vue';
|
import BaseBanner from '@/components/banners/BaseBanner.vue';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
import { i18n as locale } from '@/plugins/i18n';
|
import { i18n as locale } from '@/plugins/i18n';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
|
|
||||||
function onUpdatePlanClick() {
|
function onUpdatePlanClick() {
|
||||||
void useUIStore().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
void usePageRedirectionHelper().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,11 @@ import { i18n as locale } from '@/plugins/i18n';
|
||||||
import { getObjectKeys, isEmpty } from '@/utils/typesUtils';
|
import { getObjectKeys, isEmpty } from '@/utils/typesUtils';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import type { Placement } from '@floating-ui/core';
|
import type { Placement } from '@floating-ui/core';
|
||||||
import { useDebounce } from '@/composables/useDebounce';
|
import { useDebounce } from '@/composables/useDebounce';
|
||||||
import AnnotationTagsDropdown from '@/components/AnnotationTagsDropdown.ee.vue';
|
import AnnotationTagsDropdown from '@/components/AnnotationTagsDropdown.ee.vue';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
export type ExecutionFilterProps = {
|
export type ExecutionFilterProps = {
|
||||||
workflows?: Array<IWorkflowDb | IWorkflowShortResponse>;
|
workflows?: Array<IWorkflowDb | IWorkflowShortResponse>;
|
||||||
|
@ -25,10 +25,10 @@ export type ExecutionFilterProps = {
|
||||||
const DATE_TIME_MASK = 'YYYY-MM-DD HH:mm';
|
const DATE_TIME_MASK = 'YYYY-MM-DD HH:mm';
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const uiStore = useUIStore();
|
|
||||||
const { debounce } = useDebounce();
|
const { debounce } = useDebounce();
|
||||||
|
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ExecutionFilterProps>(), {
|
const props = withDefaults(defineProps<ExecutionFilterProps>(), {
|
||||||
workflows: () => [] as Array<IWorkflowDb | IWorkflowShortResponse>,
|
workflows: () => [] as Array<IWorkflowDb | IWorkflowShortResponse>,
|
||||||
|
@ -149,7 +149,7 @@ const onFilterReset = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToUpgrade = () => {
|
const goToUpgrade = () => {
|
||||||
void uiStore.goToUpgrade('custom-data-filter', 'upgrade-custom-data-filter');
|
void pageRedirectionHelper.goToUpgrade('custom-data-filter', 'upgrade-custom-data-filter');
|
||||||
};
|
};
|
||||||
|
|
||||||
onBeforeMount(() => {
|
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 { useRootStore } from '@/stores/root.store';
|
||||||
import { isFullExecutionResponse } from '@/utils/typeGuards';
|
import { isFullExecutionResponse } from '@/utils/typeGuards';
|
||||||
import { sanitizeHtml } from '@/utils/htmlUtils';
|
import { sanitizeHtml } from '@/utils/htmlUtils';
|
||||||
|
import { usePageRedirectionHelper } from './usePageRedirectionHelper';
|
||||||
|
|
||||||
export const useExecutionDebugging = () => {
|
export const useExecutionDebugging = () => {
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
@ -29,6 +30,8 @@ export const useExecutionDebugging = () => {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
|
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const isDebugEnabled = computed(
|
const isDebugEnabled = computed(
|
||||||
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DebugInEditor],
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DebugInEditor],
|
||||||
);
|
);
|
||||||
|
@ -147,7 +150,7 @@ export const useExecutionDebugging = () => {
|
||||||
title: i18n.baseText(uiStore.contextBasedTranslationKeys.feature.unavailable.title),
|
title: i18n.baseText(uiStore.contextBasedTranslationKeys.feature.unavailable.title),
|
||||||
footerButtonAction: () => {
|
footerButtonAction: () => {
|
||||||
uiStore.closeModal(DEBUG_PAYWALL_MODAL_KEY);
|
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 { createPinia, setActivePinia } from 'pinia';
|
||||||
import { generateUpgradeLinkUrl, useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { merge } from 'lodash-es';
|
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', () => {
|
it('should add non-production license banner to stack based on enterprise settings', () => {
|
||||||
settingsStore.setSettings(
|
settingsStore.setSettings(
|
||||||
merge({}, defaultSettings, {
|
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 () => {
|
const initialize = async () => {
|
||||||
if (state.initialized) {
|
if (state.initialized) {
|
||||||
return;
|
return;
|
||||||
|
@ -173,11 +167,22 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
||||||
state.initialized = true;
|
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 {
|
return {
|
||||||
state,
|
state,
|
||||||
initialize,
|
|
||||||
getOwnerCurrentPlan,
|
|
||||||
getInstanceCurrentUsage,
|
|
||||||
usageLeft,
|
usageLeft,
|
||||||
trialDaysLeft,
|
trialDaysLeft,
|
||||||
userIsTrialing,
|
userIsTrialing,
|
||||||
|
@ -185,10 +190,13 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
|
||||||
currentUsageData,
|
currentUsageData,
|
||||||
trialExpired,
|
trialExpired,
|
||||||
allExecutionsUsed,
|
allExecutionsUsed,
|
||||||
|
generateCloudDashboardAutoLoginLink,
|
||||||
|
initialize,
|
||||||
|
getOwnerCurrentPlan,
|
||||||
|
getInstanceCurrentUsage,
|
||||||
reset,
|
reset,
|
||||||
checkForCloudPlanData,
|
checkForCloudPlanData,
|
||||||
fetchUserCloudAccount,
|
fetchUserCloudAccount,
|
||||||
getAutoLoginCode,
|
getAutoLoginCode,
|
||||||
redirectToDashboard,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -147,7 +147,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
||||||
const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []);
|
const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []);
|
||||||
|
|
||||||
const isBelowUserQuota = computed(
|
const isBelowUserQuota = computed(
|
||||||
() =>
|
(): boolean =>
|
||||||
userManagement.value.quota === -1 ||
|
userManagement.value.quota === -1 ||
|
||||||
userManagement.value.quota > useUsersStore().allUsers.length,
|
userManagement.value.quota > useUsersStore().allUsers.length,
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,7 +30,6 @@ import {
|
||||||
SOURCE_CONTROL_PUSH_MODAL_KEY,
|
SOURCE_CONTROL_PUSH_MODAL_KEY,
|
||||||
SOURCE_CONTROL_PULL_MODAL_KEY,
|
SOURCE_CONTROL_PULL_MODAL_KEY,
|
||||||
DEBUG_PAYWALL_MODAL_KEY,
|
DEBUG_PAYWALL_MODAL_KEY,
|
||||||
N8N_PRICING_PAGE_URL,
|
|
||||||
WORKFLOW_HISTORY_VERSION_RESTORE,
|
WORKFLOW_HISTORY_VERSION_RESTORE,
|
||||||
SETUP_CREDENTIALS_MODAL_KEY,
|
SETUP_CREDENTIALS_MODAL_KEY,
|
||||||
PROJECT_MOVE_RESOURCE_MODAL,
|
PROJECT_MOVE_RESOURCE_MODAL,
|
||||||
|
@ -39,9 +38,7 @@ import {
|
||||||
COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
COMMUNITY_PLUS_ENROLLMENT_MODAL,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import type {
|
import type {
|
||||||
CloudUpdateLinkSourceType,
|
|
||||||
INodeUi,
|
INodeUi,
|
||||||
UTMCampaign,
|
|
||||||
XYPosition,
|
XYPosition,
|
||||||
Modals,
|
Modals,
|
||||||
NewCredentialsModal,
|
NewCredentialsModal,
|
||||||
|
@ -53,10 +50,8 @@ import type {
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRootStore } from '@/stores/root.store';
|
import { useRootStore } from '@/stores/root.store';
|
||||||
import * as curlParserApi from '@/api/curlHelper';
|
import * as curlParserApi from '@/api/curlHelper';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { hasPermission } from '@/utils/rbac/permissions';
|
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { dismissBannerPermanently } from '@/api/ui';
|
import { dismissBannerPermanently } from '@/api/ui';
|
||||||
import type { BannerName } from 'n8n-workflow';
|
import type { BannerName } from 'n8n-workflow';
|
||||||
|
@ -69,9 +64,9 @@ import {
|
||||||
} from './ui.utils';
|
} from './ui.utils';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import type { Connection } from '@vue-flow/core';
|
import type { Connection } from '@vue-flow/core';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
|
||||||
|
|
||||||
let savedTheme: ThemeOption = 'system';
|
let savedTheme: ThemeOption = 'system';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const value = getThemeOverride();
|
const value = getThemeOverride();
|
||||||
if (isValidTheme(value)) {
|
if (isValidTheme(value)) {
|
||||||
|
@ -189,8 +184,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const telemetry = useTelemetry();
|
|
||||||
const cloudPlanStore = useCloudPlanStore();
|
|
||||||
const userStore = useUsersStore();
|
const userStore = useUsersStore();
|
||||||
|
|
||||||
const appliedTheme = computed(() => {
|
const appliedTheme = computed(() => {
|
||||||
|
@ -525,33 +518,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
return parameters;
|
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) => {
|
const removeBannerFromStack = (name: BannerName) => {
|
||||||
bannerStack.value = bannerStack.value.filter((bannerName) => bannerName !== name);
|
bannerStack.value = bannerStack.value.filter((bannerName) => bannerName !== name);
|
||||||
};
|
};
|
||||||
|
@ -633,6 +599,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
currentView,
|
currentView,
|
||||||
isAnyModalOpen,
|
isAnyModalOpen,
|
||||||
pendingNotificationsForViews,
|
pendingNotificationsForViews,
|
||||||
|
activeModals,
|
||||||
setTheme,
|
setTheme,
|
||||||
setMode,
|
setMode,
|
||||||
setActiveId,
|
setActiveId,
|
||||||
|
@ -659,7 +626,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
setCurlCommand,
|
setCurlCommand,
|
||||||
toggleSidebarMenuCollapse,
|
toggleSidebarMenuCollapse,
|
||||||
getCurlToJson,
|
getCurlToJson,
|
||||||
goToUpgrade,
|
|
||||||
removeBannerFromStack,
|
removeBannerFromStack,
|
||||||
dismissBanner,
|
dismissBanner,
|
||||||
updateBannersHeight,
|
updateBannersHeight,
|
||||||
|
@ -668,7 +634,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||||
setNotificationsForView,
|
setNotificationsForView,
|
||||||
deleteNotificationsForView,
|
deleteNotificationsForView,
|
||||||
resetLastInteractedWith,
|
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 { defineStore } from 'pinia';
|
||||||
import { useRootStore } from '@/stores/root.store';
|
import { useRootStore } from '@/stores/root.store';
|
||||||
import { usePostHog } from './posthog.store';
|
import { usePostHog } from './posthog.store';
|
||||||
import { useSettingsStore } from './settings.store';
|
|
||||||
import { useUIStore } from './ui.store';
|
import { useUIStore } from './ui.store';
|
||||||
import { useCloudPlanStore } from './cloudPlan.store';
|
import { useCloudPlanStore } from './cloudPlan.store';
|
||||||
import * as mfaApi from '@/api/mfa';
|
import * as mfaApi from '@/api/mfa';
|
||||||
|
@ -29,6 +28,7 @@ import * as invitationsApi from '@/api/invitation';
|
||||||
import { useNpsSurveyStore } from './npsSurvey.store';
|
import { useNpsSurveyStore } from './npsSurvey.store';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
|
||||||
const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
|
const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
|
||||||
const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner;
|
const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner;
|
||||||
|
@ -49,6 +49,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const cloudPlanStore = useCloudPlanStore();
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
|
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
// Composables
|
// Composables
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
import { DOCS_DOMAIN, MODAL_CONFIRM } from '@/constants';
|
import { DOCS_DOMAIN, MODAL_CONFIRM } from '@/constants';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SettingsApiView',
|
name: 'SettingsApiView',
|
||||||
|
@ -24,6 +25,7 @@ export default defineComponent({
|
||||||
...useToast(),
|
...useToast(),
|
||||||
...useMessage(),
|
...useMessage(),
|
||||||
...useUIStore(),
|
...useUIStore(),
|
||||||
|
pageRedirectionHelper: usePageRedirectionHelper(),
|
||||||
documentTitle: useDocumentTitle(),
|
documentTitle: useDocumentTitle(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -70,7 +72,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onUpgrade() {
|
onUpgrade() {
|
||||||
void this.uiStore.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
|
void this.pageRedirectionHelper.goToUpgrade('settings-n8n-api', 'upgrade-api', 'redirect');
|
||||||
},
|
},
|
||||||
async showDeleteModal() {
|
async showDeleteModal() {
|
||||||
const confirmed = await this.confirm(
|
const confirmed = await this.confirm(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||||
|
@ -7,12 +6,13 @@ import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
||||||
import { computed, onMounted } from 'vue';
|
import { computed, onMounted } from 'vue';
|
||||||
import ExternalSecretsProviderCard from '@/components/ExternalSecretsProviderCard.ee.vue';
|
import ExternalSecretsProviderCard from '@/components/ExternalSecretsProviderCard.ee.vue';
|
||||||
import type { ExternalSecretsProvider } from '@/Interface';
|
import type { ExternalSecretsProvider } from '@/Interface';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const uiStore = useUIStore();
|
|
||||||
const externalSecretsStore = useExternalSecretsStore();
|
const externalSecretsStore = useExternalSecretsStore();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const sortedProviders = computed(() => {
|
const sortedProviders = computed(() => {
|
||||||
return ([...externalSecretsStore.providers] as ExternalSecretsProvider[]).sort((a, b) => {
|
return ([...externalSecretsStore.providers] as ExternalSecretsProvider[]).sort((a, b) => {
|
||||||
|
@ -32,7 +32,7 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function goToUpgrade() {
|
function goToUpgrade() {
|
||||||
void uiStore.goToUpgrade('external-secrets', 'upgrade-external-secrets');
|
void pageRedirectionHelper.goToUpgrade('external-secrets', 'upgrade-external-secrets');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,10 @@ import { ElTable, ElTableColumn } from 'element-plus';
|
||||||
import type { Events } from 'v3-infinite-loading';
|
import type { Events } from 'v3-infinite-loading';
|
||||||
import InfiniteLoading from 'v3-infinite-loading';
|
import InfiniteLoading from 'v3-infinite-loading';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { createFormEventBus } from 'n8n-design-system/utils';
|
import { createFormEventBus } from 'n8n-design-system/utils';
|
||||||
import type { TableColumnCtx } from 'element-plus';
|
import type { TableColumnCtx } from 'element-plus';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
type TableRow = {
|
type TableRow = {
|
||||||
status: string;
|
status: string;
|
||||||
|
@ -67,9 +67,9 @@ const toast = useToast();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const uiStore = useUIStore();
|
|
||||||
|
|
||||||
const dataTable = ref<ILdapSyncTable[]>([]);
|
const dataTable = ref<ILdapSyncTable[]>([]);
|
||||||
const tableKey = ref(0);
|
const tableKey = ref(0);
|
||||||
|
@ -89,7 +89,7 @@ const ldapConfigFormRef = ref<{ getValues: () => LDAPConfigForm }>();
|
||||||
|
|
||||||
const isLDAPFeatureEnabled = computed(() => settingsStore.settings.enterprise.ldap);
|
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 => {
|
const cellClassStyle = ({ row, column }: CellClassStyleMethodParams<TableRow>): CSSProperties => {
|
||||||
if (column.property === 'status') {
|
if (column.property === 'status') {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { deepCopy, defaultMessageEventBusDestinationOptions } from 'n8n-workflow
|
||||||
import EventDestinationCard from '@/components/SettingsLogStreaming/EventDestinationCard.ee.vue';
|
import EventDestinationCard from '@/components/SettingsLogStreaming/EventDestinationCard.ee.vue';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SettingsLogStreamingView',
|
name: 'SettingsLogStreamingView',
|
||||||
|
@ -28,6 +29,7 @@ export default defineComponent({
|
||||||
disableLicense: false,
|
disableLicense: false,
|
||||||
allDestinations: [] as MessageEventBusDestinationOptions[],
|
allDestinations: [] as MessageEventBusDestinationOptions[],
|
||||||
documentTitle: useDocumentTitle(),
|
documentTitle: useDocumentTitle(),
|
||||||
|
pageRedirectionHelper: usePageRedirectionHelper(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
@ -117,7 +119,7 @@ export default defineComponent({
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
},
|
},
|
||||||
goToUpgrade() {
|
goToUpgrade() {
|
||||||
void this.uiStore.goToUpgrade('log-streaming', 'upgrade-log-streaming');
|
void this.pageRedirectionHelper.goToUpgrade('log-streaming', 'upgrade-log-streaming');
|
||||||
},
|
},
|
||||||
storeHasItems(): boolean {
|
storeHasItems(): boolean {
|
||||||
return this.logStreamingStore.items && Object.keys(this.logStreamingStore.items).length > 0;
|
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 type { Rule, RuleGroup } from 'n8n-design-system/types';
|
||||||
import { MODAL_CONFIRM } from '@/constants';
|
import { MODAL_CONFIRM } from '@/constants';
|
||||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useLoadingService } from '@/composables/useLoadingService';
|
import { useLoadingService } from '@/composables/useLoadingService';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
@ -12,10 +11,11 @@ import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||||
import CopyInput from '@/components/CopyInput.vue';
|
import CopyInput from '@/components/CopyInput.vue';
|
||||||
import type { TupleToUnion } from '@/utils/typeHelpers';
|
import type { TupleToUnion } from '@/utils/typeHelpers';
|
||||||
import type { SshKeyTypes } from '@/Interface';
|
import type { SshKeyTypes } from '@/Interface';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const locale = useI18n();
|
const locale = useI18n();
|
||||||
const sourceControlStore = useSourceControlStore();
|
const sourceControlStore = useSourceControlStore();
|
||||||
const uiStore = useUIStore();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
@ -102,7 +102,7 @@ const onSelect = async (b: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToUpgrade = () => {
|
const goToUpgrade = () => {
|
||||||
void uiStore.goToUpgrade('source-control', 'upgrade-source-control');
|
void pageRedirectionHelper.goToUpgrade('source-control', 'upgrade-source-control');
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialize = async () => {
|
const initialize = async () => {
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { createTestingPinia } from '@pinia/testing';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import SettingsSso from './SettingsSso.vue';
|
import SettingsSso from './SettingsSso.vue';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { within, waitFor } from '@testing-library/vue';
|
import { within, waitFor } from '@testing-library/vue';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { mockedStore } from '@/__tests__/utils';
|
import { mockedStore } from '@/__tests__/utils';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const renderView = createComponentRenderer(SettingsSso);
|
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', () => {
|
describe('SettingsSso View', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
telemetryTrack.mockReset();
|
telemetryTrack.mockReset();
|
||||||
|
@ -50,7 +59,7 @@ describe('SettingsSso View', () => {
|
||||||
const ssoStore = mockedStore(useSSOStore);
|
const ssoStore = mockedStore(useSSOStore);
|
||||||
ssoStore.isEnterpriseSamlEnabled = false;
|
ssoStore.isEnterpriseSamlEnabled = false;
|
||||||
|
|
||||||
const uiStore = useUIStore();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const { getByTestId } = renderView({ pinia });
|
const { getByTestId } = renderView({ pinia });
|
||||||
|
|
||||||
|
@ -58,7 +67,7 @@ describe('SettingsSso View', () => {
|
||||||
expect(actionBox).toBeInTheDocument();
|
expect(actionBox).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(await within(actionBox).findByText('See plans'));
|
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 () => {
|
it('should show user SSO config', async () => {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, onMounted } from 'vue';
|
import { computed, ref, onMounted } from 'vue';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import CopyInput from '@/components/CopyInput.vue';
|
import CopyInput from '@/components/CopyInput.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useMessage } from '@/composables/useMessage';
|
import { useMessage } from '@/composables/useMessage';
|
||||||
|
@ -9,6 +8,7 @@ import { useToast } from '@/composables/useToast';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||||
import { useRootStore } from '@/stores/root.store';
|
import { useRootStore } from '@/stores/root.store';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const IdentityProviderSettingsType = {
|
const IdentityProviderSettingsType = {
|
||||||
URL: 'url',
|
URL: 'url',
|
||||||
|
@ -19,10 +19,10 @@ const i18n = useI18n();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const ssoStore = useSSOStore();
|
const ssoStore = useSSOStore();
|
||||||
const uiStore = useUIStore();
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const ssoActivatedLabel = computed(() =>
|
const ssoActivatedLabel = computed(() =>
|
||||||
ssoStore.isSamlLoginEnabled
|
ssoStore.isSamlLoginEnabled
|
||||||
|
@ -133,7 +133,7 @@ const onTest = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToUpgrade = () => {
|
const goToUpgrade = () => {
|
||||||
void uiStore.goToUpgrade('sso', 'upgrade-sso');
|
void pageRedirectionHelper.goToUpgrade('sso', 'upgrade-sso');
|
||||||
};
|
};
|
||||||
|
|
||||||
const isToggleSsoDisabled = computed(() => {
|
const isToggleSsoDisabled = computed(() => {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import N8nInfoTip from 'n8n-design-system/components/N8nInfoTip';
|
||||||
import { COMMUNITY_PLUS_ENROLLMENT_MODAL } from '@/constants';
|
import { COMMUNITY_PLUS_ENROLLMENT_MODAL } from '@/constants';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { getResourcePermissions } from '@/permissions';
|
import { getResourcePermissions } from '@/permissions';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const usageStore = useUsageStore();
|
const usageStore = useUsageStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -21,6 +22,7 @@ const uiStore = useUIStore();
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const queryParamCallback = ref<string>(
|
const queryParamCallback = ref<string>(
|
||||||
`callback=${encodeURIComponent(`${window.location.origin}${window.location.pathname}`)}`,
|
`callback=${encodeURIComponent(`${window.location.origin}${window.location.pathname}`)}`,
|
||||||
|
@ -129,7 +131,7 @@ const onAddActivationKey = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onViewPlans = () => {
|
const onViewPlans = () => {
|
||||||
void uiStore.goToUpgrade('usage_page', 'open');
|
void pageRedirectionHelper.goToUpgrade('usage_page', 'open');
|
||||||
sendUsageTelemetry('view_plans');
|
sendUsageTelemetry('view_plans');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { merge } from 'lodash-es';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import { STORES } from '@/constants';
|
import { STORES } from '@/constants';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const loggedInUser = createUser();
|
const loggedInUser = createUser();
|
||||||
const invitedUser = 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', () => {
|
describe('SettingsUsersView', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
copy.mockReset();
|
copy.mockReset();
|
||||||
|
@ -105,14 +115,17 @@ describe('SettingsUsersView', () => {
|
||||||
|
|
||||||
it('allows the user to upgrade', async () => {
|
it('allows the user to upgrade', async () => {
|
||||||
const { getByTestId } = renderView({ pinia });
|
const { getByTestId } = renderView({ pinia });
|
||||||
const uiStore = useUIStore();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const actionBox = getByTestId('action-box');
|
const actionBox = getByTestId('action-box');
|
||||||
expect(actionBox).toBeInTheDocument();
|
expect(actionBox).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(await within(actionBox).findByText('View plans'));
|
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 { computed, onMounted } from 'vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
const { showToast, showError } = useToast();
|
const { showToast, showError } = useToast();
|
||||||
|
@ -22,6 +23,7 @@ const uiStore = useUIStore();
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const ssoStore = useSSOStore();
|
const ssoStore = useSSOStore();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
@ -209,10 +211,10 @@ async function onDisallowSSOManualLogin(userId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function goToUpgrade() {
|
function goToUpgrade() {
|
||||||
void uiStore.goToUpgrade('settings-users', 'upgrade-users');
|
void pageRedirectionHelper.goToUpgrade('settings-users', 'upgrade-users');
|
||||||
}
|
}
|
||||||
function goToUpgradeAdvancedPermissions() {
|
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']) {
|
async function onRoleChange(user: IUser, newRoleName: UpdateGlobalRolePayload['newRoleName']) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import type { DatatableColumn, EnvironmentVariable } from '@/Interface';
|
||||||
import { uid } from 'n8n-design-system/utils';
|
import { uid } from 'n8n-design-system/utils';
|
||||||
import { getResourcePermissions } from '@/permissions';
|
import { getResourcePermissions } from '@/permissions';
|
||||||
import type { BaseTextKey } from '@/plugins/i18n';
|
import type { BaseTextKey } from '@/plugins/i18n';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const environmentsStore = useEnvironmentsStore();
|
const environmentsStore = useEnvironmentsStore();
|
||||||
|
@ -30,6 +31,7 @@ const i18n = useI18n();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const sourceControlStore = useSourceControlStore();
|
const sourceControlStore = useSourceControlStore();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
let sourceControlStoreUnsubscribe = () => {};
|
let sourceControlStoreUnsubscribe = () => {};
|
||||||
|
|
||||||
const layoutRef = ref<InstanceType<typeof ResourcesListLayout> | null>(null);
|
const layoutRef = ref<InstanceType<typeof ResourcesListLayout> | null>(null);
|
||||||
|
@ -230,7 +232,7 @@ async function deleteVariable(data: IResource) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToUpgrade() {
|
function goToUpgrade() {
|
||||||
void uiStore.goToUpgrade('variables', 'upgrade-variables');
|
void pageRedirectionHelper.goToUpgrade('variables', 'upgrade-variables');
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayName(resource: IResource) {
|
function displayName(resource: IResource) {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import WorkerList from '@/components/WorkerList.ee.vue';
|
import WorkerList from '@/components/WorkerList.ee.vue';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const uiStore = useUIStore();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const goToUpgrade = () => {
|
const goToUpgrade = () => {
|
||||||
void uiStore.goToUpgrade('worker-view', 'upgrade-worker-view');
|
void pageRedirectionHelper.goToUpgrade('worker-view', 'upgrade-worker-view');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { telemetry } from '@/plugins/telemetry';
|
import { telemetry } from '@/plugins/telemetry';
|
||||||
import { useRootStore } from '@/stores/root.store';
|
import { useRootStore } from '@/stores/root.store';
|
||||||
import { getResourcePermissions } from '@/permissions';
|
import { getResourcePermissions } from '@/permissions';
|
||||||
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
|
||||||
type WorkflowHistoryActionRecord = {
|
type WorkflowHistoryActionRecord = {
|
||||||
[K in Uppercase<WorkflowHistoryActionTypes[number]>]: Lowercase<K>;
|
[K in Uppercase<WorkflowHistoryActionTypes[number]>]: Lowercase<K>;
|
||||||
|
@ -46,6 +47,8 @@ const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const workflowHistoryStore = useWorkflowHistoryStore();
|
const workflowHistoryStore = useWorkflowHistoryStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
@ -296,7 +299,7 @@ const onPreview = async ({ event, id }: { event: MouseEvent; id: WorkflowVersion
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpgrade = () => {
|
const onUpgrade = () => {
|
||||||
void uiStore.goToUpgrade('workflow-history', 'upgrade-workflow-history');
|
void pageRedirectionHelper.goToUpgrade('workflow-history', 'upgrade-workflow-history');
|
||||||
};
|
};
|
||||||
|
|
||||||
watchEffect(async () => {
|
watchEffect(async () => {
|
||||||
|
|
Loading…
Reference in a new issue