Skip to content

Commit

Permalink
Merge pull request #259 from adhocteam/dcloud/TTAHUB-35-download-ar-a…
Browse files Browse the repository at this point in the history
…s-csv

[TTAHUB 35]: Download Activity Reports as csv files
  • Loading branch information
dcloud authored Mar 31, 2021
2 parents 0b5ea01 + 5e11452 commit d824f6e
Show file tree
Hide file tree
Showing 11 changed files with 698 additions and 0 deletions.
40 changes: 40 additions & 0 deletions docs/openapi/paths/activity-reports/download.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
get:
tags:
- activity-reports
summary: Download activity reports, in various formats.
description: >-
Multiple report ids can be selected by repeat use of the `report` parameter.
parameters:
- name: report
in: query
description: Ids of reports to include in download
required: true
style: form
schema:
type: array
minItems: 1
items:
type: integer
- name: format
in: query
description: Format to download report in
required: false
style: form
schema:
type: string
enum:
- json
- csv
default: json

responses:
200:
description: Successfully retrieved activity reports
content:
text/csv; charset=utf-8:
schema:
type: string
application/json:
schema:
type: object
$ref: '../../index.yaml#/components/schemas/activityReport'
2 changes: 2 additions & 0 deletions docs/openapi/paths/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
$ref: './adminAllUsers.yaml'
'/activity-reports':
$ref: './activity-reports/activity-reports.yaml'
'/activity-reports/download':
$ref: './activity-reports/download.yaml'
'/activity-reports/goals':
$ref: './activity-reports/goals.yaml'
'/activity-reports/approvers':
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
"cookie-session": "^1.4.0",
"cron": "^1.8.2",
"csv-parse": "^4.14.1",
"csv-stringify": "^5.6.2",
"cucumber-html-reporter": "^5.2.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
Expand Down
172 changes: 172 additions & 0 deletions src/lib/transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/**
* @param {string} field name to be retrieved
* @returns {function} Function that will return a simple value wrapped in a Promise
*/
function transformSimpleValue(instance, field) {
let value = instance[field];
if (value && Array.isArray(value)) {
value = value.join('\n');
}
const obj = {};
Object.defineProperty(obj, field, {
value,
enumerable: true,
});
return Promise.resolve(obj);
}

/*
* Generates a function that can transform values of a related model
* @param {string} field The field of the related model
* @param {string} key The key on the related model to transform
* @returns {function} A function that will perform the transformation
*/
function transformRelatedModel(field, prop) {
async function transformer(instance) {
const obj = {};
let records = await instance[field];
if (records) {
if (!Array.isArray(records)) {
records = [records];
}
const value = records.map((r) => (r[prop] || '')).join('\n');
Object.defineProperty(obj, field, {
value,
enumerable: true,
});
}
return Promise.resolve(obj);
}
return transformer;
}

/*
* Helper function for transformGoalsAndObjectives
*/
function sortObjectives(a, b) {
if (b.goal.id < a.goal.id) {
return 1;
}
if (b.id < a.id) {
return 1;
}
return -1;
}

/*
* Create an object with goals and objectives. Used by transformGoalsAndObjectives
* @param {Array<Objectives>} objectiveRecords
*/
function makeGoalsAndObjectivesObject(objectiveRecords) {
objectiveRecords.sort(sortObjectives);
let objectiveNum = 1;
let goalNum = 0;

return objectiveRecords.reduce((accum, objective) => {
const {
goal, title, status, ttaProvided,
} = objective;
const goalName = goal.name || null;
const newGoal = goalName && !Object.values(accum).includes(goalName);

if (newGoal) {
goalNum += 1;
Object.defineProperty(accum, `goal-${goalNum}`, {
value: goalName,
enumerable: true,
});
Object.defineProperty(accum, `goal-${goalNum}-status`, {
value: goal.status,
enumerable: true,
});
objectiveNum = 1;
}

const objectiveId = `${goalNum}.${objectiveNum}`;

Object.defineProperty(accum, `objective-${objectiveId}`, {
value: title,
enumerable: true,
});
Object.defineProperty(accum, `objective-${objectiveId}-status`, {
value: status,
enumerable: true,
});
Object.defineProperty(accum, `objective-${objectiveId}-ttaProvided`, {
value: ttaProvided,
enumerable: true,
});
objectiveNum += 1;

return accum;
}, {});
}

/*
* Transform goals and objectives into a format suitable for a CSV
* @param {ActivityReport} ActivityReport instance
* @returns {Promise<object>} Object with key-values for goals and objectives
*/
async function transformGoalsAndObjectives(report) {
let obj = {};
const objectiveRecords = await report.objectives;
if (objectiveRecords) {
obj = makeGoalsAndObjectivesObject(objectiveRecords);
}
return obj;
}

const arTransformers = [
'displayId',
transformRelatedModel('author', 'name'),
transformRelatedModel('approvingManager', 'name'),
transformRelatedModel('lastUpdatedBy', 'name'),
'requester',
transformRelatedModel('collaborators', 'name'),
'programTypes',
'targetPopulations',
'virtualDeliveryType',
'reason',
'participants',
'topics',
'status',
'ttaType',
'numberOfParticipants',
'deliveryMethod',
'duration',
'endDate',
'startDate',
transformRelatedModel('activityRecipients', 'name'),
'activityRecipientType',
'ECLKCResourcesUsed',
'nonECLKCResourcesUsed',
transformRelatedModel('attachments', 'originalFileName'),
transformGoalsAndObjectives,
transformRelatedModel('granteeNextSteps', 'note'),
transformRelatedModel('specialistNextSteps', 'note'),
'context',
'managerNotes',
'additionalNotes',
'lastSaved',
];

async function activityReportToCsvRecord(report, transformers = arTransformers) {
const callFunctionOrValueGetter = (x) => {
if (typeof x === 'function') {
return x(report);
}
if (typeof x === 'string') {
return transformSimpleValue(report, x);
}
return {};
};
const recordObjects = await Promise.all(transformers.map(callFunctionOrValueGetter));
const record = recordObjects.reduce((obj, value) => Object.assign(obj, value), {});
return record;
}

export {
activityReportToCsvRecord,
arTransformers,
makeGoalsAndObjectivesObject,
};
Loading

0 comments on commit d824f6e

Please sign in to comment.