mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Migrate existing users to new canvas and set new canvas as default (#11896)
This commit is contained in:
@@ -75,8 +75,13 @@ Cypress.Commands.add('signin', ({ email, password }) => {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
Cypress.env('currentUserId', response.body.data.id);
|
Cypress.env('currentUserId', response.body.data.id);
|
||||||
|
|
||||||
|
// @TODO Remove this once the switcher is removed
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem('NodeView.switcher.discovered', 'true'); // @TODO Remove this once the switcher is removed
|
win.localStorage.setItem('NodeView.migrated', 'true');
|
||||||
|
win.localStorage.setItem('NodeView.switcher.discovered.beta', 'true');
|
||||||
|
|
||||||
|
const nodeViewVersion = Cypress.env('NODE_VIEW_VERSION');
|
||||||
|
win.localStorage.setItem('NodeView.version', nodeViewVersion ?? '1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,11 +20,6 @@ beforeEach(() => {
|
|||||||
win.localStorage.setItem('N8N_THEME', 'light');
|
win.localStorage.setItem('N8N_THEME', 'light');
|
||||||
win.localStorage.setItem('N8N_AUTOCOMPLETE_ONBOARDED', 'true');
|
win.localStorage.setItem('N8N_AUTOCOMPLETE_ONBOARDED', 'true');
|
||||||
win.localStorage.setItem('N8N_MAPPING_ONBOARDED', 'true');
|
win.localStorage.setItem('N8N_MAPPING_ONBOARDED', 'true');
|
||||||
|
|
||||||
const nodeViewVersion = Cypress.env('NODE_VIEW_VERSION');
|
|
||||||
if (nodeViewVersion) {
|
|
||||||
win.localStorage.setItem('NodeView.version', nodeViewVersion);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.intercept('GET', '/rest/settings', (req) => {
|
cy.intercept('GET', '/rest/settings', (req) => {
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ const tagsEventBus = createEventBus();
|
|||||||
const sourceControlModalEventBus = createEventBus();
|
const sourceControlModalEventBus = createEventBus();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
isNewUser,
|
||||||
nodeViewVersion,
|
nodeViewVersion,
|
||||||
nodeViewSwitcherDiscovered,
|
nodeViewSwitcherDiscovered,
|
||||||
isNodeViewDiscoveryTooltipVisible,
|
isNodeViewDiscoveryTooltipVisible,
|
||||||
@@ -193,10 +194,14 @@ const workflowMenuItems = computed<ActionDropdownItem[]>(() => {
|
|||||||
actions.push({
|
actions.push({
|
||||||
id: WORKFLOW_MENU_ACTIONS.SWITCH_NODE_VIEW_VERSION,
|
id: WORKFLOW_MENU_ACTIONS.SWITCH_NODE_VIEW_VERSION,
|
||||||
...(nodeViewVersion.value === '2'
|
...(nodeViewVersion.value === '2'
|
||||||
? {}
|
? nodeViewSwitcherDiscovered.value || isNewUser.value
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
badge: locale.baseText('menuActions.badge.new'),
|
||||||
|
}
|
||||||
: nodeViewSwitcherDiscovered.value
|
: nodeViewSwitcherDiscovered.value
|
||||||
? {
|
? {
|
||||||
badge: locale.baseText('menuActions.badge.alpha'),
|
badge: locale.baseText('menuActions.badge.beta'),
|
||||||
badgeProps: {
|
badgeProps: {
|
||||||
theme: 'tertiary',
|
theme: 'tertiary',
|
||||||
},
|
},
|
||||||
@@ -756,9 +761,12 @@ function showCreateWorkflowSuccessToast(id?: string) {
|
|||||||
/>
|
/>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="mb-4xs">
|
<div class="mb-4xs">
|
||||||
<N8nBadge>{{ i18n.baseText('menuActions.badge.alpha') }}</N8nBadge>
|
<N8nBadge>{{ i18n.baseText('menuActions.badge.beta') }}</N8nBadge>
|
||||||
</div>
|
</div>
|
||||||
{{ i18n.baseText('menuActions.nodeViewDiscovery.tooltip') }}
|
<p>{{ i18n.baseText('menuActions.nodeViewDiscovery.tooltip') }}</p>
|
||||||
|
<N8nText color="text-light" size="small">
|
||||||
|
{{ i18n.baseText('menuActions.nodeViewDiscovery.tooltip.switchBack') }}
|
||||||
|
</N8nText>
|
||||||
<N8nIcon
|
<N8nIcon
|
||||||
:class="$style.closeNodeViewDiscovery"
|
:class="$style.closeNodeViewDiscovery"
|
||||||
icon="times-circle"
|
icon="times-circle"
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { useNodeViewVersionSwitcher } from './useNodeViewVersionSwitcher';
|
||||||
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { STORES } from '@/constants';
|
||||||
|
import { setActivePinia } from 'pinia';
|
||||||
|
import { mockedStore } from '@/__tests__/utils';
|
||||||
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
|
||||||
|
vi.mock('@/composables/useTelemetry', () => ({
|
||||||
|
useTelemetry: () => ({
|
||||||
|
track: vi.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useNodeViewVersionSwitcher', () => {
|
||||||
|
const initialState = {
|
||||||
|
[STORES.WORKFLOWS]: {},
|
||||||
|
[STORES.NDV]: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
const pinia = createTestingPinia({ initialState });
|
||||||
|
setActivePinia(pinia);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isNewUser', () => {
|
||||||
|
test('should return true when there are no active workflows', () => {
|
||||||
|
const { isNewUser } = useNodeViewVersionSwitcher();
|
||||||
|
expect(isNewUser.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return false when there are active workflows', () => {
|
||||||
|
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
workflowsStore.activeWorkflows = ['1'];
|
||||||
|
|
||||||
|
const { isNewUser } = useNodeViewVersionSwitcher();
|
||||||
|
expect(isNewUser.value).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('nodeViewVersion', () => {
|
||||||
|
test('should initialize with default version "2"', () => {
|
||||||
|
const { nodeViewVersion } = useNodeViewVersionSwitcher();
|
||||||
|
expect(nodeViewVersion.value).toBe('2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isNodeViewDiscoveryTooltipVisible', () => {
|
||||||
|
test('should be visible under correct conditions', () => {
|
||||||
|
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
workflowsStore.activeWorkflows = ['1'];
|
||||||
|
|
||||||
|
const ndvStore = mockedStore(useNDVStore);
|
||||||
|
ndvStore.activeNodeName = null;
|
||||||
|
|
||||||
|
const { isNodeViewDiscoveryTooltipVisible } = useNodeViewVersionSwitcher();
|
||||||
|
expect(isNodeViewDiscoveryTooltipVisible.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be visible for new users', () => {
|
||||||
|
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
workflowsStore.activeWorkflows = [];
|
||||||
|
|
||||||
|
const { isNodeViewDiscoveryTooltipVisible } = useNodeViewVersionSwitcher();
|
||||||
|
expect(isNodeViewDiscoveryTooltipVisible.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be visible when node is selected', () => {
|
||||||
|
const ndvStore = mockedStore(useNDVStore);
|
||||||
|
ndvStore.activeNodeName = 'test-node';
|
||||||
|
|
||||||
|
const { isNodeViewDiscoveryTooltipVisible } = useNodeViewVersionSwitcher();
|
||||||
|
expect(isNodeViewDiscoveryTooltipVisible.value).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('switchNodeViewVersion', () => {
|
||||||
|
test('should switch from version 2 to 1 and back', () => {
|
||||||
|
const { nodeViewVersion, switchNodeViewVersion } = useNodeViewVersionSwitcher();
|
||||||
|
|
||||||
|
switchNodeViewVersion();
|
||||||
|
|
||||||
|
expect(nodeViewVersion.value).toBe('1');
|
||||||
|
|
||||||
|
switchNodeViewVersion();
|
||||||
|
|
||||||
|
expect(nodeViewVersion.value).toBe('2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('migrateToNewNodeViewVersion', () => {
|
||||||
|
test('should not migrate if already migrated', () => {
|
||||||
|
const { nodeViewVersion, nodeViewVersionMigrated, migrateToNewNodeViewVersion } =
|
||||||
|
useNodeViewVersionSwitcher();
|
||||||
|
nodeViewVersionMigrated.value = true;
|
||||||
|
|
||||||
|
migrateToNewNodeViewVersion();
|
||||||
|
|
||||||
|
expect(nodeViewVersion.value).toBe('2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not migrate if already on version 2', () => {
|
||||||
|
const { nodeViewVersion, migrateToNewNodeViewVersion } = useNodeViewVersionSwitcher();
|
||||||
|
nodeViewVersion.value = '2';
|
||||||
|
|
||||||
|
migrateToNewNodeViewVersion();
|
||||||
|
|
||||||
|
expect(nodeViewVersion.value).not.toBe('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should migrate to version 2 if not migrated and on version 1', () => {
|
||||||
|
const { nodeViewVersion, nodeViewVersionMigrated, migrateToNewNodeViewVersion } =
|
||||||
|
useNodeViewVersionSwitcher();
|
||||||
|
nodeViewVersion.value = '1';
|
||||||
|
nodeViewVersionMigrated.value = false;
|
||||||
|
|
||||||
|
migrateToNewNodeViewVersion();
|
||||||
|
|
||||||
|
expect(nodeViewVersion.value).toBe('2');
|
||||||
|
expect(nodeViewVersionMigrated.value).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setNodeViewSwitcherDropdownOpened', () => {
|
||||||
|
test('should set discovered when dropdown is closed', () => {
|
||||||
|
const { setNodeViewSwitcherDropdownOpened, nodeViewSwitcherDiscovered } =
|
||||||
|
useNodeViewVersionSwitcher();
|
||||||
|
|
||||||
|
setNodeViewSwitcherDropdownOpened(false);
|
||||||
|
|
||||||
|
expect(nodeViewSwitcherDiscovered.value).toBe(true);
|
||||||
|
nodeViewSwitcherDiscovered.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not set discovered when dropdown is opened', () => {
|
||||||
|
const { setNodeViewSwitcherDropdownOpened, nodeViewSwitcherDiscovered } =
|
||||||
|
useNodeViewVersionSwitcher();
|
||||||
|
|
||||||
|
setNodeViewSwitcherDropdownOpened(true);
|
||||||
|
|
||||||
|
expect(nodeViewSwitcherDiscovered.value).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setNodeViewSwitcherDiscovered', () => {
|
||||||
|
test('should set nodeViewSwitcherDiscovered to true', () => {
|
||||||
|
const { setNodeViewSwitcherDiscovered, nodeViewSwitcherDiscovered } =
|
||||||
|
useNodeViewVersionSwitcher();
|
||||||
|
|
||||||
|
setNodeViewSwitcherDiscovered();
|
||||||
|
|
||||||
|
expect(nodeViewSwitcherDiscovered.value).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useLocalStorage, debouncedRef } from '@vueuse/core';
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
@@ -8,16 +7,12 @@ import { useNDVStore } from '@/stores/ndv.store';
|
|||||||
export function useNodeViewVersionSwitcher() {
|
export function useNodeViewVersionSwitcher() {
|
||||||
const ndvStore = useNDVStore();
|
const ndvStore = useNDVStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
const isNewUser = computed(() => workflowsStore.activeWorkflows.length === 0);
|
const isNewUser = computed(() => workflowsStore.activeWorkflows.length === 0);
|
||||||
const isNewUserDebounced = debouncedRef(isNewUser, 3000);
|
|
||||||
|
|
||||||
const nodeViewVersion = useLocalStorage(
|
const nodeViewVersion = useLocalStorage('NodeView.version', '2');
|
||||||
'NodeView.version',
|
const nodeViewVersionMigrated = useLocalStorage('NodeView.migrated', false);
|
||||||
settingsStore.isCanvasV2Enabled ? '2' : '1',
|
|
||||||
);
|
|
||||||
|
|
||||||
function setNodeViewSwitcherDropdownOpened(visible: boolean) {
|
function setNodeViewSwitcherDropdownOpened(visible: boolean) {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
@@ -25,20 +20,21 @@ export function useNodeViewVersionSwitcher() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeViewSwitcherDiscovered = useLocalStorage('NodeView.switcher.discovered', false);
|
const nodeViewSwitcherDiscovered = useLocalStorage('NodeView.switcher.discovered.beta', false);
|
||||||
function setNodeViewSwitcherDiscovered() {
|
function setNodeViewSwitcherDiscovered() {
|
||||||
nodeViewSwitcherDiscovered.value = true;
|
nodeViewSwitcherDiscovered.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNodeViewDiscoveryTooltipVisible = computed(
|
const isNodeViewDiscoveryTooltipVisible = computed(
|
||||||
() =>
|
() =>
|
||||||
|
!isNewUser.value &&
|
||||||
!ndvStore.activeNodeName &&
|
!ndvStore.activeNodeName &&
|
||||||
nodeViewVersion.value !== '2' &&
|
nodeViewVersion.value === '2' &&
|
||||||
!(isNewUserDebounced.value || nodeViewSwitcherDiscovered.value),
|
!nodeViewSwitcherDiscovered.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
function switchNodeViewVersion() {
|
function switchNodeViewVersion() {
|
||||||
const toVersion = nodeViewVersion.value === '1' ? '2' : '1';
|
const toVersion = nodeViewVersion.value === '2' ? '1' : '2';
|
||||||
|
|
||||||
telemetry.track('User switched canvas version', {
|
telemetry.track('User switched canvas version', {
|
||||||
to_version: toVersion,
|
to_version: toVersion,
|
||||||
@@ -47,12 +43,24 @@ export function useNodeViewVersionSwitcher() {
|
|||||||
nodeViewVersion.value = toVersion;
|
nodeViewVersion.value = toVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function migrateToNewNodeViewVersion() {
|
||||||
|
if (nodeViewVersionMigrated.value || nodeViewVersion.value === '2') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchNodeViewVersion();
|
||||||
|
nodeViewVersionMigrated.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isNewUser,
|
||||||
nodeViewVersion,
|
nodeViewVersion,
|
||||||
|
nodeViewVersionMigrated,
|
||||||
nodeViewSwitcherDiscovered,
|
nodeViewSwitcherDiscovered,
|
||||||
isNodeViewDiscoveryTooltipVisible,
|
isNodeViewDiscoveryTooltipVisible,
|
||||||
setNodeViewSwitcherDropdownOpened,
|
setNodeViewSwitcherDropdownOpened,
|
||||||
setNodeViewSwitcherDiscovered,
|
setNodeViewSwitcherDiscovered,
|
||||||
switchNodeViewVersion,
|
switchNodeViewVersion,
|
||||||
|
migrateToNewNodeViewVersion,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -912,7 +912,9 @@
|
|||||||
"menuActions.switchToOldNodeViewVersion": "Switch to old canvas",
|
"menuActions.switchToOldNodeViewVersion": "Switch to old canvas",
|
||||||
"menuActions.badge.new": "NEW",
|
"menuActions.badge.new": "NEW",
|
||||||
"menuActions.badge.alpha": "ALPHA",
|
"menuActions.badge.alpha": "ALPHA",
|
||||||
"menuActions.nodeViewDiscovery.tooltip": "Try our new, more performant canvas",
|
"menuActions.badge.beta": "BETA",
|
||||||
|
"menuActions.nodeViewDiscovery.tooltip": "You're currently using our new, more performant canvas.",
|
||||||
|
"menuActions.nodeViewDiscovery.tooltip.switchBack": "You can switch back to the old version using this menu.",
|
||||||
"multipleParameter.addItem": "Add item",
|
"multipleParameter.addItem": "Add item",
|
||||||
"multipleParameter.currentlyNoItemsExist": "Currently no items exist",
|
"multipleParameter.currentlyNoItemsExist": "Currently no items exist",
|
||||||
"multipleParameter.deleteItem": "Delete item",
|
"multipleParameter.deleteItem": "Delete item",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch } from 'vue';
|
import { computed, onMounted, watch } from 'vue';
|
||||||
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router';
|
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router';
|
||||||
import NodeViewV1 from '@/views/NodeView.vue';
|
import NodeViewV1 from '@/views/NodeView.vue';
|
||||||
import NodeViewV2 from '@/views/NodeView.v2.vue';
|
import NodeViewV2 from '@/views/NodeView.v2.vue';
|
||||||
@@ -17,7 +17,7 @@ const router = useRouter();
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const workflowHelpers = useWorkflowHelpers({ router });
|
const workflowHelpers = useWorkflowHelpers({ router });
|
||||||
|
|
||||||
const { nodeViewVersion } = useNodeViewVersionSwitcher();
|
const { nodeViewVersion, migrateToNewNodeViewVersion } = useNodeViewVersionSwitcher();
|
||||||
|
|
||||||
const workflowId = computed<string>(() => route.params.name as string);
|
const workflowId = computed<string>(() => route.params.name as string);
|
||||||
|
|
||||||
@@ -25,6 +25,10 @@ const isReadOnlyEnvironment = computed(() => {
|
|||||||
return sourceControlStore.preferences.branchReadOnly;
|
return sourceControlStore.preferences.branchReadOnly;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
migrateToNewNodeViewVersion();
|
||||||
|
});
|
||||||
|
|
||||||
watch(nodeViewVersion, () => {
|
watch(nodeViewVersion, () => {
|
||||||
router.go(0);
|
router.go(0);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user