Skip to content

Commit 6bafc02

Browse files
committed
finished rebasing
fixed wierd msg test fix test rebase fixes fixed wierd msg test remove bail fix excpetion mapper topics fixed. topics fixed. Revert "test skips" This reverts commit f9d2c46. test skips fix failing test on usage reports fix failing test on usage reports fix failing test on usage reports Refactor WorkflowController to use internal SDK
1 parent 45b1bce commit 6bafc02

File tree

273 files changed

+13039
-4876
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

273 files changed

+13039
-4876
lines changed

.cspell.json

+1
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@
727727
"dotenvcreate",
728728
"querybuilder",
729729
"liquified",
730+
"Throtler",
730731
"autoload",
731732
"novugo",
732733
"titleize",

apps/api/src/app/billing/e2e/get-platform-notification-usage.e2e-ee.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@ describe('GetPlatformNotificationUsage #novu-v2', () => {
1818
const communityOrganizationRepo = new CommunityOrganizationRepository();
1919

2020
const createUseCase = () => {
21-
const useCase = new GetPlatformNotificationUsage(
21+
return new GetPlatformNotificationUsage(
2222
environmentRepo,
2323
notificationRepo,
2424
communityOrganizationRepo,
2525
MockCacheService.createClient(),
2626
new PinoLogger({})
2727
);
28-
29-
return useCase;
3028
};
3129
let session: UserSession;
3230

apps/api/src/app/blueprint/usecases/get-grouped-blueprints/get-grouped-blueprints.usecase.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ export class GetGroupedBlueprints {
2323

2424
const popularGroup = { name: POPULAR_GROUPED_NAME, blueprints: updatePopularBlueprints };
2525

26-
return { general: generalGroups as IGroupedBlueprint[], popular: popularGroup as IGroupedBlueprint };
26+
return {
27+
general: generalGroups as unknown as IGroupedBlueprint[],
28+
popular: popularGroup as unknown as IGroupedBlueprint,
29+
};
2730
}
2831

2932
private async fetchGroupedBlueprints() {

apps/api/src/app/bridge/usecases/preview-step/preview-step.usecase.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Event, ExecuteOutput, HttpQueryKeysEnum, PostActionEnum } from '@novu/f
33
import { ExecuteBridgeRequest, ExecuteBridgeRequestCommand, InstrumentUsecase } from '@novu/application-generic';
44

55
import { PreviewStepCommand } from './preview-step.command';
6+
import { Subscriber } from '../../../inbox/utils/types';
7+
import { SubscriberResponseDto } from '../../../subscribers/dtos';
68

79
@Injectable()
810
export class PreviewStep {
@@ -37,10 +39,29 @@ export class PreviewStep {
3739
controls: command.controls || {},
3840
payload: command.payload || {},
3941
state: command.state || [],
40-
subscriber: command.subscriber || {},
42+
subscriber: this.mapSubscriberResponseToSubscriber(command.subscriber),
4143
stepId: command.stepId,
4244
workflowId: command.workflowId,
4345
action: PostActionEnum.PREVIEW,
4446
};
4547
}
48+
49+
private mapSubscriberResponseToSubscriber(
50+
subscriberResponse?: Partial<SubscriberResponseDto> | undefined
51+
): Subscriber {
52+
if (!subscriberResponse) {
53+
return {
54+
id: '',
55+
subscriberId: '',
56+
};
57+
}
58+
59+
return {
60+
id: subscriberResponse._id || subscriberResponse.subscriberId || '',
61+
firstName: subscriberResponse.firstName,
62+
lastName: subscriberResponse.lastName,
63+
avatar: subscriberResponse.avatar,
64+
subscriberId: subscriberResponse.subscriberId || '',
65+
};
66+
}
4667
}

apps/api/src/app/bridge/usecases/sync/sync.command.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { IsDefined, IsOptional, IsString, ValidateNested } from 'class-validator
22
import { Type } from 'class-transformer';
33

44
import { EnvironmentWithUserCommand, IStepControl } from '@novu/application-generic';
5-
import type { IPreferenceChannels, CustomDataType, StepType, JSONSchemaDto } from '@novu/shared';
5+
import type { CustomDataType, IPreferenceChannels, JSONSchemaDto, StepType } from '@novu/shared';
66

77
interface IStepOutput {
88
schema: JSONSchemaDto;

apps/api/src/app/bridge/usecases/sync/sync.usecase.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ import {
1414
DeleteWorkflowCommand,
1515
DeleteWorkflowUseCase,
1616
ExecuteBridgeRequest,
17+
JSONSchema,
1718
NotificationStep,
1819
UpdateWorkflow,
1920
UpdateWorkflowCommand,
2021
} from '@novu/application-generic';
2122
import {
2223
buildWorkflowPreferences,
23-
JSONSchemaDto,
24-
StepIssuesDto,
2524
StepTypeEnum,
2625
UserSessionData,
2726
WorkflowCreationSourceEnum,
@@ -35,6 +34,7 @@ import { SyncCommand } from './sync.command';
3534
import { CreateBridgeResponseDto } from '../../dtos/create-bridge-response.dto';
3635
import { BuildStepIssuesUsecase } from '../../../workflows-v2/usecases/build-step-issues/build-step-issues.usecase';
3736
import { computeWorkflowStatus } from '../../../workflows-v2/shared/compute-workflow-status';
37+
import { JSONSchemaDto, StepIssuesDto } from '../../../workflows-v2/dtos';
3838

3939
@Injectable()
4040
export class Sync {
@@ -183,9 +183,8 @@ export class Sync {
183183
return Promise.all(
184184
workflowsFromBridge.map(async (workflow, index) => {
185185
const existingFrameworkWorkflow = existingFrameworkWorkflows[index];
186-
const savedWorkflow = await this.upsertWorkflow(command, workflow, existingFrameworkWorkflow);
187186

188-
return savedWorkflow;
187+
return await this.upsertWorkflow(command, workflow, existingFrameworkWorkflow);
189188
})
190189
);
191190
}
@@ -235,10 +234,10 @@ export class Sync {
235234
__source: WorkflowCreationSourceEnum.BRIDGE,
236235
steps,
237236
controls: {
238-
schema: workflow.controls?.schema as JSONSchemaDto,
237+
schema: workflow.controls?.schema as unknown as JSONSchema,
239238
},
240239
rawData: workflow as unknown as Record<string, unknown>,
241-
payloadSchema: workflow.payload?.schema as JSONSchemaDto,
240+
payloadSchema: workflow.payload?.schema as unknown as JSONSchema,
242241
active: workflowActive,
243242
status: computeWorkflowStatus(workflowActive, steps),
244243
description: this.getWorkflowDescription(workflow),
@@ -266,7 +265,7 @@ export class Sync {
266265
workflowId: workflow.workflowId,
267266
steps,
268267
controls: {
269-
schema: workflow.controls?.schema as JSONSchemaDto,
268+
schema: workflow.controls?.schema as unknown as JSONSchemaDto,
270269
},
271270
rawData: workflow,
272271
payloadSchema: workflow.payload?.schema as unknown as JSONSchemaDto,
@@ -299,7 +298,7 @@ export class Sync {
299298
stepInternalId: foundStep?._id,
300299
workflow,
301300
stepType: step.type as StepTypeEnum,
302-
controlSchema: step.controls?.schema as JSONSchemaDto,
301+
controlSchema: step.controls?.schema as unknown as JSONSchemaDto,
303302
});
304303

305304
const template = {

apps/api/src/app/environments-v1/usecases/construct-framework-workflow/construct-framework-workflow.usecase.ts

+20-24
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { Injectable, InternalServerErrorException } from '@nestjs/common';
22
import { workflow } from '@novu/framework/express';
3-
import { ActionStep, ChannelStep, JsonSchema, Step, StepOptions, StepOutput, Workflow } from '@novu/framework/internal';
3+
import { ActionStep, ChannelStep, Schema, Step, StepOutput, Workflow } from '@novu/framework/internal';
44
import { NotificationStepEntity, NotificationTemplateEntity, NotificationTemplateRepository } from '@novu/dal';
5-
import { JSONSchemaDefinition, StepTypeEnum } from '@novu/shared';
5+
import { StepTypeEnum } from '@novu/shared';
66
import { Instrument, InstrumentUsecase, PinoLogger } from '@novu/application-generic';
77
import { AdditionalOperation, RulesLogic } from 'json-logic-js';
88
import _ from 'lodash';
99
import { ConstructFrameworkWorkflowCommand } from './construct-framework-workflow.command';
1010
import {
1111
ChatOutputRendererUsecase,
12+
EmailOutputRendererUsecase,
1213
FullPayloadForRender,
1314
InAppOutputRendererUsecase,
1415
PushOutputRendererUsecase,
15-
EmailOutputRendererUsecase,
1616
SmsOutputRendererUsecase,
1717
} from '../output-renderers';
1818
import { DelayOutputRendererUsecase } from '../output-renderers/delay-output-renderer.usecase';
@@ -54,7 +54,7 @@ export class ConstructFrameworkWorkflow {
5454
dbWorkflow.triggers[0].identifier,
5555
async ({ step, payload, subscriber }) => {
5656
const fullPayloadForRender: FullPayloadForRender = { payload, subscriber, steps: {} };
57-
for await (const staticStep of dbWorkflow.steps) {
57+
for (const staticStep of dbWorkflow.steps) {
5858
fullPayloadForRender.steps[staticStep.stepId || staticStep._templateId] = await this.constructStep(
5959
step,
6060
staticStep,
@@ -176,8 +176,12 @@ export class ConstructFrameworkWorkflow {
176176
staticStep: NotificationStepEntity,
177177
fullPayloadForRender: FullPayloadForRender
178178
): Required<Parameters<ChannelStep>[2]> {
179+
const skipFunction = (controlValues: Record<string, unknown>) =>
180+
this.processSkipOption(controlValues, fullPayloadForRender);
181+
179182
return {
180-
...this.constructCommonStepOptions(staticStep, fullPayloadForRender),
183+
skip: skipFunction,
184+
controlSchema: staticStep.template!.controls!.schema as unknown as Schema,
181185
disableOutputSanitization: true,
182186
providers: {},
183187
};
@@ -188,10 +192,17 @@ export class ConstructFrameworkWorkflow {
188192
staticStep: NotificationStepEntity,
189193
fullPayloadForRender: FullPayloadForRender
190194
): Required<Parameters<ActionStep>[2]> {
191-
const stepOptions = this.constructCommonStepOptions(staticStep, fullPayloadForRender);
192-
193-
let controlSchema = stepOptions.controlSchema as JSONSchemaDefinition;
194195
const stepType = staticStep.template!.type;
196+
const controlSchema = this.optionalAugmentControlSchemaDueToAjvBug(staticStep, stepType);
197+
198+
return {
199+
controlSchema: controlSchema as unknown as Schema,
200+
skip: (controlValues: Record<string, unknown>) => this.processSkipOption(controlValues, fullPayloadForRender),
201+
};
202+
}
203+
204+
private optionalAugmentControlSchemaDueToAjvBug(staticStep: NotificationStepEntity, stepType: StepTypeEnum) {
205+
let controlSchema = staticStep.template!.controls!.schema;
195206

196207
/*
197208
* because of the known AJV issue with anyOf, we need to find the first schema that matches the control values
@@ -204,22 +215,7 @@ export class ConstructFrameworkWorkflow {
204215
controlSchema = fistSchemaMatch ?? controlSchema.anyOf[0];
205216
}
206217

207-
return {
208-
...stepOptions,
209-
controlSchema: controlSchema as JsonSchema,
210-
};
211-
}
212-
213-
@Instrument()
214-
private constructCommonStepOptions(
215-
staticStep: NotificationStepEntity,
216-
fullPayloadForRender: FullPayloadForRender
217-
): Required<StepOptions> {
218-
return {
219-
// TODO: fix the `JSONSchemaDto` type to enforce a non-primitive schema type.
220-
controlSchema: staticStep.template!.controls!.schema as JsonSchema,
221-
skip: (controlValues: Record<string, unknown>) => this.processSkipOption(controlValues, fullPayloadForRender),
222-
};
218+
return controlSchema;
223219
}
224220

225221
@Instrument()

apps/api/src/app/inbox/e2e/get-notifications.e2e.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,15 @@ describe('Get Notifications - /inbox/notifications (GET) #novu-v2', async () =>
122122
it('should validate that the offset is greater or equals to zero', async function () {
123123
const { body, status } = await getNotifications({ limit: 1, offset: -1 });
124124

125-
expect(status).to.equal(400);
126-
expect(body.message[0]).to.equal('offset must not be less than 0');
125+
expect(status).to.equal(422);
126+
expect(body.errors.general.messages[0]).to.equal('offset must not be less than 0');
127127
});
128128

129129
it('should validate the after to mongo id', async function () {
130130
const { body, status } = await getNotifications({ limit: 1, after: 'after' });
131131

132-
expect(status).to.equal(400);
133-
expect(body.message[0]).to.equal('The after cursor must be a valid MongoDB ObjectId');
132+
expect(status).to.equal(422);
133+
expect(body.errors.general.messages[0]).to.equal('The after cursor must be a valid MongoDB ObjectId');
134134
});
135135

136136
it('should throw exception when filtering for unread and archived notifications', async function () {

apps/api/src/app/notifications/dtos/activities-response.dto.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ export class DigestTimedConfigDto {
2424
@IsString()
2525
atTime?: string;
2626

27-
@ApiPropertyOptional({ description: 'Days of the week for the digest', type: [String], enum: DaysEnum })
27+
@ApiPropertyOptional({
28+
description: 'Days of the week for the digest',
29+
type: 'array',
30+
items: {
31+
type: 'string',
32+
enum: Object.values(DaysEnum),
33+
},
34+
enumName: 'DaysEnum',
35+
})
2836
@IsOptional()
2937
@IsArray()
3038
@IsEnum(DaysEnum, { each: true })

apps/api/src/app/preferences/dtos/preferences.dto.ts

+54
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,101 @@ import { ChannelTypeEnum } from '@novu/shared';
22
import { Type } from 'class-transformer';
33
import { IsBoolean, ValidateNested } from 'class-validator';
44

5+
/**
6+
* @deprecated Use an updated preference structure.
7+
* This class will be removed in future versions.
8+
*/
59
export class WorkflowPreference {
10+
/**
11+
* @deprecated Use alternative enablement mechanism.
12+
*/
613
@IsBoolean()
714
enabled: boolean;
815

16+
/**
17+
* @deprecated Read-only flag is no longer supported.
18+
*/
919
@IsBoolean()
1020
readOnly: boolean;
1121
}
1222

23+
/**
24+
* @deprecated Use an updated channel preference structure.
25+
* Will be removed in future versions.
26+
*/
1327
export class ChannelPreference {
28+
/**
29+
* @deprecated Use alternative channel enablement method.
30+
*/
1431
@IsBoolean()
1532
enabled: boolean;
1633
}
1734

35+
/**
36+
* @deprecated Channels configuration is being restructured.
37+
* Use the new channel management approach.
38+
*/
1839
export class Channels {
40+
/**
41+
* @deprecated In-app channel preference is deprecated.
42+
*/
1943
@ValidateNested({ each: true })
2044
@Type(() => ChannelPreference)
2145
[ChannelTypeEnum.IN_APP]: ChannelPreference;
2246

47+
/**
48+
* @deprecated Email channel preference is deprecated.
49+
*/
2350
@ValidateNested({ each: true })
2451
@Type(() => ChannelPreference)
2552
[ChannelTypeEnum.EMAIL]: ChannelPreference;
2653

54+
/**
55+
* @deprecated SMS channel preference is deprecated.
56+
*/
2757
@ValidateNested({ each: true })
2858
@Type(() => ChannelPreference)
2959
[ChannelTypeEnum.SMS]: ChannelPreference;
3060

61+
/**
62+
* @deprecated Chat channel preference is deprecated.
63+
*/
3164
@ValidateNested({ each: true })
3265
@Type(() => ChannelPreference)
3366
[ChannelTypeEnum.CHAT]: ChannelPreference;
3467

68+
/**
69+
* @deprecated Push channel preference is deprecated.
70+
*/
3571
@ValidateNested({ each: true })
3672
@Type(() => ChannelPreference)
3773
[ChannelTypeEnum.PUSH]: ChannelPreference;
3874
}
3975

76+
/**
77+
* @deprecated Preferences DTO is being replaced.
78+
* Use the new preferences management approach.
79+
*/
4080
export class PreferencesDto {
81+
/**
82+
* @deprecated Global workflow preference is no longer used.
83+
*/
4184
@ValidateNested({ each: true })
4285
@Type(() => WorkflowPreference)
4386
all: WorkflowPreference;
4487

88+
/**
89+
* @deprecated Channels configuration is deprecated.
90+
*/
4591
@ValidateNested({ each: true })
4692
@Type(() => Channels)
4793
channels: Channels;
4894
}
95+
96+
// Optional: Runtime deprecation warning
97+
if (process.env.NODE_ENV !== 'production') {
98+
console.warn(
99+
'DEPRECATION WARNING: PreferencesDto and related classes are deprecated ' +
100+
'and will be removed in future versions. Please migrate to the new preferences structure.'
101+
);
102+
}

0 commit comments

Comments
 (0)