From 2866518112df0a7f146d37d630d2e45cd7e2f5f7 Mon Sep 17 00:00:00 2001 From: kgrhartlage Date: Thu, 8 Oct 2026 17:00:57 +0200 Subject: [PATCH 1/3] feat(editor): Show multiple workflows in the review detail view Show each workflow of a review as a collapsible item. The item header shows the workflow name and version. The first workflow opens by default. Only the open item mounts its diff canvases. Add a canvas id prefix to the workflow diff, so that two diffs do not share Vue Flow stores. Add an option to hide the diff title. --- .../src/types/component.ts | 2 + .../frontend/@n8n/i18n/src/locales/en.json | 3 +- .../workflowDiff/WorkflowDiffContent.test.ts | 9 ++ .../workflowDiff/WorkflowDiffContent.vue | 16 +- .../workflowDiff/WorkflowDiffSlot.test.ts | 8 + .../workflowDiff/WorkflowDiffSlot.vue | 2 + .../workflowDiff/WorkflowDiffView.test.ts | 12 ++ .../workflowDiff/WorkflowDiffView.vue | 10 +- packages/modules/inbox/frontend/package.json | 1 + .../WorkflowReviewChangesList.test.ts | 97 ++++++++++++ .../components/WorkflowReviewChangesList.vue | 143 ++++++++++++++++++ .../WorkflowReviewChangesSection.test.ts | 2 + .../WorkflowReviewChangesSection.vue | 12 +- .../WorkflowReviewDetailMetadata.test.ts | 15 ++ .../WorkflowReviewDetailMetadata.vue | 6 +- .../WorkflowReviewDetailTabs.test.ts | 45 +++++- .../components/WorkflowReviewDetailTabs.vue | 33 ++-- 17 files changed, 388 insertions(+), 28 deletions(-) create mode 100644 packages/modules/inbox/frontend/src/reviews/components/WorkflowReviewChangesList.test.ts create mode 100644 packages/modules/inbox/frontend/src/reviews/components/WorkflowReviewChangesList.vue diff --git a/packages/frontend/@n8n/frontend-module-sdk/src/types/component.ts b/packages/frontend/@n8n/frontend-module-sdk/src/types/component.ts index e8e42b07768..3c353ad7061 100644 --- a/packages/frontend/@n8n/frontend-module-sdk/src/types/component.ts +++ b/packages/frontend/@n8n/frontend-module-sdk/src/types/component.ts @@ -33,4 +33,6 @@ export type SlotWorkflowDiffProps = { sourceLabel?: string; targetLabel?: string; showFullscreenButton?: boolean; + canvasIdPrefix?: string; + hideTitle?: boolean; }; diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index aa8e9ba4591..1386b15764c 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -6183,7 +6183,7 @@ "workflowReviews.detail.metadata.otherAuthors": "Other authors", "workflowReviews.detail.metadata.reviewers": "Reviewers", "workflowReviews.detail.metadata.noReviewers": "No reviewers assigned.", - "workflowReviews.detail.metadata.workflow": "Workflow", + "workflowReviews.detail.metadata.workflow": "Workflow | Workflows", "workflowReviews.changes.workflowUnavailable": "The workflow in this review is no longer available.", "workflowReviews.changes.unavailable": "Couldn't load the changes for this review. Refresh to try again.", "workflowReviews.changes.versionUnavailable.body": "The submitted workflow version is no longer available.", @@ -7276,6 +7276,7 @@ "whatsNew.updateAvailable": "You're currently on version {currentVersion}. Update to {latestVersion} to get {count} versions worth of new features, improvements, and fixes. See what changed", "whatsNew.updateAvailable.changelogLink": "in the full changelog", "workflowDiff.changes": "Changes", + "workflowDiff.syncViews": "Sync views", "workflowDiff.nodes": "Nodes", "workflowDiff.connectors": "Connectors", "workflowDiff.compare": "Compare versions", diff --git a/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.test.ts b/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.test.ts index 075dad31d1f..e636e6f4331 100644 --- a/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.test.ts +++ b/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.test.ts @@ -115,6 +115,15 @@ describe('WorkflowDiffContent', () => { expect(screen.getByTestId('synced-canvas-bottom')).toBeInTheDocument(); }); + it('adds the prefix to the canvas ids', () => { + render(WorkflowDiffContent, { + props: { ...defaultProps, canvasIdPrefix: 'wf-1' }, + }); + + expect(screen.getByTestId('synced-canvas-wf-1-top')).toBeInTheDocument(); + expect(screen.getByTestId('synced-canvas-wf-1-bottom')).toBeInTheDocument(); + }); + it('should display source label', () => { render(WorkflowDiffContent, { props: { ...defaultProps, sourceLabel: 'Remote' }, diff --git a/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.vue b/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.vue index b8fdfb4ba42..c166b11ab50 100644 --- a/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.vue +++ b/packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.vue @@ -8,7 +8,7 @@ import NodeDiff from './NodeDiff.vue'; import HighlightedEdge from './HighlightedEdge.vue'; import Node from '@/features/workflows/canvas/components/elements/nodes/CanvasNode.vue'; import { useI18n } from '@n8n/i18n'; -import { useCssModule } from 'vue'; +import { computed, useCssModule } from 'vue'; import { N8nHeading, N8nText } from '@n8n/design-system'; import { NodeDiffStatus } from 'n8n-workflow'; @@ -29,6 +29,8 @@ const props = defineProps<{ applyLayout?: boolean; nodesDiff: Map; connectionsDiff: Map; + /** Prefix for the Vue Flow store ids. Set it when several diffs can be mounted at once. */ + canvasIdPrefix?: string; }>(); const emit = defineEmits<{ @@ -38,6 +40,14 @@ const emit = defineEmits<{ const i18n = useI18n(); const $style = useCssModule(); +// Vue Flow shares one store for each id across the page. +const sourceCanvasId = computed(() => + props.canvasIdPrefix ? `${props.canvasIdPrefix}-top` : 'top', +); +const targetCanvasId = computed(() => + props.canvasIdPrefix ? `${props.canvasIdPrefix}-bottom` : 'bottom', +); + function getNodeStatusClass(id: string) { const status = props.nodesDiff?.get(id)?.status ?? 'equal'; return $style[status]; @@ -61,7 +71,7 @@ function getEdgeStatusClass(id: string) {