From 9f45c284db1c0a59d5aeaf6970fdadc72768c67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20G=C3=B3mez=20Morales?= Date: Tue, 22 Jul 2025 13:50:18 +0200 Subject: [PATCH] feat(editor): Workflows Diff UI (no-changelog) (#17452) --- package.json | 3 +- .../src/components/N8nIcon/icons.ts | 2 + .../N8nRadioButtons/RadioButtons.vue | 1 + .../frontend/@n8n/i18n/src/locales/en.json | 2 + packages/frontend/editor-ui/package.json | 1 + .../editor-ui/src/api/sourceControl.ts | 12 + .../editor-ui/src/components/Modal.vue | 2 +- .../editor-ui/src/components/Modals.vue | 118 +-- .../editor-ui/src/components/NodeIcon.vue | 10 +- .../components/SourceControlPullModal.ee.vue | 43 +- .../SourceControlPushModal.ee.test.ts | 12 +- .../components/SourceControlPushModal.ee.vue | 32 +- .../__snapshots__/VirtualSchema.test.ts.snap | 16 +- .../src/components/canvas/Canvas.vue | 34 +- .../canvas/elements/edges/CanvasEdge.test.ts | 14 +- .../canvas/elements/edges/CanvasEdge.vue | 9 +- .../CanvasNodeDefault.test.ts.snap | 10 +- packages/frontend/editor-ui/src/constants.ts | 1 + .../features/workflow-diff/DiffBadge.test.ts | 104 +++ .../src/features/workflow-diff/DiffBadge.vue | 55 ++ .../workflow-diff/HighlightedEdge.test.ts | 145 ++++ .../workflow-diff/HighlightedEdge.vue | 24 + .../features/workflow-diff/NodeDiff.test.ts | 202 +++++ .../src/features/workflow-diff/NodeDiff.vue | 34 + .../workflow-diff/SyncedWorkflowCanvas.vue | 89 ++ .../workflow-diff/WorkflowDIffModal.test.ts | 305 +++++++ .../workflow-diff/WorkflowDiffAside.vue | 71 ++ .../workflow-diff/WorkflowDiffModal.vue | 788 ++++++++++++++++++ .../workflow-diff/useViewportSync.test.ts | 233 ++++++ .../features/workflow-diff/useViewportSync.ts | 80 ++ .../workflow-diff/useWorkflowDiff.test.ts | 638 ++++++++++++++ .../features/workflow-diff/useWorkflowDiff.ts | 242 ++++++ .../src/stores/sourceControl.store.ts | 5 + .../frontend/editor-ui/src/stores/ui.store.ts | 2 + patches/v-code-diff.patch | 21 + pnpm-lock.yaml | 44 +- 36 files changed, 3285 insertions(+), 119 deletions(-) create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/DiffBadge.test.ts create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/DiffBadge.vue create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/HighlightedEdge.test.ts create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/HighlightedEdge.vue create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/NodeDiff.test.ts create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/NodeDiff.vue create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/SyncedWorkflowCanvas.vue create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/WorkflowDIffModal.test.ts create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/WorkflowDiffAside.vue create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/WorkflowDiffModal.vue create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/useViewportSync.test.ts create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/useViewportSync.ts create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/useWorkflowDiff.test.ts create mode 100644 packages/frontend/editor-ui/src/features/workflow-diff/useWorkflowDiff.ts create mode 100644 patches/v-code-diff.patch diff --git a/package.json b/package.json index d990b26e7d..a8056ef542 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,8 @@ "js-base64": "patches/js-base64.patch", "ics": "patches/ics.patch", "minifaker": "patches/minifaker.patch", - "z-vue-scan": "patches/z-vue-scan.patch" + "z-vue-scan": "patches/z-vue-scan.patch", + "v-code-diff": "patches/v-code-diff.patch" } } } diff --git a/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts b/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts index 674963f4a0..5b54360541 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts +++ b/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts @@ -78,6 +78,7 @@ import IconLucideEyeOff from '~icons/lucide/eye-off'; import IconLucideFile from '~icons/lucide/file'; import IconLucideFileArchive from '~icons/lucide/file-archive'; import IconLucideFileCode from '~icons/lucide/file-code'; +import IconLucideFileDiff from '~icons/lucide/file-diff'; import IconLucideFileDown from '~icons/lucide/file-down'; import IconLucideFileInput from '~icons/lucide/file-input'; import IconLucideFileOutput from '~icons/lucide/file-output'; @@ -474,6 +475,7 @@ export const updatedIconSet = { file: IconLucideFile, 'file-archive': IconLucideFileArchive, 'file-code': IconLucideFileCode, + 'file-diff': IconLucideFileDiff, 'file-down': IconLucideFileDown, 'file-input': IconLucideFileInput, 'file-output': IconLucideFileOutput, diff --git a/packages/frontend/@n8n/design-system/src/components/N8nRadioButtons/RadioButtons.vue b/packages/frontend/@n8n/design-system/src/components/N8nRadioButtons/RadioButtons.vue index 2f7ac72225..65cc09194f 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nRadioButtons/RadioButtons.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nRadioButtons/RadioButtons.vue @@ -5,6 +5,7 @@ interface RadioOption { label: string; value: Value; disabled?: boolean; + data?: Record; } interface RadioButtonsProps { diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index c1b8acec67..ff12b568ed 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -2560,6 +2560,8 @@ "workflowSettings.showMessage.saveSettings.title": "Workflow settings saved", "workflowSettings.timeoutAfter": "Timeout After", "workflowSettings.timeoutWorkflow": "Timeout Workflow", + "workflowSettings.executionTimeout": "Timeout Workflow", + "workflowSettings.tags": "Tags", "workflowSettings.timezone": "Timezone", "workflowSettings.timeSavedPerExecution": "Estimated time saved", "workflowSettings.timeSavedPerExecution.hint": "Minutes per production execution", diff --git a/packages/frontend/editor-ui/package.json b/packages/frontend/editor-ui/package.json index bb47a885b5..2c843a2f66 100644 --- a/packages/frontend/editor-ui/package.json +++ b/packages/frontend/editor-ui/package.json @@ -85,6 +85,7 @@ "timeago.js": "^4.0.2", "typescript": "catalog:", "uuid": "catalog:", + "v-code-diff": "^1.13.1", "v3-infinite-loading": "^1.2.2", "vue": "catalog:frontend", "vue-agile": "^2.0.0", diff --git a/packages/frontend/editor-ui/src/api/sourceControl.ts b/packages/frontend/editor-ui/src/api/sourceControl.ts index f372d766ac..eec1c7a94c 100644 --- a/packages/frontend/editor-ui/src/api/sourceControl.ts +++ b/packages/frontend/editor-ui/src/api/sourceControl.ts @@ -9,6 +9,7 @@ import type { SourceControlStatus, SshKeyTypes, } from '@/types/sourceControl.types'; +import type { IWorkflowDb } from '@/Interface'; import { makeRestApiRequest } from '@n8n/rest-api-client'; import type { TupleToUnion } from '@/utils/typeHelpers'; @@ -56,6 +57,17 @@ export const getStatus = async (context: IRestApiContext): Promise => { + return await makeRestApiRequest( + context, + 'GET', + `${sourceControlApiRoot}/remote-content/workflow/${workflowId}`, + ); +}; + export const getAggregatedStatus = async ( context: IRestApiContext, options: { diff --git a/packages/frontend/editor-ui/src/components/Modal.vue b/packages/frontend/editor-ui/src/components/Modal.vue index 7ca6d6dce9..08b6a91371 100644 --- a/packages/frontend/editor-ui/src/components/Modal.vue +++ b/packages/frontend/editor-ui/src/components/Modal.vue @@ -160,7 +160,7 @@ function getCustomClass() { @opened="onOpened" >