Building > Features > Tasks
Ensuring that the right actions are taken for the right people at the right time
Task generation is configured in the tasks.js
file. This file is a JavaScript module which defines an array of objects conforming to the Task schema detailed below. When defining tasks, all the data about contacts on the device (both people and places) along with all their reports are available. Tasks are available only for users of type “restricted to their place”. Tasks can pull in fields from reports and pass data as inputs to the form that opens when the task is selected, enabling richer user experiences.
Task generation occurs on the client periodically and creates documents which track the status of the task over time. To avoid performance issues the developer needs to be conscious about generating too many tasks. For example, to remind a user to do something every day, you could generate one task for each day and fill up the user’s device. The recommended approach is to only generate the tasks for the near future, or only once the previous task is resolved. To limit the impact of this misconfiguration, the CHT will only generate tasks that can be completed between 60 days in the past, and (as of 4.0.0) 180 days in the future.
See Also: Tasks Overview
tasks.js
property | type | description | required |
---|---|---|---|
name | string | A unique identifier for the task. Used for querying task completeness. | yes, unique |
icon | string | The icon to show alongside the task. Should correspond with a value defined in resources.json . | no |
title | translation key | The title of the task (labeled above). | yes |
appliesTo | 'contacts' or 'reports' | Do you want to emit one task per report, or one task per contact? See Understanding the Parameters in the Task Schema. | yes |
appliesIf | function(contact, report) | If appliesTo: 'contacts' , this function is invoked once per contact and report is undefined. If appliesTo: 'reports' , this function is invoked once per report. Return true if the task should appear for the given documents. See Understanding the Parameters in the Task Schema. | no |
appliesToType | string[] | Filters the contacts or reports for which appliesIf will be evaluated. If appliesTo: 'reports' , this is an array of form codes. If appliesTo: 'contacts' , this is an array of contact types. For example, ['person'] or ['clinic', 'health_center'] . For example, ['pregnancy'] or ['P', 'pregnancy'] . See Understanding the Parameters in the Task Schema. | no |
contactLabel | string or function(contact, report) | Controls the label describing the subject of the task. Defaults to the name of the contact (contact.contact.name ). | no |
resolvedIf | function(contact, report, event, dueDate) | Return true to mark the task as “resolved”. A resolved task uses memory on the phone, but is not displayed. | no, if any actions[n].type is 'report' |
events | object[] | An event is used to specify the timing of the task. | yes |
events[n].id | string | A descriptive identifier. Used for querying task completeness. | yes if task has multiple events, unique |
events[n].days | integer | Number of days after the doc’s reported_date that the event is due | yes, if dueDate is not set |
events[n].dueDate | function(event, contact, report) | Returns a Date object for the day when this event is due. | yes, if days is not set |
events[n].start | integer | Number of days to show the task before it is due. | yes |
events[n].end | integer | Number of days to show the task after it is due. | yes |
actions | object[] | The actions (forms) that a user can access after clicking on a task. If you put multiple forms here, the user will see a task summary screen where they can select which action they would like to complete. | yes |
actions[n].type | 'report' or 'contact' | When 'report' , the action opens the given form. When 'contact' , the action redirects to a contact’s profile page. Defaults to ‘report’. | no |
actions[n].form | string | The code of the form that should open when you select the action. | yes, if actions[n].type is 'report' |
actions[n].label | translation key | The label that should appear on the task summary screen if multiple actions are present. | no |
actions[n].modifyContent | function (content, contact, report, event) | Set the values on the content object to control the data which will be passed as inputs to the form which opens when the action is selected. See Passing data from a task into the app form. | no |
priority | object or function(contact, report) returning object of same schema | Controls the “high risk” line seen above. | no |
priority.level | high or medium | Tasks that are high will display a high risk icon with the task. Default: medium . | no |
priority.label | translation key | Text shown with the task associated to the risk level. | no |
Utility functions in the Core Framework can make common tasks much easier. These are available only for Tasks and Targets. To use the function call Utils.<function-name>(<params>)
, for example Utils.addDate(report.reported_date, 10)
.
Name | Description |
---|---|
isTimely(date, event) | Returns true if the given date is after the start date and before the end date of the event. |
addDate(date, days) | Returns a new Date set to midnight the given number of days after the given date. If no date is given the date defaults to today. |
getLmpDate(doc) | Attempts to work out the LMP from the given doc. If no LMP is given it defaults to four weeks before the reported_date. |
getSchedule(name) | Returns the task schedule with the given name from the configuration. |
getMostRecentTimestamp(reports, form) | Returns the reported_date of the most recent of the reports with form ID matching the given form. |
getMostRecentReport(reports, form) | Like getMostRecentTimestamp but returns the report, not just the reported_date. From CHT v3.14.0, it also accepts an array of forms. |
isFormSubmittedInWindow(reports, form, start, end) | Returns true if any of the given reports are for the given form and were reported after start and before end. |
isFirstReportNewer(firstReport, secondReport) | Returns true if the firstReport was reported before the secondReport. |
isDateValid(date) | Returns true if the given date is a validate JavaScript Date. |
now() | Returns the current Date. |
getField(report, fieldPath) | Returns the value of the specified fieldPath. The fieldPath is a period separated json path. |
MS_IN_DAY | A constant for the number of milliseconds in a day. |
Please open an issue if you’d like other functions included.
Introduced in v3.12.0
Provides CHT-Core Framework’s functions to contact summary, targets and tasks. The API is available in the cht
reserved variable under the v1
version.
Function | Arguments | Description |
---|---|---|
hasPermissions(permissions, userRoles, chtPermissionsSettings) | permissions : String or array of permission name(s).userRoles : (Optional) Array of user roles. Default to the current logged in user.chtPermissionsSettings : (Optional) Object of configured permissions in CHT-Core’s settings. Default to the current instance’s configured permissions. | Returns true if the user has the permission(s), otherwise returns false. |
hasAnyPermission(permissionsGroups, userRoles, chtPermissionsSettings) | permissionsGroups : Array of groups of permission name(s).userRoles : (Optional) Array of user roles. Default to the current logged in user.chtPermissionsSettings : (Optional) Object of configured permissions in CHT-Core’s settings. Default to the current instance’s configured permissions. | Returns true if the user has all the permissions of any of the provided groups, otherwise returns false. |
getExtensionLib(name) | name : String of script name | Returns an executable function identified by the given name configured as extension-libs. |
analytics.getTargetDocs() | Returns three target documents of the contact, calculated for the last three reporting intervals, including the current one. When viewing one of the current logged in user’s associated facilities, returns the target documents for the contact associated with the current logged in user. Returns an empty array if no target documents are found (for example when viewing a contact that does not upload targets). Introduced in v4.11.0 |
const canEdit = cht.v1.hasPermissions('can_edit');
const canManagePlaces = cht.v1.hasPermissions(['can_create_places', 'can_update_places']);
const hasAnyGroup = cht.v1.hasAnyPermission([
['can_view_messages', 'can_view_message_action'],
['can_view_reports', 'can_verify_reports']
]);
const averageFn = cht.v1.getExtensionLib('average.js');
const targetDocs = cht.v1.analytics.getTargetDocs();
This sample tasks.js
generates two postnatal-visit tasks for each delivery form. The tasks are due 7 and 14 days after the delivery report was submitted. Each task is displayed for 2 days before the due date and 2 days after the due date.
module.exports = [
{
icon: 'mother-child',
title: 'task.postnatal_followup',
appliesTo: 'reports',
appliesToType: [ 'delivery' ],
actions: [ { form: 'postnatal_visit' } ],
events: [
{
id: 'postnatal-followup-1',
days:7,
start:2,
end:2,
},
{
id: 'postnatal-followup-2',
days:14,
start:2,
end:2,
}
],
resolvedIf: function (contact, report, event, dueDate) {
return Utils.isFormSubmittedInWindow(
contact.reports,
'delivery',
Utils.addDate(dueDate, -event.start).getTime(),
Utils.addDate(dueDate, event.end + 1).getTime()
);
}
}
];
These samples show more complex tasks which use functions kept in a separate nools-extras.js
file
tasks.js
const extras = require('./nools-extras');
const { isFormFromArraySubmittedInWindow } = extras;
module.exports = [
// PNC TASK 1: If a home delivery, needs clinic tasks
{
icon: 'mother-child',
title: 'task.postnatal_followup.title',
appliesTo: 'reports',
appliesToType: [ 'D', 'delivery' ],
appliesIf: function(c, r) {
return isCoveredByUseCase(c.contact, 'pnc') &&
r.fields &&
r.fields.delivery_code &&
r.fields.delivery_code.toUpperCase() !== 'F';
},
actions: [{
form:'postnatal_visit',
// Pass content that will be used within the task form
modifyContent: function(content, c, r, event) {
content.delivery_place = 'home';
content.event_id = event.id;
}
}],
events: [ {
id: 'postnatal-visit',
days:0, start:0, end:4,
} ],
priority: {
level: 'high',
label: [ { locale:'en', content:'Home Birth' } ],
},
resolvedIf: function(c, r, event, dueDate) {
// Resolved if there a visit report received in time window or a newer pregnancy
return r.reported_date < extras.getNewestDeliveryTimestamp(c) ||
r.reported_date < extras.getNewestPregnancyTimestamp(c) ||
isFormFromArraySubmittedInWindow(c.reports, extras.postnatalForms,
Utils.addDate(dueDate, -event.start).getTime(),
Utils.addDate(dueDate, event.end+1).getTime());
},
},
// Option 1a: Place-based task: Family survey when place is created, then every 6 months
{
icon: 'family',
title: 'task.family_survey.title',
appliesTo: 'contacts',
appliesToType: [ 'clinic' ],
actions: [ { form:'family_survey' } ],
events: [ {
id: 'family-survey',
days:0, start:0, end:14,
} ],
resolvedIf: function(c, r, event, dueDate) {
// Resolved if there a family survey received in time window
return isFormFromArraySubmittedInWindow(c.reports, 'family_survey',
Utils.addDate(dueDate, -event.start).getTime(),
Utils.addDate(dueDate, event.end+1).getTime());
},
},
// Regular check for infants
{
icon: 'infant',
title: 'task.infant.title',
appliesTo: 'contacts',
appliesToType: [ 'person' ],
actions: [ { form:'infant_assessment' } ],
events: [
{
id: 'infant_asssessment-q1',
days:91, start:7, end:14,
},
{
id: 'infant_asssessment-q2',
days:182, start:7, end:14,
},
{
id: 'infant_asssessment-q3',
days:273, start:7, end:14,
},
{
id: 'infant_asssessment-q4',
days:365, start:7, end:14,
}
]
},
// Option 2: Place-based task: Family survey every 6 months
{
icon: 'family',
title: 'task.family_survey.title',
appliesTo: 'contacts',
appliesToType: [ 'clinic' ],
appliesIf: extras.needsFamilySurvey, // function returns true if family doesn't have survey in previous 6 months
actions: [ { form:'family_survey' } ],
events: [ {
id: 'family-survey',
start:0, end:14,
dueDate: extras.getNextFamilySurveyDate // function gets expected date of next family survey
} ],
resolvedIf: function(c, r, event, dueDate) {
// Resolved if there a family survey received in time window
return isFormFromArraySubmittedInWindow(c.reports, 'family_survey',
Utils.addDate(dueDate, -event.start).getTime(),
Utils.addDate(dueDate, event.end+1).getTime());
},
},
]
nools-extras.js
module.exports = {
isCoveredByUseCase: function (contact, usecase) {
// ...
},
getNewestDeliveryTimestamp: function (c) {
// ...
},
getNewestPregnancyTimestamp: function (c) {
// ...
},
isFormFromArraySubmittedInWindow: function (reports, formsArray, startTime, endTime) {
// ...
},
};
If the resolvedIf
is undefined in an action of type report
, then resolvedIf
is going to default to defaultResolvedIf
method.
The defaultResolvedIf
method returns true
if it finds any report assigned to the contact that matches the form
defined in the action of type report
. Only the reports submitted during a specific time period are considered:
start
and end
as:start
: the latest date between start of the task window and one millisecond after the report’s reported dateend
: end of the task windowYou can also use this.definition.defaultResolvedIf
inside the resolvedIf
definition and optionally add more conditions:
resolvedIf: function (contact, report, event, dueDate) {
return this.definition.defaultResolvedIf(contact, report, event, dueDate) && otherConditions;
}
To build your tasks into your app, you must compile them into app-settings, then upload them to your instance.
cht --local compile-app-settings backup-app-settings upload-app-settings
Ensuring that the right actions are taken for the right people at the right time
Building connections between people, actions, and data systems
This document covers the configuration best practices of forms, tasks, targets, and contact profiles when building your own community health app.
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.