diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts b/packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts index a2a01e4bb..dddaca88f 100644 --- a/packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts @@ -156,7 +156,7 @@ export async function execute( const input: Record = { organizationId, name, - date, + date: new Date(date).toISOString(), }; if (attendeeIds && attendeeIds.length > 0) { input.attendeeIds = attendeeIds; diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts index 629780105..5cc3a4520 100644 --- a/packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts @@ -151,7 +151,7 @@ export async function execute( const input: Record = { meetingId }; if (name) input.name = name; - if (date) input.date = date; + if (date) input.date = new Date(date).toISOString(); if (attendeeIds && attendeeIds.length > 0) { input.attendeeIds = attendeeIds; } diff --git a/packages/n8n-node/nodes/Probo/actions/people/create.operation.ts b/packages/n8n-node/nodes/Probo/actions/people/create.operation.ts index c8509673b..4c2e760e3 100644 --- a/packages/n8n-node/nodes/Probo/actions/people/create.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/people/create.operation.ts @@ -161,8 +161,8 @@ export async function execute( input.additionalEmailAddresses = additionalFields.additionalEmailAddresses.split(',').map((e) => e.trim()).filter(Boolean); } if (additionalFields.position) input.position = additionalFields.position; - if (additionalFields.contractStartDate) input.contractStartDate = additionalFields.contractStartDate; - if (additionalFields.contractEndDate) input.contractEndDate = additionalFields.contractEndDate; + if (additionalFields.contractStartDate) input.contractStartDate = new Date(additionalFields.contractStartDate).toISOString(); + if (additionalFields.contractEndDate) input.contractEndDate = new Date(additionalFields.contractEndDate).toISOString(); const responseData = await proboApiRequest.call(this, query, { input }); diff --git a/packages/n8n-node/nodes/Probo/actions/people/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/people/update.operation.ts index a9d4c642f..1be3bd5c9 100644 --- a/packages/n8n-node/nodes/Probo/actions/people/update.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/people/update.operation.ts @@ -158,8 +158,8 @@ export async function execute( } } if (additionalFields.position !== undefined) input.position = additionalFields.position === '' ? null : additionalFields.position; - if (additionalFields.contractStartDate !== undefined) input.contractStartDate = additionalFields.contractStartDate === '' ? null : additionalFields.contractStartDate; - if (additionalFields.contractEndDate !== undefined) input.contractEndDate = additionalFields.contractEndDate === '' ? null : additionalFields.contractEndDate; + if (additionalFields.contractStartDate !== undefined) input.contractStartDate = additionalFields.contractStartDate === '' ? null : new Date(additionalFields.contractStartDate).toISOString(); + if (additionalFields.contractEndDate !== undefined) input.contractEndDate = additionalFields.contractEndDate === '' ? null : new Date(additionalFields.contractEndDate).toISOString(); const responseData = await proboApiRequest.call(this, query, { input }); diff --git a/packages/n8n-node/nodes/Probo/actions/vendor/createRiskAssessment.operation.ts b/packages/n8n-node/nodes/Probo/actions/vendor/createRiskAssessment.operation.ts new file mode 100644 index 000000000..943fdd495 --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/vendor/createRiskAssessment.operation.ts @@ -0,0 +1,137 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboApiRequest } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Vendor ID', + name: 'vendorId', + type: 'string', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['createRiskAssessment'], + }, + }, + default: '', + description: 'The ID of the vendor', + required: true, + }, + { + displayName: 'Expires At', + name: 'expiresAt', + type: 'dateTime', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['createRiskAssessment'], + }, + }, + default: '', + description: 'The expiration date of the risk assessment', + required: true, + }, + { + displayName: 'Data Sensitivity', + name: 'dataSensitivity', + type: 'options', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['createRiskAssessment'], + }, + }, + options: [ + { name: 'Critical', value: 'CRITICAL' }, + { name: 'High', value: 'HIGH' }, + { name: 'Low', value: 'LOW' }, + { name: 'Medium', value: 'MEDIUM' }, + { name: 'None', value: 'NONE' }, + ], + default: 'LOW', + description: 'The data sensitivity level', + required: true, + }, + { + displayName: 'Business Impact', + name: 'businessImpact', + type: 'options', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['createRiskAssessment'], + }, + }, + options: [ + { name: 'Critical', value: 'CRITICAL' }, + { name: 'High', value: 'HIGH' }, + { name: 'Low', value: 'LOW' }, + { name: 'Medium', value: 'MEDIUM' }, + ], + default: 'LOW', + description: 'The business impact level', + required: true, + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + rows: 4, + }, + displayOptions: { + show: { + resource: ['vendor'], + operation: ['createRiskAssessment'], + }, + }, + default: '', + description: 'Additional notes for the risk assessment', + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const vendorId = this.getNodeParameter('vendorId', itemIndex) as string; + const expiresAtRaw = this.getNodeParameter('expiresAt', itemIndex) as string; + const dataSensitivity = this.getNodeParameter('dataSensitivity', itemIndex) as string; + const businessImpact = this.getNodeParameter('businessImpact', itemIndex) as string; + const notes = this.getNodeParameter('notes', itemIndex, '') as string; + + // Ensure expiresAt is in RFC3339 format + const expiresAt = new Date(expiresAtRaw).toISOString(); + + const query = ` + mutation CreateVendorRiskAssessment($input: CreateVendorRiskAssessmentInput!) { + createVendorRiskAssessment(input: $input) { + vendorRiskAssessmentEdge { + node { + id + expiresAt + dataSensitivity + businessImpact + notes + createdAt + updatedAt + } + } + } + } + `; + + const input: Record = { + vendorId, + expiresAt, + dataSensitivity, + businessImpact, + }; + if (notes) input.notes = notes; + + const responseData = await proboApiRequest.call(this, query, { input }); + + return { + json: responseData, + pairedItem: { item: itemIndex }, + }; +} diff --git a/packages/n8n-node/nodes/Probo/actions/vendor/getAllRiskAssessments.operation.ts b/packages/n8n-node/nodes/Probo/actions/vendor/getAllRiskAssessments.operation.ts new file mode 100644 index 000000000..7f6a18221 --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/vendor/getAllRiskAssessments.operation.ts @@ -0,0 +1,135 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow'; +import { proboApiRequestAllItems } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Vendor ID', + name: 'vendorId', + type: 'string', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['getAllRiskAssessments'], + }, + }, + default: '', + description: 'The ID of the vendor', + required: true, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['getAllRiskAssessments'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['getAllRiskAssessments'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'Max number of results to return', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: ['vendor'], + operation: ['getAllRiskAssessments'], + }, + }, + options: [ + { + displayName: 'Include Vendor', + name: 'includeVendor', + type: 'boolean', + default: false, + description: 'Whether to include vendor in the response', + }, + ], + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const vendorId = this.getNodeParameter('vendorId', itemIndex) as string; + const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean; + const limit = this.getNodeParameter('limit', itemIndex, 50) as number; + const options = this.getNodeParameter('options', itemIndex, {}) as { + includeVendor?: boolean; + }; + + const vendorFragment = options.includeVendor + ? `vendor { + id + name + }` + : ''; + + const query = ` + query GetVendorRiskAssessments($vendorId: ID!, $first: Int, $after: CursorKey) { + node(id: $vendorId) { + ... on Vendor { + riskAssessments(first: $first, after: $after) { + edges { + node { + id + expiresAt + dataSensitivity + businessImpact + notes + ${vendorFragment} + createdAt + updatedAt + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + } + `; + + const vendorRiskAssessments = await proboApiRequestAllItems.call( + this, + query, + { vendorId }, + (response) => { + const data = response?.data as IDataObject | undefined; + const node = data?.node as IDataObject | undefined; + return node?.riskAssessments as IDataObject | undefined; + }, + returnAll, + limit, + ); + + return { + json: { vendorRiskAssessments }, + pairedItem: { item: itemIndex }, + }; +} diff --git a/packages/n8n-node/nodes/Probo/actions/vendor/getRiskAssessment.operation.ts b/packages/n8n-node/nodes/Probo/actions/vendor/getRiskAssessment.operation.ts new file mode 100644 index 000000000..17afede51 --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/vendor/getRiskAssessment.operation.ts @@ -0,0 +1,86 @@ +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboApiRequest } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Vendor Risk Assessment ID', + name: 'vendorRiskAssessmentId', + type: 'string', + displayOptions: { + show: { + resource: ['vendor'], + operation: ['getRiskAssessment'], + }, + }, + default: '', + description: 'The ID of the vendor risk assessment', + required: true, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: ['vendor'], + operation: ['getRiskAssessment'], + }, + }, + options: [ + { + displayName: 'Include Vendor', + name: 'includeVendor', + type: 'boolean', + default: false, + description: 'Whether to include vendor in the response', + }, + ], + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const vendorRiskAssessmentId = this.getNodeParameter('vendorRiskAssessmentId', itemIndex) as string; + const options = this.getNodeParameter('options', itemIndex, {}) as { + includeVendor?: boolean; + }; + + const vendorFragment = options.includeVendor + ? `vendor { + id + name + }` + : ''; + + const query = ` + query GetVendorRiskAssessment($vendorRiskAssessmentId: ID!) { + node(id: $vendorRiskAssessmentId) { + ... on VendorRiskAssessment { + id + expiresAt + dataSensitivity + businessImpact + notes + ${vendorFragment} + createdAt + updatedAt + } + } + } + `; + + const variables = { + vendorRiskAssessmentId, + }; + + const responseData = await proboApiRequest.call(this, query, variables); + + return { + json: responseData, + pairedItem: { item: itemIndex }, + }; +} diff --git a/packages/n8n-node/nodes/Probo/actions/vendor/index.ts b/packages/n8n-node/nodes/Probo/actions/vendor/index.ts index 649055f2c..ea9080fba 100644 --- a/packages/n8n-node/nodes/Probo/actions/vendor/index.ts +++ b/packages/n8n-node/nodes/Probo/actions/vendor/index.ts @@ -9,6 +9,9 @@ import * as updateServiceOp from './updateService.operation'; import * as deleteServiceOp from './deleteService.operation'; import * as getServiceOp from './getService.operation'; import * as getAllServicesOp from './getAllServices.operation'; +import * as createRiskAssessmentOp from './createRiskAssessment.operation'; +import * as getRiskAssessmentOp from './getRiskAssessment.operation'; +import * as getAllRiskAssessmentsOp from './getAllRiskAssessments.operation'; export const description: INodeProperties[] = [ { @@ -28,6 +31,12 @@ export const description: INodeProperties[] = [ description: 'Create a new vendor', action: 'Create a vendor', }, + { + name: 'Create Risk Assessment', + value: 'createRiskAssessment', + description: 'Create a new vendor risk assessment', + action: 'Create a vendor risk assessment', + }, { name: 'Create Service', value: 'createService', @@ -58,12 +67,24 @@ export const description: INodeProperties[] = [ description: 'Get many vendors', action: 'Get many vendors', }, + { + name: 'Get Many Risk Assessments', + value: 'getAllRiskAssessments', + description: 'Get many vendor risk assessments', + action: 'Get many vendor risk assessments', + }, { name: 'Get Many Services', value: 'getAllServices', description: 'Get many vendor services', action: 'Get many vendor services', }, + { + name: 'Get Risk Assessment', + value: 'getRiskAssessment', + description: 'Get a vendor risk assessment', + action: 'Get a vendor risk assessment', + }, { name: 'Get Service', value: 'getService', @@ -95,6 +116,9 @@ export const description: INodeProperties[] = [ ...deleteServiceOp.description, ...getServiceOp.description, ...getAllServicesOp.description, + ...createRiskAssessmentOp.description, + ...getRiskAssessmentOp.description, + ...getAllRiskAssessmentsOp.description, ]; export { @@ -108,4 +132,7 @@ export { deleteServiceOp as deleteService, getServiceOp as getService, getAllServicesOp as getAllServices, + createRiskAssessmentOp as createRiskAssessment, + getRiskAssessmentOp as getRiskAssessment, + getAllRiskAssessmentsOp as getAllRiskAssessments, }; diff --git a/pkg/authz/permissions.go b/pkg/authz/permissions.go index 542ff2a34..cf8f6efd4 100644 --- a/pkg/authz/permissions.go +++ b/pkg/authz/permissions.go @@ -461,7 +461,8 @@ var Permissions = map[uint16]map[Action][]Role{ ActionDeleteVendorDataPrivacyAgreement: EditRoles, }, coredata.VendorRiskAssessmentEntityType: { - ActionGet: NonEmployeeRoles, + ActionGet: NonEmployeeRoles, + ActionGetVendor: NonEmployeeRoles, }, coredata.FrameworkEntityType: { ActionGet: NonEmployeeRoles, diff --git a/pkg/probo/vendor_service.go b/pkg/probo/vendor_service.go index 1e0d7e3c8..e67bdacc2 100644 --- a/pkg/probo/vendor_service.go +++ b/pkg/probo/vendor_service.go @@ -683,6 +683,35 @@ func (s VendorService) GetRiskAssessment( return vendorRiskAssessment, nil } +func (s VendorService) GetByRiskAssessmentID( + ctx context.Context, + vendorRiskAssessmentID gid.GID, +) (*coredata.Vendor, error) { + vendor := &coredata.Vendor{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + vendorRiskAssessment := &coredata.VendorRiskAssessment{} + if err := vendorRiskAssessment.LoadByID(ctx, conn, s.svc.scope, vendorRiskAssessmentID); err != nil { + return fmt.Errorf("cannot load vendor risk assessment: %w", err) + } + + if err := vendor.LoadByID(ctx, conn, s.svc.scope, vendorRiskAssessment.VendorID); err != nil { + return fmt.Errorf("cannot load vendor: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, err + } + + return vendor, nil +} + func (s VendorService) Assess( ctx context.Context, req AssessVendorRequest, diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 74ad5457c..c41a3a22b 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -7105,7 +7105,7 @@ func (r *vendorRiskAssessmentResolver) Vendor(ctx context.Context, obj *types.Ve prb := r.ProboService(ctx, obj.ID.TenantID()) - vendor, err := prb.Vendors.Get(ctx, obj.ID) + vendor, err := prb.Vendors.GetByRiskAssessmentID(ctx, obj.ID) if err != nil { var errNotFound *coredata.ErrVendorNotFound if errors.As(err, &errNotFound) {