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

openmrs: add a generic http request #923

Merged
merged 11 commits into from
Jan 20, 2025
68 changes: 50 additions & 18 deletions packages/openmrs/src/Adaptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@ export function getPatient(uuid, callback = s => s) {
return async state => {
const [resolvedUuid] = expandReferences(state, uuid);
console.log(`Fetching patient by uuid: ${resolvedUuid}`);
const { instanceUrl:baseUrl } = state.configuration;

const response = await request(
state,
'GET',
`/ws/rest/v1/patient/${resolvedUuid}`
`/ws/rest/v1/patient/${resolvedUuid}`,
{
baseUrl,
}
);

console.log(`Retrieved patient with uuid: ${resolvedUuid}...`);
Expand Down Expand Up @@ -73,13 +77,15 @@ export function get(path, query, callback = s => s) {
path,
query
);

const { instanceUrl:baseUrl } = state.configuration;
const response = await request(
state,
'GET',
`/ws/rest/v1/${resolvedPath}`,
{},
resolvedQuery
{
baseUrl,
query: resolvedQuery
}
);

// TODO: later decide if we want to throw for no-results.
Expand Down Expand Up @@ -109,12 +115,15 @@ export function get(path, query, callback = s => s) {
export function post(path, data, callback = s => s) {
return async state => {
const [resolvedPath, resolvedData] = expandReferences(state, path, data);

const { instanceUrl:baseUrl } = state.configuration;
const response = await request(
state,
'POST',
`/ws/rest/v1/${resolvedPath}`,
resolvedData
{
baseUrl,
data: resolvedData,
}
);

return prepareNextState(state, response, callback);
Expand All @@ -134,15 +143,18 @@ export function post(path, data, callback = s => s) {
export function searchPatient(query, callback = s => s) {
return async state => {
const [resolvedQuery] = expandReferences(state, query);
const { instanceUrl:baseUrl } = state.configuration;

console.log('Searching for patient with query:', resolvedQuery);

const response = await request(
state,
'GET',
'/ws/rest/v1/patient',
{},
resolvedQuery
{
baseUrl,
query: resolvedQuery
},
);

return prepareNextState(state, response, callback);
Expand All @@ -162,15 +174,18 @@ export function searchPatient(query, callback = s => s) {
export function searchPerson(query, callback = s => s) {
return async state => {
const [resolvedQuery = {}] = expandReferences(state, query);
const { instanceUrl:baseUrl } = state.configuration;

console.log(`Searching for person with query:`, resolvedQuery);

const response = await request(
state,
'GET',
'/ws/rest/v1/person',
{},
resolvedQuery
{
baseUrl,
query: resolvedQuery
},
);

console.log(`Found ${response.body.results.length} people`);
Expand All @@ -193,11 +208,15 @@ export function getEncounter(uuid, callback = s => s) {
return async state => {
const [resolvedUuid] = expandReferences(state, uuid);
console.log(`Fetching encounter with UUID: ${resolvedUuid}`);
const { instanceUrl:baseUrl } = state.configuration;

const response = await request(
state,
'GET',
`/ws/rest/v1/encounter/${resolvedUuid}`
`/ws/rest/v1/encounter/${resolvedUuid}`,
{
baseUrl,
}
);

console.log(
Expand All @@ -222,13 +241,16 @@ export function getEncounters(query, callback = s => s) {
return async state => {
const [resolvedQuery] = expandReferences(state, query);
console.log('Fetching encounters by query', resolvedQuery);
const { instanceUrl:baseUrl } = state.configuration;

const response = await request(
state,
'GET',
`/ws/rest/v1/encounter/`,
{},
resolvedQuery
{
baseUrl,
query: resolvedQuery
},
);
console.log(`Found ${response.body.results.length} results`);

Expand Down Expand Up @@ -308,12 +330,16 @@ export function create(resourceType, data, callback = s => s) {
data
);
console.log('Preparing to create', resolvedResource);
const { instanceUrl:baseUrl } = state.configuration;

const response = await request(
state,
'POST',
`/ws/rest/v1/${resolvedResource}`,
resolvedData
{
baseUrl,
data: resolvedData,
}
);

console.log('Successfully created', resolvedResource);
Expand Down Expand Up @@ -344,12 +370,16 @@ export function update(resourceType, path, data, callback = s => s) {
data
);
console.log('Preparing to update', resolvedResource);
const { instanceUrl:baseUrl } = state.configuration;

const response = await request(
state,
'POST',
`/ws/rest/v1/${resolvedResource}/${resolvedPath}`,
resolvedData
{
baseUrl,
data: resolvedData,
}
);

console.log('Successfully updated', resolvedResource);
Expand Down Expand Up @@ -404,13 +434,15 @@ export function upsert(
"Preparing composed upsert (via 'get' then 'create' OR 'update') on",
resolvedResource
);

const { instanceUrl:baseUrl } = state.configuration;
return await request(
state,
'GET',
`/ws/rest/v1/${resolvedResource}`,
{},
resolvedQuery
{
baseUrl,
query: resolvedQuery
},
).then(resp => {
const resource = resp.body.results;
if (resource.length > 1) {
Expand Down
59 changes: 46 additions & 13 deletions packages/openmrs/src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import {
logResponse,
} from '@openfn/language-common/util';

/**
* Options object
* @global
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably remove this if we're not keeping it

* @typedef {Object} OpenMRSOptions
* @property {object} query - An object of query parameters to be encoded into the URL
* @property {object} headers - An object of all request headers
* @property {object} body - The request body (as JSON)
* @property {string} baseUrl - The base url for the request
* @property {string} [parseAs='json'] - The response format to parse (e.g., 'json', 'text', or 'stream')
*/

export const prepareNextState = (state, response, callback) => {
const { body, ...responseWithoutBody } = response;
const nextState = {
Expand All @@ -15,28 +26,50 @@ export const prepareNextState = (state, response, callback) => {
return callback(nextState);
};

export async function request(state, method, path, data, params) {
const { instanceUrl, username, password } = state.configuration;
const headers = makeBasicAuthHeader(username, password);
export async function request(state, method, path, options = {}) {
const {
baseUrl = 'https://demo.openmrs.org/openmrs',
query = {},
data = {},
headers = { 'content-type': 'application/json' },
parseAs = 'json',
} = options;

if (baseUrl.length <= 0) {
throw new Error(
'Invalid instanceUrl. Include instanceUrl in state.configuration'
);
}

const isAbsoluteUrl = /^(https?:|\/\/)/i.test(path);
if (isAbsoluteUrl) {
throw new Error(
`Invalid path argument: "${path}" appears to be an absolute URL. Please provide a relative path.`
);
}

const options = {
const { username, password } = state.configuration;
const authHeaders = makeBasicAuthHeader(username, password);

const opts = {
body: data,
headers: {
...authHeaders,
...headers,
'content-type': 'application/json',
},
query: params,
parseAs: 'json',
query,
parseAs,
};

const url = `${instanceUrl}${path}`;
const url = `${baseUrl}${path}`;

let allResponses;
let query = options?.query;
let allowPagination = isNaN(query?.startIndex);
let queryParams = opts?.query;
let allowPagination = isNaN(queryParams?.startIndex);

do {
const requestOptions = query ? { ...options, query } : options;
const requestOptions = queryParams ? { ...opts, queryParams } : opts;

const response = await commonRequest(method, url, requestOptions);
logResponse(response);

Expand All @@ -56,12 +89,12 @@ export async function request(state, method, path, data, params) {
const params = new URLSearchParams(urlObj.search);
const startIndex = params.get('startIndex');

query = { ...query, startIndex };
queryParams = { ...queryParams, startIndex };
} else {
delete allResponses.body.links;
break;
}
} while (allowPagination);

return allResponses;
}
}
46 changes: 46 additions & 0 deletions packages/openmrs/src/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expandReferences } from '@openfn/language-common/util';
import * as util from './Utils';

/**
* Options object
* @typedef {Object} OpenMRSOptions
josephjclark marked this conversation as resolved.
Show resolved Hide resolved
* @property {object} query - An object of query parameters to be encoded into the URL
* @property {object} headers - An object of all request headers
* @property {object} body - The request body (as JSON)
* @property {string} baseUrl - The base url for the request
hunterachieng marked this conversation as resolved.
Show resolved Hide resolved
* @property {string} [parseAs='json'] - The response format to parse (e.g., 'json', 'text', or 'stream')
*/

/**
* Make a HTTP request to any OpenMRS endpoint
* @example
* request("GET",
* "/ws/rest/v1/patient/d3f7e1a8-0114-4de6-914b-41a11fc8a1a8", {
* query:{
* limit: 1,
* offset: 20
* },
* baseUrl: "http://msf-ocg-openmrs3-dev.westeurope.cloudapp.azure.com/openmrs"
hunterachieng marked this conversation as resolved.
Show resolved Hide resolved
* });
* @function
* @public
* @param {string} method - HTTP method to use
* @param {string} path - Path to resource
* @param {OpenMRSOptions} [options={}] - An object containing either query, headers, and body for the request
hunterachieng marked this conversation as resolved.
Show resolved Hide resolved
* @returns {Operation}
*/
export function request(method, path, options = {}, callback = s => s) {
return async state => {
const [resolvedMethod, resolvedPath, resolvedOptions = {}] =
expandReferences(state, method, path, options);

const response = await util.request(
state,
resolvedMethod,
resolvedPath,
resolvedOptions
);

return util.prepareNextState(state, response, callback);
};
}
1 change: 1 addition & 0 deletions packages/openmrs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export default Adaptor;

export * from './Adaptor';
export * as fhir from './fhir';
export * as http from './http'
Loading