Skip to content

rapidpro: migrate from deprecated common-http to fetch #1166

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

Merged
merged 7 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/six-crabs-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@openfn/language-rapidpro': major
---

- Update `rapidPro` to not use deprecated `common-http`.
- Remove support and export of `axios`
- Remove export of `http` namesepace from `common`

15 changes: 13 additions & 2 deletions packages/rapidpro/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@
"operation"
],
"docs": {
"description": "Scopes an array of data based on a JSONPath.\nUseful when the source data has `n` items you would like to map to\nan operation.\nThe operation will receive a slice of the data based of each item\nof the JSONPath provided.\n\nIt also ensures the results of an operation make their way back into\nthe state's references.",
"description": "Iterates over an array of items and invokes an operation upon each one, where the state\nobject is _scoped_ so that state.data is the item under iteration.\nThe rest of the state object is untouched and can be referenced as usual.\nYou can pass an array directly, or use lazy state or a JSONPath string to\nreference a slice of state.",
"tags": [
{
"title": "public",
Expand All @@ -504,7 +504,18 @@
},
{
"title": "example",
"description": "each(\"$.[*]\",\n create(\"SObject\",\n field(\"FirstName\", sourceValue(\"$.firstName\"))\n )\n)"
"description": "each(\n $.data,\n // Inside the callback operation, `$.data` is scoped to the item under iteration\n insert(\"patient\", {\n patient_name: $.data.properties.case_name,\n patient_id: $.data.case_id,\n })\n);",
"caption": "Using lazy state ($) to iterate over items in state.data and pass each into an \"insert\" operation"
},
{
"title": "example",
"description": "each(\n $.data,\n insert(\"patient\", (state) => ({\n patient_id: state.data.case_id,\n ...state.data\n }))\n);",
"caption": "Iterate over items in state.data and pass each one into an \"insert\" operation"
},
{
"title": "example",
"description": "each(\n \"$.data[*]\",\n insert(\"patient\", (state) => ({\n patient_name: state.data.properties.case_name,\n patient_id: state.data.case_id,\n }))\n);",
"caption": "Using JSON path to iterate over items in state.data and pass each one into an \"insert\" operation"
},
{
"title": "param",
Expand Down
5 changes: 2 additions & 3 deletions packages/rapidpro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@
],
"dependencies": {
"@openfn/language-common": "workspace:*",
"axios": "^1.7.7"
"undici": "^7.8.0"
},
"devDependencies": {
"chai": "^3.4.0",
"deep-eql": "^0.1.3",
"esno": "^0.16.3",
"nock": "^12.0.3",
"rimraf": "^3.0.2",
"sinon": "^1.17.2"
},
Expand All @@ -45,4 +44,4 @@
"./package.json": "./package.json"
},
"types": "types/index.d.ts"
}
}
188 changes: 78 additions & 110 deletions packages/rapidpro/src/Adaptor.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import {
execute as commonExecute,
composeNextState,
http, // Important: this is the OLD axios-based http.
} from '@openfn/language-common';
import { expandReferences } from '@openfn/language-common/util';

const { axios } = http;
export { axios };
import { fetch } from 'undici';

/**
* Execute a sequence of operations.
Expand Down Expand Up @@ -48,31 +45,31 @@ export function execute(...operations) {
* @param {function} callback - (Optional) callback function
* @returns {Operation}
*/
export function addContact(params, callback) {
return state => {
export function addContact(params, callback = s => s) {
return async state => {
const [resolvedParams] = expandReferences(state, params);

const { host, apiVersion, token } = state.configuration;

const url = `${host}/api/${apiVersion || 'v2'}/contacts.json`;

const config = {
url,
data: resolvedParams,
headers: { Authorization: `Token ${token}` },
};
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Token ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(resolvedParams),
});

return http
.post(config)(state)
.then(response => {
console.log('Contact added with uuid:', response.data.uuid);
const nextState = {
...composeNextState(state, response.data),
response,
};
if (callback) return callback(nextState);
return nextState;
});
const result = await response.json();

console.log('Contact added with uuid:', result.uuid);

return callback({
...composeNextState(state, result),
response,
});
};
}

Expand All @@ -90,59 +87,50 @@ export function addContact(params, callback) {
* @param {function} callback - (Optional) callback function
* @returns {Operation}
*/
export function upsertContact(params, callback) {
return state => {
export function upsertContact(params, callback = s => s) {
return async state => {
const [resolvedParams] = expandReferences(state, params);

const { host, apiVersion, token } = state.configuration;

const url = `${host}/api/${apiVersion || 'v2'}/contacts.json`;

const config = {
url,
data: resolvedParams,
headers: { Authorization: `Token ${token}` },
};

return http
.post(config)(state)
.then(resp => {
console.log('Contact added with uuid:', resp.data.uuid);
return resp;
})
.catch(err => {
const { data } = err.response;
if (
data &&
data.urns &&
Array.isArray(data.urns) &&
data.urns.find(x => x.includes('URN belongs to another'))
) {
const newUrl = `${url}?urn=${config.data.urns[0]}`;
delete config.data['urns'];
return http
.post({ ...config, url: newUrl })(state)
.then(resp => {
console.log('Contact updated with uuid:', resp.data.uuid);
return resp;
});
} else {
console.log(JSON.stringify(data, null, 2));
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Token ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(resolvedParams),
});

delete err.response.request;
delete err.response.config;
let result = await response.json();
if (result && result.urns && Array.isArray(result.urns['0'])) {
const newUrl = `${url}?urn=${resolvedParams.urns[0]}`;

delete resolvedParams['urns'];
result = await fetch(newUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Token ${token}`,
},
body: JSON.stringify(resolvedParams),
});
const res = await result.json();

throw err.response;
}
})
.then(response => {
const nextState = {
...composeNextState(state, response.data),
response,
};
if (callback) return callback(nextState);
return nextState;
console.log('Contact updated with uuid:', res.uuid);
return callback({
...composeNextState(state, res),
response: {},
});
} else {
console.log('Contact added with uuid:', result.uuid);
return callback({
...composeNextState(state, result),
response: {},
});
}
};
}

Expand All @@ -160,38 +148,29 @@ export function upsertContact(params, callback) {
* @param {function} callback - (Optional) callback function
* @returns {Operation}
*/
export function startFlow(params, callback) {
return state => {
export function startFlow(params, callback = s => s) {
return async state => {
const [resolvedParams] = expandReferences(state, params);

const { host, apiVersion, token } = state.configuration;

const url = `${host}/api/${apiVersion || 'v2'}/flow_starts.json`;

const config = {
url,
data: resolvedParams,
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Token ${token}`,
'Content-Type': 'application/json',
},
};
body: JSON.stringify(resolvedParams),
});

return http
.post(config)(state)
.catch(error => {
console.log(error.response);
throw 'That was an error from RapidPro.';
})
.then(response => {
console.log('Flow started:', response.data);
const nextState = {
...composeNextState(state, response.data),
response,
};
if (callback) return callback(nextState);
return nextState;
});
const result = await response.json();
console.log('Flow started:', result);
return callback({
...composeNextState(state, result),
response,
});
};
}

Expand All @@ -209,38 +188,28 @@ export function startFlow(params, callback) {
* @param {function} callback - (Optional) callback function
* @returns {Operation}
*/
export function sendBroadcast(params, callback) {
return state => {
export function sendBroadcast(params, callback = s => s) {
return async state => {
const [resolvedParams] = expandReferences(state, params);

const { host, apiVersion, token } = state.configuration;

const url = `${host}/api/${apiVersion || 'v2'}/broadcasts.json`;

const config = {
url,
data: resolvedParams,
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Token ${token}`,
'Content-Type': 'application/json',
},
};

return http
.post(config)(state)
.catch(error => {
console.log(error.response);
throw 'That was an error from RapidPro.';
})
.then(response => {
console.log('Broadcast queued:', response.data);
const nextState = {
...composeNextState(state, response.data),
response,
};
if (callback) return callback(nextState);
return nextState;
});
body: JSON.stringify(resolvedParams),
});
const result = await response.json();
console.log('Broadcast queued:', result);
return callback({
...composeNextState(state, result),
response,
});
};
}

Expand All @@ -253,7 +222,6 @@ export {
fields,
fn,
fnIf,
http, // Important: this is the OLD axios-based http. Public docs will be incorrect.
lastReferenceValue,
merge,
sourceValue,
Expand Down
3 changes: 0 additions & 3 deletions packages/rapidpro/test/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import chai from 'chai';
const { expect } = chai;
import nock from 'nock';
const { back } = nock;
import ClientFixtures, { fixtures } from './ClientFixtures';

import Adaptor from '../src';
const { execute } = Adaptor;
Expand Down
14 changes: 8 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading