Skip to content

Commit

Permalink
feat(framework, api, web, application-generic): Add name and `descr…
Browse files Browse the repository at this point in the history
…iption` to Framework workflow options (#6708)
  • Loading branch information
rifont authored Oct 17, 2024
1 parent 64a248d commit ec35a01
Show file tree
Hide file tree
Showing 29 changed files with 623 additions and 381 deletions.
66 changes: 52 additions & 14 deletions apps/api/src/app/bridge/usecases/sync/sync.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,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 +100,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 +122,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 +138,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 +192,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 +207,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 +224,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 +258,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 +298,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 +319,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
197 changes: 195 additions & 2 deletions apps/api/src/app/events/e2e/bridge-sync.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('Bridge Sync - /bridge/sync (POST)', async () => {
showButton: { type: 'boolean', default: true },
},
},
};
} as const;

let bridgeServer: BridgeServer;
beforeEach(async () => {
Expand Down Expand Up @@ -164,7 +164,7 @@ describe('Bridge Sync - /bridge/sync (POST)', async () => {
};
},
{
controlSchema: inputPostPayload.schema as any,
controlSchema: inputPostPayload.schema,
}
);
},
Expand Down Expand Up @@ -364,4 +364,197 @@ describe('Bridge Sync - /bridge/sync (POST)', async () => {

expect(response.status).to.equal(200);
});

it('should create a workflow with a name', async () => {
const workflowId = 'hello-world-description';
const newWorkflow = workflow(
workflowId,
async ({ step }) => {
await step.email('send-email', () => ({
subject: 'Welcome!',
body: 'Hello there',
}));
},
{
name: 'My Workflow',
}
);
await bridgeServer.start({ workflows: [newWorkflow] });

const result = await session.testAgent.post(`/v1/bridge/sync`).send({
bridgeUrl: bridgeServer.serverPath,
});

const workflows = await workflowsRepository.find({
_environmentId: session.environment._id,
_id: result.body.data[0]._id,
});
expect(workflows.length).to.equal(1);

const workflowData = workflows[0];
expect(workflowData.name).to.equal('My Workflow');
});

it('should create a workflow with a name that defaults to the workflowId', async () => {
const workflowId = 'hello-world-description';
const newWorkflow = workflow(workflowId, async ({ step }) => {
await step.email('send-email', () => ({
subject: 'Welcome!',
body: 'Hello there',
}));
});
await bridgeServer.start({ workflows: [newWorkflow] });

const result = await session.testAgent.post(`/v1/bridge/sync`).send({
bridgeUrl: bridgeServer.serverPath,
});

const workflows = await workflowsRepository.find({
_environmentId: session.environment._id,
_id: result.body.data[0]._id,
});
expect(workflows.length).to.equal(1);

const workflowData = workflows[0];
expect(workflowData.name).to.equal(workflowId);
});

it('should preserve the original workflow resource when syncing a workflow that has added a name', async () => {
const workflowId = 'hello-world-description';
const newWorkflow = workflow(workflowId, async ({ step }) => {
await step.email('send-email', () => ({
subject: 'Welcome!',
body: 'Hello there',
}));
});
await bridgeServer.start({ workflows: [newWorkflow] });

const result = await session.testAgent.post(`/v1/bridge/sync`).send({
bridgeUrl: bridgeServer.serverPath,
});
const workflowDbId = result.body.data[0]._id;

const workflows = await workflowsRepository.find({
_environmentId: session.environment._id,
_id: workflowDbId,
});
expect(workflows.length).to.equal(1);

const workflowData = workflows[0];
expect(workflowData.name).to.equal(workflowId);

await bridgeServer.stop();

bridgeServer = new BridgeServer();
const newWorkflowWithName = workflow(
workflowId,
async ({ step }) => {
await step.email('send-email', () => ({
subject: 'Welcome!',
body: 'Hello there',
}));
},
{
name: 'My Workflow',
}
);
await bridgeServer.start({ workflows: [newWorkflowWithName] });

await session.testAgent.post(`/v1/bridge/sync`).send({
bridgeUrl: bridgeServer.serverPath,
});

const workflowsWithName = await workflowsRepository.find({
_environmentId: session.environment._id,
_id: workflowDbId,
});
expect(workflowsWithName.length).to.equal(1);

const workflowDataWithName = workflowsWithName[0];
expect(workflowDataWithName.name).to.equal('My Workflow');
});

it('should create a workflow with a description', async () => {
const workflowId = 'hello-world-description';
const newWorkflow = workflow(
workflowId,
async ({ step }) => {
await step.email('send-email', () => ({
subject: 'Welcome!',
body: 'Hello there',
}));
},
{
description: 'This is a description',
}
);
await bridgeServer.start({ workflows: [newWorkflow] });

const result = await session.testAgent.post(`/v1/bridge/sync`).send({
bridgeUrl: bridgeServer.serverPath,
});

const workflows = await workflowsRepository.find({
_environmentId: session.environment._id,
_id: result.body.data[0]._id,
});
expect(workflows.length).to.equal(1);

const workflowData = workflows[0];
expect(workflowData.description).to.equal('This is a description');
});

it('should unset the workflow description after the description is removed', async () => {
const workflowId = 'hello-world-description';
const newWorkflow = workflow(
workflowId,
async ({ step }) => {
await step.email('send-email', () => ({
subject: 'Welcome!',
body: 'Hello there',
}));
},
{
description: 'This is a description',
}
);
await bridgeServer.start({ workflows: [newWorkflow] });

const result = await session.testAgent.post(`/v1/bridge/sync`).send({
bridgeUrl: bridgeServer.serverPath,
});
const workflowDbId = result.body.data[0]._id;
const workflows = await workflowsRepository.find({
_environmentId: session.environment._id,
_id: workflowDbId,
});
expect(workflows.length).to.equal(1);

const workflowData = workflows[0];
expect(workflowData.description).to.equal('This is a description');

await bridgeServer.stop();

bridgeServer = new BridgeServer();
const newWorkflowWithName = workflow(workflowId, async ({ step }) => {
await step.email('send-email', () => ({
subject: 'Welcome!',
body: 'Hello there',
}));
});
await bridgeServer.start({ workflows: [newWorkflowWithName] });

await session.testAgent.post(`/v1/bridge/sync`).send({
bridgeUrl: bridgeServer.serverPath,
});

const workflowsWithDescription = await workflowsRepository.find({
_environmentId: session.environment._id,
_id: workflowDbId,
});
expect(workflowsWithDescription.length).to.equal(1);

const workflowDataWithName = workflowsWithDescription[0];
expect(workflowDataWithName.description).to.equal('');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FC } from 'react';
import { token } from '@novu/novui/tokens';
import { css, cx } from '@novu/novui/css';
import { WithLoadingSkeleton } from '@novu/novui';
import { IBridgeWorkflow } from '../../../../studio/types';
import type { DiscoverWorkflowOutput } from '@novu/framework';
import { NavMenu } from '../../../nav/NavMenu';
import { NavMenuSection } from '../../../nav/NavMenuSection';
import { LocalStudioSidebarOrganizationDisplay } from './LocalStudioSidebarOrganizationDisplay';
Expand All @@ -14,10 +14,9 @@ import { useStudioState } from '../../../../studio/StudioStateProvider';
import { NavMenuButtonInner, rawButtonBaseStyles } from '../../../nav/NavMenuButton/NavMenuButton.shared';
import { useDocsModal } from '../../../docs/useDocsModal';
import { PATHS } from '../../../docs/docs.const';
import { ROUTES } from '../../../../constants/routes';

type LocalStudioSidebarContentProps = {
workflows: IBridgeWorkflow[];
workflows: DiscoverWorkflowOutput[];
isLoading?: boolean;
};

Expand Down
Loading

0 comments on commit ec35a01

Please sign in to comment.