Skip to content

Commit

Permalink
feat(editor): Migrate existing users to new canvas and set new canvas…
Browse files Browse the repository at this point in the history
… as default (#11896)
  • Loading branch information
alexgrozav authored and despairblue committed Nov 28, 2024
1 parent 88e7952 commit 902d716
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 25 deletions.
7 changes: 6 additions & 1 deletion cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,13 @@ Cypress.Commands.add('signin', ({ email, password }) => {
.then((response) => {
Cypress.env('currentUserId', response.body.data.id);

// @TODO Remove this once the switcher is removed
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');
});
});
});
Expand Down
5 changes: 0 additions & 5 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ beforeEach(() => {
win.localStorage.setItem('N8N_THEME', 'light');
win.localStorage.setItem('N8N_AUTOCOMPLETE_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) => {
Expand Down
16 changes: 12 additions & 4 deletions packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const tagsEventBus = createEventBus();
const sourceControlModalEventBus = createEventBus();
const {
isNewUser,
nodeViewVersion,
nodeViewSwitcherDiscovered,
isNodeViewDiscoveryTooltipVisible,
Expand Down Expand Up @@ -193,10 +194,14 @@ const workflowMenuItems = computed<ActionDropdownItem[]>(() => {
actions.push({
id: WORKFLOW_MENU_ACTIONS.SWITCH_NODE_VIEW_VERSION,
...(nodeViewVersion.value === '2'
? {}
? nodeViewSwitcherDiscovered.value || isNewUser.value
? {}
: {
badge: locale.baseText('menuActions.badge.new'),
}
: nodeViewSwitcherDiscovered.value
? {
badge: locale.baseText('menuActions.badge.alpha'),
badge: locale.baseText('menuActions.badge.beta'),
badgeProps: {
theme: 'tertiary',
},
Expand Down Expand Up @@ -756,9 +761,12 @@ function showCreateWorkflowSuccessToast(id?: string) {
/>
<template #content>
<div class="mb-4xs">
<N8nBadge>{{ i18n.baseText('menuActions.badge.alpha') }}</N8nBadge>
<N8nBadge>{{ i18n.baseText('menuActions.badge.beta') }}</N8nBadge>
</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
:class="$style.closeNodeViewDiscovery"
icon="times-circle"
Expand Down
156 changes: 156 additions & 0 deletions packages/editor-ui/src/composables/useNodeViewVersionSwitcher.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
32 changes: 20 additions & 12 deletions packages/editor-ui/src/composables/useNodeViewVersionSwitcher.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,40 @@
import { computed } from 'vue';
import { useLocalStorage, debouncedRef } from '@vueuse/core';
import { useSettingsStore } from '@/stores/settings.store';
import { useLocalStorage } from '@vueuse/core';
import { useTelemetry } from '@/composables/useTelemetry';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';

export function useNodeViewVersionSwitcher() {
const ndvStore = useNDVStore();
const workflowsStore = useWorkflowsStore();
const settingsStore = useSettingsStore();
const telemetry = useTelemetry();

const isNewUser = computed(() => workflowsStore.activeWorkflows.length === 0);
const isNewUserDebounced = debouncedRef(isNewUser, 3000);

const nodeViewVersion = useLocalStorage(
'NodeView.version',
settingsStore.isCanvasV2Enabled ? '2' : '1',
);
const nodeViewVersion = useLocalStorage('NodeView.version', '2');
const nodeViewVersionMigrated = useLocalStorage('NodeView.migrated', false);

function setNodeViewSwitcherDropdownOpened(visible: boolean) {
if (!visible) {
setNodeViewSwitcherDiscovered();
}
}

const nodeViewSwitcherDiscovered = useLocalStorage('NodeView.switcher.discovered', false);
const nodeViewSwitcherDiscovered = useLocalStorage('NodeView.switcher.discovered.beta', false);
function setNodeViewSwitcherDiscovered() {
nodeViewSwitcherDiscovered.value = true;
}

const isNodeViewDiscoveryTooltipVisible = computed(
() =>
!isNewUser.value &&
!ndvStore.activeNodeName &&
nodeViewVersion.value !== '2' &&
!(isNewUserDebounced.value || nodeViewSwitcherDiscovered.value),
nodeViewVersion.value === '2' &&
!nodeViewSwitcherDiscovered.value,
);

function switchNodeViewVersion() {
const toVersion = nodeViewVersion.value === '1' ? '2' : '1';
const toVersion = nodeViewVersion.value === '2' ? '1' : '2';

telemetry.track('User switched canvas version', {
to_version: toVersion,
Expand All @@ -47,12 +43,24 @@ export function useNodeViewVersionSwitcher() {
nodeViewVersion.value = toVersion;
}

function migrateToNewNodeViewVersion() {
if (nodeViewVersionMigrated.value || nodeViewVersion.value === '2') {
return;
}

switchNodeViewVersion();
nodeViewVersionMigrated.value = true;
}

return {
isNewUser,
nodeViewVersion,
nodeViewVersionMigrated,
nodeViewSwitcherDiscovered,
isNodeViewDiscoveryTooltipVisible,
setNodeViewSwitcherDropdownOpened,
setNodeViewSwitcherDiscovered,
switchNodeViewVersion,
migrateToNewNodeViewVersion,
};
}
4 changes: 3 additions & 1 deletion packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,9 @@
"menuActions.switchToOldNodeViewVersion": "Switch to old canvas",
"menuActions.badge.new": "NEW",
"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.currentlyNoItemsExist": "Currently no items exist",
"multipleParameter.deleteItem": "Delete item",
Expand Down
8 changes: 6 additions & 2 deletions packages/editor-ui/src/views/NodeViewSwitcher.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, watch } from 'vue';
import { computed, onMounted, watch } from 'vue';
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router';
import NodeViewV1 from '@/views/NodeView.vue';
import NodeViewV2 from '@/views/NodeView.v2.vue';
Expand All @@ -17,14 +17,18 @@ const router = useRouter();
const route = useRoute();
const workflowHelpers = useWorkflowHelpers({ router });
const { nodeViewVersion } = useNodeViewVersionSwitcher();
const { nodeViewVersion, migrateToNewNodeViewVersion } = useNodeViewVersionSwitcher();
const workflowId = computed<string>(() => route.params.name as string);
const isReadOnlyEnvironment = computed(() => {
return sourceControlStore.preferences.branchReadOnly;
});
onMounted(() => {
migrateToNewNodeViewVersion();
});
watch(nodeViewVersion, () => {
router.go(0);
});
Expand Down

0 comments on commit 902d716

Please sign in to comment.