-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'next' into start-project-update
- Loading branch information
Showing
15 changed files
with
694 additions
and
132 deletions.
There are no files selected for viewing
20 changes: 20 additions & 0 deletions
20
apps/api/src/app/workflows-v2/shared/build-string-schema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { JSONSchema } from 'json-schema-to-ts'; | ||
|
||
/** | ||
* Builds a JSON schema object where each variable becomes a string property. | ||
*/ | ||
export function buildJSONSchema(variables: Record<string, unknown>): JSONSchema { | ||
const properties: Record<string, JSONSchema> = {}; | ||
|
||
for (const [variableKey, variableValue] of Object.entries(variables)) { | ||
properties[variableKey] = { | ||
type: 'string', | ||
default: variableValue, | ||
}; | ||
} | ||
|
||
return { | ||
type: 'object', | ||
properties, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 70 additions & 38 deletions
108
apps/api/src/app/workflows-v2/usecases/test-data/test-data.usecase.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,89 @@ | ||
import { JSONSchema } from 'json-schema-to-ts'; | ||
import { Injectable } from '@nestjs/common'; | ||
import { NotificationTemplateEntity } from '@novu/dal'; | ||
import { UserSessionData, WorkflowTestDataResponseDto } from '@novu/shared'; | ||
import { ControlValuesRepository, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal'; | ||
import { ControlValuesLevelEnum, StepTypeEnum, UserSessionData, WorkflowTestDataResponseDto } from '@novu/shared'; | ||
|
||
import { WorkflowTestDataCommand } from './test-data.command'; | ||
import { GetWorkflowByIdsUseCase } from '../get-workflow-by-ids/get-workflow-by-ids.usecase'; | ||
import { GetWorkflowByIdsCommand } from '../get-workflow-by-ids/get-workflow-by-ids.command'; | ||
|
||
const buildToFieldSchema = ({ user }: { user: UserSessionData }) => | ||
({ | ||
type: 'object', | ||
properties: { | ||
subscriberId: { type: 'string', default: user._id }, | ||
/* | ||
* TODO: the email and phone fields should be dynamic based on the workflow steps | ||
* if the workflow has has an email step, then email is required etc | ||
*/ | ||
email: { type: 'string', default: user.email ?? '', format: 'email' }, | ||
phone: { type: 'string', default: '' }, | ||
}, | ||
required: ['subscriberId', 'email', 'phone'], | ||
additionalProperties: false, | ||
}) as const satisfies JSONSchema; | ||
|
||
const buildPayloadSchema = () => | ||
({ | ||
type: 'object', | ||
description: 'Schema representing the workflow payload', | ||
properties: { | ||
/* | ||
* TODO: the properties should be dynamic based on the workflow variables | ||
*/ | ||
example: { type: 'string', description: 'Example field', default: 'payload.example' }, | ||
}, | ||
required: ['subscriberId', 'email', 'phone'], | ||
additionalProperties: false, | ||
}) as const satisfies JSONSchema; | ||
import { BuildDefaultPayloadUseCase } from '../build-payload-from-placeholder'; | ||
import { buildJSONSchema } from '../../shared/build-string-schema'; | ||
|
||
@Injectable() | ||
export class WorkflowTestDataUseCase { | ||
constructor(private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase) {} | ||
constructor( | ||
private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase, | ||
private controlValuesRepository: ControlValuesRepository, | ||
private buildDefaultPayloadUseCase: BuildDefaultPayloadUseCase | ||
) {} | ||
|
||
async execute(command: WorkflowTestDataCommand): Promise<WorkflowTestDataResponseDto> { | ||
const _workflowEntity: NotificationTemplateEntity | null = await this.getWorkflowByIdsUseCase.execute( | ||
const _workflowEntity: NotificationTemplateEntity = await this.fetchWorkflow(command); | ||
const toSchema = buildToFieldSchema({ user: command.user, steps: _workflowEntity.steps }); | ||
const payloadSchema = await this.buildPayloadSchema(command, _workflowEntity); | ||
|
||
return { | ||
to: toSchema, | ||
payload: payloadSchema, | ||
}; | ||
} | ||
|
||
private async fetchWorkflow(command: WorkflowTestDataCommand): Promise<NotificationTemplateEntity> { | ||
return await this.getWorkflowByIdsUseCase.execute( | ||
GetWorkflowByIdsCommand.create({ | ||
...command, | ||
identifierOrInternalId: command.identifierOrInternalId, | ||
}) | ||
); | ||
} | ||
|
||
return { | ||
to: buildToFieldSchema({ user: command.user }), | ||
payload: buildPayloadSchema(), | ||
}; | ||
private async buildPayloadSchema(command: WorkflowTestDataCommand, _workflowEntity: NotificationTemplateEntity) { | ||
let payloadVariables: Record<string, unknown> = {}; | ||
for (const step of _workflowEntity.steps) { | ||
const newValues = await this.getValues(command.user, step._templateId, _workflowEntity._id); | ||
|
||
/* | ||
* we need to build the payload defaults for each step, | ||
* because of possible duplicated values (like subject, body, etc...) | ||
*/ | ||
const currPayloadVariables = this.buildDefaultPayloadUseCase.execute({ | ||
controlValues: newValues, | ||
}).previewPayload.payload; | ||
payloadVariables = { ...payloadVariables, ...currPayloadVariables }; | ||
} | ||
|
||
return buildJSONSchema(payloadVariables || {}); | ||
} | ||
|
||
private async getValues(user: UserSessionData, _stepId: string, _workflowId: string) { | ||
const controlValuesEntity = await this.controlValuesRepository.findOne({ | ||
_environmentId: user.environmentId, | ||
_organizationId: user.organizationId, | ||
_workflowId, | ||
_stepId, | ||
level: ControlValuesLevelEnum.STEP_CONTROLS, | ||
}); | ||
|
||
return controlValuesEntity?.controls || {}; | ||
} | ||
} | ||
|
||
const buildToFieldSchema = ({ user, steps }: { user: UserSessionData; steps: NotificationStepEntity[] }) => { | ||
const isEmailExist = isContainsStepType(steps, StepTypeEnum.EMAIL); | ||
const isSmsExist = isContainsStepType(steps, StepTypeEnum.SMS); | ||
|
||
return { | ||
type: 'object', | ||
properties: { | ||
subscriberId: { type: 'string', default: user._id }, | ||
...(isEmailExist ? { email: { type: 'string', default: user.email ?? '', format: 'email' } } : {}), | ||
...(isSmsExist ? { phone: { type: 'string', default: '' } } : {}), | ||
}, | ||
required: ['subscriberId', ...(isEmailExist ? ['email'] : []), ...(isSmsExist ? ['phone'] : [])], | ||
additionalProperties: false, | ||
} as const satisfies JSONSchema; | ||
}; | ||
|
||
function isContainsStepType(steps: NotificationStepEntity[], type: StepTypeEnum) { | ||
return steps.some((step) => step.template?.type === type); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.