diff --git a/plugins/orchestrator-backend/dist-dynamic/package.json b/plugins/orchestrator-backend/dist-dynamic/package.json index 168b0d020d..c5353be5bc 100644 --- a/plugins/orchestrator-backend/dist-dynamic/package.json +++ b/plugins/orchestrator-backend/dist-dynamic/package.json @@ -62,6 +62,7 @@ "openapi-types": "^12.1.3", "winston": "^3.11.0", "yn": "^5.0.0", + "moment": "^2.29.4", "js-yaml": "^4.1.0" }, "devDependencies": {}, diff --git a/plugins/orchestrator-backend/dist-dynamic/yarn.lock b/plugins/orchestrator-backend/dist-dynamic/yarn.lock index 98513b0ced..7104c6220c 100644 --- a/plugins/orchestrator-backend/dist-dynamic/yarn.lock +++ b/plugins/orchestrator-backend/dist-dynamic/yarn.lock @@ -794,6 +794,11 @@ mock-json-schema@^1.0.7: dependencies: lodash "^4.17.21" +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" diff --git a/plugins/orchestrator-backend/package.json b/plugins/orchestrator-backend/package.json index 1ae4e0ed4c..e9d22d1dcb 100644 --- a/plugins/orchestrator-backend/package.json +++ b/plugins/orchestrator-backend/package.json @@ -85,13 +85,15 @@ "openapi-backend": "^5.10.5", "openapi-types": "^12.1.3", "winston": "^3.11.0", - "yn": "^5.0.0" + "yn": "^5.0.0", + "moment": "^2.29.4" }, "devDependencies": { "@backstage/cli": "0.23.0", "@janus-idp/cli": "1.7.1", "@types/express": "4.17.20", "@types/fs-extra": "^11.0.1", - "@types/json-schema": "^7.0.12" + "@types/json-schema": "^7.0.12", + "@backstage/backend-test-utils": "^0.2.10" } } diff --git a/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.test.ts b/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.test.ts new file mode 100644 index 0000000000..d9a49d2ccb --- /dev/null +++ b/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.test.ts @@ -0,0 +1,74 @@ +import { + WorkflowOverview, + WorkflowSpecFile, +} from '@janus-idp/backstage-plugin-orchestrator-common'; + +import { + fakeOpenAPIV3Document, + generateTestWorkflowOverview, + generateTestWorkflowSpecs, +} from '../test-utils'; +import { + mapToWorkflowOverviewDTO, + mapToWorkflowSpecFileDTO, + mapWorkflowCategoryDTOFromString, +} from './V2Mappings'; + +describe('scenarios to verify mapToWorkflowOverviewDTO', () => { + it('correctly maps WorkflowOverview', () => { + // Arrange + const overview: WorkflowOverview = generateTestWorkflowOverview({ + category: 'assessment', + }); + + // Act + const result = mapToWorkflowOverviewDTO(overview); + + // Assert + expect(result.workflowId).toBe(overview.workflowId); + expect(result.name).toBe(overview.name); + expect(result.uri).toBe(overview.uri); + expect(result.lastTriggeredMs).toBe(overview.lastTriggeredMs); + expect(result.lastRunStatus).toBe(overview.lastRunStatus); + expect(result.category).toBe('assessment'); + expect(result.avgDurationMs).toBe(overview.avgDurationMs); + expect(result.description).toBe(overview.description); + expect(Object.keys(result).length).toBe(8); + }); +}); +describe('scenarios to verify mapWorkflowCategoryDTOFromString', () => { + test.each([ + { input: 'assessment', expected: 'assessment' }, + { input: 'infrastructure', expected: 'infrastructure' }, + { input: 'random category', expected: 'infrastructure' }, + ])('mapWorkflowCategoryDTOFromString($input)', ({ input, expected }) => { + // Arrange + const overview: WorkflowOverview = generateTestWorkflowOverview({ + category: input, + }); + + // Act + const resultCategory = mapWorkflowCategoryDTOFromString(overview.category); + + // Assert + expect(resultCategory).toBeDefined(); + expect(resultCategory).toBe(expected); + }); +}); + +describe('scenarios to verify mapToWorkflowSpecFileDTO', () => { + it('correctly maps WorkflowSpecFile', () => { + // Arrange + const specV1: WorkflowSpecFile[] = generateTestWorkflowSpecs(1); + + // Act + const result = mapToWorkflowSpecFileDTO(specV1[0]); + + // Assert + expect(result.path).toBeDefined(); + expect(result.path).toEqual('/test/path/openapi_0.json'); + expect(result.content).toBeDefined(); + expect(JSON.parse(result.content)).toEqual(fakeOpenAPIV3Document()); + expect(Object.keys(result).length).toBe(2); + }); +}); diff --git a/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.ts b/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.ts new file mode 100644 index 0000000000..fadb660de2 --- /dev/null +++ b/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.ts @@ -0,0 +1,203 @@ +import moment from 'moment'; + +import { + ASSESSMENT_WORKFLOW_TYPE, + ExecuteWorkflowResponseDTO, + ProcessInstance, + ProcessInstanceDTO, + ProcessInstanceState, + ProcessInstanceStatusDTO, + WorkflowCategory, + WorkflowCategoryDTO, + WorkflowDataDTO, + WorkflowDefinition, + WorkflowDTO, + WorkflowExecutionResponse, + WorkflowListResult, + WorkflowListResultDTO, + WorkflowOverview, + WorkflowOverviewDTO, + WorkflowSpecFile, + WorkflowSpecFileDTO, +} from '@janus-idp/backstage-plugin-orchestrator-common'; + +// Mapping functions +export function mapToWorkflowOverviewDTO( + overview: WorkflowOverview, +): WorkflowOverviewDTO { + return { + ...overview, + category: mapWorkflowCategoryDTOFromString(overview.category), + }; +} + +export function mapWorkflowCategoryDTOFromString( + category?: string, +): WorkflowCategoryDTO { + return category?.toLocaleLowerCase() === 'assessment' + ? 'assessment' + : 'infrastructure'; +} + +export function mapToWorkflowListResultDTO( + definitions: WorkflowListResult, +): WorkflowListResultDTO { + const result = { + items: definitions.items.map(def => { + return { + annotations: def.definition.annotations, + category: getWorkflowCategoryDTO(def.definition), + description: def.definition.description, + name: def.definition.name, + uri: def.uri, + id: def.definition.id, + }; + }), + paginationInfo: { + limit: definitions.limit, + offset: definitions.offset, + totalCount: definitions.totalCount, + }, + }; + return result; +} + +export function getWorkflowCategoryDTO( + definition: WorkflowDefinition | undefined, +): WorkflowCategoryDTO { + let result: WorkflowCategoryDTO = 'infrastructure'; + + if ( + definition?.annotations?.find( + annotation => annotation === ASSESSMENT_WORKFLOW_TYPE, + ) + ) { + result = 'assessment'; + } + + return result; +} + +export function mapToWorkflowDTO( + uri: string, + definition: WorkflowDefinition, +): WorkflowDTO { + return { + annotations: definition.annotations, + category: getWorkflowCategoryDTO(definition), + description: definition.description, + name: definition.name, + uri: uri, + id: definition.id, + }; +} + +export function mapWorkflowCategoryDTO( + category?: WorkflowCategory, +): WorkflowCategoryDTO { + if (category === WorkflowCategory.ASSESSMENT) { + return 'assessment'; + } + return 'infrastructure'; +} + +export function getProcessInstancesDTOFromString( + state: string, +): ProcessInstanceStatusDTO { + switch (state) { + case ProcessInstanceState.Active.valueOf(): + return 'Running'; + case ProcessInstanceState.Error.valueOf(): + return 'Error'; + case ProcessInstanceState.Completed.valueOf(): + return 'Completed'; + case ProcessInstanceState.Aborted.valueOf(): + return 'Aborted'; + case ProcessInstanceState.Suspended.valueOf(): + return 'Suspended'; + default: + throw new Error( + 'state is not one of the values of type ProcessInstanceStatusDTO', + ); + } +} + +export function mapToProcessInstanceDTO( + processInstance: ProcessInstance, +): ProcessInstanceDTO { + const start = moment(processInstance.start?.toString()); + const end = moment(processInstance.end?.toString()); + const duration = moment.duration(start.diff(end)); + + let variables: Record | undefined; + if (typeof processInstance?.variables === 'string') { + variables = JSON.parse(processInstance?.variables); + } else { + variables = processInstance?.variables; + } + + return { + category: mapWorkflowCategoryDTO(processInstance.category), + description: processInstance.description, + duration: duration.humanize(), + id: processInstance.id, + name: processInstance.processName, + // To be fixed https://issues.redhat.com/browse/FLPATH-950 + // @ts-ignore + workflowdata: variables?.workflowdata, + started: start.toDate().toLocaleString(), + status: getProcessInstancesDTOFromString(processInstance.state), + workflow: processInstance.processName ?? processInstance.processId, + }; +} + +export function mapToExecuteWorkflowResponseDTO( + workflowId: string, + workflowExecutionResponse: WorkflowExecutionResponse, +): ExecuteWorkflowResponseDTO { + if (!workflowExecutionResponse?.id) { + throw new Error( + `Error while mapping ExecuteWorkflowResponse to ExecuteWorkflowResponseDTO for workflow with id ${workflowId}`, + ); + } + + return { + id: workflowExecutionResponse.id, + }; +} + +export function mapToGetWorkflowInstanceResults( + variables: string | Record, +): WorkflowDataDTO { + if (typeof variables === 'string') { + return { + variables: variables, + }; + } + + let returnObject = {}; + if (variables?.workflowdata) { + returnObject = { + ...variables.workflowdata, + }; + } else { + returnObject = { + workflowoptions: [], + }; + } + + return returnObject; +} + +export function mapToWorkflowSpecFileDTO( + specV1: WorkflowSpecFile, +): WorkflowSpecFileDTO { + if (!specV1.content) { + throw new Error('Workflow specification content is empty'); + } + + return { + content: JSON.stringify(specV1.content), + path: specV1.path, + }; +} diff --git a/plugins/orchestrator-backend/src/service/api/test-utils.ts b/plugins/orchestrator-backend/src/service/api/test-utils.ts new file mode 100644 index 0000000000..6399cfbe48 --- /dev/null +++ b/plugins/orchestrator-backend/src/service/api/test-utils.ts @@ -0,0 +1,97 @@ +import { OpenAPIV3 } from 'openapi-types'; + +import { + ProcessInstanceState, + ProcessInstanceStateValues, + WorkflowDefinition, + WorkflowOverview, + WorkflowOverviewListResult, + WorkflowSpecFile, +} from '@janus-idp/backstage-plugin-orchestrator-common'; + +interface WorkflowOverviewParams { + suffix?: string; + workflowId?: string; + name?: string; + uri?: string; + lastTriggeredMs?: number; + lastRunStatus?: ProcessInstanceStateValues; + category?: string; + avgDurationMs?: number; + description?: string; +} +export function generateTestWorkflowOverview( + params: WorkflowOverviewParams, +): WorkflowOverview { + return { + workflowId: params.workflowId ?? `testWorkflowId${params.suffix}`, + name: params.name ?? `Test Workflow${params.suffix}`, + uri: params.uri ?? 'http://example.com', + lastTriggeredMs: + params.lastTriggeredMs ?? Date.parse('2024-02-09T10:34:56Z'), + lastRunStatus: params.lastRunStatus ?? ProcessInstanceState.Completed, + category: params.category ?? 'assessment', // validate input + avgDurationMs: params.avgDurationMs ?? 1000, + description: params.description ?? 'Test Workflow Description', + }; +} + +export function generateTestWorkflowOverviewList( + howmany: number, + inputParams?: WorkflowOverviewParams, +): WorkflowOverviewListResult { + const res: WorkflowOverviewListResult = { + items: [], + totalCount: howmany, + offset: 0, + limit: 0, + }; + + for (let i = 0; i <= howmany; i++) { + const params: WorkflowOverviewParams = inputParams ?? {}; + params.suffix = i.toString(); + res.items.push(generateTestWorkflowOverview(params)); + } + + return res; +} + +// Utility function to generate fake OpenAPIV3.Document +export const fakeOpenAPIV3Document = (): OpenAPIV3.Document => { + // Customize this function based on your OpenAPI document structure + return { + openapi: '3.0.0', + info: { + title: 'Title', + version: '1.0.0', + }, + paths: {}, + }; +}; + +export function generateTestWorkflowSpecs(howmany: number): WorkflowSpecFile[] { + const res: WorkflowSpecFile[] = []; + for (let i = 0; i < howmany; i++) { + res.push({ + path: `/test/path/openapi_${i}.json`, + content: fakeOpenAPIV3Document(), + }); + } + return res; +} + +export const generateWorkflowDefinition: WorkflowDefinition = { + id: 'quarkus-backend-workflow-ci-switch', + version: '1.0', + specVersion: '0.8', + name: '[WF] Create a starter Quarkus Backend application with a CI pipeline - CI Switch', + description: + '[WF] Create a starter Quarkus Backend application with a CI pipeline - CI Switch', + states: [ + { + name: 'Test state', + type: 'operation', + end: true, + }, + ], +}; diff --git a/plugins/orchestrator-backend/src/service/api/v1.ts b/plugins/orchestrator-backend/src/service/api/v1.ts index 21c85ddfca..1f7562c13a 100644 --- a/plugins/orchestrator-backend/src/service/api/v1.ts +++ b/plugins/orchestrator-backend/src/service/api/v1.ts @@ -1,19 +1,208 @@ -import { WorkflowOverviewListResult } from '@janus-idp/backstage-plugin-orchestrator-common'; +import { OperationResult } from '@urql/core'; +import express from 'express'; +import { + AssessedProcessInstance, + fromWorkflowSource, + ProcessInstance, + WorkflowDefinition, + WorkflowExecutionResponse, + WorkflowInfo, + WorkflowItem, + WorkflowListResult, + WorkflowOverview, + WorkflowOverviewListResult, + WorkflowSpecFile, +} from '@janus-idp/backstage-plugin-orchestrator-common'; + +import { DataIndexService } from '../DataIndexService'; import { SonataFlowService } from '../SonataFlowService'; +import { WorkflowService } from '../WorkflowService'; + +export namespace V1 { + export async function getWorkflowsOverview( + sonataFlowService: SonataFlowService, + ): Promise { + const overviews = await sonataFlowService.fetchWorkflowOverviews(); + if (!overviews) { + throw new Error("Couldn't fetch workflow overviews"); + } + const result: WorkflowOverviewListResult = { + items: overviews, + limit: 0, + offset: 0, + totalCount: overviews?.length ?? 0, + }; + return result; + } + + export async function getWorkflowOverviewById( + sonataFlowService: SonataFlowService, + workflowId: string, + ): Promise { + const overviewObj = + await sonataFlowService.fetchWorkflowOverview(workflowId); + + if (!overviewObj) { + throw new Error(`Couldn't fetch workflow overview for ${workflowId}`); + } + return overviewObj; + } + + export async function getWorkflows( + sonataFlowService: SonataFlowService, + dataIndexService: DataIndexService, + ): Promise { + const definitions: WorkflowInfo[] = + await dataIndexService.getWorkflowDefinitions(); + const items: WorkflowItem[] = await Promise.all( + definitions.map(async info => { + const uri = await sonataFlowService.fetchWorkflowUri(info.id); + if (!uri) { + throw new Error(`Uri is required for workflow ${info.id}`); + } + const item: WorkflowItem = { + definition: info as WorkflowDefinition, + serviceUrl: info.serviceUrl, + uri, + }; + return item; + }), + ); + + if (!items) { + throw new Error("Couldn't fetch workflows"); + } + + return { + items: items, + limit: 0, + offset: 0, + totalCount: items?.length ?? 0, + }; + } + + export async function getWorkflowById( + sonataFlowService: SonataFlowService, + workflowId: string, + ): Promise<{ uri: string; definition: WorkflowDefinition }> { + const definition = + await sonataFlowService.fetchWorkflowDefinition(workflowId); + + if (!definition) { + throw new Error(`Couldn't fetch workflow definition for ${workflowId}`); + } + + const uri = await sonataFlowService.fetchWorkflowUri(workflowId); + if (!uri) { + throw new Error(`Couldn't fetch workflow uri for ${workflowId}`); + } + + return { uri, definition }; + } + + export async function getInstances( + dataIndexService: DataIndexService, + ): Promise { + const instances = await dataIndexService.fetchProcessInstances(); + + if (!instances) { + throw new Error("Couldn't fetch process instances"); + } + return instances; + } + + export async function getInstanceById( + dataIndexService: DataIndexService, + instanceId: string, + includeAssessment?: string, + ): Promise { + const instance = await dataIndexService.fetchProcessInstance(instanceId); -export async function getWorkflowOverviewV1( - sonataFlowService: SonataFlowService, -): Promise { - const overviews = await sonataFlowService.fetchWorkflowOverviews(); - if (!overviews) { - throw new Error("Couldn't fetch workflow overviews"); - } - const result: WorkflowOverviewListResult = { - items: overviews, - limit: 0, - offset: 0, - totalCount: overviews?.length ?? 0, - }; - return result; + if (!instance) { + throw new Error(`Couldn't fetch process instance ${instanceId}`); + } + + let assessedByInstance: ProcessInstance | undefined; + + if (!!includeAssessment && instance.businessKey) { + assessedByInstance = await dataIndexService.fetchProcessInstance( + instance.businessKey, + ); + } + + const response: AssessedProcessInstance = { + instance, + assessedBy: assessedByInstance, + }; + return response; + } + + export async function executeWorkflow( + dataIndexService: DataIndexService, + sonataFlowService: SonataFlowService, + reqBody: Record, + workflowId: string, + businessKey: string | undefined, + ): Promise { + const definition = await dataIndexService.getWorkflowDefinition(workflowId); + const serviceUrl = definition.serviceUrl; + if (!serviceUrl) { + throw new Error(`ServiceURL is not defined for workflow ${workflowId}`); + } + const executionResponse = await sonataFlowService.executeWorkflow({ + workflowId, + inputData: reqBody, + endpoint: serviceUrl, + businessKey, + }); + + if (!executionResponse) { + throw new Error(`Couldn't execute workflow ${workflowId}`); + } + + return executionResponse; + } + + export async function createWorkflow( + workflowService: WorkflowService, + uri: string, + reqBody: string, + ): Promise { + const workflowItem = uri?.startsWith('http') + ? await workflowService.saveWorkflowDefinitionFromUrl(uri) + : await workflowService.saveWorkflowDefinition({ + uri, + definition: fromWorkflowSource(reqBody), + }); + return workflowItem; + } + + export async function abortWorkflow( + dataIndexService: DataIndexService, + workflowId: string, + ): Promise { + const result = await dataIndexService.abortWorkflowInstance(workflowId); + + if (result.error) { + throw new Error( + `Can't abort workflow ${workflowId}. The error was: ${result.error}`, + ); + } + + return result; + } + + export async function getWorkflowSpecs( + workflowService: WorkflowService, + ): Promise { + return await workflowService.listStoredSpecs(); + } + + export function extractQueryParam( + req: express.Request, + key: string, + ): string | undefined { + return req.query[key] as string | undefined; + } } diff --git a/plugins/orchestrator-backend/src/service/api/v2.test.ts b/plugins/orchestrator-backend/src/service/api/v2.test.ts new file mode 100644 index 0000000000..d3a3761e07 --- /dev/null +++ b/plugins/orchestrator-backend/src/service/api/v2.test.ts @@ -0,0 +1,326 @@ +import { mockServices } from '@backstage/backend-test-utils'; + +import { + WorkflowOverviewDTO, + WorkflowOverviewListResultDTO, + WorkflowSpecFile, +} from '@janus-idp/backstage-plugin-orchestrator-common'; + +import { SonataFlowService } from '../SonataFlowService'; +import { WorkflowService } from '../WorkflowService'; +import { + mapToWorkflowOverviewDTO, + mapToWorkflowSpecFileDTO, +} from './mapping/V2Mappings'; +import { + generateTestWorkflowOverview, + generateTestWorkflowOverviewList, + generateTestWorkflowSpecs, + generateWorkflowDefinition, +} from './test-utils'; +import { V2 } from './v2'; + +jest.mock('../SonataFlowService', () => ({ + SonataFlowService: jest.fn(), +})); + +jest.mock('../DataIndexService', () => ({ + getWorkflowDefinitions: jest.fn(), +})); + +// Helper function to create a mock SonataFlowService instance +const createMockSonataFlowService = (): SonataFlowService => { + const mockSonataFlowService = new SonataFlowService( + {} as any, // Mock config + {} as any, // Mock dataIndexService + {} as any, // Mock logger + ); + + // Mock fetchWorkflowDefinition method + mockSonataFlowService.fetchWorkflowOverviews = jest.fn(); + mockSonataFlowService.fetchWorkflowOverview = jest.fn(); + mockSonataFlowService.fetchWorkflowDefinition = jest.fn(); + mockSonataFlowService.fetchWorkflowUri = jest.fn(); + + return mockSonataFlowService; +}; + +// Helper function to create a mock WorkflowService instance +const createMockWorkflowService = (): WorkflowService => { + const mockWorkflowService = new WorkflowService( + {} as any, // Mock openApiService + {} as any, // Mock dataInputSchemaService + {} as any, // Mock sonataFlowService + mockServices.rootConfig(), // Mock config + {} as any, // Mock logger + ); + mockWorkflowService.listStoredSpecs = jest.fn(); + + return mockWorkflowService; +}; + +describe('getWorkflowOverview', () => { + let mockSonataFlowService: SonataFlowService; + + beforeEach(() => { + jest.clearAllMocks(); + mockSonataFlowService = createMockSonataFlowService(); + }); + + it('0 items in workflow overview list', async () => { + // Arrange + const mockOverviewsV1 = { + items: [], + }; + + ( + mockSonataFlowService.fetchWorkflowOverviews as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + + // Act + const result: WorkflowOverviewListResultDTO = await V2.getWorkflowsOverview( + mockSonataFlowService, + ); + + // Assert + expect(result).toEqual({ + overviews: mockOverviewsV1.items.map(item => + mapToWorkflowOverviewDTO(item), + ), + paginationInfo: { + limit: 0, + offset: 0, + totalCount: mockOverviewsV1.items.length, + }, + }); + }); + + it('1 item in workflow overview list', async () => { + // Arrange + const mockOverviewsV1 = generateTestWorkflowOverviewList(1, {}); + + ( + mockSonataFlowService.fetchWorkflowOverviews as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + + // Act + const result: WorkflowOverviewListResultDTO = await V2.getWorkflowsOverview( + mockSonataFlowService, + ); + + // Assert + expect(result).toEqual({ + overviews: mockOverviewsV1.items.map(item => + mapToWorkflowOverviewDTO(item), + ), + paginationInfo: { + limit: 0, + offset: 0, + totalCount: mockOverviewsV1.items.length, + }, + }); + }); + + it('many items in workflow overview list', async () => { + // Arrange + const mockOverviewsV1 = generateTestWorkflowOverviewList(100, {}); + + ( + mockSonataFlowService.fetchWorkflowOverviews as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + + // Act + const result: WorkflowOverviewListResultDTO = await V2.getWorkflowsOverview( + mockSonataFlowService, + ); + + // Assert + expect(result).toEqual({ + overviews: mockOverviewsV1.items.map(item => + mapToWorkflowOverviewDTO(item), + ), + paginationInfo: { + limit: 0, + offset: 0, + totalCount: mockOverviewsV1.items.length, + }, + }); + }); + + it('undefined workflow overview list', async () => { + // Arrange + ( + mockSonataFlowService.fetchWorkflowOverviews as jest.Mock + ).mockRejectedValue(new Error('no workflow overview')); + + // Act + const promise = V2.getWorkflowsOverview(mockSonataFlowService); + + // Assert + await expect(promise).rejects.toThrow('no workflow overview'); + ( + mockSonataFlowService.fetchWorkflowOverviews as jest.Mock + ).mockRejectedValue(new Error('no workflow overview')); + }); +}); +describe('getWorkflowOverviewById', () => { + let mockSonataFlowService: SonataFlowService; + beforeEach(() => { + jest.clearAllMocks(); + mockSonataFlowService = createMockSonataFlowService(); + }); + + it('0 items in workflow overview list', async () => { + // Arrange + const mockOverviewsV1 = { + items: [], + }; + ( + mockSonataFlowService.fetchWorkflowOverview as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + // Act + const overviewV2 = await V2.getWorkflowOverviewById( + mockSonataFlowService, + 'test_workflowId', + ); + + // Assert + expect(overviewV2).toBeDefined(); + expect(overviewV2.workflowId).toBeUndefined(); + expect(overviewV2.name).toBeUndefined(); + expect(overviewV2.uri).toBeUndefined(); + expect(overviewV2.lastTriggeredMs).toBeUndefined(); + expect(overviewV2.lastRunStatus).toBeUndefined(); + expect(overviewV2.category).toEqual('infrastructure'); + expect(overviewV2.avgDurationMs).toBeUndefined(); + expect(overviewV2.description).toBeUndefined(); + }); + + it('1 item in workflow overview list', async () => { + // Arrange + const mockOverviewsV1 = generateTestWorkflowOverview({ + name: 'test_workflowId', + }); + + ( + mockSonataFlowService.fetchWorkflowOverview as jest.Mock + ).mockResolvedValue(mockOverviewsV1); + + // Act + const result: WorkflowOverviewDTO = await V2.getWorkflowOverviewById( + mockSonataFlowService, + 'test_workflowId', + ); + + // Assert + expect(result).toEqual(mapToWorkflowOverviewDTO(mockOverviewsV1)); + }); +}); + +describe('getWorkflowSpecs', () => { + let mockWorkflowService: WorkflowService; + beforeEach(() => { + jest.clearAllMocks(); + mockWorkflowService = createMockWorkflowService(); + }); + + it('0 items in workflow spec list', async () => { + // Arrange + const mockSpecsV1: WorkflowSpecFile[] = []; + (mockWorkflowService.listStoredSpecs as jest.Mock).mockResolvedValue( + mockSpecsV1, + ); + + // Act + const result = await V2.getWorkflowSpecs(mockWorkflowService); + + // Assert + expect(result).toBeDefined(); + expect(result).toHaveLength(0); + }); + + it('1 item in workflow spec list', async () => { + // Arrange + const mockSpecsV1 = generateTestWorkflowSpecs(1); + (mockWorkflowService.listStoredSpecs as jest.Mock).mockResolvedValue( + mockSpecsV1, + ); + + // Act + const result = await V2.getWorkflowSpecs(mockWorkflowService); + + // Assert + expect(result).toBeDefined(); + expect(result).toHaveLength(1); + expect(result).toEqual( + mockSpecsV1.map(itemV1 => mapToWorkflowSpecFileDTO(itemV1)), + ); + }); + + it('many item in workflow spec list', async () => { + // Arrange + const mockSpecsV1 = generateTestWorkflowSpecs(10); + (mockWorkflowService.listStoredSpecs as jest.Mock).mockResolvedValue( + mockSpecsV1, + ); + + // Act + const result = await V2.getWorkflowSpecs(mockWorkflowService); + + // Assert + expect(result).toBeDefined(); + expect(result).toHaveLength(10); + expect(result).toEqual( + mockSpecsV1.map(itemV1 => mapToWorkflowSpecFileDTO(itemV1)), + ); + }); +}); + +describe('getWorkflowById', () => { + let mockSonataFlowService: SonataFlowService; + beforeEach(() => { + jest.clearAllMocks(); + mockSonataFlowService = createMockSonataFlowService(); + }); + + it("Workflow doesn't exists", async () => { + ( + mockSonataFlowService.fetchWorkflowDefinition as jest.Mock + ).mockRejectedValue(new Error('No definition')); + // Act + const promise = V2.getWorkflowById( + mockSonataFlowService, + 'test_workflowId', + ); + + // Assert + await expect(promise).rejects.toThrow('No definition'); + }); + + it('1 items in workflow list', async () => { + const testUri = 'test-uri.sw.yaml'; + const wfDefinition = generateWorkflowDefinition; + + ( + mockSonataFlowService.fetchWorkflowDefinition as jest.Mock + ).mockResolvedValue(wfDefinition); + (mockSonataFlowService.fetchWorkflowUri as jest.Mock).mockResolvedValue( + testUri, + ); + // Act + const workflowV2 = await V2.getWorkflowById( + mockSonataFlowService, + 'test_workflowId', + ); + + // Assert + expect(workflowV2).toBeDefined(); + expect(workflowV2.id).toBeDefined(); + expect(workflowV2.id).toEqual(wfDefinition.id); + expect(workflowV2.name).toEqual(wfDefinition.name); + expect(workflowV2.uri).toEqual(testUri); + expect(workflowV2.description).toEqual(wfDefinition.description); + expect(workflowV2.category).toEqual('infrastructure'); + expect(workflowV2.annotations).toBeUndefined(); + }); +}); diff --git a/plugins/orchestrator-backend/src/service/api/v2.ts b/plugins/orchestrator-backend/src/service/api/v2.ts index 5149165d6a..53a6b89994 100644 --- a/plugins/orchestrator-backend/src/service/api/v2.ts +++ b/plugins/orchestrator-backend/src/service/api/v2.ts @@ -1,19 +1,234 @@ -import { WorkflowOverviewListResultDTO } from '@janus-idp/backstage-plugin-orchestrator-common'; +import { ParsedRequest } from 'openapi-backend'; +import { + AssessedProcessInstance, + AssessedProcessInstanceDTO, + ExecuteWorkflowRequestDTO, + ExecuteWorkflowResponseDTO, + ProcessInstancesDTO, + ProcessInstanceState, + WorkflowDataDTO, + WorkflowDTO, + WorkflowListResult, + WorkflowListResultDTO, + WorkflowOverviewDTO, + WorkflowOverviewListResultDTO, + WorkflowRunStatusDTO, + WorkflowSpecFileDTO, +} from '@janus-idp/backstage-plugin-orchestrator-common'; + +import { DataIndexService } from '../DataIndexService'; import { SonataFlowService } from '../SonataFlowService'; -import { getWorkflowOverviewV1 } from './v1'; - -export async function getWorkflowOverviewV2( - sonataFlowService: SonataFlowService, -): Promise { - const overviewsV1 = await getWorkflowOverviewV1(sonataFlowService); - const result: WorkflowOverviewListResultDTO = { - overviews: overviewsV1.items, - paginationInfo: { - limit: 0, - offset: 0, - totalCount: overviewsV1.items?.length ?? 0, - }, - }; - return result; +import { WorkflowService } from '../WorkflowService'; +import { + mapToExecuteWorkflowResponseDTO, + mapToGetWorkflowInstanceResults, + mapToProcessInstanceDTO, + mapToWorkflowDTO, + mapToWorkflowListResultDTO, + mapToWorkflowOverviewDTO, + mapToWorkflowSpecFileDTO, +} from './mapping/V2Mappings'; +import { V1 } from './v1'; + +export namespace V2 { + export async function getWorkflowsOverview( + sonataFlowService: SonataFlowService, + ): Promise { + const overviewsV1 = await V1.getWorkflowsOverview(sonataFlowService); + const result: WorkflowOverviewListResultDTO = { + overviews: overviewsV1.items.map(item => mapToWorkflowOverviewDTO(item)), + paginationInfo: { + limit: 0, + offset: 0, + totalCount: overviewsV1.items?.length ?? 0, + }, + }; + return result; + } + + export async function getWorkflowOverviewById( + sonataFlowService: SonataFlowService, + workflowId: string, + ): Promise { + const overviewV1 = await V1.getWorkflowOverviewById( + sonataFlowService, + workflowId, + ); + + if (!overviewV1) { + throw new Error(`Couldn't fetch workflow overview for ${workflowId}`); + } + return mapToWorkflowOverviewDTO(overviewV1); + } + + export async function getWorkflows( + sonataFlowService: SonataFlowService, + dataIndexService: DataIndexService, + ): Promise { + const definitions: WorkflowListResult = await V1.getWorkflows( + sonataFlowService, + dataIndexService, + ); + return mapToWorkflowListResultDTO(definitions); + } + + export async function getWorkflowById( + sonataFlowService: SonataFlowService, + workflowId: string, + ): Promise { + const resultV1 = await V1.getWorkflowById(sonataFlowService, workflowId); + return mapToWorkflowDTO(resultV1.uri, resultV1.definition); + } + + export async function getInstances( + dataIndexService: DataIndexService, + ): Promise { + const instances = await V1.getInstances(dataIndexService); + const result = instances.map(def => mapToProcessInstanceDTO(def)); + + return result; + } + + export async function getInstanceById( + dataIndexService: DataIndexService, + instanceId: string, + includeAssessment?: string, + ): Promise { + const instance: AssessedProcessInstance = await V1.getInstanceById( + dataIndexService, + instanceId, + includeAssessment, + ); + + if (!instance) { + throw new Error(`Couldn't fetch process instance ${instanceId}`); + } + + return { + instance: mapToProcessInstanceDTO(instance.instance), + assessedBy: instance.assessedBy + ? mapToProcessInstanceDTO(instance.assessedBy) + : undefined, + }; + } + + export async function executeWorkflow( + dataIndexService: DataIndexService, + sonataFlowService: SonataFlowService, + executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, + workflowId: string, + businessKey: string | undefined, + ): Promise { + if (!dataIndexService) { + throw new Error( + `No data index service provided for executing workflow with id ${workflowId}`, + ); + } + + if (!sonataFlowService) { + throw new Error( + `No sonata flow service provided for executing workflow with id ${workflowId}`, + ); + } + + if (Object.keys(executeWorkflowRequestDTO?.inputData).length === 0) { + throw new Error( + `ExecuteWorkflowRequestDTO.inputData is required for executing workflow with id ${workflowId}`, + ); + } + + const executeWorkflowResponse = await V1.executeWorkflow( + dataIndexService, + sonataFlowService, + executeWorkflowRequestDTO, + workflowId, + businessKey, + ); + + if (!executeWorkflowResponse) { + throw new Error('Error executing workflow with id ${workflowId}'); + } + + return mapToExecuteWorkflowResponseDTO(workflowId, executeWorkflowResponse); + } + + export async function createWorkflow( + workflowService: WorkflowService, + uri: string, + reqBody: string, + ): Promise { + const workflowItem = await V1.createWorkflow(workflowService, uri, reqBody); + return mapToWorkflowDTO(uri, workflowItem.definition); + } + + export async function abortWorkflow( + dataIndexService: DataIndexService, + workflowId: string, + ): Promise { + await V1.abortWorkflow(dataIndexService, workflowId); + return 'Workflow ${workflowId} successfully aborted'; + } + + export async function getWorkflowResults( + dataIndexService: DataIndexService, + instanceId: string, + includeAssessment?: string, + ): Promise { + if (!instanceId) { + throw new Error(`No instance id was provided to get workflow results`); + } + if (!dataIndexService) { + throw new Error( + `No data index service provided for executing workflow with id ${instanceId}`, + ); + } + + const instanceResult = await V1.getInstanceById( + dataIndexService, + instanceId, + includeAssessment, + ); + + if (!instanceResult.instance?.variables) { + throw new Error( + `Error getting workflow instance results with id ${instanceId}`, + ); + } + + return mapToGetWorkflowInstanceResults(instanceResult.instance.variables); + } + + export async function getWorkflowSpecs( + workflowService: WorkflowService, + ): Promise { + const specV1 = await V1.getWorkflowSpecs(workflowService); + return specV1.map(spec => mapToWorkflowSpecFileDTO(spec)); + } + + export async function getWorkflowStatuses(): Promise { + type Capitalized = Capitalize>; + const capitalize = (text: S): Capitalized => + (text[0].toUpperCase() + text.slice(1).toLowerCase()) as Capitalized; + const result: WorkflowRunStatusDTO[] = [ + ProcessInstanceState.Active, + ProcessInstanceState.Error, + ProcessInstanceState.Completed, + ProcessInstanceState.Aborted, + ProcessInstanceState.Suspended, + ].map( + (status): WorkflowRunStatusDTO => ({ + key: capitalize(status), + value: status, + }), + ); + return result; + } + + export function extractQueryParam( + req: ParsedRequest, + key: string, + ): string | undefined { + return req.query[key] as string | undefined; + } } diff --git a/plugins/orchestrator-backend/src/service/router.ts b/plugins/orchestrator-backend/src/service/router.ts index fae938c281..a7b8ae38a2 100644 --- a/plugins/orchestrator-backend/src/service/router.ts +++ b/plugins/orchestrator-backend/src/service/router.ts @@ -8,27 +8,21 @@ import Router from 'express-promise-router'; import { OpenAPIBackend, Request } from 'openapi-backend'; import { - AssessedProcessInstance, - fromWorkflowSource, openApiDocument, ORCHESTRATOR_SERVICE_READY_TOPIC, - ProcessInstance, QUERY_PARAM_ASSESSMENT_INSTANCE_ID, QUERY_PARAM_BUSINESS_KEY, QUERY_PARAM_INCLUDE_ASSESSMENT, QUERY_PARAM_INSTANCE_ID, QUERY_PARAM_URI, - WorkflowDefinition, - WorkflowInfo, WorkflowInputSchemaResponse, WorkflowItem, - WorkflowListResult, } from '@janus-idp/backstage-plugin-orchestrator-common'; import { RouterArgs } from '../routerWrapper'; import { ApiResponseBuilder } from '../types/apiResponse'; -import { getWorkflowOverviewV1 } from './api/v1'; -import { getWorkflowOverviewV2 } from './api/v2'; +import { V1 } from './api/v1'; +import { V2 } from './api/v2'; import { CloudEventService } from './CloudEventService'; import { DataIndexService } from './DataIndexService'; import { DataInputSchemaService } from './DataInputSchemaService'; @@ -166,7 +160,7 @@ function setupInternalRoutes( }); router.get('/workflows/overview', async (_c, res) => { - await getWorkflowOverviewV1(services.sonataFlowService) + await V1.getWorkflowsOverview(services.sonataFlowService) .then(result => res.status(200).json(result)) .catch(error => { res.status(500).send(error.message || 'Internal Server Error'); @@ -177,72 +171,49 @@ function setupInternalRoutes( api.register( 'getWorkflowsOverview', async (_c, _req, res: express.Response, next) => { - await getWorkflowOverviewV2(services.sonataFlowService) + await V2.getWorkflowsOverview(services.sonataFlowService) .then(result => res.json(result)) .catch(error => { - res.status(500).send(error.message || 'Internal Server Error'); + res.status(500).send(error.message || 'internal Server Error'); next(); }); }, ); router.get('/workflows', async (_, res) => { - const definitions: WorkflowInfo[] = - await services.dataIndexService.getWorkflowDefinitions(); - const items: WorkflowItem[] = await Promise.all( - definitions.map(async info => { - const uri = await services.sonataFlowService.fetchWorkflowUri(info.id); - if (!uri) { - throw new Error(`Uri is required for workflow ${info.id}`); - } - const item: WorkflowItem = { - definition: info as WorkflowDefinition, - serviceUrl: info.serviceUrl, - uri, - }; - return item; - }), - ); - - if (!items) { - res.status(500).send("Couldn't fetch workflows"); - return; - } - - const result: WorkflowListResult = { - items: items, - limit: 0, - offset: 0, - totalCount: items?.length ?? 0, - }; - res.status(200).json(result); + await V1.getWorkflows(services.sonataFlowService, services.dataIndexService) + .then(result => res.status(200).json(result)) + .catch(error => { + res.status(500).send(error.message || 'internal Server Error'); + }); }); - router.get('/workflows/:workflowId', async (req, res) => { - const { - params: { workflowId }, - } = req; - - const definition = - await services.sonataFlowService.fetchWorkflowDefinition(workflowId); - - if (!definition) { - res - .status(500) - .send(`Couldn't fetch workflow definition for ${workflowId}`); - return; - } + // v2 + api.register('getWorkflows', async (_c, _req, res, next) => { + await V2.getWorkflows(services.sonataFlowService, services.dataIndexService) + .then(result => res.json(result)) + .catch(error => { + res.status(500).send(error.message || 'internal Server Error'); + next(); + }); + }); - const uri = await services.sonataFlowService.fetchWorkflowUri(workflowId); - if (!uri) { - res.status(500).send(`Couldn't fetch workflow uri for ${workflowId}`); - return; - } + router.get('/workflows', async (_, res) => { + await V1.getWorkflows(services.sonataFlowService, services.dataIndexService) + .then(result => res.status(200).json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }); - res.status(200).json({ - uri, - definition, - }); + // v2 + api.register('getWorkflows', async (_c, _req, res, next) => { + await V2.getWorkflows(services.sonataFlowService, services.dataIndexService) + .then(result => res.json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + next(); + }); }); router.delete('/workflows/:workflowId/abort', async (req, res) => { @@ -250,15 +221,22 @@ function setupInternalRoutes( params: { workflowId }, } = req; - const result = - await services.dataIndexService.abortWorkflowInstance(workflowId); - - if (result.error) { - res.status(500).json(result.error); - return; - } + await V1.abortWorkflow(services.dataIndexService, workflowId) + .then(result => res.status(200).json(result.data)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }); - res.status(200).json(result.data); + // v2 + api.register('abortWorkflow', async (c, _req, res, next) => { + const workflowId = c.request.params.workflowId as string; + await V2.abortWorkflow(services.dataIndexService, workflowId) + .then(result => res.json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + next(); + }); }); router.post('/workflows/:workflowId/execute', async (req, res) => { @@ -266,89 +244,154 @@ function setupInternalRoutes( params: { workflowId }, } = req; - const businessKey = extractQueryParam(req, QUERY_PARAM_BUSINESS_KEY); + const businessKey = V1.extractQueryParam(req, QUERY_PARAM_BUSINESS_KEY); - const definition = - await services.dataIndexService.getWorkflowDefinition(workflowId); - const serviceUrl = definition.serviceUrl; - if (!serviceUrl) { - throw new Error(`ServiceURL is not defined for workflow ${workflowId}`); - } - const executionResponse = await services.sonataFlowService.executeWorkflow({ + await V1.executeWorkflow( + services.dataIndexService, + services.sonataFlowService, + req.body, workflowId, - inputData: req.body, - endpoint: serviceUrl, businessKey, - }); + ) + .then((result: any) => res.status(200).json(result)) + .catch((error: { message: any }) => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }); - if (!executionResponse) { - res.status(500).send(`Couldn't execute workflow ${workflowId}`); - return; - } + // v2 + api.register( + 'executeWorkflow', + async (c, req: express.Request, res: express.Response) => { + const workflowId = c.request.params.workflowId as string; + const businessKey = V2.extractQueryParam( + c.request, + QUERY_PARAM_BUSINESS_KEY, + ); - res.status(200).json(executionResponse); - }); + const executeWorkflowRequestDTO = req.body; + await V2.executeWorkflow( + services.dataIndexService, + services.sonataFlowService, + executeWorkflowRequestDTO, + workflowId, + businessKey, + ) + .then(result => res.status(200).json(result)) + .catch((error: { message: string }) => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }, + ); + + // v2 + api.register( + 'executeWorkflow', + async (c, req: express.Request, res: express.Response) => { + const workflowId = c.request.params.workflowId as string; + const businessKey = V2.extractQueryParam( + c.request, + QUERY_PARAM_BUSINESS_KEY, + ); + + const executeWorkflowRequestDTO = req.body; + await V2.executeWorkflow( + services.dataIndexService, + services.sonataFlowService, + executeWorkflowRequestDTO, + workflowId, + businessKey, + ) + .then(result => res.status(200).json(result)) + .catch((error: { message: string }) => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }, + ); router.get('/workflows/:workflowId/overview', async (req, res) => { const { params: { workflowId }, } = req; - const overviewObj = - await services.sonataFlowService.fetchWorkflowOverview(workflowId); - - if (!overviewObj) { - res - .status(500) - .send(`Couldn't fetch workflow overview for ${workflowId}`); - return; - } - res.status(200).json(overviewObj); + await V1.getWorkflowOverviewById( + services.sonataFlowService, + workflowId, + ).then(result => res.json(result)); }); - router.get('/instances', async (_, res) => { - const instances = await services.dataIndexService.fetchProcessInstances(); - - if (!instances) { - res.status(500).send("Couldn't fetch process instances"); - return; - } + // v2 + api.register( + 'getWorkflowOverviewById', + async (_c, req: express.Request, res: express.Response, next) => { + const { + params: { workflowId }, + } = req; + await V2.getWorkflowOverviewById(services.sonataFlowService, workflowId) + .then(result => res.json(result)) + .catch(next); + }, + ); - res.status(200).json(instances); + router.get('/instances', async (_, res) => { + await V1.getInstances(services.dataIndexService) + .then(result => res.status(200).json(result)) + .catch(error => { + res.status(500).send(error.message || 'internal Server Error'); + }); }); + // v2 + api.register( + 'getInstances', + async (_c, _req: express.Request, res: express.Response, next) => { + await V2.getInstances(services.dataIndexService) + .then(result => res.json(result)) + .catch(next); + }, + ); + router.get('/instances/:instanceId', async (req, res) => { const { params: { instanceId }, } = req; - const includeAssessment = extractQueryParam( + const includeAssessment = V1.extractQueryParam( req, QUERY_PARAM_INCLUDE_ASSESSMENT, ); - const instance = - await services.dataIndexService.fetchProcessInstance(instanceId); - - if (!instance) { - res.status(500).send(`Couldn't fetch process instance ${instanceId}`); - return; - } - - let assessedByInstance: ProcessInstance | undefined; + await V1.getInstanceById( + services.dataIndexService, + instanceId, + includeAssessment, + ) + .then(result => res.status(200).json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }); - if (!!includeAssessment && instance.businessKey) { - assessedByInstance = await services.dataIndexService.fetchProcessInstance( - instance.businessKey, + // v2 + api.register( + 'getInstanceById', + async (c, _req: express.Request, res: express.Response, next) => { + const instanceId = c.request.params.instanceId as string; + const includeAssessment = V2.extractQueryParam( + c.request, + QUERY_PARAM_INCLUDE_ASSESSMENT, ); - } - - const response: AssessedProcessInstance = { - instance, - assessedBy: assessedByInstance, - }; - - res.status(200).json(response); - }); + await V2.getInstanceById( + services.dataIndexService, + instanceId, + includeAssessment, + ) + .then(result => res.status(200).json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + next(); + }); + }, + ); router.get('/instances/:instanceId/jobs', async (req, res) => { const { @@ -371,8 +414,8 @@ function setupInternalRoutes( params: { workflowId }, } = req; - const instanceId = extractQueryParam(req, QUERY_PARAM_INSTANCE_ID); - const assessmentInstanceId = extractQueryParam( + const instanceId = V1.extractQueryParam(req, QUERY_PARAM_INSTANCE_ID); + const assessmentInstanceId = V1.extractQueryParam( req, QUERY_PARAM_ASSESSMENT_INSTANCE_ID, ); @@ -468,22 +511,39 @@ function setupInternalRoutes( }); router.post('/workflows', async (req, res) => { - const uri = extractQueryParam(req, QUERY_PARAM_URI); + const uri = V1.extractQueryParam(req, QUERY_PARAM_URI); if (!uri) { res.status(400).send('uri query param is required'); return; } - const workflowItem = uri?.startsWith('http') - ? await services.workflowService.saveWorkflowDefinitionFromUrl(uri) - : await services.workflowService.saveWorkflowDefinition({ - uri, - definition: fromWorkflowSource(req.body), - }); - res.status(201).json(workflowItem).send(); + await V1.createWorkflow(services.workflowService, uri, req.body) + .then(result => res.status(201).json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + }); }); + // v2 + api.register( + 'createWorkflow', + async (c, _req, res: express.Response, next) => { + const uri = V2.extractQueryParam(c.request, QUERY_PARAM_URI); + + if (!uri) { + res.status(400).send('uri query param is required'); + return; + } + await V2.createWorkflow(services.workflowService, uri, c.request.body) + .then(result => res.json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + next(); + }); + }, + ); + router.get('/actions/schema', async (_, res) => { const openApi = await services.openApiService.generateOpenApi(); res.json(openApi).status(200).send(); @@ -501,9 +561,50 @@ function setupInternalRoutes( }); router.get('/specs', async (_, res) => { - const specs = await services.workflowService.listStoredSpecs(); - res.status(200).json(specs); + await V1.getWorkflowSpecs(services.workflowService) + .then(result => res.status(200).json(result)) + .catch(error => { + res.status(500).send(error.message || 'Internal Server Error'); + }); }); + + // v2 + api.register( + 'getWorkflowSpecs', + async (_c, _req: express.Request, res: express.Response) => { + await V2.getWorkflowSpecs(services.workflowService) + .then(result => res.status(200).json(result)) + .catch((error: { message: string }) => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }, + ); + + // v2 + api.register( + 'getWorkflowResults', + async (c, _req: express.Request, res: express.Response) => { + const instanceId = c.request.params.instanceId as string; + + await V2.getWorkflowResults(services.dataIndexService, instanceId) + .then(result => res.status(200).json(result)) + .catch((error: { message: string }) => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }, + ); + + // v2 + api.register( + 'getWorkflowStatuses', + async (_c, _req: express.Request, res: express.Response) => { + await V2.getWorkflowStatuses() + .then(result => res.status(200).json(result)) + .catch((error: { message: string }) => { + res.status(500).send(error.message || 'Internal Server Error'); + }); + }, + ); } // ====================================================== @@ -540,10 +641,3 @@ function setupExternalRoutes( res.status(200).json(result); }); } - -function extractQueryParam( - req: express.Request, - key: string, -): string | undefined { - return req.query[key] as string | undefined; -} diff --git a/plugins/orchestrator-common/openapitools.json b/plugins/orchestrator-common/openapitools.json new file mode 100644 index 0000000000..9841a49bce --- /dev/null +++ b/plugins/orchestrator-common/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.3.0" + } +} diff --git a/plugins/orchestrator-common/scripts/openapi.sh b/plugins/orchestrator-common/scripts/openapi.sh index 31a558936a..509b5b36fb 100755 --- a/plugins/orchestrator-common/scripts/openapi.sh +++ b/plugins/orchestrator-common/scripts/openapi.sh @@ -2,9 +2,8 @@ pwd set -ex -# npx openapi typegen ./api/openapi.yaml > src/openapi/openapi.d.ts npx openapi-typescript ./src/openapi/openapi.yaml -o ./src/auto-generated/api/models/schema.ts - +npx openapi-generator-cli generate -g asciidoc -i ./src/openapi/openapi.yaml -o ./src/auto-generated/docs/index.adoc npx yaml2json -f ./src/openapi/openapi.yaml export FILE=./src/auto-generated/api/definition.ts diff --git a/plugins/orchestrator-common/src/auto-generated/api/definition.ts b/plugins/orchestrator-common/src/auto-generated/api/definition.ts index 055a4b23f2..f5f31ab19e 100644 --- a/plugins/orchestrator-common/src/auto-generated/api/definition.ts +++ b/plugins/orchestrator-common/src/auto-generated/api/definition.ts @@ -34,19 +34,401 @@ const OPENAPI = ` }, "500": { "description": "Error fetching workflow overviews", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows/{workflowId}/overview": { + "get": { + "operationId": "getWorkflowOverviewById", + "description": "Get a workflow overview by ID", + "parameters": [ + { + "name": "workflowId", + "in": "path", + "required": true, + "description": "Unique identifier of the workflow", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowOverviewDTO" + } + } + } + }, + "500": { + "description": "Error fetching workflow overview", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows": { + "get": { + "operationId": "getWorkflows", + "description": "Get a list of workflow", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowListResultDTO" + } + } + } + }, + "500": { + "description": "Error fetching workflow list", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "operationId": "createWorkflow", + "summary": "Create or update a workflow", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "body": { + "type": "string" + } + }, + "required": [ + "uri" + ] + } + } + } + }, + "parameters": [ + { + "name": "uri", + "in": "query", + "description": "URI parameter", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { - "message": { - "type": "string", - "description": "Error message" + "workflowItem": { + "$ref": "#/components/schemas/WorkflowDTO" } } } } } + }, + "500": { + "description": "Error creating workflow", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows/instances": { + "get": { + "operationId": "getInstances", + "summary": "Get instances", + "description": "Retrieve an array of instances", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInstancesDTO" + } + } + } + }, + "500": { + "description": "Error fetching instances", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows/instances/{instanceId}": { + "get": { + "summary": "Get Workflow Instance by ID", + "operationId": "getInstanceById", + "parameters": [ + { + "name": "instanceId", + "in": "path", + "required": true, + "description": "ID of the workflow instance", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInstanceDTO" + } + } + } + }, + "500": { + "description": "Error fetching instance", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows/instances/{instanceId}/results": { + "get": { + "summary": "Get workflow results", + "operationId": "getWorkflowResults", + "parameters": [ + { + "name": "instanceId", + "in": "path", + "required": true, + "description": "ID of the workflow instance", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDataDTO" + } + } + } + }, + "500": { + "description": "Error getting workflow results", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows/instances/statuses": { + "get": { + "operationId": "getWorkflowStatuses", + "summary": "Get workflow status list", + "description": "Retrieve an array of workflow statuses", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowRunStatusDTO" + } + } + } + } + }, + "500": { + "description": "Error fetching workflow statuses", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows/{workflowId}/execute": { + "post": { + "summary": "Execute a workflow", + "operationId": "executeWorkflow", + "parameters": [ + { + "name": "workflowId", + "in": "path", + "description": "ID of the workflow to execute", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecuteWorkflowRequestDTO" + } + } + } + }, + "responses": { + "200": { + "description": "Successful execution", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecuteWorkflowResponseDTO" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/workflows/{workflowId}/abort": { + "delete": { + "summary": "Abort a workflow instance", + "operationId": "abortWorkflow", + "description": "Aborts a workflow instance identified by the provided workflowId.", + "parameters": [ + { + "name": "workflowId", + "in": "path", + "required": true, + "description": "The identifier of the workflow instance to abort.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "Error aborting workflow", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/specs": { + "get": { + "summary": "Get workflow specifications", + "operationId": "getWorkflowSpecs", + "responses": { + "200": { + "description": "Successful retrieval of workflow specifications", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowSpecFileDTO" + } + } + } + } + }, + "500": { + "description": "Error fetching workflow specifications", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } } } } @@ -91,9 +473,8 @@ const OPENAPI = ` "lastRunStatus": { "type": "string" }, - "type": { - "type": "string", - "minimum": 0 + "category": { + "$ref": "#/components/schemas/WorkflowCategoryDTO" }, "avgDurationMs": { "type": "number", @@ -121,6 +502,286 @@ const OPENAPI = ` } }, "additionalProperties": false + }, + "WorkflowCategoryDTO": { + "type": "string", + "description": "Category of the workflow", + "enum": [ + "assessment", + "infrastructure" + ] + }, + "WorkflowListResultDTO": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowDTO" + } + }, + "paginationInfo": { + "$ref": "#/components/schemas/PaginationInfoDTO" + } + }, + "required": [ + "items", + "paginationInfo" + ] + }, + "WorkflowDTO": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Workflow unique identifier", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Workflow name", + "minLength": 1 + }, + "uri": { + "type": "string", + "description": "URI of the workflow definition" + }, + "category": { + "$ref": "#/components/schemas/WorkflowCategoryDTO" + }, + "description": { + "type": "string", + "description": "Description of the workflow" + }, + "annotations": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "category", + "uri" + ] + }, + "ProcessInstancesDTO": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProcessInstanceDTO" + } + }, + "AssessedProcessInstanceDTO": { + "type": "object", + "properties": { + "instance": { + "$ref": "#/components/schemas/ProcessInstanceDTO" + }, + "assessedBy": { + "$ref": "#/components/schemas/ProcessInstanceDTO" + } + }, + "required": [ + "instance" + ] + }, + "ProcessInstanceDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "workflow": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/ProcessInstanceStatusDTO" + }, + "started": { + "type": "string", + "format": "date-time" + }, + "duration": { + "type": "string" + }, + "category": { + "$ref": "#/components/schemas/WorkflowCategoryDTO" + }, + "description": { + "type": "string" + }, + "workflowdata": { + "$ref": "#/components/schemas/WorkflowDataDTO" + } + } + }, + "WorkflowDataDTO": { + "type": "object", + "properties": { + "workflowoptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowOptionsDTO" + } + } + }, + "additionalProperties": true + }, + "WorkflowOptionsDTO": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowSuggestionDTO" + } + }, + "WorkflowSuggestionDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "ProcessInstanceStatusDTO": { + "type": "string", + "description": "Status of the workflow run", + "enum": [ + "Running", + "Error", + "Completed", + "Aborted", + "Suspended" + ] + }, + "WorkflowRunStatusDTO": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "ExecuteWorkflowRequestDTO": { + "type": "object", + "properties": { + "inputData": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "inputData" + ] + }, + "ExecuteWorkflowResponseDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, + "WorkflowSpecFileDTO": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "content": { + "type": "string", + "description": "JSON string" + } + }, + "required": [ + "content" + ] + }, + "WorkflowProgressDTO": { + "allOf": [ + { + "$ref": "#/components/schemas/NodeInstanceDTO" + }, + { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/ProcessInstanceStatusDTO" + }, + "error": { + "$ref": "#/components/schemas/ProcessInstanceErrorDTO" + } + } + } + ] + }, + "NodeInstanceDTO": { + "type": "object", + "properties": { + "__typename": { + "type": "string", + "default": "NodeInstance", + "description": "Type name" + }, + "id": { + "type": "string", + "description": "Node instance ID" + }, + "name": { + "type": "string", + "description": "Node name" + }, + "type": { + "type": "string", + "description": "Node type" + }, + "enter": { + "type": "string", + "format": "date-time", + "description": "Date when the node was entered" + }, + "exit": { + "type": "string", + "format": "date-time", + "description": "Date when the node was exited (optional)" + }, + "definitionId": { + "type": "string", + "description": "Definition ID" + }, + "nodeId": { + "type": "string", + "description": "Node ID" + } + } + }, + "ProcessInstanceErrorDTO": { + "type": "object", + "properties": { + "__typename": { + "type": "string", + "default": "ProcessInstanceError", + "description": "Type name" + }, + "nodeDefinitionId": { + "type": "string", + "description": "Node definition ID" + }, + "message": { + "type": "string", + "description": "Error message (optional)" + } + } } } } diff --git a/plugins/orchestrator-common/src/auto-generated/api/models/schema.ts b/plugins/orchestrator-common/src/auto-generated/api/models/schema.ts index 0c5679a293..2cff9cdf12 100644 --- a/plugins/orchestrator-common/src/auto-generated/api/models/schema.ts +++ b/plugins/orchestrator-common/src/auto-generated/api/models/schema.ts @@ -8,6 +8,53 @@ export interface paths { /** @description Get a list of workflow overviews */ get: operations['getWorkflowsOverview']; }; + '/v2/workflows/{workflowId}/overview': { + /** @description Get a workflow overview by ID */ + get: operations['getWorkflowOverviewById']; + }; + '/v2/workflows': { + /** @description Get a list of workflow */ + get: operations['getWorkflows']; + /** Create or update a workflow */ + post: operations['createWorkflow']; + }; + '/v2/workflows/instances': { + /** + * Get instances + * @description Retrieve an array of instances + */ + get: operations['getInstances']; + }; + '/v2/workflows/instances/{instanceId}': { + /** Get Workflow Instance by ID */ + get: operations['getInstanceById']; + }; + '/v2/workflows/instances/{instanceId}/results': { + /** Get workflow results */ + get: operations['getWorkflowResults']; + }; + '/v2/workflows/instances/statuses': { + /** + * Get workflow status list + * @description Retrieve an array of workflow statuses + */ + get: operations['getWorkflowStatuses']; + }; + '/v2/workflows/{workflowId}/execute': { + /** Execute a workflow */ + post: operations['executeWorkflow']; + }; + '/v2/workflows/{workflowId}/abort': { + /** + * Abort a workflow instance + * @description Aborts a workflow instance identified by the provided workflowId. + */ + delete: operations['abortWorkflow']; + }; + '/v2/specs': { + /** Get workflow specifications */ + get: operations['getWorkflowSpecs']; + }; } export type webhooks = Record; @@ -26,7 +73,7 @@ export interface components { uri?: string; lastTriggeredMs?: number; lastRunStatus?: string; - type?: string; + category?: components['schemas']['WorkflowCategoryDTO']; avgDurationMs?: number; description?: string; }; @@ -35,6 +82,122 @@ export interface components { offset?: number; totalCount?: number; }; + /** + * @description Category of the workflow + * @enum {string} + */ + WorkflowCategoryDTO: 'assessment' | 'infrastructure'; + WorkflowListResultDTO: { + items: components['schemas']['WorkflowDTO'][]; + paginationInfo: components['schemas']['PaginationInfoDTO']; + }; + WorkflowDTO: { + /** @description Workflow unique identifier */ + id: string; + /** @description Workflow name */ + name?: string; + /** @description URI of the workflow definition */ + uri: string; + category: components['schemas']['WorkflowCategoryDTO']; + /** @description Description of the workflow */ + description?: string; + annotations?: string[]; + }; + ProcessInstancesDTO: components['schemas']['ProcessInstanceDTO'][]; + AssessedProcessInstanceDTO: { + instance: components['schemas']['ProcessInstanceDTO']; + assessedBy?: components['schemas']['ProcessInstanceDTO']; + }; + ProcessInstanceDTO: { + id?: string; + name?: string; + workflow?: string; + status?: components['schemas']['ProcessInstanceStatusDTO']; + /** Format: date-time */ + started?: string; + duration?: string; + category?: components['schemas']['WorkflowCategoryDTO']; + description?: string; + workflowdata?: components['schemas']['WorkflowDataDTO']; + }; + WorkflowDataDTO: { + workflowoptions?: components['schemas']['WorkflowOptionsDTO'][]; + [key: string]: unknown; + }; + WorkflowOptionsDTO: components['schemas']['WorkflowSuggestionDTO'][]; + WorkflowSuggestionDTO: { + id?: string; + name?: string; + }; + /** + * @description Status of the workflow run + * @enum {string} + */ + ProcessInstanceStatusDTO: + | 'Running' + | 'Error' + | 'Completed' + | 'Aborted' + | 'Suspended'; + WorkflowRunStatusDTO: { + key?: string; + value?: string; + }; + ExecuteWorkflowRequestDTO: { + inputData: { + [key: string]: string; + }; + }; + ExecuteWorkflowResponseDTO: { + id?: string; + }; + WorkflowSpecFileDTO: { + path?: string; + /** @description JSON string */ + content: string; + }; + WorkflowProgressDTO: components['schemas']['NodeInstanceDTO'] & { + status?: components['schemas']['ProcessInstanceStatusDTO']; + error?: components['schemas']['ProcessInstanceErrorDTO']; + }; + NodeInstanceDTO: { + /** + * @description Type name + * @default NodeInstance + */ + __typename?: string; + /** @description Node instance ID */ + id?: string; + /** @description Node name */ + name?: string; + /** @description Node type */ + type?: string; + /** + * Format: date-time + * @description Date when the node was entered + */ + enter?: string; + /** + * Format: date-time + * @description Date when the node was exited (optional) + */ + exit?: string; + /** @description Definition ID */ + definitionId?: string; + /** @description Node ID */ + nodeId?: string; + }; + ProcessInstanceErrorDTO: { + /** + * @description Type name + * @default ProcessInstanceError + */ + __typename?: string; + /** @description Node definition ID */ + nodeDefinitionId?: string; + /** @description Error message (optional) */ + message?: string; + }; }; responses: never; parameters: never; @@ -59,13 +222,240 @@ export interface operations { }; /** @description Error fetching workflow overviews */ 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** @description Get a workflow overview by ID */ + getWorkflowOverviewById: { + parameters: { + path: { + /** @description Unique identifier of the workflow */ + workflowId: string; + }; + }; + responses: { + /** @description Success */ + 200: { + content: { + 'application/json': components['schemas']['WorkflowOverviewDTO']; + }; + }; + /** @description Error fetching workflow overview */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** @description Get a list of workflow */ + getWorkflows: { + responses: { + /** @description Success */ + 200: { + content: { + 'application/json': components['schemas']['WorkflowListResultDTO']; + }; + }; + /** @description Error fetching workflow list */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** Create or update a workflow */ + createWorkflow: { + parameters: { + query?: { + /** @description URI parameter */ + uri?: string; + }; + }; + requestBody: { + content: { + 'application/json': { + uri: string; + body?: string; + }; + }; + }; + responses: { + /** @description Created */ + 201: { content: { 'application/json': { - /** @description Error message */ - message?: string; + workflowItem?: components['schemas']['WorkflowDTO']; }; }; }; + /** @description Error creating workflow */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** + * Get instances + * @description Retrieve an array of instances + */ + getInstances: { + responses: { + /** @description Success */ + 200: { + content: { + 'application/json': components['schemas']['ProcessInstancesDTO']; + }; + }; + /** @description Error fetching instances */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** Get Workflow Instance by ID */ + getInstanceById: { + parameters: { + path: { + /** @description ID of the workflow instance */ + instanceId: string; + }; + }; + responses: { + /** @description Successful response */ + 200: { + content: { + 'application/json': components['schemas']['ProcessInstanceDTO']; + }; + }; + /** @description Error fetching instance */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** Get workflow results */ + getWorkflowResults: { + parameters: { + path: { + /** @description ID of the workflow instance */ + instanceId: string; + }; + }; + responses: { + /** @description Successful response */ + 200: { + content: { + 'application/json': components['schemas']['WorkflowDataDTO']; + }; + }; + /** @description Error getting workflow results */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** + * Get workflow status list + * @description Retrieve an array of workflow statuses + */ + getWorkflowStatuses: { + responses: { + /** @description Success */ + 200: { + content: { + 'application/json': components['schemas']['WorkflowRunStatusDTO'][]; + }; + }; + /** @description Error fetching workflow statuses */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** Execute a workflow */ + executeWorkflow: { + parameters: { + path: { + /** @description ID of the workflow to execute */ + workflowId: string; + }; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ExecuteWorkflowRequestDTO']; + }; + }; + responses: { + /** @description Successful execution */ + 200: { + content: { + 'application/json': components['schemas']['ExecuteWorkflowResponseDTO']; + }; + }; + /** @description Internal Server Error */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** + * Abort a workflow instance + * @description Aborts a workflow instance identified by the provided workflowId. + */ + abortWorkflow: { + parameters: { + path: { + /** @description The identifier of the workflow instance to abort. */ + workflowId: string; + }; + }; + responses: { + /** @description Successful operation */ + 200: { + content: { + 'text/plain': string; + }; + }; + /** @description Error aborting workflow */ + 500: { + content: { + 'text/plain': string; + }; + }; + }; + }; + /** Get workflow specifications */ + getWorkflowSpecs: { + responses: { + /** @description Successful retrieval of workflow specifications */ + 200: { + content: { + 'application/json': components['schemas']['WorkflowSpecFileDTO'][]; + }; + }; + /** @description Error fetching workflow specifications */ + 500: { + content: { + 'text/plain': string; + }; + }; }; }; } diff --git a/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator-ignore b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator-ignore new file mode 100644 index 0000000000..7484ee590a --- /dev/null +++ b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator/FILES b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator/FILES new file mode 100644 index 0000000000..94a153d20e --- /dev/null +++ b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator/FILES @@ -0,0 +1 @@ +index.adoc diff --git a/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator/VERSION b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator/VERSION new file mode 100644 index 0000000000..8b23b8d47c --- /dev/null +++ b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.3.0 \ No newline at end of file diff --git a/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/index.adoc b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/index.adoc new file mode 100644 index 0000000000..e3ec6b6c07 --- /dev/null +++ b/plugins/orchestrator-common/src/auto-generated/docs/index.adoc/index.adoc @@ -0,0 +1,1613 @@ += Orchestrator plugin +team@openapitools.org +0.0.1 +:toc: left +:numbered: +:toclevels: 4 +:source-highlighter: highlightjs +:keywords: openapi, rest, Orchestrator plugin +:specDir: +:snippetDir: +:generator-template: v1 2019-12-20 +:info-url: https://openapi-generator.tech +:app-name: Orchestrator plugin + +[abstract] +.Abstract +API to interact with orchestrator plugin + + +// markup not found, no include::{specDir}intro.adoc[opts=optional] + + + +== Endpoints + + +[.Default] +=== Default + + +[.abortWorkflow] +==== abortWorkflow + +`DELETE /v2/workflows/{workflowId}/abort` + +Abort a workflow instance + +===== Description + +Aborts a workflow instance identified by the provided workflowId. + + +// markup not found, no include::{specDir}v2/workflows/\{workflowId\}/abort/DELETE/spec.adoc[opts=optional] + + + +===== Parameters + +====== Path Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| workflowId +| The identifier of the workflow instance to abort. +| X +| null +| + +|=== + + + + + + +===== Return Type + + +<> + + +===== Content Type + +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Successful operation +| <> + + +| 500 +| Error aborting workflow +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/\{workflowId\}/abort/DELETE/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/\{workflowId\}/abort/DELETE/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/{workflowId}/abort/DELETE/DELETE.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/\{workflowId\}/abort/DELETE/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.createWorkflow] +==== createWorkflow + +`POST /v2/workflows` + +Create or update a workflow + +===== Description + + + + +// markup not found, no include::{specDir}v2/workflows/POST/spec.adoc[opts=optional] + + + +===== Parameters + + +====== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| CreateWorkflowRequest +| <> +| X +| +| + +|=== + + + +====== Query Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| uri +| URI parameter +| - +| null +| + +|=== + + +===== Return Type + +<> + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 201 +| Created +| <> + + +| 500 +| Error creating workflow +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/POST/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/POST/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/POST/POST.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/POST/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.executeWorkflow] +==== executeWorkflow + +`POST /v2/workflows/{workflowId}/execute` + +Execute a workflow + +===== Description + + + + +// markup not found, no include::{specDir}v2/workflows/\{workflowId\}/execute/POST/spec.adoc[opts=optional] + + + +===== Parameters + +====== Path Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| workflowId +| ID of the workflow to execute +| X +| null +| + +|=== + +====== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| ExecuteWorkflowRequestDTO +| <> +| X +| +| + +|=== + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Successful execution +| <> + + +| 500 +| Internal Server Error +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/\{workflowId\}/execute/POST/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/\{workflowId\}/execute/POST/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/{workflowId}/execute/POST/POST.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/\{workflowId\}/execute/POST/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getInstanceById] +==== getInstanceById + +`GET /v2/workflows/instances/{instanceId}` + +Get Workflow Instance by ID + +===== Description + + + + +// markup not found, no include::{specDir}v2/workflows/instances/\{instanceId\}/GET/spec.adoc[opts=optional] + + + +===== Parameters + +====== Path Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| instanceId +| ID of the workflow instance +| X +| null +| + +|=== + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Successful response +| <> + + +| 500 +| Error fetching instance +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/instances/\{instanceId\}/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/instances/\{instanceId\}/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/instances/{instanceId}/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/instances/\{instanceId\}/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getInstances] +==== getInstances + +`GET /v2/workflows/instances` + +Get instances + +===== Description + +Retrieve an array of instances + + +// markup not found, no include::{specDir}v2/workflows/instances/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +array[<>] + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Success +| List[<>] + + +| 500 +| Error fetching instances +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/instances/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/instances/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/instances/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/instances/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getWorkflowOverviewById] +==== getWorkflowOverviewById + +`GET /v2/workflows/{workflowId}/overview` + + + +===== Description + +Get a workflow overview by ID + + +// markup not found, no include::{specDir}v2/workflows/\{workflowId\}/overview/GET/spec.adoc[opts=optional] + + + +===== Parameters + +====== Path Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| workflowId +| Unique identifier of the workflow +| X +| null +| + +|=== + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Success +| <> + + +| 500 +| Error fetching workflow overview +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/\{workflowId\}/overview/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/\{workflowId\}/overview/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/{workflowId}/overview/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/\{workflowId\}/overview/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getWorkflowResults] +==== getWorkflowResults + +`GET /v2/workflows/instances/{instanceId}/results` + +Get workflow results + +===== Description + + + + +// markup not found, no include::{specDir}v2/workflows/instances/\{instanceId\}/results/GET/spec.adoc[opts=optional] + + + +===== Parameters + +====== Path Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| instanceId +| ID of the workflow instance +| X +| null +| + +|=== + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Successful response +| <> + + +| 500 +| Error getting workflow results +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/instances/\{instanceId\}/results/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/instances/\{instanceId\}/results/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/instances/{instanceId}/results/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/instances/\{instanceId\}/results/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getWorkflowSpecs] +==== getWorkflowSpecs + +`GET /v2/specs` + +Get workflow specifications + +===== Description + + + + +// markup not found, no include::{specDir}v2/specs/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +array[<>] + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Successful retrieval of workflow specifications +| List[<>] + + +| 500 +| Error fetching workflow specifications +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/specs/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/specs/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/specs/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/specs/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getWorkflowStatuses] +==== getWorkflowStatuses + +`GET /v2/workflows/instances/statuses` + +Get workflow status list + +===== Description + +Retrieve an array of workflow statuses + + +// markup not found, no include::{specDir}v2/workflows/instances/statuses/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +array[<>] + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Success +| List[<>] + + +| 500 +| Error fetching workflow statuses +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/instances/statuses/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/instances/statuses/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/instances/statuses/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/instances/statuses/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getWorkflows] +==== getWorkflows + +`GET /v2/workflows` + + + +===== Description + +Get a list of workflow + + +// markup not found, no include::{specDir}v2/workflows/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Success +| <> + + +| 500 +| Error fetching workflow list +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getWorkflowsOverview] +==== getWorkflowsOverview + +`GET /v2/workflows/overview` + + + +===== Description + +Get a list of workflow overviews + + +// markup not found, no include::{specDir}v2/workflows/overview/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json +* text/plain + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Success +| <> + + +| 500 +| Error fetching workflow overviews +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}v2/workflows/overview/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}v2/workflows/overview/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :v2/workflows/overview/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}v2/workflows/overview/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[#models] +== Models + + +[#AssessedProcessInstanceDTO] +=== _AssessedProcessInstanceDTO_ + + + +[.fields-AssessedProcessInstanceDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| instance +| X +| ProcessInstanceDTO +| +| + +| assessedBy +| +| ProcessInstanceDTO +| +| + +|=== + + +[#CreateWorkflow201Response] +=== _CreateWorkflow201Response_ + + + +[.fields-CreateWorkflow201Response] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| workflowItem +| +| WorkflowDTO +| +| + +|=== + + +[#CreateWorkflowRequest] +=== _CreateWorkflowRequest_ + + + +[.fields-CreateWorkflowRequest] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| uri +| X +| String +| +| + +| body +| +| String +| +| + +|=== + + +[#ExecuteWorkflowRequestDTO] +=== _ExecuteWorkflowRequestDTO_ + + + +[.fields-ExecuteWorkflowRequestDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| inputData +| X +| Map of <> +| +| + +|=== + + +[#ExecuteWorkflowResponseDTO] +=== _ExecuteWorkflowResponseDTO_ + + + +[.fields-ExecuteWorkflowResponseDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| id +| +| String +| +| + +|=== + + +[#NodeInstanceDTO] +=== _NodeInstanceDTO_ + + + +[.fields-NodeInstanceDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| __typename +| +| String +| Type name +| + +| id +| +| String +| Node instance ID +| + +| name +| +| String +| Node name +| + +| type +| +| String +| Node type +| + +| enter +| +| Date +| Date when the node was entered +| date-time + +| exit +| +| Date +| Date when the node was exited (optional) +| date-time + +| definitionId +| +| String +| Definition ID +| + +| nodeId +| +| String +| Node ID +| + +|=== + + +[#PaginationInfoDTO] +=== _PaginationInfoDTO_ + + + +[.fields-PaginationInfoDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| limit +| +| BigDecimal +| +| + +| offset +| +| BigDecimal +| +| + +| totalCount +| +| BigDecimal +| +| + +|=== + + +[#ProcessInstanceDTO] +=== _ProcessInstanceDTO_ + + + +[.fields-ProcessInstanceDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| id +| +| String +| +| + +| name +| +| String +| +| + +| workflow +| +| String +| +| + +| status +| +| ProcessInstanceStatusDTO +| +| + +| started +| +| Date +| +| date-time + +| duration +| +| String +| +| + +| category +| +| WorkflowCategoryDTO +| +| + +| description +| +| String +| +| + +| workflowdata +| +| WorkflowDataDTO +| +| + +|=== + + +[#ProcessInstanceErrorDTO] +=== _ProcessInstanceErrorDTO_ + + + +[.fields-ProcessInstanceErrorDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| __typename +| +| String +| Type name +| + +| nodeDefinitionId +| +| String +| Node definition ID +| + +| message +| +| String +| Error message (optional) +| + +|=== + + +[#ProcessInstanceStatusDTO] +=== _ProcessInstanceStatusDTO_ + +Status of the workflow run + +[.fields-ProcessInstanceStatusDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +|=== + + +[#WorkflowCategoryDTO] +=== _WorkflowCategoryDTO_ + +Category of the workflow + +[.fields-WorkflowCategoryDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +|=== + + +[#WorkflowDTO] +=== _WorkflowDTO_ + + + +[.fields-WorkflowDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| id +| X +| String +| Workflow unique identifier +| + +| name +| +| String +| Workflow name +| + +| uri +| X +| String +| URI of the workflow definition +| + +| category +| X +| WorkflowCategoryDTO +| +| + +| description +| +| String +| Description of the workflow +| + +| annotations +| +| List of <> +| +| + +|=== + + +[#WorkflowDataDTO] +=== _WorkflowDataDTO_ + + + +[.fields-WorkflowDataDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| workflowoptions +| +| List of <> +| +| + +|=== + + +[#WorkflowListResultDTO] +=== _WorkflowListResultDTO_ + + + +[.fields-WorkflowListResultDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| items +| X +| List of <> +| +| + +| paginationInfo +| X +| PaginationInfoDTO +| +| + +|=== + + +[#WorkflowOverviewDTO] +=== _WorkflowOverviewDTO_ + + + +[.fields-WorkflowOverviewDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| workflowId +| +| String +| Workflow unique identifier +| + +| name +| +| String +| Workflow name +| + +| uri +| +| String +| +| + +| lastTriggeredMs +| +| BigDecimal +| +| + +| lastRunStatus +| +| String +| +| + +| category +| +| WorkflowCategoryDTO +| +| + +| avgDurationMs +| +| BigDecimal +| +| + +| description +| +| String +| +| + +|=== + + +[#WorkflowOverviewListResultDTO] +=== _WorkflowOverviewListResultDTO_ + + + +[.fields-WorkflowOverviewListResultDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| overviews +| +| List of <> +| +| + +| paginationInfo +| +| PaginationInfoDTO +| +| + +|=== + + +[#WorkflowProgressDTO] +=== _WorkflowProgressDTO_ + + + +[.fields-WorkflowProgressDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| __typename +| +| oas_any_type_not_mapped +| Type name +| + +| id +| +| oas_any_type_not_mapped +| Node instance ID +| + +| name +| +| oas_any_type_not_mapped +| Node name +| + +| type +| +| oas_any_type_not_mapped +| Node type +| + +| enter +| +| oas_any_type_not_mapped +| Date when the node was entered +| date-time + +| exit +| +| oas_any_type_not_mapped +| Date when the node was exited (optional) +| date-time + +| definitionId +| +| oas_any_type_not_mapped +| Definition ID +| + +| nodeId +| +| oas_any_type_not_mapped +| Node ID +| + +| status +| +| ProcessInstanceStatusDTO +| +| + +| error +| +| ProcessInstanceErrorDTO +| +| + +|=== + + +[#WorkflowRunStatusDTO] +=== _WorkflowRunStatusDTO_ + + + +[.fields-WorkflowRunStatusDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| key +| +| String +| +| + +| value +| +| String +| +| + +|=== + + +[#WorkflowSpecFileDTO] +=== _WorkflowSpecFileDTO_ + + + +[.fields-WorkflowSpecFileDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| path +| +| String +| +| + +| content +| X +| String +| JSON string +| + +|=== + + +[#WorkflowSuggestionDTO] +=== _WorkflowSuggestionDTO_ + + + +[.fields-WorkflowSuggestionDTO] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| id +| +| String +| +| + +| name +| +| String +| +| + +|=== + + diff --git a/plugins/orchestrator-common/src/openapi/openapi.yaml b/plugins/orchestrator-common/src/openapi/openapi.yaml index b5c3517323..ec87f248cd 100644 --- a/plugins/orchestrator-common/src/openapi/openapi.yaml +++ b/plugins/orchestrator-common/src/openapi/openapi.yaml @@ -22,14 +22,249 @@ paths: $ref: '#/components/schemas/WorkflowOverviewListResultDTO' '500': description: Error fetching workflow overviews + content: + text/plain: + schema: + type: string + /v2/workflows/{workflowId}/overview: + get: + operationId: getWorkflowOverviewById + description: Get a workflow overview by ID + parameters: + - name: workflowId + in: path + required: true + description: Unique identifier of the workflow + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/WorkflowOverviewDTO' + '500': + description: Error fetching workflow overview + content: + text/plain: + schema: + type: string + /v2/workflows: + get: + operationId: getWorkflows + description: Get a list of workflow + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/WorkflowListResultDTO' + '500': + description: Error fetching workflow list + content: + text/plain: + schema: + type: string + post: + operationId: createWorkflow + summary: Create or update a workflow + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + uri: + type: string + body: + type: string + required: + - uri + parameters: + - name: uri + in: query + description: URI parameter + schema: + type: string + responses: + '201': + description: Created content: application/json: schema: type: object properties: - message: - type: string - description: Error message + workflowItem: + $ref: '#/components/schemas/WorkflowDTO' + '500': + description: Error creating workflow + content: + text/plain: + schema: + type: string + /v2/workflows/instances: + get: + operationId: getInstances + summary: Get instances + description: Retrieve an array of instances + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ProcessInstancesDTO' + '500': + description: Error fetching instances + content: + text/plain: + schema: + type: string + /v2/workflows/instances/{instanceId}: + get: + summary: Get Workflow Instance by ID + operationId: getInstanceById + parameters: + - name: instanceId + in: path + required: true + description: ID of the workflow instance + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/ProcessInstanceDTO' + '500': + description: Error fetching instance + content: + text/plain: + schema: + type: string + /v2/workflows/instances/{instanceId}/results: + get: + summary: Get workflow results + operationId: getWorkflowResults + parameters: + - name: instanceId + in: path + required: true + description: ID of the workflow instance + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/WorkflowDataDTO' + '500': + description: Error getting workflow results + content: + text/plain: + schema: + type: string + /v2/workflows/instances/statuses: + get: + operationId: getWorkflowStatuses + summary: Get workflow status list + description: Retrieve an array of workflow statuses + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WorkflowRunStatusDTO' + '500': + description: Error fetching workflow statuses + content: + text/plain: + schema: + type: string + /v2/workflows/{workflowId}/execute: + post: + summary: Execute a workflow + operationId: executeWorkflow + parameters: + - name: workflowId + in: path + description: ID of the workflow to execute + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteWorkflowRequestDTO' + responses: + '200': + description: Successful execution + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteWorkflowResponseDTO' + '500': + description: Internal Server Error + content: + text/plain: + schema: + type: string + /v2/workflows/{workflowId}/abort: + delete: + summary: Abort a workflow instance + operationId: abortWorkflow + description: Aborts a workflow instance identified by the provided workflowId. + parameters: + - name: workflowId + in: path + required: true + description: The identifier of the workflow instance to abort. + schema: + type: string + responses: + '200': + description: Successful operation + content: + text/plain: + schema: + type: string + '500': + description: Error aborting workflow + content: + text/plain: + schema: + type: string + /v2/specs: + get: + summary: Get workflow specifications + operationId: getWorkflowSpecs + responses: + '200': + description: Successful retrieval of workflow specifications + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WorkflowSpecFileDTO' + '500': + description: Error fetching workflow specifications + content: + text/plain: + schema: + type: string components: schemas: WorkflowOverviewListResultDTO: @@ -59,9 +294,8 @@ components: minimum: 0 lastRunStatus: type: string - type: - type: string - minimum: 0 + category: + $ref: '#/components/schemas/WorkflowCategoryDTO' avgDurationMs: type: number minimum: 0 @@ -80,3 +314,194 @@ components: type: number minimum: 0 additionalProperties: false + WorkflowCategoryDTO: + type: string + description: Category of the workflow + enum: + - assessment + - infrastructure + WorkflowListResultDTO: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/WorkflowDTO' + paginationInfo: + $ref: '#/components/schemas/PaginationInfoDTO' + required: + - items + - paginationInfo + WorkflowDTO: + type: object + properties: + id: + type: string + description: Workflow unique identifier + minLength: 1 + name: + type: string + description: Workflow name + minLength: 1 + uri: + type: string + description: URI of the workflow definition + category: + $ref: '#/components/schemas/WorkflowCategoryDTO' + description: + type: string + description: Description of the workflow + annotations: + type: array + items: + type: string + required: + - id + - category + - uri + ProcessInstancesDTO: + type: array + items: + $ref: '#/components/schemas/ProcessInstanceDTO' + AssessedProcessInstanceDTO: + type: object + properties: + instance: + $ref: '#/components/schemas/ProcessInstanceDTO' + assessedBy: + $ref: '#/components/schemas/ProcessInstanceDTO' + required: + - instance + ProcessInstanceDTO: + type: object + properties: + id: + type: string + name: + type: string + workflow: + type: string + status: + $ref: '#/components/schemas/ProcessInstanceStatusDTO' + started: + type: string + format: date-time + duration: + type: string + category: + $ref: '#/components/schemas/WorkflowCategoryDTO' + description: + type: string + workflowdata: + $ref: '#/components/schemas/WorkflowDataDTO' + WorkflowDataDTO: + type: object + properties: + workflowoptions: + type: array + items: + $ref: '#/components/schemas/WorkflowOptionsDTO' + additionalProperties: true + WorkflowOptionsDTO: + type: array + items: + $ref: '#/components/schemas/WorkflowSuggestionDTO' + WorkflowSuggestionDTO: + type: object + properties: + id: + type: string + name: + type: string + ProcessInstanceStatusDTO: + type: string + description: Status of the workflow run + enum: + - Running + - Error + - Completed + - Aborted + - Suspended + WorkflowRunStatusDTO: + type: object + properties: + key: + type: string + value: + type: string + ExecuteWorkflowRequestDTO: + type: object + properties: + inputData: + type: object + additionalProperties: + type: string + required: + - inputData + ExecuteWorkflowResponseDTO: + type: object + properties: + id: + type: string + WorkflowSpecFileDTO: + type: object + properties: + path: + type: string + content: + type: string + description: JSON string + required: + - content + WorkflowProgressDTO: + allOf: + - $ref: '#/components/schemas/NodeInstanceDTO' + - type: object + properties: + status: + $ref: '#/components/schemas/ProcessInstanceStatusDTO' + error: + $ref: '#/components/schemas/ProcessInstanceErrorDTO' + NodeInstanceDTO: + type: object + properties: + __typename: + type: string + default: 'NodeInstance' + description: Type name + id: + type: string + description: Node instance ID + name: + type: string + description: Node name + type: + type: string + description: Node type + enter: + type: string + format: date-time + description: Date when the node was entered + exit: + type: string + format: date-time + description: Date when the node was exited (optional) + definitionId: + type: string + description: Definition ID + nodeId: + type: string + description: Node ID + ProcessInstanceErrorDTO: + type: object + properties: + __typename: + type: string + default: 'ProcessInstanceError' + description: Type name + nodeDefinitionId: + type: string + description: Node definition ID + message: + type: string + description: Error message (optional) diff --git a/plugins/orchestrator-common/src/openapi/types.ts b/plugins/orchestrator-common/src/openapi/types.ts index d51aebd49d..7d37ee53ff 100644 --- a/plugins/orchestrator-common/src/openapi/types.ts +++ b/plugins/orchestrator-common/src/openapi/types.ts @@ -3,3 +3,21 @@ import { components } from '../auto-generated/api/models/schema'; export type WorkflowOverviewListResultDTO = components['schemas']['WorkflowOverviewListResultDTO']; export type WorkflowOverviewDTO = components['schemas']['WorkflowOverviewDTO']; +export type WorkflowDTO = components['schemas']['WorkflowDTO']; +export type WorkflowListResultDTO = + components['schemas']['WorkflowListResultDTO']; +export type ProcessInstanceDTO = components['schemas']['ProcessInstanceDTO']; +export type ProcessInstancesDTO = components['schemas']['ProcessInstancesDTO']; +export type AssessedProcessInstanceDTO = + components['schemas']['AssessedProcessInstanceDTO']; +export type ExecuteWorkflowRequestDTO = + components['schemas']['ExecuteWorkflowRequestDTO']; +export type ExecuteWorkflowResponseDTO = + components['schemas']['ExecuteWorkflowResponseDTO']; +export type WorkflowDataDTO = components['schemas']['WorkflowDataDTO']; +export type WorkflowSpecFileDTO = components['schemas']['WorkflowSpecFileDTO']; +export type ProcessInstanceStatusDTO = + components['schemas']['ProcessInstanceStatusDTO']; +export type WorkflowCategoryDTO = components['schemas']['WorkflowCategoryDTO']; +export type WorkflowRunStatusDTO = + components['schemas']['WorkflowRunStatusDTO'];