Skip to content

Commit

Permalink
feat(api-service): add marks liquid parse (#7440)
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge authored Jan 7, 2025
1 parent 6606f6e commit d4439d4
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 27 deletions.
6 changes: 3 additions & 3 deletions apps/api/src/app/environments-v1/novu-bridge.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
HydrateEmailSchemaUseCase,
InAppOutputRendererUsecase,
PushOutputRendererUsecase,
RenderEmailOutputUsecase,
EmailOutputRendererUsecase,
SmsOutputRendererUsecase,
} from './usecases/output-renderers';
import { DelayOutputRendererUsecase } from './usecases/output-renderers/delay-output-renderer.usecase';
Expand All @@ -31,11 +31,11 @@ import { DigestOutputRendererUsecase } from './usecases/output-renderers/digest-
ConstructFrameworkWorkflow,
GetDecryptedSecretKey,
InAppOutputRendererUsecase,
RenderEmailOutputUsecase,
EmailOutputRendererUsecase,
SmsOutputRendererUsecase,
ChatOutputRendererUsecase,
PushOutputRendererUsecase,
RenderEmailOutputUsecase,
EmailOutputRendererUsecase,
ExpandEmailEditorSchemaUsecase,
HydrateEmailSchemaUseCase,
DelayOutputRendererUsecase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
FullPayloadForRender,
InAppOutputRendererUsecase,
PushOutputRendererUsecase,
RenderEmailOutputUsecase,
EmailOutputRendererUsecase,
SmsOutputRendererUsecase,
} from '../output-renderers';
import { DelayOutputRendererUsecase } from '../output-renderers/delay-output-renderer.usecase';
Expand All @@ -28,7 +28,7 @@ export class ConstructFrameworkWorkflow {
private logger: PinoLogger,
private workflowsRepository: NotificationTemplateRepository,
private inAppOutputRendererUseCase: InAppOutputRendererUsecase,
private emailOutputRendererUseCase: RenderEmailOutputUsecase,
private emailOutputRendererUseCase: EmailOutputRendererUsecase,
private smsOutputRendererUseCase: SmsOutputRendererUsecase,
private chatOutputRendererUseCase: ChatOutputRendererUsecase,
private pushOutputRendererUseCase: PushOutputRendererUsecase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import { Instrument, InstrumentUsecase } from '@novu/application-generic';
import { FullPayloadForRender, RenderCommand } from './render-command';
import { ExpandEmailEditorSchemaUsecase } from './expand-email-editor-schema.usecase';

export class RenderEmailOutputCommand extends RenderCommand {}
export class EmailOutputRendererCommand extends RenderCommand {}

@Injectable()
// todo rename to EmailOutputRenderer
export class RenderEmailOutputUsecase {
export class EmailOutputRendererUsecase {
constructor(private expandEmailEditorSchemaUseCase: ExpandEmailEditorSchemaUsecase) {}

@InstrumentUsecase()
async execute(renderCommand: RenderEmailOutputCommand): Promise<EmailRenderOutput> {
async execute(renderCommand: EmailOutputRendererCommand): Promise<EmailRenderOutput> {
const { body, subject } = renderCommand.controlValues;

if (!body || typeof body !== 'string') {
Expand Down Expand Up @@ -43,7 +42,7 @@ export class RenderEmailOutputUsecase {

private async parseTipTapNodeByLiquid(
tiptapNode: TipTapNode,
renderCommand: RenderEmailOutputCommand
renderCommand: EmailOutputRendererCommand
): Promise<TipTapNode> {
const client = new Liquid({
outputEscape: (output) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable no-param-reassign */
import { Injectable } from '@nestjs/common';
import { PreviewPayload, TipTapNode } from '@novu/shared';
import { z } from 'zod';
import { processNodeAttrs } from '@novu/application-generic';

import { PreviewPayload, TipTapNode } from '@novu/shared';
import { processNodeAttrs, processNodeMarks } from '@novu/application-generic';

import { HydrateEmailSchemaCommand } from './hydrate-email-schema.command';
import { PlaceholderAggregation } from '../../../workflows-v2/usecases';

Expand All @@ -18,13 +20,13 @@ export class HydrateEmailSchemaUseCase {
};

// TODO: Aligned Zod inferred type and TipTapNode to remove the need of a type assertion
const emailEditorSchema: TipTapNode = TipTapSchema.parse(JSON.parse(command.emailEditor)) as TipTapNode;
if (emailEditorSchema.content) {
this.transformContentInPlace(emailEditorSchema.content, command.fullPayloadForRender, placeholderAggregation);
const emailBody: TipTapNode = TipTapSchema.parse(JSON.parse(command.emailEditor)) as TipTapNode;
if (emailBody) {
this.transformContentInPlace([emailBody], command.fullPayloadForRender, placeholderAggregation);
}

return {
hydratedEmailSchema: emailEditorSchema,
hydratedEmailSchema: emailBody,
placeholderAggregation,
};
}
Expand Down Expand Up @@ -89,6 +91,7 @@ export class HydrateEmailSchemaUseCase {
) {
content.forEach((node, index) => {
processNodeAttrs(node);
processNodeMarks(node);

if (this.isVariableNode(node)) {
this.variableLogic(masterPayload, node, content, index, placeholderAggregation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export * from './render-command';
export * from './push-output-renderer.usecase';
export * from './sms-output-renderer.usecase';
export * from './in-app-output-renderer.usecase';
export * from './render-email-output.usecase';
export * from './email-output-renderer.usecase';
export * from './hydrate-email-schema.usecase';
export * from './hydrate-email-schema.command';
export * from './expand-email-editor-schema.usecase';
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common';
import _ from 'lodash';
import Ajv, { ErrorObject } from 'ajv';
import addFormats from 'ajv-formats';
import { captureException } from '@sentry/node';

import {
ChannelTypeEnum,
createMockObjectFromSchema,
Expand All @@ -22,8 +24,8 @@ import {
PinoLogger,
dashboardSanitizeControlValues,
} from '@novu/application-generic';
import { captureException } from '@sentry/node';
import { channelStepSchemas, actionStepSchemas } from '@novu/framework/internal';

import { PreviewStep, PreviewStepCommand } from '../../../bridge/usecases/preview-step';
import { FrameworkPreviousStepsOutputState } from '../../../bridge/usecases/preview-step/preview-step.command';
import { BuildStepDataUsecase } from '../build-step-data';
Expand Down Expand Up @@ -70,7 +72,7 @@ export class GeneratePreviewUsecase {
if (!sanitizedValidatedControls && workflow.origin === WorkflowOriginEnum.NOVU_CLOUD) {
throw new Error(
// eslint-disable-next-line max-len
'Control values normalization failed: The normalizeControlValues function requires maintenance to sanitize the provided type or data structure correctly'
'Control values normalization failed, normalizeControlValues function requires maintenance to sanitize the provided type or data structure correctly'
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JSONContent } from '@maily-to/render';
import _ from 'lodash';
import { processNodeAttrs, MailyContentTypeEnum } from '@novu/application-generic';
import { processNodeAttrs, MailyContentTypeEnum, processNodeMarks } from '@novu/application-generic';

/**
* Processes raw Maily JSON editor state by converting variables to Liquid.js output syntax
Expand Down Expand Up @@ -101,7 +101,8 @@ function processForLoopNode(node: JSONContent): JSONContent {
function processNode(node: JSONContent): JSONContent {
if (!node) return node;

const processedNode = processNodeAttrs(node);
let processedNode = processNodeAttrs(node);
processedNode = processNodeMarks(processedNode);

switch (processedNode.type) {
case MailyContentTypeEnum.VARIABLE:
Expand Down
38 changes: 32 additions & 6 deletions libs/application-generic/src/utils/process-node-attrs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ export enum MailyContentTypeEnum {
FOR = 'for',
BUTTON = 'button',
IMAGE = 'image',
LINK = 'link',
}

export const variableAttributeConfig = (type: MailyContentTypeEnum) => {
//todo add variable type
// todo add variable type
if (type === MailyContentTypeEnum.BUTTON) {
return [
{ attr: 'text', flag: 'isTextVariable' },
Expand All @@ -25,18 +26,43 @@ export const variableAttributeConfig = (type: MailyContentTypeEnum) => {
];
}

if (type === MailyContentTypeEnum.LINK) {
return [{ attr: 'href', flag: 'isUrlVariable' }];
}

return [{ attr: 'showIfKey', flag: 'showIfKey' }];
};

export function processNodeAttrs(node: JSONContent): JSONContent {
if (!node.attrs) return node;
function processAttributes(
attrs: Record<string, unknown>,
type: MailyContentTypeEnum,
): void {
if (!attrs) return;

const typeConfig = variableAttributeConfig(node.type as MailyContentTypeEnum);
const typeConfig = variableAttributeConfig(type);

for (const { attr, flag } of typeConfig) {
if (node.attrs[flag] && node.attrs[attr]) {
if (attrs[flag] && attrs[attr]) {
// eslint-disable-next-line no-param-reassign
node.attrs[attr] = wrapInLiquidOutput(node.attrs[attr] as string);
attrs[attr] = wrapInLiquidOutput(attrs[attr] as string);
}
}
}

export function processNodeAttrs(node: JSONContent): JSONContent {
if (!node.attrs) return node;

processAttributes(node.attrs, node.type as MailyContentTypeEnum);

return node;
}

export function processNodeMarks(node: JSONContent): JSONContent {
if (!node.marks) return node;

for (const mark of node.marks) {
if (mark.attrs) {
processAttributes(mark.attrs, mark.type as MailyContentTypeEnum);
}
}

Expand Down

0 comments on commit d4439d4

Please sign in to comment.