From 1434d8336c64f88d049b09db0e225c39de5bfb09 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Tue, 5 May 2026 17:26:35 +0200 Subject: [PATCH] Allow publishing generated documents as minor versions Generated documents (asset list, risk register, SoA, ...) previously only ever produced a new major version. Every regeneration of an auto-built register consumed a major number, even when the change was trivial. They now accept a minor flag and publish as currentMajor.currentMinor+1 when set, bypassing the approval flow. To carry the flag through cleanly, the document publish API was refactored. The three split mutations (publishMajor, publishMinor, requestDocumentVersionApproval) and the two bulk variants collapse into a single publishDocument / bulkPublishDocuments, both taking the new minor: Boolean! and a now-required changelog: String!. The same shape flows through the CLI ("prb document publish --minor"), the MCP tool, the n8n operations, and the Relay dialogs, where each generated-doc dialog gains a "Publish as minor" button. Publishing minor without an existing major is rejected with ErrCannotPublishMinorWithoutMajor. This is a deliberate breaking change for callers of the prior mutations. Signed-off-by: Sacha Al Himdani --- .../assets/dialogs/PublishAssetListDialog.tsx | 17 +- .../data/dialogs/PublishDataListDialog.tsx | 17 +- .../documents/_components/PublishDialog.tsx | 186 +++--------- .../_components/PublishDocumentsDialog.tsx | 116 +++----- .../dialogs/PublishFindingListDialog.tsx | 17 +- .../dialogs/PublishObligationListDialog.tsx | 17 +- ...taProtectionImpactAssessmentListDialog.tsx | 17 +- .../PublishProcessingActivityListDialog.tsx | 17 +- ...lishTransferImpactAssessmentListDialog.tsx | 17 +- .../risks/dialogs/PublishRiskListDialog.tsx | 17 +- .../PublishStatementOfApplicabilityDialog.tsx | 17 +- .../dialogs/PublishVendorListDialog.tsx | 17 +- e2e/console/asset_publish_test.go | 5 + e2e/console/datum_publish_test.go | 5 + e2e/console/document_version_test.go | 88 +++--- e2e/console/dpia_publish_test.go | 4 + e2e/console/employee_document_test.go | 25 +- e2e/console/finding_publish_test.go | 5 + e2e/console/obligation_publish_test.go | 5 + .../processing_activity_publish_test.go | 5 + e2e/console/risk_publish_test.go | 5 + .../statement_of_applicability_test.go | 6 + e2e/console/tia_publish_test.go | 4 + e2e/console/vendor_publish_test.go | 5 + .../Probo/actions/asset/publish.operation.ts | 16 +- .../Probo/actions/datum/publish.operation.ts | 16 +- .../nodes/Probo/actions/document/index.ts | 32 +-- ...sion.operation.ts => publish.operation.ts} | 56 +++- .../document/publishMinorVersion.operation.ts | 97 ------- .../document/requestApproval.operation.ts | 97 ------- .../Probo/actions/dpia/publish.operation.ts | 16 +- .../actions/finding/publish.operation.ts | 16 +- .../actions/obligation/publish.operation.ts | 16 +- .../processingActivity/publish.operation.ts | 16 +- .../Probo/actions/risk/publish.operation.ts | 16 +- .../publish.operation.ts | 16 +- .../Probo/actions/tia/publish.operation.ts | 16 +- .../Probo/actions/vendor/publish.operation.ts | 16 +- pkg/cmd/asset/publish/publish.go | 5 +- pkg/cmd/datum/publish/publish.go | 5 +- pkg/cmd/document/document.go | 6 +- .../document/publish-major/publish_major.go | 115 -------- .../publish_minor.go => publish/publish.go} | 73 +++-- pkg/cmd/dpia/publish/publish.go | 5 +- pkg/cmd/finding/publish/publish.go | 5 +- pkg/cmd/obligation/publish/publish.go | 5 +- .../processing-activity/publish/publish.go | 5 +- pkg/cmd/risk/publish/publish.go | 5 +- pkg/cmd/soa/publish/publish.go | 9 +- pkg/cmd/tia/publish/publish.go | 5 +- pkg/cmd/vendormgmt/publish/publish.go | 5 +- pkg/probo/document_approval_service.go | 140 +++------ pkg/probo/document_service.go | 203 +++++++------ pkg/probo/generated_document_service.go | 240 ++++------------ pkg/server/api/console/v1/asset_resolvers.go | 10 +- pkg/server/api/console/v1/audit_resolvers.go | 5 +- .../api/console/v1/control_resolvers.go | 5 +- ..._protection_impact_assessment_resolvers.go | 10 +- .../api/console/v1/document_resolvers.go | 271 ++++++------------ .../api/console/v1/graphql/asset.graphql | 2 + .../api/console/v1/graphql/audit.graphql | 1 + .../api/console/v1/graphql/control.graphql | 1 + .../data_protection_impact_assessment.graphql | 2 + .../api/console/v1/graphql/document.graphql | 52 ++-- .../api/console/v1/graphql/obligation.graphql | 1 + .../v1/graphql/processing_activity.graphql | 1 + .../api/console/v1/graphql/risk.graphql | 1 + .../api/console/v1/graphql/vendor.graphql | 1 + .../api/console/v1/obligation_resolvers.go | 5 +- .../v1/processing_activity_resolvers.go | 5 +- pkg/server/api/console/v1/risk_resolvers.go | 5 +- pkg/server/api/console/v1/vendor_resolvers.go | 5 +- pkg/server/api/mcp/v1/schema.resolvers.go | 117 +++----- pkg/server/api/mcp/v1/specification.yaml | 140 +++++---- 74 files changed, 1121 insertions(+), 1426 deletions(-) rename packages/n8n-node/nodes/Probo/actions/document/{publishMajorVersion.operation.ts => publish.operation.ts} (60%) delete mode 100644 packages/n8n-node/nodes/Probo/actions/document/publishMinorVersion.operation.ts delete mode 100644 packages/n8n-node/nodes/Probo/actions/document/requestApproval.operation.ts delete mode 100644 pkg/cmd/document/publish-major/publish_major.go rename pkg/cmd/document/{publish-minor/publish_minor.go => publish/publish.go} (55%) diff --git a/apps/console/src/pages/organizations/assets/dialogs/PublishAssetListDialog.tsx b/apps/console/src/pages/organizations/assets/dialogs/PublishAssetListDialog.tsx index 84d06531d..f9357a979 100644 --- a/apps/console/src/pages/organizations/assets/dialogs/PublishAssetListDialog.tsx +++ b/apps/console/src/pages/organizations/assets/dialogs/PublishAssetListDialog.tsx @@ -25,7 +25,7 @@ import { useToast, } from "@probo/ui"; import type { ReactNode } from "react"; -import { useMemo } from "react"; +import { useMemo, useRef } from "react"; import { useMutation } from "react-relay"; import { graphql } from "relay-runtime"; import { z } from "zod"; @@ -83,6 +83,8 @@ export function PublishAssetListDialog({ const [publish, isPublishing] = useMutation(publishMutation); + const minorRef = useRef(false); + const approverIds = watch("approverIds"); const hasApprovers = approverIds.length > 0; @@ -90,8 +92,9 @@ export function PublishAssetListDialog({ publish({ variables: { input: { + minor: minorRef.current, organizationId, - approverIds: data.approverIds.length > 0 ? data.approverIds : undefined, + approverIds: !minorRef.current && data.approverIds.length > 0 ? data.approverIds : undefined, }, }, onCompleted(response) { @@ -145,9 +148,19 @@ export function PublishAssetListDialog({ + - - - ) - : ( - <> - - - - )} + + diff --git a/apps/console/src/pages/organizations/documents/_components/PublishDocumentsDialog.tsx b/apps/console/src/pages/organizations/documents/_components/PublishDocumentsDialog.tsx index 9809649ae..2824a7413 100644 --- a/apps/console/src/pages/organizations/documents/_components/PublishDocumentsDialog.tsx +++ b/apps/console/src/pages/organizations/documents/_components/PublishDocumentsDialog.tsx @@ -30,8 +30,7 @@ import { useMutation } from "react-relay"; import { graphql } from "relay-runtime"; import { z } from "zod"; -import type { PublishDocumentsDialog_majorMutation } from "#/__generated__/core/PublishDocumentsDialog_majorMutation.graphql"; -import type { PublishDocumentsDialog_minorMutation } from "#/__generated__/core/PublishDocumentsDialog_minorMutation.graphql"; +import type { PublishDocumentsDialog_bulkPublishMutation } from "#/__generated__/core/PublishDocumentsDialog_bulkPublishMutation.graphql"; import { useFormWithSchema } from "#/hooks/useFormWithSchema"; type Props = { @@ -40,27 +39,11 @@ type Props = { onSave: () => void; }; -const publishMajorMutation = graphql` - mutation PublishDocumentsDialog_majorMutation( - $input: BulkPublishDocumentVersionsInput! +const bulkPublishMutation = graphql` + mutation PublishDocumentsDialog_bulkPublishMutation( + $input: BulkPublishDocumentsInput! ) { - bulkPublishMajorDocumentVersions(input: $input) { - documentVersions { - id - } - documents { - id - ...DocumentListItemFragment - } - } - } -`; - -const publishMinorMutation = graphql` - mutation PublishDocumentsDialog_minorMutation( - $input: BulkPublishDocumentVersionsInput! - ) { - bulkPublishMinorDocumentVersions(input: $input) { + bulkPublishDocuments(input: $input) { documentVersions { id } @@ -80,18 +63,14 @@ export function PublishDocumentsDialog({ const { __ } = useTranslate(); const { toast } = useToast(); const dialogRef = useDialogRef(); - const actionRef = useRef<"major" | "minor">("major"); + const minorRef = useRef(false); const schema = z.object({ changelog: z.string().min(1, __("Changelog is required")), }); - const [publishMajor, isPublishingMajor] - = useMutation(publishMajorMutation); - const [publishMinor, isPublishingMinor] - = useMutation(publishMinorMutation); - - const isBusy = isPublishingMajor || isPublishingMinor; + const [publish, isPublishing] + = useMutation(bulkPublishMutation); const { handleSubmit, @@ -103,45 +82,42 @@ export function PublishDocumentsDialog({ }, }); - const onCompleted = (_: unknown, errors: ReadonlyArray<{ message: string }> | null) => { - if (errors?.length) { - toast({ - title: __("Error"), - description: formatError(__("Failed to publish documents"), [...errors]), - variant: "error", - }); - } else { - toast({ - title: __("Success"), - description: sprintf(__("%s documents published"), documentIds.length), - variant: "success", - }); - dialogRef.current?.close(); - onSave(); - } - }; - - const onError = (error: Error) => { - toast({ - title: __("Error"), - description: error.message, - variant: "error", - }); - }; - const onSubmit = (data: z.infer) => { - const variables = { - input: { - documentIds, - changelog: data.changelog, + const minor = minorRef.current; + minorRef.current = false; + publish({ + variables: { + input: { + documentIds, + minor, + changelog: data.changelog, + }, }, - }; - - if (actionRef.current === "minor") { - publishMinor({ variables, onCompleted, onError }); - } else { - publishMajor({ variables, onCompleted, onError }); - } + onCompleted(_, errors) { + if (errors?.length) { + toast({ + title: __("Error"), + description: formatError(__("Failed to publish documents"), [...errors]), + variant: "error", + }); + return; + } + toast({ + title: __("Success"), + description: sprintf(__("%s documents published"), documentIds.length), + variant: "success", + }); + dialogRef.current?.close(); + onSave(); + }, + onError(error) { + toast({ + title: __("Error"), + description: error.message, + variant: "error", + }); + }, + }); }; return ( @@ -184,15 +160,15 @@ export function PublishDocumentsDialog({ type="submit" variant="secondary" icon={IconUpload} - disabled={isBusy} - onClick={() => { actionRef.current = "minor"; }} + disabled={isPublishing} + onClick={() => { minorRef.current = true; }} > {__("Publish as minor")} diff --git a/apps/console/src/pages/organizations/findings/dialogs/PublishFindingListDialog.tsx b/apps/console/src/pages/organizations/findings/dialogs/PublishFindingListDialog.tsx index c921750ba..cb92dd66e 100644 --- a/apps/console/src/pages/organizations/findings/dialogs/PublishFindingListDialog.tsx +++ b/apps/console/src/pages/organizations/findings/dialogs/PublishFindingListDialog.tsx @@ -25,7 +25,7 @@ import { useToast, } from "@probo/ui"; import type { ReactNode } from "react"; -import { useMemo } from "react"; +import { useMemo, useRef } from "react"; import { useMutation } from "react-relay"; import { graphql } from "relay-runtime"; import { z } from "zod"; @@ -83,6 +83,8 @@ export function PublishFindingListDialog({ const [publish, isPublishing] = useMutation(publishMutation); + const minorRef = useRef(false); + const approverIds = watch("approverIds"); const hasApprovers = approverIds.length > 0; @@ -90,8 +92,9 @@ export function PublishFindingListDialog({ publish({ variables: { input: { + minor: minorRef.current, organizationId, - approverIds: data.approverIds.length > 0 ? data.approverIds : undefined, + approverIds: !minorRef.current && data.approverIds.length > 0 ? data.approverIds : undefined, }, }, onCompleted(response) { @@ -145,9 +148,19 @@ export function PublishFindingListDialog({ +