From fbb35d432c0c30f9caea8502099dd32fd265bf6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ir=C3=A9n=C3=A9e?= Date: Thu, 16 Oct 2025 08:47:16 +0100 Subject: [PATCH] fix: Fix source control pull information toast (#20787) --- .../components/SourceControlPullModal.vue | 4 +- .../sourceControl.utils.test.ts | 12 ++- .../sourceControl.ee/sourceControl.utils.ts | 74 +++++++++++-------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/packages/frontend/editor-ui/src/features/sourceControl.ee/components/SourceControlPullModal.vue b/packages/frontend/editor-ui/src/features/sourceControl.ee/components/SourceControlPullModal.vue index 3bdc048b2dc..df677e69d42 100644 --- a/packages/frontend/editor-ui/src/features/sourceControl.ee/components/SourceControlPullModal.vue +++ b/packages/frontend/editor-ui/src/features/sourceControl.ee/components/SourceControlPullModal.vue @@ -73,7 +73,7 @@ async function loadSourceControlStatus() { try { const freshStatus = await sourceControlStore.pullWorkfolder(false); - await notifyUserAboutPullWorkFolderOutcome(freshStatus, toast); + await notifyUserAboutPullWorkFolderOutcome(freshStatus, toast, router); sourceControlEventBus.emit('pull'); close(); } catch (error) { @@ -209,7 +209,7 @@ async function pullWorkfolder() { try { const pullStatus = await sourceControlStore.pullWorkfolder(true); - await notifyUserAboutPullWorkFolderOutcome(pullStatus, toast); + await notifyUserAboutPullWorkFolderOutcome(pullStatus, toast, router); sourceControlEventBus.emit('pull'); } catch (error) { diff --git a/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.test.ts b/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.test.ts index 08f0925aea0..c7bfe7fbab1 100644 --- a/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.test.ts +++ b/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.test.ts @@ -6,6 +6,7 @@ import { notifyUserAboutPullWorkFolderOutcome, } from './sourceControl.utils'; import type { useToast } from '@/composables/useToast'; +import type { Router } from 'vue-router'; import { SOURCE_CONTROL_FILE_STATUS } from '@n8n/api-types'; @@ -39,7 +40,11 @@ describe('source control utils', () => { describe('notifyUserAboutPullWorkFolderOutcome()', () => { it('should show up to date notification when there are no changes', async () => { const toast = { showMessage: vi.fn() } as unknown as ReturnType; - await notifyUserAboutPullWorkFolderOutcome([], toast); + const router = { + push: vi.fn(), + resolve: vi.fn().mockReturnValue({ href: '/test' }), + } as unknown as Router; + await notifyUserAboutPullWorkFolderOutcome([], toast, router); expect(toast.showMessage).toHaveBeenCalledWith( expect.objectContaining({ @@ -50,6 +55,10 @@ describe('source control utils', () => { it('should show granular feedback', async () => { const toast = { showToast: vi.fn() } as unknown as ReturnType; + const router = { + push: vi.fn(), + resolve: vi.fn().mockReturnValue({ href: '/test' }), + } as unknown as Router; await notifyUserAboutPullWorkFolderOutcome( [ { @@ -94,6 +103,7 @@ describe('source control utils', () => { }, ], toast, + router, ); expect(toast.showToast).toHaveBeenNthCalledWith( diff --git a/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.ts b/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.ts index 040cd41beec..8be92d5454b 100644 --- a/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.ts +++ b/packages/frontend/editor-ui/src/features/sourceControl.ee/sourceControl.utils.ts @@ -1,5 +1,5 @@ import { h, nextTick } from 'vue'; -import { RouterLink } from 'vue-router'; +import type { Router } from 'vue-router'; import { useI18n } from '@n8n/i18n'; import { type SourceControlledFile, SOURCE_CONTROL_FILE_STATUS } from '@n8n/api-types'; import type { BaseTextKey } from '@n8n/i18n'; @@ -46,36 +46,50 @@ const pushStatusPriority: StatusPriority = { export const getPushPriorityByStatus = (status: SourceControlledFileStatus) => pushStatusPriority[status] ?? 0; -const variablesToast = { - title: i18n.baseText('settings.sourceControl.pull.upToDate.variables.title'), - message: h( - RouterLink, - { - to: { name: VIEWS.VARIABLES, query: { incomplete: 'true' } }, - onClick: () => { - telemetry.track('User clicked review variables'); +const createVariablesToast = (router: Router) => { + const route = { name: VIEWS.VARIABLES, query: { incomplete: 'true' } }; + const { href } = router.resolve(route); + + return { + title: i18n.baseText('settings.sourceControl.pull.upToDate.variables.title'), + message: h( + 'a', + { + href, + onClick: (e: MouseEvent) => { + e.preventDefault(); + telemetry.track('User clicked review variables'); + void router.push(route); + }, }, - }, - () => i18n.baseText('settings.sourceControl.pull.upToDate.variables.description'), - ), - type: 'info' as const, - duration: 0, + i18n.baseText('settings.sourceControl.pull.upToDate.variables.description'), + ), + type: 'info' as const, + duration: 0, + }; }; -const credentialsToast = { - title: i18n.baseText('settings.sourceControl.pull.upToDate.credentials.title'), - message: h( - RouterLink, - { - to: { name: VIEWS.CREDENTIALS, query: { setupNeeded: 'true' } }, - onClick: () => { - telemetry.track('User clicked review credentials'); +const createCredentialsToast = (router: Router) => { + const route = { name: VIEWS.CREDENTIALS, query: { setupNeeded: 'true' } }; + const { href } = router.resolve(route); + + return { + title: i18n.baseText('settings.sourceControl.pull.upToDate.credentials.title'), + message: h( + 'a', + { + href, + onClick: (e: MouseEvent) => { + e.preventDefault(); + telemetry.track('User clicked review credentials'); + void router.push(route); + }, }, - }, - () => i18n.baseText('settings.sourceControl.pull.upToDate.credentials.description'), - ), - type: 'info' as const, - duration: 0, + i18n.baseText('settings.sourceControl.pull.upToDate.credentials.description'), + ), + type: 'info' as const, + duration: 0, + }; }; const pullMessage = ({ @@ -126,6 +140,7 @@ const pullMessage = ({ export const notifyUserAboutPullWorkFolderOutcome = async ( files: SourceControlledFile[], toast: ReturnType, + router: Router, ) => { if (!files?.length) { toast.showMessage({ @@ -139,8 +154,8 @@ export const notifyUserAboutPullWorkFolderOutcome = async ( const { credential, tags, variables, workflow, folders } = groupBy(files, 'type'); const toastMessages = [ - ...(variables?.length ? [variablesToast] : []), - ...(credential?.length ? [credentialsToast] : []), + ...(variables?.length ? [createVariablesToast(router)] : []), + ...(credential?.length ? [createCredentialsToast(router)] : []), { title: i18n.baseText('settings.sourceControl.pull.success.title'), message: pullMessage({ credential, tags, variables, workflow, folders }), @@ -155,7 +170,6 @@ export const notifyUserAboutPullWorkFolderOutcome = async ( * Credentials * Variables */ - // toast.showToast(message); await nextTick(); }