Skip to content

Commit

Permalink
feat(email-plugin): Introduce globalTemplateVars option
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Apr 16, 2019
1 parent 35105ec commit 407d232
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 15 deletions.
42 changes: 31 additions & 11 deletions packages/email-plugin/src/event-listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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',
};
Expand All @@ -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;
}
Expand Down
49 changes: 46 additions & 3 deletions packages/email-plugin/src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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(() => '[email protected]')
.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(() => '[email protected]')
.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(() => '[email protected]')
.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', () => {
Expand Down Expand Up @@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion packages/email-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/email-plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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; };
}

/**
Expand Down

0 comments on commit 407d232

Please sign in to comment.