Skip to content
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
4 changes: 0 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

/*! m0-start */
const config = {
extends: ['airbnb-base', 'prettier', 'plugin:jest/recommended'],
env: {
Expand All @@ -25,10 +24,7 @@ const config = {
},
plugins: ['prettier']
};
/*! m0-end */

config.rules['no-param-reassign'] = ['error', {props: false}];

/*! m0-start */
module.exports = config;
/*! m0-end */
3,981 changes: 1,416 additions & 2,565 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
"pg": "^8.7.3",
"pino-http": "^5.5.0",
"pino-std-serializers": "^6.2.2",
"q-expressions": "github:CriminalInjuriesCompensationAuthority/q-expressions",
"q-router": "github:CriminalInjuriesCompensationAuthority/q-router#v3.0.2",
"q-templates-application": "github:CriminalInjuriesCompensationAuthority/q-templates-application#v12.1.2",
"q-expressions": "github:CriminalInjuriesCompensationAuthority/q-expressions#feature/task-list/wip",
"q-router": "github:CriminalInjuriesCompensationAuthority/q-router#feature/cica-1315/task-list-statuses",
"q-templates-application": "github:CriminalInjuriesCompensationAuthority/q-templates-application#task-list/rc-with-valitdator",
"semver": "^7.5.4",
"swagger-ui-express": "^4.6.3",
"uuid": "^3.3.2",
Expand Down
74 changes: 60 additions & 14 deletions questionnaire/questionnaire-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
const Ajv = require('ajv');
const AjvErrors = require('ajv-errors');
const VError = require('verror');
const createQRouter = require('q-router');
const router = require('q-router');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not necessary, the module is still called q-router!

const uuidv4 = require('uuid/v4');
const ajvFormatsMobileUk = require('ajv-formats-mobile-uk');
const templates = require('./templates');
const questionnaireResource = require('./resources/questionnaire-resource');
const createQuestionnaireHelper = require('./questionnaire/questionnaire');
const isQuestionnaireCompatible = require('./utils/isQuestionnaireVersionCompatible');
const createTaskListService = require('./task-list/task-list-service');
const getProgress = require('./utils/getProgressArray');

const defaults = {};
defaults.createQuestionnaireDAL = require('./questionnaire-dal');
Expand Down Expand Up @@ -51,6 +53,13 @@ function createQuestionnaireService({
await db.updateExpiryForAuthenticatedOwner(questionnaireId, owner);
}

function supportsTaskList(questionnaireDefinition) {
if (questionnaireDefinition.routes.type === 'parallel') {
return true;
}
return false;
}

async function createQuestionnaire(templateName, ownerData, originData, externalData) {
if (!(templateName in templates)) {
throw new VError(
Expand Down Expand Up @@ -99,7 +108,7 @@ function createQuestionnaireService({
}

return {
data: questionnaireResource({questionnaire})
data: questionnaireResource({questionnaire}, supportsTaskList(questionnaire))
};
}

Expand Down Expand Up @@ -184,7 +193,8 @@ function createQuestionnaireService({

async function getAnswers(questionnaireId) {
const questionnaire = await getQuestionnaire(questionnaireId);
const resourceCollection = questionnaire.progress.reduce((acc, sectionAnswersId) => {
const progress = getProgress(questionnaire);
const resourceCollection = progress.reduce((acc, sectionAnswersId) => {
// Does this section have answers
if (questionnaire.answers[sectionAnswersId]) {
acc.push(buildAnswerResource(sectionAnswersId, questionnaire));
Expand All @@ -206,7 +216,7 @@ function createQuestionnaireService({
const questionnaireDefinition = await getQuestionnaire(questionnaireId);

// 2 - is the section allowed to be posted to e.g. is it in their progress
const qRouter = createQRouter(questionnaireDefinition);
const qRouter = router(questionnaireDefinition);
const sectionDetails = getSection(sectionId, qRouter);

// 3 - Section is available. Validate the answers against it
Expand Down Expand Up @@ -238,7 +248,6 @@ function createQuestionnaireService({
// 4 - If we're here all is good
// Pass the answers to the router which will update the context (questionnaire) with these answers.
let answeredQuestionnaire;

if (sectionDetails.id === 'owner') {
const currentSection = qRouter.current();
currentSection.context.answers[sectionDetails.id] = coercedAnswers;
Expand Down Expand Up @@ -341,9 +350,35 @@ function createQuestionnaireService({
};
}

function getSectionRouteBySectionId(questionnaireDefinition, sectionId) {
const questionnaire = createQuestionnaireHelper({questionnaireDefinition});
const taskListService = createTaskListService({questionnaireDefinition});

const section = questionnaire.getSection(sectionId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than using questionnaire.getSection() then section.getSchema() you could just get the schema by using questionnaire.getSectionDefinition().schema

if (taskListService.isTaskListSchema({sectionSchema: section.getSchema()})) {
return {};
}

const {states} = questionnaireDefinition.routes;

if (supportsTaskList(questionnaireDefinition)) {
const tasks = states;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?

const taskIds = Object.keys(tasks);
for (let i = 0; i < taskIds.length; i += 1) {
if (sectionId in tasks[taskIds[i]].states) {
return tasks[taskIds[i]].states[sectionId];
}
}
} else if (sectionId in states) {
return states[sectionId];
}

throw new VError(`Section "${sectionId}" not found`);
}

async function buildMetaBlock(questionnaire, sectionId) {
// TODO: move this meta on to the appropriate section resource
const sectionType = questionnaire.routes.states[sectionId].type;
const sectionType = getSectionRouteBySectionId(questionnaire, sectionId).type;
const isFinalType = sectionType && sectionType === 'final';
return {
summary: questionnaire.routes.summary,
Expand Down Expand Up @@ -404,7 +439,8 @@ function createQuestionnaireService({
};
}
// 2 - get router
const qRouter = createQRouter(questionnaire);
const qRouter = router(questionnaire);

// 3 - filter or paginate progress entries if required
// Currently this only supports queries that return a single progress entry
if (query) {
Expand Down Expand Up @@ -451,6 +487,7 @@ function createQuestionnaireService({

// Is the progress entry available
if (section) {
const taskListService = createTaskListService();
if (isQuestionnaireModified) {
// Store the updated questionnaire object
await db.updateQuestionnaireByOwner(questionnaireId, section.context);
Expand All @@ -459,12 +496,20 @@ function createQuestionnaireService({
sectionId = section.id;

// Create the progress entry compound document
const previousProgressEntryLink =
section.id === section.context.routes.initial
? questionnaire.routes.referrer
: `${process.env.DCS_URL}/api/questionnaires/${
questionnaire.id
}/progress-entries?filter[sectionId]=${qRouter.previous(sectionId).id}`;
let previousProgressEntryLink;
if (
section.id === section.context.routes.initial ||
section.id === section.context.currentSectionId // task list
) {
previousProgressEntryLink = questionnaire.routes.referrer;
// TODO: pass in the sectionSchema, not the sectionId.
} else if (taskListService.isTaskListSchema({sectionId: section.id})) {
previousProgressEntryLink = undefined;
} else {
previousProgressEntryLink = `${process.env.DCS_URL}/api/questionnaires/${
questionnaire.id
}/progress-entries?filter[sectionId]=${qRouter.previous(sectionId).id}`;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine for now, but we should chuck a ToDo in to get rid of this. Would be trivial for q-router to return the correct thing rather than being queried by the DCS


compoundDocument.data = [buildProgressEntryResource(sectionId)];
// Include related resources
Expand Down Expand Up @@ -528,8 +573,9 @@ function createQuestionnaireService({

async function getAnswersBySectionId(questionnaireId, sectionId) {
const questionnaire = await getQuestionnaire(questionnaireId);
const progress = getProgress(questionnaire);

if (questionnaire.progress.includes(sectionId)) {
if (progress.includes(sectionId)) {
return {
data: buildAnswerResource(sectionId, questionnaire)
};
Expand Down
34 changes: 16 additions & 18 deletions questionnaire/questionnaire/questionnaire.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const createTaskListService = require('../task-list/task-list-service');

const defaults = {};
defaults.createSection = require('./section');
defaults.createTaxonomy = require('./taxonomy/taxonomy');
Expand All @@ -11,6 +13,7 @@ defaults.deepClone = require('./utils/deepCloneJsonDerivedObject');
defaults.getJsonExpressionEvaluator = require('./utils/getJsonExpressionEvaluator');
defaults.qExpression = require('q-expressions');
defaults.sortThemedAnswers = require('./utils/sortThemedAnswers');
defaults.getProgressArray = require('../utils/getProgressArray');

function createQuestionnaire({
questionnaireDefinition,
Expand All @@ -23,26 +26,15 @@ function createQuestionnaire({
deepClone = defaults.deepClone,
getJsonExpressionEvaluator = defaults.getJsonExpressionEvaluator,
qExpression = defaults.qExpression,
sortThemedAnswers = defaults.sortThemedAnswers
sortThemedAnswers = defaults.sortThemedAnswers,
getProgressArray = defaults.getProgressArray
}) {
function getId() {
return questionnaireDefinition.id;
}

function getProgress() {
return questionnaireDefinition.progress || [];
}

function getProgressUntil(sectionId) {
const allProgress = getProgress();
const endIndex = allProgress.indexOf(sectionId);

if (endIndex > -1) {
const progressSubset = allProgress.slice(0, endIndex);
return progressSubset;
}

return allProgress;
return getProgressArray(questionnaireDefinition);
}

function getRoles() {
Expand All @@ -54,7 +46,7 @@ function createQuestionnaire({
}

function getOrderedAnswers() {
const progress = getProgress();
const progress = getProgress(questionnaireDefinition);
const answers = getAnswers();
const orderedAnswers = {};

Expand Down Expand Up @@ -148,12 +140,13 @@ function createQuestionnaire({
return transformedData;
}

function evaluateJsonExpression(value, sectionId) {
function evaluateJsonExpression(value /* , sectionId */) {
const fnName = value[0];

if (fnName === 'summary') {
// TODO: handle multiple summary pages e.g. summarise between last summary and this one
const progressSubset = getProgressUntil(sectionId);
// const progressSubset = getProgressUntil(sectionId);
const progressSubset = getProgress(questionnaireDefinition);
const summaryOptions = value[1];

// eslint-disable-next-line no-use-before-define
Expand Down Expand Up @@ -229,12 +222,17 @@ function createQuestionnaire({
}

function getSection(sectionId, allowSummary = true) {
const taskListService = createTaskListService();
const sectionDefinition = getSectionDefinition(sectionId);
const sectionDefinitionVars = getSectionDefinitionVars(sectionDefinition);
const allQuestionnaireAnswers = {answers: getAnswers()};
const orderedValueTransformers = [];
const valueInterpolator = getValueInterpolator(allQuestionnaireAnswers);

if (taskListService.isTaskListSchema({sectionSchema: sectionDefinition.schema})) {
taskListService.updateTaskListSchema(questionnaireDefinition, sectionDefinition);
}

if (sectionDefinition.l10n !== undefined) {
const jsonExpressionEvaluator = getJsonExpressionEvaluator({
...allQuestionnaireAnswers,
Expand Down Expand Up @@ -294,7 +292,7 @@ function createQuestionnaire({
}

function getDataAttributes({
progress = getProgress(),
progress = getProgress(questionnaireDefinition),
dataAttributeTransformer,
includeMetadata = true
} = {}) {
Expand Down
27 changes: 25 additions & 2 deletions questionnaire/questionnaire/utils/sortThemedAnswers/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';

function getDependentQuestionIds(questionId, sortingInstructions) {
const questionOrdering = sortingInstructions[questionId];
const questionOrdering = sortingInstructions.questions
? sortingInstructions.questions[questionId]
: sortingInstructions[questionId];
return questionOrdering || [];
}

Expand Down Expand Up @@ -34,4 +36,25 @@ function sortThemedAnswers(themeContent, sortingInstructions) {
return themeContent;
}

module.exports = sortThemedAnswers;
function sortThemes(themeContent, sortingInstructions) {
const themesWithAnswers = sortThemedAnswers(themeContent, sortingInstructions);
if (!sortingInstructions.sections) {
return themesWithAnswers;
}
const orderMap = new Map();

sortingInstructions.sections.forEach((id, index) => {
orderMap.set(id, index);
});

return themeContent.sort((a, b) => {
const orderA = orderMap.get(a.id);
const orderB = orderMap.get(b.id);
return (
(orderA !== undefined ? orderA : Number.MAX_VALUE) -
(orderB !== undefined ? orderB : Number.MAX_VALUE)
);
});
}

module.exports = sortThemes;
Loading