Skip to content

Commit

Permalink
chore(root): Release 2024-10-18 08:05 (#6719)
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored Oct 18, 2024
2 parents b7650e7 + e058fa4 commit 71dcd8a
Show file tree
Hide file tree
Showing 88 changed files with 1,551 additions and 513 deletions.
4 changes: 2 additions & 2 deletions apps/api/migrations/changes-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
OrganizationRepository,
} from '@novu/dal';
import { ChangeEntityTypeEnum, MemberRoleEnum } from '@novu/shared';
import { CreateEnvironment } from '../src/app/environments/usecases/create-environment/create-environment.usecase';
import { CreateEnvironmentCommand } from '../src/app/environments/usecases/create-environment/create-environment.command';
import { CreateEnvironment } from '../src/app/environments-v1/usecases/create-environment/create-environment.usecase';
import { CreateEnvironmentCommand } from '../src/app/environments-v1/usecases/create-environment/create-environment.command';
import { ApplyChange } from '../src/app/change/usecases/apply-change/apply-change.usecase';
import { ApplyChangeCommand } from '../src/app/change/usecases/apply-change/apply-change.command';
import { CreateChange, CreateChangeCommand } from '@novu/application-generic';
Expand Down
6 changes: 4 additions & 2 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { AuthModule } from './app/auth/auth.module';
import { TestingModule } from './app/testing/testing.module';
import { HealthModule } from './app/health/health.module';
import { OrganizationModule } from './app/organization/organization.module';
import { EnvironmentsModule } from './app/environments/environments.module';
import { ExecutionDetailsModule } from './app/execution-details/execution-details.module';
import { EventsModule } from './app/events/events.module';
import { WidgetsModule } from './app/widgets/widgets.module';
Expand Down Expand Up @@ -44,6 +43,8 @@ import { PreferencesModule } from './app/preferences';
import { StepSchemasModule } from './app/step-schemas/step-schemas.module';
import { WorkflowModule } from './app/workflows-v2/workflow.module';
import { WorkflowModuleV1 } from './app/workflows-v1/workflow-v1.module';
import { EnvironmentsModuleV1 } from './app/environments-v1/environments-v1.module';
import { EnvironmentsModule } from './app/environments-v2/environments.module';

const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> => {
const modules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [];
Expand Down Expand Up @@ -76,7 +77,7 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
InboundParseModule,
SharedModule,
HealthModule,
EnvironmentsModule,
EnvironmentsModuleV1,
ExecutionDetailsModule,
WorkflowModuleV1,
EventsModule,
Expand Down Expand Up @@ -106,6 +107,7 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
BridgeModule,
PreferencesModule,
WorkflowModule,
EnvironmentsModule,
];

const enterpriseModules = enterpriseImports();
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/app/auth/community.auth.module.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { USE_CASES } from './usecases';
import { SharedModule } from '../shared/shared.module';
import { GitHubStrategy } from './services/passport/github.strategy';
import { OrganizationModule } from '../organization/organization.module';
import { EnvironmentsModule } from '../environments/environments.module';
import { EnvironmentsModuleV1 } from '../environments-v1/environments-v1.module';
import { JwtSubscriberStrategy } from './services/passport/subscriber-jwt.strategy';
import { RootEnvironmentGuard } from './framework/root-environment-guard.service';
import { ApiKeyStrategy } from './services/passport/apikey.strategy';
Expand All @@ -39,7 +39,7 @@ export function getCommunityAuthModuleConfig(): ModuleMetadata {
expiresIn: 360000,
},
}),
EnvironmentsModule,
EnvironmentsModuleV1,
],
controllers: [AuthController],
providers: [
Expand Down
76 changes: 59 additions & 17 deletions apps/api/src/app/bridge/usecases/sync/sync.usecase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { BadRequestException, HttpException, Injectable } from '@nestjs/common';

import {
EnvironmentRepository,
Expand Down Expand Up @@ -56,8 +56,12 @@ export class Sync {
retriesLimit: 1,
workflowOrigin: WorkflowOriginEnum.EXTERNAL,
})) as DiscoverOutput;
} catch (error: any) {
throw new BadRequestException(`Bridge URL is not valid. ${error.message}`);
} catch (error) {
if (error instanceof HttpException) {
throw new BadRequestException(error.message);
}

throw error;
}

if (!discover) {
Expand All @@ -84,7 +88,7 @@ export class Sync {
return persistedWorkflowsInBridge;
}

private async updateBridgeUrl(command: SyncCommand) {
private async updateBridgeUrl(command: SyncCommand): Promise<void> {
await this.environmentRepository.update(
{ _id: command.environmentId },
{
Expand All @@ -100,7 +104,10 @@ export class Sync {
);
}

private async disposeOldWorkflows(command: SyncCommand, createdWorkflows: NotificationTemplateEntity[]) {
private async disposeOldWorkflows(
command: SyncCommand,
createdWorkflows: NotificationTemplateEntity[]
): Promise<void> {
const persistedWorkflowIdsInBridge = createdWorkflows.map((i) => i._id);

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

private async findAllWorkflowsWithOtherIds(command: SyncCommand, persistedWorkflowIdsInBridge: string[]) {
private async findAllWorkflowsWithOtherIds(
command: SyncCommand,
persistedWorkflowIdsInBridge: string[]
): Promise<NotificationTemplateEntity[]> {
return await this.notificationTemplateRepository.find({
_environmentId: command.environmentId,
type: {
Expand All @@ -132,7 +142,10 @@ export class Sync {
});
}

private async createWorkflows(command: SyncCommand, workflowsFromBridge: DiscoverWorkflowOutput[]) {
private async createWorkflows(
command: SyncCommand,
workflowsFromBridge: DiscoverWorkflowOutput[]
): Promise<NotificationTemplateEntity[]> {
return Promise.all(
workflowsFromBridge.map(async (workflow) => {
const workflowExist = await this.notificationTemplateRepository.findByTriggerIdentifier(
Expand Down Expand Up @@ -183,7 +196,12 @@ export class Sync {
);
}

private async createWorkflow(notificationGroupId: string, isWorkflowActive, command: SyncCommand, workflow) {
private async createWorkflow(
notificationGroupId: string,
isWorkflowActive: boolean,
command: SyncCommand,
workflow: DiscoverWorkflowOutput
): Promise<NotificationTemplateEntity> {
return await this.createWorkflowUsecase.execute(
CreateWorkflowCommand.create({
origin: WorkflowOriginEnum.EXTERNAL,
Expand All @@ -193,7 +211,8 @@ export class Sync {
environmentId: command.environmentId,
organizationId: command.organizationId,
userId: command.userId,
name: workflow.workflowId,
name: this.getWorkflowName(workflow),
triggerIdentifier: workflow.workflowId,
__source: WorkflowCreationSourceEnum.BRIDGE,
steps: this.mapSteps(workflow.steps),
/** @deprecated */
Expand All @@ -209,23 +228,28 @@ export class Sync {
/** @deprecated */
(workflow.options?.payloadSchema as Record<string, unknown>),
active: isWorkflowActive,
description: this.castToAnyNotSupportedParam(workflow.options).description,
description: this.getWorkflowDescription(workflow),
data: this.castToAnyNotSupportedParam(workflow).options?.data,
tags: workflow.tags || [],
tags: this.getWorkflowTags(workflow),
critical: this.castToAnyNotSupportedParam(workflow.options)?.critical ?? false,
preferenceSettings: this.castToAnyNotSupportedParam(workflow.options)?.preferenceSettings,
})
);
}

private async updateWorkflow(workflowExist, command: SyncCommand, workflow) {
private async updateWorkflow(
workflowExist: NotificationTemplateEntity,
command: SyncCommand,
workflow: DiscoverWorkflowOutput
): Promise<NotificationTemplateEntity> {
return await this.updateWorkflowUsecase.execute(
UpdateWorkflowCommand.create({
id: workflowExist._id,
environmentId: command.environmentId,
organizationId: command.organizationId,
userId: command.userId,
name: workflow.workflowId,
name: this.getWorkflowName(workflow),
workflowId: workflow.workflowId,
steps: this.mapSteps(workflow.steps, workflowExist),
inputs: {
schema: workflow.controls?.schema || workflow.inputs.schema,
Expand All @@ -238,17 +262,20 @@ export class Sync {
(workflow.payload?.schema as Record<string, unknown>) ||
(workflow.options?.payloadSchema as Record<string, unknown>),
type: WorkflowTypeEnum.BRIDGE,
description: this.castToAnyNotSupportedParam(workflow.options).description,
description: this.getWorkflowDescription(workflow),
data: this.castToAnyNotSupportedParam(workflow.options)?.data,
tags: workflow.tags,
tags: this.getWorkflowTags(workflow),
active: this.castToAnyNotSupportedParam(workflow.options)?.active ?? true,
critical: this.castToAnyNotSupportedParam(workflow.options)?.critical ?? false,
preferenceSettings: this.castToAnyNotSupportedParam(workflow.options)?.preferenceSettings,
})
);
}

private mapSteps(commandWorkflowSteps: DiscoverStepOutput[], workflow?: NotificationTemplateEntity | undefined) {
private mapSteps(
commandWorkflowSteps: DiscoverStepOutput[],
workflow?: NotificationTemplateEntity | undefined
): NotificationStep[] {
const steps: NotificationStep[] = commandWorkflowSteps.map((step) => {
const foundStep = workflow?.steps?.find((workflowStep) => workflowStep.stepId === step.stepId);

Expand All @@ -275,7 +302,10 @@ export class Sync {
return steps;
}

private async getNotificationGroup(notificationGroupIdCommand: string | undefined, environmentId: string) {
private async getNotificationGroup(
notificationGroupIdCommand: string | undefined,
environmentId: string
): Promise<string | undefined> {
let notificationGroupId = notificationGroupIdCommand;

if (!notificationGroupId) {
Expand All @@ -293,6 +323,18 @@ export class Sync {
return notificationGroupId;
}

private getWorkflowName(workflow: DiscoverWorkflowOutput): string {
return workflow.name || workflow.workflowId;
}

private getWorkflowDescription(workflow: DiscoverWorkflowOutput): string {
return workflow.description || '';
}

private getWorkflowTags(workflow: DiscoverWorkflowOutput): string[] {
return workflow.tags || [];
}

private castToAnyNotSupportedParam(param: any): any {
return param as any;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ import { ApiCommonResponses, ApiResponse } from '../shared/framework/response.de
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
import { SdkGroupName } from '../shared/framework/swagger/sdk.decorators';

/**
* @deprecated use EnvironmentsControllerV2
*/
@ApiCommonResponses()
@Controller('/environments')
@UseInterceptors(ClassSerializerInterceptor)
@UserAuthentication()
@ApiTags('Environments')
export class EnvironmentsController {
export class EnvironmentsControllerV1 {
constructor(
private createEnvironmentUsecase: CreateEnvironment,
private updateEnvironmentUsecase: UpdateEnvironment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { forwardRef, Module } from '@nestjs/common';

import { SharedModule } from '../shared/shared.module';
import { USE_CASES } from './usecases';
import { EnvironmentsController } from './environments.controller';
import { EnvironmentsControllerV1 } from './environments-v1.controller';
import { NotificationGroupsModule } from '../notification-groups/notification-groups.module';
import { AuthModule } from '../auth/auth.module';
import { LayoutsModule } from '../layouts/layouts.module';
Expand All @@ -16,8 +16,8 @@ import { NovuBridgeModule } from './novu-bridge.module';
forwardRef(() => LayoutsModule),
NovuBridgeModule,
],
controllers: [EnvironmentsController],
controllers: [EnvironmentsControllerV1],
providers: [...USE_CASES],
exports: [...USE_CASES],
})
export class EnvironmentsModule {}
export class EnvironmentsModuleV1 {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsDefined, IsString } from 'class-validator';

export class GetEnvironmentTagsDto {
@ApiProperty()
@IsDefined()
@IsString()
name: string;
}
34 changes: 34 additions & 0 deletions apps/api/src/app/environments-v2/environments.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ClassSerializerInterceptor, Controller, Get, Param, UseInterceptors } from '@nestjs/common';
import { UserSessionData } from '@novu/shared';
import { ApiTags } from '@nestjs/swagger';
import { UserSession } from '../shared/framework/user.decorator';
import { GetEnvironmentTags, GetEnvironmentTagsCommand } from './usecases/get-environment-tags';
import { ExternalApiAccessible } from '../auth/framework/external-api.decorator';
import { ApiCommonResponses, ApiResponse } from '../shared/framework/response.decorator';
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
import { GetEnvironmentTagsDto } from './dtos/get-environment-tags.dto';

@ApiCommonResponses()
@Controller({ path: `/environments`, version: '2' })
@UseInterceptors(ClassSerializerInterceptor)
@UserAuthentication()
@ApiTags('Environments')
export class EnvironmentsController {
constructor(private getEnvironmentTagsUsecase: GetEnvironmentTags) {}

@Get('/:environmentId/tags')
@ApiResponse(GetEnvironmentTagsDto)
@ExternalApiAccessible()
async getEnvironmentTags(
@UserSession() user: UserSessionData,
@Param('environmentId') environmentId: string
): Promise<GetEnvironmentTagsDto[]> {
return await this.getEnvironmentTagsUsecase.execute(
GetEnvironmentTagsCommand.create({
environmentId,
userId: user._id,
organizationId: user.organizationId,
})
);
}
}
12 changes: 12 additions & 0 deletions apps/api/src/app/environments-v2/environments.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { EnvironmentsController } from './environments.controller';
import { GetEnvironmentTags } from './usecases/get-environment-tags';
import { SharedModule } from '../shared/shared.module';

@Module({
imports: [SharedModule],
controllers: [EnvironmentsController],
providers: [GetEnvironmentTags],
exports: [],
})
export class EnvironmentsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { EnvironmentWithUserCommand } from '../../../shared/commands/project.command';

export class GetEnvironmentTagsCommand extends EnvironmentWithUserCommand {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { EnvironmentRepository, NotificationTemplateRepository } from '@novu/dal';
import { UserSession } from '@novu/testing';
import { expect } from 'chai';

describe('Get Environment Tags - /v2/environments/:environmentId/tags (GET)', async () => {
let session: UserSession;
const environmentRepository = new EnvironmentRepository();
const notificationTemplateRepository = new NotificationTemplateRepository();

before(async () => {
session = new UserSession();
await session.initialize();
});

it('should return correct tags for the environment', async () => {
await notificationTemplateRepository.create({
_environmentId: session.environment._id,
tags: ['tag1', 'tag2'],
});
await notificationTemplateRepository.create({
_environmentId: session.environment._id,
tags: ['tag2', 'tag3', null, '', undefined],
});

const { body } = await session.testAgent.get(`/v2/environments/${session.environment._id}/tags`);

expect(body.data).to.be.an('array');
expect(body.data).to.have.lengthOf(3);
expect(body.data).to.deep.include({ name: 'tag1' });
expect(body.data).to.deep.include({ name: 'tag2' });
expect(body.data).to.deep.include({ name: 'tag3' });
});

it('should return an empty array when no tags exist', async () => {
const newEnvironment = await environmentRepository.create({
name: 'Test Environment',
_organizationId: session.organization._id,
});

const { body } = await session.testAgent.get(`/v2/environments/${newEnvironment._id}/tags`);

expect(body.data).to.be.an('array');
expect(body.data).to.have.lengthOf(0);
});

it('should throw NotFoundException for non-existent environment', async () => {
const nonExistentId = '60a5f2f2f2f2f2f2f2f2f2f2';
const { body } = await session.testAgent.get(`/v2/environments/${nonExistentId}/tags`);

expect(body.statusCode).to.equal(404);
expect(body.message).to.equal(`Environment ${nonExistentId} not found`);
});
});
Loading

0 comments on commit 71dcd8a

Please sign in to comment.