Skip to content

Commit cbd7f26

Browse files
author
James Fuqian
authored
Merge pull request #7 from CMSgov/jfuqian/BB2-896-Responsive-Design-Node-Js-Sample
[BB2-896] Responsive Design NodeJS React Sample
2 parents fac8eb4 + 839aa39 commit cbd7f26

17 files changed

Lines changed: 1773 additions & 875 deletions

ErrorResponses.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Blue Button 2.0 API Error Responses
2+
3+
## Overview
4+
5+
This document serves as a supplementary to Blue Button 2.0 API Developer Documents. It gives more details on the most common error responses and how to properly handle them.
6+
7+
8+
## Error Responses and Client Actions
9+
10+
### Authorization Requests
11+
12+
13+
| Status Code | End Point URL | Error Message | Action | Comments |
14+
| -------------- | --------------- | -------------------------- | ----------------- | ------------------------------------------------------ |
15+
| 400<br>BAD REQUEST | /v[12]/o/.* | the response comes from blue button.<br>Example message:<br>error: unsupported grant type | Fix the request<br> | request has invalid parameter(s) |
16+
| 403<br>FORBIDDEN | /v[12]/o/authorize/<br>/v[12]/o/authorize/(?P<uuid>[\w-]+)/$<br>/v[12]/o/token | You do not have permission to perform this action. | | request does not pass permission check |
17+
| 403<br>FORBIDDEN | /v[12]/o/authorize/<br>/v[12]/o/authorize/(?P<uuid>[\w-]+)/$ | This application, {your app name}, is temporarily inactive. <br>If you are the app maintainer, please contact the Blue Button 2.0 API team. <br>If you are a Medicare Beneficiary and need assistance, please contact the application's support team <br>or call 1-800-MEDICARE (1-800-633-4227) | | the app is disabled by Blue Button 2.0 API administrator usually <br>due to abnormal usage pattern etc., contact CMS as instructed, <br>it is recommended to stop the app and resolve with Blue Button 2.0 API team |
18+
| 404<br>NOT FOUND | /v[12]/o/.* | Medicare is unable to retrieve your data at this time due to an internal issue.<br>Our team is aware of the issue and is working to resolve it.<br>Please try again at a later time. We apologize for any inconvenience. | | If any abnormality encountered during authorization, e.g. <br>the patient is not found by mbi hash / hicn hash lookup, the message will be <br>rendered as html page to the beneficiary, and with a 404 status code.<br>the authorization process aborted. |
19+
| 502<br>BAD GATEWAY | /v[12]/o/.* | An error occurred connecting to medicare.gov account<br><br>other additional messages:<br><br>BBMyMedicareSLSxTokenException, or<br>BBMyMedicareSLSxSignoutException, or<br>BBMyMedicareSLSxValidateSignoutException, or<br>BBMyMedicareCallbackAuthenticateSlsUserInfoValidateException, or<br>BBMyMedicareSLSxUserinfoException at /mymedicare/sls-callback | | Abnormality encountered during authorization for various causes as indicated by <br>error name in addition to the general message:<br><br>An error occurred connecting to medicare.gov account |
20+
| 500<br>SERVER ERROR | /v[12]/o/.* | The root cause of the 500 error, some times, is indicated by the error message, <br>the app can choose to retry the failed request depend on the nature of the root cause, <br>examples that might be retriable are those related to network down (temporarily):<br>Example:<br>ConnectionError at /mymedicare/login<br>HTTPSConnectionPool(host='test.accounts.cms.gov', port=443): <br>Max retries exceeded with url: /health (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f46599dafd0>: <br>Failed to establish a new connection: [Errno -2] Name or service not known')) | | App retry on request during authorization<br>is not recommended. |
21+
22+
23+
### Data Requests
24+
25+
26+
| Status Code | End Point URL | Error Message | Action | Comments |
27+
| -------------- | --------------- | -------------------------- | ----------------- | ------------------------------------------------------ |
28+
| 400<br>BAD REQUEST | /v[12]/fhir/.* | the response comes from FHIR data backend.<br>Example message:<br>details: IllegalArgumentException: Unsupported ID pattern | <br>Fix the request<br> | fhir request has invalid parameter(s) |
29+
| 403<br>FORBIDDEN | /v[12]/fhir/.* | You do not have permission to perform this action. | | the request is not in the scope of the grant authorized, <br>e.g. the beneficiary did not grant access to the demographic data |
30+
| 403<br>FORBIDDEN | /v[12]/fhir/.* | This application, {your app name}, is temporarily inactive. <br>If you are the app maintainer, please contact the Blue Button 2.0 API team. <br>If you are a Medicare Beneficiary and need assistance, please contact the application's support team <br>or call 1-800-MEDICARE (1-800-633-4227) | | the app is disabled by Blue Button 2.0 API administrator usually <br>due to abnormal usage pattern etc., contact CMS as instructed, <br>it is recommended to stop the app and resolve with Blue Button 2.0 API team |
31+
| 404<br>NOT FOUND | /v[12]/fhir/.* | The requested resource does not exist | | for example, for a fhir read request as:<br>/v2/fhir/Patient/-1234567890<br>but there is not a patient with<br>fhir_id = -1234567890, a 404 is returned |
32+
| 502<br>BAD GATEWAY | /v[12]/fhir/.* | An error occurred contacting the upstream server: <error details><br>Example:<br>UpstreamServerException('An error occurred contacting the upstream server:Failed to call access method: <br>java.lang.IllegalArgumentException: _lastUpdate lower bound has an invalid prefix') | | An error occurred in FHIR data backend when retrieving the resources, <br>it could be client side error e.g. a malformed query parameter in the URL where the error code should be 400 BAD REQUEST, <br>or a back end internal error.<br>the action on the 502 error is on a case by case basis, e.g. if the root cause of the 502 is actually a bad query parameter, <br>then retry is a sensible action. |
33+
| 500<br>SERVER ERROR | /v[12]/fhir/.* | The root cause of the 500 error, some times, is indicated by the error message, <br>the app can choose to retry the failed request depend on the nature of the root cause, <br>examples that might be retriable are those related to network down (temporarily):<br>Example:<br>ConnectionError at /mymedicare/login<br>HTTPSConnectionPool(host='test.accounts.cms.gov', port=443): <br>Max retries exceeded with url: /health (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f46599dafd0>: <br>Failed to establish a new connection: [Errno -2] Name or service not known')) | Heuristic on Retry | App can choose to retry on some of the 500 errors as shown by the example, this is a heuristic approach. |
34+
35+
36+
### Retry
37+
38+
39+
40+
41+
Auto retrying (with sensible retry settings) on FHIR Data read/search to overcome a FHIR backend network temporary downtime is recommended.
42+
43+
44+
Due to the involvement of the end user (beneficiary), auto retrying requests in the authorization flow are not recommended.
45+

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,30 @@ Read the DEVELOPER NOTES found in the code to understand the application
5757
and where you will need to make adjustments/changes as well as some
5858
suggestions for best practices.
5959

60+
Usage Examples
61+
-----------
62+
63+
To start the sample in Docker :
64+
65+
1. go to the base directory of the repo
66+
2. docker-compose up
67+
68+
To start the sample in native OS (e.g. Linux) with server and client components started in separate windows :
69+
70+
1. go to the base directory of the repo
71+
2. run below to start the server:
72+
1. yarn --cwd server install
73+
2. yarn --cwd server start:dev
74+
3. run below to start the client:
75+
1. yarn --cwd client install
76+
2. yarn --cwd client start-native
77+
78+
To stop the sample:
79+
80+
Both ways of starting the sample are running the sample in foreground, logging and tracing from both client and server components are on stdout of the command window, to stop the sample, press Ctl C, which will terminate both the client and server components.
81+
82+
For client and server started separately in their command window, type Ctrl C respectively
83+
84+
Error Responses and handling:
85+
-----------------------------
86+
[See ErrorResponses.md](./ErrorResponses.md)

client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"web-vitals": "^1.0.1"
2424
},
2525
"scripts": {
26-
"start": "react-scripts start",
26+
"start": "REACT_APP_CTX=docker react-scripts start",
27+
"start-native": "REACT_APP_CTX=native react-scripts start",
2728
"build": "react-scripts build",
2829
"test": "react-scripts test",
2930
"eject": "react-scripts eject"

client/src/components/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Badge } from '@cmsgov/design-system';
22
import { Link as RouterLink } from 'react-router-dom';
33

4-
export default function Header({ }) {
4+
export default function Header() {
55
return (
66
<header className="ds-u-padding--3 ds-u-sm-padding--6 ds-u-display--block ds-u-fill--primary-darkest">
77
<h1 className="ds-u-margin--0 ds-u-color--white ds-u-font-size--display ds-u-text-align--center">

client/src/components/patientData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import chart from '../images/who-charted.png'
44
import { SettingsType } from '../types/settings';
55
import { useState } from 'react';
66

7-
export default function PatientData({ }) {
7+
export default function PatientData() {
88
const [header] = useState('Add your Medicare Prescription Drug data');
99
const [settingsState] = useState<SettingsType>({
1010
pkce: true,

client/src/components/records.tsx

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ export type EOBRecord = {
88
amount: number
99
}
1010

11-
export default function Records({ }) {
11+
export type ErrorResponse = {
12+
type: string,
13+
content: string,
14+
}
15+
16+
export default function Records() {
1217
const [records, setRecords] = useState<EOBRecord[]>([]);
18+
const [message, setMessage] = useState<ErrorResponse>();
1319
/*
1420
* DEVELOPER NOTES:
1521
* Here we are parsing through the different PDE Claim records
@@ -31,49 +37,81 @@ export default function Records({ }) {
3137
.then(res => {
3238
return res.json();
3339
}).then(eobData => {
34-
const records: EOBRecord[] = eobData.entry.map((resourceData: any) => {
35-
const resource = resourceData.resource;
36-
return {
37-
id: resource.id,
38-
code: resource.item[0]?.productOrService?.coding[0]?.code || 'Unknown',
39-
display: resource.item[0]?.productOrService?.coding[0]?.display || 'Unknown Prescription Drug',
40-
amount: resource.item[0]?.adjudication[7]?.amount?.value || '0'
40+
if (eobData.entry) {
41+
const records: EOBRecord[] = eobData.entry.map((resourceData: any) => {
42+
const resource = resourceData.resource;
43+
return {
44+
id: resource.id,
45+
code: resource.item[0]?.productOrService?.coding[0]?.code || 'Unknown',
46+
display: resource.item[0]?.productOrService?.coding[0]?.display || 'Unknown Prescription Drug',
47+
amount: resource.item[0]?.adjudication[7]?.amount?.value || '0'
48+
}
49+
});
50+
setRecords(records);
51+
}
52+
else {
53+
if (eobData.message) {
54+
setMessage({"type": "error", "content": eobData.message || "Unknown"})
4155
}
42-
});
43-
setRecords(records);
56+
}
4457
});
4558
}, [])
4659

47-
return (
48-
<div className='full-width-card'>
49-
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
50-
<TableCaption>Medicare Prescription Drug Claims Data</TableCaption>
51-
<TableHead>
52-
<TableRow>
53-
<TableCell id="column_1">NDC Code</TableCell>
54-
<TableCell id="column_2">Prescription Drug Name</TableCell>
55-
<TableCell id="column_3">Cost</TableCell>
56-
</TableRow>
57-
</TableHead>
58-
<TableBody>
59-
60-
{records.map(record => {
61-
return (
62-
<TableRow key={record.id}>
63-
<TableCell stackedTitle="NDC Code" headers="column_1">
64-
{record.code}
65-
</TableCell>
66-
<TableCell stackedTitle="Prescription Drug Name" headers="column_2">
67-
{record.display}
68-
</TableCell>
69-
<TableCell stackedTitle="Cost" headers="column_3">
70-
${record.amount}.00
71-
</TableCell>
72-
</TableRow>
73-
)
74-
})}
75-
</TableBody>
76-
</Table>
77-
</div>
78-
);
60+
if (message) {
61+
return (
62+
<div className='full-width-card'>
63+
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
64+
<TableCaption>Error Response</TableCaption>
65+
<TableHead>
66+
<TableRow>
67+
<TableCell id="column_1">Type</TableCell>
68+
<TableCell id="column_2">Content</TableCell>
69+
</TableRow>
70+
</TableHead>
71+
<TableBody>
72+
<TableRow>
73+
<TableCell stackedTitle="Type" headers="column_1">
74+
{message.type}
75+
</TableCell>
76+
<TableCell stackedTitle="Content" headers="column_2">
77+
{message.content}
78+
</TableCell>
79+
</TableRow>
80+
</TableBody>
81+
</Table>
82+
</div>
83+
);
84+
} else {
85+
return (
86+
<div className='full-width-card'>
87+
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
88+
<TableCaption>Medicare Prescription Drug Claims Data</TableCaption>
89+
<TableHead>
90+
<TableRow>
91+
<TableCell id="column_1">NDC Code</TableCell>
92+
<TableCell id="column_2">Prescription Drug Name</TableCell>
93+
<TableCell id="column_3">Cost</TableCell>
94+
</TableRow>
95+
</TableHead>
96+
<TableBody>
97+
{records.map(record => {
98+
return (
99+
<TableRow key={record.id}>
100+
<TableCell stackedTitle="NDC Code" headers="column_1">
101+
{record.code}
102+
</TableCell>
103+
<TableCell stackedTitle="Prescription Drug Name" headers="column_2">
104+
{record.display}
105+
</TableCell>
106+
<TableCell stackedTitle="Cost" headers="column_3">
107+
${record.amount}.00
108+
</TableCell>
109+
</TableRow>
110+
)
111+
})}
112+
</TableBody>
113+
</Table>
114+
</div>
115+
);
116+
}
79117
};

client/src/setupProxy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module.exports = function(app) {
44
app.use(
55
'/api',
66
createProxyMiddleware({
7-
target: 'http://server:3001',
7+
target: (process.env.REACT_APP_CTX === 'docker' ? 'http://server:3001' : 'http://localhost:3001'),
88
changeOrigin: true,
99
})
1010
);

client/yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3224,9 +3224,9 @@ caniuse-api@^3.0.0:
32243224
lodash.uniq "^4.5.0"
32253225

32263226
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181:
3227-
version "1.0.30001191"
3228-
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz#bacb432b6701f690c8c5f7c680166b9a9f0843d9"
3229-
integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==
3227+
version "1.0.30001279"
3228+
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz"
3229+
integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==
32303230

32313231
capture-exit@^2.0.0:
32323232
version "2.0.0"

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ services:
77
dockerfile: ./Dockerfile
88
ports:
99
- "3001:3001"
10+
- "9229:9229"
1011
client:
1112
build:
1213
context: ./client

server/eslintrc.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"env": {
3+
"browser": true,
4+
"es2021": true
5+
},
6+
"extends": [
7+
"airbnb-base",
8+
"airbnb-typescript/base"
9+
],
10+
"parser": "@typescript-eslint/parser",
11+
"parserOptions": {
12+
"ecmaVersion": 12,
13+
"sourceType": "module"
14+
},
15+
"plugins": [
16+
"@typescript-eslint"
17+
],
18+
"rules": {
19+
}
20+
}

0 commit comments

Comments
 (0)