A community user recently asked how we might use OpenFn to fetch data from CommCare. Per their API documentation, we can see that there are multiple API endpoints to choose from to work with case
or form
data.
For example, if we want to get case
data, we can leverage the List Cases
API endpoint and use the OpenFn http
adaptor to send a GET request to extract data.
fn(state => {
//Let's add an fn(...) block to build our query URL
//see CommCare API docs for request parameter options
const caseType = 'Household'; //define case type(s) you have in CommCare
const limit = 1000; //define how many to extract; max limit is 1k, so you may need to use paging to get more
const startDate = '2023-05-31T14:00:00'; //define extract start date
const url = `${state.configuration.baseURl}?type=${caseType}&indexed_on_start=${startDate}&limit=${limit}`;
return { ...state, url };
});
get(url, {
headers: { 'content-type': 'application/json' },
});
Now if you are expecting to query more than 1000 records (the CommCare API limit), then the CommCare API will use paging to return only 1k records per page. he trick is that then you will need to send multiple GET requests to page through the API and extract all data (e.g., if there are 1200 cases, the API will separate the data across 2 pages, and you would need to send 2 GET requests to get all of the data).
See the code below for an example job that will dynamically send GET requests depending on the # of total pages returned by the CommCare API.
fn(state => {
const { baseUrl } = state.configuration;
const caseTypes = ['Household', 'Patient']; //define case type(s) you have in CommCare
const limit = 1000; //define how many to extract; max limit is 1k, so you may need to use paging to get more
const startDate = '2023-05-31T14:00:00'; //define extract start date
const queries = caseTypes.map(
t => `?type=${t}&indexed_on_start=${startDate}&limit=${limit}`
);
return { ...state, queries, baseUrl, payloads: [] };
});
// create a "recursiveGet" which will call itself again if CommCare tells us there's
// more data to fetch; so if there are 1,200 records --> then 2 GET requests will be sent
fn(state => {
const recursiveGet = url =>
get(
url,
{
headers: { 'content-type': 'application/json' },
},
nextState => {
const now = new Date();
const { baseUrl, data, payloads } = nextState;
const { meta, objects } = data;
console.log('Metadata from CommCare response:', meta);
const finalState = {
...nextState,
payloads: [...payloads, ...objects],
};
if (meta.next) {
console.log('Another query detected, recursing...');
return recursiveGet(`${baseUrl}${meta.next}`)(finalState);
}
finalState.lastRunAt = now.toISOString().slice(0, 19);
return finalState;
}
);
return { ...state, recursiveGet };
});
// for each initial query, fetch data recursively
each(
'$.queries[*]',
fn(state => {
return state.recursiveGet(`${state.baseUrl}${state.data}`)(state);
})
);
// log the total number of payloads returned by CommCare
fn(state => {
console.log('Count of payloads', state.payloads.length);
return { ...state, references: [], data: {} };
});
OpenFn’s CommCare adaptor (see OpenFn Docs) doesn’t yet have a function for extracting data from CommCare, but there are available functions for submitting form submissions and/or bulk uploading case
data via CommCare’s Bulk Upload feature - see submitXls docs.