Răsfoiți Sursa

feat(email-plugin): Introduce globalTemplateVars option

Michael Bromley 6 ani în urmă
părinte
comite
407d232aff

+ 31 - 11
packages/email-plugin/src/event-listener.ts

@@ -37,6 +37,11 @@ export interface EmailTemplateConfig {
     subject: string;
 }
 
+export type SetTemplateVarsFn<Event> = (
+    event: Event,
+    globals: { [key: string]: any },
+) => { [key: string]: any };
+
 /**
  * @description
  * An EmailEventListener is used to listen for events and set up a {@link EmailEventHandler} which
@@ -100,7 +105,7 @@ export class EmailEventListener<T extends string> {
  */
 export class EmailEventHandler<T extends string = string, Event extends EventWithContext = EventWithContext> {
     private setRecipientFn: (event: Event) => string;
-    private setTemplateVarsFn: (event: Event) => { [key: string]: any; };
+    private setTemplateVarsFn: SetTemplateVarsFn<Event>;
     private filterFns: Array<(event: Event) => boolean> = [];
     private configurations: EmailTemplateConfig[] = [];
     private defaultSubject: string;
@@ -140,7 +145,7 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
      * A function which returns an object hash of variables which will be made available to the Handlebars template
      * and subject line for interpolation.
      */
-    setTemplateVars(templateVarsFn: (event: Event) => { [key: string]: any; }): EmailEventHandler<T, Event> {
+    setTemplateVars(templateVarsFn: SetTemplateVarsFn<Event>): EmailEventHandler<T, Event> {
         this.setTemplateVarsFn = templateVarsFn;
         return this;
     }
@@ -169,23 +174,30 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
      * @description
      * Used internally by the EmailPlugin to handle incoming events.
      */
-    handle(event: Event): { recipient: string; templateVars: any; subject: string; templateFile: string; } | undefined {
+    handle(
+        event: Event,
+        globals: { [key: string]: any } = {},
+    ): { recipient: string; templateVars: any; subject: string; templateFile: string } | undefined {
         for (const filterFn of this.filterFns) {
             if (!filterFn(event)) {
                 return;
             }
         }
         if (!this.setRecipientFn) {
-            throw new Error(`No setRecipientFn has been defined. ` +
-            `Remember to call ".setRecipient()" when setting up the EmailEventHandler for ${this.type}`);
+            throw new Error(
+                `No setRecipientFn has been defined. ` +
+                    `Remember to call ".setRecipient()" when setting up the EmailEventHandler for ${
+                        this.type
+                    }`,
+            );
         }
         const { ctx } = event;
         const configuration = this.getBestConfiguration(ctx.channel.code, ctx.languageCode);
         const recipient = this.setRecipientFn(event);
-        const templateVars = this.setTemplateVarsFn ? this.setTemplateVarsFn(event) : {};
+        const templateVars = this.setTemplateVarsFn ? this.setTemplateVarsFn(event, globals) : {};
         return {
             recipient,
-            templateVars,
+            templateVars: { ...globals, ...templateVars },
             subject: configuration ? configuration.subject : this.defaultSubject,
             templateFile: configuration ? configuration.templateFile : 'body.hbs',
         };
@@ -201,17 +213,25 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
         return this;
     }
 
-    private getBestConfiguration(channelCode: string, languageCode: LanguageCode): EmailTemplateConfig | undefined {
-        if ( this.configurations.length === 0) {
+    private getBestConfiguration(
+        channelCode: string,
+        languageCode: LanguageCode,
+    ): EmailTemplateConfig | undefined {
+        if (this.configurations.length === 0) {
             return;
         }
         const exactMatch = this.configurations.find(c => {
-            return (c.channelCode === channelCode || c.channelCode === 'default') && c.languageCode === languageCode;
+            return (
+                (c.channelCode === channelCode || c.channelCode === 'default') &&
+                c.languageCode === languageCode
+            );
         });
         if (exactMatch) {
             return exactMatch;
         }
-        const channelMatch = this.configurations.find(c => c.channelCode === channelCode && c.languageCode === 'default');
+        const channelMatch = this.configurations.find(
+            c => c.channelCode === channelCode && c.languageCode === 'default',
+        );
         if (channelMatch) {
             return channelMatch;
         }

+ 46 - 3
packages/email-plugin/src/plugin.spec.ts

@@ -7,22 +7,24 @@ import path from 'path';
 import { orderConfirmationHandler } from './default-email-handlers';
 import { EmailEventHandler, EmailEventListener } from './event-listener';
 import { EmailPlugin } from './plugin';
+import { EmailPluginOptions } from './types';
 
 describe('EmailPlugin', () => {
     let plugin: EmailPlugin;
     let eventBus: EventBus;
     let onSend: jest.Mock;
 
-    async function initPluginWithHandlers(handlers: Array<EmailEventHandler<any, any>>, templatePath?: string) {
+    async function initPluginWithHandlers(handlers: Array<EmailEventHandler<string, any>>, options?: Partial<EmailPluginOptions>) {
         eventBus = new EventBus();
         onSend = jest.fn();
         plugin = new EmailPlugin({
-            templatePath: templatePath || path.join(__dirname, '../test-templates'),
+            templatePath: path.join(__dirname, '../test-templates'),
             transport: {
                 type: 'testing',
                 onSend,
             },
             handlers,
+            ...options,
         });
 
         const inject = (token: any): any => {
@@ -114,6 +116,47 @@ describe('EmailPlugin', () => {
             await pause();
             expect(onSend.mock.calls[0][0].body).toContain('this is the test var');
         });
+
+        it('interpolates globalTemplateVars', async () => {
+            const handler = new EmailEventListener('test')
+                .on(MockEvent)
+                .setRecipient(() => 'test@test.com')
+                .setSubject('Hello {{ globalVar }}');
+
+            await initPluginWithHandlers([handler], { globalTemplateVars: { globalVar: 'baz' } });
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(onSend.mock.calls[0][0].subject).toBe('Hello baz');
+        });
+
+        it('globalTemplateVars available in setTemplateVars method', async () => {
+            const handler = new EmailEventListener('test')
+                .on(MockEvent)
+                .setRecipient(() => 'test@test.com')
+                .setSubject('Hello {{ testVar }}')
+                .setTemplateVars((event, globals) => ({ testVar: globals.globalVar + ' quux' }));
+
+            await initPluginWithHandlers([handler], { globalTemplateVars: { globalVar: 'baz' } });
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(onSend.mock.calls[0][0].subject).toBe('Hello baz quux');
+        });
+
+        it('setTemplateVars overrides globals', async () => {
+            const handler = new EmailEventListener('test')
+                .on(MockEvent)
+                .setRecipient(() => 'test@test.com')
+                .setSubject('Hello {{ name }}')
+                .setTemplateVars((event, globals) => ({ name: 'quux' }));
+
+            await initPluginWithHandlers([handler], { globalTemplateVars: { name: 'baz' } });
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(onSend.mock.calls[0][0].subject).toBe('Hello quux');
+        });
     });
 
     describe('multiple configs', () => {
@@ -152,7 +195,7 @@ describe('EmailPlugin', () => {
     describe('orderConfirmationHandler', () => {
 
         beforeEach(async () => {
-            await initPluginWithHandlers([orderConfirmationHandler], path.join(__dirname, '../templates'));
+            await initPluginWithHandlers([orderConfirmationHandler], { templatePath: path.join(__dirname, '../templates') });
         });
 
         const ctx = {

+ 1 - 1
packages/email-plugin/src/plugin.ts

@@ -191,7 +191,7 @@ export class EmailPlugin implements VendurePlugin {
 
     private async handleEvent(handler: EmailEventHandler, event: EventWithContext) {
         const { type } = handler;
-        const result = handler.handle(event);
+        const result = handler.handle(event, this.options.globalTemplateVars);
         if (!result) {
             return;
         }

+ 7 - 0
packages/email-plugin/src/types.ts

@@ -40,6 +40,13 @@ export interface EmailPluginOptions {
      * emails, and how those emails are generated.
      */
     handlers: EmailEventHandler[];
+    /**
+     * @description
+     * An object containing variables which are made available to all templates. For example,
+     * the storefront URL could be defined here and then used in the "email address verification"
+     * email.
+     */
+    globalTemplateVars?: { [key: string]: any; };
 }
 
 /**