mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
feat(editor): New users see whatsnew notification only if new (#17409)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
import { useVersionsStore } from './versions.store';
|
import { useVersionsStore } from './versions.store';
|
||||||
|
import { useUsersStore } from './users.store';
|
||||||
import * as versionsApi from '@n8n/rest-api-client/api/versions';
|
import * as versionsApi from '@n8n/rest-api-client/api/versions';
|
||||||
import type { IVersionNotificationSettings } from '@n8n/api-types';
|
import type { IVersionNotificationSettings } from '@n8n/api-types';
|
||||||
import type { Version, WhatsNewArticle, WhatsNewSection } from '@n8n/rest-api-client/api/versions';
|
import type { Version, WhatsNewArticle, WhatsNewSection } from '@n8n/rest-api-client/api/versions';
|
||||||
@@ -18,6 +19,8 @@ vi.mock('@/composables/useToast', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mock('./users.store');
|
||||||
|
|
||||||
const settings: IVersionNotificationSettings = {
|
const settings: IVersionNotificationSettings = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
endpoint: 'https://test.api.n8n.io/api/versions/',
|
endpoint: 'https://test.api.n8n.io/api/versions/',
|
||||||
@@ -505,3 +508,86 @@ describe('versions.store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('shouldShowWhatsNewCallout', () => {
|
||||||
|
let versionsStore: ReturnType<typeof useVersionsStore>;
|
||||||
|
|
||||||
|
const makeArticle = (overrides: Partial<WhatsNewArticle> = {}): WhatsNewArticle => ({
|
||||||
|
id: 1,
|
||||||
|
title: 'Test',
|
||||||
|
content: 'Content',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
publishedAt: new Date().toISOString(),
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if there are no articles', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
vi.mocked(useUsersStore).mockReturnValue({ currentUser: null } as any);
|
||||||
|
versionsStore = useVersionsStore();
|
||||||
|
Object.defineProperty(versionsStore, 'lastDismissedWhatsNewCallout', { get: () => [] });
|
||||||
|
versionsStore.whatsNew.items = [];
|
||||||
|
expect(versionsStore.shouldShowWhatsNewCallout()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user has no createdAt and not all articles are dismissed', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
vi.mocked(useUsersStore).mockReturnValue({ currentUser: null } as any);
|
||||||
|
versionsStore = useVersionsStore();
|
||||||
|
Object.defineProperty(versionsStore, 'lastDismissedWhatsNewCallout', { get: () => [] });
|
||||||
|
versionsStore.whatsNew.items = [makeArticle()];
|
||||||
|
expect(versionsStore.shouldShowWhatsNewCallout()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if all articles are dismissed', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
vi.mocked(useUsersStore).mockReturnValue({ currentUser: null } as any);
|
||||||
|
versionsStore = useVersionsStore();
|
||||||
|
versionsStore.whatsNew.items = [makeArticle()];
|
||||||
|
versionsStore.dismissWhatsNewCallout();
|
||||||
|
expect(versionsStore.shouldShowWhatsNewCallout()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user createdAt is before article updatedAt', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
vi.mocked(useUsersStore).mockReturnValue({
|
||||||
|
currentUser: { createdAt: new Date(now - 10000).toISOString() },
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} as any);
|
||||||
|
versionsStore = useVersionsStore();
|
||||||
|
Object.defineProperty(versionsStore, 'lastDismissedWhatsNewCallout', { get: () => [] });
|
||||||
|
versionsStore.whatsNew.items = [makeArticle({ updatedAt: new Date(now).toISOString() })];
|
||||||
|
expect(versionsStore.shouldShowWhatsNewCallout()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if user createdAt is after article updatedAt', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
vi.mocked(useUsersStore).mockReturnValue({
|
||||||
|
currentUser: { createdAt: new Date(now).toISOString() },
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} as any);
|
||||||
|
versionsStore = useVersionsStore();
|
||||||
|
Object.defineProperty(versionsStore, 'lastDismissedWhatsNewCallout', { get: () => [] });
|
||||||
|
versionsStore.whatsNew.items = [
|
||||||
|
makeArticle({ updatedAt: new Date(now - 10000).toISOString() }),
|
||||||
|
];
|
||||||
|
expect(versionsStore.shouldShowWhatsNewCallout()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles missing updatedAt on article', () => {
|
||||||
|
vi.mocked(useUsersStore).mockReturnValue({
|
||||||
|
currentUser: { createdAt: new Date().toISOString() },
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} as any);
|
||||||
|
versionsStore = useVersionsStore();
|
||||||
|
Object.defineProperty(versionsStore, 'lastDismissedWhatsNewCallout', { get: () => [] });
|
||||||
|
versionsStore.whatsNew.items = [makeArticle({ updatedAt: undefined })];
|
||||||
|
expect(versionsStore.shouldShowWhatsNewCallout()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { useToast } from '@/composables/useToast';
|
|||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useSettingsStore } from './settings.store';
|
import { useSettingsStore } from './settings.store';
|
||||||
|
import { useUsersStore } from './users.store';
|
||||||
import { useStorage } from '@/composables/useStorage';
|
import { useStorage } from '@/composables/useStorage';
|
||||||
import { jsonParse } from 'n8n-workflow';
|
import { jsonParse } from 'n8n-workflow';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
@@ -52,6 +53,7 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
|
|||||||
const { showToast, showMessage } = useToast();
|
const { showToast, showMessage } = useToast();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const usersStore = useUsersStore();
|
||||||
const readWhatsNewArticlesStorage = useStorage(LOCAL_STORAGE_READ_WHATS_NEW_ARTICLES);
|
const readWhatsNewArticlesStorage = useStorage(LOCAL_STORAGE_READ_WHATS_NEW_ARTICLES);
|
||||||
const lastDismissedWhatsNewCalloutStorage = useStorage(LOCAL_STORAGE_DISMISSED_WHATS_NEW_CALLOUT);
|
const lastDismissedWhatsNewCalloutStorage = useStorage(LOCAL_STORAGE_DISMISSED_WHATS_NEW_CALLOUT);
|
||||||
|
|
||||||
@@ -169,9 +171,22 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowWhatsNewCallout = (): boolean => {
|
const shouldShowWhatsNewCallout = (): boolean => {
|
||||||
return !whatsNewArticles.value.every((item) =>
|
const createdAt = usersStore.currentUser?.createdAt;
|
||||||
|
let hasNewArticle = false;
|
||||||
|
if (createdAt) {
|
||||||
|
const userCreatedAt = new Date(createdAt).getTime();
|
||||||
|
hasNewArticle = whatsNewArticles.value.some((item) => {
|
||||||
|
const updatedAt = item.updatedAt ? new Date(item.updatedAt).getTime() : 0;
|
||||||
|
return updatedAt > userCreatedAt;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
hasNewArticle = true;
|
||||||
|
}
|
||||||
|
const allArticlesDismissed = whatsNewArticles.value.every((item) =>
|
||||||
lastDismissedWhatsNewCallout.value.includes(item.id),
|
lastDismissedWhatsNewCallout.value.includes(item.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return hasNewArticle && !allArticlesDismissed;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchWhatsNew = async () => {
|
const fetchWhatsNew = async () => {
|
||||||
@@ -273,5 +288,7 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
|
|||||||
isWhatsNewArticleRead,
|
isWhatsNewArticleRead,
|
||||||
setWhatsNewArticleRead,
|
setWhatsNewArticleRead,
|
||||||
closeWhatsNewCallout,
|
closeWhatsNewCallout,
|
||||||
|
shouldShowWhatsNewCallout,
|
||||||
|
dismissWhatsNewCallout,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user