Skip to content

Commit

Permalink
Merge branch 'SIMSBIOHUB-393' into dataset_security_feature
Browse files Browse the repository at this point in the history
  • Loading branch information
MacQSL committed Dec 8, 2023
2 parents b001aef + d7c9192 commit 216b747
Show file tree
Hide file tree
Showing 16 changed files with 582 additions and 21 deletions.
5 changes: 5 additions & 0 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"dompurify": "^2.4.0",
"express": "~4.17.1",
"formik": "~2.2.6",
"fuse.js": "^7.0.0",
"handlebars": "^4.7.7",
"jest-watch-typeahead": "^2.2.2",
"jszip": "^3.10.1",
Expand Down
7 changes: 7 additions & 0 deletions app/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import DatasetsRouter from 'features/datasets/DatasetsRouter';
import HomeRouter from 'features/home/HomeRouter';
import MapRouter from 'features/map/MapRouter';
import SearchRouter from 'features/search/SearchRouter';
import SubmissionsRouter from 'features/submissions/SubmissionsRouter';
import BaseLayout from 'layouts/BaseLayout';
import ContentLayout from 'layouts/ContentLayout';
import LoginPage from 'pages/authentication/LoginPage';
Expand Down Expand Up @@ -42,6 +43,12 @@ const AppRouter: React.FC<React.PropsWithChildren> = () => {
</BaseLayout>
</Route>

<Route path="/submissions">
<BaseLayout>
<SubmissionsRouter />
</BaseLayout>
</Route>

<Route path="/map">
<ContentLayout>
<MapRouter />
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/layout/header/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('Header', () => {
);

expect(getByText('Home')).toBeVisible();
expect(getByText('Find Datasets')).toBeVisible();
expect(getByText('Submissions')).toBeVisible();
expect(getByText('Map Search')).toBeVisible();
expect(getByText('Manage Users')).toBeVisible();
});
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/layout/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ const Header: React.FC<React.PropsWithChildren> = () => {
Dashboard
</Link>
</SystemRoleGuard>
<Link to="/search" id="menu_search">
Find Datasets
<Link to="/submissions" id="submissions">
Submissions
</Link>
<Link to="/map" id="menu_map">
Map Search
Expand Down
6 changes: 5 additions & 1 deletion app/src/features/datasets/DatasetsRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ const DatasetsRouter: React.FC<React.PropsWithChildren> = () => {
<Switch>
<Redirect exact from="/datasets/:id" to="/datasets/:id/details" />

<RouteWithTitle exact path="/datasets/:id/details" title={getTitle('Datasets')}>
{/* <RouteWithTitle exact path="/datasets" title={getTitle('Datasets')}>
<DatasetListPage />
</RouteWithTitle> */}

<RouteWithTitle exact path="/datasets/:id/details" title={getTitle('Dataset Details')}>
<DatasetPage />
</RouteWithTitle>

Expand Down
48 changes: 33 additions & 15 deletions app/src/features/search/SearchComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import InputAdornment from '@mui/material/InputAdornment';
import { makeStyles } from '@mui/styles';
import { useFormikContext } from 'formik';
import { IAdvancedSearch } from 'interfaces/useSearchApi.interface';
import { ChangeEvent } from 'react';

const useStyles = makeStyles((theme: Theme) => ({
export const useSearchInputStyles = makeStyles((theme: Theme) => ({
searchInputContainer: {
position: 'relative'
},
Expand Down Expand Up @@ -59,28 +60,45 @@ const useStyles = makeStyles((theme: Theme) => ({
}
}));

interface ISearchInputProps {
placeholderText: string;
handleChange: (e: ChangeEvent<any>) => void;
value: string;
}

export const SearchInput = (props: ISearchInputProps) => {
const classes = useSearchInputStyles();
return (
<Input
tabIndex={0}
className={classes.searchInput}
name="keywords"
fullWidth
startAdornment={
<InputAdornment position="start">
<Icon path={mdiMagnify} size={1} />
</InputAdornment>
}
disableUnderline={true}
placeholder={props.placeholderText}
onChange={props.handleChange}
value={props.value}
/>
);
};

const SearchComponent: React.FC<React.PropsWithChildren> = () => {
const classes = useStyles();
const classes = useSearchInputStyles();

const formikProps = useFormikContext<IAdvancedSearch>();
const { handleSubmit, handleChange, values } = formikProps;

return (
<form onSubmit={handleSubmit} autoComplete="off">
<Box className={classes.searchInputContainer}>
<Input
tabIndex={0}
className={classes.searchInput}
name="keywords"
fullWidth
startAdornment={
<InputAdornment position="start">
<Icon path={mdiMagnify} size={1} />
</InputAdornment>
}
disableUnderline={true}
placeholder="Enter a species name or keyword"
onChange={handleChange}
<SearchInput
placeholderText="Enter a species name or keyword"
handleChange={handleChange}
value={values.keywords}
/>
<Button
Expand Down
147 changes: 147 additions & 0 deletions app/src/features/submissions/SubmissionsListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Container from '@mui/material/Container';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Stack from '@mui/system/Stack';
import SecureDataAccessRequestDialog from 'features/datasets/security/SecureDataAccessRequestDialog';
import { SearchInput } from 'features/search/SearchComponent';
import { FuseResult } from 'fuse.js';
import { useApi } from 'hooks/useApi';
import useDataLoader from 'hooks/useDataLoader';
import useDownloadJSON from 'hooks/useDownloadJSON';
import useFuzzySearch from 'hooks/useFuzzySearch';
import { SECURITY_APPLIED_STATUS } from 'interfaces/useDatasetApi.interface';
import { ISubmission } from 'interfaces/useSubmissionsApi.interface';
import React, { useState } from 'react';
import DatasetSortMenu from './components/SubmissionsListSortMenu';

/**
* Renders reviewed Submissions as cards with download and request access actions
*
* @returns {*}
*/
const SubmissionsListPage = () => {
const biohubApi = useApi();
const download = useDownloadJSON();

const submissionsLoader = useDataLoader(() => biohubApi.submissions.listReviewedSubmissions());
submissionsLoader.load();

// this is what the page renders / mutates
const [openRequestAccess, setOpenRequestAccess] = useState(false);
const { fuzzyData, handleFuzzyData, handleSearch, searchValue, highlight } = useFuzzySearch<ISubmission>(
submissionsLoader.data,
{ keys: ['name', 'description'] }
);

const handleDownload = async (dataset: FuseResult<ISubmission>) => {
// make request here for JSON data of submission and children
const data = await biohubApi.submissions.getSubmissionDownloadPackage();
download(data, `${dataset.item.name.toLowerCase().replace(/ /g, '-')}-${dataset.item.submission_feature_id}`);
};

const handleRequestAccess = () => {
setOpenRequestAccess(true);
};

return (
<>
<SecureDataAccessRequestDialog
open={openRequestAccess}
onClose={() => setOpenRequestAccess(false)}
artifacts={[]}
initialArtifactSelection={[]}
/>
<Box>
<Paper
square
elevation={0}
sx={{
py: 4
}}>
<Container maxWidth="xl">
<Typography variant="h1" mb={2}>
Submissions
</Typography>
<SearchInput
placeholderText="Enter a submission title or keyword"
value={searchValue}
handleChange={handleSearch}
/>
</Container>
</Paper>
<Container maxWidth="xl">
<Box py={4} display="flex" alignItems="center" justifyContent="space-between">
<Typography fontWeight="bold">
{searchValue
? `${fuzzyData.length} records found for "${searchValue}"`
: `${fuzzyData.length} records found`}
</Typography>
<DatasetSortMenu
data={fuzzyData}
handleSortedFuzzyData={(data) => {
handleFuzzyData(data);
}}
/>
</Box>
<Stack spacing={2} mb={2}>
{fuzzyData?.map((dataset) => (
<Card elevation={0} key={dataset.item.submission_feature_id}>
<CardHeader
title={highlight(dataset.item.name, dataset?.matches?.find((match) => match.key === 'name')?.indices)}
subheader={
<Typography variant="body2" color="textSecondary">
{dataset.item.submission_date.toDateString()}
</Typography>
}
action={
<>
{(dataset.item.security === SECURITY_APPLIED_STATUS.SECURED ||
dataset.item.security === SECURITY_APPLIED_STATUS.PARTIALLY_SECURED) && (
<Button
variant={'outlined'}
sx={{ ml: 'auto', minWidth: 150 }}
disableElevation
onClick={() => {
handleRequestAccess();
}}>
Request Access
</Button>
)}
{(dataset.item.security === SECURITY_APPLIED_STATUS.UNSECURED ||
dataset.item.security === SECURITY_APPLIED_STATUS.PARTIALLY_SECURED) && (
<Button
variant="contained"
sx={{ ml: 1, minWidth: 150 }}
disableElevation
onClick={() => {
handleDownload(dataset);
}}>
Download
</Button>
)}
</>
}
/>
<CardContent sx={{ pt: 0, display: 'flex', alignItems: 'center' }}>
<Typography variant="body1" color="textSecondary">
{highlight(
dataset.item.description,
dataset?.matches?.find((match) => match.key === 'description')?.indices
)}
</Typography>
</CardContent>
</Card>
))}
</Stack>
</Container>
</Box>
</>
);
};

export default SubmissionsListPage;
28 changes: 28 additions & 0 deletions app/src/features/submissions/SubmissionsRouter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Redirect, Route, Switch } from 'react-router';
import RouteWithTitle from 'utils/RouteWithTitle';
import { getTitle } from 'utils/Utils';
import DatasetListPage from './SubmissionsListPage';

/**
* Router for all `/submissions/*` pages.
*
* @return {*}
*/
const SubmissionsRouter = () => {
return (
<Switch>
{/* <Redirect exact from="/datasets/:id" to="/submissions/:id/details" /> */}

<RouteWithTitle exact path="/submissions" title={getTitle('Submissions')}>
<DatasetListPage />
</RouteWithTitle>

{/* Catch any unknown routes, and re-direct to the not found page */}
<Route path="/submissions/*">
<Redirect to="/page-not-found" />
</Route>
</Switch>
);
};

export default SubmissionsRouter;
Loading

0 comments on commit 216b747

Please sign in to comment.