diff --git a/apps/api/src/app/bridge/usecases/preview-step/preview-step.usecase.ts b/apps/api/src/app/bridge/usecases/preview-step/preview-step.usecase.ts index 22db671f9f4..2a1ccf7dfcc 100644 --- a/apps/api/src/app/bridge/usecases/preview-step/preview-step.usecase.ts +++ b/apps/api/src/app/bridge/usecases/preview-step/preview-step.usecase.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { Event, ExecuteOutput, HttpQueryKeysEnum, PostActionEnum } from '@novu/framework/internal'; -import { ExecuteBridgeRequest, ExecuteBridgeRequestCommand } from '@novu/application-generic'; +import { ExecuteBridgeRequest, ExecuteBridgeRequestCommand, InstrumentUsecase } from '@novu/application-generic'; import { PreviewStepCommand } from './preview-step.command'; @@ -8,6 +8,7 @@ import { PreviewStepCommand } from './preview-step.command'; export class PreviewStep { constructor(private executeBridgeRequest: ExecuteBridgeRequest) {} + @InstrumentUsecase() async execute(command: PreviewStepCommand): Promise { const event = this.buildBridgeEventPayload(command); const executeCommand = this.createExecuteCommand(command, event); diff --git a/apps/api/src/app/environments-v1/usecases/construct-framework-workflow/construct-framework-workflow.usecase.ts b/apps/api/src/app/environments-v1/usecases/construct-framework-workflow/construct-framework-workflow.usecase.ts index 1c429ba1d02..59b53303a4b 100644 --- a/apps/api/src/app/environments-v1/usecases/construct-framework-workflow/construct-framework-workflow.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/construct-framework-workflow/construct-framework-workflow.usecase.ts @@ -3,6 +3,7 @@ import { workflow } from '@novu/framework/express'; import { ActionStep, ChannelStep, JsonSchema, Step, StepOptions, StepOutput, Workflow } from '@novu/framework/internal'; import { NotificationStepEntity, NotificationTemplateEntity, NotificationTemplateRepository } from '@novu/dal'; import { StepTypeEnum } from '@novu/shared'; +import { Instrument, InstrumentUsecase } from '@novu/application-generic'; import { ConstructFrameworkWorkflowCommand } from './construct-framework-workflow.command'; import { ChatOutputRendererUsecase, @@ -28,6 +29,7 @@ export class ConstructFrameworkWorkflow { private digestOutputRendererUseCase: DigestOutputRendererUsecase ) {} + @InstrumentUsecase() async execute(command: ConstructFrameworkWorkflowCommand): Promise { const dbWorkflow = await this.getDbWorkflow(command.environmentId, command.workflowId); if (command.controlValues) { @@ -39,6 +41,7 @@ export class ConstructFrameworkWorkflow { return this.constructFrameworkWorkflow(dbWorkflow); } + @Instrument() private constructFrameworkWorkflow(newWorkflow: NotificationTemplateEntity): Workflow { return workflow( newWorkflow.triggers[0].identifier, @@ -67,6 +70,7 @@ export class ConstructFrameworkWorkflow { ); } + @Instrument() private constructStep( step: Step, staticStep: NotificationStepEntity, @@ -154,6 +158,7 @@ export class ConstructFrameworkWorkflow { } } + @Instrument() private constructChannelStepOptions(staticStep: NotificationStepEntity): Required[2]> { return { ...this.constructCommonStepOptions(staticStep), @@ -164,12 +169,14 @@ export class ConstructFrameworkWorkflow { }; } + @Instrument() private constructActionStepOptions(staticStep: NotificationStepEntity): Required[2]> { return { ...this.constructCommonStepOptions(staticStep), }; } + @Instrument() private constructCommonStepOptions(staticStep: NotificationStepEntity): Required { return { // TODO: fix the `JSONSchemaDto` type to enforce a non-primitive schema type. @@ -181,6 +188,8 @@ export class ConstructFrameworkWorkflow { skip: (controlValues) => false, }; } + + @Instrument() private async getDbWorkflow(environmentId: string, workflowId: string): Promise { const foundWorkflow = await this.workflowsRepository.findByTriggerIdentifier(environmentId, workflowId); diff --git a/apps/api/src/app/environments-v1/usecases/output-renderers/chat-output-renderer.usecase.ts b/apps/api/src/app/environments-v1/usecases/output-renderers/chat-output-renderer.usecase.ts index 1cad2b13e25..9a3374f8521 100644 --- a/apps/api/src/app/environments-v1/usecases/output-renderers/chat-output-renderer.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/output-renderers/chat-output-renderer.usecase.ts @@ -1,10 +1,12 @@ // Concrete Renderer for Chat Preview import { ChatRenderOutput } from '@novu/shared'; import { Injectable } from '@nestjs/common'; +import { InstrumentUsecase } from '@novu/application-generic'; import { RenderCommand } from './render-command'; @Injectable() export class ChatOutputRendererUsecase { + @InstrumentUsecase() execute(renderCommand: RenderCommand): ChatRenderOutput { const body = renderCommand.controlValues.body as string; diff --git a/apps/api/src/app/environments-v1/usecases/output-renderers/delay-output-renderer.usecase.ts b/apps/api/src/app/environments-v1/usecases/output-renderers/delay-output-renderer.usecase.ts index a0ca156edcb..3f4fc681aeb 100644 --- a/apps/api/src/app/environments-v1/usecases/output-renderers/delay-output-renderer.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/output-renderers/delay-output-renderer.usecase.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { DelayRenderOutput } from '@novu/shared'; +import { InstrumentUsecase } from '@novu/application-generic'; import { RenderCommand } from './render-command'; import { DelayTimeControlType, @@ -8,6 +9,7 @@ import { @Injectable() export class DelayOutputRendererUsecase { + @InstrumentUsecase() execute(renderCommand: RenderCommand): DelayRenderOutput { const delayTimeControlType: DelayTimeControlType = DelayTimeControlZodSchema.parse(renderCommand.controlValues); diff --git a/apps/api/src/app/environments-v1/usecases/output-renderers/digest-output-renderer.usecase.ts b/apps/api/src/app/environments-v1/usecases/output-renderers/digest-output-renderer.usecase.ts index 5ac7ab57bc6..a54c89aec85 100644 --- a/apps/api/src/app/environments-v1/usecases/output-renderers/digest-output-renderer.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/output-renderers/digest-output-renderer.usecase.ts @@ -1,5 +1,6 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { DigestRenderOutput } from '@novu/shared'; +import { InstrumentUsecase } from '@novu/application-generic'; import { RenderCommand } from './render-command'; import { DigestControlSchemaType, @@ -10,6 +11,7 @@ import { @Injectable() export class DigestOutputRendererUsecase { + @InstrumentUsecase() execute(renderCommand: RenderCommand): DigestRenderOutput { const parse: DigestControlSchemaType = DigestControlZodSchema.parse(renderCommand.controlValues); if ( diff --git a/apps/api/src/app/environments-v1/usecases/output-renderers/in-app-output-renderer.usecase.ts b/apps/api/src/app/environments-v1/usecases/output-renderers/in-app-output-renderer.usecase.ts index 44236d2f5e5..9dc621ac43f 100644 --- a/apps/api/src/app/environments-v1/usecases/output-renderers/in-app-output-renderer.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/output-renderers/in-app-output-renderer.usecase.ts @@ -1,6 +1,7 @@ // Concrete Renderer for In-App Message Preview import { InAppRenderOutput, RedirectTargetEnum } from '@novu/shared'; import { Injectable } from '@nestjs/common'; +import { Instrument, InstrumentUsecase } from '@novu/application-generic'; import { RenderCommand } from './render-command'; import { InAppActionType, @@ -12,6 +13,7 @@ import { isValidUrlForActionButton } from '../../../workflows-v2/util/url-utils' @Injectable() export class InAppOutputRendererUsecase { + @InstrumentUsecase() execute(renderCommand: RenderCommand): InAppRenderOutput { const inApp: InAppControlType = InAppControlZodSchema.parse(renderCommand.controlValues); if (!inApp) { diff --git a/apps/api/src/app/environments-v1/usecases/output-renderers/push-output-renderer.usecase.ts b/apps/api/src/app/environments-v1/usecases/output-renderers/push-output-renderer.usecase.ts index 326fe07f4bf..313c9298cf5 100644 --- a/apps/api/src/app/environments-v1/usecases/output-renderers/push-output-renderer.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/output-renderers/push-output-renderer.usecase.ts @@ -1,9 +1,11 @@ import { PushRenderOutput } from '@novu/shared'; import { Injectable } from '@nestjs/common'; +import { InstrumentUsecase } from '@novu/application-generic'; import { RenderCommand } from './render-command'; @Injectable() export class PushOutputRendererUsecase { + @InstrumentUsecase() execute(renderCommand: RenderCommand): PushRenderOutput { const subject = renderCommand.controlValues.subject as string; const body = renderCommand.controlValues.body as string; diff --git a/apps/api/src/app/environments-v1/usecases/output-renderers/render-email-output.usecase.ts b/apps/api/src/app/environments-v1/usecases/output-renderers/render-email-output.usecase.ts index 25fe821c97e..e3bfa56781e 100644 --- a/apps/api/src/app/environments-v1/usecases/output-renderers/render-email-output.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/output-renderers/render-email-output.usecase.ts @@ -1,6 +1,7 @@ -import { EmailRenderOutput } from '@novu/shared'; +import { EmailRenderOutput, TipTapNode } from '@novu/shared'; import { Injectable } from '@nestjs/common'; -import { render } from '@maily-to/render'; +import { render as mailyRender } from '@maily-to/render'; +import { Instrument, InstrumentUsecase } from '@novu/application-generic'; import { FullPayloadForRender, RenderCommand } from './render-command'; import { ExpandEmailEditorSchemaUsecase } from './expand-email-editor-schema.usecase'; import { EmailStepControlZodSchema } from '../../../workflows-v2/shared'; @@ -9,17 +10,24 @@ export class RenderEmailOutputCommand extends RenderCommand {} @Injectable() export class RenderEmailOutputUsecase { - constructor(private expendEmailEditorSchemaUseCase: ExpandEmailEditorSchemaUsecase) {} + constructor(private expandEmailEditorSchemaUseCase: ExpandEmailEditorSchemaUsecase) {} + @InstrumentUsecase() async execute(renderCommand: RenderEmailOutputCommand): Promise { const { emailEditor, subject } = EmailStepControlZodSchema.parse(renderCommand.controlValues); const expandedSchema = this.transformForAndShowLogic(emailEditor, renderCommand.fullPayloadForRender); - const htmlRendered = await render(expandedSchema); + const htmlRendered = await this.renderEmail(expandedSchema); return { subject, body: htmlRendered }; } + @Instrument() + private renderEmail(content: TipTapNode): Promise { + return mailyRender(content); + } + + @Instrument() private transformForAndShowLogic(body: string, fullPayloadForRender: FullPayloadForRender) { - return this.expendEmailEditorSchemaUseCase.execute({ emailEditorJson: body, fullPayloadForRender }); + return this.expandEmailEditorSchemaUseCase.execute({ emailEditorJson: body, fullPayloadForRender }); } } diff --git a/apps/api/src/app/environments-v1/usecases/output-renderers/sms-output-renderer.usecase.ts b/apps/api/src/app/environments-v1/usecases/output-renderers/sms-output-renderer.usecase.ts index 47a34748d45..1558a371c12 100644 --- a/apps/api/src/app/environments-v1/usecases/output-renderers/sms-output-renderer.usecase.ts +++ b/apps/api/src/app/environments-v1/usecases/output-renderers/sms-output-renderer.usecase.ts @@ -1,10 +1,12 @@ // Concrete Renderer for SMS Preview import { SmsRenderOutput } from '@novu/shared'; import { Injectable } from '@nestjs/common'; +import { InstrumentUsecase } from '@novu/application-generic'; import { RenderCommand } from './render-command'; @Injectable() export class SmsOutputRendererUsecase { + @InstrumentUsecase() execute(renderCommand: RenderCommand): SmsRenderOutput { const body = renderCommand.controlValues.body as string; diff --git a/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts b/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts index f8f4dfb52db..8225b0ce2c0 100644 --- a/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/build-step-data/build-step-data.usecase.ts @@ -1,7 +1,7 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { ControlValuesLevelEnum, StepDataDto, WorkflowOriginEnum } from '@novu/shared'; import { ControlValuesRepository, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal'; -import { GetWorkflowByIdsUseCase } from '@novu/application-generic'; +import { GetWorkflowByIdsUseCase, Instrument, InstrumentUsecase } from '@novu/application-generic'; import { BuildStepDataCommand } from './build-step-data.command'; import { InvalidStepException } from '../../exceptions/invalid-step.exception'; import { BuildAvailableVariableSchemaUsecase } from '../build-variable-schema'; @@ -14,6 +14,7 @@ export class BuildStepDataUsecase { private buildAvailableVariableSchemaUsecase: BuildAvailableVariableSchemaUsecase // Dependency injection for new use case ) {} + @InstrumentUsecase() async execute(command: BuildStepDataCommand): Promise { const workflow = await this.fetchWorkflow(command); @@ -49,6 +50,7 @@ export class BuildStepDataUsecase { }; } + @Instrument() private async fetchWorkflow(command: BuildStepDataCommand) { return await this.getWorkflowByIdsUseCase.execute({ identifierOrInternalId: command.identifierOrInternalId, @@ -58,6 +60,7 @@ export class BuildStepDataUsecase { }); } + @Instrument() private async getValues(command: BuildStepDataCommand, currentStep: NotificationStepEntity, _workflowId: string) { const controlValuesEntity = await this.controlValuesRepository.findOne({ _environmentId: command.user.environmentId, @@ -70,6 +73,7 @@ export class BuildStepDataUsecase { return controlValuesEntity?.controls || {}; } + @Instrument() private async loadStepsFromDb(command: BuildStepDataCommand, workflow: NotificationTemplateEntity) { const currentStep = workflow.steps.find( (stepItem) => stepItem._id === command.stepId || stepItem.stepId === command.stepId diff --git a/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts b/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts index f3af2e6023f..4a78e81e600 100644 --- a/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/build-test-data/build-workflow-test-data.usecase.ts @@ -2,13 +2,19 @@ import { Injectable } from '@nestjs/common'; import { NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal'; import { JSONSchemaDto, StepTypeEnum, UserSessionData, WorkflowTestDataResponseDto } from '@novu/shared'; -import { GetWorkflowByIdsCommand, GetWorkflowByIdsUseCase } from '@novu/application-generic'; +import { + GetWorkflowByIdsCommand, + GetWorkflowByIdsUseCase, + Instrument, + InstrumentUsecase, +} from '@novu/application-generic'; import { WorkflowTestDataCommand } from './build-workflow-test-data.command'; @Injectable() export class BuildWorkflowTestDataUseCase { constructor(private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase) {} + @InstrumentUsecase() async execute(command: WorkflowTestDataCommand): Promise { const _workflowEntity: NotificationTemplateEntity = await this.fetchWorkflow(command); const toSchema = buildToFieldSchema({ user: command.user, steps: _workflowEntity.steps }); @@ -20,6 +26,7 @@ export class BuildWorkflowTestDataUseCase { }; } + @Instrument() private async fetchWorkflow(command: WorkflowTestDataCommand): Promise { return await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ diff --git a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts index 849cea23a80..0070700da6f 100644 --- a/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts @@ -8,6 +8,7 @@ import { StepDataDto, UserSessionData, } from '@novu/shared'; +import { Instrument, InstrumentUsecase } from '@novu/application-generic'; import { PreviewStep, PreviewStepCommand } from '../../../bridge/usecases/preview-step'; import { FrameworkPreviousStepsOutputState } from '../../../bridge/usecases/preview-step/preview-step.command'; import { StepMissingControlsException } from '../../exceptions/step-not-found-exception'; @@ -23,6 +24,7 @@ export class GeneratePreviewUsecase { private buildStepDataUsecase: BuildStepDataUsecase ) {} + @InstrumentUsecase() async execute(command: GeneratePreviewCommand): Promise { const dto = command.generatePreviewRequestDto; const stepData = await this.getStepData(command); @@ -45,6 +47,7 @@ export class GeneratePreviewUsecase { }; } + @Instrument() private async getValidatedContent(dto: GeneratePreviewRequestDto, stepData: StepDataDto, user: UserSessionData) { if (!stepData.controls?.dataSchema) { throw new StepMissingControlsException(stepData.stepId, stepData); @@ -60,6 +63,7 @@ export class GeneratePreviewUsecase { }); } + @Instrument() private async getStepData(command: GeneratePreviewCommand) { return await this.buildStepDataUsecase.execute({ identifierOrInternalId: command.workflowId, @@ -67,9 +71,12 @@ export class GeneratePreviewUsecase { user: command.user, }); } + private isFrameworkError(obj: any): obj is FrameworkError { return typeof obj === 'object' && obj.status === '400' && obj.name === 'BridgeRequestError'; } + + @Instrument() private async executePreviewUsecase( command: GeneratePreviewCommand, stepData: StepDataDto, diff --git a/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts index e0f3164b340..776b5d7b744 100644 --- a/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/get-workflow/get-workflow.usecase.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { WorkflowResponseDto } from '@novu/shared'; -import { GetWorkflowByIdsCommand, GetWorkflowByIdsUseCase } from '@novu/application-generic'; +import { GetWorkflowByIdsCommand, GetWorkflowByIdsUseCase, InstrumentUsecase } from '@novu/application-generic'; import { GetWorkflowCommand } from './get-workflow.command'; import { toResponseWorkflowDto } from '../../mappers/notification-template-mapper'; @@ -9,6 +9,8 @@ import { toResponseWorkflowDto } from '../../mappers/notification-template-mappe @Injectable() export class GetWorkflowUseCase { constructor(private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase) {} + + @InstrumentUsecase() async execute(command: GetWorkflowCommand): Promise { const workflowEntity = await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ diff --git a/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts index fc54259789b..dd0c2a3fdcd 100644 --- a/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/list-workflows/list-workflow.usecase.ts @@ -2,12 +2,15 @@ import { Injectable } from '@nestjs/common'; import { NotificationTemplateRepository } from '@novu/dal'; import { ListWorkflowResponse } from '@novu/shared'; +import { InstrumentUsecase } from '@novu/application-generic'; import { ListWorkflowsCommand } from './list-workflows.command'; import { toWorkflowsMinifiedDtos } from '../../mappers/notification-template-mapper'; @Injectable() export class ListWorkflowsUseCase { constructor(private notificationTemplateRepository: NotificationTemplateRepository) {} + + @InstrumentUsecase() async execute(command: ListWorkflowsCommand): Promise { const res = await this.notificationTemplateRepository.getList( command.user.organizationId, diff --git a/apps/api/src/app/workflows-v2/usecases/post-process-workflow-update/post-process-workflow-update.usecase.ts b/apps/api/src/app/workflows-v2/usecases/post-process-workflow-update/post-process-workflow-update.usecase.ts index 40322f1ba1c..fd3e7f46afc 100644 --- a/apps/api/src/app/workflows-v2/usecases/post-process-workflow-update/post-process-workflow-update.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/post-process-workflow-update/post-process-workflow-update.usecase.ts @@ -10,9 +10,9 @@ import { WorkflowResponseDto, WorkflowStatusEnum, } from '@novu/shared'; -import { ControlValuesRepository, NotificationStepEntity, NotificationTemplateRepository } from '@novu/dal'; +import { NotificationStepEntity, NotificationTemplateRepository } from '@novu/dal'; import { Injectable } from '@nestjs/common'; -import { WorkflowInternalResponseDto } from '@novu/application-generic'; +import { Instrument, InstrumentUsecase, WorkflowInternalResponseDto } from '@novu/application-generic'; import { PostProcessWorkflowUpdateCommand } from './post-process-workflow-update.command'; import { OverloadContentDataOnWorkflowUseCase } from '../overload-content-data'; @@ -32,10 +32,10 @@ import { OverloadContentDataOnWorkflowUseCase } from '../overload-content-data'; export class PostProcessWorkflowUpdate { constructor( private notificationTemplateRepository: NotificationTemplateRepository, - private controlValuesRepository: ControlValuesRepository, private overloadContentDataOnWorkflowUseCase: OverloadContentDataOnWorkflowUseCase ) {} + @InstrumentUsecase() async execute(command: PostProcessWorkflowUpdateCommand): Promise { const workflowIssues = await this.validateWorkflow(command); const stepIssues = this.validateSteps(command.workflow.steps, command.workflow._id); @@ -107,6 +107,7 @@ export class PostProcessWorkflowUpdate { return stepIdToIssues; } + @Instrument() private async validateWorkflow( command: PostProcessWorkflowUpdateCommand ): Promise> { @@ -117,6 +118,7 @@ export class PostProcessWorkflowUpdate { return issues; } + @Instrument() private async addTriggerIdentifierNotUniqueIfApplicable( command: PostProcessWorkflowUpdateCommand, issues: Record diff --git a/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts b/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts index 7121b19547c..32e39114570 100644 --- a/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts @@ -12,6 +12,7 @@ import { WorkflowResponseDto, } from '@novu/shared'; import { PreferencesEntity, PreferencesRepository } from '@novu/dal'; +import { Instrument, InstrumentUsecase } from '@novu/application-generic'; import { SyncToEnvironmentCommand } from './sync-to-environment.command'; import { GetWorkflowCommand, GetWorkflowUseCase } from '../get-workflow'; import { UpsertWorkflowCommand, UpsertWorkflowUseCase } from '../upsert-workflow'; @@ -35,6 +36,7 @@ export class SyncToEnvironmentUseCase { private buildStepDataUsecase: BuildStepDataUsecase ) {} + @InstrumentUsecase() async execute(command: SyncToEnvironmentCommand): Promise { if (command.user.environmentId === command.targetEnvironmentId) { throw new BadRequestException('Cannot sync workflow to the same environment'); @@ -55,6 +57,7 @@ export class SyncToEnvironmentUseCase { ); } + @Instrument() private async buildRequestDto( originWorkflow: WorkflowResponseDto, preferencesToClone: PreferencesEntity[], @@ -68,6 +71,7 @@ export class SyncToEnvironmentUseCase { return await this.mapWorkflowToCreateWorkflowDto(originWorkflow, preferencesToClone, command); } + @Instrument() private async getWorkflowToClone(command: SyncToEnvironmentCommand): Promise { return this.getWorkflowUseCase.execute( GetWorkflowCommand.create({ @@ -77,6 +81,7 @@ export class SyncToEnvironmentUseCase { ); } + @Instrument() private async findWorkflowInTargetEnvironment( command: SyncToEnvironmentCommand, externalId: string @@ -93,6 +98,7 @@ export class SyncToEnvironmentUseCase { } } + @Instrument() private async mapWorkflowToCreateWorkflowDto( originWorkflow: WorkflowResponseDto, preferences: PreferencesEntity[], @@ -110,6 +116,7 @@ export class SyncToEnvironmentUseCase { }; } + @Instrument() private async mapWorkflowToUpdateWorkflowDto( originWorkflow: WorkflowResponseDto, existingWorkflowInProd: WorkflowResponseDto | undefined, @@ -127,6 +134,7 @@ export class SyncToEnvironmentUseCase { }; } + @Instrument() private async mapStepsToDto( originSteps: StepResponseDto[], command: SyncToEnvironmentCommand, diff --git a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts index b1fd3ef556c..898487d7b02 100644 --- a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts @@ -24,6 +24,8 @@ import { shortId, UpdateWorkflow, UpdateWorkflowCommand, + InstrumentUsecase, + Instrument, } from '@novu/application-generic'; import { BadRequestException, Injectable } from '@nestjs/common'; import { UpsertWorkflowCommand } from './upsert-workflow.command'; @@ -43,6 +45,7 @@ export class UpsertWorkflowUseCase { private patchStepDataUsecase: PatchStepUsecase ) {} + @InstrumentUsecase() async execute(command: UpsertWorkflowCommand): Promise { const workflowForUpdate = await this.queryWorkflow(command); let persistedWorkflow = await this.createOrUpdateWorkflow(workflowForUpdate, command); @@ -57,6 +60,7 @@ export class UpsertWorkflowUseCase { return toResponseWorkflowDto(persistedWorkflow); } + @Instrument() private async getWorkflow(workflowId: string, command: UpsertWorkflowCommand): Promise { return await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ @@ -68,6 +72,7 @@ export class UpsertWorkflowUseCase { ); } + @Instrument() private async persistWorkflow(workflowWithIssues: WorkflowInternalResponseDto) { const command = UpdateWorkflowCommand.create({ id: workflowWithIssues._id, @@ -81,6 +86,7 @@ export class UpsertWorkflowUseCase { await this.updateWorkflowUsecase.execute(command); } + @Instrument() private async queryWorkflow(command: UpsertWorkflowCommand): Promise { if (!command.identifierOrInternalId) { return null; @@ -96,6 +102,7 @@ export class UpsertWorkflowUseCase { ); } + @Instrument() private async createOrUpdateWorkflow( existingWorkflow: NotificationTemplateEntity | null, command: UpsertWorkflowCommand @@ -113,6 +120,7 @@ export class UpsertWorkflowUseCase { ); } + @Instrument() private async buildCreateWorkflowGenericCommand(command: UpsertWorkflowCommand): Promise { const { user } = command; // It's safe to assume we're dealing with CreateWorkflowDto on the creation path @@ -278,6 +286,7 @@ export class UpsertWorkflowUseCase { * @deprecated This method will be removed in future versions. * Please use `the patch step data instead, do not add here anything` instead. */ + @Instrument() private async upsertControlValues( workflow: NotificationTemplateEntity, command: UpsertWorkflowCommand diff --git a/apps/api/src/app/workflows-v2/usecases/validate-content/prepare-and-validate-content/prepare-and-validate-content.usecase.ts b/apps/api/src/app/workflows-v2/usecases/validate-content/prepare-and-validate-content/prepare-and-validate-content.usecase.ts index 382334c0e73..8a58b125684 100644 --- a/apps/api/src/app/workflows-v2/usecases/validate-content/prepare-and-validate-content/prepare-and-validate-content.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/validate-content/prepare-and-validate-content/prepare-and-validate-content.usecase.ts @@ -9,7 +9,7 @@ import { StepTypeEnum, UserSessionData, } from '@novu/shared'; -import { TierRestrictionsValidateUsecase } from '@novu/application-generic'; +import { Instrument, InstrumentUsecase, TierRestrictionsValidateUsecase } from '@novu/application-generic'; import { PrepareAndValidateContentCommand } from './prepare-and-validate-content.command'; import { flattenJson, flattenToNested, mergeObjects } from '../../../util/jsonUtils'; @@ -39,6 +39,7 @@ export class PrepareAndValidateContentUsecase { private tierRestrictionsValidateUsecase: TierRestrictionsValidateUsecase ) {} + @InstrumentUsecase() async execute(command: PrepareAndValidateContentCommand): Promise { const controlValueToPlaceholders = this.collectPlaceholders(command.controlValues); const controlValueToValidPlaceholders = this.validatePlaceholders( @@ -234,6 +235,7 @@ export class PrepareAndValidateContentUsecase { return targetText.trim(); } + @Instrument() private async buildIssues( payload: PreviewPayload, providedPayload: PreviewPayload, @@ -338,6 +340,7 @@ export class PrepareAndValidateContentUsecase { ); } + @Instrument() private async computeTierIssues( defaultControlValues: Record, user: UserSessionData, diff --git a/apps/worker/src/app/workflow/usecases/execute-bridge-job/execute-bridge-job.usecase.ts b/apps/worker/src/app/workflow/usecases/execute-bridge-job/execute-bridge-job.usecase.ts index 0929bfb518b..25c49e1bd82 100644 --- a/apps/worker/src/app/workflow/usecases/execute-bridge-job/execute-bridge-job.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/execute-bridge-job/execute-bridge-job.usecase.ts @@ -26,6 +26,8 @@ import { DetailEnum, ExecuteBridgeRequest, ExecuteBridgeRequestCommand, + Instrument, + InstrumentUsecase, } from '@novu/application-generic'; import { ExecuteBridgeJobCommand } from './execute-bridge-job.command'; @@ -43,6 +45,7 @@ export class ExecuteBridgeJob { private executeBridgeRequest: ExecuteBridgeRequest ) {} + @InstrumentUsecase() async execute(command: ExecuteBridgeJobCommand): Promise { const stepId = command.job.step.stepId || command.job.step.uuid; @@ -183,6 +186,7 @@ export class ExecuteBridgeJob { return previousJobs; } + @Instrument() private async sendBridgeRequest({ statelessBridgeUrl, event, @@ -222,6 +226,7 @@ export class ExecuteBridgeJob { }) as Promise; } + @Instrument() private async mapState(job: JobEntity, payload: Record) { let output = {}; diff --git a/libs/application-generic/src/usecases/delete-preferences/delete-preferences.usecase.ts b/libs/application-generic/src/usecases/delete-preferences/delete-preferences.usecase.ts index a2219bfbfcf..e5c4eba18a5 100644 --- a/libs/application-generic/src/usecases/delete-preferences/delete-preferences.usecase.ts +++ b/libs/application-generic/src/usecases/delete-preferences/delete-preferences.usecase.ts @@ -1,11 +1,13 @@ import { Injectable } from '@nestjs/common'; import { PreferencesEntity, PreferencesRepository } from '@novu/dal'; import { DeletePreferencesCommand } from './delete-preferences.command'; +import { Instrument, InstrumentUsecase } from '../../instrumentation'; @Injectable() export class DeletePreferencesUseCase { constructor(private preferencesRepository: PreferencesRepository) {} + @InstrumentUsecase() public async execute(command: DeletePreferencesCommand): Promise { const existingPreference = await this.getPreference(command); @@ -24,6 +26,7 @@ export class DeletePreferencesUseCase { await this.deletePreferences(command, existingPreference._id); } + @Instrument() private async deletePreferences( command: DeletePreferencesCommand, preferencesId: string, @@ -37,6 +40,7 @@ export class DeletePreferencesUseCase { }); } + @Instrument() private async getPreference( command: DeletePreferencesCommand, ): Promise { diff --git a/libs/application-generic/src/usecases/execute-bridge-request/execute-bridge-request.usecase.ts b/libs/application-generic/src/usecases/execute-bridge-request/execute-bridge-request.usecase.ts index 2049aa7622d..c283a6edbe0 100644 --- a/libs/application-generic/src/usecases/execute-bridge-request/execute-bridge-request.usecase.ts +++ b/libs/application-generic/src/usecases/execute-bridge-request/execute-bridge-request.usecase.ts @@ -39,6 +39,7 @@ import { } from '../get-decrypted-secret-key'; import { BRIDGE_EXECUTION_ERROR } from '../../utils'; import { HttpRequestHeaderKeysEnum } from '../../http'; +import { Instrument, InstrumentUsecase } from '../../instrumentation'; export const DEFAULT_TIMEOUT = 5_000; // 5 seconds export const DEFAULT_RETRIES_LIMIT = 3; @@ -104,6 +105,7 @@ export class ExecuteBridgeRequest { private getDecryptedSecretKey: GetDecryptedSecretKey, ) {} + @InstrumentUsecase() async execute( command: ExecuteBridgeRequestCommand, ): Promise> { @@ -190,6 +192,7 @@ export class ExecuteBridgeRequest { } } + @Instrument() private async buildRequestHeaders(command: ExecuteBridgeRequestCommand) { const novuSignatureHeader = await this.buildRequestSignature(command); @@ -200,6 +203,7 @@ export class ExecuteBridgeRequest { }; } + @Instrument() private async buildRequestSignature(command: ExecuteBridgeRequestCommand) { const secretKey = await this.getDecryptedSecretKey.execute( GetDecryptedSecretKeyCommand.create({ @@ -217,6 +221,7 @@ export class ExecuteBridgeRequest { return novuSignatureHeader; } + @Instrument() private createHmacBySecretKey( secretKey: string, timestamp: number, @@ -239,6 +244,7 @@ export class ExecuteBridgeRequest { * @param statelessBridgeUrl - The URL of the stateless bridge app. * @returns The correct bridge URL. */ + @Instrument() private getBridgeUrl( environmentBridgeUrl: string, environmentId: string, @@ -251,10 +257,11 @@ export class ExecuteBridgeRequest { } switch (workflowOrigin) { - case WorkflowOriginEnum.NOVU_CLOUD: + case WorkflowOriginEnum.NOVU_CLOUD: { const apiUrl = this.getApiUrl(action); return `${apiUrl}/v1/environments/${environmentId}/bridge`; + } case WorkflowOriginEnum.EXTERNAL: { if (!environmentBridgeUrl) { throw new BadRequestException({ @@ -287,6 +294,7 @@ export class ExecuteBridgeRequest { return apiUrl; } + @Instrument() private async handleResponseError( error: unknown, url: string, diff --git a/libs/application-generic/src/usecases/tier-restrictions-validate/tier-restrictions-validate.usecase.ts b/libs/application-generic/src/usecases/tier-restrictions-validate/tier-restrictions-validate.usecase.ts index 0e3f01d11a6..7fd3afee851 100644 --- a/libs/application-generic/src/usecases/tier-restrictions-validate/tier-restrictions-validate.usecase.ts +++ b/libs/application-generic/src/usecases/tier-restrictions-validate/tier-restrictions-validate.usecase.ts @@ -14,6 +14,7 @@ import { MAX_DELAY_FREE_TIER, MAX_DELAY_BUSINESS_TIER, } from './tier-restrictions-validate.consts'; +import { InstrumentUsecase } from '../../instrumentation'; @Injectable() export class TierRestrictionsValidateUsecase { @@ -21,6 +22,7 @@ export class TierRestrictionsValidateUsecase { private organizationRepository: CommunityOrganizationRepository, ) {} + @InstrumentUsecase() async execute( command: TierRestrictionsValidateCommand, ): Promise { diff --git a/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts b/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts index 02bbe302b8f..b45a001fdcd 100644 --- a/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts +++ b/libs/application-generic/src/usecases/workflow/delete-workflow/delete-workflow.usecase.ts @@ -5,9 +5,8 @@ import { MessageTemplateRepository, NotificationTemplateEntity, NotificationTemplateRepository, - PreferencesRepository, } from '@novu/dal'; -import { PreferencesTypeEnum, WorkflowOriginEnum } from '@novu/shared'; +import { PreferencesTypeEnum } from '@novu/shared'; import { DeleteWorkflowCommand } from './delete-workflow.command'; import { InvalidateCacheService } from '../../../services/cache/invalidate-cache.service'; @@ -21,6 +20,7 @@ import { DeletePreferencesUseCase, DeletePreferencesCommand, } from '../../delete-preferences'; +import { Instrument, InstrumentUsecase } from '../../../instrumentation'; @Injectable() export class DeleteWorkflowUseCase { @@ -28,12 +28,12 @@ export class DeleteWorkflowUseCase { private notificationTemplateRepository: NotificationTemplateRepository, private messageTemplateRepository: MessageTemplateRepository, private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase, - private preferencesRepository: PreferencesRepository, private invalidateCache: InvalidateCacheService, private controlValuesRepository: ControlValuesRepository, private deletePreferencesUsecase: DeletePreferencesUseCase, ) {} + @InstrumentUsecase() async execute(command: DeleteWorkflowCommand): Promise { const workflowEntity = await this.getWorkflowByIdsUseCase.execute( GetWorkflowByIdsCommand.create({ @@ -47,6 +47,7 @@ export class DeleteWorkflowUseCase { await this.deleteRelatedEntities(command, workflowEntity); } + @Instrument() private async deleteRelatedEntities( command: DeleteWorkflowCommand, workflow: NotificationTemplateEntity, @@ -94,6 +95,7 @@ export class DeleteWorkflowUseCase { }); } + @Instrument() private async invalidateCacheForWorkflow( workflow: NotificationTemplateEntity, command: DeleteWorkflowCommand, @@ -112,11 +114,4 @@ export class DeleteWorkflowUseCase { }), }); } - - private isNovuCloud(workflow: NotificationTemplateEntity) { - return ( - workflow.origin === WorkflowOriginEnum.NOVU_CLOUD || - workflow.origin === WorkflowOriginEnum.NOVU_CLOUD_V1 - ); - } } diff --git a/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts b/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts index 8a4801fccba..2658ba189f0 100644 --- a/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts +++ b/libs/application-generic/src/usecases/workflow/get-workflow-by-ids/get-workflow-by-ids.usecase.ts @@ -17,6 +17,7 @@ import { GetPreferences, GetPreferencesCommand } from '../../get-preferences'; import { GetWorkflowByIdsCommand } from './get-workflow-by-ids.command'; import { WorkflowInternalResponseDto } from './get-workflow-by-ids.dto'; +import { Instrument, InstrumentUsecase } from '../../../instrumentation'; @Injectable() export class GetWorkflowByIdsUseCase { @@ -25,15 +26,51 @@ export class GetWorkflowByIdsUseCase { @Inject(forwardRef(() => GetPreferences)) private getPreferences: GetPreferences, ) {} + + @InstrumentUsecase() async execute( command: GetWorkflowByIdsCommand, ): Promise { + const workflowEntity = await this.getDbWorkflow(command); + + const workflowPreferences = await this.getWorkflowPreferences( + command, + workflowEntity, + ); + + /** + * @deprecated - use `userPreferences` and `defaultPreferences` instead + */ + const preferenceSettings = workflowPreferences + ? GetPreferences.mapWorkflowPreferencesToChannelPreferences( + workflowPreferences.preferences, + ) + : workflowEntity.preferenceSettings; + const userPreferences = workflowPreferences + ? workflowPreferences.source.USER_WORKFLOW + : buildWorkflowPreferencesFromPreferenceChannels( + workflowEntity.critical, + workflowEntity.preferenceSettings, + ); + const defaultPreferences = workflowPreferences + ? workflowPreferences.source.WORKFLOW_RESOURCE + : DEFAULT_WORKFLOW_PREFERENCES; + + return { + ...workflowEntity, + preferenceSettings, + userPreferences, + defaultPreferences, + }; + } + + @Instrument() + private async getDbWorkflow(command: GetWorkflowByIdsCommand) { const isInternalId = NotificationTemplateRepository.isInternalId( command.identifierOrInternalId, ); let workflowEntity: NotificationTemplateEntity | null; - if (isInternalId) { workflowEntity = await this.notificationTemplateRepository.findById( command.identifierOrInternalId, @@ -54,36 +91,20 @@ export class GetWorkflowByIdsUseCase { }); } - const workflowPreferences = await this.getPreferences.safeExecute( + return workflowEntity; + } + + @Instrument() + private async getWorkflowPreferences( + command: GetWorkflowByIdsCommand, + workflowEntity: NotificationTemplateEntity, + ) { + return await this.getPreferences.safeExecute( GetPreferencesCommand.create({ environmentId: command.environmentId, organizationId: command.organizationId, templateId: workflowEntity._id, }), ); - /** - * @deprecated - use `userPreferences` and `defaultPreferences` instead - */ - const preferenceSettings = workflowPreferences - ? GetPreferences.mapWorkflowPreferencesToChannelPreferences( - workflowPreferences.preferences, - ) - : workflowEntity.preferenceSettings; - const userPreferences = workflowPreferences - ? workflowPreferences.source.USER_WORKFLOW - : buildWorkflowPreferencesFromPreferenceChannels( - workflowEntity.critical, - workflowEntity.preferenceSettings, - ); - const defaultPreferences = workflowPreferences - ? workflowPreferences.source.WORKFLOW_RESOURCE - : DEFAULT_WORKFLOW_PREFERENCES; - - return { - ...workflowEntity, - preferenceSettings, - userPreferences, - defaultPreferences, - }; } }