Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: FI-1990: Update landing page layout #363

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5c81566
WIP
AlyssaWang May 5, 2023
4b14191
update suite options page
AlyssaWang May 9, 2023
43b8594
refactor selection panel from landing and suite pages
AlyssaWang May 10, 2023
60bdbe8
update page styles
AlyssaWang May 12, 2023
c0a8b35
remove flex props
AlyssaWang May 12, 2023
c5445bf
Merge branch 'main' of github.com:inferno-framework/inferno-core into…
AlyssaWang May 12, 2023
6a568a2
resolve suite flex issues
AlyssaWang May 15, 2023
e1ee862
add disabled logic
AlyssaWang May 15, 2023
ce294a2
Merge branch 'main' into lp-layout
AlyssaWang May 17, 2023
ae5ea25
add tablet support
AlyssaWang May 18, 2023
0fb2c42
update layout
AlyssaWang May 22, 2023
f4d3ca9
WIP: modify selection panel
AlyssaWang May 24, 2023
6cd42bc
Merge branch 'main' into lp-layout
AlyssaWang May 24, 2023
36685e2
Merge branch 'main' into lp-layout
AlyssaWang May 25, 2023
4b92119
Merge branch 'main' into lp-layout
AlyssaWang May 31, 2023
838c777
update layout
AlyssaWang Jun 1, 2023
8dcfa0c
revise routing scheme
AlyssaWang Jun 1, 2023
4d8d379
clean up styles
AlyssaWang Jun 1, 2023
88d2249
fix flex model
AlyssaWang Jun 1, 2023
23e72d7
reduce flickering on new session
AlyssaWang Jun 2, 2023
f2798f5
remove ref width
AlyssaWang Jun 2, 2023
82276b0
limit async calls
AlyssaWang Jun 2, 2023
757e11a
update styles
AlyssaWang Jun 2, 2023
4c5d93c
resolve rerendering bug
AlyssaWang Jun 2, 2023
e5e0c6e
fix scroll issue
AlyssaWang Jun 2, 2023
a9c6f3b
add level 1 header
AlyssaWang Jun 2, 2023
9d33911
Merge branch 'main' into lp-layout
AlyssaWang Jun 13, 2023
43549db
Merge branch 'main' of github.com:inferno-framework/inferno-core into…
AlyssaWang Jun 21, 2023
5a0072c
Merge branch 'lp-layout' of github.com:inferno-framework/inferno-core…
AlyssaWang Jun 21, 2023
209c7da
Merge branch 'main' of github.com:inferno-framework/inferno-core into…
AlyssaWang Jun 26, 2023
f48b809
resolve lint errors
AlyssaWang Jun 26, 2023
9420b65
update tests
AlyssaWang Jun 27, 2023
c0a9fbe
Merge branch 'main' into lp-layout-2
AlyssaWang Jul 5, 2023
d346a41
Merge branch 'main' of github.com:inferno-framework/inferno-core into…
AlyssaWang Jul 6, 2023
96427fc
add null type
AlyssaWang Jul 6, 2023
0c80cac
Merge branch 'main' into lp-layout-2
AlyssaWang Jul 13, 2023
74651cb
Merge branch 'main' into lp-layout-2
AlyssaWang Jul 24, 2023
8e7f35a
Merge branch 'main' into lp-layout-2
AlyssaWang Jul 26, 2023
451aef0
remove suite options page
AlyssaWang Jul 31, 2023
9af7fce
Merge branch 'main' into lp-layout-2
AlyssaWang Aug 2, 2023
235e2a1
Merge branch 'main' of github.com:inferno-framework/inferno-core into…
AlyssaWang Aug 7, 2023
515f9de
add default suite options
AlyssaWang Aug 7, 2023
0d58e4d
Merge branch 'main' into lp-layout-2
AlyssaWang Aug 16, 2023
5244fb6
Merge branch 'main' into lp-layout-2
AlyssaWang Aug 24, 2023
e13f77d
Merge branch 'main' into lp-layout-2
AlyssaWang Sep 12, 2023
d34ec25
Merge branch 'main' into lp-layout-2
AlyssaWang Sep 19, 2023
358c1e5
Merge branch 'main' into lp-layout-2
AlyssaWang Sep 25, 2023
c10807c
Merge branch 'main' into lp-layout-2
AlyssaWang Nov 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 10 additions & 19 deletions client/src/components/App/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
import React from 'react';
import { createBrowserRouter } from 'react-router-dom';
import LandingPage from '~/components/LandingPage';
import SuiteOptionsPage from '~/components/SuiteOptionsPage';
import TestSessionWrapper from '~/components/TestSuite/TestSessionWrapper';
import { basePath } from '~/api/infernoApiService';
import Page from '~/components/App/Page';
import { TestSuite } from '~/models/testSuiteModels';

export const router = (testSuites: TestSuite[]) => {
const landingPageRoutes = ['/', ':test_suite_id'].map((route) => ({
path: route,
element: (
<Page title={`Inferno Test Suites`}>
<LandingPage testSuites={testSuites} />
</Page>
),
}));

return createBrowserRouter(
[
{
path: '/',
element: (
<Page title={`Inferno Test Suites`}>
<LandingPage testSuites={testSuites} />
</Page>
),
},
{
path: ':test_suite_id',
element: <Page title="Options" />,
loader: ({ params }) => {
if (testSuites.length === 0) return <></>;
const suiteId: string = params.test_suite_id || '';
const suite = testSuites.find((suite) => suite.id === suiteId);
return <SuiteOptionsPage testSuite={suite} />;
},
},
...landingPageRoutes,
{
// Title for TestSessionWrapper is set in the component
// because testSession is not set at the time of render
Expand Down
3 changes: 1 addition & 2 deletions client/src/components/Header/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import { Theme } from '@mui/material/styles';
import { makeStyles } from 'tss-react/mui';
import lightTheme from '~/styles/theme';

export default makeStyles()((theme: Theme) => ({
appbar: {
Expand Down Expand Up @@ -36,7 +35,7 @@ export default makeStyles()((theme: Theme) => ({
fontStyle: 'italic',
},
homeLink: {
color: lightTheme.palette.common.orangeDarker,
color: theme.palette.common.orangeDarker,
textDecoration: 'none',
},
}));
266 changes: 192 additions & 74 deletions client/src/components/LandingPage/LandingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React, { FC, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { Typography, Container, Box } from '@mui/material';
import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
import { useSnackbar } from 'notistack';
import { TestSuite, TestSession } from '~/models/testSuiteModels';
import { TestSuite, TestSession, SuiteOption } from '~/models/testSuiteModels';
import {
ListOptionSelection,
RadioOptionSelection,
isListOptionSelection,
isRadioOptionSelection,
} from '~/models/selectionModels';
import { postTestSessions } from '~/api/TestSessionApi';
import { getStaticPath } from '~/api/infernoApiService';
import { useEffectOnce } from '~/hooks/useEffectOnce';
import { useAppStore } from '~/store/app';
import infernoLogo from '~/images/inferno_logo.png';
import SelectionPanel from '~/components/_common/SelectionPanel/SelectionPanel';
Expand All @@ -21,42 +24,117 @@ export interface LandingPageProps {
}

const LandingPage: FC<LandingPageProps> = ({ testSuites }) => {
const location = useLocation();
const navigate = useNavigate();
const { classes } = useStyles();
const { enqueueSnackbar } = useSnackbar();
const [testSuiteChosen, setTestSuiteChosen] = React.useState<ListOptionSelection>('');
const { test_suite_id } = useParams<{ test_suite_id: string }>();

/* Selections and state */
const [selectedTestSuiteId, setSelectedTestSuiteId] = React.useState<ListOptionSelection>(
test_suite_id || ''
);
const selectedTestSuite = testSuites?.find(
(suite: TestSuite) => suite.id === selectedTestSuiteId
);
const defaultSuiteOptions = selectedTestSuite?.suite_options?.map((option) => ({
// just grab the first to start
// perhaps choices should be persisted in the URL to make it easy to share specific options
id: option.id,
value: option && option.list_options ? option.list_options[0].value : '',
}));
const [selectedSuiteOptions, setSelectedSuiteOptions] = React.useState<SuiteOption[]>(
defaultSuiteOptions || []
);
const [showLandingPage, setShowLandingPage] = React.useState<boolean>(false);
const [showSuiteSelection, setShowSuiteSelection] = React.useState<boolean>(true);

/* CSS variables */
const windowIsSmall = useAppStore((state) => state.windowIsSmall);
const smallWindowThreshold = useAppStore((state) => state.smallWindowThreshold);
const [descriptionWidth, setDescriptionWidth] = React.useState<string>('');

useEffect(() => {
// Only let this trigger once due to async createTestSession
useEffectOnce(() => {
if (testSuites?.length === 1) {
setTestSuiteChosen(testSuites[0].id);
// If only one suite, then default to that suite
setSelectedTestSuiteId(testSuites[0].id);
startTestingClick(testSuites[0]);
} else {
// Handle options and descriptions displays
const suiteDescriptionExists =
selectedTestSuite?.suite_summary || selectedTestSuite?.description;
const suiteOptionsExists =
selectedTestSuite?.suite_options && selectedTestSuite?.suite_options.length > 0;

if (selectedTestSuite && !suiteDescriptionExists && !suiteOptionsExists) {
// If no description and no options, start a test session
startTestingClick(selectedTestSuite);
} else if (selectedTestSuite && suiteOptionsExists) {
// If options, set selection panel to show options
setShowSuiteSelection(false);
setShowLandingPage(true);
} else {
setShowLandingPage(true);
}
}
}, []);
});

useEffect(() => {
// Handle browser back and forward button behavior
setSelectedTestSuiteId(test_suite_id || '');
}, [location]);

const setSelected = (selection: ListOptionSelection | RadioOptionSelection[]) => {
// Check if list option to avoid type errors
if (isListOptionSelection(selection)) setTestSuiteChosen(selection);
useEffect(() => {
getDescriptionWidth();
}, [windowIsSmall]);

const getDescriptionWidth = () => {
setDescriptionWidth(windowIsSmall ? '100%' : `${smallWindowThreshold / 2}px`);
};

// Set selected suite ID and update URL
const setSuiteSelected = (selection: ListOptionSelection | RadioOptionSelection[] | null) => {
// Check if list option to avoid type errors, allow empty string
if (selection !== null && selection !== undefined && isListOptionSelection(selection)) {
setSelectedTestSuiteId(selection);
navigate(`/${selection}`);
}
};

// Set options radio selections
const setOptionsSelected = (selection: ListOptionSelection | RadioOptionSelection[] | null) => {
// Check if radio option to avoid type errors
if (selection && isRadioOptionSelection(selection)) setSelectedSuiteOptions(selection);
};

// Either show options or start test session
const startTestingClick = (suite?: TestSuite) => {
if (suite && suite.suite_options && suite.suite_options.length > 0) {
navigate(`${suite.id}`);
} else if ((suite && suite?.id) || testSuiteChosen) {
postTestSessions(suite?.id || testSuiteChosen, null, null)
.then((testSession: TestSession | null) => {
if (testSession && testSession.test_suite) {
navigate(`/${testSession.test_suite_id}/${testSession.id}`);
}
})
.catch((e: Error) => {
enqueueSnackbar(`Error while creating test session: ${e.message}`, { variant: 'error' });
});
setShowSuiteSelection(false);
setShowLandingPage(true);
} else if (suite && suite.id) {
createTestSession(suite.id, null);
} else {
enqueueSnackbar(`No test suite selected.`, { variant: 'error' });
}
};

// Start test session
const createTestSession = (id: string, options: SuiteOption[] | null = null): void => {
postTestSessions(id, null, options)
.then((testSession: TestSession | null) => {
if (testSession && testSession.test_suite) {
navigate(`/${testSession.test_suite_id}/${testSession.id}`);
navigate(0); // Refresh page to prevent React Router bug where rerendering fails
}
})
.catch((e: Error) => {
enqueueSnackbar(`Error while creating test session: ${e.message}`, { variant: 'error' });
});
};

if (!showLandingPage) return <></>;
return (
<Container
maxWidth={false}
Expand All @@ -66,79 +144,119 @@ const LandingPage: FC<LandingPageProps> = ({ testSuites }) => {
windowIsSmall
? {}
: {
minHeight: '400px',
maxHeight: '100vh',
flexDirection: 'column',
minHeight: '500px',
py: 10,
}
}
>
<Box
display="flex"
flexDirection="column"
justifyContent={windowIsSmall ? 'center' : 'flex-end'}
alignItems="center"
overflow="initial"
minHeight="300px"
pb={windowIsSmall ? 0 : 2}
px={2}
maxWidth={windowIsSmall ? '100%' : '50%'}
maxHeight={windowIsSmall ? 'none' : '100%'}
minHeight={windowIsSmall ? 'unset' : '100%'}
overflow="auto"
my={3}
className={classes.flexContainer}
>
<Box my={2} alignItems="center" maxWidth="800px">
<Box display="flex" alignItems="center" justifyContent="center">
<Box
maxWidth={descriptionWidth}
maxHeight={windowIsSmall ? 'none' : '100%'}
my={2}
className={classes.flexContainer}
>
<Box className={classes.flexContainer}>
<img
src={getStaticPath(infernoLogo as string)}
alt="Inferno Logo"
style={{ height: windowIsSmall ? '5em' : '8em' }}
/>
{!selectedTestSuite?.suite_summary && !selectedTestSuite?.description && (
<Typography
variant="h4"
component="h1"
align="center"
className={classes.title}
sx={{ fontSize: windowIsSmall ? '2rem' : 'auto' }}
>
FHIR Testing with Inferno
</Typography>
)}
</Box>
<Typography
variant="h4"
component="h1"
align="center"
sx={{
color: lightTheme.palette.common.grayDark,
fontSize: windowIsSmall ? '2rem' : 'auto',
fontWeight: 'bolder',
}}
>
FHIR Testing with Inferno
</Typography>
</Box>
<Box mb={2} alignItems="center" maxWidth="600px">
<Typography
variant="h5"
component="h2"
align="center"
sx={{
fontSize: windowIsSmall ? '1.2rem' : 'auto',
}}
{selectedTestSuite?.suite_summary || selectedTestSuite?.description ? (
<Box
maxWidth={descriptionWidth}
overflow="auto"
mb={2}
px={2}
className={classes.leftBorder}
>
Test your server's conformance to authentication, authorization, and FHIR content
standards.
</Typography>
</Box>
<Typography variant="h5" component="h1" className={classes.title}>
{selectedTestSuite.title}
</Typography>
<Typography variant="h6" component="h2" sx={{ wordBreak: 'break-word' }}>
<ReactMarkdown>
{selectedTestSuite?.suite_summary || selectedTestSuite?.description || ''}
</ReactMarkdown>
</Typography>
</Box>
) : (
<Box maxWidth={descriptionWidth} mb={2} px={2} className={classes.flexContainer}>
<Typography
variant="h5"
component="h2"
align="center"
sx={{ fontSize: windowIsSmall ? '1.2rem' : 'revert' }}
>
Test your server's conformance to authentication, authorization, and FHIR content
standards.
</Typography>
</Box>
)}
</Box>
<Box
display="flex"
flexDirection="column"
justifyContent="flex-start"
alignItems="center"
overflow="initial"
width="100%"
minHeight="200px"
py={4}
height={windowIsSmall ? 'unset' : '100%'}
width={windowIsSmall ? '100%' : 'unset'}
maxWidth={windowIsSmall ? '100%' : '50%'}
className={classes.flexContainer}
py={windowIsSmall ? 3 : 0}
sx={{ backgroundColor: lightTheme.palette.common.grayLightest }}
>
<SelectionPanel
title="Test Suites"
options={(testSuites || []).sort((testSuite1: TestSuite, testSuite2: TestSuite): number =>
testSuite1.title.localeCompare(testSuite2.title)
<Box display="flex" justifyContent="center" maxHeight="100%" mx={3}>
{showSuiteSelection ? (
// Suite selection
<SelectionPanel
title="Test Suites"
options={(testSuites || []).sort(
(testSuite1: TestSuite, testSuite2: TestSuite): number =>
testSuite1.title.localeCompare(testSuite2.title)
)}
selection={selectedTestSuiteId}
setSelection={setSuiteSelected}
submitAction={() =>
startTestingClick(
testSuites?.find((suite: TestSuite) => suite.id === selectedTestSuiteId)
)
}
submitText="Select Suite"
/>
) : (
// Options selection
<SelectionPanel
title="Options"
options={selectedTestSuite?.suite_options || []}
selection={defaultSuiteOptions}
setSelection={setOptionsSelected}
showBackButton={true}
backTooltipText="Back to Suites"
backClickHandler={() => {
setShowSuiteSelection(true);
}}
submitAction={() => createTestSession(selectedTestSuiteId, selectedSuiteOptions)}
submitText="Start Testing"
/>
)}
setSelection={setSelected}
submitAction={() =>
startTestingClick(testSuites?.find((suite: TestSuite) => suite.id === testSuiteChosen))
}
submitText="Select Suite"
/>
</Box>
</Box>
</Container>
);
Expand Down
Loading
Loading