diff --git a/.gitignore b/.gitignore index 6c00c877..ead9ca31 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ build/ # Package Files # *.jar -*.log \ No newline at end of file +*.log +/dot-net/UnitTesting/.vs/WriteUnitTest/v15/Server/sqlite3 +/dot-net/UnitTesting/WriteUnitTest/obj/Debug diff --git a/dot-net/UnitTesting/WriteUnitTest.UnitTests/Services/LessonServiceUnitTests.cs b/dot-net/UnitTesting/WriteUnitTest.UnitTests/Services/LessonServiceUnitTests.cs index c016f412..914f8364 100644 --- a/dot-net/UnitTesting/WriteUnitTest.UnitTests/Services/LessonServiceUnitTests.cs +++ b/dot-net/UnitTesting/WriteUnitTest.UnitTests/Services/LessonServiceUnitTests.cs @@ -1,13 +1,26 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; +using WriteUnitTest.Services; +using WriteUnitTest.Entities; namespace WriteUnitTest.UnitTests.Services { - [TestClass] public class LessonServiceUnitTests { - [TestMethod] - public void UpdateLessonGrade_Test() + private LessonService service = null; + + public LessonServiceUnitTests() { + this.service = new LessonService(); + } + + [Theory] + [InlineData(12, 80.0d, true)] + [InlineData(46, 65.0d, false)] + public void UpdateLessonGrade_Validate_IsPassed_Test(int lessonId, double grade, bool expected) + { + var lesson = this.service.UpdateLessonGrade(lessonId, grade); + + Assert.Equal(expected, lesson.IsPassed); } } } \ No newline at end of file diff --git a/dot-net/UnitTesting/WriteUnitTest.UnitTests/WriteUnitTest.UnitTests.csproj b/dot-net/UnitTesting/WriteUnitTest.UnitTests/WriteUnitTest.UnitTests.csproj index f3d83f84..b2cc638c 100644 --- a/dot-net/UnitTesting/WriteUnitTest.UnitTests/WriteUnitTest.UnitTests.csproj +++ b/dot-net/UnitTesting/WriteUnitTest.UnitTests/WriteUnitTest.UnitTests.csproj @@ -1,5 +1,8 @@  + + + Debug AnyCPU @@ -16,6 +19,8 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + + true @@ -35,7 +40,22 @@ 4 + + ..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll + + + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll + @@ -43,18 +63,24 @@ - - - - False - - - + + + + {00a40a05-8314-4f25-a444-46ddeac3497e} + WriteUnitTest + + + + + + + + @@ -75,6 +101,16 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + React App + + + +
+ + + diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/public/manifest.json b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/public/manifest.json new file mode 100644 index 00000000..1f2f141f --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/API.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/API.js new file mode 100644 index 00000000..4d46888a --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/API.js @@ -0,0 +1,43 @@ +const axios = require("axios"); +const API_URL = 'http://localhost:3000/Patient/'; + +// Get Patient Info +export async function getPatientInfo(patientId) { + try { + const url = `${API_URL}${patientId}`; + const response = await axios.get(url); + const data = response.data; + return data; + } catch (error) { + console.log(error); + } + + // return rp({ + // ...API_OPTIONS, + // url: `${API_ADDRESS}/Patient/${patientID}` + // }) + // .catch((rpError) => { + // // Rethrow an interpreted error + // throw interpretError(rpError) + // }) +} + +// Get Patient Conditions +export async function getPatientConditions(patientId) { + try { + const url = `${API_URL}condition/${patientId}`; + const response = await axios.get(url); + const data = response.data; + return data; + } catch (error) { + console.log(error); + } +// return rp({ +// ...API_OPTIONS, +// url: `${API_ADDRESS}/Condition?patient=${patientID}` +// }) +// .catch((rpError) => { +// // Rethrow an interpreted error +// throw interpretError(rpError) +// }) +} \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.css b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.css new file mode 100644 index 00000000..835ff468 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.css @@ -0,0 +1,22 @@ +.App { + text-align: center; +} + +.app-header { + background-color: #09255e; + color: white; + line-height: 5vh; + min-height: 5vh; + width: 100%; + font-size: x-large; +} + +section { + margin-top: 2em; + text-align: center; +} + +.input-label { + text-align: left; + margin-right: 0.4em; +} \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.js new file mode 100644 index 00000000..7fc82002 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; + +import './App.css'; +import { getPatientInfo } from './API'; + +import PatientInfo from './PatientInfo'; +import Spinner from './Spinner'; +import Error from './Error'; + +class App extends Component { + constructor(props) { + super(props); + this.state = { + error: null, + isSearching: false, + patientInfo: null, + patientId: '' + }; + } + + componentDidMount() { + if (window.location.hash) { + this.searchPatient(window.location.hash.substr(1)); // strip initial # char + } + } + + onSubmit = (event) => { + event.preventDefault(); + this.searchPatient(this.state.patientId); + } + + searchPatient(patientId) { + (async () => { + patientId = patientId.replace(/\D/g, ''); // remove non-numeric chars + if (patientId.length !== 7) { + this.setState({ error: 'Please enter a valid numeric patient id (7 digits).' }); + return; + } + + window.location.hash = patientId; + this.setState({isSearching: true, error: null, patientId, patientInfo: null}); + try { + const patientInfo = await getPatientInfo(patientId) + this.setState({patientInfo, isSearching: false}); + } + catch (error) { + this.setState({error, isSearching: false}); + } + })(); + } + + updatePatientId = (event) => { + this.setState({ + patientId: event.target.value + }); + } + + render() { + const {error, isSearching, patientId, patientInfo} = this.state; + + return ( +
+
+ Patient Search +
+
+
+ + + + {isSearching && Searching for patient} + {patientInfo && } + {error && {error}} + +
+
+ ); + } +} + +export default App; \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.test.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.test.js new file mode 100644 index 00000000..a754b201 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Error.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Error.js new file mode 100644 index 00000000..3bddae95 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Error.js @@ -0,0 +1,11 @@ + +import React from 'react'; + +export default function Error({ children }) { + return ( +
+
An error has occurred:
+
{children}
+
+ ); +} \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/PatientInfo.css b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/PatientInfo.css new file mode 100644 index 00000000..b9096ecf --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/PatientInfo.css @@ -0,0 +1,15 @@ +.patient-info { + border-radius: 5px; + border: thin solid lightgray; + margin: 2em 0; + padding: 0.5em; + } + +.patient-info-table { + margin-top: 0.5em; +} + +.patient-detail-info { + font-size: larger; + font-weight: bold; +} \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/PatientInfo.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/PatientInfo.js new file mode 100644 index 00000000..22faf0ca --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/PatientInfo.js @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import ReactTable from 'react-table'; + +import './PatientInfo.css'; +import 'react-table/react-table.css'; + +import { getPatientConditions } from './API'; +// import { capitalize } from './utils'; +import Error from './Error'; +import Spinner from './Spinner'; +import * as moment from 'moment'; +import * as capitalize from 'capitalize'; + +const COLUMNS = [ + { Header: 'Condition', accessor: (c) => c.resource.code.text, id: 'condition', Cell: props =>
{props.value}
}, + { Header: 'Date Recorded', accessor: (c) => c.resource.dateRecorded, id: 'dateRecorded', Cell: props => moment(props.value).format('LL') }, + { Header: 'PubMed', accessor: (c) => , id: 'pubMed'} +]; +const TABLE_PROPS = { + columns: COLUMNS, + className: 'patient-info-table', + defaultSorted: [ + {id: 'dateRecorded', desc: true} + ], + noDataText: 'No patient conditions found.', + defaultPageSize: 10 +} + +function PubMedLink({ condition }) { + const conditionName = condition.resource.code.text; + const url = `https://www.ncbi.nlm.nih.gov/pubmed/?term=${encodeURIComponent(conditionName)}`; + return ( + + PubMed + + ); +} + +function isConditionActive(condition) { + return condition.resource.clinicalStatus === 'active'; +} + +class PatientInfo extends Component { + constructor(props) { + super(props); + this.state = {isSearching: true, conditions: null}; + } + + componentDidMount() { + (async () => { + try { + const conditions = await getPatientConditions(this.props.patient.id); + const activeConditions = conditions.entry + ? conditions.entry.filter(isConditionActive) + : []; + this.setState({conditions: activeConditions, isSearching: false}) + } + catch (error) { + this.setState({error, isSearching: false}); + } + })(); + } + + render() { + const { patient } = this.props; + const { error, conditions, isSearching } = this.state; + return ( +
+
+
{capitalize.words(patient.name[0].text)} ({patient.id})
+
Birth Date: {moment(patient.birthDate).format('LL')}
+
Gender: {capitalize.words(patient.gender)}
+
+ {isSearching && Getting patient conditions} + {error && {error}} + {conditions && ( + + )} +
+ ) + } +} + +export default PatientInfo; \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Spinner.css b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Spinner.css new file mode 100644 index 00000000..2493c084 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Spinner.css @@ -0,0 +1,12 @@ +/* .Spinner { + line-height: 1.5em; + margin: 1em 0; + } */ + + .Spinner svg { + fill: darkblue; + margin-right: 0.25em; + vertical-align: middle; + stroke: darkblue; + } + \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Spinner.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Spinner.js new file mode 100644 index 00000000..a5bf5799 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/Spinner.js @@ -0,0 +1,13 @@ +import React from 'react'; + +import './Spinner.css'; +import { ReactComponent as Rings } from './images/rings.svg'; + +export default function Spinner({ children }) { + return ( +
+ + {children || 'Please wait'}... +
+ ); +} \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/images/rings.svg b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/images/rings.svg new file mode 100644 index 00000000..ad9cb74d --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/images/rings.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/index.css b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/index.css new file mode 100644 index 00000000..cee5f348 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/index.css @@ -0,0 +1,14 @@ +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/index.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/index.js new file mode 100644 index 00000000..0c5e75da --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/index.js @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render(, document.getElementById('root')); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: http://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/logo.svg b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/logo.svg new file mode 100644 index 00000000..6b60c104 --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/serviceWorker.js b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/serviceWorker.js new file mode 100644 index 00000000..2283ff9c --- /dev/null +++ b/javascript/SMART-on-FHIR/chrisdeyloff-smart-fhir/src/serviceWorker.js @@ -0,0 +1,135 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read http://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit http://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +}