Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deployment type filter to email reporting #29

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions manage/app/controllers/email/reporting.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import query from 'leads-manage/gql/queries/email-report/run';
export default Controller.extend(ComponentQueryManager, LoadingMixin, {
center: moment(),
isRunning: false,
excludeDeploymentTypeEntities: false,

canSubmit: computed('isRunning', 'range.{start,end}', function() {
if (this.get('isRunning')) return false;
Expand All @@ -28,12 +29,31 @@ export default Controller.extend(ComponentQueryManager, LoadingMixin, {
return end.valueOf();
}),

exportUrl: computed('range.{start,end}', 'excludeDeploymentTypeEntities', 'deploymentTypeEntities.[]', function() {
const { start, end } = this.getProperties('start', 'end');
const types = this.get('deploymentTypeEntities');
const exclude = this.get('excludeDeploymentTypeEntities');
const entities = (types || []).map(type => encodeURIComponent(type.entity)).join(',');
const params = new URLSearchParams();
params.set('start', start);
params.set('end', end);
if (entities) {
if (exclude) {
params.set('excludeDeploymentTypeEntities', entities);
} else {
params.set('includeDeploymentTypeEntities', entities);
}
}
return `/export/email-deployment-report?${params}`;
}),

init() {
this._super(...arguments);
this.set('range', {
start: moment().startOf('week'),
end: moment().endOf('week'),
});
this.set('deploymentTypeEntities', []);
},

actions: {
Expand All @@ -50,9 +70,16 @@ export default Controller.extend(ComponentQueryManager, LoadingMixin, {
this.set('isRunning', true);
this.showLoading();

const types = this.get('deploymentTypeEntities');
const exclude = this.get('excludeDeploymentTypeEntities');
const entities = (types || []).map(type => type.entity);

const input = {
start: this.get('range.start').valueOf(),
end: this.get('range.end').valueOf(),
...(entities.length && {
...(exclude ? { excludeDeploymentTypeEntities: entities } : { includeDeploymentTypeEntities: entities })
})
}
const variables = { input };

Expand Down
193 changes: 117 additions & 76 deletions manage/app/templates/email/reporting.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,89 +8,130 @@
<div class="card-header"></div>
<div class="card-body">

{{#power-calendar-range
center=center
selected=range
onCenterChange=(action (mut center) value="moment")
onSelect=(action "setRange" value="moment")
as |calendar|
}}
{{calendar.nav}}
{{calendar.days}}
{{/power-calendar-range}}

<div class="mt-3">
<button class="btn btn-success" disabled={{eq canSubmit false}} {{action "runReport"}}>{{entypo-icon "pie-chart"}} Run Report</button>
{{#if result}}
<a class="btn btn-success" role="button" href="/export/email-deployment-report?start={{start}}&end={{end}}">{{entypo-icon "download"}} Export Report</a>
{{/if}}
<div class="row">
<div class="col-lg-4 col-xl-5">
<div class="form-group">
<label>Deployment Types</label>
{{type-ahead
placeholder="Begin typing to find a deployment type..."
type="email-deployment-type"
field="data.Name"
selected=deploymentTypeEntities
multiple=true
allowClear=true
onChange=(action (mut deploymentTypeEntities))
}}
<div class="custom-control custom-checkbox mt-1">
{{input type="checkbox" checked=excludeDeploymentTypeEntities class="custom-control-input" id="exclude-deployment-type-ids"}}
<label class="custom-control-label" for="exclude-deployment-type-ids">Exclude?</label>
</div>
<small class="form-text text-muted">Optional. The deployment types to {{#if excludeDeploymentTypeEntities}}exclude from{{else}}include in{{/if}} this report.</small>
</div>
</div>
<div class="col-lg-8 col-xl-7">
<div class="row">
<div class="form-group mb-0">
<label>Date Range {{required-field-label}}</label>
{{#power-calendar-range
center=center
selected=range
onCenterChange=(action (mut center) value="moment")
onSelect=(action "setRange" value="moment")
as |calendar|
}}
{{calendar.nav}}
{{calendar.days}}
{{/power-calendar-range}}
<p class="form-text mb-0 text-muted">
{{#if (and range.start range.end)}}
{{moment-format range.start "MMM Do, YYYY"}}
-
{{moment-format range.end "MMM Do, YYYY"}}
{{else}}
Select a date range...
{{/if}}
</p>
</div>
</div>
</div>
</div>
</div>

<div class="card-footer d-flex justify-content-between">
<button class="btn btn-success" disabled={{eq canSubmit false}} {{action "runReport"}}>{{entypo-icon "pie-chart"}} Run Report</button>
{{#if result}}
<div class="row mt-3">
<div class="col">
<h5>{{moment-format result.start "MMM Do, YYYY"}} through {{moment-format result.end "MMM Do, YYYY"}}</h5>
{{!-- @todo include new parameters! --}}
<a class="btn btn-success" role="button" href={{ exportUrl }}>{{entypo-icon "download"}} Export Report</a>
{{/if}}
</div>
</div>
</div>
</div>

{{#each result.weeks as |week|}}
<div class="table-responsive">
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Year</th>
<th>Week</th>
<th>Starting</th>
<th>Category</th>
<th># Sent</th>
<th>Total Sent</th>
<th>Avg. Sent</th>
<th>Avg. Delivered</th>
<th>Avg. Delivery Rate</th>
<th>Total Unique Opens</th>
<th>Avg. Unique Opens</th>
<th>Avg. Unq Open Rate</th>
<th>Total Unique Clicks</th>
<th>Avg. Unique Clicks</th>
<th>Avg. Unq CTR</th>
<th>Avg. Unq CTOR</th>
</tr>
</thead>
<tbody>
{{#each week.types as |c|}}
<tr>
<td>{{week.year}}</td>
<td>{{week.number}}</td>
<td>{{moment-format week.starting "MMM D"}}</td>
<td>{{c.name}}</td>
<td>{{c.deploymentCount}}</td>
<td>{{format-number c.totalSent format="0,0"}}</td>
<td>{{format-number c.avgSent format="0,0"}}</td>
<td>{{format-number c.avgDelivered format="0,0"}}</td>
<td>{{format-number c.avgDeliveryRate format="00.0%"}}</td>
<td>{{format-number c.totalUniqueOpens format="0,0"}}</td>
<td>{{format-number c.avgUniqueOpens format="0,0"}}</td>
<td>{{format-number c.avgUniqueOpenRate format="00.0%"}}</td>
<td>{{format-number c.totalUniqueClicks format="0,0"}}</td>
<td>{{format-number c.avgUniqueClicks format="0,0"}}</td>
<td>{{format-number c.avgUniqueClickToDeliveredRate format="00.0%"}}</td>
<td>{{format-number c.avgUniqueClickToOpenRate format="00.0%"}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
{{else}}
<tr>
<td class="text-center text-muted" colspan="17">No results found.</td>
</tr>
{{/each}}
{{#if result}}
<div class="row mt-4">
<div class="col">
<div class="card list-index">
<div class="card-header"></div>
<div class="card-body">
<h5>{{moment-format result.start "MMM Do, YYYY"}} through {{moment-format result.end "MMM Do, YYYY"}}</h5>

{{#each result.weeks as |week|}}
<div class="table-responsive">
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Year</th>
<th>Week</th>
<th>Starting</th>
<th>Category</th>
<th># Sent</th>
<th>Total Sent</th>
<th>Avg. Sent</th>
<th>Avg. Delivered</th>
<th>Avg. Delivery Rate</th>
<th>Total Unique Opens</th>
<th>Avg. Unique Opens</th>
<th>Avg. Unq Open Rate</th>
<th>Total Unique Clicks</th>
<th>Avg. Unique Clicks</th>
<th>Avg. Unq CTR</th>
<th>Avg. Unq CTOR</th>
</tr>
</thead>
<tbody>
{{#each week.types as |c|}}
<tr>
<td>{{week.year}}</td>
<td>{{week.number}}</td>
<td>{{moment-format week.starting "MMM D"}}</td>
<td>{{c.name}}</td>
<td>{{c.deploymentCount}}</td>
<td>{{format-number c.totalSent format="0,0"}}</td>
<td>{{format-number c.avgSent format="0,0"}}</td>
<td>{{format-number c.avgDelivered format="0,0"}}</td>
<td>{{format-number c.avgDeliveryRate format="00.0%"}}</td>
<td>{{format-number c.totalUniqueOpens format="0,0"}}</td>
<td>{{format-number c.avgUniqueOpens format="0,0"}}</td>
<td>{{format-number c.avgUniqueOpenRate format="00.0%"}}</td>
<td>{{format-number c.totalUniqueClicks format="0,0"}}</td>
<td>{{format-number c.avgUniqueClicks format="0,0"}}</td>
<td>{{format-number c.avgUniqueClickToDeliveredRate format="00.0%"}}</td>
<td>{{format-number c.avgUniqueClickToOpenRate format="00.0%"}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>

{{/if}}
{{else}}
<tr>
<td class="text-center text-muted" colspan="17">No results found.</td>
</tr>
{{/each}}

</div>
<div class="card-footer"></div>
</div>
<div class="card-footer"></div>
</div>
</div>
</div>
{{/if}}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ type EmailDeploymentTypeEdge {
input EmailDeploymentReportInput {
start: Date
end: Date

"The EmailDeploymentType entities that should be included/excluded from the query."
includeDeploymentTypeEntities: [String!]! = []
excludeDeploymentTypeEntities: [String!]! = []
}

input EmailDeploymentSortInput {
Expand Down
16 changes: 14 additions & 2 deletions monorepo/services/server/src/graphql/resolvers/email-deployment.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,20 @@ module.exports = {
*/
emailDeploymentReport: (_, { input }, { auth }) => {
auth.check();
const { start, end } = input;
return emailDeploymentReportService.create({ start, end });
const {
start,
end,
includeDeploymentTypeEntities = [],
excludeDeploymentTypeEntities = [],
} = input;
const includeOmedaDeploymentTypeIds = includeDeploymentTypeEntities.map((e) => parseInt(e.split('*')[1], 10));
const excludeOmedaDeploymentTypeIds = excludeDeploymentTypeEntities.map((e) => parseInt(e.split('*')[1], 10));
return emailDeploymentReportService.create({
start,
end,
includeOmedaDeploymentTypeIds,
excludeOmedaDeploymentTypeIds,
});
},

/**
Expand Down
11 changes: 10 additions & 1 deletion monorepo/services/server/src/routes/exports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,17 @@ router.get('/line-item/:hash/email/metrics', asyncRoute(async (req, res) => {
router.get('/email-deployment-report', asyncRoute(async (req, res) => {
const start = new Date(parseInt(req.query.start, 10));
const end = new Date(parseInt(req.query.end, 10));
const includeDeploymentTypeEntities = (req.query.includeDeploymentTypeEntities && req.query.includeDeploymentTypeEntities.split(',')) || [];
const excludeDeploymentTypeEntities = (req.query.excludeDeploymentTypeEntities && req.query.excludeDeploymentTypeEntities.split(',')) || [];
const includeOmedaDeploymentTypeIds = includeDeploymentTypeEntities.map((e) => parseInt(e.split('*')[1], 10));
const excludeOmedaDeploymentTypeIds = excludeDeploymentTypeEntities.map((e) => parseInt(e.split('*')[1], 10));

const rows = await emailDeploymentReportService.export({ start, end });
const rows = await emailDeploymentReportService.export({
start,
end,
includeOmedaDeploymentTypeIds,
excludeOmedaDeploymentTypeIds,
});

let csv;
if (rows.length) {
Expand Down
27 changes: 24 additions & 3 deletions monorepo/services/server/src/services/email-deployment-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,18 @@ module.exports = {
return parseFloat((Math.round((num * Math.pow(10, dec)) + (sign * 0.0001)) / Math.pow(10, dec)).toFixed(dec));
},

async export({ start, end }) {
const results = await this.create({ start, end });
async export({
start,
end,
includeOmedaDeploymentTypeIds = [],
excludeOmedaDeploymentTypeIds = [],
}) {
const results = await this.create({
start,
end,
includeOmedaDeploymentTypeIds,
excludeOmedaDeploymentTypeIds,
});
if (!isArray(results.weeks)) return [];
return results.weeks.reduce((arr, week) => {
const { year, week: weekNumber, types } = week;
Expand Down Expand Up @@ -56,7 +66,12 @@ module.exports = {
}, []);
},

async create({ start, end }) {
async create({
start,
end,
includeOmedaDeploymentTypeIds = [],
excludeOmedaDeploymentTypeIds = [],
}) {
const now = new Date();

const starting = start
Expand All @@ -82,6 +97,12 @@ module.exports = {
$match: {
'omeda.SentDate': { $gte: starting, $lte: ending },
'omeda.DeploymentDesignation': 'Newsletter',
...((includeOmedaDeploymentTypeIds.length || excludeOmedaDeploymentTypeIds.length) && {
'omeda.DeploymentTypeId': {
...(includeOmedaDeploymentTypeIds.length && { $in: includeOmedaDeploymentTypeIds }),
...(excludeOmedaDeploymentTypeIds.length && { $nin: excludeOmedaDeploymentTypeIds }),
},
}),
},
},
{
Expand Down