Skip to content

Commit

Permalink
feat(api): add query parser (#7267)
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge authored Dec 16, 2024
1 parent 0b6a990 commit 340c71c
Show file tree
Hide file tree
Showing 16 changed files with 875 additions and 34 deletions.
19 changes: 17 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,26 @@
"endsPattern": "Started application in NODE_ENV"
}
},
},
{
"type": "npm",
"script": "start:test",
"isBackground": true,
"label": "WORKER TEST",
"path": "/apps/worker",
"problemMatcher": {
"base": "$tsc-watch",
"owner": "typescript",
"background": {
"activeOnStart": true,
"beginsPattern": "Successfully compiled",
"endsPattern": "Started application in NODE_ENV"
}
},
"icon": {
"id": "server",
"color": "terminal.ansiGreen"
},
"dependsOn": ["SHARED", "API", "APPLICATION GENERIC", "DAL", "EE - TRANSLATION", "EE - BILLING"]
}
},
{
"type": "npm",
Expand Down
2 changes: 2 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"helmet": "^6.0.1",
"i18next": "^23.7.6",
"ioredis": "5.3.2",
"json-logic-js": "^2.0.5",
"json-schema-to-ts": "^3.0.0",
"jsonwebtoken": "9.0.0",
"liquidjs": "^10.14.0",
Expand Down Expand Up @@ -119,6 +120,7 @@
"@types/passport-jwt": "^3.0.3",
"@types/sinon": "^9.0.0",
"@types/supertest": "^2.0.8",
"@types/json-logic-js": "^2.0.8",
"async": "^3.2.0",
"chai": "^4.2.0",
"mocha": "^10.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ 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 { 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,
Expand All @@ -15,10 +17,14 @@ import {
} from '../output-renderers';
import { DelayOutputRendererUsecase } from '../output-renderers/delay-output-renderer.usecase';
import { DigestOutputRendererUsecase } from '../output-renderers/digest-output-renderer.usecase';
import { evaluateRules } from '../../../shared/services/query-parser/query-parser.service';

const LOG_CONTEXT = 'ConstructFrameworkWorkflow';

@Injectable()
export class ConstructFrameworkWorkflow {
constructor(
private logger: PinoLogger,
private workflowsRepository: NotificationTemplateRepository,
private inAppOutputRendererUseCase: InAppOutputRendererUsecase,
private emailOutputRendererUseCase: RenderEmailOutputUsecase,
Expand Down Expand Up @@ -103,65 +109,68 @@ export class ConstructFrameworkWorkflow {
return this.inAppOutputRendererUseCase.execute({ controlValues, fullPayloadForRender });
},
// Step options
this.constructChannelStepOptions(staticStep)
this.constructChannelStepOptions(staticStep, fullPayloadForRender)
);
case StepTypeEnum.EMAIL:
return step.email(
stepId,
async (controlValues) => {
return this.emailOutputRendererUseCase.execute({ controlValues, fullPayloadForRender });
},
this.constructChannelStepOptions(staticStep)
this.constructChannelStepOptions(staticStep, fullPayloadForRender)
);
case StepTypeEnum.SMS:
return step.inApp(
stepId,
async (controlValues) => {
return this.smsOutputRendererUseCase.execute({ controlValues, fullPayloadForRender });
},
this.constructChannelStepOptions(staticStep)
this.constructChannelStepOptions(staticStep, fullPayloadForRender)
);
case StepTypeEnum.CHAT:
return step.inApp(
stepId,
async (controlValues) => {
return this.chatOutputRendererUseCase.execute({ controlValues, fullPayloadForRender });
},
this.constructChannelStepOptions(staticStep)
this.constructChannelStepOptions(staticStep, fullPayloadForRender)
);
case StepTypeEnum.PUSH:
return step.inApp(
stepId,
async (controlValues) => {
return this.pushOutputRendererUseCase.execute({ controlValues, fullPayloadForRender });
},
this.constructChannelStepOptions(staticStep)
this.constructChannelStepOptions(staticStep, fullPayloadForRender)
);
case StepTypeEnum.DIGEST:
return step.digest(
stepId,
async (controlValues) => {
return this.digestOutputRendererUseCase.execute({ controlValues, fullPayloadForRender });
},
this.constructActionStepOptions(staticStep)
this.constructActionStepOptions(staticStep, fullPayloadForRender)
);
case StepTypeEnum.DELAY:
return step.delay(
stepId,
async (controlValues) => {
return this.delayOutputRendererUseCase.execute({ controlValues, fullPayloadForRender });
},
this.constructActionStepOptions(staticStep)
this.constructActionStepOptions(staticStep, fullPayloadForRender)
);
default:
throw new InternalServerErrorException(`Step type ${stepType} is not supported`);
}
}

@Instrument()
private constructChannelStepOptions(staticStep: NotificationStepEntity): Required<Parameters<ChannelStep>[2]> {
private constructChannelStepOptions(
staticStep: NotificationStepEntity,
fullPayloadForRender: FullPayloadForRender
): Required<Parameters<ChannelStep>[2]> {
return {
...this.constructCommonStepOptions(staticStep),
...this.constructCommonStepOptions(staticStep, fullPayloadForRender),
// TODO: resolve this from the Step options
disableOutputSanitization: false,
// TODO: add providers
Expand All @@ -170,22 +179,24 @@ export class ConstructFrameworkWorkflow {
}

@Instrument()
private constructActionStepOptions(staticStep: NotificationStepEntity): Required<Parameters<ActionStep>[2]> {
private constructActionStepOptions(
staticStep: NotificationStepEntity,
fullPayloadForRender: FullPayloadForRender
): Required<Parameters<ActionStep>[2]> {
return {
...this.constructCommonStepOptions(staticStep),
...this.constructCommonStepOptions(staticStep, fullPayloadForRender),
};
}

@Instrument()
private constructCommonStepOptions(staticStep: NotificationStepEntity): Required<StepOptions> {
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,
/*
* TODO: add conditions
* Used to construct conditions defined with https://react-querybuilder.js.org/ or similar
*/
skip: (controlValues) => false,
skip: (controlValues: Record<string, unknown>) => this.processSkipOption(controlValues, fullPayloadForRender),
};
}

Expand All @@ -199,7 +210,24 @@ export class ConstructFrameworkWorkflow {

return foundWorkflow;
}

private processSkipOption(controlValues: { [x: string]: unknown }, variables: FullPayloadForRender) {
const skipRules = controlValues.skip as RulesLogic<AdditionalOperation>;

if (_.isEmpty(skipRules)) {
return false;
}

const { result, error } = evaluateRules(skipRules, variables);

if (error) {
this.logger.error({ err: error }, 'Failed to evaluate skip rule', LOG_CONTEXT);
}

return result;
}
}

const PERMISSIVE_EMPTY_SCHEMA = {
type: 'object',
properties: {},
Expand Down
Loading

0 comments on commit 340c71c

Please sign in to comment.