Skip to content

Commit

Permalink
feat(api): treat workflow name as editable, non-unique values (#6780)
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge authored Nov 1, 2024
1 parent b0b97f3 commit b6a7c68
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 56 deletions.
28 changes: 21 additions & 7 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,8 @@
"beginsPattern": "Successfully compiled",
"endsPattern": "Started application in NODE_ENV"
}
},
"dependsOn": [
"SHARED",
"APPLICATION GENERIC",
"DAL"
]
},
}
},
{
"type": "npm",
"script": "start",
Expand Down Expand Up @@ -56,6 +51,25 @@
]
},
{
"type": "npm",
"script": "start",
"isBackground": true,
"label": "DASHBOARD",
"path": "/apps/dashboard",
"icon": {
"id": "browser",
"color": "terminal.ansiGreen"
},
"problemMatcher": {
"base": "$tsc-watch",
"owner": "typescript",
"background": {
"activeOnStart": true,
"beginsPattern": "Compiling...",
"endsPattern": "webpack compiled successfully"
}
}
}, {
"type": "npm",
"script": "start",
"isBackground": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ export class SyncToEnvironmentUseCase {
preferences: PreferencesEntity[]
): Promise<UpdateWorkflowDto> {
return {
updatedAt: new Date().toISOString(),
workflowId: workflow.triggers[0].identifier,
name: workflow.name,
active: workflow.active,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
NotificationGroupRepository,
NotificationStepEntity,
NotificationTemplateEntity,
NotificationTemplateRepository,
PreferencesEntity,
} from '@novu/dal';
import {
Expand All @@ -25,9 +26,12 @@ import {
import {
CreateWorkflowDto,
DEFAULT_WORKFLOW_PREFERENCES,
IdentifierOrInternalId,
StepCreateDto,
StepDto,
StepUpdateDto,
UpdateWorkflowDto,
UserSessionData,
WorkflowCreationSourceEnum,
WorkflowOriginEnum,
WorkflowPreferences,
Expand Down Expand Up @@ -69,21 +73,27 @@ export class UpsertWorkflowUseCase {
private getPreferencesUseCase: GetPreferences
) {}
async execute(command: UpsertWorkflowCommand): Promise<WorkflowResponseDto> {
const workflowForUpdate: NotificationTemplateEntity | null = command.identifierOrInternalId
? await this.getWorkflowByIdsUseCase.execute(
GetWorkflowByIdsCommand.create({
...command,
identifierOrInternalId: command.identifierOrInternalId,
})
)
: null;
const workflowForUpdate = await this.queryWorkflow(command);
const workflow = await this.createOrUpdateWorkflow(workflowForUpdate, command);
const stepIdToControlValuesMap = await this.upsertControlValues(workflow, command);
const preferences = await this.upsertPreference(command, workflow);

return toResponseWorkflowDto(workflow, preferences, stepIdToControlValuesMap);
}

private async queryWorkflow(command: UpsertWorkflowCommand): Promise<NotificationTemplateEntity | null> {
if (!command.identifierOrInternalId) {
return null;
}

return await this.getWorkflowByIdsUseCase.execute(
GetWorkflowByIdsCommand.create({
...command,
identifierOrInternalId: command.identifierOrInternalId,
})
);
}

private async upsertControlValues(workflow: NotificationTemplateEntity, command: UpsertWorkflowCommand) {
const stepIdToControlValuesMap: { [p: string]: ControlValuesEntity } = {};
for (const persistedStep of workflow.steps) {
Expand Down Expand Up @@ -179,12 +189,14 @@ export class UpsertWorkflowUseCase {
}

private async createOrUpdateWorkflow(
existingWorkflow: NotificationTemplateEntity | null | undefined,
existingWorkflow: NotificationTemplateEntity | null,
command: UpsertWorkflowCommand
): Promise<NotificationTemplateEntity> {
if (existingWorkflow) {
if (existingWorkflow && isWorkflowUpdateDto(command.workflowDto, command.identifierOrInternalId)) {
return await this.updateWorkflowUsecase.execute(
UpdateWorkflowCommand.create(this.convertCreateToUpdateCommand(command, existingWorkflow))
UpdateWorkflowCommand.create(
this.convertCreateToUpdateCommand(command.workflowDto, command.user, existingWorkflow)
)
);
}

Expand Down Expand Up @@ -219,30 +231,27 @@ export class UpsertWorkflowUseCase {
description: workflowDto.description || '',
tags: workflowDto.tags || [],
critical: false,
triggerIdentifier: workflowDto.workflowId ?? slugify(workflowDto.name),
triggerIdentifier: slugify(workflowDto.name),
};
}

private convertCreateToUpdateCommand(
command: UpsertWorkflowCommand,
workflowDto: UpdateWorkflowDto,
user: UserSessionData,
existingWorkflow: NotificationTemplateEntity
): UpdateWorkflowCommand {
const { workflowDto } = command;
const { user } = command;

return {
id: existingWorkflow._id,
environmentId: existingWorkflow._environmentId,
organizationId: user.organizationId,
userId: user._id,
name: command.workflowDto.name,
name: workflowDto.name,
steps: this.mapSteps(workflowDto.steps, existingWorkflow),
rawData: workflowDto,
type: WorkflowTypeEnum.BRIDGE,
description: workflowDto.description,
tags: workflowDto.tags,
active: workflowDto.active ?? true,
workflowId: workflowDto.workflowId,
};
}

Expand Down Expand Up @@ -337,3 +346,10 @@ export class UpsertWorkflowUseCase {
)?._id;
}
}

function isWorkflowUpdateDto(
workflowDto: CreateWorkflowDto | UpdateWorkflowDto,
id?: IdentifierOrInternalId
): workflowDto is UpdateWorkflowDto {
return !!id;
}
1 change: 0 additions & 1 deletion apps/api/src/app/workflows-v2/workflow.controller.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,6 @@ function buildUpdateRequest(workflowCreated: WorkflowResponseDto): UpdateWorkflo
return {
...updateRequest,
name: TEST_WORKFLOW_UPDATED_NAME,
workflowId: `${slugify(TEST_WORKFLOW_UPDATED_NAME)}`,
steps,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const EditStepSidebar = () => {
const { reset, setError } = form;

const { workflow, error } = useFetchWorkflow({
workflowId,
workflowSlug: workflowId,
});

const step = useMemo(() => workflow?.steps.find((el) => el._id === stepId), [stepId, workflow]);
Expand Down
1 change: 1 addition & 0 deletions libs/application-generic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"rrule": "^2.7.2",
"rxjs": "7.8.1",
"sanitize-html": "^2.4.0",
"nanoid": "^3.1.20",
"shortid": "^2.2.16"
},
"optionalDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
NotFoundException,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import shortid from 'shortid';

import {
FeedRepository,
Expand Down Expand Up @@ -43,6 +42,7 @@ import {
CreateMessageTemplateCommand,
} from '../message-template';
import { ApiException, PlatformException } from '../../utils/exceptions';
import { shortId } from '../../utils/generate-id';

@Injectable()
export class CreateWorkflow {
Expand Down Expand Up @@ -169,18 +169,14 @@ export class CreateWorkflow {
contentService.extractMessageVariables(command.steps);
const subscriberVariables =
contentService.extractSubscriberMessageVariables(command.steps);

const templateCheckIdentifier =
await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
triggerIdentifier,
);
const identifier = await this.generateUniqueIdentifier(
command,
triggerIdentifier,
);

const trigger: INotificationTrigger = {
type: TriggerTypeEnum.EVENT,
identifier: `${triggerIdentifier}${
!templateCheckIdentifier ? '' : `-${shortid.generate()}`
}`,
identifier,
variables: variables.map((i) => {
return {
name: i.name,
Expand Down Expand Up @@ -208,6 +204,38 @@ export class CreateWorkflow {
return trigger;
}

private async generateUniqueIdentifier(
command: CreateWorkflowCommand,
triggerIdentifier: string,
) {
const maxAttempts = 3;
let identifier = '';

for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const candidateIdentifier =
attempt === 0 ? triggerIdentifier : `${triggerIdentifier}-${shortId()}`;

const isIdentifierExist =
await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
candidateIdentifier,
);

if (!isIdentifierExist) {
identifier = candidateIdentifier;
break;
}
}

if (!identifier) {
throw new ApiException(
`Unable to generate a unique identifier. Please provide a different workflow name.${command.name}`,
);
}

return identifier;
}

private sendTemplateCreationEvent(
command: CreateWorkflowCommand,
triggerIdentifier: string,
Expand Down
8 changes: 8 additions & 0 deletions libs/application-generic/src/utils/generate-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { customAlphabet } from 'nanoid';

export const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
const nanoid = customAlphabet(ALPHABET);

export function shortId() {
return nanoid(4);
}
1 change: 1 addition & 0 deletions libs/application-generic/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './bridge';
export * from './subscriber';
export * from './variants';
export * from './deepmerge';
export * from './generate-id';
3 changes: 3 additions & 0 deletions packages/shared/src/dto/workflows/create-workflow-dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
import { PreferencesRequestDto, StepCreateDto, WorkflowCommonsFields } from './workflow-commons-fields';
import { WorkflowCreationSourceEnum } from '../../types';

export type CreateWorkflowDto = WorkflowCommonsFields & {
workflowId: string;

steps: StepCreateDto[];

__source: WorkflowCreationSourceEnum;
Expand Down
5 changes: 4 additions & 1 deletion packages/shared/src/dto/workflows/update-workflow-dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { PreferencesRequestDto, StepCreateDto, StepUpdateDto, WorkflowCommonsFields } from './workflow-commons-fields';

export type UpdateWorkflowDto = WorkflowCommonsFields & {
updatedAt: string;
/**
* We allow to update workflow id only for code first workflows
*/
workflowId?: string;

steps: (StepCreateDto | StepUpdateDto)[];

Expand Down
4 changes: 0 additions & 4 deletions packages/shared/src/dto/workflows/workflow-commons-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ export class WorkflowCommonsFields {
@IsDefined()
name: string;

@IsString()
@IsDefined()
workflowId: string;

@IsString()
@IsOptional()
description?: string;
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/dto/workflows/workflow-response-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ export class WorkflowResponseDto extends WorkflowCommonsFields {
@IsObject()
@IsOptional()
issues?: Record<string, unknown>;

@IsString()
@IsDefined()
workflowId: string;
}
Loading

0 comments on commit b6a7c68

Please sign in to comment.