Skip to content

Commit 162e66c

Browse files
fix(api): Return all relevant subscriber fields as test data
Return all subscriber fields as the test data.to field based on the variables used in the workflow and the steps that the workflow includes. That is, an SMS step requires a phone, an Email step an email and any other field should be present in the To test data object if it's used as a variable in the content of the workflow steps.
1 parent a23f80f commit 162e66c

File tree

3 files changed

+53
-22
lines changed

3 files changed

+53
-22
lines changed

apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts

+48-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
StepTypeEnum,
77
UserSessionData,
88
WorkflowTestDataResponseDto,
9+
Variables,
910
} from '@novu/shared';
1011
import {
1112
GetWorkflowByIdsCommand,
@@ -30,7 +31,16 @@ export class BuildWorkflowTestDataUseCase {
3031
@InstrumentUsecase()
3132
async execute(command: WorkflowTestDataCommand): Promise<WorkflowTestDataResponseDto> {
3233
const workflow = await this.fetchWorkflow(command);
33-
const toSchema = this.buildToFieldSchema({ user: command.user, steps: workflow.steps });
34+
const variables = await this.extractVariables.execute(
35+
ExtractVariablesCommand.create({
36+
environmentId: command.user.environmentId,
37+
organizationId: command.user.organizationId,
38+
userId: command.user._id,
39+
workflowId: workflow._id,
40+
})
41+
);
42+
const toSchema = this.buildToFieldSchema({ user: command.user, steps: workflow.steps, variables });
43+
3444
const payloadSchema = await this.resolvePayloadSchema(workflow, command);
3545
const payloadSchemaMock = this.generatePayloadMock(payloadSchema);
3646

@@ -84,38 +94,58 @@ export class BuildWorkflowTestDataUseCase {
8494
private buildToFieldSchema({
8595
user,
8696
steps,
97+
variables,
8798
}: {
8899
user: UserSessionData;
89100
steps: NotificationStepEntity[];
101+
variables: Variables;
90102
}): JSONSchemaDto {
91-
const hasEmailStep = this.hasStepType(steps, StepTypeEnum.EMAIL);
92-
const hasSmsStep = this.hasStepType(steps, StepTypeEnum.SMS);
93-
94-
const properties: { [key: string]: JSONSchemaDto } = {
95-
subscriberId: { type: 'string', default: user._id },
96-
};
103+
// TODO: add subscriber schema
104+
const toSchema = buildVariablesSchema({});
97105

98106
const required: string[] = ['subscriberId'];
107+
toSchema.properties!.subscriberId = { type: 'string', default: user._id };
99108

100-
if (hasEmailStep) {
101-
properties.email = { type: 'string', default: user.email ?? '', format: 'email' };
109+
if (this.hasStep(steps, StepTypeEnum.EMAIL)) {
110+
toSchema.properties!.email = { type: 'string', default: user.email ?? '', format: 'email' };
102111
required.push('email');
103112
}
104113

105-
if (hasSmsStep) {
106-
properties.phone = { type: 'string', default: '' };
114+
if (this.hasStep(steps, StepTypeEnum.SMS)) {
115+
toSchema.properties!.phone = { type: 'string', default: '' };
107116
required.push('phone');
108117
}
109118

110-
return {
111-
type: 'object',
112-
properties,
113-
required,
114-
additionalProperties: false,
115-
} satisfies JSONSchemaDto;
119+
if (variables.subscriber.firstName) {
120+
toSchema.properties!.firstName = { type: 'string', default: user.firstName || '' };
121+
}
122+
123+
if (variables.subscriber.lastName) {
124+
toSchema.properties!.lastName = { type: 'string', default: user.lastName || '' };
125+
}
126+
127+
if (variables.subscriber.isOnline) {
128+
toSchema.properties!.avatar = { type: 'boolean', default: true };
129+
}
130+
131+
if (variables.subscriber.isLastOnline) {
132+
toSchema.properties!.avatar = { type: 'string', format: 'date-time', default: new Date().toISOString() };
133+
}
134+
135+
// TODO: add locale as an enum
136+
if (variables.subscriber.locale) {
137+
toSchema.properties!.locale = { type: 'string', default: '' };
138+
}
139+
140+
// TODO: add timezone as an enum
141+
if (variables.subscriber.timezone) {
142+
toSchema.properties!.timezone = { type: 'string', default: '' };
143+
}
144+
145+
return toSchema;
116146
}
117147

118-
private hasStepType(steps: NotificationStepEntity[], type: StepTypeEnum): boolean {
148+
private hasStep(steps: NotificationStepEntity[], type: StepTypeEnum): boolean {
119149
return steps.some((step) => step.template?.type === type);
120150
}
121151
}

apps/api/src/app/workflows-v2/usecases/build-variable-schema/build-available-variable-schema.usecase.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ export class BuildVariableSchemaUsecase {
3737
type: 'object',
3838
description: 'Schema representing the subscriber entity',
3939
properties: {
40+
subscriberId: { type: 'string', description: 'Unique identifier for the subscriber' },
4041
firstName: { type: 'string', description: "Subscriber's first name" },
4142
lastName: { type: 'string', description: "Subscriber's last name" },
4243
email: { type: 'string', description: "Subscriber's email address" },
4344
phone: { type: 'string', description: "Subscriber's phone number (optional)" },
4445
avatar: { type: 'string', description: "URL to the subscriber's avatar image (optional)" },
4546
locale: { type: 'string', description: 'Locale for the subscriber (optional)' },
46-
subscriberId: { type: 'string', description: 'Unique identifier for the subscriber' },
47+
timezone: { type: 'string', description: 'Timezone for the subscriber (optional)' },
4748
isOnline: { type: 'boolean', description: 'Indicates if the subscriber is online (optional)' },
4849
lastOnlineAt: {
4950
type: 'string',

apps/api/src/app/workflows-v2/usecases/extract-variables/extract-variables.usecase.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Injectable } from '@nestjs/common';
22
import { ControlValuesRepository } from '@novu/dal';
3-
import { ControlValuesLevelEnum } from '@novu/shared';
3+
import { ControlValuesLevelEnum, Variables } from '@novu/shared';
44
import { Instrument, InstrumentUsecase } from '@novu/application-generic';
55

66
import { keysToObject } from '../../util/utils';
@@ -12,11 +12,11 @@ export class ExtractVariables {
1212
constructor(private readonly controlValuesRepository: ControlValuesRepository) {}
1313

1414
@InstrumentUsecase()
15-
async execute(command: ExtractVariablesCommand): Promise<Record<string, unknown>> {
15+
async execute(command: ExtractVariablesCommand) {
1616
const controlValues = await this.getControlValues(command);
1717
const extractedVariables = await this.extractAllVariables(controlValues);
1818

19-
return keysToObject(extractedVariables);
19+
return keysToObject<Variables>(extractedVariables);
2020
}
2121

2222
private async getControlValues(command: ExtractVariablesCommand) {

0 commit comments

Comments
 (0)