Skip to content
This repository has been archived by the owner on Oct 21, 2022. It is now read-only.

Commit

Permalink
Merge pull request #52 from OpenFn/48-upsert
Browse files Browse the repository at this point in the history
  • Loading branch information
taylordowns2000 committed Dec 24, 2021
2 parents 9acd3e3 + d3fe349 commit 89c2e07
Show file tree
Hide file tree
Showing 7 changed files with 570 additions and 283 deletions.
123 changes: 123 additions & 0 deletions ast.json
Expand Up @@ -360,6 +360,129 @@
},
"valid": true
},
{
"name": "upsert",
"params": [
"resourceType",
"query",
"data",
"options",
"callback"
],
"docs": {
"description": "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.",
"tags": [
{
"title": "public",
"description": null,
"type": null
},
{
"title": "function",
"description": null,
"name": null
},
{
"title": "param",
"description": "The type of a resource to `upsert`. E.g. `trackedEntityInstances`",
"type": {
"type": "NameExpression",
"name": "string"
},
"name": "resourceType"
},
{
"title": "param",
"description": "A query object that allows to uniquely identify the resource to update. If no matches found, then the resource will be created.",
"type": {
"type": "NameExpression",
"name": "Object"
},
"name": "query"
},
{
"title": "param",
"description": "The data to use for update or create depending on the result of the query.",
"type": {
"type": "NameExpression",
"name": "Object"
},
"name": "data"
},
{
"title": "param",
"description": "Optional configuration that will be applied to both the `get` and the `create` or `update` operations.",
"type": {
"type": "OptionalType",
"expression": {
"type": "RecordType",
"fields": [
{
"type": "FieldType",
"key": "apiVersion",
"value": {
"type": "NameExpression",
"name": "object"
}
},
{
"type": "FieldType",
"key": "requestConfig",
"value": {
"type": "NameExpression",
"name": "object"
}
},
{
"type": "FieldType",
"key": "params",
"value": {
"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": "throws",
"description": "Throws range error",
"type": {
"type": "NameExpression",
"name": "RangeError"
}
},
{
"title": "returns",
"description": null,
"type": {
"type": "NameExpression",
"name": "Operation"
}
},
{
"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"
}
]
},
"valid": true
},
{
"name": "discover",
"params": [
Expand Down
106 changes: 58 additions & 48 deletions lib/Adaptor.js
Expand Up @@ -7,6 +7,7 @@ exports.execute = execute;
exports.create = create;
exports.update = update;
exports.get = get;
exports.upsert = upsert;
exports.discover = discover;
exports.patch = patch;
exports.destroy = destroy;
Expand Down Expand Up @@ -537,55 +538,64 @@ function get(resourceType, query, options = {}, callback = false) {
return (0, _Utils.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 `insert` or `update`. E.g. `trackedEntityInstances`
// * @param {Object} data - The update data containing new values
// * @param {{replace:boolean, apiVersion: number,strict: boolean,responseType: string}} [options] - `Optional` options for `upsertTEI` operation. Defaults to `{replace: false, apiVersion: state.configuration.apiVersion,strict: true,responseType: 'json'}`.
// * @param {function} [callback] - Optional callback to handle the response
// * @throws {RangeError} - Throws range error
// * @returns {Operation}
// * @example <caption>Example `expression.js` of upsert</caption>
// * upsert(
// * 'trackedEntityInstances',
// * {
// * attributeId: 'lZGmxYbs97q',
// * attributeValue: state =>
// * state.data.attributes.find(obj => obj.attribute === 'lZGmxYbs97q')
// * .value,
// * },
// * state.data,
// * { ou: 'TSyzvBiovKh' }
// * );
// */
// export function upsert(resourceType, data, options = {}, callback = false) {
// return state => {
// console.log(`Preparing upsert via 'get' then 'create' OR 'update'...`);
// return get(
// resourceType,
// data
// )(state).then(resp => {
// const resources = resp.data[resourceType];
// if (resources.length > 1) {
// throw new RangeError(
// `Cannot upsert on Non-unique attribute. The operation found more than one records for your request.`
// );
// } else if (resources.length <= 0) {
// return create(resourceType, data, options, callback)(state);
// } else {
// const pathName =
// resourceType === 'trackedEntityInstances'
// ? 'trackedEntityInstance'
// : 'id';
// const path = resources[0][pathName];
// return update(resourceType, path, data, options, callback)(state);
// }
// });
// };
// }
}
/**
* 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 {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 <caption>Example `expression.js` of upsert</caption>
* upsert('trackedEntityInstances', {
* ou: 'TSyzvBiovKh',
* filter: ['w75KJ2mc4zz:Eq:Qassim'],
* }, {
* orgUnit: 'TSyzvBiovKh',
* trackedEntityType: 'nEenWmSyUEp',
* attributes: [
* {
* attribute: 'w75KJ2mc4zz',
* value: 'Qassim',
* },
* ],
* });
*/


function upsert(resourceType, // resourceType supplied to both the `get` and the `create/update`
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.
) {
return state => {
console.log(`Preparing upsert via 'get' then 'create' OR 'update'...`);
return get(resourceType, query, options)(state).then(resp => {
const resources = resp.data[resourceType];

if (resources.length > 1) {
throw new RangeError(`Cannot upsert on Non-unique attribute. The operation found more than one records for your request.`);
} else if (resources.length <= 0) {
return create(resourceType, data, options)(state);
} else {
// Pick out the first (and only) resource in the array and grab its
// ID to be used in the subsequent `update` by the path determined
// by the `selectId(...)` function.
const path = resources[0][(0, _Utils.selectId)(resourceType)];
return update(resourceType, path, data, options)(state);
}
}).then(result => {
_Utils.Log.success(`Performed a "composed upsert" on ${resourceType}`);

return (0, _Utils.handleResponse)(result, state, callback);
});
};
}
/**
* Discover `DHIS2` `api` `endpoint` `query parameters` and allowed `operators` for a given resource's endpoint.
* @public
Expand Down
20 changes: 20 additions & 0 deletions lib/Utils.js
Expand Up @@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildUrl = buildUrl;
exports.selectId = selectId;
exports.handleResponse = handleResponse;
exports.prettyJson = prettyJson;
exports.nestArray = nestArray;
Expand Down Expand Up @@ -41,6 +42,25 @@ exports.Log = Log;
function buildUrl(urlString, hostUrl, apiVersion) {
const pathSuffix = apiVersion ? `/${apiVersion}${urlString}` : `${urlString}`;
return hostUrl + '/api' + pathSuffix;
}
/**
* Determines the attribute name for a DHIS2 system ID given a resource type.
* @param {string} resourceType
* @returns {string}
*/


function selectId(resourceType) {
switch (resourceType) {
case 'trackedEntityInstances':
return 'trackedEntityInstance';
// We can extend here if we find other special kinds of resourceType
// case 'other-special-case':
// return 'other-special-id';

default:
return 'id';
}
} // Write a unit test for this one


Expand Down

0 comments on commit 89c2e07

Please sign in to comment.