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

Feature/342 - Implement sorting, filtering and search on the dashboard #346

Merged
merged 6 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function ListItemLink(props) {

const Sidebar = (props) => {
const classes = useStyles();
const {experiments} = props;
const { experiments, searchText, setSearchText } = props;
const [open, setOpen] = React.useState(false);
const [dialogOpen, setDialogOpen] = React.useState(false);

Expand All @@ -191,6 +191,8 @@ const Sidebar = (props) => {
<img src={SEARCH} alt="search" />
</InputAdornment>,
}}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
</Box>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ArrowRightIcon from '@material-ui/icons/ArrowRight';
import WorkspaceService from "../../service/WorkspaceService";
import { getDateFromDateTime } from "../../utils";
import { DeleteExperimentDialog } from "../DeleteExperimentDialog";
import { ExplorationSpinalCordDialog } from "./ExplorationSpinalCordDialog";
Expand Down Expand Up @@ -276,6 +275,7 @@ const ExperimentCard = ({
experiment,
type,
handleDialogToggle,
tagsOptions,
handleShareDialogToggle,
handleShareMultipleDialogToggle,
refreshExperimentList
Expand All @@ -286,11 +286,9 @@ const ExperimentCard = ({
history.push(`/experiments/${experiment.id}`)
}

const api = WorkspaceService.getApi();
const [experimentMenuEl, setExperimentMenuEl] = React.useState(null);
const [openDeleteDialog, setOpenDeleteDialog] = React.useState(false);
const [explorationDialogOpen, setExplorationDialogOpen] = React.useState(false);
const [tagsOptions, setTagsOptions] = React.useState([]);

const handleExplorationDialogToggle = () => {
setExplorationDialogOpen((prevOpen) => !prevOpen);
Expand All @@ -309,13 +307,6 @@ const ExperimentCard = ({
setOpenDeleteDialog(!openDeleteDialog);
};

React.useEffect(() => {
const fetchTagOptions = async () => {
const res = await api.listTags()
setTagsOptions(res.data)
};
fetchTagOptions().catch(console.error);
}, []);

return (
<Grid item xs={12} md={3} key={`${experiment.name}experiment_${experiment.id}`}>
Expand Down
129 changes: 115 additions & 14 deletions applications/portal/frontend/src/components/home/ExperimentList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import FormLabel from '@material-ui/core/FormLabel';
import Checkbox from '@material-ui/core/Checkbox';
import { SalkTeamInfo } from "./SalkTeamInfo";
import { SALK_TEAM } from "../../utilities/constants";
import WorkspaceService from "../../service/WorkspaceService";

const useStyles = makeStyles(() => ({
subHeader: {
Expand Down Expand Up @@ -170,12 +171,10 @@ const ExperimentList = (props) => {
const classes = useStyles();
const { experiments, refreshExperimentList } = props;
const [anchorEl, setAnchorEl] = React.useState(null);
const [value, setValue] = React.useState('Alphabetical');
const [filterAnchorEL, setFilterAnchorEL] = React.useState(null);
const [infoDrawer, setInfoDrawer] = React.useState(false);
const handleChange = (event) => {
setValue(event.target.value);
};
const [tagsOptions, setTagsOptions] = React.useState([]);

const openSortingMenu = (event) => {
setAnchorEl(event.currentTarget);
};
Expand All @@ -196,12 +195,92 @@ const ExperimentList = (props) => {
setInfoDrawer((prevOpen) => !prevOpen )
}

const tags = ["Project A", "Tag X", "Label 1"];
const { heading, description, type, infoIcon, handleDialogToggle, handleShareMultipleDialogToggle, handleShareDialogToggle } = props;
const sortOptions = ["Alphabetical", "Date created", "Last viewed"];
const orderOptions = ["Oldest first", "Newest first"];

const sortOptions = {
0: "Alphabetical",
1: "Date created",
2: "Last modified"
}
const orderOptions = {
0: "Oldest first",
1: "Newest first"
}
const orderOptionsAlphabetical = {
0: "A-Z",
1: "Z-A"
}

// Default viewing option - Date created, Newest first
const [selectedSortIndex, setSelectedSortIndex] = React.useState(1);
const [selectedOrderIndex, setSelectedOrderIndex] = React.useState(1);

const [selectedTags, setSelectedTags] = React.useState([]);



const filterExperimentsByTags = (exp) => {
exp = exp.filter(exp => {
if (selectedTags.length === 0) {
return true;
} else {
return exp.tags.map(t => t.name).some(t => selectedTags.includes(t));
}
});
return exp;
}

const sortAndOrderExperiments = (exp) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nicer, readability wise if instead of comparing the current selection with numbers, we would compare them with ENUM value.

f.e.

else if (selectedSortIndex === Alphabetical) {

let reversal = selectedOrderIndex === 0 ? false : true;
if (selectedSortIndex === 0) {
exp.sort((a, b) => a.name.localeCompare(b.name));
} else if (selectedSortIndex === 1) {
exp.sort((a, b) => new Date(a.date_created) - new Date(b.date_created));
} else if (selectedSortIndex === 2) {
exp.sort((a, b) => new Date(a.last_modified) - new Date(b.last_modified));
}

if (reversal) {
exp.reverse();
}
return exp;
}

const handleSortAndOrderChange = (sortindex, orderindex) => {
setSelectedSortIndex(sortindex);
setSelectedOrderIndex(orderindex);
}

const handleTagSelection = (event, tag) => {
const checked = event.target.checked;
if (checked) {
setSelectedTags([...selectedTags, tag]);
} else {
setSelectedTags(selectedTags.filter(t => t !== tag));
}
}

const experimentItems = React.useMemo(() => {
const sortedExperiments = sortAndOrderExperiments(experiments)
const filteredExperiments = filterExperimentsByTags(sortedExperiments)
return filteredExperiments;

}, [experiments, selectedTags, selectedSortIndex, selectedOrderIndex]);


const hash = useLocation()?.hash;
const api = WorkspaceService.getApi();

React.useEffect(() => {
const fetchTagOptions = async () => {
const res = await api.listTags();
return res.data;
}
fetchTagOptions().then(data => {
setTagsOptions(data.map(tag => tag.name));
}).catch(console.error);
}, []);


return (
<>
Expand All @@ -213,7 +292,7 @@ const ExperimentList = (props) => {
</Button>
<Menu
id="filter-menu"
className={`${classes.filterMenu} scrollable`}
className={`${classes.filterMenu} scrollable scrollbar`}
D-GopalKrishna marked this conversation as resolved.
Show resolved Hide resolved
anchorEl={filterAnchorEL}
keepMounted
open={Boolean(filterAnchorEL)}
Expand All @@ -227,7 +306,7 @@ const ExperimentList = (props) => {
label={'All tags'}
D-GopalKrishna marked this conversation as resolved.
Show resolved Hide resolved
/>
{
tags.map((tag, i) => <FormControlLabel
tagsOptions.map((tag, i) => <FormControlLabel
D-GopalKrishna marked this conversation as resolved.
Show resolved Hide resolved
labelPlacement="start"
control={
<Checkbox checkedIcon={<img src={CHECK} alt="" />} />
Expand All @@ -239,6 +318,7 @@ const ExperimentList = (props) => {
</>
}
key={`filter_${i}`}
onChange={(e) => handleTagSelection(e, tag)}
/>)
}
</Menu>
Expand Down Expand Up @@ -272,28 +352,49 @@ const ExperimentList = (props) => {
>
<FormControl component="fieldset">
<FormLabel component="legend">Sort by</FormLabel>
<RadioGroup aria-label="sort-by" name="sort-by" value={value} onChange={handleChange}>
<RadioGroup aria-label="sort-by" name="sort-by" >
{
sortOptions.map((option) => <FormControlLabel key={option} value={option} control={<Radio checkedIcon={<img src={CHECK} alt="" />} />} label={option} />)
Object.keys(sortOptions).map((option, index) =>
<FormControlLabel key={option} value={+option} control={
<Radio
checkedIcon={
<img src={CHECK} alt="" />
}
checked={selectedSortIndex === +option}
onChange={() => handleSortAndOrderChange(+option, selectedOrderIndex)}
/>
} label={sortOptions[option]} />
)
}
</RadioGroup>
</FormControl>

<FormControl component="fieldset">
<FormLabel component="legend">Order</FormLabel>
<RadioGroup aria-label="order" name="order" value={value} onChange={handleChange}>
<RadioGroup aria-label="order" name="order" >
{
orderOptions.map((option) => <FormControlLabel key={option} value={option} control={<Radio checkedIcon={<img src={CHECK} alt="" />} />} label={option} />)
Object.keys(selectedSortIndex === 0 ? orderOptionsAlphabetical : orderOptions).map((option, index) =>
<FormControlLabel key={option} value={+option} control={
<Radio
checkedIcon={
<img src={CHECK} alt="" />
}
checked={selectedOrderIndex === +option}
onChange={() => handleSortAndOrderChange(selectedSortIndex, +option)}
/>
} label={selectedSortIndex === 0 ? orderOptionsAlphabetical[option] : orderOptions[option]} />
)
}
</RadioGroup>
</FormControl>
</Menu>
</Box>
<Box p={5}>
<Grid container item spacing={3}>
{experiments.map( exp => (
{experimentItems?.map(exp => (
<ExperimentCard
key={exp.id} experiment={exp} type={type} handleDialogToggle={handleDialogToggle}
tagsOptions={tagsOptions}
handleShareDialogToggle={handleShareDialogToggle}
handleShareMultipleDialogToggle={handleShareMultipleDialogToggle}
refreshExperimentList={refreshExperimentList}
Expand Down
16 changes: 14 additions & 2 deletions applications/portal/frontend/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default (props: any) => {
// selectedRef.current.scrollIntoView();
}
const [dialogOpen, setDialogOpen] = React.useState(false);
const [searchText, setSearchText] = React.useState('');

const handleDialogToggle = () => {
setDialogOpen((prevOpen) => !prevOpen);
Expand All @@ -78,17 +79,28 @@ export default (props: any) => {
setShareMultipleDialogOpen((prevOpen) => !prevOpen);
};

const searchedExperiments = React.useMemo(() => {
return experiments.filter((experiment: any) => {
return experiment.name.toLowerCase().includes(searchText.toLowerCase());
});
}, [experiments, searchText]);

useEffect(() => {
fetchExperiments().catch(console.error);
}, [latestExperimentId])

return (
<Box display="flex">
<Sidebar experiments={experiments} executeScroll={(r: string) => executeScroll(r)} />
<Sidebar
experiments={experiments}
executeScroll={(r: string) => executeScroll(r)}
searchText={searchText}
setSearchText={(text: string) => setSearchText(text)}
/>
<Box className={classes.layoutContainer}>
<div ref={myRef} id={EXPERIMENTS_HASH}>
<ExperimentList
experiments={experiments} heading={"My experiments"}
experiments={searchedExperiments} heading={"My experiments"}
description={`${experiments.length} experiments`} type={EXPERIMENTS_HASH}
infoIcon={false}
handleShareDialogToggle={handleShareDialogToggle} handleShareMultipleDialogToggle={handleShareMultipleDialogToggle}
Expand Down
Loading