diff --git a/client/src/components/InputsModal/AuthSettings.ts b/client/src/components/InputsModal/AuthSettings.ts index 3d3fbf1bc..cf750dfe7 100644 --- a/client/src/components/InputsModal/AuthSettings.ts +++ b/client/src/components/InputsModal/AuthSettings.ts @@ -177,7 +177,7 @@ export const getAuthFields = ( }, ] as TestInput[]; - // If the requirement contains custom fields, replace default fields + // If the input contains custom fields, replace default fields const fieldsToUpdate = components.map((component) => component.name); fields.forEach((field, i) => { if (fieldsToUpdate.includes(field.name)) { @@ -322,7 +322,7 @@ export const getAccessFields = ( }, ] as TestInput[]; - // If the requirement contains custom fields, replace default fields + // If the input contains custom fields, replace default fields const fieldsToUpdate = components.map((component) => component.name); fields.forEach((field, i) => { if (fieldsToUpdate.includes(field.name)) { diff --git a/client/src/components/InputsModal/AuthTypeSelector.tsx b/client/src/components/InputsModal/AuthTypeSelector.tsx index 6979e209e..2e016c431 100644 --- a/client/src/components/InputsModal/AuthTypeSelector.tsx +++ b/client/src/components/InputsModal/AuthTypeSelector.tsx @@ -55,7 +55,7 @@ const AuthTypeSelector: FC = ({ input, index, inputsMap, setIn return ( = ({ requirement, isMissingInput = false }) => { +const FieldLabel: FC = ({ input, isMissingInput = false }) => { const { classes } = useStyles(); - const fieldLabelText = (requirement.title || requirement.name) as string; + const fieldLabelText = (input.title || input.name) as string; // Radio buttons and single checkboxes will always have an input value const requiredLabel = - !requirement.optional && - requirement.type !== 'radio' && - !(requirement.type === 'checkbox' && !requirement.options?.list_options?.length) + !input.optional && + input.type !== 'radio' && + !(input.type === 'checkbox' && !input.options?.list_options?.length) ? ' (required)' : ''; - const lockedIcon = requirement.locked && ( - - ); + const lockedIcon = input.locked && ; return ( <> diff --git a/client/src/components/InputsModal/InputAccess.tsx b/client/src/components/InputsModal/InputAccess.tsx index cc321c82e..936235437 100644 --- a/client/src/components/InputsModal/InputAccess.tsx +++ b/client/src/components/InputsModal/InputAccess.tsx @@ -1,5 +1,7 @@ import React, { FC, useEffect } from 'react'; -import { Card, CardContent, InputLabel, List, ListItem, Typography } from '@mui/material'; +import { Card, CardContent, InputLabel, List, ListItem } from '@mui/material'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import { Auth, TestInput } from '~/models/testSuiteModels'; import { AuthType, getAccessFields } from '~/components/InputsModal/AuthSettings'; import FieldLabel from '~/components/InputsModal/FieldLabel'; @@ -8,25 +10,23 @@ import useStyles from './styles'; import AuthTypeSelector from './AuthTypeSelector'; export interface InputAccessProps { - requirement: TestInput; + input: TestInput; index: number; inputsMap: Map; setInputsMap: (map: Map, edited?: boolean) => void; } -const InputAccess: FC = ({ requirement, index, inputsMap, setInputsMap }) => { +const InputAccess: FC = ({ input, index, inputsMap, setInputsMap }) => { const { classes } = useStyles(); const [accessValues, setAccessValues] = React.useState>(new Map()); const [accessValuesPopulated, setAccessValuesPopulated] = React.useState(false); // Default auth type settings const [authType, setAuthType] = React.useState( - requirement.options?.components - ? (requirement.options?.components[0].default as string) - : 'public', + input.options?.components ? (input.options?.components[0].default as string) : 'public', ); const [accessFields, setAccessFields] = React.useState( - getAccessFields(authType as AuthType, accessValues, requirement.options?.components || []), + getAccessFields(authType as AuthType, accessValues, input.options?.components || []), ); useEffect(() => { @@ -64,7 +64,7 @@ const InputAccess: FC = ({ requirement, index, inputsMap, setI useEffect(() => { // Recalculate hidden fields setAccessFields( - getAccessFields(authType as AuthType, accessValues, requirement.options?.components || []), + getAccessFields(authType as AuthType, accessValues, input.options?.components || []), ); // Update inputsMap while maintaining hidden values @@ -73,32 +73,28 @@ const InputAccess: FC = ({ requirement, index, inputsMap, setI const accessValuesObject = Object.fromEntries(accessValues) as Auth; const combinedValues = { ...combinedStartingValues, ...accessValuesObject }; const stringifiedAccessValues = JSON.stringify(combinedValues); - inputsMap.set(requirement.name, stringifiedAccessValues); + inputsMap.set(input.name, stringifiedAccessValues); setInputsMap(new Map(inputsMap)); } }, [accessValues]); const getStartingValues = () => { - // Pre-populate values from AuthFields, requirement, and inputsMap in order of precedence + // Pre-populate values from AuthFields, input, and inputsMap in order of precedence const fieldDefaultValues = accessFields.reduce( (acc, field) => ({ ...acc, [field.name]: field.default }), {}, ) as Auth; - const requirementDefaultValues = - requirement.default && typeof requirement.default === 'string' - ? (JSON.parse(requirement.default) as Auth) - : {}; - const requirementStartingValues = - requirement.value && typeof requirement.value === 'string' - ? (JSON.parse(requirement.value) as Auth) - : {}; - const inputsMapValues = inputsMap.get(requirement.name) - ? (JSON.parse(inputsMap.get(requirement.name) as string) as Auth) + const inputDefaultValues = + input.default && typeof input.default === 'string' ? (JSON.parse(input.default) as Auth) : {}; + const inputStartingValues = + input.value && typeof input.value === 'string' ? (JSON.parse(input.value) as Auth) : {}; + const inputsMapValues = inputsMap.get(input.name) + ? (JSON.parse(inputsMap.get(input.name) as string) as Auth) : {}; return { ...fieldDefaultValues, - ...requirementDefaultValues, - ...requirementStartingValues, + ...inputDefaultValues, + ...inputStartingValues, ...inputsMapValues, } as Auth; }; @@ -113,20 +109,20 @@ const InputAccess: FC = ({ requirement, index, inputsMap, setI - + - {requirement.description && ( - - {requirement.description} - + {input.description && ( + + {input.description} + )} ; setInputsMap: (map: Map, edited?: boolean) => void; } -const InputAuth: FC = ({ requirement, index, inputsMap, setInputsMap }) => { +const InputAuth: FC = ({ input, index, inputsMap, setInputsMap }) => { const { classes } = useStyles(); const [authValues, setAuthValues] = React.useState>(new Map()); const [authValuesPopulated, setAuthValuesPopulated] = React.useState(false); // Default auth type settings const [authType, setAuthType] = React.useState( - requirement.options?.components - ? (requirement.options?.components[0].default as string) - : 'public', + input.options?.components ? (input.options?.components[0].default as string) : 'public', ); const [authFields, setAuthFields] = React.useState( - getAuthFields(authType as AuthType, authValues, requirement.options?.components || []), + getAuthFields(authType as AuthType, authValues, input.options?.components || []), ); useEffect(() => { @@ -64,40 +64,34 @@ const InputAuth: FC = ({ requirement, index, inputsMap, setInput useEffect(() => { // Recalculate hidden fields - setAuthFields( - getAuthFields(authType as AuthType, authValues, requirement.options?.components || []), - ); + setAuthFields(getAuthFields(authType as AuthType, authValues, input.options?.components || [])); // Update inputsMap if (authValuesPopulated) { const stringifiedAuthValues = JSON.stringify(Object.fromEntries(authValues)); - inputsMap.set(requirement.name, stringifiedAuthValues); + inputsMap.set(input.name, stringifiedAuthValues); setInputsMap(new Map(inputsMap)); } }, [authValues]); const getStartingValues = () => { - // Pre-populate values from AuthFields, requirement, and inputsMap in order of precedence + // Pre-populate values from AuthFields, input, and inputsMap in order of precedence const fieldDefaultValues = authFields.reduce( (acc, field) => ({ ...acc, [field.name]: field.default }), {}, ) as Auth; - const requirementDefaultValues = - requirement.default && typeof requirement.default === 'string' - ? (JSON.parse(requirement.default) as Auth) - : {}; - const requirementStartingValues = - requirement.value && typeof requirement.value === 'string' - ? (JSON.parse(requirement.value) as Auth) - : {}; - const inputsMapValues = inputsMap.get(requirement.name) - ? (JSON.parse(inputsMap.get(requirement.name) as string) as Auth) + const inputDefaultValues = + input.default && typeof input.default === 'string' ? (JSON.parse(input.default) as Auth) : {}; + const inputStartingValues = + input.value && typeof input.value === 'string' ? (JSON.parse(input.value) as Auth) : {}; + const inputsMapValues = inputsMap.get(input.name) + ? (JSON.parse(inputsMap.get(input.name) as string) as Auth) : {}; return { ...fieldDefaultValues, - ...requirementDefaultValues, - ...requirementStartingValues, + ...inputDefaultValues, + ...inputStartingValues, ...inputsMapValues, } as Auth; }; @@ -110,14 +104,14 @@ const InputAuth: FC = ({ requirement, index, inputsMap, setInput return ( - {requirement.description && ( - - {requirement.description} - + {input.description && ( + + {input.description} + )} ; setInputsMap: (map: Map, edited?: boolean) => void; } const InputCheckboxGroup: FC = ({ - requirement, + input, index, inputsMap, setInputsMap, @@ -33,19 +34,19 @@ const InputCheckboxGroup: FC = ({ let inputMapValues: string[] = []; try { // Parse JSON string of values - inputMapValues = JSON.parse(inputsMap.get(requirement.name) as string) as string[]; + inputMapValues = JSON.parse(inputsMap.get(input.name) as string) as string[]; // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // If not JSON string, then either array or single value - if (Array.isArray(inputsMap.get(requirement.name))) { - inputMapValues = inputsMap.get(requirement.name) as string[]; + if (Array.isArray(inputsMap.get(input.name))) { + inputMapValues = inputsMap.get(input.name) as string[]; } else { - inputMapValues = [inputsMap.get(requirement.name) as string]; // expecting single value + inputMapValues = [inputsMap.get(input.name) as string]; // expecting single value } } - const defaultValues = inputMapValues || requirement.default || []; - const options = requirement.options?.list_options; + const defaultValues = inputMapValues || input.default || []; + const options = input.options?.list_options; let startingValues = {}; // Convert array of checked values to map from item name to checked status @@ -63,12 +64,11 @@ const InputCheckboxGroup: FC = ({ return startingValues as CheckboxValues; }); - const isMissingInput = - hasBeenModified && !requirement.optional && inputsMap.get(requirement.name) === '[]'; + const isMissingInput = hasBeenModified && !input.optional && inputsMap.get(input.name) === '[]'; useEffect(() => { // Make sure starting values get set in inputsMap - inputsMap.set(requirement.name, transformValuesToJSONArray(values)); + inputsMap.set(input.name, transformValuesToJSONArray(values)); setInputsMap(new Map(inputsMap), false); }, []); @@ -77,7 +77,7 @@ const InputCheckboxGroup: FC = ({ ...values, [event.target.name]: event.target.checked, }; - inputsMap.set(requirement.name, transformValuesToJSONArray(newValues)); + inputsMap.set(input.name, transformValuesToJSONArray(newValues)); setInputsMap(new Map(inputsMap)); setValues(newValues); setHasBeenModified(true); @@ -98,22 +98,22 @@ const InputCheckboxGroup: FC = ({ - + - {requirement.description && ( - - {requirement.description} - + {input.description && ( + + {input.description} + )} - - {requirement.options?.list_options?.map((option, i) => ( + + {input.options?.list_options?.map((option, i) => ( ; setInputsMap: (map: Map, edited?: boolean) => void; @@ -20,7 +15,7 @@ export interface InputComboboxProps { } const InputCombobox: FC = ({ - requirement, + input, index, inputsMap, setInputsMap, @@ -29,12 +24,12 @@ const InputCombobox: FC = ({ const { classes } = useStyles(); const getDefaultValue = (): InputOption | null => { - const options = requirement.options?.list_options; + const options = input.options?.list_options; if (!options) return null; let defaultValue = options[0]; // set to first option if no default provided - if (requirement.default && typeof requirement.default === 'string') { - const discoveredOption = options.find((option) => option.value === requirement.default); + if (input.default && typeof input.default === 'string') { + const discoveredOption = options.find((option) => option.value === input.default); if (discoveredOption) defaultValue = discoveredOption; } return defaultValue; @@ -44,32 +39,32 @@ const InputCombobox: FC = ({ - - + + - {requirement.description && ( - - {requirement.description} - + {input.description && ( + + {input.description} + )} option.value === value.value} renderInput={(params) => ( = ({ )} onChange={(event, newValue: InputOption | null) => { const value = newValue?.value; - inputsMap.set(requirement.name, value); + inputsMap.set(input.name, value); setInputsMap(new Map(inputsMap)); }} /> diff --git a/client/src/components/InputsModal/InputFields.tsx b/client/src/components/InputsModal/InputFields.tsx index 366de7553..541f632f2 100644 --- a/client/src/components/InputsModal/InputFields.tsx +++ b/client/src/components/InputsModal/InputFields.tsx @@ -19,14 +19,14 @@ export interface InputFieldsProps { const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) => { return ( - {inputs.map((requirement: TestInput, index: number) => { - if (!requirement.hide) { - switch (requirement.type) { + {inputs.map((input: TestInput, index: number) => { + if (!input.hide) { + switch (input.type) { case 'auth_info': - if (requirement.options?.mode === 'auth') { + if (input.options?.mode === 'auth') { return ( setInputsMap(newInputsMap)} @@ -36,7 +36,7 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) } return ( setInputsMap(newInputsMap)} @@ -46,7 +46,7 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) case 'oauth_credentials': return ( setInputsMap(newInputsMap)} @@ -54,10 +54,10 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) /> ); case 'checkbox': - if (requirement.options?.list_options?.length) { + if (input.options?.list_options?.length) { return ( @@ -70,7 +70,7 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) // if no options listed then assume single checkbox input return ( @@ -83,7 +83,7 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) case 'radio': return ( setInputsMap(newInputsMap)} @@ -93,7 +93,7 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) case 'select': return ( @@ -105,7 +105,7 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) default: return ( setInputsMap(newInputsMap)} diff --git a/client/src/components/InputsModal/InputHelpers.ts b/client/src/components/InputsModal/InputHelpers.ts index 4f751cd4f..ad17c753a 100644 --- a/client/src/components/InputsModal/InputHelpers.ts +++ b/client/src/components/InputsModal/InputHelpers.ts @@ -116,6 +116,7 @@ export const serializeMap = ( ): string => { const flatObj = inputs.map((requirement: TestInput) => { // Parse out \n chars from descriptions + // const parsedDescription = requirement.description?.replaceAll('\n', ' ').trim(); const parsedDescription = requirement.description?.replaceAll('\n', ' ').trim(); if (requirement.type === 'oauth_credentials') { return { diff --git a/client/src/components/InputsModal/InputOAuthCredentials.tsx b/client/src/components/InputsModal/InputOAuthCredentials.tsx index 8335a0cc4..a5b5a146b 100644 --- a/client/src/components/InputsModal/InputOAuthCredentials.tsx +++ b/client/src/components/InputsModal/InputOAuthCredentials.tsx @@ -11,20 +11,22 @@ import { ListItemButton, Typography, } from '@mui/material'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import { OAuthCredentials, TestInput } from '~/models/testSuiteModels'; import FieldLabel from './FieldLabel'; import useStyles from './styles'; import RequiredInputWarning from './RequiredInputWarning'; export interface InputOAuthCredentialsProps { - requirement: TestInput; + input: TestInput; index: number; inputsMap: Map; setInputsMap: (map: Map, edited?: boolean) => void; } const InputOAuthCredentials: FC = ({ - requirement, + input, index, inputsMap, setInputsMap, @@ -42,7 +44,7 @@ const InputOAuthCredentials: FC = ({ client_id: '', client_secret: '', token_url: '', - ...JSON.parse((inputsMap.get(requirement.name) as string) || '{}'), + ...JSON.parse((inputsMap.get(input.name) as string) || '{}'), } as OAuthCredentials; const showRefreshDetails = !!oAuthCredentials.refresh_token; @@ -51,7 +53,7 @@ const InputOAuthCredentials: FC = ({ { name: 'access_token', title: 'Access Token', - optional: requirement.optional, + optional: input.optional, }, { name: 'refresh_token', @@ -106,26 +108,26 @@ const InputOAuthCredentials: FC = ({ - + {fieldLabel} {field.description && ( - - {field.description} - + + {input.description} + )} = ({ onChange={(event) => { const value = event.target.value; oAuthCredentials[field.name as keyof OAuthCredentials] = value; - inputsMap.set(requirement.name, JSON.stringify(oAuthCredentials)); + inputsMap.set(input.name, JSON.stringify(oAuthCredentials)); setInputsMap(new Map(inputsMap)); }} /> @@ -152,15 +154,15 @@ const InputOAuthCredentials: FC = ({ - + - {requirement.description && ( + {input.description && ( - {requirement.description} + {input.description} )} {oAuthFields.map((field) => !field.hide && oAuthField(field))} diff --git a/client/src/components/InputsModal/InputRadioGroup.tsx b/client/src/components/InputsModal/InputRadioGroup.tsx index 1f57e55d6..2d962854f 100644 --- a/client/src/components/InputsModal/InputRadioGroup.tsx +++ b/client/src/components/InputsModal/InputRadioGroup.tsx @@ -6,38 +6,32 @@ import { ListItem, Radio, RadioGroup, - Typography, } from '@mui/material'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import { TestInput } from '~/models/testSuiteModels'; import FieldLabel from './FieldLabel'; import useStyles from './styles'; export interface InputRadioGroupProps { - requirement: TestInput; + input: TestInput; index: number; inputsMap: Map; setInputsMap: (map: Map, edited?: boolean) => void; } -const InputRadioGroup: FC = ({ - requirement, - index, - inputsMap, - setInputsMap, -}) => { +const InputRadioGroup: FC = ({ input, index, inputsMap, setInputsMap }) => { const { classes } = useStyles(); const firstOptionValue = - requirement.options?.list_options && requirement.options?.list_options?.length > 0 - ? requirement.options?.list_options[0]?.value + input.options?.list_options && input.options?.list_options?.length > 0 + ? input.options?.list_options[0]?.value : ''; // Set starting value to first option if no value and no default useEffect(() => { const startingValue = - (inputsMap.get(requirement.name) as string) || - (requirement.default as string) || - firstOptionValue; - inputsMap.set(requirement.name, startingValue); + (inputsMap.get(input.name) as string) || (input.default as string) || firstOptionValue; + inputsMap.set(input.name, startingValue); setInputsMap(new Map(inputsMap)); }, []); @@ -45,32 +39,30 @@ const InputRadioGroup: FC = ({ - + - {requirement.description && ( - - {requirement.description} - + {input.description && ( + + {input.description} + )} { - inputsMap.set(requirement.name, event.target.value); + inputsMap.set(input.name, event.target.value); setInputsMap(new Map(inputsMap)); }} > - {requirement.options?.list_options?.map((option, i) => ( + {input.options?.list_options?.map((option, i) => ( } diff --git a/client/src/components/InputsModal/InputSingleCheckbox.tsx b/client/src/components/InputsModal/InputSingleCheckbox.tsx index e511216a5..4ff54ae21 100644 --- a/client/src/components/InputsModal/InputSingleCheckbox.tsx +++ b/client/src/components/InputsModal/InputSingleCheckbox.tsx @@ -1,25 +1,20 @@ import React, { FC, useEffect } from 'react'; -import { - Checkbox, - FormControl, - FormControlLabel, - FormGroup, - ListItem, - Typography, -} from '@mui/material'; +import { Checkbox, FormControl, FormControlLabel, FormGroup, ListItem } from '@mui/material'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import { TestInput } from '~/models/testSuiteModels'; import FieldLabel from '~/components/InputsModal/FieldLabel'; import useStyles from '~/components/InputsModal/styles'; export interface InputSingleCheckboxProps { - requirement: TestInput; + input: TestInput; index: number; inputsMap: Map; setInputsMap: (map: Map, edited?: boolean) => void; } const InputSingleCheckbox: FC = ({ - requirement, + input, index, inputsMap, setInputsMap, @@ -28,18 +23,17 @@ const InputSingleCheckbox: FC = ({ const [hasBeenModified, setHasBeenModified] = React.useState(false); const [value, setValue] = React.useState(false); - const isMissingInput = - hasBeenModified && !requirement.optional && inputsMap.get(requirement.name) === false; + const isMissingInput = hasBeenModified && !input.optional && inputsMap.get(input.name) === false; // No "required" formatting because single checkboxes always have a value assigned - const fieldLabel = ; + const fieldLabel = ; useEffect(() => { - const inputsValue = inputsMap.get(requirement.name) as string; + const inputsValue = inputsMap.get(input.name) as string; let startingValue = false; if (inputsValue === 'true') { startingValue = true; - } else if (inputsValue !== 'false' && (requirement.default as string) === 'true') { + } else if (inputsValue !== 'false' && (input.default as string) === 'true') { startingValue = true; } setValue(startingValue); @@ -49,7 +43,7 @@ const InputSingleCheckbox: FC = ({ const newValue = event.target.checked; setValue(newValue); setHasBeenModified(true); - inputsMap.set(requirement.name, newValue.toString()); + inputsMap.set(input.name, newValue.toString()); setInputsMap(new Map(inputsMap)); }; @@ -57,20 +51,20 @@ const InputSingleCheckbox: FC = ({ - {requirement.description && ( - - {requirement.description} - + {input.description && ( + + {input.description} + )} {/* TODO: required means set to true and locked? */} - + = ({ /> } label={fieldLabel} - key={`checkbox-${requirement.name}`} + key={`checkbox-${input.name}`} /> diff --git a/client/src/components/InputsModal/InputTextField.tsx b/client/src/components/InputsModal/InputTextField.tsx index 2a7458c10..28ea22b07 100644 --- a/client/src/components/InputsModal/InputTextField.tsx +++ b/client/src/components/InputsModal/InputTextField.tsx @@ -1,59 +1,55 @@ import React, { FC } from 'react'; -import { FormControl, FormLabel, Input, ListItem, Typography } from '@mui/material'; +import { FormControl, FormLabel, Input, ListItem } from '@mui/material'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import { TestInput } from '~/models/testSuiteModels'; import FieldLabel from './FieldLabel'; import useStyles from './styles'; export interface InputTextFieldProps { - requirement: TestInput; + input: TestInput; index: number; inputsMap: Map; setInputsMap: (map: Map, edited?: boolean) => void; } -const InputTextField: FC = ({ - requirement, - index, - inputsMap, - setInputsMap, -}) => { +const InputTextField: FC = ({ input, index, inputsMap, setInputsMap }) => { const { classes } = useStyles(); const [hasBeenModified, setHasBeenModified] = React.useState(false); - const isMissingInput = - hasBeenModified && !requirement.optional && !inputsMap.get(requirement.name); + const isMissingInput = hasBeenModified && !input.optional && !inputsMap.get(input.name); return ( - - + + - {requirement.description && ( - - {requirement.description} - + {input.description && ( + + {input.description} + )} { if (e.currentTarget === e.target) { setHasBeenModified(true); @@ -61,7 +57,7 @@ const InputTextField: FC = ({ }} onChange={(event) => { const value = event.target.value; - inputsMap.set(requirement.name, value); + inputsMap.set(input.name, value); setInputsMap(new Map(inputsMap)); }} /> diff --git a/client/src/components/InputsModal/InputsModal.tsx b/client/src/components/InputsModal/InputsModal.tsx index 142fad170..e4ad0fe85 100644 --- a/client/src/components/InputsModal/InputsModal.tsx +++ b/client/src/components/InputsModal/InputsModal.tsx @@ -82,11 +82,8 @@ const InputsModal: FC = ({ // Set persisted values and defaults at render useEffect(() => { inputsMap.clear(); - inputs.forEach((requirement: TestInput) => { - inputsMap.set( - requirement.name, - sessionData.get(requirement.name) || requirement.default || '', - ); + inputs.forEach((input: TestInput) => { + inputsMap.set(input.name, sessionData.get(input.name) || input.default || ''); }); setInputsMap(new Map(inputsMap)); }, [inputs, sessionData]); diff --git a/client/src/components/InputsModal/__tests__/Inputs.test.tsx b/client/src/components/InputsModal/__tests__/Inputs.test.tsx index eb1827014..0fdb6c0c6 100644 --- a/client/src/components/InputsModal/__tests__/Inputs.test.tsx +++ b/client/src/components/InputsModal/__tests__/Inputs.test.tsx @@ -35,7 +35,7 @@ describe('Input Components', () => { ()} setInputsMap={() => {}} @@ -71,7 +71,7 @@ describe('Input Components', () => { ()} setInputsMap={() => {}} @@ -95,7 +95,7 @@ describe('Input Components', () => { ()} setInputsMap={() => {}} @@ -119,7 +119,7 @@ describe('Input Components', () => { ()} setInputsMap={() => {}} @@ -143,7 +143,7 @@ describe('Input Components', () => { ()} setInputsMap={() => {}} @@ -176,7 +176,7 @@ describe('Input Components', () => { ()} setInputsMap={() => {}} @@ -209,7 +209,7 @@ describe('Input Components', () => { ()} setInputsMap={() => {}} diff --git a/client/src/components/InputsModal/styles.tsx b/client/src/components/InputsModal/styles.tsx index 69e2d6c63..5acd15ab0 100644 --- a/client/src/components/InputsModal/styles.tsx +++ b/client/src/components/InputsModal/styles.tsx @@ -28,11 +28,13 @@ export default makeStyles()((theme: Theme) => ({ fontWeight: 600, }, inputDescription: { - mx: 0, - mb: 1, fontSize: '0.95rem', color: theme.palette.common.gray, - '&.Mui-disabled': { + '& > p': { + margin: '0 0 8px 0 !important', + lineHeight: '1.75rem', + }, + '& .Mui-disabled': { color: theme.palette.common.grayDark, }, }, @@ -49,7 +51,7 @@ export default makeStyles()((theme: Theme) => ({ width: '100%', mx: 2, borderColor: theme.palette.common.gray, - '&:focus-within': { + '& :focus-within': { borderColor: theme.palette.secondary.main, }, }, @@ -71,21 +73,21 @@ export default makeStyles()((theme: Theme) => ({ '& .MuiToggleButtonGroup-grouped': { margin: theme.spacing(0.5), border: 0, - '&.Mui-disabled': { + '& .Mui-disabled': { border: 0, }, - '&:not(:first-of-type), :first-of-type': { + '& :not(:first-of-type), :first-of-type': { borderRadius: theme.shape.borderRadius, }, }, }, toggleButton: { color: theme.palette.common.grayDark, - '&:hover, :focus-within': { + '& :hover, :focus-within': { backgroundColor: theme.palette.common.grayLight, fontWeight: 'bolder', }, - '&.Mui-selected': { + '& .Mui-selected': { backgroundColor: 'unset', border: `1px solid ${theme.palette.secondary.main} !important`, color: theme.palette.secondary.main, diff --git a/client/src/components/SuiteOptionsPage/SuiteOptionsPage.tsx b/client/src/components/SuiteOptionsPage/SuiteOptionsPage.tsx index 2765820e9..d179df1d9 100644 --- a/client/src/components/SuiteOptionsPage/SuiteOptionsPage.tsx +++ b/client/src/components/SuiteOptionsPage/SuiteOptionsPage.tsx @@ -1,9 +1,9 @@ import React, { FC, useEffect, useRef } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; +import { Typography, Box, Container } from '@mui/material'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { useSnackbar } from 'notistack'; -import { Typography, Box, Container } from '@mui/material'; import { basePath } from '~/api/infernoApiService'; import { postTestSessions } from '~/api/TestSessionApi'; import { TestSuite, TestSession, SuiteOption } from '~/models/testSuiteModels'; diff --git a/client/src/components/TestSuite/TestSuiteDetails/TestGroupCard.tsx b/client/src/components/TestSuite/TestSuiteDetails/TestGroupCard.tsx index 76f7eab0d..70aa66046 100644 --- a/client/src/components/TestSuite/TestSuiteDetails/TestGroupCard.tsx +++ b/client/src/components/TestSuite/TestSuiteDetails/TestGroupCard.tsx @@ -1,13 +1,13 @@ import React, { FC, useMemo } from 'react'; import { Box, Card, Divider, Typography } from '@mui/material'; -import useStyles from './styles'; import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import { TestGroup, RunnableType, TestSuite } from '~/models/testSuiteModels'; import InputOutputList from './TestListItem/InputOutputList'; import ResultIcon from './ResultIcon'; import TestRunButton from '~/components/TestSuite/TestRunButton/TestRunButton'; import { shouldShowDescription } from '~/components/TestSuite/TestSuiteUtilities'; -import remarkGfm from 'remark-gfm'; +import useStyles from './styles'; interface TestGroupCardProps { children: React.ReactNode; diff --git a/config/presets/demo_preset.json.erb b/config/presets/demo_preset.json.erb index 0e8b98cef..7a9084c7f 100644 --- a/config/presets/demo_preset.json.erb +++ b/config/presets/demo_preset.json.erb @@ -14,6 +14,7 @@ "name": "patient_id", "type": "text", "title": "Patient ID", + "description": "\n### This is a markdown description.\nThis is a new line.", "value": "85" }, { diff --git a/dev_suites/dev_demo_ig_stu1/groups/demo_group.rb b/dev_suites/dev_demo_ig_stu1/groups/demo_group.rb index 19788486c..26a98f849 100644 --- a/dev_suites/dev_demo_ig_stu1/groups/demo_group.rb +++ b/dev_suites/dev_demo_ig_stu1/groups/demo_group.rb @@ -6,22 +6,21 @@ class DemoGroup < Inferno::TestGroup title 'Demo Group' description %( - # This is a markdown header - **Inferno** [github](https://github.com/inferno-framework/inferno-core) + # This is a markdown header + **Inferno** [github](https://github.com/inferno-framework/inferno-core) - Below is a markdown table - | Column 1 | Column 2 | Column 3 | - | :--- | :---: | ---: | - | Entry 1 | Entry 2 | Entry 3| - | Entry 4 | Entry 5 | Entry 6 | + Below is a markdown table + | Column 1 | Column 2 | Column 3 | + | :--- | :---: | ---: | + | Entry 1 | Entry 2 | Entry 3| + | Entry 4 | Entry 5 | Entry 6 | - This is a dummy canonical link http://hl7.org/fhir/ValueSet/my-valueset|0.8 that should not be - interpreted as a table - - > This is a blockquote. - > - > Blockquotes are useful for quoting standards or other references. + This is a dummy canonical link http://hl7.org/fhir/ValueSet/my-valueset|0.8 that should not be + interpreted as a table + > This is a blockquote. + > + > Blockquotes are useful for quoting standards or other references. ) # Inputs and outputs @@ -31,7 +30,13 @@ class DemoGroup < Inferno::TestGroup # while also allowing type hints at a higher level. input :url, title: 'URL', description: 'Insert url of FHIR server', default: 'https://inferno.healthit.gov/reference-server/r4' - input :patient_id, title: 'Patient ID', default: '85' + input :patient_id, + title: 'Patient ID', + default: '85', + description: %( + ### This is a markdown description. + This is a new line. + ) input :bearer_token, optional: true, default: 'SAMPLE_TOKEN' output :observation_id, diff --git a/dev_suites/dev_infrastructure_test/serializer_group.rb b/dev_suites/dev_infrastructure_test/serializer_group.rb index e54699690..1c94d4cb9 100644 --- a/dev_suites/dev_infrastructure_test/serializer_group.rb +++ b/dev_suites/dev_infrastructure_test/serializer_group.rb @@ -10,6 +10,14 @@ class SerializerGroup < Inferno::TestGroup description: 'INPUT3_DESCRIPTION', default: 'INPUT3_DEFAULT', type: 'text' + input :markdown_input, + description: %( + # Markdown Title + + Markdown description + ), + default: 'INPUT3_DEFAULT', + type: 'text' output :output3 diff --git a/lib/inferno/apps/web/serializers/input.rb b/lib/inferno/apps/web/serializers/input.rb index 3fb0e77de..14a776396 100644 --- a/lib/inferno/apps/web/serializers/input.rb +++ b/lib/inferno/apps/web/serializers/input.rb @@ -1,3 +1,4 @@ +require_relative 'markdown_extractor' require_relative 'serializer' module Inferno @@ -7,7 +8,7 @@ class Input < Serializer identifier :name field :title, if: :field_present? - field :description, if: :field_present? + field :description, extractor: MarkdownExtractor, if: :field_present? field :type, if: :field_present? field :default, if: :field_present? field :optional, if: :field_present? diff --git a/lib/inferno/apps/web/serializers/markdown_extractor.rb b/lib/inferno/apps/web/serializers/markdown_extractor.rb new file mode 100644 index 000000000..1eb8e7f0a --- /dev/null +++ b/lib/inferno/apps/web/serializers/markdown_extractor.rb @@ -0,0 +1,16 @@ +require 'blueprinter' +require_relative '../../../utils/markdown_formatter' + +module Inferno + module Web + module Serializers + class MarkdownExtractor < Blueprinter::Extractor + include Inferno::Utils::MarkdownFormatter + + def extract(field_name, object, _local_options, _options = {}) + format_markdown(object.send(field_name)) + end + end + end + end +end diff --git a/spec/inferno/utils/preset_template_generator_spec.rb b/spec/inferno/utils/preset_template_generator_spec.rb index 75b5bf700..fec7a8cfc 100644 --- a/spec/inferno/utils/preset_template_generator_spec.rb +++ b/spec/inferno/utils/preset_template_generator_spec.rb @@ -14,7 +14,11 @@ _description: 'Insert url of FHIR server', value: 'https://inferno.healthit.gov/reference-server/r4' }, - { name: 'patient_id', _type: 'text', _title: 'Patient ID', value: '85' }, + { name: 'patient_id', _type: 'text', _title: 'Patient ID', + _description: %( + ### This is a markdown description. + This is a new line. + ), value: '85' }, { name: 'bearer_token', _type: 'text', _optional: true, value: 'SAMPLE_TOKEN' }, { name: 'textarea', _type: 'textarea', _title: 'Textarea Input Example', _description: 'Insert something like a patient resource json here', _optional: true, value: nil }, diff --git a/spec/inferno/web/serializers/test_group_spec.rb b/spec/inferno/web/serializers/test_group_spec.rb index 9d574b4d3..7a01c4d1e 100644 --- a/spec/inferno/web/serializers/test_group_spec.rb +++ b/spec/inferno/web/serializers/test_group_spec.rb @@ -1,6 +1,8 @@ require_relative '../../../../lib/inferno/apps/web/serializers/test_group' +require_relative '../../../../lib/inferno/utils/markdown_formatter' RSpec.describe Inferno::Web::Serializers::TestGroup do + include Inferno::Utils::MarkdownFormatter let(:group) { InfrastructureTest::SerializerGroup } before do @@ -46,7 +48,10 @@ input = Inferno::Entities::Input.new(**raw_input) - expect(input).to eq(definition) + expect(input.name).to eq(definition.name) + expect(input.type).to eq(definition.type) + expect(input.default).to eq(definition.default) + expect(input.description).to eq(format_markdown(definition.description)) end group.output_definitions.each_value do |definition|