From a1a1d452521a0d1850698210b3d5a382d3f84d15 Mon Sep 17 00:00:00 2001 From: ainouzgali Date: Thu, 14 Mar 2024 12:08:54 +0200 Subject: [PATCH 1/2] feat: recognize variables in i18n use --- .../update-message-template.usecase.ts | 2 +- .../shared/helpers/content.service.spec.ts | 18 ++++++++++++ .../handlebar-helpers/getTemplateVariables.ts | 29 ++++++++++++++++--- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/apps/api/src/app/message-template/usecases/update-message-template/update-message-template.usecase.ts b/apps/api/src/app/message-template/usecases/update-message-template/update-message-template.usecase.ts index a3c4d91516e..f1398f9a3c6 100644 --- a/apps/api/src/app/message-template/usecases/update-message-template/update-message-template.usecase.ts +++ b/apps/api/src/app/message-template/usecases/update-message-template/update-message-template.usecase.ts @@ -33,7 +33,7 @@ export class UpdateMessageTemplate { updatePayload.name = command.name; } - if (command.content !== null) { + if (command.content !== null || command.content !== undefined) { updatePayload.content = command.contentType === 'editor' ? sanitizeMessageContent(command.content) : command.content; } diff --git a/apps/api/src/app/shared/helpers/content.service.spec.ts b/apps/api/src/app/shared/helpers/content.service.spec.ts index 4482495d79e..4c3e9c0e2fd 100644 --- a/apps/api/src/app/shared/helpers/content.service.spec.ts +++ b/apps/api/src/app/shared/helpers/content.service.spec.ts @@ -256,6 +256,24 @@ describe('ContentService', function () { expect(variables[0].name).to.include('customVariables'); }); + it('should extract i18n content variables', function () { + const contentService = new ContentService(); + const { variables } = contentService.extractMessageVariables([ + { + template: { + type: StepTypeEnum.IN_APP, + content: '{{i18n "group.key" var=customVar.subVar var2=secVar}}', + }, + }, + ]); + + expect(variables.length).to.equal(2); + + const variablesNames = variables.map((variable) => variable.name); + expect(variablesNames).to.include('customVar.subVar'); + expect(variablesNames).to.include('secVar'); + }); + it('should extract action steps variables', function () { const contentService = new ContentService(); const { variables } = contentService.extractMessageVariables([ diff --git a/libs/shared/src/consts/handlebar-helpers/getTemplateVariables.ts b/libs/shared/src/consts/handlebar-helpers/getTemplateVariables.ts index 388f8800751..6551adc9f78 100644 --- a/libs/shared/src/consts/handlebar-helpers/getTemplateVariables.ts +++ b/libs/shared/src/consts/handlebar-helpers/getTemplateVariables.ts @@ -10,19 +10,40 @@ export interface IMustacheVariable { } export function getTemplateVariables(bod: any[]): IMustacheVariable[] { + const pairVariables = bod + .filter((body) => body.type === 'HashPair') + .flatMap((body) => { + const varName = body.value?.original as string; + + if (!shouldAddVariable(varName)) { + return []; + } + + return { + type: TemplateVariableTypeEnum.STRING, + name: body.value?.original as string, + defaultValue: '', + required: false, + }; + }); + const stringVariables: IMustacheVariable[] = bod .filter((body) => body.type === 'MustacheStatement') .flatMap((body) => { const varName = body.params[0]?.original || (body.path.original as string); - if (body.path.original === HandlebarHelpersEnum.I18N) { + if (body.path?.original === HandlebarHelpersEnum.I18N) { + if (body.hash?.pairs) { + return getTemplateVariables(body.hash.pairs); + } + return []; } if (!shouldAddVariable(varName)) { return []; } - if (body.params[0]?.original) { + if (body.params?.[0]?.original) { if (!(Object.values(HandlebarHelpersEnum) as string[]).includes(body.path.original)) { return []; } @@ -30,7 +51,7 @@ export function getTemplateVariables(bod: any[]): IMustacheVariable[] { return { type: TemplateVariableTypeEnum.STRING, - name: body.params[0]?.original || (body.path.original as string), + name: body.params?.[0]?.original || (body.path?.original as string), defaultValue: '', required: false, }; @@ -89,7 +110,7 @@ export function getTemplateVariables(bod: any[]): IMustacheVariable[] { ]; }); - return stringVariables.concat(arrayVariables).concat(boolVariables); + return stringVariables.concat(arrayVariables).concat(boolVariables).concat(pairVariables); } const shouldAddVariable = (variableName): boolean => { From b8392dc97035c6b3f032ca4b3e779c871c9318a8 Mon Sep 17 00:00:00 2001 From: ainouzgali Date: Thu, 14 Mar 2024 14:45:17 +0200 Subject: [PATCH 2/2] fix: catch parse error --- .../src/pages/templates/hooks/usePreviewEmailTemplate.ts | 3 ++- .../src/pages/templates/hooks/usePreviewInAppTemplate.ts | 3 ++- apps/web/src/utils/utils.ts | 8 ++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/web/src/pages/templates/hooks/usePreviewEmailTemplate.ts b/apps/web/src/pages/templates/hooks/usePreviewEmailTemplate.ts index 0965203d7e1..327a8bcb0e6 100644 --- a/apps/web/src/pages/templates/hooks/usePreviewEmailTemplate.ts +++ b/apps/web/src/pages/templates/hooks/usePreviewEmailTemplate.ts @@ -5,6 +5,7 @@ import { usePreviewEmail } from '../../../api/hooks'; import { IForm } from '../components/formTypes'; import { useStepFormCombinedErrors } from './useStepFormCombinedErrors'; import { useStepFormPath } from './useStepFormPath'; +import { parsePayload } from '../../../utils'; export const usePreviewEmailTemplate = ({ locale, payload }: { locale?: string; payload: string }) => { const { watch } = useFormContext(); @@ -39,7 +40,7 @@ export const usePreviewEmailTemplate = ({ locale, payload }: { locale?: string; contentType: contentType, content, layoutId: layoutId, - payload: JSON.parse(payloadArg), + payload: parsePayload(payloadArg), subject: subject ?? '', locale, }); diff --git a/apps/web/src/pages/templates/hooks/usePreviewInAppTemplate.ts b/apps/web/src/pages/templates/hooks/usePreviewInAppTemplate.ts index a8beaf4cbfd..e5fff8af8a4 100644 --- a/apps/web/src/pages/templates/hooks/usePreviewInAppTemplate.ts +++ b/apps/web/src/pages/templates/hooks/usePreviewInAppTemplate.ts @@ -6,6 +6,7 @@ import { useProcessVariables } from '../../../hooks'; import { IForm } from '../components/formTypes'; import { useStepFormCombinedErrors } from './useStepFormCombinedErrors'; import { useStepFormPath } from './useStepFormPath'; +import { parsePayload } from '../../../utils'; export type ParsedPreviewStateType = { ctaButtons: IMessageButton[]; @@ -42,7 +43,7 @@ export const usePreviewInAppTemplate = ({ locale }: { locale?: string }) => { getInAppPreview({ locale, content: templateContent as string, - payload: JSON.parse(payloadArg), + payload: parsePayload(payloadArg), cta: templateCta, }); }, diff --git a/apps/web/src/utils/utils.ts b/apps/web/src/utils/utils.ts index 7416e14e6b5..66f66c7433d 100644 --- a/apps/web/src/utils/utils.ts +++ b/apps/web/src/utils/utils.ts @@ -18,3 +18,11 @@ export function formatNumber(num: number, digits: number) { return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : '0'; } + +export function parsePayload(payload: string) { + try { + return JSON.parse(payload); + } catch (e) { + return {}; + } +}