Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 8 additions & 6 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"generate:sdk": " (cd ../../libs/internal-sdk && speakeasy run --skip-compile --minimal --skip-versioning) && (cd ../../libs/internal-sdk && pnpm build) ",
"test": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test mocha --timeout 5000 --require ts-node/register --exit 'src/**/*.spec.ts'",
"test:e2e:novu-v0": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test mocha --timeout 5000 --retries 3 --grep '#novu-v0' --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e{,-ee}.ts",
"test:e2e:novu-v2": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test CI_EE_TEST=true CLERK_ENABLED=true NODE_OPTIONS=--max_old_space_size=8192 mocha --bail --timeout 10000 --retries 3 --grep '#novu-v2' --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e{,-ee}.ts",
"test:e2e:novu-v2": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test CI_EE_TEST=true CLERK_ENABLED=true NODE_OPTIONS=--max_old_space_size=8192 mocha --timeout 5000 --retries 3 --grep '#novu-v2' --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e{,-ee}.ts",
"migration": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly",
"link:submodules": "pnpm link ../../enterprise/packages/auth && pnpm link ../../enterprise/packages/translation && pnpm link ../../enterprise/packages/billing",
"admin:remove-user-account": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly ./admin/remove-user-account.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ describe('GetPlatformNotificationUsage #novu-v2', () => {
const communityOrganizationRepo = new CommunityOrganizationRepository();

const createUseCase = () => {
const useCase = new GetPlatformNotificationUsage(
return new GetPlatformNotificationUsage(
environmentRepo,
notificationRepo,
communityOrganizationRepo,
MockCacheService.createClient(),
new PinoLogger({})
);

return useCase;
};
let session: UserSession;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export class GetGroupedBlueprints {

const popularGroup = { name: POPULAR_GROUPED_NAME, blueprints: updatePopularBlueprints };

return { general: generalGroups as IGroupedBlueprint[], popular: popularGroup as IGroupedBlueprint };
return {
general: generalGroups as unknown as IGroupedBlueprint[],
popular: popularGroup as unknown as IGroupedBlueprint,
};
}

private async fetchGroupedBlueprints() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { EnvironmentWithUserCommand } from '@novu/application-generic';
import { Subscriber } from '@novu/framework/internal';
import { JobStatusEnum, WorkflowOriginEnum } from '@novu/shared';
import { SubscriberResponseDtoOptional } from '../../../subscribers/dtos';

export class PreviewStepCommand extends EnvironmentWithUserCommand {
workflowId: string;
stepId: string;
controls: Record<string, unknown>;
payload: Record<string, unknown>;
subscriber?: Subscriber;
subscriber?: SubscriberResponseDtoOptional;
workflowOrigin: WorkflowOriginEnum;
state?: FrameworkPreviousStepsOutputState[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Event, ExecuteOutput, HttpQueryKeysEnum, PostActionEnum } from '@novu/f
import { ExecuteBridgeRequest, ExecuteBridgeRequestCommand, InstrumentUsecase } from '@novu/application-generic';

import { PreviewStepCommand } from './preview-step.command';
import { Subscriber } from '../../../inbox/utils/types';
import { SubscriberResponseDto } from '../../../subscribers/dtos';

@Injectable()
export class PreviewStep {
Expand Down Expand Up @@ -37,10 +39,29 @@ export class PreviewStep {
controls: command.controls || {},
payload: command.payload || {},
state: command.state || [],
subscriber: command.subscriber || {},
subscriber: this.mapSubscriberResponseToSubscriber(command.subscriber),
stepId: command.stepId,
workflowId: command.workflowId,
action: PostActionEnum.PREVIEW,
};
}

private mapSubscriberResponseToSubscriber(
subscriberResponse?: Partial<SubscriberResponseDto> | undefined
): Subscriber {
if (!subscriberResponse) {
return {
id: '',
subscriberId: '',
};
}

return {
id: subscriberResponse._id || '',
firstName: subscriberResponse.firstName,
lastName: subscriberResponse.lastName,
avatar: subscriberResponse.avatar,
subscriberId: subscriberResponse.subscriberId || '',
};
}
}
2 changes: 1 addition & 1 deletion apps/api/src/app/bridge/usecases/sync/sync.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IsDefined, IsOptional, IsString, ValidateNested } from 'class-validator
import { Type } from 'class-transformer';

import { EnvironmentWithUserCommand, IStepControl } from '@novu/application-generic';
import type { IPreferenceChannels, CustomDataType, StepType, JSONSchemaDto } from '@novu/shared';
import type { CustomDataType, IPreferenceChannels, JSONSchemaDto, StepType } from '@novu/shared';

interface IStepOutput {
schema: JSONSchemaDto;
Expand Down
15 changes: 7 additions & 8 deletions apps/api/src/app/bridge/usecases/sync/sync.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ import {
DeleteWorkflowCommand,
DeleteWorkflowUseCase,
ExecuteBridgeRequest,
JSONSchema,
NotificationStep,
UpdateWorkflow,
UpdateWorkflowCommand,
} from '@novu/application-generic';
import {
buildWorkflowPreferences,
JSONSchemaDto,
StepIssuesDto,
StepTypeEnum,
UserSessionData,
WorkflowCreationSourceEnum,
Expand All @@ -35,6 +34,7 @@ import { SyncCommand } from './sync.command';
import { CreateBridgeResponseDto } from '../../dtos/create-bridge-response.dto';
import { BuildStepIssuesUsecase } from '../../../workflows-v2/usecases/build-step-issues/build-step-issues.usecase';
import { computeWorkflowStatus } from '../../../workflows-v2/shared/compute-workflow-status';
import { JSONSchemaDto, StepIssuesDto } from '../../../workflows-v2/dtos';

@Injectable()
export class Sync {
Expand Down Expand Up @@ -183,9 +183,8 @@ export class Sync {
return Promise.all(
workflowsFromBridge.map(async (workflow, index) => {
const existingFrameworkWorkflow = existingFrameworkWorkflows[index];
const savedWorkflow = await this.upsertWorkflow(command, workflow, existingFrameworkWorkflow);

return savedWorkflow;
return await this.upsertWorkflow(command, workflow, existingFrameworkWorkflow);
})
);
}
Expand Down Expand Up @@ -235,10 +234,10 @@ export class Sync {
__source: WorkflowCreationSourceEnum.BRIDGE,
steps,
controls: {
schema: workflow.controls?.schema as JSONSchemaDto,
schema: workflow.controls?.schema as unknown as JSONSchema,
},
rawData: workflow as unknown as Record<string, unknown>,
payloadSchema: workflow.payload?.schema as JSONSchemaDto,
payloadSchema: workflow.payload?.schema as unknown as JSONSchema,
active: workflowActive,
status: computeWorkflowStatus(workflowActive, steps),
description: this.getWorkflowDescription(workflow),
Expand Down Expand Up @@ -266,7 +265,7 @@ export class Sync {
workflowId: workflow.workflowId,
steps,
controls: {
schema: workflow.controls?.schema as JSONSchemaDto,
schema: workflow.controls?.schema as unknown as JSONSchemaDto,
},
rawData: workflow,
payloadSchema: workflow.payload?.schema as unknown as JSONSchemaDto,
Expand Down Expand Up @@ -299,7 +298,7 @@ export class Sync {
stepInternalId: foundStep?._id,
workflow,
stepType: step.type as StepTypeEnum,
controlSchema: step.controls?.schema as JSONSchemaDto,
controlSchema: step.controls?.schema as unknown as JSONSchemaDto,
});

const template = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { workflow } from '@novu/framework/express';
import { ActionStep, ChannelStep, JsonSchema, Step, StepOptions, StepOutput, Workflow } from '@novu/framework/internal';
import { ActionStep, ChannelStep, Schema, Step, StepOutput, Workflow } from '@novu/framework/internal';
import { NotificationStepEntity, NotificationTemplateEntity, NotificationTemplateRepository } from '@novu/dal';
import { JSONSchemaDefinition, StepTypeEnum } from '@novu/shared';
import { StepTypeEnum } from '@novu/shared';
import { Instrument, InstrumentUsecase, PinoLogger } from '@novu/application-generic';
import { AdditionalOperation, RulesLogic } from 'json-logic-js';
import _ from 'lodash';
import { ConstructFrameworkWorkflowCommand } from './construct-framework-workflow.command';
import {
ChatOutputRendererUsecase,
EmailOutputRendererUsecase,
FullPayloadForRender,
InAppOutputRendererUsecase,
PushOutputRendererUsecase,
EmailOutputRendererUsecase,
SmsOutputRendererUsecase,
} from '../output-renderers';
import { DelayOutputRendererUsecase } from '../output-renderers/delay-output-renderer.usecase';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class ConstructFrameworkWorkflow {
dbWorkflow.triggers[0].identifier,
async ({ step, payload, subscriber }) => {
const fullPayloadForRender: FullPayloadForRender = { payload, subscriber, steps: {} };
for await (const staticStep of dbWorkflow.steps) {
for (const staticStep of dbWorkflow.steps) {
fullPayloadForRender.steps[staticStep.stepId || staticStep._templateId] = await this.constructStep(
step,
staticStep,
Expand Down Expand Up @@ -176,8 +176,12 @@ export class ConstructFrameworkWorkflow {
staticStep: NotificationStepEntity,
fullPayloadForRender: FullPayloadForRender
): Required<Parameters<ChannelStep>[2]> {
const skipFunction = (controlValues: Record<string, unknown>) =>
this.processSkipOption(controlValues, fullPayloadForRender);

return {
...this.constructCommonStepOptions(staticStep, fullPayloadForRender),
skip: skipFunction,
controlSchema: staticStep.template!.controls!.schema as unknown as Schema,
disableOutputSanitization: true,
providers: {},
};
Expand All @@ -188,10 +192,17 @@ export class ConstructFrameworkWorkflow {
staticStep: NotificationStepEntity,
fullPayloadForRender: FullPayloadForRender
): Required<Parameters<ActionStep>[2]> {
const stepOptions = this.constructCommonStepOptions(staticStep, fullPayloadForRender);

let controlSchema = stepOptions.controlSchema as JSONSchemaDefinition;
const stepType = staticStep.template!.type;
const controlSchema = this.optionalAugmentControlSchemaDueToAjvBug(staticStep, stepType);

return {
controlSchema: controlSchema as unknown as Schema,
skip: (controlValues: Record<string, unknown>) => this.processSkipOption(controlValues, fullPayloadForRender),
};
}

private optionalAugmentControlSchemaDueToAjvBug(staticStep: NotificationStepEntity, stepType: StepTypeEnum) {
let controlSchema = staticStep.template!.controls!.schema;

/*
* because of the known AJV issue with anyOf, we need to find the first schema that matches the control values
Expand All @@ -204,22 +215,7 @@ export class ConstructFrameworkWorkflow {
controlSchema = fistSchemaMatch ?? controlSchema.anyOf[0];
}

return {
...stepOptions,
controlSchema: controlSchema as JsonSchema,
};
}

@Instrument()
private constructCommonStepOptions(
staticStep: NotificationStepEntity,
fullPayloadForRender: FullPayloadForRender
): Required<StepOptions> {
return {
// TODO: fix the `JSONSchemaDto` type to enforce a non-primitive schema type.
controlSchema: staticStep.template!.controls!.schema as JsonSchema,
skip: (controlValues: Record<string, unknown>) => this.processSkipOption(controlValues, fullPayloadForRender),
};
return controlSchema;
}

@Instrument()
Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/app/inbox/e2e/get-notifications.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ describe('Get Notifications - /inbox/notifications (GET) #novu-v2', async () =>
it('should validate that the offset is greater or equals to zero', async function () {
const { body, status } = await getNotifications({ limit: 1, offset: -1 });

expect(status).to.equal(400);
expect(body.message[0]).to.equal('offset must not be less than 0');
expect(status).to.equal(422);
expect(body.errors.general.messages[0]).to.equal('offset must not be less than 0');
});

it('should validate the after to mongo id', async function () {
const { body, status } = await getNotifications({ limit: 1, after: 'after' });

expect(status).to.equal(400);
expect(body.message[0]).to.equal('The after cursor must be a valid MongoDB ObjectId');
expect(status).to.equal(422);
expect(body.errors.general.messages[0]).to.equal('The after cursor must be a valid MongoDB ObjectId');
});

it('should throw exception when filtering for unread and archived notifications', async function () {
Expand Down
10 changes: 9 additions & 1 deletion apps/api/src/app/notifications/dtos/activities-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ export class DigestTimedConfigDto {
@IsString()
atTime?: string;

@ApiPropertyOptional({ description: 'Days of the week for the digest', type: [String], enum: DaysEnum })
@ApiPropertyOptional({
description: 'Days of the week for the digest',
type: 'array',
items: {
type: 'string',
enum: Object.values(DaysEnum),
},
enumName: 'DaysEnum',
})
@IsOptional()
@IsArray()
@IsEnum(DaysEnum, { each: true })
Expand Down
Loading
Loading