diff --git a/packages/dhis2/CHANGELOG.md b/packages/dhis2/CHANGELOG.md
index a368a171d..0b0a7b317 100644
--- a/packages/dhis2/CHANGELOG.md
+++ b/packages/dhis2/CHANGELOG.md
@@ -1,5 +1,70 @@
# @openfn/language-dhis2
+## 6.0.0
+
+### Major Changes
+
+- b44a3b1: Migrates the adaptor to the new Tracker API (v36+) for
+ `trackedEntities`, `enrollments`, `events` and `relationships`. Note that
+ `trackedEntities` is no longer used.
+
+ This release is designed for compatibility with DHIS2 v42, which drops support
+ for a number of endpoints.
+
+ The `create`, `update`, `upsert` and `destroy` functions will automatically
+ map affected resources to the new tracker API endpoint.
+
+ If you have an existing workflow which uses these functions with
+ `trackedEntities`, `enrollments`, `events` or `relationships`, the data and
+ options you pass may be incompatible with the new tracker API. You should
+ review your code carefully against the
+ [DHIS2 Tracker Migration Guide](https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker-deprecated.html#webapi_tracker_migration)
+ to see what's changed.
+
+ For example, if you used to do:
+
+ ```js
+ create('trackedEntityInstances', {
+ /*...*/
+ });
+ ```
+
+ You should now do:
+
+ ```js
+ create('trackedEntities', {
+ /*...*/
+ });
+ ```
+
+ The payloads have also changed shape, so for example if you used to:
+
+ ```js
+ create('events', {
+ trackedEntityInstance: 'eBAyeGv0exc',
+ eventDate: '2024-01-01',
+ /* ... */
+ });
+ ```
+
+ You should now do:
+
+ ```js
+ create('events', {
+ trackedEntity: 'eBAyeGv0exc',
+ occurredAt: '2024-01-01',
+ /* ... */
+ });
+ ```
+
+ The HTTP APIs `get()`, `patch()`, and `post()` do not automatically map to the
+ new tracker: they continue to call the URL you provide with the data you send.
+ You can use this to continue to call the old tracker API directly.
+
+### Minor Changes
+
+- d30f39f: Added new post() operation
+
## 5.0.8
### Patch Changes
diff --git a/packages/dhis2/ast.json b/packages/dhis2/ast.json
index c33662197..ab0595f52 100644
--- a/packages/dhis2/ast.json
+++ b/packages/dhis2/ast.json
@@ -23,7 +23,7 @@
},
{
"title": "param",
- "description": "Type of resource to create. E.g. `trackedEntityInstances`, `programs`, `events`, ...",
+ "description": "Type of resource to create. E.g. `trackedEntities`, `programs`, `events`, ...",
"type": {
"type": "NameExpression",
"name": "string"
@@ -78,22 +78,22 @@
{
"title": "example",
"description": "create('programs', {\n name: 'name 20',\n shortName: 'n20',\n programType: 'WITHOUT_REGISTRATION',\n});",
- "caption": "a program"
+ "caption": "Createa program"
},
{
"title": "example",
"description": "create('events', {\n program: 'eBAyeGv0exc',\n orgUnit: 'DiszpKrYNg8',\n status: 'COMPLETED',\n});",
- "caption": "an event"
+ "caption": "Create a single event"
},
{
"title": "example",
- "description": "create('trackedEntityInstances', {\n orgUnit: 'TSyzvBiovKh',\n trackedEntityType: 'nEenWmSyUEp',\n attributes: [\n {\n attribute: 'w75KJ2mc4zz',\n value: 'Gigiwe',\n },\n ]\n});",
- "caption": "a trackedEntityInstance"
+ "description": "create('trackedEntities', {\n orgUnit: 'TSyzvBiovKh',\n trackedEntityType: 'nEenWmSyUEp',\n attributes: [\n {\n attribute: 'w75KJ2mc4zz',\n value: 'Gigiwe',\n },\n ]\n});",
+ "caption": "Create a single tracker entity. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#webapi_nti_import Create tracker docs}"
},
{
"title": "example",
"description": "create('dataSets', { name: 'OpenFn Data Set', periodType: 'Monthly' });",
- "caption": "a dataSet"
+ "caption": "Create a dataSet"
},
{
"title": "example",
@@ -103,32 +103,37 @@
{
"title": "example",
"description": "create('dataElements', {\n aggregationType: 'SUM',\n domainType: 'AGGREGATE',\n valueType: 'NUMBER',\n name: 'Paracetamol',\n shortName: 'Para',\n});",
- "caption": "a dataElement"
+ "caption": "Create a dataElement"
},
{
"title": "example",
"description": "create('dataElementGroups', {\n name: 'Data Element Group 1',\n dataElements: [],\n});",
- "caption": "a dataElementGroup"
+ "caption": "Create a dataElementGroup"
},
{
"title": "example",
"description": "create('dataElementGroupSets', {\n name: 'Data Element Group Set 4',\n dataDimension: true,\n shortName: 'DEGS4',\n dataElementGroups: [],\n});",
- "caption": "a dataElementGroupSet"
+ "caption": "Create a dataElementGroupSet"
},
{
"title": "example",
"description": "create('dataValueSets', {\n dataElement: 'f7n9E0hX8qk',\n period: '201401',\n orgUnit: 'DiszpKrYNg8',\n value: '12',\n});",
- "caption": "a dataValueSet"
+ "caption": "Create a dataValueSet"
},
{
"title": "example",
"description": "create('dataValueSets', {\n dataSet: 'pBOMPrpg1QX',\n completeDate: '2014-02-03',\n period: '201401',\n orgUnit: 'DiszpKrYNg8',\n dataValues: [\n {\n dataElement: 'f7n9E0hX8qk',\n value: '1',\n },\n {\n dataElement: 'Ix2HsbDMLea',\n value: '2',\n },\n {\n dataElement: 'eY5ehpbEsB7',\n value: '3',\n },\n ],\n});",
- "caption": "a dataValueSet with related dataValues"
+ "caption": "Create a dataValueSet with related dataValues"
+ },
+ {
+ "title": "example",
+ "description": "create('enrollments', {\n trackedEntity: 'bmshzEacgxa',\n orgUnit: 'TSyzvBiovKh',\n program: 'gZBxv9Ujxg0',\n enrollmentDate: '2013-09-17',\n incidentDate: '2013-09-17',\n});",
+ "caption": "Create an enrollment"
},
{
"title": "example",
- "description": "create('enrollments', {\n trackedEntityInstance: 'bmshzEacgxa',\n orgUnit: 'TSyzvBiovKh',\n program: 'gZBxv9Ujxg0',\n enrollmentDate: '2013-09-17',\n incidentDate: '2013-09-17',\n});",
- "caption": "an enrollment"
+ "description": "create(\"tracker\", {\n enrollments: [\n {\n trackedEntity: \"bmshzEacgxa\",\n orgUnit: \"TSyzvBiovKh\",\n program: \"gZBxv9Ujxg0\",\n enrollmentDate: \"2013-09-17\",\n incidentDate: \"2013-09-17\",\n },\n ],\n trackedEntities: [\n {\n orgUnit: \"TSyzvBiovKh\",\n trackedEntityType: \"nEenWmSyUEp\",\n attributes: [\n {\n attribute: \"w75KJ2mc4zz\",\n value: \"Gigiwe\",\n },\n ],\n },\n ],\n});",
+ "caption": "Create an multiple objects with the Tracker API"
}
]
},
@@ -227,48 +232,48 @@
},
{
"title": "example",
- "description": "update('trackedEntityInstances', 'IeQfgUtGPq2', {\n created: '2015-08-06T21:12:37.256',\n orgUnit: 'TSyzvBiovKh',\n createdAtClient: '2015-08-06T21:12:37.256',\n trackedEntityInstance: 'IeQfgUtGPq2',\n lastUpdated: '2015-08-06T21:12:37.257',\n trackedEntityType: 'nEenWmSyUEp',\n inactive: false,\n deleted: false,\n featureType: 'NONE',\n programOwners: [\n {\n ownerOrgUnit: 'TSyzvBiovKh',\n program: 'IpHINAT79UW',\n trackedEntityInstance: 'IeQfgUtGPq2',\n },\n ],\n enrollments: [],\n relationships: [],\n attributes: [\n {\n lastUpdated: '2016-01-12T00:00:00.000',\n displayName: 'Last name',\n created: '2016-01-12T00:00:00.000',\n valueType: 'TEXT',\n attribute: 'zDhUuAYrxNC',\n value: 'Russell',\n },\n {\n lastUpdated: '2016-01-12T00:00:00.000',\n code: 'MMD_PER_NAM',\n displayName: 'First name',\n created: '2016-01-12T00:00:00.000',\n valueType: 'TEXT',\n attribute: 'w75KJ2mc4zz',\n value: 'Catherine',\n },\n ],\n});",
- "caption": "a trackedEntityInstance"
+ "description": "update('trackedEntities', '', {\n createdAt: '2015-08-06T21:12:37.256',\n orgUnit: 'TSyzvBiovKh',\n createdAtClient: '2015-08-06T21:12:37.256',\n trackedEntity: 'IeQfgUtGPq2',\n trackedEntityType: 'nEenWmSyUEp',\n inactive: false,\n deleted: false,\n featureType: 'NONE',\n programOwners: [\n {\n ownerOrgUnit: 'TSyzvBiovKh',\n program: 'IpHINAT79UW',\n trackedEntity: 'IeQfgUtGPq2',\n },\n ],\n attributes: [\n {\n lastUpdated: '2016-01-12T00:00:00.000',\n displayName: 'Last name',\n created: '2016-01-12T00:00:00.000',\n valueType: 'TEXT',\n attribute: 'zDhUuAYrxNC',\n value: 'Russell',\n },\n {\n lastUpdated: '2016-01-12T00:00:00.000',\n code: 'MMD_PER_NAM',\n displayName: 'First name',\n created: '2016-01-12T00:00:00.000',\n valueType: 'TEXT',\n attribute: 'w75KJ2mc4zz',\n value: 'Catherine',\n },\n ],\n});",
+ "caption": "Update a tracker entity. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#webapi_nti_import Update tracker docs}"
},
{
"title": "example",
"description": "update('dataSets', 'lyLU2wR22tC', { name: 'OpenFN Data Set', periodType: 'Weekly' });",
- "caption": "a dataSet"
+ "caption": "Update a dataSet"
},
{
"title": "example",
- "description": "update('dataSetNotificationTemplates', 'VbQBwdm1wVP', {\n dataSetNotificationTrigger: 'DATA_SET_COMPLETION',\n notificationRecipient: 'ORGANISATION_UNIT_CONTACT',\n name: 'Notification',\n messageTemplate: 'Hello Updated,\n deliveryChannels: ['SMS'],\n dataSets: [],\n});",
+ "description": "update('dataSetNotificationTemplates', 'VbQBwdm1wVP', {\n dataSetNotificationTrigger: 'DATA_SET_COMPLETION',\n notificationRecipient: 'ORGANISATION_UNIT_CONTACT',\n name: 'Notification',\n messageTemplate: 'Hello Updated',\n deliveryChannels: ['SMS'],\n dataSets: [],\n});",
"caption": "a dataSetNotification"
},
{
"title": "example",
"description": "update('dataElements', 'FTRrcoaog83', {\n aggregationType: 'SUM',\n domainType: 'AGGREGATE',\n valueType: 'NUMBER',\n name: 'Paracetamol',\n shortName: 'Para',\n});",
- "caption": "a dataElement"
+ "caption": "Update a dataElement"
},
{
"title": "example",
"description": "update('dataElementGroups', 'QrprHT61XFk', {\n name: 'Data Element Group 1',\n dataElements: [],\n});",
- "caption": "a dataElementGroup"
+ "caption": "Update a dataElementGroup"
},
{
"title": "example",
"description": "update('dataElementGroupSets', 'VxWloRvAze8', {\n name: 'Data Element Group Set 4',\n dataDimension: true,\n shortName: 'DEGS4',\n dataElementGroups: [],\n});",
- "caption": "a dataElementGroupSet"
+ "caption": "Update a dataElementGroupSet"
},
{
"title": "example",
"description": "update('dataValueSets', 'AsQj6cDsUq4', {\n dataElement: 'f7n9E0hX8qk',\n period: '201401',\n orgUnit: 'DiszpKrYNg8',\n value: '12',\n});",
- "caption": "a dataValueSet"
+ "caption": "Update a dataValueSet"
},
{
"title": "example",
"description": "update('dataValueSets', 'Ix2HsbDMLea', {\n dataSet: 'pBOMPrpg1QX',\n completeDate: '2014-02-03',\n period: '201401',\n orgUnit: 'DiszpKrYNg8',\n dataValues: [\n {\n dataElement: 'f7n9E0hX8qk',\n value: '1',\n },\n {\n dataElement: 'Ix2HsbDMLea',\n value: '2',\n },\n {\n dataElement: 'eY5ehpbEsB7',\n value: '3',\n },\n ],\n});",
- "caption": "a dataValueSet with related dataValues"
+ "caption": "Update a dataValueSet with related dataValues"
},
{
"title": "example",
- "description": "update('enrollments', 'CmsHzercTBa' {\n trackedEntityInstance: 'bmshzEacgxa',\n orgUnit: 'TSyzvBiovKh',\n program: 'gZBxv9Ujxg0',\n enrollmentDate: '2013-10-17',\n incidentDate: '2013-10-17',\n});",
- "caption": "a single enrollment"
+ "description": "update('enrollments', 'CmsHzercTBa' {\n trackedEntity: 'bmshzEacgxa',\n orgUnit: 'TSyzvBiovKh',\n program: 'gZBxv9Ujxg0',\n enrollmentDate: '2013-10-17',\n incidentDate: '2013-10-17',\n});",
+ "caption": "Update an enrollment given the provided ID"
}
]
},
@@ -283,7 +288,7 @@
"callback"
],
"docs": {
- "description": "Get data. Generic helper method for getting data of any kind from DHIS2.\n- This can be used to get `DataValueSets`,`events`,`trackedEntityInstances`,`etc.`",
+ "description": "Get data. Generic helper method for getting data of any kind from DHIS2.\n- This can be used to get `DataValueSets`,`events`,`trackers`,`etc.`",
"tags": [
{
"title": "public",
@@ -297,7 +302,7 @@
},
{
"title": "param",
- "description": "The type of resource to get(use its `plural` name). E.g. `dataElements`, `trackedEntityInstances`,`organisationUnits`, etc.",
+ "description": "The type of resource to get(use its `plural` name). E.g. `dataElements`, `tracker/trackedEntities`,`organisationUnits`, etc.",
"type": {
"type": "NameExpression",
"name": "string"
@@ -348,22 +353,122 @@
{
"title": "example",
"description": "get('dataValueSets', {\n dataSet: 'pBOMPrpg1QX',\n orgUnit: 'DiszpKrYNg8',\n period: '201401',\n fields: '*',\n});",
- "caption": "all data values for the 'pBOMPrpg1QX' dataset"
+ "caption": "Get all data values for the 'pBOMPrpg1QX' dataset"
},
{
"title": "example",
"description": "get('programs', { orgUnit: 'TSyzvBiovKh', fields: '*' });",
- "caption": "all programs for an organization unit"
+ "caption": "Get all programs for an organization unit"
},
{
"title": "example",
- "description": "get('trackedEntityInstances', {\n ou: 'DiszpKrYNg8',\n filter: ['flGbXLXCrEo:Eq:124', 'w75KJ2mc4zz:Eq:John'],\n});",
- "caption": "a single tracked entity instance by a unique external ID"
+ "description": "get('tracker/trackedEntities/F8yKM85NbxW');",
+ "caption": "Get a single tracked entity given the provided ID. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#tracked-entities-get-apitrackertrackedentities TrackedEntities docs}"
+ },
+ {
+ "title": "example",
+ "description": "get('tracker/enrollments/abcd');",
+ "caption": "Get an enrollment given the provided ID. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#enrollments-get-apitrackerenrollments Enrollment docs}"
+ },
+ {
+ "title": "example",
+ "description": "get('tracker/events');",
+ "caption": "Get all events matching given criteria. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#events-get-apitrackerevents Events docs}"
+ },
+ {
+ "title": "example",
+ "description": "get('tracker/relationships', {\n trackedEntity:['F8yKM85NbxW'],\n});",
+ "caption": "Get the relationship between two tracker entities. The only required parameters are 'trackedEntity', 'enrollment' or 'event'. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#relationships-get-apitrackerrelationships Relationships docs}"
}
]
},
"valid": true
},
+ {
+ "name": "post",
+ "params": [
+ "resourceType",
+ "data",
+ "query",
+ "options",
+ "callback"
+ ],
+ "docs": {
+ "description": "Post data. Generic helper method for posting data of any kind to DHIS2.\nThis can be used to create `DataValueSets`,`events`,`trackers`,etc.",
+ "tags": [
+ {
+ "title": "public",
+ "description": null,
+ "type": null
+ },
+ {
+ "title": "function",
+ "description": null,
+ "name": null
+ },
+ {
+ "title": "param",
+ "description": "Type of resource to create. E.g. `trackedEntities`, `programs`, `events`, ...",
+ "type": {
+ "type": "NameExpression",
+ "name": "string"
+ },
+ "name": "resourceType"
+ },
+ {
+ "title": "magic",
+ "description": "resourceType $.children.resourceTypes[*]"
+ },
+ {
+ "title": "param",
+ "description": "Object which defines data that will be used to create a given instance of resource. To create a single instance of a resource, `data` must be a javascript object, and to create multiple instances of a resources, `data` must be an array of javascript objects.",
+ "type": {
+ "type": "NameExpression",
+ "name": "Dhis2Data"
+ },
+ "name": "data"
+ },
+ {
+ "title": "param",
+ "description": "Optional `options` to define URL parameters via params (E.g. `filter`, `dimension` and other import parameters), request config (E.g. `auth`) and the DHIS2 apiVersion.",
+ "type": {
+ "type": "OptionalType",
+ "expression": {
+ "type": "NameExpression",
+ "name": "Object"
+ }
+ },
+ "name": "options"
+ },
+ {
+ "title": "param",
+ "description": "Optional callback to handle the response",
+ "type": {
+ "type": "OptionalType",
+ "expression": {
+ "type": "NameExpression",
+ "name": "function"
+ }
+ },
+ "name": "callback"
+ },
+ {
+ "title": "returns",
+ "description": "state",
+ "type": {
+ "type": "NameExpression",
+ "name": "Operation"
+ }
+ },
+ {
+ "title": "example",
+ "description": "post(\"tracker\", {\n events: [\n {\n program: \"eBAyeGv0exc\",\n orgUnit: \"DiszpKrYNg8\",\n status: \"COMPLETED\",\n },\n ],\n});",
+ "caption": "Create an event"
+ }
+ ]
+ },
+ "valid": false
+ },
{
"name": "upsert",
"params": [
@@ -388,7 +493,7 @@
},
{
"title": "param",
- "description": "The type of a resource to `upsert`. E.g. `trackedEntityInstances`",
+ "description": "The type of a resource to `upsert`. E.g. `trackedEntities`",
"type": {
"type": "NameExpression",
"name": "string"
@@ -480,8 +585,8 @@
},
{
"title": "example",
- "description": "upsert('trackedEntityInstances', {\n ou: 'TSyzvBiovKh',\n filter: ['w75KJ2mc4zz:Eq:Qassim'],\n}, {\n orgUnit: 'TSyzvBiovKh',\n trackedEntityType: 'nEenWmSyUEp',\n attributes: [\n {\n attribute: 'w75KJ2mc4zz',\n value: 'Qassim',\n },\n ],\n});",
- "caption": "Example `expression.js` of upsert"
+ "description": "upsert('trackedEntities', {\n orgUnit: 'TSyzvBiovKh',\n filter: ['w75KJ2mc4zz:Eq:Qassim'],\n}, {\n orgUnit: 'TSyzvBiovKh',\n trackedEntityType: 'nEenWmSyUEp',\n attributes: [\n {\n attribute: 'w75KJ2mc4zz',\n value: 'Qassim',\n },\n ],\n});",
+ "caption": "Upsert a trackedEntity"
}
]
},
@@ -517,7 +622,7 @@
},
{
"title": "param",
- "description": "The path for a given endpoint. E.g. `/trackedEntityInstances` or `/dataValueSets`",
+ "description": "The path for a given endpoint. E.g. `/trackedEntities` or `/dataValueSets`",
"type": {
"type": "NameExpression",
"name": "string"
@@ -534,7 +639,7 @@
},
{
"title": "example",
- "description": "discover('post', '/trackedEntityInstances')",
+ "description": "discover('post', '/trackedEntities')",
"caption": "a list of parameters allowed on a given endpoint for specific http method"
}
]
@@ -655,7 +760,7 @@
},
{
"title": "param",
- "description": "The type of resource to be deleted. E.g. `trackedEntityInstances`, `organisationUnits`, etc.",
+ "description": "The type of resource to be deleted. E.g. `trackedEntities`, `organisationUnits`, etc.",
"type": {
"type": "NameExpression",
"name": "string"
@@ -742,8 +847,8 @@
},
{
"title": "example",
- "description": "destroy('trackedEntityInstances', 'LcRd6Nyaq7T');",
- "caption": "a tracked entity instance"
+ "description": "destroy('trackedEntities', 'LcRd6Nyaq7T');",
+ "caption": "a tracked entity instance. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#webapi_nti_import Delete tracker docs}"
}
]
},
@@ -752,7 +857,7 @@
{
"name": "findAttributeValue",
"params": [
- "trackedEntityInstance",
+ "trackedEntity",
"attributeDisplayName"
],
"docs": {
@@ -765,7 +870,7 @@
},
{
"title": "example",
- "description": "findAttributeValue(state.data.trackedEntityInstances[0], 'first name')"
+ "description": "findAttributeValue(state.data.trackedEntities[0], 'first name')"
},
{
"title": "function",
@@ -779,7 +884,7 @@
"type": "NameExpression",
"name": "Object"
},
- "name": "trackedEntityInstance"
+ "name": "trackedEntity"
},
{
"title": "param",
@@ -809,7 +914,7 @@
"value"
],
"docs": {
- "description": "Converts an attribute ID and value into a DSHI2 attribute object",
+ "description": "Converts an attribute ID and value into a DHIS2 attribute object",
"tags": [
{
"title": "public",
@@ -862,7 +967,7 @@
"value"
],
"docs": {
- "description": "Converts a dataElement and value into a DSHI2 dataValue object",
+ "description": "Converts a dataElement and value into a DHIS2 dataValue object",
"tags": [
{
"title": "public",
@@ -1473,6 +1578,92 @@
]
},
"valid": true
+ },
+ {
+ "name": "cursor",
+ "params": [
+ "value",
+ "options"
+ ],
+ "docs": {
+ "description": "Sets a cursor property on state.\nSupports natural language dates like `now`, `today`, `yesterday`, `n hours ago`, `n days ago`, and `start`,\nwhich will be converted relative to the environment (ie, the Lightning or CLI locale). Custom timezones\nare not yet supported.\nYou can provide a formatter to customise the final cursor value, which is useful for normalising\ndifferent inputs. The custom formatter runs after natural language date conversion.\nSee the usage guide at {@link https://docs.openfn.org/documentation/jobs/job-writing-guide#using-cursors}",
+ "tags": [
+ {
+ "title": "public",
+ "description": null,
+ "type": null
+ },
+ {
+ "title": "function",
+ "description": null,
+ "name": null
+ },
+ {
+ "title": "example",
+ "description": "cursor($.cursor, { defaultValue: 'today' })",
+ "caption": "Use a cursor from state if present, or else use the default value"
+ },
+ {
+ "title": "example",
+ "description": "cursor(22)",
+ "caption": "Use a pagination cursor"
+ },
+ {
+ "title": "param",
+ "description": "the cursor value. Usually an ISO date, natural language date, or page number",
+ "type": {
+ "type": "NameExpression",
+ "name": "any"
+ },
+ "name": "value"
+ },
+ {
+ "title": "param",
+ "description": "options to control the cursor.",
+ "type": {
+ "type": "NameExpression",
+ "name": "object"
+ },
+ "name": "options"
+ },
+ {
+ "title": "param",
+ "description": "set the cursor key. Will persist through the whole run.",
+ "type": {
+ "type": "NameExpression",
+ "name": "string"
+ },
+ "name": "options.key"
+ },
+ {
+ "title": "param",
+ "description": "the value to use if value is falsy",
+ "type": {
+ "type": "NameExpression",
+ "name": "any"
+ },
+ "name": "options.defaultValue"
+ },
+ {
+ "title": "param",
+ "description": "custom formatter for the final cursor value",
+ "type": {
+ "type": "NameExpression",
+ "name": "Function"
+ },
+ "name": "options.format"
+ },
+ {
+ "title": "returns",
+ "description": null,
+ "type": {
+ "type": "NameExpression",
+ "name": "Operation"
+ }
+ }
+ ]
+ },
+ "valid": false
}
]
}
\ No newline at end of file
diff --git a/packages/dhis2/package.json b/packages/dhis2/package.json
index a40388fe1..fbf653c70 100644
--- a/packages/dhis2/package.json
+++ b/packages/dhis2/package.json
@@ -1,6 +1,6 @@
{
"name": "@openfn/language-dhis2",
- "version": "5.0.8",
+ "version": "6.0.0",
"description": "DHIS2 Language Pack for OpenFn",
"homepage": "https://docs.openfn.org",
"repository": {
diff --git a/packages/dhis2/src/Adaptor.js b/packages/dhis2/src/Adaptor.js
index 7049723cb..b83e4618f 100644
--- a/packages/dhis2/src/Adaptor.js
+++ b/packages/dhis2/src/Adaptor.js
@@ -1,21 +1,19 @@
import axios from 'axios';
+import { execute as commonExecute } from '@openfn/language-common';
+import { expandReferences } from '@openfn/language-common/util';
import _ from 'lodash';
-import {
- execute as commonExecute,
- expandReferences,
-} from '@openfn/language-common';
+const { indexOf } = _;
import {
CONTENT_TYPES,
generateUrl,
handleResponse,
- nestArray,
prettyJson,
selectId,
+ shouldUseNewTracker,
+ ensureArray,
} from './Utils';
import { request } from './Client';
-const { indexOf } = _;
-
/**
* Execute a sequence of operations.
* Wraps `language-common/execute`, and prepends initial state for DHIS2.
@@ -36,10 +34,12 @@ export function execute(...operations) {
return state => {
const version = state.configuration?.apiVersion;
- if (+version >= 42)
+
+ if (+version < 36) {
console.warn(
- `WARNING: This adaptor is incompatible with DHIS2 API version 42+. See here: https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-master/tracker.html.`
+ `WARNING: This adaptor is INCOMPATIBLE with DHIS2 tracker API versions before v36. Some functionality may break. See https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-master/tracker.html`
);
+ }
return commonExecute(
configMigrationHelper,
@@ -128,26 +128,26 @@ axios.interceptors.response.use(
* Create a record
* @public
* @function
- * @param {string} resourceType - Type of resource to create. E.g. `trackedEntityInstances`, `programs`, `events`, ...
+ * @param {string} resourceType - Type of resource to create. E.g. `trackedEntities`, `programs`, `events`, ...
* @magic resourceType $.children.resourceTypes[*]
* @param {Dhis2Data} data - Object which defines data that will be used to create a given instance of resource. To create a single instance of a resource, `data` must be a javascript object, and to create multiple instances of a resources, `data` must be an array of javascript objects.
* @param {Object} [options] - Optional `options` to define URL parameters via params (E.g. `filter`, `dimension` and other import parameters), request config (E.g. `auth`) and the DHIS2 apiVersion.
* @param {function} [callback] - Optional callback to handle the response
* @returns {Operation}
- * @example
a program
+ * @example Create a program
* create('programs', {
* name: 'name 20',
* shortName: 'n20',
* programType: 'WITHOUT_REGISTRATION',
* });
- * @example an event
+ * @example Create a single event
* create('events', {
* program: 'eBAyeGv0exc',
* orgUnit: 'DiszpKrYNg8',
* status: 'COMPLETED',
* });
- * @example a trackedEntityInstance
- * create('trackedEntityInstances', {
+ * @example Create a single tracker entity. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#webapi_nti_import Create tracker docs}
+ * create('trackedEntities', {
* orgUnit: 'TSyzvBiovKh',
* trackedEntityType: 'nEenWmSyUEp',
* attributes: [
@@ -157,7 +157,7 @@ axios.interceptors.response.use(
* },
* ]
* });
- * @example a dataSet
+ * @example Create a dataSet
* create('dataSets', { name: 'OpenFn Data Set', periodType: 'Monthly' });
* @example a dataSetNotification
* create('dataSetNotificationTemplates', {
@@ -168,7 +168,7 @@ axios.interceptors.response.use(
* deliveryChannels: ['SMS'],
* dataSets: [],
* });
- * @example a dataElement
+ * @example Create a dataElement
* create('dataElements', {
* aggregationType: 'SUM',
* domainType: 'AGGREGATE',
@@ -176,26 +176,26 @@ axios.interceptors.response.use(
* name: 'Paracetamol',
* shortName: 'Para',
* });
- * @example a dataElementGroup
+ * @example Create a dataElementGroup
* create('dataElementGroups', {
* name: 'Data Element Group 1',
* dataElements: [],
* });
- * @example a dataElementGroupSet
+ * @example Create a dataElementGroupSet
* create('dataElementGroupSets', {
* name: 'Data Element Group Set 4',
* dataDimension: true,
* shortName: 'DEGS4',
* dataElementGroups: [],
* });
- * @example a dataValueSet
+ * @example Create a dataValueSet
* create('dataValueSets', {
* dataElement: 'f7n9E0hX8qk',
* period: '201401',
* orgUnit: 'DiszpKrYNg8',
* value: '12',
* });
- * @example a dataValueSet with related dataValues
+ * @example Create a dataValueSet with related dataValues
* create('dataValueSets', {
* dataSet: 'pBOMPrpg1QX',
* completeDate: '2014-02-03',
@@ -216,33 +216,69 @@ axios.interceptors.response.use(
* },
* ],
* });
- * @example an enrollment
+ * @example Create an enrollment
* create('enrollments', {
- * trackedEntityInstance: 'bmshzEacgxa',
+ * trackedEntity: 'bmshzEacgxa',
* orgUnit: 'TSyzvBiovKh',
* program: 'gZBxv9Ujxg0',
* enrollmentDate: '2013-09-17',
* incidentDate: '2013-09-17',
* });
+ * @example Create an multiple objects with the Tracker API
+ * create("tracker", {
+ * enrollments: [
+ * {
+ * trackedEntity: "bmshzEacgxa",
+ * orgUnit: "TSyzvBiovKh",
+ * program: "gZBxv9Ujxg0",
+ * enrollmentDate: "2013-09-17",
+ * incidentDate: "2013-09-17",
+ * },
+ * ],
+ * trackedEntities: [
+ * {
+ * orgUnit: "TSyzvBiovKh",
+ * trackedEntityType: "nEenWmSyUEp",
+ * attributes: [
+ * {
+ * attribute: "w75KJ2mc4zz",
+ * value: "Gigiwe",
+ * },
+ * ],
+ * },
+ * ],
+ * });
*/
-export function create(resourceType, data, options = {}, callback = false) {
+export function create(resourceType, data, options = {}, callback = s => s) {
return state => {
console.log(`Preparing create operation...`);
- const resolvedResourceType = expandReferences(resourceType)(state);
- const resolvedData = expandReferences(data)(state);
- const resolvedOptions = expandReferences(options)(state);
+ const [resolvedResourceType, resolvedData, resolvedOptions] =
+ expandReferences(state, resourceType, data, options);
const { params, requestConfig } = resolvedOptions;
const { configuration } = state;
- return request(configuration, {
- method: 'post',
- url: generateUrl(configuration, resolvedOptions, resolvedResourceType),
- params,
- data: nestArray(resolvedData, resolvedResourceType),
- ...requestConfig,
- }).then(result => {
+ let promise;
+ if (shouldUseNewTracker(resolvedResourceType)) {
+ promise = callNewTracker(
+ 'create',
+ configuration,
+ resolvedOptions,
+ resolvedResourceType,
+ resolvedData
+ );
+ } else {
+ promise = request(configuration, {
+ method: 'post',
+ url: generateUrl(configuration, resolvedOptions, resolvedResourceType),
+ params,
+ data: resolvedData,
+ ...requestConfig,
+ });
+ }
+
+ return promise.then(result => {
const details = `with response ${JSON.stringify(result.data, null, 2)}`;
console.log(`Created ${resolvedResourceType} ${details}`);
@@ -279,13 +315,12 @@ export function create(resourceType, data, options = {}, callback = false) {
* storedBy: 'admin',
* dataValues: [],
* });
- * @example a trackedEntityInstance
- * update('trackedEntityInstances', 'IeQfgUtGPq2', {
- * created: '2015-08-06T21:12:37.256',
+ * @example Update a tracker entity. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#webapi_nti_import Update tracker docs}
+ * update('trackedEntities', '', {
+ * createdAt: '2015-08-06T21:12:37.256',
* orgUnit: 'TSyzvBiovKh',
* createdAtClient: '2015-08-06T21:12:37.256',
- * trackedEntityInstance: 'IeQfgUtGPq2',
- * lastUpdated: '2015-08-06T21:12:37.257',
+ * trackedEntity: 'IeQfgUtGPq2',
* trackedEntityType: 'nEenWmSyUEp',
* inactive: false,
* deleted: false,
@@ -294,11 +329,9 @@ export function create(resourceType, data, options = {}, callback = false) {
* {
* ownerOrgUnit: 'TSyzvBiovKh',
* program: 'IpHINAT79UW',
- * trackedEntityInstance: 'IeQfgUtGPq2',
+ * trackedEntity: 'IeQfgUtGPq2',
* },
* ],
- * enrollments: [],
- * relationships: [],
* attributes: [
* {
* lastUpdated: '2016-01-12T00:00:00.000',
@@ -319,18 +352,18 @@ export function create(resourceType, data, options = {}, callback = false) {
* },
* ],
* });
- * @example a dataSet
+ * @example Update a dataSet
* update('dataSets', 'lyLU2wR22tC', { name: 'OpenFN Data Set', periodType: 'Weekly' });
* @example a dataSetNotification
* update('dataSetNotificationTemplates', 'VbQBwdm1wVP', {
* dataSetNotificationTrigger: 'DATA_SET_COMPLETION',
* notificationRecipient: 'ORGANISATION_UNIT_CONTACT',
* name: 'Notification',
- * messageTemplate: 'Hello Updated,
+ * messageTemplate: 'Hello Updated',
* deliveryChannels: ['SMS'],
* dataSets: [],
* });
- * @example a dataElement
+ * @example Update a dataElement
* update('dataElements', 'FTRrcoaog83', {
* aggregationType: 'SUM',
* domainType: 'AGGREGATE',
@@ -338,26 +371,26 @@ export function create(resourceType, data, options = {}, callback = false) {
* name: 'Paracetamol',
* shortName: 'Para',
* });
- * @example a dataElementGroup
+ * @example Update a dataElementGroup
* update('dataElementGroups', 'QrprHT61XFk', {
* name: 'Data Element Group 1',
* dataElements: [],
* });
- * @example a dataElementGroupSet
+ * @example Update a dataElementGroupSet
* update('dataElementGroupSets', 'VxWloRvAze8', {
* name: 'Data Element Group Set 4',
* dataDimension: true,
* shortName: 'DEGS4',
* dataElementGroups: [],
* });
- * @example a dataValueSet
+ * @example Update a dataValueSet
* update('dataValueSets', 'AsQj6cDsUq4', {
* dataElement: 'f7n9E0hX8qk',
* period: '201401',
* orgUnit: 'DiszpKrYNg8',
* value: '12',
* });
- * @example a dataValueSet with related dataValues
+ * @example Update a dataValueSet with related dataValues
* update('dataValueSets', 'Ix2HsbDMLea', {
* dataSet: 'pBOMPrpg1QX',
* completeDate: '2014-02-03',
@@ -378,9 +411,9 @@ export function create(resourceType, data, options = {}, callback = false) {
* },
* ],
* });
- * @example a single enrollment
+ * @example Update an enrollment given the provided ID
* update('enrollments', 'CmsHzercTBa' {
- * trackedEntityInstance: 'bmshzEacgxa',
+ * trackedEntity: 'bmshzEacgxa',
* orgUnit: 'TSyzvBiovKh',
* program: 'gZBxv9Ujxg0',
* enrollmentDate: '2013-10-17',
@@ -392,31 +425,42 @@ export function update(
path,
data,
options = {},
- callback = false
+ callback = s => s
) {
return state => {
console.log(`Preparing update operation...`);
- const resolvedResourceType = expandReferences(resourceType)(state);
- const resolvedPath = expandReferences(path)(state);
- const resolvedData = expandReferences(data)(state);
- const resolvedOptions = expandReferences(options)(state);
+ const [resolvedResourceType, resolvedPath, resolvedData, resolvedOptions] =
+ expandReferences(state, resourceType, path, data, options);
- const { params, requestConfig } = resolvedOptions;
+ const { requestConfig } = resolvedOptions;
const { configuration } = state;
- return request(configuration, {
- method: 'put',
- url: generateUrl(
+ let promise;
+ if (shouldUseNewTracker(resolvedResourceType)) {
+ promise = callNewTracker(
+ 'update',
configuration,
resolvedOptions,
resolvedResourceType,
- resolvedPath
- ),
- params,
- data: resolvedData,
- ...requestConfig,
- }).then(result => {
+ resolvedData
+ );
+ } else {
+ promise = request(configuration, {
+ method: 'put',
+ url: generateUrl(
+ configuration,
+ resolvedOptions,
+ resolvedResourceType,
+ resolvedPath
+ ),
+ options,
+ data: resolvedData,
+ ...requestConfig,
+ });
+ }
+
+ return promise.then(result => {
console.log(`Updated ${resolvedResourceType} at ${resolvedPath}`);
return handleResponse(result, state, callback);
});
@@ -425,36 +469,40 @@ export function update(
/**
* Get data. Generic helper method for getting data of any kind from DHIS2.
- * - This can be used to get `DataValueSets`,`events`,`trackedEntityInstances`,`etc.`
+ * - This can be used to get `DataValueSets`,`events`,`trackers`,`etc.`
* @public
* @function
- * @param {string} resourceType - The type of resource to get(use its `plural` name). E.g. `dataElements`, `trackedEntityInstances`,`organisationUnits`, etc.
+ * @param {string} resourceType - The type of resource to get(use its `plural` name). E.g. `dataElements`, `tracker/trackedEntities`,`organisationUnits`, etc.
* @param {Object} query - A query object that will limit what resources are retrieved when converted into request params.
* @param {Object} [options] - Optional `options` to define URL parameters via params beyond filters, request configuration (e.g. `auth`) and DHIS2 api version to use.
* @param {function} [callback] - Optional callback to handle the response
* @returns {Operation} state
- * @example all data values for the 'pBOMPrpg1QX' dataset
+ * @example Get all data values for the 'pBOMPrpg1QX' dataset
* get('dataValueSets', {
* dataSet: 'pBOMPrpg1QX',
* orgUnit: 'DiszpKrYNg8',
* period: '201401',
* fields: '*',
* });
- * @example all programs for an organization unit
+ * @example Get all programs for an organization unit
* get('programs', { orgUnit: 'TSyzvBiovKh', fields: '*' });
- * @example a single tracked entity instance by a unique external ID
- * get('trackedEntityInstances', {
- * ou: 'DiszpKrYNg8',
- * filter: ['flGbXLXCrEo:Eq:124', 'w75KJ2mc4zz:Eq:John'],
+ * @example Get a single tracked entity given the provided ID. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#tracked-entities-get-apitrackertrackedentities TrackedEntities docs}
+ * get('tracker/trackedEntities/F8yKM85NbxW');
+ * @example Get an enrollment given the provided ID. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#enrollments-get-apitrackerenrollments Enrollment docs}
+ * get('tracker/enrollments/abcd');
+ * @example Get all events matching given criteria. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#events-get-apitrackerevents Events docs}
+ * get('tracker/events');
+ * @example Get the relationship between two tracker entities. The only required parameters are 'trackedEntity', 'enrollment' or 'event'. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#relationships-get-apitrackerrelationships Relationships docs}
+ * get('tracker/relationships', {
+ * trackedEntity:['F8yKM85NbxW'],
* });
*/
export function get(resourceType, query, options = {}, callback = false) {
return state => {
console.log('Preparing get operation...');
- const resolvedResourceType = expandReferences(resourceType)(state);
- const resolvedQuery = expandReferences(query)(state);
- const resolvedOptions = expandReferences(options)(state);
+ const [resolvedResourceType, resolvedQuery, resolvedOptions] =
+ expandReferences(state, resourceType, query, options);
const { params, requestConfig } = resolvedOptions;
const { configuration } = state;
@@ -472,20 +520,72 @@ export function get(resourceType, query, options = {}, callback = false) {
};
}
+/**
+ * Post data. Generic helper method for posting data of any kind to DHIS2.
+ * This can be used to create `DataValueSets`,`events`,`trackers`,etc.
+ * @public
+ * @function
+ * @param {string} resourceType - Type of resource to create. E.g. `trackedEntities`, `programs`, `events`, ...
+ * @magic resourceType $.children.resourceTypes[*]
+ * @param {Dhis2Data} data - Object which defines data that will be used to create a given instance of resource. To create a single instance of a resource, `data` must be a javascript object, and to create multiple instances of a resources, `data` must be an array of javascript objects.
+ * @param {Object} [options] - Optional `options` to define URL parameters via params (E.g. `filter`, `dimension` and other import parameters), request config (E.g. `auth`) and the DHIS2 apiVersion.
+ * @param {function} [callback] - Optional callback to handle the response
+ * @returns {Operation} state
+ * @example Create an event
+ * post("tracker", {
+ * events: [
+ * {
+ * program: "eBAyeGv0exc",
+ * orgUnit: "DiszpKrYNg8",
+ * status: "COMPLETED",
+ * },
+ * ],
+ * });
+ */
+export function post(
+ resourceType,
+ data,
+ query,
+ options = {},
+ callback = s => s
+) {
+ return state => {
+ console.log('Preparing post operation...');
+
+ const [resolvedResourceType, resolvedQuery, resolvedOptions, resolvedData] =
+ expandReferences(state, resourceType, query, options, data);
+
+ const { params, requestConfig } = resolvedOptions;
+ const { configuration } = state;
+
+ return request(configuration, {
+ method: 'post',
+ url: generateUrl(configuration, resolvedOptions, resolvedResourceType),
+ params: { ...resolvedQuery, ...params },
+ responseType: 'json',
+ data: resolvedData,
+ ...requestConfig,
+ }).then(result => {
+ console.log(`Created ${resolvedResourceType}`);
+ return handleResponse(result, state, callback);
+ });
+ };
+}
+
/**
* Upsert a record. A generic helper function used to atomically either insert a row, or on the basis of the row already existing, UPDATE that existing row instead.
* @public
* @function
- * @param {string} resourceType - The type of a resource to `upsert`. E.g. `trackedEntityInstances`
+ * @param {string} resourceType - The type of a resource to `upsert`. E.g. `trackedEntities`
* @param {Object} query - A query object that allows to uniquely identify the resource to update. If no matches found, then the resource will be created.
* @param {Object} data - The data to use for update or create depending on the result of the query.
* @param {{ apiVersion: object, requestConfig: object, params: object }} [options] - Optional configuration that will be applied to both the `get` and the `create` or `update` operations.
* @param {function} [callback] - Optional callback to handle the response
* @throws {RangeError} - Throws range error
* @returns {Operation}
- * @example Example `expression.js` of upsert
- * upsert('trackedEntityInstances', {
- * ou: 'TSyzvBiovKh',
+ * @example Upsert a trackedEntity
+ * upsert('trackedEntities', {
+ * orgUnit: 'TSyzvBiovKh',
* filter: ['w75KJ2mc4zz:Eq:Qassim'],
* }, {
* orgUnit: 'TSyzvBiovKh',
@@ -503,19 +603,32 @@ export function upsert(
query, // query supplied to the `get`
data, // data supplied to the `create/update`
options = {}, // options supplied to both the `get` and the `create/update`
- callback = false // callback for the upsert itself.
+ callback = s => s // callback for the upsert itself.
) {
return state => {
- console.log(`Preparing upsert via 'get' then 'create' OR 'update'...`);
-
- // NOTE: that these parameters are all expanded by the `get`, `create`, and
- // `update` functions used inside this composed "upsert" function.
- return get(
- resourceType,
- query,
- options
- )(state)
- .then(resp => {
+ const [resolvedResourceType, resolvedOptions, resolvedData] =
+ expandReferences(state, resourceType, options, data);
+
+ let promise;
+
+ if (shouldUseNewTracker(resolvedResourceType)) {
+ const { configuration } = state;
+ promise = callNewTracker(
+ 'create_and_update',
+ configuration,
+ resolvedOptions,
+ resolvedResourceType,
+ resolvedData
+ );
+ } else {
+ // NOTE: that these parameters are all expanded by the `get`, `create`, and
+ // `update` functions used inside this composed "upsert" function.
+ console.log(`Preparing upsert via 'get' then 'create' OR 'update'...`);
+ promise = get(
+ resourceType,
+ query,
+ options
+ )(state).then(resp => {
const resources = resp.data[resourceType];
if (resources.length > 1) {
throw new RangeError(
@@ -530,11 +643,13 @@ export function upsert(
const path = resources[0][selectId(resourceType)];
return update(resourceType, path, data, options)(state);
}
- })
- .then(result => {
- console.log(`Performed a "composed upsert" on ${resourceType}`);
- return handleResponse(result, state, callback);
});
+ }
+
+ return promise.then(result => {
+ console.log(`Performed a "composed upsert" on ${resourceType}`);
+ return handleResponse(result, state, callback);
+ });
};
}
@@ -543,10 +658,10 @@ export function upsert(
* @public
* @function
* @param {string} httpMethod - The HTTP to inspect parameter usage for a given endpoint, e.g., `get`, `post`,`put`,`patch`,`delete`
- * @param {string} endpoint - The path for a given endpoint. E.g. `/trackedEntityInstances` or `/dataValueSets`
+ * @param {string} endpoint - The path for a given endpoint. E.g. `/trackedEntities` or `/dataValueSets`
* @returns {Operation}
* @example a list of parameters allowed on a given endpoint for specific http method
- * discover('post', '/trackedEntityInstances')
+ * discover('post', '/trackedEntities')
*/
export function discover(httpMethod, endpoint) {
return state => {
@@ -654,15 +769,13 @@ export function patch(
path,
data,
options = {},
- callback = false
+ callback = s => s
) {
return state => {
console.log('Preparing patch operation...');
- const resolvedResourceType = expandReferences(resourceType)(state);
- const resolvedPath = expandReferences(path)(state);
- const resolvedData = expandReferences(data)(state);
- const resolvedOptions = expandReferences(options)(state);
+ const [resolvedResourceType, resolvedPath, resolvedData, resolvedOptions] =
+ expandReferences(state, resourceType, path, data, options);
const { params, requestConfig } = resolvedOptions;
const { configuration } = state;
@@ -689,14 +802,14 @@ export function patch(
* Delete a record. A generic helper function to delete an object
* @public
* @function
- * @param {string} resourceType - The type of resource to be deleted. E.g. `trackedEntityInstances`, `organisationUnits`, etc.
+ * @param {string} resourceType - The type of resource to be deleted. E.g. `trackedEntities`, `organisationUnits`, etc.
* @param {string} path - Can be an `id` of an `object` or `path` to the `nested object` to `delete`.
* @param {Object} [data] - Optional. This is useful when you want to remove multiple objects from a collection in one request. You can send `data` as, for example, `{"identifiableObjects": [{"id": "IDA"}, {"id": "IDB"}, {"id": "IDC"}]}`. See more {@link https://docs.dhis2.org/2.34/en/dhis2_developer_manual/web-api.html#deleting-objects on DHIS2 API docs}
* @param {{apiVersion: number,operationName: string,resourceType: string}} [options] - Optional `options` for `del` operation including params e.g. `{preheatCache: true, strategy: 'UPDATE', mergeMode: 'REPLACE'}`. Run `discover` or see {@link https://docs.dhis2.org/2.34/en/dhis2_developer_manual/web-api.html#create-update-parameters DHIS2 documentation}. Defaults to `{operationName: 'delete', apiVersion: state.configuration.apiVersion, responseType: 'json'}`
* @param {function} [callback] - Optional callback to handle the response
* @returns {Operation}
- * @example a tracked entity instance
- * destroy('trackedEntityInstances', 'LcRd6Nyaq7T');
+ * @example a tracked entity instance. See {@link https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-241/tracker.html#webapi_nti_import Delete tracker docs}
+ * destroy('trackedEntities', 'LcRd6Nyaq7T');
*/
export function destroy(
resourceType,
@@ -708,26 +821,37 @@ export function destroy(
return state => {
console.log('Preparing destroy operation...');
- const resolvedResourceType = expandReferences(resourceType)(state);
- const resolvedPath = expandReferences(path)(state);
- const resolvedData = expandReferences(data)(state);
- const resolvedOptions = expandReferences(options)(state);
+ const [resolvedResourceType, resolvedPath, resolvedData, resolvedOptions] =
+ expandReferences(state, resourceType, path, data, options);
const { params, requestConfig } = resolvedOptions;
const { configuration } = state;
- return request({
- method: 'delete',
- url: generateUrl(
+ let promise;
+ if (shouldUseNewTracker(resolvedResourceType)) {
+ promise = callNewTracker(
+ 'delete',
configuration,
resolvedOptions,
resolvedResourceType,
- resolvedPath
- ),
- params,
- resolvedData,
- ...requestConfig,
- }).then(result => {
+ resolvedData
+ );
+ } else {
+ promise = request({
+ method: 'delete',
+ url: generateUrl(
+ configuration,
+ resolvedOptions,
+ resolvedResourceType,
+ resolvedPath
+ ),
+ params,
+ resolvedData,
+ ...requestConfig,
+ });
+ }
+
+ return promise.then(result => {
console.log(`Deleted ${resolvedResourceType} at ${resolvedPath}`);
return handleResponse(result, state, callback);
});
@@ -738,23 +862,20 @@ export function destroy(
* Gets an attribute value by its case-insensitive display name
* @public
* @example
- * findAttributeValue(state.data.trackedEntityInstances[0], 'first name')
+ * findAttributeValue(state.data.trackedEntities[0], 'first name')
* @function
- * @param {Object} trackedEntityInstance - A tracked entity instance (TEI) object
+ * @param {Object} trackedEntity - A tracked entity instance (TEI) object
* @param {string} attributeDisplayName - The 'displayName' to search for in the TEI's attributes
* @returns {string}
*/
-export function findAttributeValue(
- trackedEntityInstance,
- attributeDisplayName
-) {
- return trackedEntityInstance?.attributes?.find(
+export function findAttributeValue(trackedEntity, attributeDisplayName) {
+ return trackedEntity?.attributes?.find(
a => a?.displayName.toLowerCase() == attributeDisplayName.toLowerCase()
)?.value;
}
/**
- * Converts an attribute ID and value into a DSHI2 attribute object
+ * Converts an attribute ID and value into a DHIS2 attribute object
* @public
* @example
* attr('w75KJ2mc4zz', 'Elias')
@@ -768,7 +889,7 @@ export function attr(attribute, value) {
}
/**
- * Converts a dataElement and value into a DSHI2 dataValue object
+ * Converts a dataElement and value into a DHIS2 dataValue object
* @public
* @example
* dv('f7n9E0hX8qk', 12)
@@ -781,11 +902,51 @@ export function dv(dataElement, value) {
return { dataElement, value };
}
+function callNewTracker(
+ type = 'update',
+ configuration,
+ options,
+ resourceType,
+ data = {}
+) {
+ const { params, requestConfig, ...opts } = options;
+ let importStrategy;
+ switch (type) {
+ case 'create':
+ importStrategy = 'CREATE';
+ break;
+ case 'update':
+ importStrategy = 'UPDATE';
+ break;
+ case 'delete':
+ importStrategy = 'DELETE';
+ break;
+ default:
+ importStrategy = 'CREATE_AND_UPDATE';
+ }
+
+ return request(configuration, {
+ method: 'post',
+ url: generateUrl(
+ configuration,
+ {
+ ...opts,
+ importStrategy,
+ },
+ 'tracker'
+ ),
+ params: { async: false, ...params },
+ data: ensureArray(data, resourceType),
+ ...requestConfig,
+ });
+}
+
export {
alterState,
dataPath,
dataValue,
dateFns,
+ cursor,
each,
field,
fields,
diff --git a/packages/dhis2/src/Utils.js b/packages/dhis2/src/Utils.js
index a1a3bd5ff..53d96c3aa 100644
--- a/packages/dhis2/src/Utils.js
+++ b/packages/dhis2/src/Utils.js
@@ -1,5 +1,11 @@
import { composeNextState } from '@openfn/language-common';
+export function shouldUseNewTracker(resourceType) {
+ return /^(enrollments|relationships|events|trackedEntities)$/.test(
+ resourceType
+ );
+}
+
export const CONTENT_TYPES = {
xml: 'application/xml',
json: 'application/json',
@@ -43,10 +49,8 @@ export function prettyJson(data) {
return JSON.stringify(data, null, 2);
}
-const isArray = variable => !!variable && variable.constructor === Array;
-
-export function nestArray(data, key) {
- return isArray(data) ? { [key]: data } : data;
+export function ensureArray(data, key) {
+ return Array.isArray(data) ? { [key]: data } : { [key]: [data] };
}
export function generateUrl(configuration, options, resourceType, path = null) {
diff --git a/packages/dhis2/test/index.test.js b/packages/dhis2/test/index.test.js
index 1f5aff40d..75c33eb21 100644
--- a/packages/dhis2/test/index.test.js
+++ b/packages/dhis2/test/index.test.js
@@ -1,7 +1,12 @@
import chai from 'chai';
import { execute, create, update, get, upsert } from '../src/Adaptor';
import { dataValue } from '@openfn/language-common';
-import { buildUrl, generateUrl, nestArray } from '../src/Utils';
+import {
+ buildUrl,
+ generateUrl,
+ ensureArray,
+ shouldUseNewTracker,
+} from '../src/Utils';
import nock from 'nock';
const { expect } = chai;
@@ -142,6 +147,33 @@ describe('get', () => {
});
});
+describe('helperfunctions', () => {
+ it('should use the new tracker for enrollments', () => {
+ const result = shouldUseNewTracker('enrollments');
+ expect(result).to.be.true;
+ });
+
+ it('should use the new tracker for events', () => {
+ const result = shouldUseNewTracker('events');
+ expect(result).to.be.true;
+ });
+
+ it('should use the new tracker for trackedEntities', () => {
+ const result = shouldUseNewTracker('trackedEntities');
+ expect(result).to.be.true;
+ });
+
+ it('should use the old API for dataValueSets', () => {
+ const result = shouldUseNewTracker('dataValueSets');
+ expect(result).to.be.false;
+ });
+
+ it('should use the old API for dataElements', () => {
+ const result = shouldUseNewTracker('dataElements');
+ expect(result).to.be.false;
+ });
+});
+
describe('create', () => {
const state = {
configuration: {
@@ -152,6 +184,7 @@ describe('create', () => {
data: {
program: 'program1',
orgUnit: 'org50',
+ trackedEntityType: 'nEenWmSyUEp',
status: 'COMPLETED',
date: '02-02-20',
},
@@ -159,12 +192,18 @@ describe('create', () => {
it('should make an authenticated POST to the right url', async () => {
testServer
- .post('/api/events', {
- program: 'program1',
- orgUnit: 'org50',
- status: 'COMPLETED',
- date: '02-02-20',
+ .post('/api/tracker', {
+ events: [
+ {
+ program: 'program1',
+ orgUnit: 'org50',
+ trackedEntityType: 'nEenWmSyUEp',
+ status: 'COMPLETED',
+ date: '02-02-20',
+ },
+ ],
})
+ .query({ async: false })
.times(2)
.matchHeader('authorization', 'Basic YWRtaW46ZGlzdHJpY3Q=')
.reply(200, {
@@ -172,7 +211,9 @@ describe('create', () => {
message: 'the response',
});
- const finalState = await execute(create('events', state.data))(state);
+ const finalState = await execute(create('events', state => state.data))(
+ state
+ );
expect(finalState.data).to.eql({
httpStatus: 'OK',
@@ -182,10 +223,15 @@ describe('create', () => {
it('should recursively expand references', async () => {
testServer
- .post('/api/events', {
- program: 'abc',
- orgUnit: 'org50',
+ .post('/api/tracker', {
+ events: [
+ {
+ program: 'abc',
+ orgUnit: 'org50',
+ },
+ ],
})
+ .query({ async: false })
.reply(200, {
httpStatus: 'OK',
message: 'the response',
@@ -202,6 +248,85 @@ describe('create', () => {
});
});
+describe('post', () => {
+ const state = {
+ configuration: {
+ username: 'admin',
+ password: 'district',
+ hostUrl: 'https://play.dhis2.org/2.36.4',
+ },
+ data: {
+ program: 'program1',
+ orgUnit: 'org50',
+ trackedEntityType: 'nEenWmSyUEp',
+ status: 'COMPLETED',
+ date: '02-02-20',
+ },
+ };
+
+ it('should make an authenticated POST to the right url', async () => {
+ testServer
+ .post('/api/tracker', {
+ events: [
+ {
+ program: 'program1',
+ orgUnit: 'org50',
+ trackedEntityType: 'nEenWmSyUEp',
+ status: 'COMPLETED',
+ date: '02-02-20',
+ },
+ ],
+ })
+ .times(2)
+ .matchHeader('authorization', 'Basic YWRtaW46ZGlzdHJpY3Q=')
+ .reply(200, {
+ httpStatus: 'OK',
+ message: 'the response',
+ });
+
+ const finalState = await execute(
+ create('tracker', { events: [state.data] })
+ )(state);
+
+ expect(finalState.data).to.eql({
+ httpStatus: 'OK',
+ message: 'the response',
+ });
+ });
+
+ it('should recursively expand references', async () => {
+ testServer
+ .post('/api/tracker', {
+ relationships: [
+ {
+ program: 'abc',
+ orgUnit: 'org50',
+ },
+ ],
+ })
+ .reply(200, {
+ httpStatus: 'OK',
+ message: 'the response',
+ });
+
+ const finalState = await execute(
+ create('tracker', {
+ relationships: [
+ {
+ program: 'abc',
+ orgUnit: state => state.data.orgUnit,
+ },
+ ],
+ })
+ )(state);
+
+ expect(finalState.data).to.eql({
+ httpStatus: 'OK',
+ message: 'the response',
+ });
+ });
+});
+
describe('update', () => {
const state = {
configuration: {
@@ -219,7 +344,7 @@ describe('update', () => {
it('should make an authenticated PUT to the right url', async () => {
testServer
- .put('/api/events/qAZJCrNJK8H')
+ .put('/api/dataValueSets/AsQj6cDsUq4')
.matchHeader('authorization', 'Basic YWRtaW46ZGlzdHJpY3Q=')
.reply(200, {
httpStatus: 'OK',
@@ -227,7 +352,7 @@ describe('update', () => {
});
const finalState = await execute(
- update('events', 'qAZJCrNJK8H', state => ({
+ update('dataValueSets', 'AsQj6cDsUq4', state => ({
...state.data,
date: state.data.currentDate,
}))
@@ -241,7 +366,7 @@ describe('update', () => {
it('should recursively expand refs', async () => {
testServer
- .put('/api/events/qAZJCrNJK8H', {
+ .put('/api/dataValueSets/AsQj6cDsUq4', {
program: 'program',
orgUnit: 'hardcoded',
date: '02-02-20',
@@ -252,7 +377,7 @@ describe('update', () => {
});
const finalState = await execute(
- update('events', 'qAZJCrNJK8H', {
+ update('dataValueSets', 'AsQj6cDsUq4', {
program: dataValue('program'),
orgUnit: 'hardcoded',
date: resp => resp.data.currentDate,
@@ -281,38 +406,24 @@ describe('upsert', () => {
it('should make a get and then an update if one item is found', async () => {
testServer
- .get(
- '/api/trackedEntityInstances?ou=DiszpKrYNg8&filter=w75KJ2mc4zz:Eq:Johns&filter=zDhUuAYrxNC:Eq:Doe'
- )
+ .get('/api/dataValueSets?orgUnit=DiszpKrYNg8')
.reply(200, {
httpStatus: 'OK',
message: 'the response',
- trackedEntityInstances: [{ trackedEntityInstance: 123 }],
+ dataValueSets: [{ id: 123 }],
})
- .put('/api/trackedEntityInstances/123')
+ .put('/api/dataValueSets/123')
.reply(200, { httpStatus: 'OK', message: 'updated tei' });
const finalState = await execute(
upsert(
- 'trackedEntityInstances',
+ 'dataValueSets',
{
- ou: 'DiszpKrYNg8',
- filter: ['w75KJ2mc4zz:Eq:Johns', 'zDhUuAYrxNC:Eq:Doe'],
+ orgUnit: 'DiszpKrYNg8',
},
{
orgUnit: 'DiszpKrYNg8',
trackedEntityType: 'nEenWmSyUEp',
- attributes: [
- {
- lastUpdated: '2016-01-12T00:00:00.000',
- code: 'MMD_PER_NAM',
- displayName: 'First name',
- created: '2016-01-12T00:00:00.000',
- valueType: 'TEXT',
- attribute: 'w75KJ2mc4zz',
- value: 'Elias',
- },
- ],
}
)
)(state);
@@ -332,38 +443,24 @@ describe('upsert', () => {
it('should make a get and then a create if nothing is found', async () => {
testServer
- .get(
- '/api/trackedEntityInstances?ou=DiszpKrYNg8&filter=w75KJ2mc4zz:Eq:No&filter=zDhUuAYrxNC:Eq:One'
- )
+ .get('/api/dataValueSets?orgUnit=DiszpKrYNg8')
.reply(200, {
httpStatus: 'OK',
message: 'the response',
- trackedEntityInstances: [],
+ dataValueSets: [],
})
- .post('/api/trackedEntityInstances')
+ .post('/api/dataValueSets')
.reply(201, { httpStatus: 'OK', message: 'created tei' });
const finalState = await execute(
upsert(
- 'trackedEntityInstances',
+ 'dataValueSets',
{
- ou: 'DiszpKrYNg8',
- filter: ['w75KJ2mc4zz:Eq:No', 'zDhUuAYrxNC:Eq:One'],
+ orgUnit: 'DiszpKrYNg8',
},
{
orgUnit: 'DiszpKrYNg8',
trackedEntityType: 'nEenWmSyUEp',
- attributes: [
- {
- lastUpdated: '2016-01-12T00:00:00.000',
- code: 'MMD_PER_NAM',
- displayName: 'First name',
- created: '2016-01-12T00:00:00.000',
- valueType: 'TEXT',
- attribute: 'w75KJ2mc4zz',
- value: 'Elias',
- },
- ],
}
)
)(state);
@@ -382,19 +479,11 @@ describe('upsert', () => {
});
it('should make a get and FAIL if more than one thing is found', async () => {
- testServer
- .get(
- '/api/trackedEntityInstances?ou=DiszpKrYNg8&filter=w75KJ2mc4zz:Eq:John&filter=zDhUuAYrxNC:Eq:Doe'
- )
- .reply(200, {
- httpStatus: 'OK',
- message: 'the response',
- trackedEntityInstances: [
- { trackedEntityInstance: 1 },
- { trackedEntityInstance: 2 },
- { trackedEntityInstance: 3 },
- ],
- });
+ testServer.get('/api/dataValueSets?orgUnit=DiszpKrYNg8').reply(200, {
+ httpStatus: 'OK',
+ message: 'the response',
+ dataValueSets: [{ id: 1 }, { id: 2 }, { id: 3 }],
+ });
const expectThrowsAsync = async (method, errorMessage) => {
let error = null;
@@ -413,26 +502,69 @@ describe('upsert', () => {
() =>
execute(
upsert(
- 'trackedEntityInstances',
+ 'dataValueSets',
{
- ou: 'DiszpKrYNg8',
- filter: ['w75KJ2mc4zz:Eq:John', 'zDhUuAYrxNC:Eq:Doe'],
+ orgUnit: 'DiszpKrYNg8',
},
{
orgUnit: 'TSyzvBiovKh',
trackedEntityType: 'nEenWmSyUEp',
- attributes: [
- {
- attribute: 'w75KJ2mc4zz',
- value: 'Qassim',
- },
- ],
}
)
)(state),
'Cannot upsert on Non-unique attribute. The operation found more than one records for your request.'
);
});
+
+ it('should make a post only when new tracker is called', async () => {
+ testServer
+ .post('/api/tracker', {
+ events: [
+ {
+ orgUnit: 'DiszpKrYNg8',
+ trackedEntityType: 'nEenWmSyUEp',
+ attributes: [
+ {
+ attribute: 'w75KJ2mc4zz',
+ value: 'Qassim',
+ },
+ ],
+ },
+ ],
+ })
+ .query({ async: false })
+ .reply(200, {
+ httpStatus: 'OK',
+ message: 'created tei',
+ });
+
+ const finalState = await execute(
+ upsert(
+ 'events',
+ {
+ orgUnit: 'DiszpKrYNg8',
+ trackedEntities: ['F8yKM85NbxW'],
+ },
+ [
+ {
+ orgUnit: 'DiszpKrYNg8',
+ trackedEntityType: 'nEenWmSyUEp',
+ attributes: [
+ {
+ attribute: 'w75KJ2mc4zz',
+ value: 'Qassim',
+ },
+ ],
+ },
+ ]
+ )
+ )(state);
+
+ expect(finalState.data).to.eql({
+ httpStatus: 'OK',
+ message: 'created tei',
+ });
+ });
});
describe('URL builders', () => {
@@ -529,7 +661,7 @@ describe('URL builders', () => {
});
});
-describe('nestArray', () => {
+describe('ensureArray', () => {
it('when an array is passed it gets nested inside that "entity" key', async () => {
const state = {
configuration: {
@@ -541,7 +673,7 @@ describe('nestArray', () => {
data: [{ a: 1 }],
};
- const body = nestArray(state.data, 'events');
+ const body = ensureArray(state.data, 'events');
expect(body).to.eql({ events: [{ a: 1 }] });
});
@@ -556,8 +688,8 @@ describe('nestArray', () => {
data: { b: 2 },
};
- const body = nestArray(state.data, 'events');
+ const body = ensureArray(state.data, 'events');
- expect(body).to.eql({ b: 2 });
+ expect(body).to.eql({ events: [{ b: 2 }] });
});
});
diff --git a/packages/dhis2/test/integration.js b/packages/dhis2/test/integration.js
index 684ce59a4..c13df5e1c 100644
--- a/packages/dhis2/test/integration.js
+++ b/packages/dhis2/test/integration.js
@@ -265,20 +265,20 @@ describe('Integration tests', () => {
it('should get a single TEI based on multiple filters', async () => {
const finalState = await execute(
- get('trackedEntityInstances', {
- program: 'IpHINAT79UW',
- ou: 'DiszpKrYNg8',
- filter: ['w75KJ2mc4zz:Eq:Sophia', 'zDhUuAYrxNC:Eq:Jackson'],
+ get('tracker/trackedEntities', {
+ program: 'fDd25txQckK',
+ orgUnit: 'DiszpKrYNg8',
+ filter: ['w75KJ2mc4zz:Eq:Elanor'],
})
)(state);
- expect(finalState.data.trackedEntityInstances.length).to.eq(1);
+ expect(finalState.data.instances.length).to.eq(1);
const finalState2 = await execute(
get('trackedEntityInstances', {
- program: 'IpHINAT79UW',
+ program: 'fDd25txQckK',
ou: 'DiszpKrYNg8',
- filter: ['w75KJ2mc4zz:Eq:Sophia', 'zDhUuAYrxNC:Eq:NotJackson'],
+ filter: ['w75KJ2mc4zz:Eq:Elanor', 'zDhUuAYrxNC:Eq:NotJackson'],
})
)(state);
@@ -426,4 +426,4 @@ describe('Integration tests', () => {
);
});
});
-});
+});
\ No newline at end of file