Skip to content

Commit 761f99d

Browse files
committed
Merge branch 'next' into nv-4480-workflow-editor-autosave
2 parents e8e7e50 + 6ffc13f commit 761f99d

File tree

133 files changed

+2512
-744
lines changed

Some content is hidden

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

133 files changed

+2512
-744
lines changed

.source

apps/api/migrations/changes-migration.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import {
1212
OrganizationRepository,
1313
} from '@novu/dal';
1414
import { ChangeEntityTypeEnum, MemberRoleEnum } from '@novu/shared';
15-
import { CreateEnvironment } from '../src/app/environments/usecases/create-environment/create-environment.usecase';
16-
import { CreateEnvironmentCommand } from '../src/app/environments/usecases/create-environment/create-environment.command';
15+
import { CreateEnvironment } from '../src/app/environments-v1/usecases/create-environment/create-environment.usecase';
16+
import { CreateEnvironmentCommand } from '../src/app/environments-v1/usecases/create-environment/create-environment.command';
1717
import { ApplyChange } from '../src/app/change/usecases/apply-change/apply-change.usecase';
1818
import { ApplyChangeCommand } from '../src/app/change/usecases/apply-change/apply-change.command';
1919
import { CreateChange, CreateChangeCommand } from '@novu/application-generic';

apps/api/src/app.module.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { AuthModule } from './app/auth/auth.module';
1212
import { TestingModule } from './app/testing/testing.module';
1313
import { HealthModule } from './app/health/health.module';
1414
import { OrganizationModule } from './app/organization/organization.module';
15-
import { EnvironmentsModule } from './app/environments/environments.module';
1615
import { ExecutionDetailsModule } from './app/execution-details/execution-details.module';
1716
import { EventsModule } from './app/events/events.module';
1817
import { WidgetsModule } from './app/widgets/widgets.module';
@@ -44,6 +43,8 @@ import { PreferencesModule } from './app/preferences';
4443
import { StepSchemasModule } from './app/step-schemas/step-schemas.module';
4544
import { WorkflowModule } from './app/workflows-v2/workflow.module';
4645
import { WorkflowModuleV1 } from './app/workflows-v1/workflow-v1.module';
46+
import { EnvironmentsModuleV1 } from './app/environments-v1/environments-v1.module';
47+
import { EnvironmentsModule } from './app/environments-v2/environments.module';
4748

4849
const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> => {
4950
const modules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [];
@@ -76,7 +77,7 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
7677
InboundParseModule,
7778
SharedModule,
7879
HealthModule,
79-
EnvironmentsModule,
80+
EnvironmentsModuleV1,
8081
ExecutionDetailsModule,
8182
WorkflowModuleV1,
8283
EventsModule,
@@ -106,6 +107,7 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
106107
BridgeModule,
107108
PreferencesModule,
108109
WorkflowModule,
110+
EnvironmentsModule,
109111
];
110112

111113
const enterpriseModules = enterpriseImports();

apps/api/src/app/auth/community.auth.module.config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { USE_CASES } from './usecases';
1313
import { SharedModule } from '../shared/shared.module';
1414
import { GitHubStrategy } from './services/passport/github.strategy';
1515
import { OrganizationModule } from '../organization/organization.module';
16-
import { EnvironmentsModule } from '../environments/environments.module';
16+
import { EnvironmentsModuleV1 } from '../environments-v1/environments-v1.module';
1717
import { JwtSubscriberStrategy } from './services/passport/subscriber-jwt.strategy';
1818
import { RootEnvironmentGuard } from './framework/root-environment-guard.service';
1919
import { ApiKeyStrategy } from './services/passport/apikey.strategy';
@@ -39,7 +39,7 @@ export function getCommunityAuthModuleConfig(): ModuleMetadata {
3939
expiresIn: 360000,
4040
},
4141
}),
42-
EnvironmentsModule,
42+
EnvironmentsModuleV1,
4343
],
4444
controllers: [AuthController],
4545
providers: [

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

+59-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BadRequestException, Injectable } from '@nestjs/common';
1+
import { BadRequestException, HttpException, Injectable } from '@nestjs/common';
22

33
import {
44
EnvironmentRepository,
@@ -56,8 +56,12 @@ export class Sync {
5656
retriesLimit: 1,
5757
workflowOrigin: WorkflowOriginEnum.EXTERNAL,
5858
})) as DiscoverOutput;
59-
} catch (error: any) {
60-
throw new BadRequestException(`Bridge URL is not valid. ${error.message}`);
59+
} catch (error) {
60+
if (error instanceof HttpException) {
61+
throw new BadRequestException(error.message);
62+
}
63+
64+
throw error;
6165
}
6266

6367
if (!discover) {
@@ -84,7 +88,7 @@ export class Sync {
8488
return persistedWorkflowsInBridge;
8589
}
8690

87-
private async updateBridgeUrl(command: SyncCommand) {
91+
private async updateBridgeUrl(command: SyncCommand): Promise<void> {
8892
await this.environmentRepository.update(
8993
{ _id: command.environmentId },
9094
{
@@ -100,7 +104,10 @@ export class Sync {
100104
);
101105
}
102106

103-
private async disposeOldWorkflows(command: SyncCommand, createdWorkflows: NotificationTemplateEntity[]) {
107+
private async disposeOldWorkflows(
108+
command: SyncCommand,
109+
createdWorkflows: NotificationTemplateEntity[]
110+
): Promise<void> {
104111
const persistedWorkflowIdsInBridge = createdWorkflows.map((i) => i._id);
105112

106113
const workflowsToDelete = await this.findAllWorkflowsWithOtherIds(command, persistedWorkflowIdsInBridge);
@@ -119,7 +126,10 @@ export class Sync {
119126
);
120127
}
121128

122-
private async findAllWorkflowsWithOtherIds(command: SyncCommand, persistedWorkflowIdsInBridge: string[]) {
129+
private async findAllWorkflowsWithOtherIds(
130+
command: SyncCommand,
131+
persistedWorkflowIdsInBridge: string[]
132+
): Promise<NotificationTemplateEntity[]> {
123133
return await this.notificationTemplateRepository.find({
124134
_environmentId: command.environmentId,
125135
type: {
@@ -132,7 +142,10 @@ export class Sync {
132142
});
133143
}
134144

135-
private async createWorkflows(command: SyncCommand, workflowsFromBridge: DiscoverWorkflowOutput[]) {
145+
private async createWorkflows(
146+
command: SyncCommand,
147+
workflowsFromBridge: DiscoverWorkflowOutput[]
148+
): Promise<NotificationTemplateEntity[]> {
136149
return Promise.all(
137150
workflowsFromBridge.map(async (workflow) => {
138151
const workflowExist = await this.notificationTemplateRepository.findByTriggerIdentifier(
@@ -183,7 +196,12 @@ export class Sync {
183196
);
184197
}
185198

186-
private async createWorkflow(notificationGroupId: string, isWorkflowActive, command: SyncCommand, workflow) {
199+
private async createWorkflow(
200+
notificationGroupId: string,
201+
isWorkflowActive: boolean,
202+
command: SyncCommand,
203+
workflow: DiscoverWorkflowOutput
204+
): Promise<NotificationTemplateEntity> {
187205
return await this.createWorkflowUsecase.execute(
188206
CreateWorkflowCommand.create({
189207
origin: WorkflowOriginEnum.EXTERNAL,
@@ -193,7 +211,8 @@ export class Sync {
193211
environmentId: command.environmentId,
194212
organizationId: command.organizationId,
195213
userId: command.userId,
196-
name: workflow.workflowId,
214+
name: this.getWorkflowName(workflow),
215+
triggerIdentifier: workflow.workflowId,
197216
__source: WorkflowCreationSourceEnum.BRIDGE,
198217
steps: this.mapSteps(workflow.steps),
199218
/** @deprecated */
@@ -209,23 +228,28 @@ export class Sync {
209228
/** @deprecated */
210229
(workflow.options?.payloadSchema as Record<string, unknown>),
211230
active: isWorkflowActive,
212-
description: this.castToAnyNotSupportedParam(workflow.options).description,
231+
description: this.getWorkflowDescription(workflow),
213232
data: this.castToAnyNotSupportedParam(workflow).options?.data,
214-
tags: workflow.tags || [],
233+
tags: this.getWorkflowTags(workflow),
215234
critical: this.castToAnyNotSupportedParam(workflow.options)?.critical ?? false,
216235
preferenceSettings: this.castToAnyNotSupportedParam(workflow.options)?.preferenceSettings,
217236
})
218237
);
219238
}
220239

221-
private async updateWorkflow(workflowExist, command: SyncCommand, workflow) {
240+
private async updateWorkflow(
241+
workflowExist: NotificationTemplateEntity,
242+
command: SyncCommand,
243+
workflow: DiscoverWorkflowOutput
244+
): Promise<NotificationTemplateEntity> {
222245
return await this.updateWorkflowUsecase.execute(
223246
UpdateWorkflowCommand.create({
224247
id: workflowExist._id,
225248
environmentId: command.environmentId,
226249
organizationId: command.organizationId,
227250
userId: command.userId,
228-
name: workflow.workflowId,
251+
name: this.getWorkflowName(workflow),
252+
workflowId: workflow.workflowId,
229253
steps: this.mapSteps(workflow.steps, workflowExist),
230254
inputs: {
231255
schema: workflow.controls?.schema || workflow.inputs.schema,
@@ -238,17 +262,20 @@ export class Sync {
238262
(workflow.payload?.schema as Record<string, unknown>) ||
239263
(workflow.options?.payloadSchema as Record<string, unknown>),
240264
type: WorkflowTypeEnum.BRIDGE,
241-
description: this.castToAnyNotSupportedParam(workflow.options).description,
265+
description: this.getWorkflowDescription(workflow),
242266
data: this.castToAnyNotSupportedParam(workflow.options)?.data,
243-
tags: workflow.tags,
267+
tags: this.getWorkflowTags(workflow),
244268
active: this.castToAnyNotSupportedParam(workflow.options)?.active ?? true,
245269
critical: this.castToAnyNotSupportedParam(workflow.options)?.critical ?? false,
246270
preferenceSettings: this.castToAnyNotSupportedParam(workflow.options)?.preferenceSettings,
247271
})
248272
);
249273
}
250274

251-
private mapSteps(commandWorkflowSteps: DiscoverStepOutput[], workflow?: NotificationTemplateEntity | undefined) {
275+
private mapSteps(
276+
commandWorkflowSteps: DiscoverStepOutput[],
277+
workflow?: NotificationTemplateEntity | undefined
278+
): NotificationStep[] {
252279
const steps: NotificationStep[] = commandWorkflowSteps.map((step) => {
253280
const foundStep = workflow?.steps?.find((workflowStep) => workflowStep.stepId === step.stepId);
254281

@@ -275,7 +302,10 @@ export class Sync {
275302
return steps;
276303
}
277304

278-
private async getNotificationGroup(notificationGroupIdCommand: string | undefined, environmentId: string) {
305+
private async getNotificationGroup(
306+
notificationGroupIdCommand: string | undefined,
307+
environmentId: string
308+
): Promise<string | undefined> {
279309
let notificationGroupId = notificationGroupIdCommand;
280310

281311
if (!notificationGroupId) {
@@ -293,6 +323,18 @@ export class Sync {
293323
return notificationGroupId;
294324
}
295325

326+
private getWorkflowName(workflow: DiscoverWorkflowOutput): string {
327+
return workflow.name || workflow.workflowId;
328+
}
329+
330+
private getWorkflowDescription(workflow: DiscoverWorkflowOutput): string {
331+
return workflow.description || '';
332+
}
333+
334+
private getWorkflowTags(workflow: DiscoverWorkflowOutput): string[] {
335+
return workflow.tags || [];
336+
}
337+
296338
private castToAnyNotSupportedParam(param: any): any {
297339
return param as any;
298340
}

apps/api/src/app/environments/environments.controller.ts apps/api/src/app/environments-v1/environments-v1.controller.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ import { ApiCommonResponses, ApiResponse } from '../shared/framework/response.de
3232
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
3333
import { SdkGroupName } from '../shared/framework/swagger/sdk.decorators';
3434

35+
/**
36+
* @deprecated use EnvironmentsControllerV2
37+
*/
3538
@ApiCommonResponses()
3639
@Controller('/environments')
3740
@UseInterceptors(ClassSerializerInterceptor)
3841
@UserAuthentication()
3942
@ApiTags('Environments')
40-
export class EnvironmentsController {
43+
export class EnvironmentsControllerV1 {
4144
constructor(
4245
private createEnvironmentUsecase: CreateEnvironment,
4346
private updateEnvironmentUsecase: UpdateEnvironment,

apps/api/src/app/environments/environments.module.ts apps/api/src/app/environments-v1/environments-v1.module.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { forwardRef, Module } from '@nestjs/common';
22

33
import { SharedModule } from '../shared/shared.module';
44
import { USE_CASES } from './usecases';
5-
import { EnvironmentsController } from './environments.controller';
5+
import { EnvironmentsControllerV1 } from './environments-v1.controller';
66
import { NotificationGroupsModule } from '../notification-groups/notification-groups.module';
77
import { AuthModule } from '../auth/auth.module';
88
import { LayoutsModule } from '../layouts/layouts.module';
@@ -16,8 +16,8 @@ import { NovuBridgeModule } from './novu-bridge.module';
1616
forwardRef(() => LayoutsModule),
1717
NovuBridgeModule,
1818
],
19-
controllers: [EnvironmentsController],
19+
controllers: [EnvironmentsControllerV1],
2020
providers: [...USE_CASES],
2121
exports: [...USE_CASES],
2222
})
23-
export class EnvironmentsModule {}
23+
export class EnvironmentsModuleV1 {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsDefined, IsString } from 'class-validator';
3+
4+
export class GetEnvironmentTagsDto {
5+
@ApiProperty()
6+
@IsDefined()
7+
@IsString()
8+
name: string;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ClassSerializerInterceptor, Controller, Get, Param, UseInterceptors } from '@nestjs/common';
2+
import { UserSessionData } from '@novu/shared';
3+
import { ApiTags } from '@nestjs/swagger';
4+
import { UserSession } from '../shared/framework/user.decorator';
5+
import { GetEnvironmentTags, GetEnvironmentTagsCommand } from './usecases/get-environment-tags';
6+
import { ExternalApiAccessible } from '../auth/framework/external-api.decorator';
7+
import { ApiCommonResponses, ApiResponse } from '../shared/framework/response.decorator';
8+
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
9+
import { GetEnvironmentTagsDto } from './dtos/get-environment-tags.dto';
10+
11+
@ApiCommonResponses()
12+
@Controller({ path: `/environments`, version: '2' })
13+
@UseInterceptors(ClassSerializerInterceptor)
14+
@UserAuthentication()
15+
@ApiTags('Environments')
16+
export class EnvironmentsController {
17+
constructor(private getEnvironmentTagsUsecase: GetEnvironmentTags) {}
18+
19+
@Get('/:environmentId/tags')
20+
@ApiResponse(GetEnvironmentTagsDto)
21+
@ExternalApiAccessible()
22+
async getEnvironmentTags(
23+
@UserSession() user: UserSessionData,
24+
@Param('environmentId') environmentId: string
25+
): Promise<GetEnvironmentTagsDto[]> {
26+
return await this.getEnvironmentTagsUsecase.execute(
27+
GetEnvironmentTagsCommand.create({
28+
environmentId,
29+
userId: user._id,
30+
organizationId: user.organizationId,
31+
})
32+
);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Module } from '@nestjs/common';
2+
import { EnvironmentsController } from './environments.controller';
3+
import { GetEnvironmentTags } from './usecases/get-environment-tags';
4+
import { SharedModule } from '../shared/shared.module';
5+
6+
@Module({
7+
imports: [SharedModule],
8+
controllers: [EnvironmentsController],
9+
providers: [GetEnvironmentTags],
10+
exports: [],
11+
})
12+
export class EnvironmentsModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { EnvironmentWithUserCommand } from '../../../shared/commands/project.command';
2+
3+
export class GetEnvironmentTagsCommand extends EnvironmentWithUserCommand {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { EnvironmentRepository, NotificationTemplateRepository } from '@novu/dal';
2+
import { UserSession } from '@novu/testing';
3+
import { expect } from 'chai';
4+
5+
describe('Get Environment Tags - /v2/environments/:environmentId/tags (GET)', async () => {
6+
let session: UserSession;
7+
const environmentRepository = new EnvironmentRepository();
8+
const notificationTemplateRepository = new NotificationTemplateRepository();
9+
10+
before(async () => {
11+
session = new UserSession();
12+
await session.initialize();
13+
});
14+
15+
it('should return correct tags for the environment', async () => {
16+
await notificationTemplateRepository.create({
17+
_environmentId: session.environment._id,
18+
tags: ['tag1', 'tag2'],
19+
});
20+
await notificationTemplateRepository.create({
21+
_environmentId: session.environment._id,
22+
tags: ['tag2', 'tag3', null, '', undefined],
23+
});
24+
25+
const { body } = await session.testAgent.get(`/v2/environments/${session.environment._id}/tags`);
26+
27+
expect(body.data).to.be.an('array');
28+
expect(body.data).to.have.lengthOf(3);
29+
expect(body.data).to.deep.include({ name: 'tag1' });
30+
expect(body.data).to.deep.include({ name: 'tag2' });
31+
expect(body.data).to.deep.include({ name: 'tag3' });
32+
});
33+
34+
it('should return an empty array when no tags exist', async () => {
35+
const newEnvironment = await environmentRepository.create({
36+
name: 'Test Environment',
37+
_organizationId: session.organization._id,
38+
});
39+
40+
const { body } = await session.testAgent.get(`/v2/environments/${newEnvironment._id}/tags`);
41+
42+
expect(body.data).to.be.an('array');
43+
expect(body.data).to.have.lengthOf(0);
44+
});
45+
46+
it('should throw NotFoundException for non-existent environment', async () => {
47+
const nonExistentId = '60a5f2f2f2f2f2f2f2f2f2f2';
48+
const { body } = await session.testAgent.get(`/v2/environments/${nonExistentId}/tags`);
49+
50+
expect(body.statusCode).to.equal(404);
51+
expect(body.message).to.equal(`Environment ${nonExistentId} not found`);
52+
});
53+
});

0 commit comments

Comments
 (0)