diff --git a/packages/server/__tests__/arpa_reporter/server/lib/audit-report.spec.js b/packages/server/__tests__/arpa_reporter/server/lib/audit-report.spec.js index 2cc5d8ba4..b60412126 100644 --- a/packages/server/__tests__/arpa_reporter/server/lib/audit-report.spec.js +++ b/packages/server/__tests__/arpa_reporter/server/lib/audit-report.spec.js @@ -157,7 +157,20 @@ describe('audit report generation', () => { 'Project Expenditure Category Group': '2-Negative Economic Impacts', 'Project ID': '4', }]; - const headers = audit_report.createHeadersProjectSummariesV2(projects); + const headers = audit_report.sortHeadersWithDates(projects, + [ + 'Capital Expenditure Amount', + 'Project Description', + 'Project Expenditure Category', + 'Project Expenditure Category Group', + 'Project ID', + ], + [ + 'Total Aggregate Expenditures', + 'Total Aggregate Obligations', + 'Total Expenditures for Awards Greater or Equal to $50k', + 'Total Obligations for Awards Greater or Equal to $50k', + ]); const headersExpected = [ 'Project ID', 'Project Description', diff --git a/packages/server/__tests__/email/email.test.js b/packages/server/__tests__/email/email.test.js index 5d4e5f3d4..70663c86b 100644 --- a/packages/server/__tests__/email/email.test.js +++ b/packages/server/__tests__/email/email.test.js @@ -194,6 +194,7 @@ describe('Email sender', () => { sinon.replace(emailService, 'getTransport', sinon.fake.returns({ sendEmail: sendFake })); email.deliverEmail({ + fromName: 'Foo', toAddress: 'foo@bar.com', ccAddress: 'cc@example.com', emailHTML: '
foo
', @@ -203,6 +204,7 @@ describe('Email sender', () => { expect(sendFake.calledOnce).to.equal(true); expect(sendFake.firstCall.args).to.deep.equal([{ + fromName: 'Foo', toAddress: 'foo@bar.com', ccAddress: 'cc@example.com', subject: 'test foo email', diff --git a/packages/server/src/arpa_reporter/lib/audit-report.js b/packages/server/src/arpa_reporter/lib/audit-report.js index 7f9e2fa32..483c496b8 100644 --- a/packages/server/src/arpa_reporter/lib/audit-report.js +++ b/packages/server/src/arpa_reporter/lib/audit-report.js @@ -398,16 +398,16 @@ async function createReportsGroupedBySubAward(periodId, tenantId, dateFormat = R // - the initial value for each column in this row is zero subAwardReportingPeriodIds.forEach((id) => { const endDate = endDatesByReportingPeriodId[id]; - row[`${endDate} Awards > 50000 SubAward Amount`] = 0; - row[`${endDate} Awards > 50000 SubAward Expenditure`] = 0; + row[`${endDate} Awards > 50000 SubAward Amount (Obligation)`] = 0; + row[`${endDate} Awards > 50000 SubAward Current Expenditure Amount`] = 0; }); // Sum the total value of each initialized column from the corresponding subtotal // provided by each subAward record subAwardRecords.forEach((record) => { const endDate = endDatesByReportingPeriodId[record.upload.reporting_period_id]; - row[`${endDate} Awards > 50000 SubAward Amount`] += (record.content.Award_Amount__c || 0); - row[`${endDate} Awards > 50000 SubAward Expenditure`] += (record.content.Expenditure_Amount__c || 0); + row[`${endDate} Awards > 50000 SubAward Amount (Obligation)`] += (record.content.Award_Amount__c || 0); + row[`${endDate} Awards > 50000 SubAward Current Expenditure Amount`] += (record.content.Expenditure_Amount__c || 0); }); subAwardLogger.fields.subAward.totalColumns = Object.keys(row).length; @@ -465,8 +465,8 @@ async function createKpiDataGroupedByProject(periodId, tenantId, logger = log) { * The headers are split into the date and non-date headers. * The non-date headers come first with an ordering, then the date headers. */ -function createHeadersProjectSummariesV2(projectSummaryGroupedByProject) { - const keys = Array.from(new Set(projectSummaryGroupedByProject.map(Object.keys).flat())); +function sortHeadersWithDates(data, expectedOrderWithoutDate, expectedOrderWithDate) { + const keys = Array.from(new Set(data.map(Object.keys).flat())); // split up by date and not date const withDate = keys.filter((x) => REPORTING_DATE_REGEX.exec(x)); const withoutDate = keys.filter((x) => REPORTING_DATE_REGEX.exec(x) == null); @@ -486,23 +486,6 @@ function createHeadersProjectSummariesV2(projectSummaryGroupedByProject) { return x; }, {}); - const expectedOrderWithoutDate = [ - 'Project ID', - 'Project Description', - 'Project Expenditure Category Group', - 'Project Expenditure Category', - 'Capital Expenditure Amount', - ]; - - const expectedOrderWithDate = [ - 'Total Aggregate Obligations', - 'Total Aggregate Expenditures', - 'Total Obligations for Awards Greater or Equal to $50k', - 'Total Expenditures for Awards Greater or Equal to $50k', - 'Total Obligations for Aggregate Awards < $50k', - 'Total Expenditures for Aggregate Awards < $50k', - ]; - // first add the properly ordered non-date headers, // then add the headers sorted by the header group then the date const headers = [ @@ -514,6 +497,7 @@ function createHeadersProjectSummariesV2(projectSummaryGroupedByProject) { .map((date) => `${date.format(REPORTING_DATE_FORMAT)} ${x[0]}`)).flat(), ]; + debugger; return headers; } @@ -579,10 +563,36 @@ async function generate(requestHost, tenantId, periodId) { const sheet1 = jsonToSheet(obligations, 'Obligations & Expenditures'); const sheet2 = jsonToSheet(projectSummaries, 'Project Summaries'); const sheet3 = jsonToSheet(projectSummaryGroupedByProject, 'Project Summaries V2', { - header: createHeadersProjectSummariesV2(projectSummaryGroupedByProject), + header: sortHeadersWithDates( + projectSummaryGroupedByProject, + [ + 'Project ID', + 'Project Description', + 'Project Expenditure Category Group', + 'Project Expenditure Category', + 'Capital Expenditure Amount', + ], + [ + 'Total Aggregate Obligations', + 'Total Aggregate Expenditures', + 'Total Obligations for Awards Greater or Equal to $50k', + 'Total Expenditures for Awards Greater or Equal to $50k', + 'Total Obligations for Aggregate Awards < $50k', + 'Total Expenditures for Aggregate Awards < $50k', + ], + ), }); // FIXME need to sort - const sheet4 = jsonToSheet(projectSummaryGroupedBySubAward, 'SubAward Summaries'); + const sheet4 = jsonToSheet(projectSummaryGroupedBySubAward, 'SubAward Summaries', { + header: sortHeadersWithDates( + projectSummaryGroupedBySubAward, + ['SubAward ID'], + [ + 'Awards > 50000 SubAward Amount (Obligation)', + 'Awards > 50000 SubAward Current Expenditure Amount', + ], + ), + }); const sheet5 = jsonToSheet(KPIDataGroupedByProject, 'KPI'); log.info('finished building sheets from aggregated data'); @@ -704,7 +714,7 @@ module.exports = { generateAndSendEmail, processSQSMessageRequest, sendEmailWithLink, - createHeadersProjectSummariesV2, + sortHeadersWithDates, // export for testing getRecordsByProject, diff --git a/packages/server/src/lib/email.js b/packages/server/src/lib/email.js index 320611d3d..7676f34c8 100644 --- a/packages/server/src/lib/email.js +++ b/packages/server/src/lib/email.js @@ -18,6 +18,7 @@ const ASYNC_REPORT_TYPES = { const HELPDESK_EMAIL = 'grants-helpdesk@usdigitalresponse.org'; async function deliverEmail({ + fromName, toAddress, ccAddress, emailHTML, @@ -25,6 +26,7 @@ async function deliverEmail({ subject, }) { return emailService.getTransport().sendEmail({ + fromName, toAddress, ccAddress, subject, @@ -33,16 +35,38 @@ async function deliverEmail({ }); } +function buildBaseUrlSafe() { + const baseUrl = new URL(process.env.WEBSITE_DOMAIN); + baseUrl.searchParams.set('utm_source', 'usdr-grants'); + baseUrl.searchParams.set('utm_medium', 'email'); + return baseUrl.toString(); +} + +/** + * Adds the base email HTML around the email body HTML. Specifically, adds the USDR logo header, + * footer, title, preheader, etc. + * + * @param {string} emailHTML - Rendered email body HTML + * @param {object} brandDetails - Options to control how the base branding is rendered + * @param {string} brandDetails.tool_name - Name of the product triggering the email, rendered + * underneath the USDR logo + * @param {string} brandDetails.title - Rendered as the HTML