Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/pull-request-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
if: ${{ github.base_ref == 'main' }}
if: ${{ github.base_ref == 'main' && github.actor != 'dependabot[bot]' }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.DS_Store
.env
.env*

# Logs
logs
Expand Down
41 changes: 33 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,55 @@ This is a command-line app in Node.js. To run the app:
1. Install NodeJS and the Node Package Manager (npm).
2. Clone this repository.
3. Copy the file `dotenv.sample` and name the new copy `.env`.
4. Edit the `.env` file and fill in your project ID, service account name, and private key. See **Prerequisites** above for how to get this information. Be sure to include the begin/end tags “--BEGIN RSA PRIVATE KEY--“ and “--END RSA PRIVATE KEY--" in your private key, and use line breaks, looking something like this:
4. Edit the `.env` file and fill in your project ID, service account name, and private key. See **Prerequisites** above for how to get this information. See important private key format info below.
5. From the application directory, install the necessary npm libraries: `npm install`.
6. Run the script: `node quickstart.js`.

### Formatting the Private Key

NOTE! Be sure to include the begin/end tags `--BEGIN RSA PRIVATE KEY--` and `--END RSA PRIVATE KEY--` in your private key. Also format the line breaks using `\n`. The final string should look something like this:

```
RKS_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n230703de230703de230703de\nb62b0e24b62b0e24b62b0e24\n...\n-----END RSA PRIVATE KEY-----"
```

5. From the application directory, install the necessary npm libraries: `npm install`.
6. Run the script: `node quickstart.js`.
## Output

Though primarily intended to show you how to make MyDataHelps API calls, the quickstart also prints out some useful info for debugging.

### Service Token Output

If successful, the app will print out your token and number of participants in the console, like so:
First, it will print out a service token and participant count:

The service access token is only valid for a few minutes, but you can copy/paste it into a REST query tool of your choice to try out advanced queries.

```
Obtained service access token:
YOUR TOKEN HERE
SERVICE_TOKEN_HERE

Total Participants: 5
```

The service access token is only valid for a few minutes, but you can copy/paste it into a REST query tool of your choice to try out advanced queries.
### Participant Token Output

If you have participants, the quickstart will also print out the first participant's details and a participant token for them.

You can also edit the code to specify a participant identifier and get a participant access token for that participant. This token is only needed for [MyDataHelps Embeddables](https://developer.mydatahelps.org/embeddables), and is useful for testing with the [MyDataHelps Starter Kit](https://github.com/CareEvolution/MyDataHelpsStarterKit).
This token is only needed for [MyDataHelps Embeddables](https://developer.mydatahelps.org/embeddables), and is useful for testing with the [MyDataHelps Starter Kit](https://github.com/CareEvolution/MyDataHelpsStarterKit).


```
Participant Found. ID: 59348b19-6e61-4390-a91f-bd0f90a4f307
{
id: '59348b19-6e61-4390-a91f-bd0f90a4f307',
enrolled: true,
...
}

Obtained participant access token for 59348b19-6e61-4390-a91f-bd0f90a4f307: PARTICIPANT_TOKEN_HERE
```

## Troubleshooting

If you see an error when running the script, double-check the information in the Prerequisites, particularly the format of the private key.

If you have trouble getting the app to work, feel free to [contact MyDataHelps Support](https://developer.mydatahelps.org/help.html).
If you have trouble getting the app to work, feel free to [contact MyDataHelps Support](https://support.mydatahelps.org/contact-us).
2 changes: 2 additions & 0 deletions dotenv.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ RKS_SERVICE_ACCOUNT=

# You can use \n to separate the lines in your private key
RKS_PRIVATE_KEY=

RKS_ENDPOINT=https://designer.mydatahelps.org
82 changes: 56 additions & 26 deletions quickstart.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ const { v4: uuidv4 } = require('uuid');

// **NOTE!** In a real production app you would want these to be sourced from real environment variables. The .env file is just
// a convenience for development.
const rksProjectId = process.env.RKS_PROJECT_ID;
const rksServiceAccount = process.env.RKS_SERVICE_ACCOUNT;
const mdhProjectId = process.env.RKS_PROJECT_ID;
const mdhServiceAccount = process.env.RKS_SERVICE_ACCOUNT;
const privateKey = process.env.RKS_PRIVATE_KEY;

const baseUrl = 'https://designer.mydatahelps.org';
const baseUrl = process.env.RKS_ENDPOINT || "https://designer.mydatahelps.org";
const tokenUrl = `${baseUrl}/identityserver/connect/token`;

async function getServiceAccessToken() {

const assertion = {
"iss": rksServiceAccount,
"sub": rksServiceAccount,
"iss": mdhServiceAccount,
"sub": mdhServiceAccount,
"aud": tokenUrl,
"exp": Math.floor(new Date().getTime() / 1000) + 200,
"jti": uuidv4()
Expand Down Expand Up @@ -73,6 +73,29 @@ async function getFromApi(serviceAccessToken, resourceUrl, queryParams = null, r
return { data: await response.json(), status: response.status };
}

async function postToApi(serviceAccessToken, resourceUrl, queryParams = null, body = null, raiseError = true) {

const queryString = queryParams ? `?${new URLSearchParams(queryParams)}` : '';
const url = `${baseUrl}${resourceUrl}${queryString}`;

const response = await fetch(url, {
headers: {
"Authorization": `Bearer ${serviceAccessToken}`,
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8"
},
method: 'POST',
body: JSON.stringify(body)
});

if (!response.ok && raiseError) {
const error = `API call to ${url} failed. ${response.status} - ${await response.text()}`;
throw new Error(error);
}

return { data: await response.json(), status: response.status };
}

// Get a participant access token for the specified participant
// Used for MyDataHelps Embeddables ONLY
async function getParticipantAccessToken(serviceAccessToken, participantID, scopes) {
Expand Down Expand Up @@ -112,33 +135,40 @@ async function quickstart() {
console.log(serviceAccessToken);

// Get all participants
url = `/api/v1/administration/projects/${rksProjectId}/participants`;
url = `/api/v1/administration/projects/${mdhProjectId}/participants`;
response = await getFromApi(serviceAccessToken, url);
const participants = response.data;
console.log(`\nTotal Participants: ${participants.totalParticipants}`);


// Exit now if no participants found.
if (!participants["participants"]) {
return;
}

// Get first PPT for debugging
const firstParticipant = participants["participants"][0];

// Get a specific participant by identifier. We disable 'raiseError' here
// so we can handle the 404 case ourselves.
const participantIdentifier = "YOUR_PARTICIPANT_IDENTIFIER"
const participantIdentifier = firstParticipant["participantIdentifier"];

if (participantIdentifier != "YOUR_PARTICIPANT_IDENTIFIER") {
url = `/api/v1/administration/projects/${rksProjectId}/participants/${participantIdentifier}`;
response = await getFromApi(serviceAccessToken, url, null, false );
if (response.status === 404) {
console.log("\nParticipant not found.");
} else {
const participant = response.data;
console.log(`\nParticipant Found. ID: ${participant.id}`);

// NOTE: This piece is only necessary when using MyDataHelps Embeddables in a custom app.
// Most API use cases do NOT require a participant token.
// Be sure to:
// 1. Use the internal ID field (from participant.id above) and NOT participantIdentifier
// 2. Request the correct scope(s) for your needs.
const scopes = "Participant:read SurveyAnswers:read"
const participantAccessToken = await getParticipantAccessToken(serviceAccessToken, participant.id, scopes);
console.log(`\nObtained participant access token for ${participant.id}: ${participantAccessToken}`);
}
url = `/api/v1/administration/projects/${mdhProjectId}/participants/${participantIdentifier}`;
response = await getFromApi(serviceAccessToken, url, null, false );
if (response.status === 404) {
console.log("\nParticipant not found.");
} else {
const participant = response.data;
console.log(`\nParticipant Found. ID: ${participant.id}`);
console.log(participant);

// NOTE: This piece is only necessary when using MyDataHelps Embeddables in a custom app.
// Most API use cases do NOT require a participant token.
// Be sure to:
// 1. Use the internal ID field (from participant.id above) and NOT participantIdentifier
// 2. Request the correct scope(s) for your needs. The default ones here mirror the MyDataHelpsStarterKit needs.
const scopes = "Participant:read SurveyTasks:read SurveyAnswers:read Notifications:read DataCollectionSettings:read ExternalAccounts:connect ExternalAccounts:read Project:read";
const participantAccessToken = await getParticipantAccessToken(serviceAccessToken, participant.id, scopes);
console.log(`\nObtained participant access token for ${participant.id}: ${participantAccessToken}`);
}
}

Expand Down
Loading