diff --git a/Common/Types/Workflow/ComponentID.ts b/Common/Types/Workflow/ComponentID.ts index fd991cb5b7..75ea15576b 100644 --- a/Common/Types/Workflow/ComponentID.ts +++ b/Common/Types/Workflow/ComponentID.ts @@ -3,6 +3,7 @@ enum ComponentID { Log = 'log', Schedule = 'schedule', JavaScriptCode = 'javascript', + Manual = 'manual' } export default ComponentID; diff --git a/Common/Types/Workflow/Components/Manual.ts b/Common/Types/Workflow/Components/Manual.ts index 769f7c49e4..0fcacfcf43 100644 --- a/Common/Types/Workflow/Components/Manual.ts +++ b/Common/Types/Workflow/Components/Manual.ts @@ -1,4 +1,5 @@ import IconProp from '../../Icon/IconProp'; +import ComponentID from '../ComponentID'; import ComponentMetadata, { ComponentInputType, ComponentType, @@ -6,7 +7,7 @@ import ComponentMetadata, { const components: Array = [ { - id: 'manual', + id: ComponentID.Manual, title: 'Manual', category: 'Utils', description: 'Run this workflow manually', diff --git a/CommonServer/Services/WorkflowService.ts b/CommonServer/Services/WorkflowService.ts index afb012a971..d22a5425b7 100644 --- a/CommonServer/Services/WorkflowService.ts +++ b/CommonServer/Services/WorkflowService.ts @@ -8,6 +8,13 @@ import { NodeDataProp, NodeType, } from 'Common/Types/Workflow/Component'; +import API from 'Common/Utils/API'; +import EmptyResponseData from 'Common/Types/API/EmptyResponse'; +import URL from 'Common/Types/API/URL'; +import Protocol from 'Common/Types/API/Protocol'; +import { WorkflowHostname } from '../Config'; +import Route from 'Common/Types/API/Route'; +import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization'; export class Service extends DatabaseService { public constructor(postgresDatabase?: PostgresDatabase) { @@ -56,6 +63,14 @@ export class Service extends DatabaseService { }, }); + await API.post( + new URL(Protocol.HTTP, WorkflowHostname, new Route('/workflow/update/'+onUpdate.updateBy.query._id!)), + {}, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + } + ); + return onUpdate; } } diff --git a/CommonServer/Types/Workflow/ComponentCode.ts b/CommonServer/Types/Workflow/ComponentCode.ts index 3e3e751792..bf7353a525 100644 --- a/CommonServer/Types/Workflow/ComponentCode.ts +++ b/CommonServer/Types/Workflow/ComponentCode.ts @@ -42,9 +42,20 @@ export interface InitProps { ) => Promise; } +export interface UpdateProps { + workflowId: ObjectID; +} + export default class ComponentCode { private metadata: ComponentMetadata | null = null; + public executeWorkflow: ((executeWorkflow: ExecuteWorkflowType) => Promise) | null = null; + + public scheduleWorkflow: (( + executeWorkflow: ExecuteWorkflowType, + scheduleAt: string + ) => Promise) | null = null; + public constructor() {} public setMetadata(metadata: ComponentMetadata): void { @@ -59,10 +70,22 @@ export default class ComponentCode { return this.metadata; } + public async setupComponent(props: InitProps): Promise { + + this.executeWorkflow = props.executeWorkflow; + this.scheduleWorkflow = props.scheduleWorkflow; + + return await this.init(props); + } + public async init(_props: InitProps): Promise { return await Promise.resolve(); } + public async update(_props: UpdateProps): Promise { + return await Promise.resolve(); + } + public async run( _args: JSONObject, _options: RunOptions diff --git a/CommonServer/Types/Workflow/Components/Index.ts b/CommonServer/Types/Workflow/Components/Index.ts index a15150334a..b3fc7e7292 100644 --- a/CommonServer/Types/Workflow/Components/Index.ts +++ b/CommonServer/Types/Workflow/Components/Index.ts @@ -19,12 +19,14 @@ import UpdateManyBaseModel from './UpdateManyBaseModel'; import OnDeleteBaseModel from './OnDeleteBaseModel'; import DeleteOneBaseModel from './DeleteOneBaseModel'; import DeleteManyBaseModel from './DeleteManyBaseMoidel'; +import ManualTrigger from './Manual'; const Components: Dictionary = { [ComponentID.Webhook]: new WebhookTrigger(), [ComponentID.Log]: new Log(), [ComponentID.Schedule]: new Schedule(), [ComponentID.JavaScriptCode]: new JavaScirptCode(), + [ComponentID.Manual]: new ManualTrigger() }; for (const baseModelService of BaseModelServices) { diff --git a/CommonServer/Types/Workflow/Components/Manual.ts b/CommonServer/Types/Workflow/Components/Manual.ts new file mode 100644 index 0000000000..baab1ef3ae --- /dev/null +++ b/CommonServer/Types/Workflow/Components/Manual.ts @@ -0,0 +1,26 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import ComponentMetadata from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import ManualComponents from 'Common/Types/Workflow/Components/Manual'; +import ComponentCode, { + InitProps, +} from '../ComponentCode'; + +export default class ManualTrigger extends ComponentCode { + public constructor() { + super(); + const Component: ComponentMetadata | undefined = + ManualComponents.find((i: ComponentMetadata) => { + return i.id === ComponentID.Manual; + }); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + this.setMetadata(Component); + } + + public override async init(_props: InitProps): Promise { + // do nothing because its a manual component. + } +} diff --git a/CommonServer/Types/Workflow/Components/Schedule.ts b/CommonServer/Types/Workflow/Components/Schedule.ts index 4fbc295d0b..40dcae9689 100644 --- a/CommonServer/Types/Workflow/Components/Schedule.ts +++ b/CommonServer/Types/Workflow/Components/Schedule.ts @@ -10,6 +10,7 @@ import QueryHelper from '../../Database/QueryHelper'; import ComponentCode, { ExecuteWorkflowType, InitProps, + UpdateProps, } from '../ComponentCode'; export default class WebhookTrigger extends ComponentCode { @@ -61,4 +62,44 @@ export default class WebhookTrigger extends ComponentCode { } } } + + public override async update(props: UpdateProps): Promise { + const workflow: Workflow | null = await WorkflowService.findOneBy({ + query: { + triggerId: ComponentID.Schedule, + _id: props.workflowId.toString(), + triggerArguments: QueryHelper.notNull(), + }, + select: { + _id: true, + triggerArguments: true, + }, + props: { + isRoot: true, + } + }); + + if(!workflow){ + return; + } + + if(!this.scheduleWorkflow){ + return; + } + + const executeWorkflow: ExecuteWorkflowType = { + workflowId: new ObjectID(workflow._id!), + returnValues: {}, + }; + + if ( + workflow.triggerArguments && + workflow.triggerArguments['schedule'] + ) { + await this.scheduleWorkflow( + executeWorkflow, + workflow.triggerArguments['schedule'] as string + ); + } + } } diff --git a/CommonUI/src/Components/Workflow/Utils.ts b/CommonUI/src/Components/Workflow/Utils.ts index bb6b6091ae..2633c4cc89 100644 --- a/CommonUI/src/Components/Workflow/Utils.ts +++ b/CommonUI/src/Components/Workflow/Utils.ts @@ -64,6 +64,10 @@ export const componentInputTypeToFormFieldType: Function = ( return { fieldType: FormFieldSchemaType.Dropdown, dropdownOptions: [ + { + label: 'Every Minute', + value: '* * * * *', + }, { label: 'Every 30 minutes', value: '*/30 * * * *', diff --git a/Workflow/API/Workflow.ts b/Workflow/API/Workflow.ts new file mode 100644 index 0000000000..9ed89a7c14 --- /dev/null +++ b/Workflow/API/Workflow.ts @@ -0,0 +1,74 @@ +import Express, { + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from 'CommonServer/Utils/Express'; +import Response from 'CommonServer/Utils/Response'; +import ObjectID from 'Common/Types/ObjectID'; +import BadDataException from 'Common/Types/Exception/BadDataException'; +import WorkflowService from 'CommonServer/Services/WorkflowService'; +import Workflow from 'Model/Models/Workflow'; +import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; +import ComponentCode from 'CommonServer/Types/Workflow/ComponentCode'; +import Components from 'CommonServer/Types/Workflow/Components/Index'; + +export default class WorkflowAPI { + public router!: ExpressRouter; + + public constructor() { + this.router = Express.getRouter(); + + this.router.get(`/update/:workflowId`, ClusterKeyAuthorization.isAuthorizedServiceMiddleware, this.updateWorkflow); + + this.router.post(`/update/:workflowId`, ClusterKeyAuthorization.isAuthorizedServiceMiddleware, this.updateWorkflow); + } + + public async updateWorkflow( + req: ExpressRequest, + res: ExpressResponse + ): Promise { + // add this workflow to the run queue and return the 200 response. + + if (!req.params['workflowId']) { + return Response.sendErrorResponse( + req, + res, + new BadDataException('workflowId not found in URL') + ); + } + + const workflow: Workflow | null = await WorkflowService.findOneById({ + id: new ObjectID(req.params['workflowId']), + select: { + _id: true, + triggerId: true, + }, + props: { + isRoot: true, + } + }); + + if(!workflow){ + return Response.sendJsonObjectResponse(req, res, { + status: 'Workflow not found', + }); + } + + + const componentCode: ComponentCode | undefined = Components[workflow.triggerId as string]; + + if(!componentCode){ + return Response.sendJsonObjectResponse(req, res, { + status: 'Component not found', + }); + } + + await componentCode.update({ + workflowId: workflow.id! + }); + + return Response.sendJsonObjectResponse(req, res, { + status: 'Updated', + }); + } +} diff --git a/Workflow/Index.ts b/Workflow/Index.ts index 4c356bb463..a81f9665fd 100644 --- a/Workflow/Index.ts +++ b/Workflow/Index.ts @@ -14,6 +14,7 @@ import QueueWorker from 'CommonServer/Infrastructure/QueueWorker'; import RunWorkflow from './Services/RunWorkflow'; import { JSONObject } from 'Common/Types/JSON'; import ObjectID from 'Common/Types/ObjectID'; +import WorkflowAPI from './API/Workflow'; const APP_NAME: string = 'workflow'; @@ -21,6 +22,8 @@ const app: ExpressApplication = Express.getExpressApp(); app.use(`/${APP_NAME}/manual`, new ManualAPI().router); +app.use(`/${APP_NAME}`, new WorkflowAPI().router); + app.get( `/${APP_NAME}/docs/:componentName`, (req: ExpressRequest, res: ExpressResponse) => {