Skip to content

Commit 381272e

Browse files
author
James Fuqian
authored
Merge pull request #7 from CMSgov/jfuqian/BB2-896-Responsive-Design-Sample-App-Python-React-2
[BB2-896] Responsive Design Sample App Python React 2
2 parents d24fa31 + 62dbda3 commit 381272e

13 files changed

Lines changed: 305 additions & 211 deletions

File tree

README.md

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

58+
Debugging server component
59+
--------------------------
60+
debugpy remote debugging enabled on port 5678 for server in docker compose, developer can attach to server from IDE e.g. vscode.
61+
62+
Error Responses and handling:
63+
-----------------------------
64+
[See ErrorResponses.md](./ErrorResponses.md)

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
};

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+
- "5678:5678"
1011
volumes:
1112
- ./server:/server
1213
client:

server/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ COPY ["./src/prestart/env/sandbox.sample.env","./src/prestart/env/development.en
1111
COPY . .
1212

1313
RUN pip install pipenv
14+
RUN pip install debugpy
1415
RUN pipenv install --system --deploy --ignore-pipfile
1516

1617
ENV ENV "development"
1718

1819
EXPOSE 3001
1920

20-
CMD ["sh", "-c", "python run.py --ENV ${ENV}"]
21+
CMD ["sh", "-c", "python -m debugpy --listen 0.0.0.0:5678 run.py --ENV ${ENV}"]

server/src/app/views.py

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
# views.py
1+
import json
22

33
from flask import redirect, request
4-
import requests
54
from ..data.Database import *
65
from . import app
76
from ..entities.Settings import Settings
8-
from ..utils.configUtil import getConfigSettings
9-
from ..utils.bb2Util import generateAuthorizeUrl, getAccessToken, getBenefitData
10-
from ..utils.userUtil import clearBB2Data, getLoggedInUser
7+
from ..utils.config_util import get_config_settings
8+
from ..utils.bb2_util import generate_authorize_url, get_access_token, get_benefit_data
9+
from ..utils.user_util import clear_bb2_data, get_loggedin_user
1110
from ..shared.LoggerFactory import LoggerFactory
12-
import json
1311

1412
"""
1513
This is the location of all the routes, via the port specified in the config, that allows the
@@ -20,67 +18,62 @@
2018

2119
# initialize the logger object
2220
myLogger = LoggerFactory.get_logger(log_file=__name__,log_level='DEBUG')
23-
loggedInUser = getLoggedInUser()
21+
loggedInUser = get_loggedin_user()
2422

2523
#########################################################################################
2624
# Test route
2725
#########################################################################################
2826
@app.route('/',methods=['GET'])
29-
def verifyPortListening():
27+
def verify_port_listening():
3028
return 'Listening on Port 3001 for the Server!'
3129

3230
#########################################################################################
3331
# Authorize routes
3432
#########################################################################################
3533

3634
@app.route('/api/authorize/authurl',methods=['GET'])
37-
def getAuthUrl():
35+
def get_auth_url():
3836
""" DEVELOPER NOTE:
3937
* to utilize the latest security features/best practices
4038
* it is recommended to utilize pkce
4139
"""
4240
# get configuration and settings
43-
myEnv = request.args.get('env') or 'development'
44-
myVersion = request.args.get('version') or 'v2'
41+
my_env = request.args.get('env') or 'development'
42+
my_version = request.args.get('version') or 'v2'
4543
PKCE = request.args.get('pkce') or True
46-
47-
settings = Settings(myEnv,myVersion,PKCE)
48-
49-
configSettings = getConfigSettings(myEnv)
50-
authUrl = generateAuthorizeUrl(settings, configSettings)
51-
return authUrl
44+
return generate_authorize_url(Settings(my_env, my_version, PKCE), get_config_settings(my_env))
5245

5346
@app.route('/api/authorize/currentAuthToken',methods=['GET'])
54-
def getCurrentAuthToken():
47+
def get_current_auth_token():
5548
return loggedInUser.get('authToken')
5649

5750
@app.route('/api/bluebutton/callback/',methods=['GET'])
58-
def authorizationCallback():
51+
def authorization_callback():
5952
try:
60-
requestQuery = request.args
53+
request_query = request.args
6154

62-
if (requestQuery.get('error') == BENE_DENIED_ACCESS):
55+
if (request_query.get('error') == BENE_DENIED_ACCESS):
6356
# clear all saved claims data since the bene has denied access for the application
64-
clearBB2Data()
57+
clear_bb2_data()
6558
myLogger.error('Beneficiary denied application access to their data')
6659
return redirect('http://localhost:3000')
6760

68-
if (requestQuery.get('code') == ''):
61+
if (request_query.get('code') == ''):
6962
myLogger.error('Response was missing access code!')
70-
if (DBsettings.pkce and requestQuery.get('state')):
63+
if (DBsettings.pkce and request_query.get('state')):
7164
myLogger.error('State is required when using PKCE')
7265

7366
# get configuration and settings
74-
myEnv = requestQuery.get('env') or 'development'
75-
myVersion = requestQuery.get('version') or 'v2'
76-
PKCE = requestQuery.get('pkce') or True
67+
my_env = request_query.get('env') or 'development'
68+
my_version = request_query.get('version') or 'v2'
69+
PKCE = request_query.get('pkce') or True
7770

78-
settings = Settings(myEnv,myVersion,PKCE)
71+
settings = Settings(my_env, my_version, PKCE)
7972

80-
configSettings = getConfigSettings(myEnv)
73+
config_settings = get_config_settings(my_env)
8174

8275
# this gets the token from Medicare.gov once the 'user' authenticates their Medicare.gov account
83-
authToken = getAccessToken(requestQuery.get('code'),requestQuery.get('state'),configSettings=configSettings,settings=settings)
76+
auth_token = get_access_token(request_query.get('code'), request_query.get('state'), config_settings=config_settings, settings=settings)
8477

8578
"""DEVELOPER NOTES:
8679
* This is where you would most likely place some type of
@@ -90,26 +83,34 @@ def authorizationCallback():
9083
* Here we are however, just updating the loggedInUser we pulled from our MockDb, but we aren't persisting that change
9184
* back into our mocked DB, normally you would want to do this
9285
"""
93-
94-
#Here we are grabbing the mocked 'user' for our application
86+
87+
# Here we are grabbing the mocked 'user' for our application
9588
# to be able to store the access token for that user
9689
# thereby linking the 'user' of our sample applicaiton with their Medicare.gov account
9790
# providing access to their Medicare data to our sample application
98-
loggedInUser.update({'authToken':authToken})
99-
100-
""" DEVELOPER NOTES:
101-
* Here we will use the token to get the EoB data for the mocked 'user' of the sample application
102-
* then to save trips to the BB2 API we will store it in the mocked db with the mocked 'user'
103-
*
104-
* You could also request data for the Patient endpoint and/or the Coverage endpoint here
105-
* using similar functionality
106-
"""
107-
eobData = getBenefitData(settings=settings,configsSettings=configSettings,query=requestQuery,loggedInUser=loggedInUser)
108-
109-
if (eobData != None and eobData != ''):
110-
loggedInUser.update({'eobData':json.dumps(eobData)})
91+
if auth_token and auth_token.get('expires_at') is not None:
92+
loggedInUser.update({'authToken': auth_token})
93+
94+
""" DEVELOPER NOTES:
95+
* Here we will use the token to get the EoB data for the mocked 'user' of the sample application
96+
* then to save trips to the BB2 API we will store it in the mocked db with the mocked 'user'
97+
*
98+
* You could also request data for the Patient endpoint and/or the Coverage endpoint here
99+
* using similar functionality
100+
"""
101+
102+
eob_data = get_benefit_data(settings=settings,configs_settings=config_settings, query=request_query, logged_in_user=loggedInUser)
103+
104+
if eob_data:
105+
if eob_data.get('entry', None) is not None:
106+
loggedInUser['eobData'] = eob_data
107+
else:
108+
# error or malformed bundle, send generic error message to client
109+
loggedInUser.update({'eobData': {'message': 'Unable to load EOB Data - fetch FHIR resource error.'}})
111110
else:
112-
loggedInUser.update({'eobData':json.dumps('Unable to load EOB Data!')})
111+
clear_bb2_data()
112+
# send generic error message to FE
113+
loggedInUser.update({'eobData': {'message': 'Unable to load EOB Data - authorization failed.'}})
113114

114115
except BaseException as err:
115116
"""DEVELOPER NOTES:
@@ -135,10 +136,8 @@ def authorizationCallback():
135136
* DB you would choose to use
136137
"""
137138
@app.route('/api/data/benefit',methods=['GET'])
138-
def getPatientEOB():
139-
if (loggedInUser != None
140-
and loggedInUser.get('eobData') != None
141-
and loggedInUser.get('eobData') != ''):
142-
return json.loads(loggedInUser.get('eobData'))
139+
def get_patient_eob():
140+
if loggedInUser and loggedInUser.get('eobData'):
141+
return loggedInUser.get('eobData')
143142
else:
144-
return ''
143+
return {}

server/src/data/Database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
'userName' : '',
2323
'pcp' : '',
2424
'primaryFacility' : '',
25-
'eobData' : ''
25+
'eobData' : {}
2626
}
2727

2828
DBusers = [basicUser]

0 commit comments

Comments
 (0)