Skip to content

Commit

Permalink
chore(api): Simplify patch workflow endpoint
Browse files Browse the repository at this point in the history
Currently patch should only toggle the workflow active status
  • Loading branch information
SokratisVidros committed Dec 12, 2024
1 parent ea360de commit 4d4cba9
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 119 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EnvironmentWithUserObjectCommand } from '@novu/application-generic';
import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';

export class PatchWorkflowCommand extends EnvironmentWithUserObjectCommand {
@IsString()
Expand All @@ -9,16 +9,4 @@ export class PatchWorkflowCommand extends EnvironmentWithUserObjectCommand {
@IsBoolean()
@IsOptional()
active?: boolean;

@IsString()
@IsOptional()
name?: string;

@IsString()
@IsOptional()
description?: string;

@IsArray()
@IsOptional()
tags?: string[];
}
Original file line number Diff line number Diff line change
@@ -1,78 +1,51 @@
import { Injectable } from '@nestjs/common';
import { UserSessionData, WorkflowResponseDto } from '@novu/shared';
import { NotificationTemplateEntity, NotificationTemplateRepository } from '@novu/dal';
import { GetWorkflowByIdsUseCase, WorkflowInternalResponseDto } from '@novu/application-generic';
import { PostProcessWorkflowUpdate } from '../post-process-workflow-update';
import { Injectable, NotFoundException } from '@nestjs/common';
import { WorkflowResponseDto } from '@novu/shared';
import { NotificationTemplateRepository } from '@novu/dal';
import { PatchWorkflowCommand } from './patch-workflow.command';
import { GetWorkflowUseCase } from '../get-workflow';

@Injectable()
export class PatchWorkflowUsecase {
constructor(
private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase,
private notificationTemplateRepository: NotificationTemplateRepository,
private postProcessWorkflowUpdate: PostProcessWorkflowUpdate,
private getWorkflowUseCase: GetWorkflowUseCase
) {}

// NOTICE: The TODOS in this usecase explore a different way to fetch and update a resource, leveraging model classes
async execute(command: PatchWorkflowCommand): Promise<WorkflowResponseDto> {
const persistedWorkflow = await this.fetchWorkflow(command);
let transientWorkflow = this.patchWorkflowFields(persistedWorkflow, command);

transientWorkflow = await this.postProcessWorkflowUpdate.execute({
workflow: transientWorkflow,
user: command.user,
// TODO: Return a Mongoose model
const workflow = await this.notificationTemplateRepository.findOne({
_id: command.workflowIdOrInternalId,
_environmentId: command.user.environmentId,
});
await this.persistWorkflow(transientWorkflow, command.user);

return await this.getWorkflowUseCase.execute({
workflowIdOrInternalId: command.workflowIdOrInternalId,
user: command.user,
});
}

private patchWorkflowFields(
persistedWorkflow: WorkflowInternalResponseDto,
command: PatchWorkflowCommand
): WorkflowInternalResponseDto {
const transientWorkflow = { ...persistedWorkflow };
if (command.active !== undefined && command.active !== null) {
transientWorkflow.active = command.active;
}

if (command.name !== undefined && command.name !== null) {
transientWorkflow.name = command.name;
}

if (command.description !== undefined && command.description !== null) {
transientWorkflow.description = command.description;
if (!workflow) {
throw new NotFoundException({
message: 'Workflow cannot be found',
workflowId: command.workflowIdOrInternalId,
});
}

if (command.tags !== undefined && command.tags !== null) {
transientWorkflow.tags = command.tags;
if (typeof command.active === 'boolean') {
workflow.active = command.active;
}

return transientWorkflow;
}

private async persistWorkflow(workflowWithIssues: NotificationTemplateEntity, userSessionData: UserSessionData) {
// TODO: Update the workflow using the Mongoose model.save()
await this.notificationTemplateRepository.update(
{
_id: workflowWithIssues._id,
_environmentId: userSessionData.environmentId,
_id: workflow._id,
_environmentId: command.user.environmentId,
},
{
...workflowWithIssues,
}
workflow
);
}

private async fetchWorkflow(command: PatchWorkflowCommand): Promise<WorkflowInternalResponseDto> {
return await this.getWorkflowByIdsUseCase.execute({
/*
* TODO: Convert to a serializer that also enriches the workflow with necessary data from other collections
* such as preferences, message templates, etc...
*/
return await this.getWorkflowUseCase.execute({
workflowIdOrInternalId: command.workflowIdOrInternalId,
environmentId: command.user.environmentId,
organizationId: command.user.organizationId,
userId: command.user._id,
user: command.user,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
import { BadRequestException, Injectable } from '@nestjs/common';
import { UpsertWorkflowCommand } from './upsert-workflow.command';
import { stepTypeToControlSchema } from '../../shared';
import { PatchStepUsecase } from '../patch-step-data';
import { PostProcessWorkflowUpdate } from '../post-process-workflow-update';
import { GetWorkflowCommand, GetWorkflowUseCase } from '../get-workflow';
import { UpsertWorkflowDataCommand } from './upsert-workflow-data.command';
Expand All @@ -48,22 +47,19 @@ export class UpsertWorkflowUseCase {
@InstrumentUsecase()
async execute(command: UpsertWorkflowCommand): Promise<WorkflowResponseDto> {
const workflowForUpdate = await this.queryWorkflow(command);
let persistedWorkflow = await this.createOrUpdateWorkflow(workflowForUpdate, command);
persistedWorkflow = await this.upsertControlValues(persistedWorkflow, command);
const persistedWorkflow = await this.createOrUpdateWorkflow(workflowForUpdate, command);
const validatedWorkflowWithIssues = await this.workflowUpdatePostProcess.execute({
user: command.user,
workflow: persistedWorkflow,
});
await this.persistWorkflow(validatedWorkflowWithIssues);

const workflow = await this.getWorkflowUseCase.execute(
return await this.getWorkflowUseCase.execute(
GetWorkflowCommand.create({
workflowIdOrInternalId: validatedWorkflowWithIssues._id,
user: command.user,
})
);

return workflow;
}

@Instrument()
Expand Down Expand Up @@ -286,44 +282,6 @@ export class UpsertWorkflowUseCase {
)
)?._id;
}

/**
* @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
): Promise<WorkflowInternalResponseDto> {
for (const step of workflow.steps) {
const controlValues = this.findControlValueInRequest(step, command.workflowDto.steps);
if (!controlValues) {
continue;
}
await this.patchStepDataUsecase.execute({
controlValues,
workflowIdOrInternalId: workflow._id,
stepIdOrInternalId: step._templateId,
user: command.user,
});
}

return await this.getWorkflow(workflow._id, command);
}

private findControlValueInRequest(
step: NotificationStepEntity,
steps: (StepCreateDto | StepUpdateDto)[] | StepCreateDto[]
): Record<string, unknown> | undefined {
return steps.find((stepRequest) => {
if (this.isStepUpdateDto(stepRequest)) {
return stepRequest._id === step._templateId;
}

return stepRequest.name === step.name;
})?.controlValues;
}
}

function isWorkflowUpdateDto(workflowDto: UpsertWorkflowDataCommand, id?: string): workflowDto is UpdateWorkflowDto {
Expand Down
10 changes: 0 additions & 10 deletions packages/shared/src/clients/workflows-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
GeneratePreviewResponseDto,
GetListQueryParams,
ListWorkflowResponse,
PatchStepDataDto,
PatchWorkflowDto,
StepDataDto,
SyncWorkflowDto,
Expand Down Expand Up @@ -48,14 +47,6 @@ export const createWorkflowClient = (baseUrl: string, headers: HeadersInit = {})
return await baseClient.safeGet<StepDataDto>(`/v2/workflows/${workflowId}/steps/${stepId}`);
};

const patchWorkflowStepData = async (
workflowId: string,
stepId: string,
patchStepDataDto: PatchStepDataDto
): Promise<NovuRestResult<StepDataDto, HttpError>> => {
return await baseClient.safePatch<StepDataDto>(`/v2/workflows/${workflowId}/steps/${stepId}`, patchStepDataDto);
};

const patchWorkflow = async (
workflowId: string,
patchWorkflowDto: PatchWorkflowDto
Expand Down Expand Up @@ -147,7 +138,6 @@ export const createWorkflowClient = (baseUrl: string, headers: HeadersInit = {})
searchWorkflows,
getWorkflowTestData,
getWorkflowStepData,
patchWorkflowStepData,
patchWorkflow,
searchWorkflowsV1,
createWorkflowsV1,
Expand Down

0 comments on commit 4d4cba9

Please sign in to comment.