Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
Binary file added .github/cht-11192-e2e-verification.jpg

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's not include this image in the PR. It can stay on the PR body.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 15 additions & 11 deletions admin/src/js/services/message-queue.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const lineageFactory = require('@medic/lineage');
const messageUtils = require('@medic/message-utils');
const registrationUtils = require('@medic/registration-utils');
const extensionLibs = require('@medic/extension-libs');
const constants = require('@medic/constants');
const DOC_TYPES = constants.DOC_TYPES;

Expand All @@ -15,6 +16,7 @@ angular.module('services').factory('MessageQueueUtils',

return {
lineage: lineageFactory($q, DB({ remote: true })),
loadExtensionLibs: () => extensionLibs.load(DB({ remote: true })),
messages: messageUtils,
registrations: registrationUtils
};
Expand Down Expand Up @@ -47,7 +49,7 @@ angular.module('services').factory('MessageQueue',

const findIdByKey = (contactsByReference, key) => {
const row = contactsByReference.rows.find((row) => row.key[1] === key);
return row && row.id;
return row && row.id; // NOSONAR: browserify-ngannotate does not support optional chaining.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

misplaced comment?

};

const findPatientUuid = (contactsByReference, message) => {
Expand Down Expand Up @@ -184,7 +186,7 @@ angular.module('services').factory('MessageQueue',
});
};

const generateScheduledMessages = function(messages, settings) {
const generateScheduledMessages = function(messages, settings, extensionLibs) {
const translate = function(key, locale) {
return $translate.instant(key, null, 'no-interpolation', locale, null);
};
Expand All @@ -199,14 +201,15 @@ angular.module('services').factory('MessageQueue',
message: message.scheduled_sms.content
};

message.sms = MessageQueueUtils.messages.generate(
settings,
message.sms = MessageQueueUtils.messages.generate({
config: settings,
translate,
message.doc,
doc: message.doc,
content,
message.scheduled_sms.recipient,
message.context
)[0];
recipient: message.scheduled_sms.recipient,
extraContext: message.context,
extensionLibs,
})[0];
});

return messages;
Expand Down Expand Up @@ -314,9 +317,10 @@ angular.module('services').factory('MessageQueue',
.all([
Settings(),
DB({ remote: true }).query('medic-admin/message_queue', params.list),
DB({ remote: true }).query('medic-admin/message_queue', params.count)
DB({ remote: true }).query('medic-admin/message_queue', params.count),
MessageQueueUtils.loadExtensionLibs(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Admin message queue hard-fails if extension-libs can't load.

API and Sentinel tolerate load failures but admin's query() rejects entirely. A transient failure fetching the doc shouldn't take down the whole message-queue tab.

Suggestion: catch and fall back to {}, matching the behavior everywhere else.

])
.then(([settings, messagesList, messagesCount]) => {
.then(([settings, messagesList, messagesCount, extensionLibs]) => {
const messages = messagesList.rows.map((row) => {
const extras = {
doc: row.doc,
Expand All @@ -331,7 +335,7 @@ angular.module('services').factory('MessageQueue',

return getSubjectsAndRegistrations(messages, settings)
.then(hydrateContacts)
.then((messages) => generateScheduledMessages(messages, settings))
.then((messages) => generateScheduledMessages(messages, settings, extensionLibs))
.then(getRecipients)
.then((messages) => {
return {
Expand Down
36 changes: 26 additions & 10 deletions admin/tests/unit/services/message-queue.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('MessageQueue service', function() {
translate.preferredLanguage = sinon.stub();
clock = sinon.useFakeTimers();
utils = {
loadExtensionLibs: sinon.stub().resolves({}),
messages: {
generate: sinon.stub()
},
Expand Down Expand Up @@ -92,13 +93,24 @@ describe('MessageQueue service', function() {

return service.query('tab', 10, 5, false).then(result => {
chai.expect(result).to.deep.equal({ messages: [], total: 0 });
chai.expect(utils.loadExtensionLibs.calledOnceWithExactly()).to.equal(true);
chai.expect(query.callCount).to.equal(2);
chai.expect(query.args[0])
.to.deep.equal(['medic-admin/message_queue', { limit: 5, skip: 10, reduce: false, include_docs: true }]);
chai.expect(query.args[1]).to.deep.equal(['medic-admin/message_queue', { reduce: true, group_level: 1 }]);
});
});

it('rejects when extension-libs cannot be loaded', () => {
const error = new Error('extension-libs unavailable');
utils.loadExtensionLibs.rejects(error);
query.resolves({ rows: [] });

return service.query('tab')
.then(() => chai.expect.fail('Expected query to reject'))
.catch(err => chai.expect(err).to.equal(error));
});

it('should query the message_queue view with correct default params', () => {
query
.withArgs('medic-admin/message_queue', sinon.match({ reduce: false })).resolves({ rows: [] })
Expand Down Expand Up @@ -298,7 +310,7 @@ describe('MessageQueue service', function() {

translate.instant.withArgs('task1').returns('task 1 translation');

utils.messages.generate.callsFake((settings, translate, doc, content, recipient) => ([{
utils.messages.generate.callsFake(({ doc, content, recipient }) => ([{
message: content.translationKey || content.message,
to: recipient,
error: doc.error
Expand Down Expand Up @@ -579,7 +591,7 @@ describe('MessageQueue service', function() {
]);
utils.lineage.fillParentsInDocs.callsFake(doc => doc);

utils.messages.generate.callsFake((settings, translate, doc, content, recipient) => ([{
utils.messages.generate.callsFake(({ content, recipient }) => ([{
message: content.translation_key,
to: recipient
}]));
Expand Down Expand Up @@ -740,7 +752,7 @@ describe('MessageQueue service', function() {
]);
utils.lineage.fillParentsInDocs.callsFake(doc => doc);

utils.messages.generate.callsFake((settings, translate, doc, content, recipient) => ([{
utils.messages.generate.callsFake(({ content, recipient }) => ([{
message: content.translationKey || content.message,
to: recipient
}]));
Expand All @@ -765,8 +777,12 @@ describe('MessageQueue service', function() {
return service.query('tab').then((result) => {
chai.expect(utils.registrations.isValidRegistration.callCount).to.equal(13);
chai.expect(utils.messages.generate.callCount).to.equal(6);
const getGenerateArgs = index => {
const options = utils.messages.generate.args[index][0];
return [ options.doc, options.content, options.recipient, options.extraContext ];
};

chai.expect(utils.messages.generate.args[0].slice(2)).to.deep.equal([
chai.expect(getGenerateArgs(0)).to.deep.equal([
{
_id: 'report_id1',
reported_date: 100,
Expand Down Expand Up @@ -794,7 +810,7 @@ describe('MessageQueue service', function() {
}
]);

chai.expect(utils.messages.generate.args[1].slice(2)).to.deep.equal([
chai.expect(getGenerateArgs(1)).to.deep.equal([
{
_id: 'report_id2',
reported_date: 200,
Expand All @@ -818,7 +834,7 @@ describe('MessageQueue service', function() {
}
]);

chai.expect(utils.messages.generate.args[2].slice(2)).to.deep.equal([
chai.expect(getGenerateArgs(2)).to.deep.equal([
{
_id: 'report_id3',
reported_date: 200,
Expand All @@ -842,7 +858,7 @@ describe('MessageQueue service', function() {
}
]);

chai.expect(utils.messages.generate.args[3].slice(2)).to.deep.equal([
chai.expect(getGenerateArgs(3)).to.deep.equal([
{
_id: 'report_id4',
reported_date: 200,
Expand All @@ -868,7 +884,7 @@ describe('MessageQueue service', function() {
}
]);

chai.expect(utils.messages.generate.args[4].slice(2)).to.deep.equal([
chai.expect(getGenerateArgs(4)).to.deep.equal([
{
_id: 'report_id5',
reported_date: 200,
Expand Down Expand Up @@ -897,7 +913,7 @@ describe('MessageQueue service', function() {
}
]);

chai.expect(utils.messages.generate.args[5].slice(2)).to.deep.equal([
chai.expect(getGenerateArgs(5)).to.deep.equal([
{
_id: 'report_id6',
reported_date: 200,
Expand Down Expand Up @@ -1059,7 +1075,7 @@ describe('MessageQueue service', function() {
]);
utils.lineage.fillParentsInDocs.callsFake(doc => doc);

utils.messages.generate.callsFake((settings, translate, doc, content, recipient) => ([{
utils.messages.generate.callsFake(({ doc, content, recipient }) => ([{
message: content.translationKey || content.message,
to: recipient,
error: doc.error
Expand Down
3 changes: 3 additions & 0 deletions api/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const _ = require('lodash');
let translationCache = {};
let settings = {};
let transitionsLib;
let extensionLibs = {};

_.templateSettings.interpolate = /\{\{(.+?)\}\}/g;

Expand Down Expand Up @@ -52,9 +53,11 @@ module.exports = {
set: (newSettings) => settings = newSettings,
setTranslationCache: (newTranslations) => translationCache = newTranslations,
setTransitionsLib: (newTransitionsLib) => transitionsLib = newTransitionsLib,
setExtensionLibs: (newExtensionLibs) => extensionLibs = newExtensionLibs,

get: key => (key ? settings[key] : settings),
getAll: () => settings,
getExtensionLibs: () => extensionLibs,
getTranslations: keys => {
if (!keys) {
return translationCache;
Expand Down
19 changes: 19 additions & 0 deletions api/src/services/config-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const config = require('../config');
const environment = require('@medic/environment');
const dataContext = require('./data-context');
const deployInfo = require('./deploy-info');
const extensionLibs = require('@medic/extension-libs');

const MEDIC_DDOC_ID = '_design/medic';

Expand Down Expand Up @@ -76,6 +77,13 @@ const loadSettings = () => {
.then(settings => config.set(settings));
};

const loadExtensionLibs = () => extensionLibs
.load(db.medic)
.then(config.setExtensionLibs)
.catch(err => {
logger.error('Error loading extension libs - starting up anyway: %o', err);
});


const handleDdocChange = () => {
logger.info('Detected ddoc change - reloading');
Expand Down Expand Up @@ -110,6 +118,11 @@ const handleTranslationsChange = () => {
.then(() => updateServiceWorker());
};

const handleExtensionLibsChange = () => {
logger.info('Detected extension-libs change - reloading');
return loadExtensionLibs();
};

const handleFormChange = (change) => {
logger.info('Detected form change for %s', change.id);
if (change.deleted) {
Expand Down Expand Up @@ -142,6 +155,7 @@ const updateServiceWorker = () => {
const load = () => {
loadViewMaps();
return loadTranslations()
.then(() => loadExtensionLibs())
.then(() => loadSettings())
.then(() => addUserRolesToDb())
.then(() => initTransitionLib())
Expand All @@ -168,6 +182,10 @@ const listen = () => {
return handleTranslationsChange();
}

if (change.id === DOC_IDS.EXTENSION_LIBS) {
return handleExtensionLibsChange();
}

if (change.id.startsWith('form:')) {
return handleFormChange(change);
}
Expand Down Expand Up @@ -198,6 +216,7 @@ module.exports = {
listen,
updateServiceWorker,
loadTranslations,
loadExtensionLibs,
watch,
addUserRolesToDb,
};
15 changes: 8 additions & 7 deletions api/src/services/export/message-mapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,15 @@ module.exports = {
translationKey: task.message_key,
message: task.message
};
task.messages = messageUtils.generate(
config.get(),
config.translate,
record,
task.messages = messageUtils.generate({
config: config.get(),
translate: config.translate,
doc: record,
content,
task.recipient,
context
);
recipient: task.recipient,
extraContext: context,
extensionLibs: config.getExtensionLibs(),
});
}

const taskType = (task.translation_key && config.translate(task.translation_key, { group: task.group })) ||
Expand Down
18 changes: 18 additions & 0 deletions api/tests/mocha/services/config-watcher.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const bootstrap = require('../../../src/services/config-watcher');
const manifest = require('../../../src/services/manifest');
const environment = require('@medic/environment');
const deployInfo = require('../../../src/services/deploy-info');
const extensionLibs = require('@medic/extension-libs');

describe('Configuration', () => {
beforeEach(() => {
Expand All @@ -29,10 +30,12 @@ describe('Configuration', () => {
sinon.spy(config, 'set');
sinon.spy(config, 'setTranslationCache');
sinon.spy(config, 'setTransitionsLib');
sinon.spy(config, 'setExtensionLibs');
sinon.stub(fs, 'watch');
sinon.stub(translations, 'getTranslationDocs');
sinon.stub(dbWatcher, 'listen');
sinon.stub(deployInfo, 'store');
sinon.stub(extensionLibs, 'load').resolves({});
});

afterEach(() => {
Expand All @@ -58,6 +61,7 @@ describe('Configuration', () => {
['docs_by_replication_key'],
]);
chai.expect(translations.getTranslationDocs.callCount).to.equal(1);
chai.expect(extensionLibs.load.calledOnceWithExactly(db.medic)).to.equal(true);

chai.expect(settingsService.update.callCount).to.equal(1);
chai.expect(settingsService.get.callCount).to.equal(1);
Expand Down Expand Up @@ -190,6 +194,20 @@ describe('Configuration', () => {
});
});

describe('extension-libs changes', () => {
it('reloads extension-libs', () => {
return dbWatcher.medic.args[0][0]({ id: DOC_IDS.EXTENSION_LIBS }).then(() => {
chai.expect(extensionLibs.load.calledOnceWithExactly(db.medic)).to.equal(true);
});
});

it('continues when extension-libs cannot be loaded', () => {
extensionLibs.load.rejects(new Error('failed to load'));

return dbWatcher.medic.args[0][0]({ id: DOC_IDS.EXTENSION_LIBS });
});
});

describe('branding changes', () => {
it('generates service worker when branding doc is updated', () => {
manifest.generate.resolves();
Expand Down
20 changes: 11 additions & 9 deletions api/tests/mocha/services/export/message-mapper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('Message mapper', () => {
sinon.stub(messageUtils, 'generate');
sinon.stub(registrationUtils, 'isValidRegistration');
sinon.stub(config, 'get');
sinon.stub(config, 'getExtensionLibs').returns({});
sinon.stub(config, 'translate');
});

Expand Down Expand Up @@ -424,20 +425,21 @@ describe('Message mapper', () => {
chai.expect(config.translate.args[0]).to.deep.equal(['task-translation-key', { group: 'gr' }]);

chai.expect(messageUtils.generate.callCount).to.deep.equal(1);
chai.expect(messageUtils.generate.args[0]).to.deep.equal([
{ config: 1 },
config.translate,
record,
{
chai.expect(messageUtils.generate.args[0]).to.deep.equal([{
config: { config: 1 },
translate: config.translate,
doc: record,
content: {
translationKey: 'alpha',
message: undefined
},
'random recipient',
{
recipient: 'random recipient',
extraContext: {
patient: record.patient,
registrations: record.registrations
}
]);
},
extensionLibs: {},
}]);

chai.expect(result.length).to.equal(2);
chai.expect(result[0]).to.deep.equal([
Expand Down
Loading