Skip to content

Commit

Permalink
refactor(api): wip generate tiptap variables
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge committed Dec 18, 2024
1 parent 57c531d commit 2bdf193
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ export class HydrateEmailSchemaUseCase {
node,
placeholderAggregation: PlaceholderAggregation
) {
const resolvedValue = this.getValueByPath(masterPayload, node.attrs.id);
const { fallback } = node.attrs;
const variableName = node.attrs.id;
const buildLiquidJSDefault = (mailyFallback: string) => (mailyFallback ? ` | default: '${mailyFallback}'` : '');
const finalValue = `{{ ${variableName} ${buildLiquidJSDefault(fallback)} }}`;

const finalValue = resolvedValue || fallback || `{{${node.attrs.id}}}`;
placeholderAggregation.regularPlaceholdersToDefaultValue[`{{${node.attrs.id}}}`] = finalValue;

return finalValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { render as mailyRender } from '@maily-to/render';
import { Instrument, InstrumentUsecase } from '@novu/application-generic';
import isEmpty from 'lodash/isEmpty';
import { Liquid } from 'liquidjs';
import { FullPayloadForRender, RenderCommand } from './render-command';
import { ExpandEmailEditorSchemaUsecase } from './expand-email-editor-schema.usecase';
import { emailStepControlZodSchema } from '../../../workflows-v2/shared';
Expand All @@ -21,10 +22,26 @@ export class RenderEmailOutputUsecase {
return { subject, body: '' };
}

const expandedSchema = this.transformForAndShowLogic(body, renderCommand.fullPayloadForRender);
const htmlRendered = await this.renderEmail(expandedSchema);
const expandedMailyContent = this.transformForAndShowLogic(body, renderCommand.fullPayloadForRender);
const parsedTipTap = await this.parseTipTapNodeByLiquid(expandedMailyContent, renderCommand);
const renderedHtml = await this.renderEmail(parsedTipTap);

return { subject, body: htmlRendered };
return { subject, body: renderedHtml };
}

private async parseTipTapNodeByLiquid(
value: TipTapNode,
renderCommand: RenderEmailOutputCommand
): Promise<TipTapNode> {
const client = new Liquid();
const templateString = client.parse(JSON.stringify(value));
const parsedTipTap = await client.render(templateString, {
payload: renderCommand.fullPayloadForRender.payload,
subscriber: renderCommand.fullPayloadForRender.subscriber,
steps: renderCommand.fullPayloadForRender.steps,
});

return JSON.parse(parsedTipTap);
}

@Instrument()
Expand Down
145 changes: 145 additions & 0 deletions apps/api/src/app/workflows-v2/e2e/generate-preview.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,151 @@ describe('Workflow Step Preview - POST /:workflowId/step/:stepId/preview', () =>
});
});

it('should transform tip tap node to liquid variables', async () => {
const workflow = await createWorkflow();

const stepId = workflow.steps[1]._id; // Using the email step (second step)
const bodyControlValue = {
type: 'doc',
content: [
{
type: 'heading',
attrs: { textAlign: 'left', level: 1 },
content: [
{ type: 'text', text: 'New Maily Email Editor ' },
{ type: 'variable', attrs: { id: 'payload.foo', label: null, fallback: null, showIfKey: null } },
{ type: 'text', text: ' ' },
],
},
{
type: 'paragraph',
attrs: { textAlign: 'left' },
content: [
{ type: 'text', text: 'free text last name is: ' },
{
type: 'variable',
attrs: { id: 'subscriber.lastName', label: null, fallback: null, showIfKey: `payload.show` },
},
{ type: 'text', text: ' ' },
{ type: 'hardBreak' },
{ type: 'text', text: 'extra data : ' },
{ type: 'variable', attrs: { id: 'payload.extraData', label: null, fallback: null, showIfKey: null } },
{ type: 'text', text: ' ' },
],
},
],
};
const controlValues = {
subject: 'Hello {{subscriber.firstName}} World!',
body: JSON.stringify(bodyControlValue),
};

const { status, body } = await session.testAgent.post(`/v2/workflows/${workflow._id}/step/${stepId}/preview`).send({
controlValues,
previewPayload: {},
});

expect(status).to.equal(201);
expect(body.data.result.type).to.equal('email');
expect(body.data.result.preview.subject).to.equal('Hello {{subscriber.firstName}} World!');
expect(body.data.result.preview.body).to.include('{{subscriber.lastName}}');
expect(body.data.result.preview.body).to.include('{{payload.foo}}');
// expect(body.data.result.preview.body).to.include('{{payload.show}}');
expect(body.data.result.preview.body).to.include('{{payload.extraData}}');
expect(body.data.previewPayloadExample).to.deep.equal({
subscriber: {
firstName: '{{subscriber.firstName}}',
lastName: '{{subscriber.lastName}}',
},
payload: {
foo: '{{payload.foo}}',
show: '{{payload.show}}',
extraData: '{{payload.extraData}}',
},
});
});

it('should render tip tap node with api client variables example', async () => {
const workflow = await createWorkflow();

const stepId = workflow.steps[1]._id; // Using the email step (second step)
const bodyControlValue = {
type: 'doc',
content: [
{
type: 'heading',
attrs: { textAlign: 'left', level: 1 },
content: [
{ type: 'text', text: 'New Maily Email Editor ' },
{ type: 'variable', attrs: { id: 'payload.foo', label: null, fallback: null, showIfKey: null } },
{ type: 'text', text: ' ' },
],
},
{
type: 'paragraph',
attrs: { textAlign: 'left' },
content: [
{ type: 'text', text: 'free text last name is: ' },
{
type: 'variable',
attrs: { id: 'subscriber.lastName', label: null, fallback: null, showIfKey: `payload.show` },
},
{ type: 'text', text: ' ' },
{ type: 'hardBreak' },
{ type: 'text', text: 'extra data : ' },
{
type: 'variable',
attrs: {
id: 'payload.extraData',
label: null,
fallback: 'fallback extra data is awesome',
showIfKey: null,
},
},
{ type: 'text', text: ' ' },
],
},
],
};
const controlValues = {
subject: 'Hello {{subscriber.firstName}} World!',
body: JSON.stringify(bodyControlValue),
};

const { status, body } = await session.testAgent.post(`/v2/workflows/${workflow._id}/step/${stepId}/preview`).send({
controlValues,
previewPayload: {
subscriber: {
firstName: 'John',
// lastName: 'Doe',
},
payload: {
foo: 'foo from client',
show: false,
extraData: '',
},
},
});

expect(status).to.equal(201);
expect(body.data.result.type).to.equal('email');
expect(body.data.result.preview.subject).to.equal('Hello John World!');
expect(body.data.result.preview.body).to.include('{{subscriber.lastName}}');
expect(body.data.result.preview.body).to.include('foo from client');
expect(body.data.result.preview.body).to.include('fallback extra data is awesome');
expect(body.data.previewPayloadExample).to.deep.equal({
subscriber: {
firstName: 'John',
lastName: '{{subscriber.lastName}}',
},
payload: {
foo: 'foo from client',
show: false,
extraData: '',
},
});
});

async function createWorkflow(overrides: Partial<NotificationTemplateEntity> = {}): Promise<WorkflowResponseDto> {
const createWorkflowDto: CreateWorkflowDto = {
__source: WorkflowCreationSourceEnum.EDITOR,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Injectable } from '@nestjs/common';
import { ControlValuesEntity, ControlValuesRepository } from '@novu/dal';
import { ControlValuesRepository } from '@novu/dal';
import { ControlValuesLevelEnum, JSONSchemaDto } from '@novu/shared';
import { Instrument, InstrumentUsecase } from '@novu/application-generic';
import { flattenObjectValues } from '../../util/utils';
import { pathsToObject } from '../../util/path-to-object';
import { extractLiquidTemplateVariables } from '../../util/template-parser/liquid-parser';
import { convertJsonToSchemaWithDefaults } from '../../util/jsonToSchema';
import { BuildPayloadSchemaCommand } from './build-payload-schema.command';
import { transformMailyContentToLiquid } from '../generate-preview/transform-maily-content-to-liquid';
import { isStringTipTapNode } from '../../util/tip-tap.util';

@Injectable()
export class BuildPayloadSchema {
Expand All @@ -20,7 +22,7 @@ export class BuildPayloadSchema {
return {};
}

const templateVars = this.extractTemplateVariables(controlValues);
const templateVars = await this.processControlValues(controlValues);
if (templateVars.length === 0) {
return {};
}
Expand Down Expand Up @@ -58,12 +60,31 @@ export class BuildPayloadSchema {
}

@Instrument()
private extractTemplateVariables(controlValues: Record<string, unknown>[]): string[] {
const controlValuesString = controlValues.map(flattenObjectValues).flat().join(' ');
private async processControlValues(controlValues: Record<string, unknown>[]): Promise<string[]> {
const allVariables: string[] = [];

const test = extractLiquidTemplateVariables(controlValuesString);
const test2 = test.validVariables.map((variable) => variable.name);
for (const controlValue of controlValues) {
const processedControlValue = await this.processControlValue(controlValue);
const controlValuesString = flattenObjectValues(processedControlValue).join(' ');
const templateVariables = extractLiquidTemplateVariables(controlValuesString);
allVariables.push(...templateVariables.validVariables.map((variable) => variable.name));
}

return [...new Set(allVariables)];
}

@Instrument()
private async processControlValue(controlValue: Record<string, unknown>): Promise<Record<string, unknown>> {
const processedValue: Record<string, unknown> = {};

for (const [key, value] of Object.entries(controlValue)) {
if (isStringTipTapNode(value)) {
processedValue[key] = transformMailyContentToLiquid(JSON.parse(value));
} else {
processedValue[key] = value;
}
}

return test2;
return processedValue;
}
}
Loading

0 comments on commit 2bdf193

Please sign in to comment.