From 0d94dd8fc62ef2a634d2ebc3ca902d4ee9da5390 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Tue, 2 Dec 2025 10:43:06 +0100 Subject: [PATCH] Add control operations Signed-off-by: Bryan Frimin --- packages/n8n-node/nodes/Probo/Probo.node.ts | 5 + .../Probo/actions/control/create.operation.ts | 147 ++++++++++++++++++ .../Probo/actions/control/delete.operation.ts | 42 +++++ .../Probo/actions/control/get.operation.ts | 55 +++++++ .../Probo/actions/control/getAll.operation.ts | 100 ++++++++++++ .../nodes/Probo/actions/control/index.ts | 61 ++++++++ .../Probo/actions/control/update.operation.ts | 138 ++++++++++++++++ .../n8n-node/nodes/Probo/actions/index.ts | 2 + 8 files changed, 550 insertions(+) create mode 100644 packages/n8n-node/nodes/Probo/actions/control/create.operation.ts create mode 100644 packages/n8n-node/nodes/Probo/actions/control/delete.operation.ts create mode 100644 packages/n8n-node/nodes/Probo/actions/control/get.operation.ts create mode 100644 packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts create mode 100644 packages/n8n-node/nodes/Probo/actions/control/index.ts create mode 100644 packages/n8n-node/nodes/Probo/actions/control/update.operation.ts diff --git a/packages/n8n-node/nodes/Probo/Probo.node.ts b/packages/n8n-node/nodes/Probo/Probo.node.ts index 341746808..67377704c 100644 --- a/packages/n8n-node/nodes/Probo/Probo.node.ts +++ b/packages/n8n-node/nodes/Probo/Probo.node.ts @@ -62,6 +62,11 @@ export class Probo implements INodeType { type: 'options', noDataExpression: true, options: [ + { + name: 'Control', + value: 'control', + description: 'Manage controls', + }, { name: 'Execute', value: 'execute', diff --git a/packages/n8n-node/nodes/Probo/actions/control/create.operation.ts b/packages/n8n-node/nodes/Probo/actions/control/create.operation.ts new file mode 100644 index 000000000..2ff335bd3 --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/control/create.operation.ts @@ -0,0 +1,147 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboApiRequest } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Framework ID', + name: 'frameworkId', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['create'], + }, + }, + default: '', + description: 'The ID of the framework', + required: true, + }, + { + displayName: 'Section Title', + name: 'sectionTitle', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['create'], + }, + }, + default: '', + description: 'The section title of the control', + required: true, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['create'], + }, + }, + default: '', + description: 'The name of the control', + required: true, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + displayOptions: { + show: { + resource: ['control'], + operation: ['create'], + }, + }, + options: [ + { + name: 'Included', + value: 'INCLUDED', + }, + { + name: 'Excluded', + value: 'EXCLUDED', + }, + ], + default: 'INCLUDED', + description: 'The status of the control', + required: true, + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['create'], + }, + }, + default: '', + description: 'The description of the control', + }, + { + displayName: 'Exclusion Justification', + name: 'exclusionJustification', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['create'], + }, + }, + default: '', + description: 'The justification for excluding the control', + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const frameworkId = this.getNodeParameter('frameworkId', itemIndex) as string; + const sectionTitle = this.getNodeParameter('sectionTitle', itemIndex) as string; + const name = this.getNodeParameter('name', itemIndex) as string; + const status = this.getNodeParameter('status', itemIndex) as string; + const description = this.getNodeParameter('description', itemIndex, '') as string; + const exclusionJustification = this.getNodeParameter('exclusionJustification', itemIndex, '') as string; + + const query = ` + mutation CreateControl($input: CreateControlInput!) { + createControl(input: $input) { + controlEdge { + node { + id + sectionTitle + name + description + status + exclusionJustification + createdAt + updatedAt + } + } + } + } + `; + + const variables = { + input: { + frameworkId, + sectionTitle, + name, + status, + ...(description && { description }), + ...(exclusionJustification && { exclusionJustification }), + }, + }; + + const responseData = await proboApiRequest.call(this, query, variables); + + return { + json: responseData, + pairedItem: { item: itemIndex }, + }; +} + diff --git a/packages/n8n-node/nodes/Probo/actions/control/delete.operation.ts b/packages/n8n-node/nodes/Probo/actions/control/delete.operation.ts new file mode 100644 index 000000000..1661fbb4b --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/control/delete.operation.ts @@ -0,0 +1,42 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboApiRequest } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Control ID', + name: 'controlId', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['delete'], + }, + }, + default: '', + description: 'The ID of the control to delete', + required: true, + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const controlId = this.getNodeParameter('controlId', itemIndex) as string; + + const query = ` + mutation DeleteControl($input: DeleteControlInput!) { + deleteControl(input: $input) { + deletedControlId + } + } + `; + + const responseData = await proboApiRequest.call(this, query, { input: { controlId } }); + + return { + json: responseData, + pairedItem: { item: itemIndex }, + }; +} + diff --git a/packages/n8n-node/nodes/Probo/actions/control/get.operation.ts b/packages/n8n-node/nodes/Probo/actions/control/get.operation.ts new file mode 100644 index 000000000..1f4eb499c --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/control/get.operation.ts @@ -0,0 +1,55 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboApiRequest } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Control ID', + name: 'controlId', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['get'], + }, + }, + default: '', + description: 'The ID of the control', + required: true, + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const controlId = this.getNodeParameter('controlId', itemIndex) as string; + + const query = ` + query GetControl($controlId: ID!) { + node(id: $controlId) { + ... on Control { + id + sectionTitle + name + description + status + exclusionJustification + createdAt + updatedAt + } + } + } + `; + + const variables = { + controlId, + }; + + const responseData = await proboApiRequest.call(this, query, variables); + + return { + json: responseData, + pairedItem: { item: itemIndex }, + }; +} + diff --git a/packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts b/packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts new file mode 100644 index 000000000..75c17dd65 --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/control/getAll.operation.ts @@ -0,0 +1,100 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboApiRequestAllItems } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Framework ID', + name: 'frameworkId', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['getAll'], + }, + }, + default: '', + description: 'The ID of the framework', + required: true, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: ['control'], + operation: ['getAll'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: ['control'], + operation: ['getAll'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'Max number of results to return', + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const frameworkId = this.getNodeParameter('frameworkId', itemIndex) as string; + const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean; + const limit = this.getNodeParameter('limit', itemIndex, 50) as number; + + const query = ` + query GetControls($frameworkId: ID!, $first: Int, $after: CursorKey) { + node(id: $frameworkId) { + ... on Framework { + controls(first: $first, after: $after) { + edges { + node { + id + sectionTitle + name + description + status + exclusionJustification + createdAt + updatedAt + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + } + `; + + const controls = await proboApiRequestAllItems.call( + this, + query, + { frameworkId }, + (response) => response?.data?.node?.controls, + returnAll, + limit, + ); + + return { + json: { controls }, + pairedItem: { item: itemIndex }, + }; +} + diff --git a/packages/n8n-node/nodes/Probo/actions/control/index.ts b/packages/n8n-node/nodes/Probo/actions/control/index.ts new file mode 100644 index 000000000..cf08f03f7 --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/control/index.ts @@ -0,0 +1,61 @@ +import type { INodeProperties } from 'n8n-workflow'; +import * as createOp from './create.operation'; +import * as updateOp from './update.operation'; +import * as deleteOp from './delete.operation'; +import * as getOp from './get.operation'; +import * as getAllOp from './getAll.operation'; + +export const description: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['control'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new control', + action: 'Create a control', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a control', + action: 'Delete a control', + }, + { + name: 'Get', + value: 'get', + description: 'Get a control', + action: 'Get a control', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all controls', + action: 'Get all controls', + }, + { + name: 'Update', + value: 'update', + description: 'Update an existing control', + action: 'Update a control', + }, + ], + default: 'create', + }, + ...createOp.description, + ...updateOp.description, + ...deleteOp.description, + ...getOp.description, + ...getAllOp.description, +]; + +export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll }; + diff --git a/packages/n8n-node/nodes/Probo/actions/control/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/control/update.operation.ts new file mode 100644 index 000000000..2b91822f0 --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/control/update.operation.ts @@ -0,0 +1,138 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboApiRequest } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Control ID', + name: 'id', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['update'], + }, + }, + default: '', + description: 'The ID of the control to update', + required: true, + }, + { + displayName: 'Section Title', + name: 'sectionTitle', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['update'], + }, + }, + default: '', + description: 'The section title of the control', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['update'], + }, + }, + default: '', + description: 'The name of the control', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['update'], + }, + }, + default: '', + description: 'The description of the control', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + displayOptions: { + show: { + resource: ['control'], + operation: ['update'], + }, + }, + options: [ + { + name: 'Included', + value: 'INCLUDED', + }, + { + name: 'Excluded', + value: 'EXCLUDED', + }, + ], + default: '', + description: 'The status of the control', + }, + { + displayName: 'Exclusion Justification', + name: 'exclusionJustification', + type: 'string', + displayOptions: { + show: { + resource: ['control'], + operation: ['update'], + }, + }, + default: '', + description: 'The justification for excluding the control', + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const id = this.getNodeParameter('id', itemIndex) as string; + const sectionTitle = this.getNodeParameter('sectionTitle', itemIndex, '') as string; + const name = this.getNodeParameter('name', itemIndex, '') as string; + const description = this.getNodeParameter('description', itemIndex, '') as string; + const status = this.getNodeParameter('status', itemIndex, '') as string; + const exclusionJustification = this.getNodeParameter('exclusionJustification', itemIndex, '') as string; + + const query = ` + mutation UpdateControl($input: UpdateControlInput!) { + updateControl(input: $input) { + control { + id + sectionTitle + name + description + status + exclusionJustification + createdAt + updatedAt + } + } + } + `; + + const input: Record = { id }; + if (sectionTitle) input.sectionTitle = sectionTitle; + if (name) input.name = name; + if (description) input.description = description; + if (status) input.status = status; + if (exclusionJustification) input.exclusionJustification = exclusionJustification; + + const responseData = await proboApiRequest.call(this, query, { input }); + + return { + json: responseData, + pairedItem: { item: itemIndex }, + }; +} + diff --git a/packages/n8n-node/nodes/Probo/actions/index.ts b/packages/n8n-node/nodes/Probo/actions/index.ts index 350d1b9dd..0b4e2df42 100644 --- a/packages/n8n-node/nodes/Probo/actions/index.ts +++ b/packages/n8n-node/nodes/Probo/actions/index.ts @@ -1,4 +1,5 @@ import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow'; +import * as control from './control'; import * as execute from './execute'; import * as framework from './framework'; import * as measure from './measure'; @@ -14,6 +15,7 @@ export interface OperationModule { } export const resources: Record = { + control: control as ResourceModule, execute: execute as ResourceModule, framework: framework as ResourceModule, measure: measure as ResourceModule,