Add approval quorum and decision read tools

Expose document version approval quorums and decisions
through MCP, CLI, and n8n. This lets users inspect who
approved or rejected a document version, including the
rejection comment, without relying solely on the audit
log.

MCP tools: listDocumentVersionApprovalQuorums,
getDocumentVersionApprovalQuorum,
listDocumentVersionApprovalDecisions,
getDocumentVersionApprovalDecision.

CLI commands: document list-approval-quorums,
view-approval-quorum, list-approval-decisions,
view-approval-decision.

n8n operations: Get/Get Many Approval Quorums and
Approval Decisions on the Document resource.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-20 10:10:39 +02:00
parent 685e9d2e69
commit 5b8918bd5a
14 changed files with 1528 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
// Copyright (c) 2025-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.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Approval Quorum ID',
name: 'approvalQuorumId',
type: 'string',
displayOptions: {
show: {
resource: ['document'],
operation: ['getAllApprovalDecisions'],
},
},
default: '',
description: 'The ID of the approval quorum',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['document'],
operation: ['getAllApprovalDecisions'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['document'],
operation: ['getAllApprovalDecisions'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const approvalQuorumId = this.getNodeParameter('approvalQuorumId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetDocumentVersionApprovalDecisions($approvalQuorumId: ID!, $first: Int, $after: CursorKey) {
node(id: $approvalQuorumId) {
... on DocumentVersionApprovalQuorum {
decisions(first: $first, after: $after) {
edges {
node {
id
state
comment
decidedAt
approver {
id
fullName
}
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const approvalDecisions = await proboApiRequestAllItems.call(
this,
query,
{ approvalQuorumId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.decisions as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { approvalDecisions },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,113 @@
// Copyright (c) 2025-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.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { proboApiRequestAllItems } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Document Version ID',
name: 'documentVersionId',
type: 'string',
displayOptions: {
show: {
resource: ['document'],
operation: ['getAllApprovalQuorums'],
},
},
default: '',
description: 'The ID of the document version',
required: true,
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['document'],
operation: ['getAllApprovalQuorums'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['document'],
operation: ['getAllApprovalQuorums'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const documentVersionId = this.getNodeParameter('documentVersionId', itemIndex) as string;
const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
const query = `
query GetDocumentVersionApprovalQuorums($documentVersionId: ID!, $first: Int, $after: CursorKey) {
node(id: $documentVersionId) {
... on DocumentVersion {
approvalQuorums(first: $first, after: $after) {
edges {
node {
id
status
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
const approvalQuorums = await proboApiRequestAllItems.call(
this,
query,
{ documentVersionId },
(response) => {
const data = response?.data as IDataObject | undefined;
const node = data?.node as IDataObject | undefined;
return node?.approvalQuorums as IDataObject | undefined;
},
returnAll,
limit,
);
return {
json: { approvalQuorums },
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025-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.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Approval Decision ID',
name: 'approvalDecisionId',
type: 'string',
displayOptions: {
show: {
resource: ['document'],
operation: ['getApprovalDecision'],
},
},
default: '',
description: 'The ID of the document version approval decision',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const approvalDecisionId = this.getNodeParameter('approvalDecisionId', itemIndex) as string;
const query = `
query GetDocumentVersionApprovalDecision($approvalDecisionId: ID!) {
node(id: $approvalDecisionId) {
... on DocumentVersionApprovalDecision {
id
state
comment
decidedAt
approver {
id
fullName
}
createdAt
updatedAt
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { approvalDecisionId });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2025-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.
import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { proboApiRequest } from '../../GenericFunctions';
export const description: INodeProperties[] = [
{
displayName: 'Approval Quorum ID',
name: 'approvalQuorumId',
type: 'string',
displayOptions: {
show: {
resource: ['document'],
operation: ['getApprovalQuorum'],
},
},
default: '',
description: 'The ID of the document version approval quorum',
required: true,
},
];
export async function execute(
this: IExecuteFunctions,
itemIndex: number,
): Promise<INodeExecutionData> {
const approvalQuorumId = this.getNodeParameter('approvalQuorumId', itemIndex) as string;
const query = `
query GetDocumentVersionApprovalQuorum($approvalQuorumId: ID!) {
node(id: $approvalQuorumId) {
... on DocumentVersionApprovalQuorum {
id
status
createdAt
updatedAt
}
}
}
`;
const responseData = await proboApiRequest.call(this, query, { approvalQuorumId });
return {
json: responseData,
pairedItem: { item: itemIndex },
};
}

View File

@@ -34,6 +34,10 @@ import * as getAllSignaturesOp from './getAllSignatures.operation';
import * as requestSignatureOp from './requestSignature.operation';
import * as cancelSignatureOp from './cancelSignature.operation';
import * as sendSigningNotificationsOp from './sendSigningNotifications.operation';
import * as getApprovalQuorumOp from './getApprovalQuorum.operation';
import * as getAllApprovalQuorumsOp from './getAllApprovalQuorums.operation';
import * as getApprovalDecisionOp from './getApprovalDecision.operation';
import * as getAllApprovalDecisionsOp from './getAllApprovalDecisions.operation';
export const description: INodeProperties[] = [
{
@@ -89,12 +93,36 @@ export const description: INodeProperties[] = [
description: 'Get a document',
action: 'Get a document',
},
{
name: 'Get Approval Decision',
value: 'getApprovalDecision',
description: 'Get an approval decision',
action: 'Get an approval decision',
},
{
name: 'Get Approval Quorum',
value: 'getApprovalQuorum',
description: 'Get an approval quorum',
action: 'Get an approval quorum',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many documents',
action: 'Get many documents',
},
{
name: 'Get Many Approval Decisions',
value: 'getAllApprovalDecisions',
description: 'Get many approval decisions for an approval quorum',
action: 'Get many approval decisions',
},
{
name: 'Get Many Approval Quorums',
value: 'getAllApprovalQuorums',
description: 'Get many approval quorums for a document version',
action: 'Get many approval quorums',
},
{
name: 'Get Many Signatures',
value: 'getAllSignatures',
@@ -197,6 +225,10 @@ export const description: INodeProperties[] = [
...requestSignatureOp.description,
...cancelSignatureOp.description,
...sendSigningNotificationsOp.description,
...getApprovalQuorumOp.description,
...getAllApprovalQuorumsOp.description,
...getApprovalDecisionOp.description,
...getAllApprovalDecisionsOp.description,
];
export {
@@ -221,4 +253,8 @@ export {
requestSignatureOp as requestSignature,
cancelSignatureOp as cancelSignature,
sendSigningNotificationsOp as sendSigningNotifications,
getApprovalQuorumOp as getApprovalQuorum,
getAllApprovalQuorumsOp as getAllApprovalQuorums,
getApprovalDecisionOp as getApprovalDecision,
getAllApprovalDecisionsOp as getAllApprovalDecisions,
};

View File

@@ -22,12 +22,16 @@ import (
"go.probo.inc/probo/pkg/cmd/document/delete"
deletedraft "go.probo.inc/probo/pkg/cmd/document/delete-draft"
"go.probo.inc/probo/pkg/cmd/document/list"
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/unarchive"
"go.probo.inc/probo/pkg/cmd/document/update"
"go.probo.inc/probo/pkg/cmd/document/view"
viewapprovaldecision "go.probo.inc/probo/pkg/cmd/document/view-approval-decision"
viewapprovalquorum "go.probo.inc/probo/pkg/cmd/document/view-approval-quorum"
viewversion "go.probo.inc/probo/pkg/cmd/document/view-version"
)
@@ -49,6 +53,10 @@ func NewCmdDocument(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(deletedraft.NewCmdDeleteDraft(f))
cmd.AddCommand(publishmajor.NewCmdPublishMajor(f))
cmd.AddCommand(publishminor.NewCmdPublishMinor(f))
cmd.AddCommand(listapprovalquorums.NewCmdListApprovalQuorums(f))
cmd.AddCommand(viewapprovalquorum.NewCmdViewApprovalQuorum(f))
cmd.AddCommand(listapprovaldecisions.NewCmdListApprovalDecisions(f))
cmd.AddCommand(viewapprovaldecision.NewCmdViewApprovalDecision(f))
return cmd
}

View File

@@ -0,0 +1,206 @@
// 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 listapprovaldecisions
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const listQuery = `
query($id: ID!, $first: Int, $after: CursorKey, $orderBy: DocumentVersionApprovalDecisionOrder) {
node(id: $id) {
__typename
... on DocumentVersionApprovalQuorum {
decisions(first: $first, after: $after, orderBy: $orderBy) {
totalCount
edges {
node {
id
approver {
fullName
}
state
comment
decidedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`
type approvalDecision struct {
ID string `json:"id"`
Approver struct {
FullName string `json:"fullName"`
} `json:"approver"`
State string `json:"state"`
Comment *string `json:"comment"`
DecidedAt *string `json:"decidedAt"`
}
func NewCmdListApprovalDecisions(f *cmdutil.Factory) *cobra.Command {
var (
flagLimit int
flagOrderBy string
flagOrderDir string
flagOutput *string
)
cmd := &cobra.Command{
Use: "list-approval-decisions <quorum-id>",
Short: "List approval decisions for a quorum",
Example: ` # List approval decisions for a quorum
prb document list-approval-decisions <quorum-id>`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmdutil.ValidateOutputFlag(flagOutput); err != nil {
return err
}
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(),
)
variables := map[string]any{
"id": args[0],
}
if flagOrderBy != "" {
if err := cmdutil.ValidateEnum(
"order-by",
flagOrderBy,
[]string{"CREATED_AT"},
); err != nil {
return err
}
if err := cmdutil.ValidateEnum(
"order-direction",
flagOrderDir,
[]string{"ASC", "DESC"},
); err != nil {
return err
}
variables["orderBy"] = map[string]any{
"field": flagOrderBy,
"direction": flagOrderDir,
}
}
decisions, totalCount, err := api.Paginate(
client,
listQuery,
variables,
flagLimit,
func(data json.RawMessage) (*api.Connection[approvalDecision], error) {
var resp struct {
Node *struct {
Typename string `json:"__typename"`
Decisions api.Connection[approvalDecision] `json:"decisions"`
} `json:"node"`
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, err
}
if resp.Node == nil {
return nil, fmt.Errorf("approval quorum %s not found", args[0])
}
if resp.Node.Typename != "DocumentVersionApprovalQuorum" {
return nil, fmt.Errorf("expected DocumentVersionApprovalQuorum node, got %s", resp.Node.Typename)
}
return &resp.Node.Decisions, nil
},
)
if err != nil {
return err
}
if *flagOutput == cmdutil.OutputJSON {
return cmdutil.PrintJSON(f.IOStreams.Out, decisions)
}
if len(decisions) == 0 {
_, _ = fmt.Fprintln(f.IOStreams.Out, "No approval decisions found.")
return nil
}
rows := make([][]string, 0, len(decisions))
for _, d := range decisions {
comment := ""
if d.Comment != nil {
comment = *d.Comment
}
decidedAt := ""
if d.DecidedAt != nil {
decidedAt = cmdutil.FormatTime(*d.DecidedAt)
}
rows = append(rows, []string{
d.ID,
d.Approver.FullName,
d.State,
comment,
decidedAt,
})
}
t := cmdutil.NewTable("ID", "APPROVER", "STATE", "COMMENT", "DECIDED AT").Rows(rows...)
_, _ = fmt.Fprintln(f.IOStreams.Out, t)
if totalCount > len(decisions) {
_, _ = fmt.Fprintf(
f.IOStreams.ErrOut,
"\nShowing %d of %d approval decisions\n",
len(decisions),
totalCount,
)
}
return nil
},
}
cmd.Flags().IntVarP(&flagLimit, "limit", "L", 30, "Maximum number of approval decisions to list")
cmd.Flags().StringVar(&flagOrderBy, "order-by", "", "Order by field (CREATED_AT)")
cmd.Flags().StringVar(&flagOrderDir, "order-direction", "DESC", "Sort direction (ASC, DESC)")
flagOutput = cmdutil.AddOutputFlag(cmd)
return cmd
}

View File

@@ -0,0 +1,186 @@
// 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 listapprovalquorums
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const listQuery = `
query($id: ID!, $first: Int, $after: CursorKey, $orderBy: DocumentVersionApprovalQuorumOrder) {
node(id: $id) {
__typename
... on DocumentVersion {
approvalQuorums(first: $first, after: $after, orderBy: $orderBy) {
totalCount
edges {
node {
id
status
createdAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`
type approvalQuorum struct {
ID string `json:"id"`
Status string `json:"status"`
CreatedAt string `json:"createdAt"`
}
func NewCmdListApprovalQuorums(f *cmdutil.Factory) *cobra.Command {
var (
flagLimit int
flagOrderBy string
flagOrderDir string
flagOutput *string
)
cmd := &cobra.Command{
Use: "list-approval-quorums <document-version-id>",
Short: "List approval quorums for a document version",
Example: ` # List approval quorums for a document version
prb document list-approval-quorums <document-version-id>`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmdutil.ValidateOutputFlag(flagOutput); err != nil {
return err
}
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(),
)
variables := map[string]any{
"id": args[0],
}
if flagOrderBy != "" {
if err := cmdutil.ValidateEnum(
"order-by",
flagOrderBy,
[]string{"CREATED_AT"},
); err != nil {
return err
}
if err := cmdutil.ValidateEnum(
"order-direction",
flagOrderDir,
[]string{"ASC", "DESC"},
); err != nil {
return err
}
variables["orderBy"] = map[string]any{
"field": flagOrderBy,
"direction": flagOrderDir,
}
}
quorums, totalCount, err := api.Paginate(
client,
listQuery,
variables,
flagLimit,
func(data json.RawMessage) (*api.Connection[approvalQuorum], error) {
var resp struct {
Node *struct {
Typename string `json:"__typename"`
ApprovalQuorums api.Connection[approvalQuorum] `json:"approvalQuorums"`
} `json:"node"`
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, err
}
if resp.Node == nil {
return nil, fmt.Errorf("document version %s not found", args[0])
}
if resp.Node.Typename != "DocumentVersion" {
return nil, fmt.Errorf("expected DocumentVersion node, got %s", resp.Node.Typename)
}
return &resp.Node.ApprovalQuorums, nil
},
)
if err != nil {
return err
}
if *flagOutput == cmdutil.OutputJSON {
return cmdutil.PrintJSON(f.IOStreams.Out, quorums)
}
if len(quorums) == 0 {
_, _ = fmt.Fprintln(f.IOStreams.Out, "No approval quorums found.")
return nil
}
rows := make([][]string, 0, len(quorums))
for _, q := range quorums {
rows = append(rows, []string{
q.ID,
q.Status,
cmdutil.FormatTime(q.CreatedAt),
})
}
t := cmdutil.NewTable("ID", "STATUS", "CREATED AT").Rows(rows...)
_, _ = fmt.Fprintln(f.IOStreams.Out, t)
if totalCount > len(quorums) {
_, _ = fmt.Fprintf(
f.IOStreams.ErrOut,
"\nShowing %d of %d approval quorums\n",
len(quorums),
totalCount,
)
}
return nil
},
}
cmd.Flags().IntVarP(&flagLimit, "limit", "L", 30, "Maximum number of approval quorums to list")
cmd.Flags().StringVar(&flagOrderBy, "order-by", "", "Order by field (CREATED_AT)")
cmd.Flags().StringVar(&flagOrderDir, "order-direction", "DESC", "Sort direction (ASC, DESC)")
flagOutput = cmdutil.AddOutputFlag(cmd)
return cmd
}

View File

@@ -0,0 +1,155 @@
// 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 viewapprovaldecision
import (
"encoding/json"
"fmt"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const viewQuery = `
query($id: ID!) {
node(id: $id) {
__typename
... on DocumentVersionApprovalDecision {
id
quorum {
id
}
approver {
id
fullName
}
state
comment
decidedAt
createdAt
updatedAt
}
}
}
`
type viewResponse struct {
Node *struct {
Typename string `json:"__typename"`
ID string `json:"id"`
Quorum struct {
ID string `json:"id"`
} `json:"quorum"`
Approver struct {
ID string `json:"id"`
FullName string `json:"fullName"`
} `json:"approver"`
State string `json:"state"`
Comment *string `json:"comment"`
DecidedAt *string `json:"decidedAt"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
} `json:"node"`
}
func NewCmdViewApprovalDecision(f *cmdutil.Factory) *cobra.Command {
var flagOutput *string
cmd := &cobra.Command{
Use: "view-approval-decision <id>",
Short: "View an approval decision",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmdutil.ValidateOutputFlag(flagOutput); err != nil {
return err
}
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(),
)
data, err := client.Do(
viewQuery,
map[string]any{"id": args[0]},
)
if err != nil {
return err
}
var resp viewResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
if resp.Node == nil {
return fmt.Errorf("approval decision %s not found", args[0])
}
if resp.Node.Typename != "DocumentVersionApprovalDecision" {
return fmt.Errorf("expected DocumentVersionApprovalDecision node, got %s", resp.Node.Typename)
}
if *flagOutput == cmdutil.OutputJSON {
return cmdutil.PrintJSON(f.IOStreams.Out, resp.Node)
}
d := resp.Node
out := f.IOStreams.Out
bold := lipgloss.NewStyle().Bold(true)
label := lipgloss.NewStyle().Foreground(lipgloss.Color("242")).Width(22)
_, _ = fmt.Fprintf(out, "%s\n\n", bold.Render("Approval Decision"))
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("ID:"), d.ID)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Quorum ID:"), d.Quorum.ID)
_, _ = fmt.Fprintf(out, "%s%s (%s)\n", label.Render("Approver:"), d.Approver.FullName, d.Approver.ID)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("State:"), d.State)
if d.Comment != nil && *d.Comment != "" {
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Comment:"), *d.Comment)
}
_, _ = fmt.Fprintln(out)
if d.DecidedAt != nil {
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Decided:"), cmdutil.FormatTime(*d.DecidedAt))
}
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Created:"), cmdutil.FormatTime(d.CreatedAt))
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Updated:"), cmdutil.FormatTime(d.UpdatedAt))
return nil
},
}
flagOutput = cmdutil.AddOutputFlag(cmd)
return cmd
}

View File

@@ -0,0 +1,134 @@
// 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 viewapprovalquorum
import (
"encoding/json"
"fmt"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"go.probo.inc/probo/pkg/cli/api"
"go.probo.inc/probo/pkg/cmd/cmdutil"
)
const viewQuery = `
query($id: ID!) {
node(id: $id) {
__typename
... on DocumentVersionApprovalQuorum {
id
documentVersion {
id
}
status
createdAt
updatedAt
}
}
}
`
type viewResponse struct {
Node *struct {
Typename string `json:"__typename"`
ID string `json:"id"`
DocumentVersion struct {
ID string `json:"id"`
} `json:"documentVersion"`
Status string `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
} `json:"node"`
}
func NewCmdViewApprovalQuorum(f *cmdutil.Factory) *cobra.Command {
var flagOutput *string
cmd := &cobra.Command{
Use: "view-approval-quorum <id>",
Short: "View an approval quorum",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmdutil.ValidateOutputFlag(flagOutput); err != nil {
return err
}
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(),
)
data, err := client.Do(
viewQuery,
map[string]any{"id": args[0]},
)
if err != nil {
return err
}
var resp viewResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}
if resp.Node == nil {
return fmt.Errorf("approval quorum %s not found", args[0])
}
if resp.Node.Typename != "DocumentVersionApprovalQuorum" {
return fmt.Errorf("expected DocumentVersionApprovalQuorum node, got %s", resp.Node.Typename)
}
if *flagOutput == cmdutil.OutputJSON {
return cmdutil.PrintJSON(f.IOStreams.Out, resp.Node)
}
q := resp.Node
out := f.IOStreams.Out
bold := lipgloss.NewStyle().Bold(true)
label := lipgloss.NewStyle().Foreground(lipgloss.Color("242")).Width(22)
_, _ = fmt.Fprintf(out, "%s\n\n", bold.Render("Approval Quorum"))
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("ID:"), q.ID)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Version ID:"), q.DocumentVersion.ID)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Status:"), q.Status)
_, _ = fmt.Fprintln(out)
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Created:"), cmdutil.FormatTime(q.CreatedAt))
_, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Updated:"), cmdutil.FormatTime(q.UpdatedAt))
return nil
},
}
flagOutput = cmdutil.AddOutputFlag(cmd)
return cmd
}

View File

@@ -739,6 +739,29 @@ func (s *DocumentApprovalService) CountDecisions(
return count, nil
}
func (s *DocumentApprovalService) GetDecision(
ctx context.Context,
decisionID gid.GID,
) (*coredata.DocumentVersionApprovalDecision, error) {
decision := &coredata.DocumentVersionApprovalDecision{}
err := s.svc.pg.WithConn(
ctx,
func(ctx context.Context, conn pg.Querier) error {
if err := decision.LoadByID(ctx, conn, s.svc.scope, decisionID); err != nil {
return fmt.Errorf("cannot load approval decision: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return decision, nil
}
func (s *DocumentApprovalService) GetViewerDecision(
ctx context.Context,
documentVersionID gid.GID,

View File

@@ -4048,3 +4048,91 @@ func (r *Resolver) ListWebhookEventsTool(ctx context.Context, req *mcp.CallToolR
return nil, types.NewListWebhookEventsOutput(page), nil
}
func (r *Resolver) ListDocumentVersionApprovalQuorumsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDocumentVersionApprovalQuorumsInput) (*mcp.CallToolResult, types.ListDocumentVersionApprovalQuorumsOutput, error) {
r.MustAuthorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.DocumentVersionID)
pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{
Field: coredata.DocumentVersionApprovalQuorumOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
p, err := svc.DocumentApprovals.ListQuorums(ctx, input.DocumentVersionID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list approval quorums: %w", err))
}
return nil, types.NewListDocumentVersionApprovalQuorumsOutput(p), nil
}
func (r *Resolver) GetDocumentVersionApprovalQuorumTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetDocumentVersionApprovalQuorumInput) (*mcp.CallToolResult, types.GetDocumentVersionApprovalQuorumOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.ID)
quorum, err := svc.DocumentApprovals.GetQuorum(ctx, input.ID)
if err != nil {
panic(fmt.Errorf("cannot get approval quorum: %w", err))
}
return nil, types.GetDocumentVersionApprovalQuorumOutput{
ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum),
}, nil
}
func (r *Resolver) ListDocumentVersionApprovalDecisionsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDocumentVersionApprovalDecisionsInput) (*mcp.CallToolResult, types.ListDocumentVersionApprovalDecisionsOutput, error) {
r.MustAuthorize(ctx, input.QuorumID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.QuorumID)
pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{
Field: coredata.DocumentVersionApprovalDecisionOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
var states []coredata.DocumentVersionApprovalDecisionState
if input.Filter != nil {
states = input.Filter.States
}
filter := coredata.NewDocumentVersionApprovalDecisionFilter(states)
p, err := svc.DocumentApprovals.ListDecisions(ctx, input.QuorumID, cursor, filter)
if err != nil {
panic(fmt.Errorf("cannot list approval decisions: %w", err))
}
return nil, types.NewListDocumentVersionApprovalDecisionsOutput(p), nil
}
func (r *Resolver) GetDocumentVersionApprovalDecisionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetDocumentVersionApprovalDecisionInput) (*mcp.CallToolResult, types.GetDocumentVersionApprovalDecisionOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.ID)
decision, err := svc.DocumentApprovals.GetDecision(ctx, input.ID)
if err != nil {
panic(fmt.Errorf("cannot get approval decision: %w", err))
}
return nil, types.GetDocumentVersionApprovalDecisionOutput{
ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision),
}, nil
}

View File

@@ -82,6 +82,32 @@ components:
$ref: "#/components/schemas/OrderDirection"
description: Vendor order direction
DocumentVersionApprovalQuorumOrderBy:
type: object
required:
- field
- direction
properties:
field:
$ref: "#/components/schemas/DocumentVersionApprovalQuorumOrderField"
description: Order field
direction:
$ref: "#/components/schemas/OrderDirection"
description: Order direction
DocumentVersionApprovalDecisionOrderBy:
type: object
required:
- field
- direction
properties:
field:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionOrderField"
description: Order field
direction:
$ref: "#/components/schemas/OrderDirection"
description: Order direction
ProfileOrderBy:
type: object
required:
@@ -5164,6 +5190,36 @@ components:
- PUBLISHED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionStatus
DocumentVersionApprovalQuorumStatus:
type: string
enum:
- PENDING
- APPROVED
- REJECTED
- VOIDED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatus
DocumentVersionApprovalQuorumOrderField:
type: string
enum:
- CREATED_AT
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumOrderField
DocumentVersionApprovalDecisionState:
type: string
enum:
- PENDING
- APPROVED
- REJECTED
- VOIDED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionState
DocumentVersionApprovalDecisionOrderField:
type: string
enum:
- CREATED_AT
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionOrderField
DocumentStatus:
type: string
enum:
@@ -5362,6 +5418,83 @@ components:
format: date-time
description: Update timestamp
DocumentVersionApprovalQuorum:
type: object
required:
- id
- organization_id
- version_id
- status
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
version_id:
$ref: "#/components/schemas/GID"
description: Document version ID
status:
$ref: "#/components/schemas/DocumentVersionApprovalQuorumStatus"
description: Approval quorum status
created_at:
type: string
format: date-time
description: Creation timestamp
updated_at:
type: string
format: date-time
description: Update timestamp
DocumentVersionApprovalDecision:
type: object
required:
- id
- organization_id
- quorum_id
- approver_id
- state
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval decision ID
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
quorum_id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
approver_id:
$ref: "#/components/schemas/GID"
description: Approver profile ID
state:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionState"
description: Decision state
comment:
type:
- string
- "null"
description: Approval or rejection comment
decided_at:
type:
- string
- "null"
format: date-time
description: Decision timestamp
created_at:
type: string
format: date-time
description: Creation timestamp
updated_at:
type: string
format: date-time
description: Update timestamp
DocumentVersionSignature:
type: object
required:
@@ -5876,6 +6009,110 @@ components:
document_version:
$ref: "#/components/schemas/DocumentVersion"
ListDocumentVersionApprovalQuorumsInput:
type: object
required:
- document_version_id
properties:
document_version_id:
$ref: "#/components/schemas/GID"
description: Document version ID
order_by:
$ref: "#/components/schemas/DocumentVersionApprovalQuorumOrderBy"
description: Order by
size:
type: integer
description: Page size
cursor:
$ref: "#/components/schemas/CursorKey"
description: Page cursor
ListDocumentVersionApprovalQuorumsOutput:
type: object
required:
- approval_quorums
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
description: Next cursor
approval_quorums:
type: array
items:
$ref: "#/components/schemas/DocumentVersionApprovalQuorum"
GetDocumentVersionApprovalQuorumInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
GetDocumentVersionApprovalQuorumOutput:
type: object
required:
- approval_quorum
properties:
approval_quorum:
$ref: "#/components/schemas/DocumentVersionApprovalQuorum"
ListDocumentVersionApprovalDecisionsInput:
type: object
required:
- quorum_id
properties:
quorum_id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
order_by:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionOrderBy"
description: Order by
size:
type: integer
description: Page size
cursor:
$ref: "#/components/schemas/CursorKey"
description: Page cursor
filter:
type: object
properties:
states:
type: array
items:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionState"
description: Filter by decision states
ListDocumentVersionApprovalDecisionsOutput:
type: object
required:
- approval_decisions
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
description: Next cursor
approval_decisions:
type: array
items:
$ref: "#/components/schemas/DocumentVersionApprovalDecision"
GetDocumentVersionApprovalDecisionInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval decision ID
GetDocumentVersionApprovalDecisionOutput:
type: object
required:
- approval_decision
properties:
approval_decision:
$ref: "#/components/schemas/DocumentVersionApprovalDecision"
SendSigningNotificationsInput:
type: object
required:
@@ -9175,3 +9412,39 @@ tools:
$ref: "#/components/schemas/ListWebhookEventsInput"
outputSchema:
$ref: "#/components/schemas/ListWebhookEventsOutput"
- name: listDocumentVersionApprovalQuorums
description: List approval quorums for a document version
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalQuorumsInput"
outputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalQuorumsOutput"
- name: getDocumentVersionApprovalQuorum
description: Get a document version approval quorum by ID
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalQuorumInput"
outputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalQuorumOutput"
- name: listDocumentVersionApprovalDecisions
description: List approval decisions for an approval quorum
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalDecisionsInput"
outputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalDecisionsOutput"
- name: getDocumentVersionApprovalDecision
description: Get a document version approval decision by ID
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalDecisionInput"
outputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalDecisionOutput"

View File

@@ -190,3 +190,64 @@ func NewListDocumentVersionSignaturesOutput(signaturePage *page.Page[*coredata.D
DocumentVersionSignatures: signatures,
}
}
func NewDocumentVersionApprovalQuorum(q *coredata.DocumentVersionApprovalQuorum) *DocumentVersionApprovalQuorum {
return &DocumentVersionApprovalQuorum{
ID: q.ID,
OrganizationID: q.OrganizationID,
VersionID: q.VersionID,
Status: q.Status,
CreatedAt: q.CreatedAt,
UpdatedAt: q.UpdatedAt,
}
}
func NewListDocumentVersionApprovalQuorumsOutput(quorumPage *page.Page[*coredata.DocumentVersionApprovalQuorum, coredata.DocumentVersionApprovalQuorumOrderField]) ListDocumentVersionApprovalQuorumsOutput {
quorums := make([]*DocumentVersionApprovalQuorum, 0, len(quorumPage.Data))
for _, q := range quorumPage.Data {
quorums = append(quorums, NewDocumentVersionApprovalQuorum(q))
}
var nextCursor *page.CursorKey
if len(quorumPage.Data) > 0 {
cursorKey := quorumPage.Data[len(quorumPage.Data)-1].CursorKey(quorumPage.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListDocumentVersionApprovalQuorumsOutput{
NextCursor: nextCursor,
ApprovalQuorums: quorums,
}
}
func NewDocumentVersionApprovalDecision(d *coredata.DocumentVersionApprovalDecision) *DocumentVersionApprovalDecision {
return &DocumentVersionApprovalDecision{
ID: d.ID,
OrganizationID: d.OrganizationID,
QuorumID: d.QuorumID,
ApproverID: d.ApproverID,
State: d.State,
Comment: d.Comment,
DecidedAt: d.DecidedAt,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
}
}
func NewListDocumentVersionApprovalDecisionsOutput(decisionPage *page.Page[*coredata.DocumentVersionApprovalDecision, coredata.DocumentVersionApprovalDecisionOrderField]) ListDocumentVersionApprovalDecisionsOutput {
decisions := make([]*DocumentVersionApprovalDecision, 0, len(decisionPage.Data))
for _, d := range decisionPage.Data {
decisions = append(decisions, NewDocumentVersionApprovalDecision(d))
}
var nextCursor *page.CursorKey
if len(decisionPage.Data) > 0 {
cursorKey := decisionPage.Data[len(decisionPage.Data)-1].CursorKey(decisionPage.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListDocumentVersionApprovalDecisionsOutput{
NextCursor: nextCursor,
ApprovalDecisions: decisions,
}
}