Skip to content

Instantly share code, notes, and snippets.

@davidgf
Last active May 4, 2018 06:34
Show Gist options
  • Save davidgf/c03baedee14a758c406a58de99ea277a to your computer and use it in GitHub Desktop.
Save davidgf/c03baedee14a758c406a58de99ea277a to your computer and use it in GitHub Desktop.
Example of counting email events by type and campaign in a functional way
import R from 'ramda';
import Promise from 'bluebird';
const ReportRepository = {
incrementCounters: () => Promise.resolve('Counters incremented')
};
const supportedEventTypes = ['email.delivered', 'email.reported', 'email.bounced'];
const eventTypeSupported = R.pipe(
R.prop('type'),
R.partialRight(R.contains, [supportedEventTypes])
);
const eventHasCampaignId = R.pipe(
R.prop('payload'),
R.has('campaignId')
);
const isValidEvent = R.allPass([eventTypeSupported, eventHasCampaignId]);
const isBounce = R.where({
type: R.equals('email.bounced'),
payload: R.complement(R.propEq('bounceType', 'Transient'))
});
const isSoftBounce = R.where({
type: R.equals('email.bounced'),
payload: R.propEq('bounceType', 'Transient')
});
const isComplaint = R.propEq('type', 'email.reported');
const isDelivery = R.propEq('type', 'email.delivered');
const eventToCounter = R.cond([
[isBounce, R.always('bouncesCount')],
[isSoftBounce, R.always('softBouncesCount')],
[isDelivery, R.always('sentCount')],
[isComplaint, R.always('complaintsCount')]
]);
const eventsByCampaign = R.groupBy(R.path(['payload', 'campaignId']));
const execute = function incrementReportCounters(events = [], repository = ReportRepository) {
const campaignIdCountersPairs = R.pipe(
R.filter(isValidEvent),
eventsByCampaign,
R.map(R.countBy(eventToCounter)),
R.toPairs
)(events);
return Promise.map(campaignIdCountersPairs, ([campaignId, counters]) => {
return repository.incrementCounters(campaignId, counters)
.catch(err => console.error(err));
});
};
export default {
execute
};
import chai from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import IncrementReportCounters from './IncrementReportCounters';
const { expect } = chai;
chai.use(sinonChai);
const ReportRepository = {
incrementCounters: () => Promise.resolve('Counters incremented')
};
describe('IncrementReportCounters', () => {
describe('.execute', () => {
const aCampaignId = 'a-campaign-id';
const anotherCampaignId = 'another-campaign-id';
const events = [
{ type: 'email.delivered', payload: { campaignId: aCampaignId } },
{ type: 'email.delivered', payload: { campaignId: aCampaignId } },
{ type: 'email.delivered', payload: { campaignId: aCampaignId } },
{ type: 'email.bounced', payload: { campaignId: aCampaignId, bounceType: 'Transient' } },
{ type: 'email.bounced', payload: { campaignId: aCampaignId } },
{ type: 'email.reported', payload: { campaignId: anotherCampaignId } },
{ type: 'email.reported', payload: { without: 'campaignId' } },
{ type: 'email.notsupported', payload: { campaignId: aCampaignId } }
];
beforeEach(() => sinon.stub(ReportRepository, 'incrementCounters').resolves(true));
afterEach(() => ReportRepository.incrementCounters.restore());
it('increments the appropriate report counters', async () => {
const expectations = [
[aCampaignId, { softBouncesCount: 1, bouncesCount: 1, sentCount: 3 }],
[anotherCampaignId, { complaintsCount: 1 }]
];
await IncrementReportCounters.execute(events, ReportRepository);
expect(ReportRepository.incrementCounters).to.have.callCount(expectations.length);
expectations.forEach((expected) => {
expect(ReportRepository.incrementCounters).to.have.been.calledWithExactly(...expected);
});
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment