Skip to content

Commit

Permalink
Merge pull request #141 from cluebbehusen/master
Browse files Browse the repository at this point in the history
Added bulk survey response export in csv format
  • Loading branch information
Anthony Roach authored Jun 5, 2020
2 parents 6ec24fa + 6564dd8 commit 1007471
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 0 deletions.
112 changes: 112 additions & 0 deletions lib/cmds/survey_cmds/export-responses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use strict';

const querystring = require('querystring');
const { get } = require('../../api');
const plainPrint = require('../../plainPrint');

exports.command = 'export-responses <projectId> <surveyId>';
exports.desc = 'Export all responses in .csv format';
exports.builder = yargs => {
yargs.positional('projectId', {
describe: 'The ID of the project containing the survey.',
type: 'string'
}).positional('surveyId', {
describe: 'The ID of the survey to export responses from.',
type: 'string'
});
};

function buildQuestionList (surveyItem, results) {
if (surveyItem.linkId && surveyItem.text && surveyItem.type && surveyItem.type !== 'group' && surveyItem.type !== 'display') {
results.push({
text: surveyItem.text.replace(/\n/g, ' ').replace(/"/g, '""'),
linkId: surveyItem.linkId
});
}
if (surveyItem.item) {
(surveyItem.item || []).forEach(_ => buildQuestionList(_, results));
}
}

function generateLinkAnswerMap (responseItem, results) {
if (responseItem.linkId && responseItem.answer) {
results[responseItem.linkId] = responseItem.answer;
}
if (responseItem.item) {
(responseItem.item || []).forEach(_ => generateLinkAnswerMap(_, results));
}
}

function handleDefaultValue (value) {
return value[Object.keys(value)[0]];
}

function handleValueCoding (coding) {
if (coding.valueCoding.display) {
return coding.valueCoding.display;
} else if (coding.valueCoding.code) {
return coding.valueCoding.code;
} else {
return '';
}
}

function handleValueQuantity (responseValue) {
const {
value = '',
unit = ''
} = responseValue.valueQuantity || {};
return `${value} ${unit}`;
}

exports.handler = async argv => {
const surveyUrl = `/v1/survey/projects/${argv.projectId}/surveys/${argv.surveyId}`;
const survey = await get(argv, surveyUrl);
const results = [{text: 'Patient ID', linkId: ''}, {text: 'Date', linkId: ''}];
(survey.data.item || []).forEach(_ => buildQuestionList(_, results));
const responseItems = [];
const responseQuery = querystring.stringify({
'surveyId': `${argv.surveyId}`,
'pageSize': 10
});
const responsesUrl = `/v1/survey/projects/${argv.projectId}/responses?${responseQuery}`;
let responses = await get(argv, responsesUrl);
responseItems.push(...responses.data.items);
while (responses.data.links.next) {
const nextPageUrl = responses.data.links.next;
responses = await get(argv, nextPageUrl);
responseItems.push(...responses.data.items);
}
const csvRows = [];
csvRows.push(`"${results.map(_ => _.text).join('","')}"`);
responseItems.forEach(responseItem => {
const responseColumns = [];
responseColumns.push(responseItem.subject.reference);
responseColumns.push(responseItem.authored);
const responseItemResults = {};
(responseItem.item || []).forEach(_ => generateLinkAnswerMap(_, responseItemResults));
results.map(_ => _.linkId)
.filter(_ => _ !== '')
.forEach(linkId => {
if (responseItemResults[linkId]) {
let responseString = '';
responseItemResults[linkId].forEach(value => {
if (value.valueQuantity) {
responseString = responseString.concat(handleValueQuantity(value));
} else if (value.valueCoding) {
responseString = responseString.concat(handleValueCoding(value));
} else {
responseString = responseString.concat(handleDefaultValue(value));
}
responseString = responseString.concat('|');
});
responseColumns.push(responseString.slice(0, -1).replace(/"/g, '""').replace(/\n/g, ' '));
} else {
// optional questions may not have answers
responseColumns.push('');
}
});
csvRows.push(`"${responseColumns.join('","')}"`);
});
plainPrint(csvRows.join('\n'));
};
10 changes: 10 additions & 0 deletions lib/cmds/surveys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';

const options = require('../common-yargs');

exports.command = 'surveys <command>';
exports.desc = 'Perform operations on surveys.';
exports.builder = yargs => {
return options(yargs.commandDir('survey_cmds'));
};
exports.handler = function (argv) {};
5 changes: 5 additions & 0 deletions lib/plainPrint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = function (data) {
console.log(data);
};
51 changes: 51 additions & 0 deletions test/unit/commands/survey-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const yargs = require('yargs');
const sinon = require('sinon');
const test = require('ava');
const proxyquire = require('proxyquire');

const questionnaire = require('./survey/questionnaire.json');
const responseOne = require('./survey/response_one.json');
const responseTwo = require('./survey/response_two.json');

const getStub = sinon.stub();
const printSpy = sinon.spy();
let callback;

const mocks = {
'../../api': {
get: getStub
},
'../../plainPrint': (data) => {
printSpy(data);
callback();
}
};

const exportResponses = proxyquire('../../../lib/cmds/survey_cmds/export-responses', mocks);

test.afterEach.always(t => {
getStub.reset();
printSpy.resetHistory();
callback = null;
});

test.serial.cb('The export command should list responses in csv format', t => {
getStub.onFirstCall().returns(questionnaire);
getStub.onSecondCall().returns(responseOne);
getStub.onThirdCall().returns(responseTwo);

callback = () => {
t.is(getStub.callCount, 3);
t.is(getStub.getCall(0).args[1], '/v1/survey/projects/projectId/surveys/surveyId');
t.is(getStub.getCall(1).args[1], '/v1/survey/projects/projectId/responses?surveyId=surveyId&pageSize=10');
t.is(getStub.getCall(2).args[1], 'testurl');
t.is(printSpy.callCount, 1);
t.is(printSpy.getCall(0).args[0], '"Patient ID","Date","Systolic blood pressure","Diastolic blood pressure","What country do you live in?"\n"73d77961-a8b7-48e0-b76f-66e53485038b","2020-06-04T14:56:14.402Z","120 millimeter of mercury","80 millimeter of mercury","United States of America"\n"73d77961-a8b7-48e0-b76f-66e53485038b","2020-06-04T14:56:14.402Z","120 millimeter of mercury","80 millimeter of mercury","United States of America"');
t.end();
};

yargs.command(exportResponses)
.parse('export-responses projectId surveyId');
});
28 changes: 28 additions & 0 deletions test/unit/commands/survey/questionnaire.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"item": [
{
"linkId": "ba0953cc-f0fd-4f3d-bfbd-9320e0c080a2",
"text": "Page 1",
"type": "group",
"item": [
{
"type": "quantity",
"text": "Systolic blood pressure",
"linkId": "f076e708-add9-4c1a-ba9a-9782b05c2bad"
},
{
"type": "quantity",
"text": "Diastolic blood pressure",
"linkId": "cd88370d-44c8-491f-a71c-fcb53f02a451"
},
{
"linkId": "17b9a36d-cb0c-44c5-83d1-c1f8597548a8",
"type": "choice",
"text": "What country do you live in?"
}
]
}
]
}
}
60 changes: 60 additions & 0 deletions test/unit/commands/survey/response_one.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"data": {
"items": [
{
"subject": {
"reference": "73d77961-a8b7-48e0-b76f-66e53485038b"
},
"authored": "2020-06-04T14:56:14.402Z",
"item": [
{
"linkId": "ba0953cc-f0fd-4f3d-bfbd-9320e0c080a2",
"item": [
{
"linkId": "f076e708-add9-4c1a-ba9a-9782b05c2bad",
"answer": [
{
"valueQuantity": {
"unit": "millimeter of mercury",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]",
"value": 120
}
}
]
},
{
"linkId": "cd88370d-44c8-491f-a71c-fcb53f02a451",
"answer": [
{
"valueQuantity": {
"unit": "millimeter of mercury",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]",
"value": 80
}
}
]
},
{
"linkId": "17b9a36d-cb0c-44c5-83d1-c1f8597548a8",
"answer": [
{
"valueCoding": {
"display": "United States of America",
"code": "US"
}
}
]
}
]
}
]
}
],
"links": {
"self": "fillin",
"next": "testurl"
}
}
}
59 changes: 59 additions & 0 deletions test/unit/commands/survey/response_two.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"data": {
"items": [
{
"subject": {
"reference": "73d77961-a8b7-48e0-b76f-66e53485038b"
},
"authored": "2020-06-04T14:56:14.402Z",
"item": [
{
"linkId": "ba0953cc-f0fd-4f3d-bfbd-9320e0c080a2",
"item": [
{
"linkId": "f076e708-add9-4c1a-ba9a-9782b05c2bad",
"answer": [
{
"valueQuantity": {
"unit": "millimeter of mercury",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]",
"value": 120
}
}
]
},
{
"linkId": "cd88370d-44c8-491f-a71c-fcb53f02a451",
"answer": [
{
"valueQuantity": {
"unit": "millimeter of mercury",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]",
"value": 80
}
}
]
},
{
"linkId": "17b9a36d-cb0c-44c5-83d1-c1f8597548a8",
"answer": [
{
"valueCoding": {
"display": "United States of America",
"code": "US"
}
}
]
}
]
}
]
}
],
"links": {
"self": "fillin"
}
}
}

0 comments on commit 1007471

Please sign in to comment.