Skip to content

Commit

Permalink
Add USCDIv4, Support mono repo, Modularize validator actions, Improve…
Browse files Browse the repository at this point in the history
… C-CDA results performance and add syntax highlighting
  • Loading branch information
drbgfc committed Oct 15, 2024
1 parent 29866d7 commit a9032ad
Show file tree
Hide file tree
Showing 15 changed files with 222 additions and 90 deletions.
23 changes: 17 additions & 6 deletions .env
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV3_SENDER_URL=https://api.github.com/repos/onc-healthit/2015-edition-cures-update-uscdi-v3-testdata/contents/Cures Update Svap Uscdiv3 Sender SUT Test Data
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV3_RECEIVER_URL=https://api.github.com/repos/onc-healthit/2015-edition-cures-update-uscdi-v3-testdata/contents/Cures Update Svap Uscdiv3 Receiver SUT Test Data
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV3_DOWNLOAD_URL=https://codeload.github.com/onc-healthit/2015-edition-cures-update-uscdi-v3-testdata/zip/master
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_V1_SENDER_URL=https://api.github.com/repos/onc-healthit/2015-edition-cures-update-data/contents/Cures Update Sender SUT Test Data
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_V1_RECEIVER_URL=https://api.github.com/repos/onc-healthit/2015-edition-cures-update-data/contents/Cures Update Receiver SUT Test Data
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_DOWNLOAD_URL=https://codeload.github.com/onc-healthit/2015-edition-cures-update-data/zip/master
NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_LOCATION=onc-healthit/ccda-uscdi-certification-testdata
NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API=https://api.github.com/repos/$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_LOCATION
NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_DOWNLOAD=https://codeload.github.com/$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_LOCATION/zip/main

# Example of completed API URL: https://api.github.com/repos/onc-healthit/ccda-uscdi-certification-testdata/contents/uscdi-v4-testdata/Sender SUT Test Data
# NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV4_SENDER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v4-testdata/Sender SUT Test Data
# NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV4_RECEIVER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v4-testdata/Receiver SUT Test Data
# TODO: When the uscdi-v4-testdata folder is created, remove the following 2 env vars and uncomment the 2 v4 urls above
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV4_SENDER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v3-testdata/Sender SUT Test Data
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV4_RECEIVER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v3-testdata/Receiver SUT Test Data

NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV3_SENDER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v3-testdata/Sender SUT Test Data
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_USCDIV3_RECEIVER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v3-testdata/Receiver SUT Test Data

NEXT_PUBLIC_CCDA_VALIDATOR_CURES_V1_SENDER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v1-testdata/Sender SUT Test Data
NEXT_PUBLIC_CCDA_VALIDATOR_CURES_V1_RECEIVER_URL=$NEXT_PUBLIC_CCDA_VALIDATOR_TEST_DATA_MONO_REPO_API/contents/uscdi-v1-testdata/Receiver SUT Test Data

NEXT_PUBLIC_RELEASE_VERSION_URL=https://raw.githubusercontent.com/onc-healthit/site-content/master/site-ui-4/version.md
NEXT_PUBLIC_RELEASE_DATE_URL=https://raw.githubusercontent.com/onc-healthit/site-content/master/site-ui-4/release-date.md

Expand Down
9 changes: 9 additions & 0 deletions public/c-cda/uscdi-v4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/app/c-cda/uscdi-v4/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import USCDIV4ValidatorComponent from '@/components/c-cda/validation/uscdi-v4/USCDIV4Validator'

export default function USCDIV4Validator() {
return <USCDIV4ValidatorComponent />
}
30 changes: 21 additions & 9 deletions src/components/c-cda/CCDAHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import oneclick from '@public/c-cda/oneclick.svg'
import scorecard from '@public/c-cda/scorecard.svg'
import uscdiv1 from '@public/c-cda/uscdi-v1.svg'
import uscdiv3 from '@public/c-cda/uscdi-v3.svg'
import uscdiv4 from '@public/c-cda/uscdi-v4.svg'
import BannerBox from '@shared/BannerBox'
import CardWithImage from '@shared/CardWithImage'
import styles from '@shared/styles.module.css'
import Link from 'next/link'
import SectionHeader from '../shared/SectionHeader'

const CCDAHome = () => {
const maxWidth = 550
const maxWidthMainValidatorCards = 390
const maxWidthAdditionalToolCards = 550
const flexibleBox = { display: 'flex', gap: 4, flexDirection: 'row', width: '100%', justifyContent: 'space-between' }
return (
<>
Expand All @@ -37,23 +39,33 @@ const CCDAHome = () => {
<Container>
<SectionHeader header={'C-CDA Validators'} subHeader={'The latest C-CDA validators from ONC'} />
<Box display={'flex'} width={'100%'} justifyContent={'space-between'}>
{/* TODO: Update each description to be unique to each validator version */}
<CardWithImage
title={'C-CDA Validator: USCDI V4'}
cardImage={uscdiv4}
cardHeader={'C-CDA Validator: USCDI v4'}
description={`This area provides tools for testing conformance of artifacts to industry standards and specific criteria. There are both context-free validators for general testing and validators for the ONC Health IT Certification Program's certification criteria.`}
pathname={'/c-cda/uscdi-v4'}
maxWidth={maxWidthMainValidatorCards}
imageWidth={maxWidthMainValidatorCards + 'px'}
/>
<CardWithImage
title={'C-CDA Validator: USCDI v3'}
cardImage={uscdiv3}
cardHeader={'C-CDA Validator: USCDI v3'}
description={`This area provides tools for testing conformance of artifacts to industry standards and specific criteria. There are both context-free validators for general testing and validators for the ONC Health IT Certification Program's certification criteria.`}
pathname={'/c-cda/uscdi-v3'}
maxWidth={maxWidth}
imageWidth={maxWidth + 'px'}
maxWidth={maxWidthMainValidatorCards}
imageWidth={maxWidthMainValidatorCards + 'px'}
/>
<CardWithImage
title={'C-CDA Validator: USCDI v1'}
cardImage={uscdiv1}
cardHeader={'C-CDA Validator: USCDI v1'}
description={`This area provides tools for testing conformance of artifacts to industry standards and specific criteria. There are both context-free validators for general testing and validators for the ONC Health IT Certification Program's certification criteria.`}
pathname={'/c-cda/uscdi-v1'}
maxWidth={maxWidth}
imageWidth={maxWidth + 'px'}
maxWidth={maxWidthMainValidatorCards}
imageWidth={maxWidthMainValidatorCards + 'px'}
/>
</Box>
<Divider sx={{ p: 2, borderBottomWidth: 2 }} />
Expand All @@ -71,8 +83,8 @@ const CCDAHome = () => {
'The SITE C-CDA Scorecard provides an enhanced level of interoperability for C-CDA documents by using a comprehensive scoring system, which allows implementers to improve the data quality and representation of their C-CDA documents.'
}
pathname={'/c-cda/scorecard/'}
maxWidth={maxWidth}
imageWidth={maxWidth + 'px'}
maxWidth={maxWidthAdditionalToolCards}
imageWidth={maxWidthAdditionalToolCards + 'px'}
/>
{/* TODO: Are we going to make a landing page for the one click scorecard or just link to the ONC page? */}
<CardWithImage
Expand All @@ -83,8 +95,8 @@ const CCDAHome = () => {
'Providers can use the One Click Scorecard with Direct to evaluate the quality of clinical summary documents (C-CDAs) received, or created, by their system.'
}
pathname={'https://oncprojectracking.healthit.gov/wiki/display/TechLabTU/ONC+One+Click+Scorecard'}
maxWidth={maxWidth}
imageWidth={maxWidth + 'px'}
maxWidth={maxWidthAdditionalToolCards}
imageWidth={maxWidthAdditionalToolCards + 'px'}
/>
</Box>
</Container>
Expand Down
15 changes: 9 additions & 6 deletions src/components/c-cda/validation/ValidatorForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { useFormState } from 'react-dom'
interface ValidatorFormProps {
senderCriteriaOptions: []
receiverCriteriaOptions: []
// Note: GitHub doesn't have obvious native support for a specific path within a repo to download.
// So, for now, with the mono repo update, We get everything, making this prop end up essentially static.
// TODO: Analyze if there is a workaround, or if GitHub does actually support a specific path?
downloadAllScenariosUrl: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
formAction: (prevState: object | undefined, formData: FormData) => Promise<{ response: any } | undefined>
Expand Down Expand Up @@ -149,11 +152,11 @@ export default function ValidatorForm({
{/* Criteria Selection */}
<Box sx={{ pt: 3 }}>
<FormControl fullWidth>
<InputLabel id="uscdi-v3-full-criteria-input-label">Select validation criteria</InputLabel>
<InputLabel id="full-criteria-input-label">Select validation criteria</InputLabel>
<Select
id="uscdi-v3-full-criteria-select"
id="full-criteria-select"
label="Select validation criteria"
labelId="uscdi-v3-full-criteria-input-label"
labelId="full-criteria-input-label"
value={criteriaOption}
onChange={handleCriteriaChange}
name="validationObjective"
Expand All @@ -171,11 +174,11 @@ export default function ValidatorForm({
{!hideScenario && (
<Box sx={{ pt: 3 }}>
<FormControl fullWidth>
<InputLabel id="uscdi-v3-full-criteria-input-label">Select scenario file</InputLabel>
<InputLabel id="full-criteria-input-label">Select scenario file</InputLabel>
<Select
id="uscdi-v3-full-scenario-select"
id="full-scenario-select"
label="Select scenario file"
labelId="uscdi-v3-full-scenario-input-label"
labelId="full-scenario-input-label"
value={scenarioOption}
name="referenceFileName"
onChange={handleScenarioChange}
Expand Down
86 changes: 30 additions & 56 deletions src/components/c-cda/validation/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import https from 'https'
import axios from 'axios'
import { GENERIC_ERROR_MESSAGE } from '@/constants/errorConstants'
//Get Scenario file options

// Get Scenario file options
export async function getScenarioOptions(criteriaUrl: string) {
const res = await fetch(criteriaUrl)
if (!res.ok) {
Expand All @@ -11,7 +12,7 @@ export async function getScenarioOptions(criteriaUrl: string) {
return res.json()
}

//Get token from keycloak
// Get token from keycloak
async function getToken() {
const clientSecret = process.env.CCDA_VALIDATOR_CLIENT_SECRET || ''
const clientId = process.env.CCDA_VALIDATOR_CLIENT_ID || ''
Expand Down Expand Up @@ -39,26 +40,32 @@ async function getToken() {
}
}

//CCDA Validator API Post Call for version V3
export async function postToValidatorV3(prevState: object | undefined, formData: FormData) {
const postToUscdiValidator = async (formData: FormData, uscdiVersion: string) => {
const ccdaValidatorUrl = process.env.CCDA_VALIDATOR_URL
const token = await getToken()
const uploadFile = formData.get('ccdaFile')
console.log('uploaded File', uploadFile)
formData.append('curesUpdate', 'false')
formData.append('svap2022', 'false')
formData.append('svap2023', 'true')
if (formData.get('version') === 'v3IG') {

formData.append('curesUpdate', uscdiVersion === 'v1' ? 'true' : 'false')
formData.append('svap2022', uscdiVersion === 'v2' ? 'true' : 'false')
formData.append('svap2023', uscdiVersion === 'v3' ? 'true' : 'false')
// TODO: Update v4 based on arg value or impl if complex type when implemented in ref val
formData.append('uscdiv4', uscdiVersion === 'v4' ? 'true' : 'false')

if (formData.get('version') === `${uscdiVersion}IG`) {
formData.append('referenceFileName', 'Readme.txt')
formData.append('validationObjective', 'C-CDA_IG_Only')
}

if (
formData.get('validationObjective') === 'C-CDA_IG_Only' ||
formData.get('validationObjective') === 'C-CDA_IG_Plus_Vocab'
) {
formData.append('referenceFileName', 'Readme.txt')
}
console.log('Submitted data for Validator V3: ', formData)

console.log(`Submitted data for Validator ${uscdiVersion}: `, formData)

const config = {
method: 'post',
url: ccdaValidatorUrl,
Expand All @@ -70,8 +77,8 @@ export async function postToValidatorV3(prevState: object | undefined, formData:

try {
const response = await axios.request(config)
//console.log(JSON.stringify(response.data))
console.log('Validator V3 API response status', response.status)
console.log('Response data', JSON.stringify(response.data))
console.log(`Validator ${uscdiVersion} API response status`, response.status)
return { response: response.data }
} catch (error) {
if (axios.isAxiosError(error)) {
Expand All @@ -88,51 +95,18 @@ export async function postToValidatorV3(prevState: object | undefined, formData:
}
}

//CCDA Validator API Post Call for version V1
// C-CDA R2.1 Validator API Post Call for USCDI version 1
// Note: C-CDA R2.1 Validator for USCDI version 2 has been sunsetted by the ONC - but version 1 is still supported
export async function postToValidatorV1(prevState: object | undefined, formData: FormData) {
const ccdaValidatorUrl = process.env.CCDA_VALIDATOR_URL
const token = await getToken()
const uploadFile = formData.get('ccdaFile')
console.log('uploaded File', uploadFile)
formData.append('curesUpdate', 'true')
formData.append('svap2022', 'false')
formData.append('svap2023', 'false')
if (formData.get('version') === 'v1IG') {
formData.append('referenceFileName', 'Readme.txt')
formData.append('validationObjective', 'C-CDA_IG_Only')
}
if (
formData.get('validationObjective') === 'C-CDA_IG_Only' ||
formData.get('validationObjective') === 'C-CDA_IG_Plus_Vocab'
) {
formData.append('referenceFileName', 'Readme.txt')
}
console.log('Submitted data for Validator V1: ', formData)
const config = {
method: 'post',
url: ccdaValidatorUrl,
headers: {
Authorization: 'Bearer ' + token.access_token,
},
data: formData,
}
return await postToUscdiValidator(formData, 'v1')
}

try {
const response = await axios.request(config)
//console.log(JSON.stringify(response.data))
console.log('Validator V1 response status', response.status)
return { response: response.data }
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response?.data)
return {
response: {
error: GENERIC_ERROR_MESSAGE,
errorStatus: error.response?.status,
},
}
} else {
console.error(error)
}
}
// C-CDA R2.1 Validator API Post Call for USCDI version 3
export async function postToValidatorV3(prevState: object | undefined, formData: FormData) {
return await postToUscdiValidator(formData, 'v3')
}

// C-CDA R2.1 Validator API Post Call for USCDI version 4
export async function postToValidatorV4(prevState: object | undefined, formData: FormData) {
return await postToUscdiValidator(formData, 'v4')
}
18 changes: 16 additions & 2 deletions src/components/c-cda/validation/results/OriginalCCDAResult.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import palette from '@/styles/palette'
import { Accordion, AccordionDetails, AccordionSummary, Box, Typography } from '@mui/material'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { prism } from 'react-syntax-highlighter/dist/esm/styles/prism'
import xmlFormatter from 'xml-formatter'
interface OriginalCCDAResultProps {
xmlData: { ccdaFileContents: string }
}

const OriginalCCDAResult = ({ xmlData }: OriginalCCDAResultProps) => {
return (
<Accordion
slotProps={{
transition: {
timeout: 50, // Adjust the expand/collapse animation speed (set to 0 to disable)
},
}}
sx={{
py: 0,
'&:before': {
Expand All @@ -19,12 +27,18 @@ const OriginalCCDAResult = ({ xmlData }: OriginalCCDAResultProps) => {
elevation={1}
>
<AccordionSummary sx={{ borderBottom: `1px solid ${palette.divider}` }} expandIcon={<ExpandMoreIcon />}>
<Typography sx={{ fontWeight: 'bold', border: `` }}>C-CDA</Typography>
<Typography sx={{ fontWeight: 'bold', border: `` }}>C-CDA Data</Typography>
</AccordionSummary>

<AccordionDetails sx={{ p: 2 }}>
<Box sx={{ marginBottom: 1, width: '100%', overflow: 'auto' }} p={2}>
<pre style={{ whiteSpace: 'pre-line', wordWrap: 'break-word' }}>{xmlData.ccdaFileContents}</pre>
<SyntaxHighlighter language="xml" style={prism} wrapLongLines={true}>
{xmlFormatter(xmlData.ccdaFileContents, {
indentation: ' ',
collapseContent: true,
lineSeparator: '\n',
})}
</SyntaxHighlighter>
</Box>
</AccordionDetails>
</Accordion>
Expand Down
2 changes: 1 addition & 1 deletion src/components/c-cda/validation/results/ValidationMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const ValidatorMenu = ({
) : null}

<MenuItem sx={{ fontWeight: 'bold' }} onClick={() => onScroll(originalCCDARef)}>
Original C-CDA
Submitted C-CDA Document
</MenuItem>
</List>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { Typography, Box, Accordion, AccordionSummary, AccordionDetails } from '@mui/material'
import palette from '@/styles/palette'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
Expand Down Expand Up @@ -33,18 +33,36 @@ const DetailsAccordion = ({
defaultExpanded,
referenceCCDAResults,
}: DetailsProps) => {
const [expanded, setExpanded] = useState(false)
const [expanded, setExpanded] = useState(
disabled || details.length === 0 ? false : defaultExpanded !== undefined ? defaultExpanded : false
)

const [content, setContent] = useState<CCDAValidationResult[]>([])
const [contentLoaded, setContentLoaded] = useState(false)
const userClickedAccordian = useRef(false) // Prevents scroll to default expanded accordians on component load

useEffect(() => {
if (expanded && !contentLoaded) {
setContent(details)
setTimeout(() => {
setContentLoaded(true)
}, 250)
}

// Scroll after content has been set
if (expanded && contentLoaded && userClickedAccordian.current) {
executeScroll()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [expanded, contentLoaded, details])

const handleAccordionChange = () => {
userClickedAccordian.current = !expanded // Set to true only when expanding
setExpanded((prevExpanded) => !prevExpanded)

// If expanding, reset contentLoaded to false to show the loading message
if (!expanded) {
setTimeout(() => {
setContent(details)
setContentLoaded(true)
executeScroll()
}, 500)
setContentLoaded(false)
}
}

Expand All @@ -59,6 +77,11 @@ const DetailsAccordion = ({

return (
<Accordion
slotProps={{
transition: {
timeout: 50, // Adjust the expand/collapse animation speed (set to 0 to disable)
},
}}
sx={{
py: 0,
'&:before': {
Expand Down
Loading

0 comments on commit a9032ad

Please sign in to comment.