From 30826a44e3729e27625fc9f51c50a30a7718982e Mon Sep 17 00:00:00 2001 From: Dave M <64168322+replicantSocks@users.noreply.github.com> Date: Tue, 21 Nov 2023 10:50:41 -0500 Subject: [PATCH] feat: add/fix fields in csv export (#2180) * feat: add/fix fields in csv export * fix: column header flipped in test --------- --- packages/server/__tests__/api/grants.test.js | 19 +++++++++++--- packages/server/src/db/index.js | 27 +++++++++++++++++--- packages/server/src/lib/email.js | 1 + packages/server/src/routes/grants.js | 8 +++++- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/packages/server/__tests__/api/grants.test.js b/packages/server/__tests__/api/grants.test.js index 1fc79c6a4..c930d1228 100644 --- a/packages/server/__tests__/api/grants.test.js +++ b/packages/server/__tests__/api/grants.test.js @@ -478,7 +478,7 @@ describe('`/api/grants` endpoint', () => { 'Title', 'Viewed By', 'Interested Teams', - 'Status', + 'Opportunity Status', 'Opportunity Category', 'Cost Sharing', 'Award Ceiling', @@ -487,11 +487,24 @@ describe('`/api/grants` endpoint', () => { 'Agency Code', 'Grant Id', 'URL', + 'Funding Type', + 'Appropriations Bill', + 'Agency Code', + 'Eligibility', ]; + const txt = await response.text(); + const rows = txt.split('\n'); + expect(rows[0]).to.equal(expectedCsvHeaders.join(',')); + + const cells = rows[1].split(','); + const valMap = new Map([...Array(cells.length).keys()].map((i) => [expectedCsvHeaders[i], cells[i]])); - expect(txt.split('\n')[0]).to.equal(expectedCsvHeaders.join(',')); - expect(txt.split('\n')[1]).to.contain('HHS-2021-IHS-TPI-0001,Community Health Aide Program: Tribal Planning &'); + expect(valMap.get('Opportunity Number')).to.equal('HHS-2021-IHS-TPI-0001'); + expect(valMap.get('Title')).to.equal('Community Health Aide Program: Tribal Planning & Implementation'); + expect(valMap.get('Funding Type')).to.equal('Other'); + expect(valMap.get('Agency Code')).to.equal('HHS-IHS'); + expect(valMap.get('Eligibility')).to.equal('"Native American tribal organizations (other than Federally recognized tribal governments)|Others(see text field entitled ""Additional Information on Eligibility"" for clarification)|Native American tribal governments(Federally recognized)"'); }); it('produces same number of rows as grid', async () => { diff --git a/packages/server/src/db/index.js b/packages/server/src/db/index.js index c5c13a8c6..4b677eaa1 100755 --- a/packages/server/src/db/index.js +++ b/packages/server/src/db/index.js @@ -668,6 +668,23 @@ function validateSearchFilters(filters) { return errors; } +function addCsvData(qb) { + qb + .select(knex.raw(` + CASE + WHEN grants.funding_instrument_codes = 'G' THEN 'Grant' + WHEN grants.funding_instrument_codes = 'CA' THEN 'Cooperative Agreement' + WHEN grants.funding_instrument_codes = 'PC' THEN 'Procurement Contract' + ELSE 'Other' + END as funding_type + `)) + .select(knex.raw(`array_to_string(array_agg(${TABLES.eligibility_codes}.label), '|') AS eligibility`)) + .leftJoin( + `${TABLES.eligibility_codes}`, + `${TABLES.eligibility_codes}.code`, '=', knex.raw(`ANY(string_to_array(${TABLES.grants}.eligibility_codes, ' '))`), + ); +} + /* filters: { reviewStatuses: List[Enum['Applied', 'Not Applying', 'Interested']], @@ -689,15 +706,15 @@ function validateSearchFilters(filters) { tenantId: number agencyId: number */ -async function getGrantsNew(filters, paginationParams, orderingParams, tenantId, agencyId) { - console.log(JSON.stringify([filters, paginationParams, orderingParams, tenantId, agencyId])); +async function getGrantsNew(filters, paginationParams, orderingParams, tenantId, agencyId, toCsv) { + console.log(JSON.stringify([filters, paginationParams, orderingParams, tenantId, agencyId, toCsv])); const errors = validateSearchFilters(filters); if (errors.length > 0) { throw new Error(`Invalid filters: ${errors.join(', ')}`); } - const data = await knex(TABLES.grants) + const query = knex(TABLES.grants) .select([ 'grants.grant_id', 'grants.grant_number', @@ -767,6 +784,10 @@ async function getGrantsNew(filters, paginationParams, orderingParams, tenantId, 'grants.funding_instrument_codes', 'grants.bill', ); + if (toCsv) { + query.modify(addCsvData); + } + const data = await query; const fullCount = data.length > 0 ? data[0].full_count : 0; diff --git a/packages/server/src/lib/email.js b/packages/server/src/lib/email.js index 69b315d4c..ec9a070ea 100644 --- a/packages/server/src/lib/email.js +++ b/packages/server/src/lib/email.js @@ -269,6 +269,7 @@ async function getAndSendGrantForSavedSearch({ await db.buildPaginationParams({ currentPage: 1, perPage: 31 }), {}, userSavedSearch.tenantId, + false, ); return sendGrantDigest({ diff --git a/packages/server/src/routes/grants.js b/packages/server/src/routes/grants.js index 675b05685..de9f64572 100755 --- a/packages/server/src/routes/grants.js +++ b/packages/server/src/routes/grants.js @@ -80,6 +80,7 @@ router.get('/next', requireUser, async (req, res) => { orderingParams, user.tenant_id, user.agency_id, + false, ); return res.json(grants); @@ -121,6 +122,7 @@ router.get('/exportCSVNew', requireUser, async (req, res) => { orderingParams, user.tenant_id, user.agency_id, + true, ); // Generate CSV @@ -155,7 +157,7 @@ router.get('/exportCSVNew', requireUser, async (req, res) => { { key: 'title', header: 'Title' }, { key: 'viewed_by', header: 'Viewed By' }, { key: 'interested_agencies', header: 'Interested Teams' }, - { key: 'opportunity_status', header: 'Status' }, + { key: 'opportunity_status', header: 'Opportunity Status' }, { key: 'opportunity_category', header: 'Opportunity Category' }, { key: 'cost_sharing', header: 'Cost Sharing' }, { key: 'award_ceiling', header: 'Award Ceiling' }, @@ -164,6 +166,10 @@ router.get('/exportCSVNew', requireUser, async (req, res) => { { key: 'agency_code', header: 'Agency Code' }, { key: 'grant_id', header: 'Grant Id' }, { key: 'url', header: 'URL' }, + { key: 'funding_type', header: 'Funding Type' }, + { key: 'bill', header: 'Appropriations Bill' }, + { key: 'agency_code', header: 'Agency Code' }, + { key: 'eligibility', header: 'Eligibility' }, ], });