Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Endpoint for canceling events in bulk #6059

Open
wants to merge 3 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions apps/api/src/app/events/dtos/trigger-bulk-cancel-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsBoolean, IsOptional, IsString } from 'class-validator';

export class TriggerBulkCancelResponseDto {
@ApiProperty({
description: 'transaction id for trigger',
})
@IsString()
transactionId: string;

@ApiProperty({
description: 'The success of the trigger',
})
@IsBoolean()
success: boolean;

@ApiProperty({
description: 'In case of an error, this field will contain the error message',
})
@IsArray()
@IsOptional()
error?: string[];
}
11 changes: 11 additions & 0 deletions apps/api/src/app/events/dtos/trigger-event-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,14 @@ export class BulkTriggerEventDto {
@ArrayMaxSize(100)
events: TriggerEventRequestDto[];
}

export class BulkCancelEventDto {
@ApiProperty({
isArray: true,
type: 'string',
})
@IsArray()
@ArrayNotEmpty()
@ArrayMaxSize(100)
transactionIds: string[];
}
34 changes: 33 additions & 1 deletion apps/api/src/app/events/events.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { ResourceCategory } from '@novu/application-generic';

import {
BulkCancelEventDto,
BulkTriggerEventDto,
TestSendEmailRequestDto,
TriggerEventRequestDto,
Expand All @@ -23,6 +24,7 @@ import { ParseEventRequest, ParseEventRequestMulticastCommand } from './usecases
import { ProcessBulkTrigger, ProcessBulkTriggerCommand } from './usecases/process-bulk-trigger';
import { TriggerEventToAll, TriggerEventToAllCommand } from './usecases/trigger-event-to-all';
import { SendTestEmail, SendTestEmailCommand } from './usecases/send-test-email';
import { ProcessBulkCancel, ProcessBulkCancelCommand } from './usecases/process-bulk-cancel';

import { UserSession } from '../shared/framework/user.decorator';
import { ExternalApiAccessible } from '../auth/framework/external-api.decorator';
Expand All @@ -31,6 +33,7 @@ import { DataBooleanDto } from '../shared/dtos/data-wrapper-dto';
import { ThrottlerCategory, ThrottlerCost } from '../rate-limiting/guards';
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
import { SdkGroupName, SdkMethodName, SdkUsageExample } from '../shared/framework/swagger/sdk.decorators';
import { TriggerBulkCancelResponseDto } from './dtos/trigger-bulk-cancel-response.dto';

@ThrottlerCategory(ApiRateLimitCategoryEnum.TRIGGER)
@ResourceCategory(ResourceEnum.EVENTS)
Expand All @@ -46,7 +49,8 @@ export class EventsController {
private triggerEventToAll: TriggerEventToAll,
private sendTestEmail: SendTestEmail,
private parseEventRequest: ParseEventRequest,
private processBulkTriggerUsecase: ProcessBulkTrigger
private processBulkTriggerUsecase: ProcessBulkTrigger,
private processBulkCancel: ProcessBulkCancel
) {}

@ExternalApiAccessible()
Expand Down Expand Up @@ -204,4 +208,32 @@ export class EventsController {
})
);
}

@ExternalApiAccessible()
@UserAuthentication()
@ThrottlerCost(ApiRateLimitCostEnum.BULK)
@Post('/trigger/bulk-cancel')
@ApiOkResponse({
type: TriggerBulkCancelResponseDto,
Copy link
Collaborator

@rifont rifont Aug 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The new endpoint takes an array of transactionIds, therefore the endpoint should produce an array of cancel response Dtos, one response Dto per transactionId. We can achieve specification of this with Nestjs swagger plugin by wrapping the type field with square braces.‏

Suggested change
type: TriggerBulkCancelResponseDto,
type: [TriggerBulkCancelResponseDto],

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

})
@ApiOperation({
summary: 'Bulk Cancel triggered event',
description: `
Using a previously generated transactionId during the event trigger,
will cancel any active or pending workflows. This is useful to cancel active digests, delays etc...
`,
})
@SdkMethodName('triggerBulkCancel')
@SdkUsageExample('Bulk Cancel Triggered Event')
@SdkGroupName('')
async triggerBulkCancel(@UserSession() user: UserSessionData, @Body() body: BulkCancelEventDto) {
return await this.processBulkCancel.execute(
ProcessBulkCancelCommand.create({
userId: user._id,
environmentId: user.environmentId,
organizationId: user.organizationId,
transactionIds: body.transactionIds,
})
);
}
}
2 changes: 2 additions & 0 deletions apps/api/src/app/events/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { VerifyPayload } from './verify-payload';
import { ParseEventRequest } from './parse-event-request';
import { ProcessBulkTrigger } from './process-bulk-trigger';
import { SendTestEmail } from './send-test-email';
import { ProcessBulkCancel } from './process-bulk-cancel';

export const USE_CASES = [
CancelDelayed,
Expand All @@ -12,4 +13,5 @@ export const USE_CASES = [
ParseEventRequest,
ProcessBulkTrigger,
SendTestEmail,
ProcessBulkCancel,
];
2 changes: 2 additions & 0 deletions apps/api/src/app/events/usecases/process-bulk-cancel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ProcessBulkCancelCommand } from './process-bulk-cancel.command';
export { ProcessBulkCancel } from './process-bulk-cancel.usecase';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { EnvironmentWithUserCommand } from '../../../shared/commands/project.command';
import { IsArray } from 'class-validator';

export class ProcessBulkCancelCommand extends EnvironmentWithUserCommand {
@IsArray()
transactionIds: string[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Injectable } from '@nestjs/common';
import { TriggerBulkCancelResponseDto } from '../../dtos/trigger-bulk-cancel-response.dto';
import { CancelDelayed, CancelDelayedCommand } from '../cancel-delayed';
import { ProcessBulkCancelCommand } from './process-bulk-cancel.command';

@Injectable()
export class ProcessBulkCancel {
constructor(private cancelDelayed: CancelDelayed) {}

async execute(command: ProcessBulkCancelCommand) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chore: Please add a return typing to this function using the Dto to fully enforce the use-case boundary with type-safety.‏

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const results: TriggerBulkCancelResponseDto[] = [];

for (const transactionId of command.transactionIds) {
let result: TriggerBulkCancelResponseDto;

try {
const promise = await this.cancelDelayed.execute(
CancelDelayedCommand.create({
...command,
transactionId: transactionId,
})
);

result = {
transactionId,
success: promise,
};
} catch (e) {
let error: string[];
if (e.response?.message) {
error = Array.isArray(e.response?.message) ? e.response?.message : [e.response?.message];
} else {
error = [e.message];
}

result = {
transactionId,
success: false,
error,
};
}

results.push(result);
}

return results;
}
}
3 changes: 2 additions & 1 deletion apps/api/src/metadata.ts

Large diffs are not rendered by default.

Loading