Skip to content

Commit

Permalink
fix(editor): Move versions check to init function and refactor store …
Browse files Browse the repository at this point in the history
…(no-changelog) (n8n-io#8067)
  • Loading branch information
alexgrozav authored Dec 20, 2023
1 parent faadfd6 commit fcff34c
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 85 deletions.
32 changes: 32 additions & 0 deletions cypress/composables/versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Getters
*/

export function getVersionUpdatesPanelOpenButton() {
return cy.getByTestId('version-updates-panel-button');
}

export function getVersionUpdatesPanel() {
return cy.getByTestId('version-updates-panel');
}

export function getVersionUpdatesPanelCloseButton() {
return getVersionUpdatesPanel().get('.el-drawer__close-btn').first();
}

export function getVersionCard() {
return cy.getByTestId('version-card');
}

/**
* Actions
*/

export function openVersionUpdatesPanel() {
getVersionUpdatesPanelOpenButton().click();
getVersionUpdatesPanel().should('be.visible');
}

export function closeVersionUpdatesPanel() {
getVersionUpdatesPanelCloseButton().click();
}
66 changes: 66 additions & 0 deletions cypress/e2e/36-versions.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { INSTANCE_OWNER } from '../constants';
import { WorkflowsPage } from '../pages/workflows';
import {
closeVersionUpdatesPanel,
getVersionCard,
getVersionUpdatesPanelOpenButton,
openVersionUpdatesPanel,
} from '../composables/versions';

const workflowsPage = new WorkflowsPage();

describe('Versions', () => {
it('should open updates panel', () => {
cy.intercept('GET', '/rest/settings', (req) => {
req.continue((res) => {
if (res.body.hasOwnProperty('data')) {
res.body.data = {
...res.body.data,
releaseChannel: 'stable',
versionCli: '1.0.0',
versionNotifications: {
enabled: true,
endpoint: 'https://api.n8n.io/api/versions/',
infoUrl: 'https://docs.n8n.io/getting-started/installation/updating.html',
},
};
}
});
}).as('settings');

cy.intercept('GET', 'https://api.n8n.io/api/versions/1.0.0', [
{
name: '1.3.1',
createdAt: '2023-08-18T11:53:12.857Z',
hasSecurityIssue: null,
hasSecurityFix: null,
securityIssueFixVersion: null,
hasBreakingChange: null,
documentationUrl: 'https://docs.n8n.io/release-notes/#n8n131',
nodes: [],
description: 'Includes <strong>bug fixes</strong>',
},
{
name: '1.0.5',
createdAt: '2023-07-24T10:54:56.097Z',
hasSecurityIssue: false,
hasSecurityFix: null,
securityIssueFixVersion: null,
hasBreakingChange: true,
documentationUrl: 'https://docs.n8n.io/release-notes/#n8n104',
nodes: [],
description: 'Includes <strong>core functionality</strong> and <strong>bug fixes</strong>',
},
]);

cy.signin(INSTANCE_OWNER);

cy.visit(workflowsPage.url);
cy.wait('@settings');

getVersionUpdatesPanelOpenButton().should('contain', '2 updates');
openVersionUpdatesPanel();
getVersionCard().should('have.length', 2);
closeVersionUpdatesPanel();
});
});
8 changes: 2 additions & 6 deletions packages/editor-ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { newVersions } from '@/mixins/newVersions';
import BannerStack from '@/components/banners/BannerStack.vue';
import Modals from '@/components/Modals.vue';
Expand Down Expand Up @@ -69,15 +68,13 @@ export default defineComponent({
Telemetry,
Modals,
},
mixins: [newVersions, userHelpers],
setup(props) {
mixins: [userHelpers],
setup() {
return {
...useGlobalLinkActions(),
...useHistoryHelper(useRoute()),
...useToast(),
externalHooks: useExternalHooks(),
// eslint-disable-next-line @typescript-eslint/no-misused-promises
...newVersions.setup?.(props),
};
},
computed: {
Expand Down Expand Up @@ -115,7 +112,6 @@ export default defineComponent({
async mounted() {
this.logHiringBanner();
void this.checkForNewVersions();
void initializeAuthenticatedFeatures();
void useExternalHooks().run('app.mount');
Expand Down
86 changes: 62 additions & 24 deletions packages/editor-ui/src/__tests__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,63 @@ import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { initializeAuthenticatedFeatures } from '@/init';
import type { SpyInstance } from 'vitest';
import { initializeAuthenticatedFeatures, initializeCore } from '@/init';
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import { useSettingsStore } from '@/stores/settings.store';
import { useVersionsStore } from '@/stores/versions.store';

vi.mock('@/stores/users.store', () => ({
useUsersStore: vi.fn(),
useUsersStore: vi.fn().mockReturnValue({ initialize: vi.fn() }),
}));

vi.mock('@/stores/n8nRoot.store', () => ({
useRootStore: vi.fn(),
}));

describe('Init', () => {
describe('Authenticated Features', () => {
let settingsStore: ReturnType<typeof useSettingsStore>;
let cloudPlanStore: ReturnType<typeof useCloudPlanStore>;
let sourceControlStore: ReturnType<typeof useSourceControlStore>;
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
let cloudStoreSpy: SpyInstance<[], Promise<void>>;
let templatesTestSpy: SpyInstance<[], Promise<void>>;
let sourceControlSpy: SpyInstance<[], Promise<void>>;
let nodeTranslationSpy: SpyInstance<[], Promise<void>>;

beforeAll(() => {
setActivePinia(createTestingPinia());
settingsStore = useSettingsStore();
cloudPlanStore = useCloudPlanStore();
sourceControlStore = useSourceControlStore();
nodeTypesStore = useNodeTypesStore();
let settingsStore: ReturnType<typeof useSettingsStore>;
let cloudPlanStore: ReturnType<typeof useCloudPlanStore>;
let sourceControlStore: ReturnType<typeof useSourceControlStore>;
let usersStore: ReturnType<typeof useUsersStore>;
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
let versionsStore: ReturnType<typeof useVersionsStore>;

beforeEach(() => {
setActivePinia(createTestingPinia());
settingsStore = useSettingsStore();
cloudPlanStore = useCloudPlanStore();
sourceControlStore = useSourceControlStore();
nodeTypesStore = useNodeTypesStore();
usersStore = useUsersStore();
versionsStore = useVersionsStore();
versionsStore = useVersionsStore();
});

describe('initializeCore()', () => {
afterEach(() => {
vi.clearAllMocks();
});

it('should initialize core features only once', async () => {
const usersStoreSpy = vi.spyOn(usersStore, 'initialize');
const settingsStoreSpy = vi.spyOn(settingsStore, 'initialize');
const versionsSpy = vi.spyOn(versionsStore, 'checkForNewVersions');

await initializeCore();

expect(settingsStoreSpy).toHaveBeenCalled();
expect(usersStoreSpy).toHaveBeenCalled();
expect(versionsSpy).toHaveBeenCalled();

await initializeCore();

expect(settingsStoreSpy).toHaveBeenCalledTimes(1);
});
});

describe('initializeAuthenticatedFeatures()', () => {
beforeEach(() => {
vi.spyOn(settingsStore, 'isCloudDeployment', 'get').mockReturnValue(true);
vi.spyOn(settingsStore, 'isTemplatesEnabled', 'get').mockReturnValue(true);
vi.spyOn(sourceControlStore, 'isEnterpriseSourceControlEnabled', 'get').mockReturnValue(true);
Expand All @@ -43,35 +69,47 @@ describe('Init', () => {
vi.mock('@/hooks/register', () => ({
initializeCloudHooks: vi.fn(),
}));
cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize');
templatesTestSpy = vi.spyOn(settingsStore, 'testTemplatesEndpoint');
sourceControlSpy = vi.spyOn(sourceControlStore, 'getPreferences');
nodeTranslationSpy = vi.spyOn(nodeTypesStore, 'getNodeTranslationHeaders');
});

afterEach(() => {
vi.clearAllMocks();
});

it('should not init authenticated features if user is not logged in', async () => {
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize');
const templatesTestSpy = vi.spyOn(settingsStore, 'testTemplatesEndpoint');
const sourceControlSpy = vi.spyOn(sourceControlStore, 'getPreferences');
const nodeTranslationSpy = vi.spyOn(nodeTypesStore, 'getNodeTranslationHeaders');
vi.mocked(useUsersStore).mockReturnValue({ currentUser: null } as ReturnType<
typeof useUsersStore
>);

await initializeAuthenticatedFeatures();
expect(cloudStoreSpy).not.toHaveBeenCalled();
expect(templatesTestSpy).not.toHaveBeenCalled();
expect(sourceControlSpy).not.toHaveBeenCalled();
expect(nodeTranslationSpy).not.toHaveBeenCalled();
});
it('should init authenticated features if user is not logged in', async () => {

it('should init authenticated features only once if user is logged in', async () => {
const cloudStoreSpy = vi.spyOn(cloudPlanStore, 'initialize');
const templatesTestSpy = vi.spyOn(settingsStore, 'testTemplatesEndpoint');
const sourceControlSpy = vi.spyOn(sourceControlStore, 'getPreferences');
const nodeTranslationSpy = vi.spyOn(nodeTypesStore, 'getNodeTranslationHeaders');
vi.mocked(useUsersStore).mockReturnValue({ currentUser: { id: '123' } } as ReturnType<
typeof useUsersStore
>);

await initializeAuthenticatedFeatures();

expect(cloudStoreSpy).toHaveBeenCalled();
expect(templatesTestSpy).toHaveBeenCalled();
expect(sourceControlSpy).toHaveBeenCalled();
expect(nodeTranslationSpy).toHaveBeenCalled();

await initializeAuthenticatedFeatures();

expect(cloudStoreSpy).toHaveBeenCalledTimes(1);
});
});
});
7 changes: 6 additions & 1 deletion packages/editor-ui/src/components/MainSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@
/></template>
<template #menuSuffix>
<div>
<div v-if="hasVersionUpdates" :class="$style.updates" @click="openUpdatesPanel">
<div
v-if="hasVersionUpdates"
data-test-id="version-updates-panel-button"
:class="$style.updates"
@click="openUpdatesPanel"
>
<div :class="$style.giftContainer">
<GiftNotificationIcon />
</div>
Expand Down
9 changes: 7 additions & 2 deletions packages/editor-ui/src/components/UpdatesPanel.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<template>
<ModalDrawer :name="VERSIONS_MODAL_KEY" direction="ltr" width="520px">
<ModalDrawer
:name="VERSIONS_MODAL_KEY"
direction="ltr"
width="520px"
data-test-id="version-updates-panel"
>
<template #header>
<span :class="$style.title">
{{ $locale.baseText('updatesPanel.weVeBeenBusy') }}
Expand Down Expand Up @@ -31,7 +36,7 @@
</p>

<n8n-link v-if="infoUrl" :to="infoUrl" :bold="true">
<font-awesome-icon icon="info-circle"></font-awesome-icon>
<font-awesome-icon icon="info-circle" class="mr-2xs" />
<span>
{{ $locale.baseText('updatesPanel.howToUpdateYourN8nVersion') }}
</span>
Expand Down
8 changes: 7 additions & 1 deletion packages/editor-ui/src/components/VersionCard.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<template>
<a v-if="version" :href="version.documentationUrl" target="_blank" :class="$style.card">
<a
v-if="version"
:href="version.documentationUrl"
target="_blank"
:class="$style.card"
data-test-id="version-card"
>
<div :class="$style.header">
<div>
<div :class="$style.name">
Expand Down
4 changes: 4 additions & 0 deletions packages/editor-ui/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useSettingsStore } from '@/stores/settings.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useUsersStore } from '@/stores/users.store';
import { initializeCloudHooks } from '@/hooks/register';
import { useVersionsStore } from '@/stores/versions.store';

let coreInitialized = false;
let authenticatedFeaturesInitialized = false;
Expand All @@ -20,10 +21,13 @@ export async function initializeCore() {

const settingsStore = useSettingsStore();
const usersStore = useUsersStore();
const versionsStore = useVersionsStore();

await settingsStore.initialize();
await usersStore.initialize();

void versionsStore.checkForNewVersions();

if (settingsStore.isCloudDeployment) {
try {
await initializeCloudHooks();
Expand Down
50 changes: 0 additions & 50 deletions packages/editor-ui/src/mixins/newVersions.ts

This file was deleted.

Loading

0 comments on commit fcff34c

Please sign in to comment.