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 <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-05-05 17:26:35 +02:00
parent 81e52be58b
commit 1434d8336c
74 changed files with 1121 additions and 1426 deletions

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -108,6 +109,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -141,7 +143,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -108,6 +109,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -141,7 +143,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -25,8 +25,7 @@ import (
listapprovaldecisions "go.probo.inc/probo/pkg/cmd/document/list-approval-decisions"
listapprovalquorums "go.probo.inc/probo/pkg/cmd/document/list-approval-quorums"
listversions "go.probo.inc/probo/pkg/cmd/document/list-versions"
publishmajor "go.probo.inc/probo/pkg/cmd/document/publish-major"
publishminor "go.probo.inc/probo/pkg/cmd/document/publish-minor"
"go.probo.inc/probo/pkg/cmd/document/publish"
"go.probo.inc/probo/pkg/cmd/document/unarchive"
"go.probo.inc/probo/pkg/cmd/document/update"
"go.probo.inc/probo/pkg/cmd/document/view"
@@ -51,8 +50,7 @@ func NewCmdDocument(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(listversions.NewCmdListVersions(f))
cmd.AddCommand(viewversion.NewCmdViewVersion(f))
cmd.AddCommand(deletedraft.NewCmdDeleteDraft(f))
cmd.AddCommand(publishmajor.NewCmdPublishMajor(f))
cmd.AddCommand(publishminor.NewCmdPublishMinor(f))
cmd.AddCommand(publish.NewCmdPublish(f))
cmd.AddCommand(listapprovalquorums.NewCmdListApprovalQuorums(f))
cmd.AddCommand(viewapprovalquorum.NewCmdViewApprovalQuorum(f))
cmd.AddCommand(listapprovaldecisions.NewCmdListApprovalDecisions(f))

View File

@@ -1,115 +0,0 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package publishminor
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const publishMinorMutation = `
mutation($input: PublishMinorDocumentVersionInput!) {
publishMinorDocumentVersion(input: $input) {
documentVersion {
id
title
major
minor
status
}
}
}
`
type publishResponse struct {
PublishMinorDocumentVersion struct {
DocumentVersion struct {
ID string `json:"id"`
Title string `json:"title"`
Major int `json:"major"`
Minor int `json:"minor"`
Status string `json:"status"`
} `json:"documentVersion"`
} `json:"publishMinorDocumentVersion"`
}
func NewCmdPublishMinor(f *cmdutil.Factory) *cobra.Command {
var flagChangelog string
cmd := &cobra.Command{
Use: "publish-minor <document-id>",
Short: "Publish a minor version of a document",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := f.Config()
if err != nil {
return err
}
host, hc, err := cfg.DefaultHost()
if err != nil {
return err
}
client := api.NewClient(
host,
hc.Token,
"/api/console/v1/graphql",
cfg.HTTPTimeoutDuration(),
)
input := map[string]any{
"documentId": args[0],
}
if flagChangelog != "" {
input["changelog"] = flagChangelog
}
data, err := client.Do(
publishMinorMutation,
map[string]any{"input": input},
)
if err != nil {
return err
}
var resp publishResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
v := resp.PublishMinorDocumentVersion.DocumentVersion
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Published minor version %s (%s v%d.%d)\n",
v.ID,
v.Title,
v.Major,
v.Minor,
)
return nil
},
}
cmd.Flags().StringVar(&flagChangelog, "changelog", "", "Changelog for this version")
return cmd
}

View File

@@ -12,7 +12,7 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package publishmajor
package publish
import (
"encoding/json"
@@ -23,9 +23,9 @@ import (
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const publishMajorMutation = `
mutation($input: PublishMajorDocumentVersionInput!) {
publishMajorDocumentVersion(input: $input) {
const publishMutation = `
mutation($input: PublishDocumentInput!) {
publishDocument(input: $input) {
documentVersion {
id
title
@@ -33,12 +33,16 @@ mutation($input: PublishMajorDocumentVersionInput!) {
minor
status
}
approvalQuorum {
id
status
}
}
}
`
type publishResponse struct {
PublishMajorDocumentVersion struct {
PublishDocument struct {
DocumentVersion struct {
ID string `json:"id"`
Title string `json:"title"`
@@ -46,17 +50,35 @@ type publishResponse struct {
Minor int `json:"minor"`
Status string `json:"status"`
} `json:"documentVersion"`
} `json:"publishMajorDocumentVersion"`
ApprovalQuorum *struct {
ID string `json:"id"`
Status string `json:"status"`
} `json:"approvalQuorum"`
} `json:"publishDocument"`
}
func NewCmdPublishMajor(f *cmdutil.Factory) *cobra.Command {
var flagChangelog string
func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagMinor bool
flagApprover []string
flagChangelog string
)
cmd := &cobra.Command{
Use: "publish-major <document-id>",
Short: "Publish a major version of a document",
Args: cobra.ExactArgs(1),
Use: "publish <document-id>",
Short: "Publish a document",
Long: `Publish the latest draft of a document.
By default, the draft is published as a new major version. Pass --minor to
publish as a minor version (the document must already have a major version).
When --approver is set (one or more profile IDs), an approval is requested
instead of publishing immediately. Approvers are ignored with --minor.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if flagChangelog == "" {
return fmt.Errorf("--changelog is required")
}
cfg, err := f.Config()
if err != nil {
return err
@@ -76,14 +98,16 @@ func NewCmdPublishMajor(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"documentId": args[0],
"minor": flagMinor,
"changelog": flagChangelog,
}
if flagChangelog != "" {
input["changelog"] = flagChangelog
if len(flagApprover) > 0 {
input["approverIds"] = flagApprover
}
data, err := client.Do(
publishMajorMutation,
publishMutation,
map[string]any{"input": input},
)
if err != nil {
@@ -95,10 +119,23 @@ func NewCmdPublishMajor(f *cmdutil.Factory) *cobra.Command {
return fmt.Errorf("cannot parse response: %w", err)
}
v := resp.PublishMajorDocumentVersion.DocumentVersion
v := resp.PublishDocument.DocumentVersion
if resp.PublishDocument.ApprovalQuorum != nil {
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Requested approval for %s (%s v%d.%d, status %s)\n",
v.ID,
v.Title,
v.Major,
v.Minor,
v.Status,
)
return nil
}
_, _ = fmt.Fprintf(
f.IOStreams.Out,
"Published major version %s (%s v%d.%d)\n",
"Published %s (%s v%d.%d)\n",
v.ID,
v.Title,
v.Major,
@@ -109,7 +146,9 @@ func NewCmdPublishMajor(f *cmdutil.Factory) *cobra.Command {
},
}
cmd.Flags().StringVar(&flagChangelog, "changelog", "", "Changelog for this version")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().StringVar(&flagChangelog, "changelog", "", "Changelog for this version (required)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -108,6 +109,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -142,7 +144,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -108,6 +109,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -141,7 +143,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -108,6 +109,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -141,7 +143,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -108,6 +109,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -142,7 +144,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -109,6 +110,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -142,7 +144,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -68,7 +68,10 @@ type publishResponse struct {
}
func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var flagApprover []string
var (
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
Use: "publish <soa-id>",
@@ -99,6 +102,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"statementOfApplicabilityId": args[0],
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -131,7 +135,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
},
}
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -108,6 +109,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -142,7 +144,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -71,6 +71,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
var (
flagOrg string
flagApprover []string
flagMinor bool
)
cmd := &cobra.Command{
@@ -109,6 +110,7 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
input := map[string]any{
"organizationId": flagOrg,
"minor": flagMinor,
}
if len(flagApprover) > 0 {
@@ -142,7 +144,8 @@ func NewCmdPublish(f *cmdutil.Factory) *cobra.Command {
}
cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated)")
cmd.Flags().StringArrayVar(&flagApprover, "approver", nil, "Approver profile ID (can be repeated; ignored with --minor)")
cmd.Flags().BoolVar(&flagMinor, "minor", false, "Publish as a minor version (no approval flow)")
return cmd
}

View File

@@ -34,7 +34,6 @@ import (
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/page"
"go.probo.inc/probo/pkg/statelesstoken"
"go.probo.inc/probo/pkg/validator"
)
type (
@@ -49,12 +48,6 @@ type (
ErrApprovalDecisionAlreadyMade struct{}
RequestApprovalRequest struct {
DocumentID gid.GID
ApproverIDs []gid.GID
Changelog *string
}
ApproveDocumentVersionRequest struct {
DocumentVersionID gid.GID
IdentityID gid.GID
@@ -79,85 +72,6 @@ func (e ErrApprovalDecisionAlreadyMade) Error() string {
return "approval decision has already been made"
}
func (req *RequestApprovalRequest) Validate() error {
v := validator.New()
v.Check(req.DocumentID, "document_id", validator.Required(), validator.GID(coredata.DocumentEntityType))
v.Check(len(req.ApproverIDs), "approver_ids", validator.Min(1), validator.Max(100))
v.Check(req.ApproverIDs, "approver_ids", validator.NoDuplicates())
v.CheckEach(req.ApproverIDs, "approver_ids", func(index int, item any) {
v.Check(item, fmt.Sprintf("approver_ids[%d]", index), validator.GID(coredata.MembershipProfileEntityType))
})
v.Check(req.Changelog, "changelog", validator.Required(), validator.SafeText(5000))
return v.Error()
}
func (s *DocumentApprovalService) RequestApproval(
ctx context.Context,
req RequestApprovalRequest,
) (*coredata.DocumentVersionApprovalQuorum, error) {
if err := req.Validate(); err != nil {
return nil, err
}
var quorum *coredata.DocumentVersionApprovalQuorum
err := s.svc.pg.WithTx(
ctx,
func(ctx context.Context, tx pg.Tx) error {
profiles := &coredata.MembershipProfiles{}
if err := profiles.LoadByIDs(ctx, tx, s.svc.scope, req.ApproverIDs); err != nil {
return fmt.Errorf("cannot load approver profiles: %w", err)
}
now := time.Now()
for _, p := range *profiles {
if p.ContractEndDate != nil && p.ContractEndDate.Before(now) {
return &ErrProfileContractEnded{ProfileID: p.ID}
}
}
document := &coredata.Document{}
if err := document.LoadByID(ctx, tx, s.svc.scope, req.DocumentID); err != nil {
return fmt.Errorf("cannot load document: %w", err)
}
if document.ArchivedAt != nil {
return &ErrDocumentArchived{}
}
documentVersion, err := s.loadLatestVersion(ctx, tx, req.DocumentID)
if err != nil {
return fmt.Errorf("cannot load latest version: %w", err)
}
if documentVersion.Status != coredata.DocumentVersionStatusDraft {
return &ErrDocumentVersionNotDraft{}
}
q, err := s.RequestApprovalInTx(ctx, tx, document, documentVersion, req.ApproverIDs, req.Changelog)
if err != nil {
return err
}
quorum = q
defaultApprovers := &coredata.DocumentDefaultApprovers{}
if err := defaultApprovers.MergeByDocumentID(ctx, tx, s.svc.scope, req.DocumentID, document.OrganizationID, req.ApproverIDs); err != nil {
return fmt.Errorf("cannot update default approvers: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return quorum, nil
}
func (s *DocumentApprovalService) RequestApprovalInTx(
ctx context.Context,
tx pg.Tx,
@@ -219,7 +133,13 @@ func (s *DocumentApprovalService) RequestApprovalInTx(
return quorum, nil
}
func (s *DocumentApprovalService) BulkPublishMajorVersions(
// BulkPublishVersions publishes (or requests approval for) the latest draft of
// each document. When req.Minor is true each draft is published as a minor
// bump and approvers are not consulted. When req.Minor is false, each
// document's saved default approvers are honoured: if the document has any,
// an approval is requested for it; otherwise it is published as a major
// bump. Documents with no draft (or already pending approval) are skipped.
func (s *DocumentApprovalService) BulkPublishVersions(
ctx context.Context,
req BulkPublishVersionsRequest,
) ([]*coredata.DocumentVersion, []*coredata.Document, error) {
@@ -235,7 +155,6 @@ func (s *DocumentApprovalService) BulkPublishMajorVersions(
return fmt.Errorf("cannot load latest version for %q: %w", documentID, err)
}
// Skip documents already pending approval.
if dv.Status == coredata.DocumentVersionStatusPendingApproval {
continue
}
@@ -249,30 +168,47 @@ func (s *DocumentApprovalService) BulkPublishMajorVersions(
return &ErrDocumentArchived{}
}
defaultApprovers := &coredata.DocumentDefaultApprovers{}
if err := defaultApprovers.LoadByDocumentID(ctx, tx, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load default approvers for %q: %w", documentID, err)
// Treat minor on an already-published version as a no-op so the
// operation is idempotent: the doc is included in the result
// without modification.
if req.Minor && dv.Status == coredata.DocumentVersionStatusPublished {
publishedVersions = append(publishedVersions, dv)
updatedDocuments = append(updatedDocuments, document)
continue
}
if dv.Status != coredata.DocumentVersionStatusDraft {
continue
}
if len(*defaultApprovers) > 0 {
approverIDs := make([]gid.GID, len(*defaultApprovers))
for i, a := range *defaultApprovers {
approverIDs[i] = a.ApproverProfileID
}
if _, err := s.RequestApprovalInTx(ctx, tx, document, dv, approverIDs, &req.Changelog); err != nil {
return fmt.Errorf("cannot request approval for %q: %w", documentID, err)
}
} else {
if req.Minor {
var err error
document, dv, err = s.svc.Documents.publishMajorVersionInTx(ctx, tx, documentID, &req.Changelog, true)
document, dv, err = s.svc.Documents.publishMinorVersionInTx(ctx, tx, documentID, &req.Changelog, true)
if err != nil {
return fmt.Errorf("cannot publish document %q: %w", documentID, err)
}
} else {
defaultApprovers := &coredata.DocumentDefaultApprovers{}
if err := defaultApprovers.LoadByDocumentID(ctx, tx, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load default approvers for %q: %w", documentID, err)
}
if len(*defaultApprovers) > 0 {
approverIDs := make([]gid.GID, len(*defaultApprovers))
for i, a := range *defaultApprovers {
approverIDs[i] = a.ApproverProfileID
}
if _, err := s.RequestApprovalInTx(ctx, tx, document, dv, approverIDs, &req.Changelog); err != nil {
return fmt.Errorf("cannot request approval for %q: %w", documentID, err)
}
} else {
var err error
document, dv, err = s.svc.Documents.publishMajorVersionInTx(ctx, tx, documentID, &req.Changelog, true)
if err != nil {
return fmt.Errorf("cannot publish document %q: %w", documentID, err)
}
}
}
publishedVersions = append(publishedVersions, dv)

View File

@@ -75,6 +75,9 @@ type (
ErrDocumentArchived struct {
}
ErrCannotPublishMinorWithoutMajor struct {
}
ErrDocumentDraftNotDeletable struct {
}
@@ -126,8 +129,22 @@ type (
BulkPublishVersionsRequest struct {
DocumentIDs []gid.GID
Minor bool
Changelog string
}
PublishDocumentRequest struct {
DocumentID gid.GID
Minor bool
ApproverIDs []gid.GID
Changelog string
}
PublishDocumentResult struct {
Document *coredata.Document
Version *coredata.DocumentVersion
Quorum *coredata.DocumentVersionApprovalQuorum
}
)
const (
@@ -159,6 +176,20 @@ func (cdr *CreateDocumentRequest) Validate() error {
return v.Error()
}
func (req *PublishDocumentRequest) Validate() error {
v := validator.New()
v.Check(req.DocumentID, "document_id", validator.Required(), validator.GID(coredata.DocumentEntityType))
v.Check(len(req.ApproverIDs), "approver_ids", validator.Max(100))
v.Check(req.ApproverIDs, "approver_ids", validator.NoDuplicates())
v.CheckEach(req.ApproverIDs, "approver_ids", func(index int, item any) {
v.Check(item, fmt.Sprintf("approver_ids[%d]", index), validator.GID(coredata.MembershipProfileEntityType))
})
v.Check(req.Changelog, "changelog", validator.Required(), validator.SafeText(5000))
return v.Error()
}
func (udr *UpdateDocumentRequest) Validate() error {
v := validator.New()
@@ -219,6 +250,10 @@ func (e ErrDocumentArchived) Error() string {
return "cannot modify an archived document"
}
func (e ErrCannotPublishMinorWithoutMajor) Error() string {
return "cannot publish a minor version before a major version exists"
}
func (e ErrDocumentDraftNotDeletable) Error() string {
return "latest version is not a deletable draft"
}
@@ -506,123 +541,105 @@ func (s DocumentService) generateChangelog(
return &text, nil
}
func (s *DocumentService) BulkPublishMinorVersions(
// PublishVersion is the single entry point for publishing a document
// version. The behaviour depends on req.Minor and req.ApproverIDs:
// - Minor=true: publish the existing draft as a minor bump (currentMajor.
// currentMinor+1). ApproverIDs are ignored. Errors with
// ErrCannotPublishMinorWithoutMajor when the document has never been
// published.
// - Minor=false with ApproverIDs: open an approval quorum on the draft as
// a pending major bump (currentMajor+1.0). Result.Quorum is set.
// - Minor=false without ApproverIDs: publish the draft immediately as a
// major bump (currentMajor+1.0).
func (s *DocumentService) PublishVersion(
ctx context.Context,
req BulkPublishVersionsRequest,
) ([]*coredata.DocumentVersion, []*coredata.Document, error) {
var publishedVersions []*coredata.DocumentVersion
var updatedDocuments []*coredata.Document
req PublishDocumentRequest,
) (*PublishDocumentResult, error) {
if err := req.Validate(); err != nil {
return nil, err
}
result := &PublishDocumentResult{}
err := s.svc.pg.WithTx(
ctx,
func(ctx context.Context, tx pg.Tx) error {
for _, documentID := range req.DocumentIDs {
dv := &coredata.DocumentVersion{}
if err := dv.LoadLatestVersion(ctx, tx, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load latest version for %q: %w", documentID, err)
}
dv := &coredata.DocumentVersion{}
if err := dv.LoadLatestVersion(ctx, tx, s.svc.scope, req.DocumentID); err != nil {
return fmt.Errorf("cannot load latest version: %w", err)
}
// Skip documents already pending approval.
if dv.Status == coredata.DocumentVersionStatusPendingApproval {
continue
}
if dv.Status == coredata.DocumentVersionStatusPendingApproval {
return &ErrDocumentVersionPendingApproval{}
}
document, version, err := s.publishMinorVersionInTx(ctx, tx, documentID, &req.Changelog, true)
if req.Minor {
document, version, err := s.publishMinorVersionInTx(ctx, tx, req.DocumentID, &req.Changelog, false)
if err != nil {
return fmt.Errorf("cannot publish document %q: %w", documentID, err)
return fmt.Errorf("cannot publish minor version: %w", err)
}
publishedVersions = append(publishedVersions, version)
updatedDocuments = append(updatedDocuments, document)
result.Document = document
result.Version = version
return nil
}
return nil
},
)
if err != nil {
return nil, nil, err
}
return publishedVersions, updatedDocuments, nil
}
func (s *DocumentService) PublishMajorVersion(
ctx context.Context,
documentID gid.GID,
publishedBy gid.GID,
changelog *string,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var document *coredata.Document
var documentVersion *coredata.DocumentVersion
err := s.svc.pg.WithTx(
ctx,
func(ctx context.Context, tx pg.Tx) error {
dv := &coredata.DocumentVersion{}
if err := dv.LoadLatestVersion(ctx, tx, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load latest version: %w", err)
if len(req.ApproverIDs) == 0 {
document, version, err := s.publishMajorVersionInTx(ctx, tx, req.DocumentID, &req.Changelog, false)
if err != nil {
return fmt.Errorf("cannot publish major version: %w", err)
}
result.Document = document
result.Version = version
return nil
}
if dv.Status == coredata.DocumentVersionStatusPendingApproval {
return &ErrDocumentVersionPendingApproval{}
profiles := &coredata.MembershipProfiles{}
if err := profiles.LoadByIDs(ctx, tx, s.svc.scope, req.ApproverIDs); err != nil {
return fmt.Errorf("cannot load approver profiles: %w", err)
}
var err error
now := time.Now()
for _, p := range *profiles {
if p.ContractEndDate != nil && p.ContractEndDate.Before(now) {
return &ErrProfileContractEnded{ProfileID: p.ID}
}
}
document, documentVersion, err = s.publishMajorVersionInTx(ctx, tx, documentID, changelog, false)
document := &coredata.Document{}
if err := document.LoadByID(ctx, tx, s.svc.scope, req.DocumentID); err != nil {
return fmt.Errorf("cannot load document: %w", err)
}
if document.ArchivedAt != nil {
return &ErrDocumentArchived{}
}
if dv.Status != coredata.DocumentVersionStatusDraft {
return &ErrDocumentVersionNotDraft{}
}
quorum, err := s.svc.DocumentApprovals.RequestApprovalInTx(ctx, tx, document, dv, req.ApproverIDs, &req.Changelog)
if err != nil {
return fmt.Errorf("cannot publish major version: %w", err)
return fmt.Errorf("cannot request approval: %w", err)
}
defaultApprovers := &coredata.DocumentDefaultApprovers{}
if err := defaultApprovers.MergeByDocumentID(ctx, tx, s.svc.scope, req.DocumentID, document.OrganizationID, req.ApproverIDs); err != nil {
return fmt.Errorf("cannot update default approvers: %w", err)
}
result.Document = document
result.Version = dv
result.Quorum = quorum
return nil
},
)
if err != nil {
return nil, nil, err
return nil, err
}
return document, documentVersion, nil
}
func (s *DocumentService) PublishMinorVersion(
ctx context.Context,
documentID gid.GID,
publishedBy gid.GID,
changelog *string,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var document *coredata.Document
var documentVersion *coredata.DocumentVersion
err := s.svc.pg.WithTx(
ctx,
func(ctx context.Context, tx pg.Tx) error {
dv := &coredata.DocumentVersion{}
if err := dv.LoadLatestVersion(ctx, tx, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load latest version: %w", err)
}
if dv.Status == coredata.DocumentVersionStatusPendingApproval {
return &ErrDocumentVersionPendingApproval{}
}
var err error
document, documentVersion, err = s.publishMinorVersionInTx(ctx, tx, documentID, changelog, false)
if err != nil {
return fmt.Errorf("cannot publish minor version: %w", err)
}
return nil
},
)
if err != nil {
return nil, nil, err
}
return document, documentVersion, nil
return result, nil
}
func (s *DocumentService) Create(
@@ -2788,6 +2805,10 @@ func (s *DocumentService) publishMinorVersionInTx(
return document, documentVersion, nil
}
if document.CurrentPublishedMajor == nil || document.CurrentPublishedMinor == nil {
return nil, nil, &ErrCannotPublishMinorWithoutMajor{}
}
document.CurrentPublishedMajor = &documentVersion.Major
document.CurrentPublishedMinor = &documentVersion.Minor

View File

@@ -38,6 +38,7 @@ func (s *GeneratedDocumentService) PublishStatementOfApplicability(
ctx context.Context,
statementOfApplicabilityID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -109,35 +110,21 @@ func (s *GeneratedDocumentService) PublishStatementOfApplicability(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if len(approverIDs) > 0 {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: soa.OrganizationID,
DocumentID: document.ID,
Title: soa.Name,
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeStatementOfApplicability,
Orientation: coredata.DocumentVersionOrientationLandscape,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, soa.OrganizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, soa.OrganizationID, approverIDs, minor, now)
},
)
@@ -299,6 +286,7 @@ func (s *GeneratedDocumentService) PublishDataList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -348,8 +336,6 @@ func (s *GeneratedDocumentService) PublishDataList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -374,35 +360,21 @@ func (s *GeneratedDocumentService) PublishDataList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Data",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationPortrait,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -550,6 +522,7 @@ func (s *GeneratedDocumentService) PublishAssetList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -599,8 +572,6 @@ func (s *GeneratedDocumentService) PublishAssetList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -625,35 +596,21 @@ func (s *GeneratedDocumentService) PublishAssetList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Assets",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationPortrait,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -822,6 +779,7 @@ func (s *GeneratedDocumentService) PublishFindingList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -871,8 +829,6 @@ func (s *GeneratedDocumentService) PublishFindingList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -897,35 +853,21 @@ func (s *GeneratedDocumentService) PublishFindingList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Findings",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationLandscape,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -1139,6 +1081,7 @@ func (s *GeneratedDocumentService) PublishObligationList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -1188,8 +1131,6 @@ func (s *GeneratedDocumentService) PublishObligationList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -1214,35 +1155,21 @@ func (s *GeneratedDocumentService) PublishObligationList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Obligations",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationLandscape,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -1424,6 +1351,7 @@ func (s *GeneratedDocumentService) PublishProcessingActivityList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -1473,8 +1401,6 @@ func (s *GeneratedDocumentService) PublishProcessingActivityList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -1499,35 +1425,21 @@ func (s *GeneratedDocumentService) PublishProcessingActivityList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Processing Activities",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationPortrait,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -1804,6 +1716,7 @@ func (s *GeneratedDocumentService) PublishDataProtectionImpactAssessmentList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -1853,8 +1766,6 @@ func (s *GeneratedDocumentService) PublishDataProtectionImpactAssessmentList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -1879,35 +1790,21 @@ func (s *GeneratedDocumentService) PublishDataProtectionImpactAssessmentList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Data Protection Impact Assessments",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationPortrait,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -2029,6 +1926,7 @@ func (s *GeneratedDocumentService) PublishTransferImpactAssessmentList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -2078,8 +1976,6 @@ func (s *GeneratedDocumentService) PublishTransferImpactAssessmentList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -2104,35 +2000,21 @@ func (s *GeneratedDocumentService) PublishTransferImpactAssessmentList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Transfer Impact Assessments",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationPortrait,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -2254,6 +2136,7 @@ func (s *GeneratedDocumentService) PublishVendorList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
// Phase 1: collect data and render the prosemirror document outside any
// write transaction. Both the bulk reads of vendors + sub-entities and the
@@ -2316,8 +2199,6 @@ func (s *GeneratedDocumentService) PublishVendorList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -2342,35 +2223,21 @@ func (s *GeneratedDocumentService) PublishVendorList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Vendors",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationPortrait,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -2771,6 +2638,7 @@ func (s *GeneratedDocumentService) PublishRiskList(
ctx context.Context,
organizationID gid.GID,
approverIDs []gid.GID,
minor bool,
) (*coredata.Document, *coredata.DocumentVersion, error) {
var (
document *coredata.Document
@@ -2820,8 +2688,6 @@ func (s *GeneratedDocumentService) PublishRiskList(
}
}
hasApprovers := len(approverIDs) > 0
if existingDoc == nil {
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
@@ -2846,35 +2712,21 @@ func (s *GeneratedDocumentService) PublishRiskList(
document = existingDoc
}
newMajor := nextDocumentMajor(document)
versionStatus := coredata.DocumentVersionStatusPublished
var publishedAt *time.Time
if hasApprovers {
versionStatus = coredata.DocumentVersionStatusDraft
} else {
publishedAt = &now
}
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
documentVersion = &coredata.DocumentVersion{
ID: documentVersionID,
OrganizationID: organizationID,
DocumentID: document.ID,
Title: "Risks",
Major: newMajor,
Minor: 0,
Content: prosemirrorJSON,
Status: versionStatus,
Classification: coredata.DocumentClassificationConfidential,
DocumentType: coredata.DocumentTypeRegister,
Orientation: coredata.DocumentVersionOrientationPortrait,
PublishedAt: publishedAt,
CreatedAt: now,
UpdatedAt: now,
}
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, newMajor, now)
return s.publishOrRequestApproval(ctx, tx, document, documentVersion, organizationID, approverIDs, minor, now)
},
)
@@ -3039,19 +2891,14 @@ func formatRiskTreatment(t coredata.RiskTreatment) string {
}
}
// nextDocumentMajor returns the major version to use for a new published
// version of a generated document.
func nextDocumentMajor(doc *coredata.Document) int {
if doc.CurrentPublishedMajor != nil {
return *doc.CurrentPublishedMajor + 1
}
return 1
}
// publishOrRequestApproval inserts a freshly built generated document version
// and either requests approval (if approverIDs is non-empty) or marks the
// document as currently published at newMajor.0. The pending-approval insert
// conflict is mapped to a friendlier error.
// publishOrRequestApproval finalises a freshly built generated document
// version. The version's Major, Minor, Status and PublishedAt fields are
// computed here based on the document's current published state, the minor
// flag, and whether approvers were provided. When minor is true the version
// is always published at currentMajor.(currentMinor+1) and approvers are
// ignored. When minor is false a non-empty approverIDs triggers an approval
// request at (currentMajor+1).0; otherwise the version is published at
// (currentMajor+1).0.
func (s *GeneratedDocumentService) publishOrRequestApproval(
ctx context.Context,
tx pg.Tx,
@@ -3059,9 +2906,34 @@ func (s *GeneratedDocumentService) publishOrRequestApproval(
version *coredata.DocumentVersion,
organizationID gid.GID,
approverIDs []gid.GID,
newMajor int,
minor bool,
now time.Time,
) error {
if minor {
if document.CurrentPublishedMajor == nil || document.CurrentPublishedMinor == nil {
return &ErrCannotPublishMinorWithoutMajor{}
}
version.Major = *document.CurrentPublishedMajor
version.Minor = *document.CurrentPublishedMinor + 1
version.Status = coredata.DocumentVersionStatusPublished
version.PublishedAt = &now
approverIDs = nil
} else {
if document.CurrentPublishedMajor != nil {
version.Major = *document.CurrentPublishedMajor + 1
} else {
version.Major = 1
}
version.Minor = 0
if len(approverIDs) > 0 {
version.Status = coredata.DocumentVersionStatusDraft
version.PublishedAt = nil
} else {
version.Status = coredata.DocumentVersionStatusPublished
version.PublishedAt = &now
}
}
if err := version.Insert(ctx, tx, s.svc.scope); err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return fmt.Errorf("a version is pending approval, approve or reject it before publishing a new one: %w", err)
@@ -3080,8 +2952,8 @@ func (s *GeneratedDocumentService) publishOrRequestApproval(
return nil
}
document.CurrentPublishedMajor = &newMajor
document.CurrentPublishedMinor = new(0)
document.CurrentPublishedMajor = &version.Major
document.CurrentPublishedMinor = &version.Minor
document.UpdatedAt = now
if err := document.Update(ctx, tx, s.svc.scope); err != nil {

View File

@@ -403,11 +403,14 @@ func (r *mutationResolver) PublishDataList(ctx context.Context, input types.Publ
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishDataList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishDataList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish data list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
@@ -426,11 +429,14 @@ func (r *mutationResolver) PublishAssetList(ctx context.Context, input types.Pub
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishAssetList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishAssetList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish asset list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -679,11 +679,14 @@ func (r *mutationResolver) PublishFindingList(ctx context.Context, input types.P
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishFindingList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishFindingList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish finding list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -771,11 +771,14 @@ func (r *mutationResolver) PublishStatementOfApplicability(ctx context.Context,
prb := r.ProboService(ctx, input.StatementOfApplicabilityID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishStatementOfApplicability(ctx, input.StatementOfApplicabilityID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishStatementOfApplicability(ctx, input.StatementOfApplicabilityID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish statement of applicability", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -274,11 +274,14 @@ func (r *mutationResolver) PublishDataProtectionImpactAssessmentList(ctx context
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishDataProtectionImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishDataProtectionImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish data protection impact assessment list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
@@ -297,11 +300,14 @@ func (r *mutationResolver) PublishTransferImpactAssessmentList(ctx context.Conte
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishTransferImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishTransferImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish transfer impact assessment list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -980,198 +980,21 @@ func (r *mutationResolver) DeleteDocument(ctx context.Context, input types.Delet
}, nil
}
// PublishMajorDocumentVersion is the resolver for the publishMajorDocumentVersion field.
func (r *mutationResolver) PublishMajorDocumentVersion(ctx context.Context, input types.PublishMajorDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) {
if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish); err != nil {
// PublishDocument is the resolver for the publishDocument field.
func (r *mutationResolver) PublishDocument(ctx context.Context, input types.PublishDocumentInput) (*types.PublishDocumentPayload, error) {
action := probo.ActionDocumentVersionPublish
if !input.Minor && len(input.ApproverIds) > 0 {
action = probo.ActionDocumentVersionRequestApproval
}
if err := r.authorize(ctx, input.DocumentID, action); err != nil {
return nil, err
}
prb := r.ProboService(ctx, input.DocumentID.TenantID())
document, documentVersion, err := prb.Documents.PublishMajorVersion(
ctx,
input.DocumentID,
authn.IdentityFromContext(ctx).ID,
input.Changelog,
)
if err != nil {
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
return nil, gqlutils.Conflict(ctx, errArchived)
}
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
return nil, gqlutils.Invalid(ctx, errNotDraft)
}
if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok {
return nil, gqlutils.Conflict(ctx, errPending)
}
r.logger.ErrorCtx(ctx, "cannot publish major document version", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.PublishDocumentVersionPayload{
Document: types.NewDocument(document),
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
// PublishMinorDocumentVersion is the resolver for the publishMinorDocumentVersion field.
func (r *mutationResolver) PublishMinorDocumentVersion(ctx context.Context, input types.PublishMinorDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) {
if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish); err != nil {
return nil, err
}
prb := r.ProboService(ctx, input.DocumentID.TenantID())
document, documentVersion, err := prb.Documents.PublishMinorVersion(
ctx,
input.DocumentID,
authn.IdentityFromContext(ctx).ID,
input.Changelog,
)
if err != nil {
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
return nil, gqlutils.Conflict(ctx, errArchived)
}
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
return nil, gqlutils.Invalid(ctx, errNotDraft)
}
if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok {
return nil, gqlutils.Conflict(ctx, errPending)
}
r.logger.ErrorCtx(ctx, "cannot publish minor document version", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.PublishDocumentVersionPayload{
Document: types.NewDocument(document),
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
// BulkPublishMajorDocumentVersions is the resolver for the bulkPublishMajorDocumentVersions field.
func (r *mutationResolver) BulkPublishMajorDocumentVersions(ctx context.Context, input types.BulkPublishDocumentVersionsInput) (*types.BulkPublishDocumentVersionsPayload, error) {
if len(input.DocumentIds) == 0 {
return &types.BulkPublishDocumentVersionsPayload{
DocumentVersions: []*types.DocumentVersion{},
Documents: []*types.Document{},
}, nil
}
for _, documentID := range input.DocumentIds {
if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil {
return nil, err
}
}
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
versions, documents, err := prb.DocumentApprovals.BulkPublishMajorVersions(ctx, probo.BulkPublishVersionsRequest{
DocumentIDs: input.DocumentIds,
Changelog: input.Changelog,
})
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
return nil, gqlutils.Conflict(ctx, errArchived)
}
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
return nil, gqlutils.Invalid(ctx, errNotDraft)
}
r.logger.ErrorCtx(ctx, "cannot bulk publish major document versions", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
typesVersions := make([]*types.DocumentVersion, len(versions))
for i, v := range versions {
typesVersions[i] = types.NewDocumentVersion(v)
}
typesDocuments := make([]*types.Document, len(documents))
for i, d := range documents {
typesDocuments[i] = types.NewDocument(d)
}
return &types.BulkPublishDocumentVersionsPayload{
DocumentVersions: typesVersions,
Documents: typesDocuments,
}, nil
}
// BulkPublishMinorDocumentVersions is the resolver for the bulkPublishMinorDocumentVersions field.
func (r *mutationResolver) BulkPublishMinorDocumentVersions(ctx context.Context, input types.BulkPublishDocumentVersionsInput) (*types.BulkPublishDocumentVersionsPayload, error) {
if len(input.DocumentIds) == 0 {
return &types.BulkPublishDocumentVersionsPayload{
DocumentVersions: []*types.DocumentVersion{},
Documents: []*types.Document{},
}, nil
}
for _, documentID := range input.DocumentIds {
if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil {
return nil, err
}
}
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
versions, documents, err := prb.Documents.BulkPublishMinorVersions(ctx, probo.BulkPublishVersionsRequest{
DocumentIDs: input.DocumentIds,
Changelog: input.Changelog,
})
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
return nil, gqlutils.Conflict(ctx, errArchived)
}
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
return nil, gqlutils.Invalid(ctx, errNotDraft)
}
r.logger.ErrorCtx(ctx, "cannot bulk publish minor document versions", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
typesVersions := make([]*types.DocumentVersion, len(versions))
for i, v := range versions {
typesVersions[i] = types.NewDocumentVersion(v)
}
typesDocuments := make([]*types.Document, len(documents))
for i, d := range documents {
typesDocuments[i] = types.NewDocument(d)
}
return &types.BulkPublishDocumentVersionsPayload{
DocumentVersions: typesVersions,
Documents: typesDocuments,
}, nil
}
// RequestDocumentVersionApproval is the resolver for the requestDocumentVersionApproval field.
func (r *mutationResolver) RequestDocumentVersionApproval(ctx context.Context, input types.RequestDocumentVersionApprovalInput) (*types.RequestDocumentVersionApprovalPayload, error) {
if err := r.authorize(ctx, input.DocumentID, probo.ActionDocumentVersionRequestApproval); err != nil {
return nil, err
}
prb := r.ProboService(ctx, input.DocumentID.TenantID())
quorum, err := prb.DocumentApprovals.RequestApproval(ctx, probo.RequestApprovalRequest{
result, err := prb.Documents.PublishVersion(ctx, probo.PublishDocumentRequest{
DocumentID: input.DocumentID,
Minor: input.Minor,
ApproverIDs: input.ApproverIds,
Changelog: input.Changelog,
})
@@ -1181,7 +1004,15 @@ func (r *mutationResolver) RequestDocumentVersionApproval(ctx context.Context, i
}
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
return nil, gqlutils.Conflict(ctx, errNotDraft)
return nil, gqlutils.Invalid(ctx, errNotDraft)
}
if errPending, ok := errors.AsType[*probo.ErrDocumentVersionPendingApproval](err); ok {
return nil, gqlutils.Conflict(ctx, errPending)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
if errContractEnded, ok := errors.AsType[*probo.ErrProfileContractEnded](err); ok {
@@ -1192,12 +1023,72 @@ func (r *mutationResolver) RequestDocumentVersionApproval(ctx context.Context, i
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
}
r.logger.ErrorCtx(ctx, "cannot request document version approval", log.Error(err))
r.logger.ErrorCtx(ctx, "cannot publish document", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.RequestDocumentVersionApprovalPayload{
ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum),
payload := &types.PublishDocumentPayload{
Document: types.NewDocument(result.Document),
DocumentVersion: types.NewDocumentVersion(result.Version),
}
if result.Quorum != nil {
payload.ApprovalQuorum = types.NewDocumentVersionApprovalQuorum(result.Quorum)
}
return payload, nil
}
// BulkPublishDocuments is the resolver for the bulkPublishDocuments field.
func (r *mutationResolver) BulkPublishDocuments(ctx context.Context, input types.BulkPublishDocumentsInput) (*types.BulkPublishDocumentsPayload, error) {
if len(input.DocumentIds) == 0 {
return &types.BulkPublishDocumentsPayload{
DocumentVersions: []*types.DocumentVersion{},
Documents: []*types.Document{},
}, nil
}
for _, documentID := range input.DocumentIds {
if err := r.authorize(ctx, documentID, probo.ActionDocumentVersionPublish); err != nil {
return nil, err
}
}
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
versions, documents, err := prb.DocumentApprovals.BulkPublishVersions(ctx, probo.BulkPublishVersionsRequest{
DocumentIDs: input.DocumentIds,
Minor: input.Minor,
Changelog: input.Changelog,
})
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errArchived, ok := errors.AsType[*probo.ErrDocumentArchived](err); ok {
return nil, gqlutils.Conflict(ctx, errArchived)
}
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
return nil, gqlutils.Invalid(ctx, errNotDraft)
}
r.logger.ErrorCtx(ctx, "cannot bulk publish documents", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
typesVersions := make([]*types.DocumentVersion, len(versions))
for i, v := range versions {
typesVersions[i] = types.NewDocumentVersion(v)
}
typesDocuments := make([]*types.Document, len(documents))
for i, d := range documents {
typesDocuments[i] = types.NewDocument(d)
}
return &types.BulkPublishDocumentsPayload{
DocumentVersions: typesVersions,
Documents: typesDocuments,
}, nil
}

View File

@@ -219,6 +219,7 @@ type DeleteDatumPayload {
input PublishDataListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type PublishDataListPayload {
@@ -229,6 +230,7 @@ type PublishDataListPayload {
input PublishAssetListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type PublishAssetListPayload {

View File

@@ -276,6 +276,7 @@ extend type Mutation {
input PublishFindingListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type PublishFindingListPayload {

View File

@@ -375,6 +375,7 @@ input DeleteStatementOfApplicabilityInput {
input PublishStatementOfApplicabilityInput {
statementOfApplicabilityId: ID!
approverIds: [ID!]
minor: Boolean!
}
type CreateControlPayload {

View File

@@ -184,11 +184,13 @@ input DeleteTransferImpactAssessmentInput {
input PublishDataProtectionImpactAssessmentListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
input PublishTransferImpactAssessmentListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type CreateDataProtectionImpactAssessmentPayload {

View File

@@ -536,21 +536,12 @@ extend type Mutation {
archiveDocument(input: ArchiveDocumentInput!): ArchiveDocumentPayload!
unarchiveDocument(input: UnarchiveDocumentInput!): UnarchiveDocumentPayload!
deleteDocument(input: DeleteDocumentInput!): DeleteDocumentPayload!
publishMajorDocumentVersion(
input: PublishMajorDocumentVersionInput!
): PublishDocumentVersionPayload!
publishMinorDocumentVersion(
input: PublishMinorDocumentVersionInput!
): PublishDocumentVersionPayload!
bulkPublishMajorDocumentVersions(
input: BulkPublishDocumentVersionsInput!
): BulkPublishDocumentVersionsPayload!
bulkPublishMinorDocumentVersions(
input: BulkPublishDocumentVersionsInput!
): BulkPublishDocumentVersionsPayload!
requestDocumentVersionApproval(
input: RequestDocumentVersionApprovalInput!
): RequestDocumentVersionApprovalPayload!
publishDocument(
input: PublishDocumentInput!
): PublishDocumentPayload!
bulkPublishDocuments(
input: BulkPublishDocumentsInput!
): BulkPublishDocumentsPayload!
voidDocumentVersionApproval(
input: VoidDocumentVersionApprovalInput!
): VoidDocumentVersionApprovalPayload!
@@ -641,25 +632,17 @@ input ExportEmployeeDocumentVersionPDFInput {
documentVersionId: ID!
}
input PublishMajorDocumentVersionInput {
input PublishDocumentInput {
documentId: ID!
changelog: String
}
input PublishMinorDocumentVersionInput {
documentId: ID!
changelog: String
}
input BulkPublishDocumentVersionsInput {
documentIds: [ID!]!
minor: Boolean!
approverIds: [ID!]
changelog: String!
}
input RequestDocumentVersionApprovalInput {
documentId: ID!
approverIds: [ID!]!
changelog: String
input BulkPublishDocumentsInput {
documentIds: [ID!]!
minor: Boolean!
changelog: String!
}
input VoidDocumentVersionApprovalInput {
@@ -756,20 +739,17 @@ type ExportEmployeeDocumentVersionPDFPayload {
data: String!
}
type PublishDocumentVersionPayload {
type PublishDocumentPayload {
document: Document!
documentVersion: DocumentVersion!
approvalQuorum: DocumentVersionApprovalQuorum
}
type BulkPublishDocumentVersionsPayload {
type BulkPublishDocumentsPayload {
documentVersions: [DocumentVersion!]!
documents: [Document!]!
}
type RequestDocumentVersionApprovalPayload {
approvalQuorum: DocumentVersionApprovalQuorum!
}
type VoidDocumentVersionApprovalPayload {
approvalQuorum: DocumentVersionApprovalQuorum!
documentVersion: DocumentVersion!

View File

@@ -96,6 +96,7 @@ extend type Mutation {
input PublishObligationListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type PublishObligationListPayload {

View File

@@ -262,6 +262,7 @@ input DeleteProcessingActivityInput {
input PublishProcessingActivityListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type CreateProcessingActivityPayload {

View File

@@ -157,6 +157,7 @@ extend type Mutation {
input PublishRiskListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type PublishRiskListPayload {

View File

@@ -465,6 +465,7 @@ extend type Mutation {
input PublishVendorListInput {
organizationId: ID!
approverIds: [ID!]
minor: Boolean!
}
type PublishVendorListPayload {

View File

@@ -120,11 +120,14 @@ func (r *mutationResolver) PublishObligationList(ctx context.Context, input type
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishObligationList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishObligationList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish obligation list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -132,11 +132,14 @@ func (r *mutationResolver) PublishProcessingActivityList(ctx context.Context, in
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishProcessingActivityList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishProcessingActivityList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish processing activity list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -247,11 +247,14 @@ func (r *mutationResolver) PublishRiskList(ctx context.Context, input types.Publ
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishRiskList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishRiskList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish risk list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -573,11 +573,14 @@ func (r *mutationResolver) PublishVendorList(ctx context.Context, input types.Pu
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
document, documentVersion, err := prb.GeneratedDocuments.PublishVendorList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := prb.GeneratedDocuments.PublishVendorList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if errMinor, ok := errors.AsType[*probo.ErrCannotPublishMinorWithoutMajor](err); ok {
return nil, gqlutils.Invalid(ctx, errMinor)
}
r.logger.ErrorCtx(ctx, "cannot publish vendor list", log.Error(err))
return nil, gqlutils.Internal(ctx)
}

View File

@@ -3508,74 +3508,6 @@ func (r *Resolver) ListAuditLogEntriesTool(ctx context.Context, req *mcp.CallToo
return nil, types.NewListAuditLogEntriesOutput(p), nil
}
func (r *Resolver) RequestDocumentVersionApprovalTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RequestDocumentVersionApprovalInput) (*mcp.CallToolResult, types.RequestDocumentVersionApprovalOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionRequestApproval)
svc := r.ProboService(ctx, input.DocumentID)
quorum, err := svc.DocumentApprovals.RequestApproval(ctx, probo.RequestApprovalRequest{
DocumentID: input.DocumentID,
ApproverIDs: input.ApproverIds,
Changelog: input.Changelog,
})
if err != nil {
panic(fmt.Errorf("cannot request document version approval: %w", err))
}
documentVersion, err := svc.Documents.GetVersion(ctx, quorum.VersionID)
if err != nil {
panic(fmt.Errorf("cannot get document version: %w", err))
}
return nil, types.RequestDocumentVersionApprovalOutput{
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
func (r *Resolver) PublishMajorDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishMajorDocumentVersionInput) (*mcp.CallToolResult, types.PublishMajorDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish)
svc := r.ProboService(ctx, input.DocumentID)
user := authn.IdentityFromContext(ctx)
document, documentVersion, err := svc.Documents.PublishMajorVersion(
ctx,
input.DocumentID,
user.ID,
input.Changelog,
)
if err != nil {
panic(fmt.Errorf("cannot publish major document version: %w", err))
}
return nil, types.PublishMajorDocumentVersionOutput{
Document: types.NewDocument(document),
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
func (r *Resolver) PublishMinorDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishMinorDocumentVersionInput) (*mcp.CallToolResult, types.PublishMinorDocumentVersionOutput, error) {
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionPublish)
svc := r.ProboService(ctx, input.DocumentID)
user := authn.IdentityFromContext(ctx)
document, documentVersion, err := svc.Documents.PublishMinorVersion(
ctx,
input.DocumentID,
user.ID,
input.Changelog,
)
if err != nil {
panic(fmt.Errorf("cannot publish minor document version: %w", err))
}
return nil, types.PublishMinorDocumentVersionOutput{
Document: types.NewDocument(document),
DocumentVersion: types.NewDocumentVersion(documentVersion),
}, nil
}
func (r *Resolver) ListMeasureDocumentsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListMeasureDocumentsInput) (*mcp.CallToolResult, types.ListMeasureDocumentsOutput, error) {
r.MustAuthorize(ctx, input.MeasureID, probo.ActionMeasureGet)
@@ -3652,7 +3584,7 @@ func (r *Resolver) PublishStatementOfApplicabilityTool(ctx context.Context, req
svc := r.ProboService(ctx, input.ID)
document, documentVersion, err := svc.GeneratedDocuments.PublishStatementOfApplicability(ctx, input.ID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishStatementOfApplicability(ctx, input.ID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishStatementOfApplicabilityOutput{}, fmt.Errorf("cannot publish statement of applicability: %w", err)
}
@@ -3882,7 +3814,7 @@ func (r *Resolver) PublishDataListTool(ctx context.Context, req *mcp.CallToolReq
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishDataList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishDataList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishDataListOutput{}, fmt.Errorf("cannot publish data list: %w", err)
}
@@ -3898,7 +3830,7 @@ func (r *Resolver) PublishAssetListTool(ctx context.Context, req *mcp.CallToolRe
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishAssetList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishAssetList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishAssetListOutput{}, fmt.Errorf("cannot publish asset list: %w", err)
}
@@ -4665,7 +4597,7 @@ func (r *Resolver) PublishFindingListTool(ctx context.Context, req *mcp.CallTool
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishFindingList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishFindingList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishFindingListOutput{}, fmt.Errorf("cannot publish finding list: %w", err)
}
@@ -4681,7 +4613,7 @@ func (r *Resolver) PublishObligationListTool(ctx context.Context, req *mcp.CallT
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishObligationList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishObligationList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishObligationListOutput{}, fmt.Errorf("cannot publish obligation list: %w", err)
}
@@ -4697,7 +4629,7 @@ func (r *Resolver) PublishProcessingActivityListTool(ctx context.Context, req *m
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishProcessingActivityList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishProcessingActivityList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishProcessingActivityListOutput{}, fmt.Errorf("cannot publish processing activity list: %w", err)
}
@@ -4713,7 +4645,7 @@ func (r *Resolver) PublishDataProtectionImpactAssessmentListTool(ctx context.Con
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishDataProtectionImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishDataProtectionImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishDataProtectionImpactAssessmentListOutput{}, fmt.Errorf("cannot publish DPIA list: %w", err)
}
@@ -4729,7 +4661,7 @@ func (r *Resolver) PublishTransferImpactAssessmentListTool(ctx context.Context,
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishTransferImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishTransferImpactAssessmentList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishTransferImpactAssessmentListOutput{}, fmt.Errorf("cannot publish TIA list: %w", err)
}
@@ -4745,7 +4677,7 @@ func (r *Resolver) PublishVendorListTool(ctx context.Context, req *mcp.CallToolR
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishVendorList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishVendorList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishVendorListOutput{}, fmt.Errorf("cannot publish vendor list: %w", err)
}
@@ -5098,7 +5030,7 @@ func (r *Resolver) PublishRiskListTool(ctx context.Context, req *mcp.CallToolReq
svc := r.ProboService(ctx, input.OrganizationID)
document, documentVersion, err := svc.GeneratedDocuments.PublishRiskList(ctx, input.OrganizationID, input.ApproverIds)
document, documentVersion, err := svc.GeneratedDocuments.PublishRiskList(ctx, input.OrganizationID, input.ApproverIds, input.Minor)
if err != nil {
return nil, types.PublishRiskListOutput{}, fmt.Errorf("cannot publish risk list: %w", err)
}
@@ -5222,3 +5154,32 @@ func (r *Resolver) ListSCIMEventsTool(ctx context.Context, req *mcp.CallToolRequ
return nil, types.NewListSCIMEventsOutput(p), nil
}
func (r *Resolver) PublishDocumentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishDocumentInput) (*mcp.CallToolResult, types.PublishDocumentOutput, error) {
action := probo.ActionDocumentVersionPublish
if !input.Minor && len(input.ApproverIds) > 0 {
action = probo.ActionDocumentVersionRequestApproval
}
r.MustAuthorize(ctx, input.DocumentID, action)
svc := r.ProboService(ctx, input.DocumentID)
result, err := svc.Documents.PublishVersion(ctx, probo.PublishDocumentRequest{
DocumentID: input.DocumentID,
Minor: input.Minor,
ApproverIDs: input.ApproverIds,
Changelog: input.Changelog,
})
if err != nil {
panic(fmt.Errorf("cannot publish document: %w", err))
}
output := types.PublishDocumentOutput{
Document: types.NewDocument(result.Document),
DocumentVersion: types.NewDocumentVersion(result.Version),
}
if result.Quorum != nil {
output.ApprovalQuorum = types.NewDocumentVersionApprovalQuorum(result.Quorum)
}
return nil, output, nil
}

View File

@@ -5965,31 +5965,29 @@ components:
document_version:
$ref: "#/components/schemas/DocumentVersion"
PublishMajorDocumentVersionInput:
PublishDocumentInput:
type: object
required:
- document_id
- minor
- changelog
properties:
document_id:
$ref: "#/components/schemas/GID"
description: Document ID
minor:
type: boolean
description: When true, publish the draft as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
approver_ids:
type: array
items:
$ref: "#/components/schemas/GID"
description: Approver profile IDs (ignored when minor is true)
changelog:
type: string
description: Changelog for this version
PublishMinorDocumentVersionInput:
type: object
required:
- document_id
properties:
document_id:
$ref: "#/components/schemas/GID"
description: Document ID
changelog:
type: string
description: Changelog for this version
PublishDocumentVersionOutput:
PublishDocumentOutput:
type: object
description: document_version.content is markdown
required:
@@ -6000,33 +5998,9 @@ components:
$ref: "#/components/schemas/Document"
document_version:
$ref: "#/components/schemas/DocumentVersion"
RequestDocumentVersionApprovalInput:
type: object
required:
- document_id
- approver_ids
properties:
document_id:
$ref: "#/components/schemas/GID"
description: Document ID
approver_ids:
type: array
items:
$ref: "#/components/schemas/GID"
description: Approver profile IDs
changelog:
type: string
description: Changelog for this version
RequestDocumentVersionApprovalOutput:
type: object
description: document_version.content is markdown
required:
- document_version
properties:
document_version:
$ref: "#/components/schemas/DocumentVersion"
approval_quorum:
$ref: "#/components/schemas/DocumentVersionApprovalQuorum"
description: Set when an approval was requested instead of publishing.
DeleteDocumentInput:
type: object
@@ -6721,6 +6695,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6729,7 +6704,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishDataListOutput:
type: object
@@ -6748,6 +6726,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6756,7 +6735,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishAssetListOutput:
type: object
@@ -6775,6 +6757,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6783,7 +6766,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishFindingListOutput:
type: object
@@ -6802,6 +6788,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6810,7 +6797,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishObligationListOutput:
type: object
@@ -6829,6 +6819,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6837,7 +6828,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishProcessingActivityListOutput:
type: object
@@ -6856,6 +6850,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6864,7 +6859,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishDataProtectionImpactAssessmentListOutput:
type: object
@@ -6883,6 +6881,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6891,7 +6890,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishTransferImpactAssessmentListOutput:
type: object
@@ -6910,6 +6912,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6918,7 +6921,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishVendorListOutput:
type: object
@@ -6937,6 +6943,7 @@ components:
type: object
required:
- organization_id
- minor
properties:
organization_id:
$ref: "#/components/schemas/GID"
@@ -6945,7 +6952,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishRiskListOutput:
type: object
@@ -6964,6 +6974,7 @@ components:
type: object
required:
- id
- minor
properties:
id:
$ref: "#/components/schemas/GID"
@@ -6972,7 +6983,10 @@ components:
type: array
items:
$ref: "#/components/schemas/GID"
description: Optional approver profile IDs. If provided, creates a draft pending approval instead of publishing immediately.
description: Optional approver profile IDs. When minor is false and at least one ID is provided, an approval is requested as a major version. Ignored when minor is true.
minor:
type: boolean
description: When true, publish as a minor version (currentMajor.currentMinor+1) and ignore approver_ids; the document must already have a published major version. When false, publish as a new major version; if approver_ids are provided, an approval is requested instead.
PublishStatementOfApplicabilityOutput:
type: object
@@ -11231,30 +11245,14 @@ tools:
$ref: "#/components/schemas/GetDocumentVersionInput"
outputSchema:
$ref: "#/components/schemas/GetDocumentVersionOutput"
- name: publishMajorDocumentVersion
description: Publish a draft document version as a new major version
- name: publishDocument
description: Publish the latest draft of a document. Set minor=true to publish a minor version (no approval flow). When minor=false, providing approver_ids triggers an approval request as a new major version; otherwise the draft is published immediately as a new major version.
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/PublishMajorDocumentVersionInput"
$ref: "#/components/schemas/PublishDocumentInput"
outputSchema:
$ref: "#/components/schemas/PublishDocumentVersionOutput"
- name: publishMinorDocumentVersion
description: Publish a draft document version as a minor version
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/PublishMinorDocumentVersionInput"
outputSchema:
$ref: "#/components/schemas/PublishDocumentVersionOutput"
- name: requestDocumentVersionApproval
description: Request approval for a document version
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/RequestDocumentVersionApprovalInput"
outputSchema:
$ref: "#/components/schemas/RequestDocumentVersionApprovalOutput"
$ref: "#/components/schemas/PublishDocumentOutput"
- name: deleteDocument
description: Delete a document
hints: