Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for cht-core v3.12 and the cht-script-api #151

Merged
merged 18 commits into from
Jan 3, 2022
Merged
Show file tree
Hide file tree
Changes from 15 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
10 changes: 10 additions & 0 deletions all-chts-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,14 @@ module.exports = {
nootils: require('cht-core-3-11/shared-libs/rules-engine/node_modules/medic-nootils'),
Lineage: require('cht-core-3-11/shared-libs/lineage'),
},
'3.12': {
ddocs: require('./build/cht-core-3-12-ddocs.json'),
RegistrationUtils: require('cht-core-3-12/shared-libs/registration-utils'),
CalendarInterval: require('cht-core-3-12/shared-libs/calendar-interval'),
RulesEngineCore: require('cht-core-3-12/shared-libs/rules-engine'),
RulesEmitter: require('cht-core-3-12/shared-libs/rules-engine/src/rules-emitter'),
nootils: require('cht-core-3-12/shared-libs/rules-engine/node_modules/medic-nootils'),
Lineage: require('cht-core-3-12/shared-libs/lineage'),
ChtScriptApi: require('cht-core-3-12/shared-libs/cht-script-api'),
},
};
23,821 changes: 23,818 additions & 3 deletions dist/all-chts-bundle.dev.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/all-chts-bundle.dev.js.map

Large diffs are not rendered by default.

47 changes: 39 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"chai-exclude": "^2.0.2",
"cht-core-3-10": "git+https://github.com/medic/cht-core.git#3.10.x",
"cht-core-3-11": "git+https://github.com/medic/cht-core.git#3.11.x",
"cht-core-3-12": "git+https://github.com/medic/cht-core.git#3.12.x",
"cht-core-3-9": "git+https://github.com/medic/cht-core.git#3.9.x",
"couchdb-compile": "^1.11.0",
"enketo-core": "4.41.6",
Expand All @@ -40,6 +41,7 @@
"openrosa-xpath-evaluator": "^1.5.1",
"raw-loader": "^1.0.0",
"rewire": "^4.0.1",
"semver": "^7.3.5",
"underscore": "^1.10.2",
"webpack": "^5.37.0",
"webpack-cli": "^3.3.0"
Expand Down
51 changes: 35 additions & 16 deletions src/core-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ class CoreAdapter {
this.lineageLib = core.Lineage(Promise, this.pouchdb);
}

async fetchTargets(user, state) {
this.pouchdbStateHash = await prepare(this.core, this.rulesEngine, this.appSettings, this.pouchdb, this.pouchdbStateHash, user, state);
async fetchTargets(user, userRoles, state) {
this.pouchdbStateHash = await prepare(this.core, this.rulesEngine, this.appSettings, this.pouchdb, this.pouchdbStateHash, user, userRoles, state);

const uhcMonthStartDate = getMonthStartDate(this.appSettings);
const relevantInterval = this.core.CalendarInterval.getCurrent(uhcMonthStartDate);
return this.rulesEngine.fetchTargets(relevantInterval);
}

async fetchTasksFor(user, state) {
this.pouchdbStateHash = await prepare(this.core, this.rulesEngine, this.appSettings, this.pouchdb, this.pouchdbStateHash, user, state);
async fetchTasksFor(user, userRoles, state) {
this.pouchdbStateHash = await prepare(this.core, this.rulesEngine, this.appSettings, this.pouchdb, this.pouchdbStateHash, user, userRoles, state);
return this.rulesEngine.fetchTasksFor();
}

Expand Down Expand Up @@ -70,15 +70,16 @@ class CoreAdapter {
}
}

const prepare = async (chtCore, rulesEngine, appSettings, pouchdb, pouchdbStateHash, user, state) => {
await prepareRulesEngine(chtCore, rulesEngine, appSettings, user, pouchdb.name);
const prepare = async (chtCore, rulesEngine, appSettings, pouchdb, pouchdbStateHash, user, userRoles, state) => {
await prepareRulesEngine(chtCore, rulesEngine, appSettings, user, userRoles, pouchdb.name);
const { updatedSubjectIds, newPouchdbState } = await syncPouchWithState(chtCore, pouchdb, pouchdbStateHash, state);
await rulesEngine.updateEmissionsFor(updatedSubjectIds);
return newPouchdbState;
};

const prepareRulesEngine = async (chtCore, rulesEngine, appSettings, user, sessionId) => {
const rulesSettings = getRulesSettings(appSettings, user, sessionId);
const prepareRulesEngine = async (chtCore, rulesEngine, appSettings, user, userRoles, sessionId) => {
const rulesSettings = getRulesSettings(appSettings, user, userRoles, sessionId, chtCore.ChtScriptApi);

if (!rulesEngine.isEnabled()) {
await rulesEngine.initialize(rulesSettings);
} else {
Expand All @@ -92,11 +93,7 @@ const prepareRulesEngine = async (chtCore, rulesEngine, appSettings, user, sessi
*/
if (chtCore.RulesEmitter.isEnabled()) {
chtCore.RulesEmitter.shutdown();
chtCore.RulesEmitter.initialize({
rules: appSettings.tasks.rules,
contact: user,
taskSchedules: rulesSettings.taskSchedules
});
chtCore.RulesEmitter.initialize(rulesSettings);
kennsippell marked this conversation as resolved.
Show resolved Hide resolved
}
};

Expand Down Expand Up @@ -177,22 +174,44 @@ const getMonthStartDate = settings => {
);
};

const getRulesSettings = (settingsDoc, userContactDoc, sessionId) => {
// cht-core/src/ts/services/cht-script-api.service.ts
const chtScriptApiWithDefaults = (chtScriptApi, settingsDoc, defaultUserRoles) => {
if (!chtScriptApi) {
return;
}

const defaultChtPermissionSettings = settingsDoc.permissions;
return {
v1: {
hasPermissions: (permissions, userRoles = defaultUserRoles, chtPermissionsSettings = defaultChtPermissionSettings) => {
return chtScriptApi.v1.hasPermissions(permissions, userRoles, chtPermissionsSettings);
},
hasAnyPermission: (permissionsGroupList, userRoles = defaultUserRoles, chtPermissionsSettings = defaultChtPermissionSettings) => {
return chtScriptApi.v1.hasAnyPermission(permissionsGroupList, userRoles, chtPermissionsSettings);
}
}
};
};

const getRulesSettings = (settingsDoc, userContactDoc, userRoles, sessionId, chtScriptApi) => {
const settingsTasks = settingsDoc && settingsDoc.tasks || {};
// https://github.com/medic/cht-conf-test-harness/issues/106
// const filterTargetByContext = (target) => target.context ? !!this.parseProvider.parse(target.context)({ user: userContactDoc }) : true;
const targets = settingsTasks.targets && settingsTasks.targets.items || [];

return {
rules: settingsTasks.rules,
taskSchedules: settingsTasks.schedules,
targets: targets,
enableTasks: true,
enableTargets: true,
contact: userContactDoc, // <- this goes to rules emitter
user: { _id: `org.couchdb.user:${userContactDoc ? userContactDoc._id : 'default'}` },
user: {
_id: `org.couchdb.user:${userContactDoc ? userContactDoc._id : 'default'}`,
roles: userRoles,
},
monthStartDate: getMonthStartDate(settingsDoc),
sessionId,
chtScriptApi: chtScriptApiWithDefaults(chtScriptApi, settingsDoc, userRoles),
};
};

Expand Down
4 changes: 2 additions & 2 deletions src/dev-mode/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ module.exports = {
stubbedNoolsLib.pathToProject = pathToProject;
if (!core.RulesEmitter.isMock) {
console.warn('******************************************');
console.warn('**** cht-conf-test-harness dev mode ****');
console.warn('***** cht-conf-test-harness dev mode *****');
console.warn('******************************************');
Object.assign(core.RulesEmitter, devRulesEmitter(core));
}
},
}
};
4 changes: 3 additions & 1 deletion src/dev-mode/mock.cht-conf.nools-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
* @module mock.cht-conf.nools-lib
*/
module.exports.pathToProject = undefined;
module.exports = function(c, user, Utils, Task, Target, emit) {
module.exports = function(c, user, Utils, chtScriptApi, Task, Target, emit) {
const cacheBefore = Object.keys(require.cache);
try {
global.Utils = Utils;
global.user = user;
global.cht = chtScriptApi;

const tasks = require(`${module.exports.pathToProject}/tasks.js`);
const targets = require(`${module.exports.pathToProject}/targets.js`);
Expand All @@ -25,6 +26,7 @@ module.exports = function(c, user, Utils, Task, Target, emit) {
} finally {
delete global.Utils;
delete global.user;
delete global.cht;

const cacheAfter = Object.keys(require.cache).filter(key => !cacheBefore.includes(key));
cacheAfter.forEach(key => { delete require.cache[key]; });
Expand Down
4 changes: 3 additions & 1 deletion src/dev-mode/mock.rules-engine.rules-emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const mockNoolsLib = require('./mock.cht-conf.nools-lib');

let enabled = false;
let Utils;
let chtScriptApi;
let user;

module.exports = chtCore => {
Expand All @@ -36,6 +37,7 @@ module.exports = chtCore => {
const settingsDoc = { tasks: { schedules: settings.taskSchedules } };
Utils = chtCore.nootils(settingsDoc);
user = settings.contact;
chtScriptApi = settings.chtScriptApi;

return true;
},
Expand Down Expand Up @@ -81,7 +83,7 @@ module.exports = chtCore => {
};

for (const container of containers) {
mockNoolsLib(container, user, Utils, Task, Target, emitCallback);
mockNoolsLib(container, user, Utils, chtScriptApi, Task, Target, emitCallback);
}

return Promise.resolve(results);
Expand Down
19 changes: 17 additions & 2 deletions src/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class Harness {
* @param {string} [options.harnessDataPath=path.join(options.directory, 'harness.defaults.json')] Path to harness configuration file
* @param {string} [options.coreVersion=harness configuration file] The version of cht-core to emulate @example "3.8.0"
* @param {string} [options.user=harness configuration file] The default {@link HarnessInputs} controlling the environment in which your application is running
* @param {string} [options.userRoles=harness configuration file] The default {@link HarnessInputs} controlling the environment in which your application is running
* @param {string} [options.subject=harness configuration file] The default {@link HarnessInputs} controlling the environment in which your application is running
* @param {Object} [options.content=harness configuration file] The default {@link HarnessInputs} controlling the environment in which your application is running
* @param {Object} [options.contactSummary=harness configuration file] The default {@link HarnessInputs} controlling the environment in which your application is running
Expand Down Expand Up @@ -84,6 +85,7 @@ class Harness {
{
subject: 'default_subject',
user: 'default_user',
userRoles: ['default_role'],
content: { source: 'action' },
docs: [
{ _id: 'default_user', type: 'contact' },
Expand Down Expand Up @@ -321,6 +323,7 @@ class Harness {
* @param {Object=} options Some options when checking for tasks
* @param {string} [options.title=undefined] Filter the returns tasks to those with attribute `title` equal to this value. Filter is skipped if undefined.
* @param {Object} [options.user=Default specified via constructor] The current logged-in user which is viewing the tasks.
* @param {Object} [options.userRoles=Default specified via constructor] The roles associated with the current logged-in user which is viewing the tasks.
* @param {string} [options.actionForm] Filter task documents to only those whose action opens the form equal to this parameter. Filter is skipped if undefined.
* @param {boolean} [options.ownedBySubject] Filter task documents to only those owned by the subject. Filter is skipped if false.
*
Expand All @@ -330,6 +333,7 @@ class Harness {
options = _.defaults(options, {
subject: this.options.subject,
user: this.options.user,
userRoles: this.options.userRoles,
actionForm: this.options.actionForm,
ownedBySubject: this.options.ownedBySubject,
title: undefined,
Expand All @@ -345,7 +349,7 @@ class Harness {

const user = await resolveMock(this.coreAdapter, this.state, options.user);
const subject = await resolveMock(this.coreAdapter, this.state, options.subject, { hydrate: false });
const tasks = await this.coreAdapter.fetchTasksFor(user, stateEnsuringPresenceOfMocks(this.state, user, subject));
const tasks = await this.coreAdapter.fetchTasksFor(user, options.userRoles, stateEnsuringPresenceOfMocks(this.state, user, subject));

tasks.forEach(task => task.emission.actions.forEach(action => {
action.forId = task.emission.forId; // required to hydrate contact in loadAction()
Expand All @@ -359,6 +363,8 @@ class Harness {
*
* @param {Object=} options Some options when summarizing the tasks
* @param {string} [options.title=undefined] Filter task documents counted to only those with emitted `title` equal to this parameter. Filter is skipped if undefined.
* @param {Object} [options.user=Default specified via constructor] The current logged-in user which is viewing the tasks.
* @param {Object} [options.userRoles=Default specified via constructor] The roles associated with the current logged-in user which is viewing the tasks.
* @param {string} [options.actionForm] Filter task documents counted to only those whose action opens the form equal to this parameter. Filter is skipped if undefined.
* @param {boolean} [options.ownedBySubject] Filter task documents counted to only those owned by the subject. Filter is skipped if false.
*
Expand Down Expand Up @@ -405,6 +411,8 @@ class Harness {
* Check the state of targets
* @param {Object=} options Some options for looking for checking for targets
* @param {string|string[]} [options.type=undefined] Filter the returns targets to those with an `id` which matches type (when string) or is included in type (when Array).
* @param {Object} [options.user=Default specified via constructor] The current logged-in user which is viewing the tasks.
* @param {Object} [options.userRoles=Default specified via constructor] The roles associated with the current logged-in user which is viewing the tasks.
*
* @returns {Target[]} An array of targets which would be visible to the user
*/
Expand All @@ -413,6 +421,7 @@ class Harness {
type: undefined,
subject: this.options.subject,
user: this.options.user,
userRoles: this.options.userRoles,
});

if (options.now) {
Expand All @@ -421,7 +430,7 @@ class Harness {

const user = await resolveMock(this.coreAdapter, this.state, options.user);
const subject = await resolveMock(this.coreAdapter, this.state, options.subject, { hydrate: false });
const targets = await this.coreAdapter.fetchTargets(user, stateEnsuringPresenceOfMocks(this.state, user, subject));
const targets = await this.coreAdapter.fetchTargets(user, options.userRoles, stateEnsuringPresenceOfMocks(this.state, user, subject));

return targets
.filter(target =>
Expand Down Expand Up @@ -516,6 +525,12 @@ class Harness {
}
set user(value) { this.options.user = value; }

/**
* `userRoles` from the {@link HarnessInputs} set through the constructor (defaulting to values from harness.defaults.json file)
*/
get userRoles() { return this.options.userRoles; }
set userRoles(value) { this.options.userRoles = value; }

/**
* `coreVersion` is the version of the cht-core that is being emulated in testing (eg. 3.9.0)
*/
Expand Down
4 changes: 4 additions & 0 deletions src/jsdocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
* In harness.getTargets(), this is the global `user` object available in targets.js. (hydrated)
* In harness.getTasks(), this is the global `user` object available in tasks.js. (hydrated)
* In contact-summary code, this is the global `user` object in contact-summary.templated.js. (hydrated)
* @see userRoles For setting the user's role
*
* @property {Array<string>} userRoles This represents the 'roles' assigned to the current user that is logged in. Roles control the user's permissions ([roles documentation](https://docs.communityhealthtoolkit.org/apps/concepts/users/#roles))
* @example harness.userRoles = ['chw']
*
* @property {string|Object} subject This represents the contact that is being "acted on" or the "subject of the test".
* The harness.fillForm() function simulates "completing an action" on the subject's profile page.
* The harness.getTasks() function returns the tasks listed on the subject's profile page.
Expand Down
3 changes: 2 additions & 1 deletion test/app-forms.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ const path = require('path');
const Harness = require('../src/harness');

const harness = new Harness({
directory: path.join(__dirname, 'collateral'),
directory: path.join(__dirname, 'collateral', 'project-without-source'),
xformFolderPath: path.join(__dirname, 'collateral', 'forms'),
harnessDataPath: path.join(__dirname, 'collateral', 'harness.defaults.json'),
verbose: false,
reportFormErrors: false
});
Expand Down
3 changes: 2 additions & 1 deletion test/collateral/harness.defaults.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"coreVersion": "3.10.3",
"coreVersion": "3.12",
"user": "chw_area_contact_id",
"userRoles": ["chw_role", "role_dne"],
"subject": "patient_id",
"useDevMode": false,
"ownedBySubject": true,
Expand Down
Loading