mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +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 { useVersionsStore } from './versions.store';
|
||||
import { useUsersStore } from './users.store';
|
||||
import * as versionsApi from '@n8n/rest-api-client/api/versions';
|
||||
import type { IVersionNotificationSettings } from '@n8n/api-types';
|
||||
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 = {
|
||||
enabled: true,
|
||||
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 { computed, ref } from 'vue';
|
||||
import { useSettingsStore } from './settings.store';
|
||||
import { useUsersStore } from './users.store';
|
||||
import { useStorage } from '@/composables/useStorage';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
@@ -52,6 +53,7 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
|
||||
const { showToast, showMessage } = useToast();
|
||||
const uiStore = useUIStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const usersStore = useUsersStore();
|
||||
const readWhatsNewArticlesStorage = useStorage(LOCAL_STORAGE_READ_WHATS_NEW_ARTICLES);
|
||||
const lastDismissedWhatsNewCalloutStorage = useStorage(LOCAL_STORAGE_DISMISSED_WHATS_NEW_CALLOUT);
|
||||
|
||||
@@ -169,9 +171,22 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
|
||||
};
|
||||
|
||||
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),
|
||||
);
|
||||
|
||||
return hasNewArticle && !allArticlesDismissed;
|
||||
};
|
||||
|
||||
const fetchWhatsNew = async () => {
|
||||
@@ -273,5 +288,7 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, () => {
|
||||
isWhatsNewArticleRead,
|
||||
setWhatsNewArticleRead,
|
||||
closeWhatsNewCallout,
|
||||
shouldShowWhatsNewCallout,
|
||||
dismissWhatsNewCallout,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user