From 6b799158aa7b5ddf0c9e9059a8df654326bd2816 Mon Sep 17 00:00:00 2001 From: George Djabarov <39195835+djabarovgeorge@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:16:07 +0200 Subject: [PATCH] feat(api): expose payload schema on get step response (#6867) Co-authored-by: GalT <39020298+tatarco@users.noreply.github.com> --- .../get-step-schema/get-step-data.usecase.ts | 41 +++++++++++++++++-- .../workflows-v2/workflow.controller.e2e.ts | 38 +++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/apps/api/src/app/workflows-v2/usecases/get-step-schema/get-step-data.usecase.ts b/apps/api/src/app/workflows-v2/usecases/get-step-schema/get-step-data.usecase.ts index 6c36ca6ffc5..e4f731acab7 100644 --- a/apps/api/src/app/workflows-v2/usecases/get-step-schema/get-step-data.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/get-step-schema/get-step-data.usecase.ts @@ -6,11 +6,13 @@ import { GetStepDataCommand } from './get-step-data.command'; import { mapStepTypeToResult } from '../../shared'; import { GetWorkflowByIdsUseCase } from '../get-workflow-by-ids/get-workflow-by-ids.usecase'; import { InvalidStepException } from '../../exceptions/invalid-step.exception'; +import { BuildDefaultPayloadUseCase } from '../build-payload-from-placeholder'; @Injectable() export class GetStepDataUsecase { constructor( private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase, + private buildDefaultPayloadUseCase: BuildDefaultPayloadUseCase, private controlValuesRepository: ControlValuesRepository ) {} @@ -21,20 +23,30 @@ export class GetStepDataUsecase { if (!currentStep.name || !currentStep._templateId || !currentStep.stepId) { throw new InvalidStepException(currentStep); } + const controlValues = await this.getValues(command, currentStep, workflow._id); + const payloadSchema = this.buildPayloadSchema(controlValues); return { controls: { dataSchema: currentStep.template?.controls?.schema, uiSchema: currentStep.template?.controls?.uiSchema, - values: await this.getValues(command, currentStep, workflow._id), + values: controlValues, }, - variables: buildVariablesSchema(previousSteps), + variables: buildVariablesSchema(previousSteps, payloadSchema), name: currentStep.name, _id: currentStep._templateId, stepId: currentStep.stepId, }; } + private buildPayloadSchema(controlValues: Record) { + const payloadVariables = this.buildDefaultPayloadUseCase.execute({ + controlValues, + }).previewPayload.payload; + + return buildStringSchema(payloadVariables || {}); + } + private async fetchWorkflow(command: GetStepDataCommand) { const workflow = await this.getWorkflowByIdsUseCase.execute({ identifierOrInternalId: command.identifierOrInternalId, @@ -108,12 +120,16 @@ const buildSubscriberSchema = () => additionalProperties: false, }) as const satisfies JSONSchema; -function buildVariablesSchema(previousSteps?: NotificationStepEntity[]): JSONSchema { +function buildVariablesSchema( + previousSteps: NotificationStepEntity[] | undefined, + payloadSchema: JSONSchema +): JSONSchema { return { type: 'object', properties: { subscriber: buildSubscriberSchema(), steps: buildPreviousStepsSchema(previousSteps), + payload: payloadSchema, }, additionalProperties: false, } as const satisfies JSONSchema; @@ -142,3 +158,22 @@ function buildPreviousStepsSchema(previousSteps: NotificationStepEntity[] | unde description: 'Previous Steps Results', } as const satisfies JSONSchema; } + +/** + * Builds a JSON schema object where each variable becomes a string property. + */ +function buildStringSchema(variables: Record): JSONSchema { + const properties: Record = {}; + + for (const [variableKey, variableValue] of Object.entries(variables)) { + properties[variableKey] = { + type: 'string', + default: variableValue, + }; + } + + return { + type: 'object', + properties, + }; +} diff --git a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts index 961af38c516..62816b8ab91 100644 --- a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts +++ b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts @@ -422,6 +422,44 @@ describe('Workflow Controller E2E API Testing', () => { const stepRetrievedByStepIdentifier = await getStepData(internalWorkflowId, stepIdentifier); expect(stepRetrievedByStepIdentifier._id).to.equal(stepId); }); + + it('should get step payload variables', async () => { + const steps = [ + { + ...buildEmailStep(), + controlValues: { + body: 'Welcome to our newsletter {{bodyText}}{{bodyText2}}{{payload.prefixBodyText}}', + subject: 'Welcome to our newsletter {{subjectText}} {{payload.prefixSubjectText}}', + }, + }, + { ...buildInAppStep(), controlValues: { subject: 'Welcome to our newsletter {{inAppSubjectText}}' } }, + ]; + const createWorkflowDto: CreateWorkflowDto = buildCreateWorkflowDto('', { steps }); + const res = await session.testAgent.post(`${v2Prefix}/workflows`).send(createWorkflowDto); + expect(res.status).to.be.equal(201); + const workflowCreated: WorkflowResponseDto = res.body.data; + const stepData = await getStepData(workflowCreated._id, workflowCreated.steps[0]._id); + const { variables } = stepData; + + if (typeof variables === 'boolean') throw new Error('Variables is not an object'); + const { properties } = variables; + expect(properties).to.be.ok; + if (!properties) throw new Error('Payload schema is not valid'); + + expect(properties.payload).to.deep.equal({ + type: 'object', + properties: { + prefixSubjectText: { + type: 'string', + default: '{{payload.prefixSubjectText}}', + }, + prefixBodyText: { + type: 'string', + default: '{{payload.prefixBodyText}}', + }, + }, + }); + }); }); async function updateWorkflowRest(id: string, workflow: UpdateWorkflowDto): Promise {