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

273 Editing an experiment tag leads to blank page #317

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -25,7 +25,7 @@ const useStyles = makeStyles(() => ({

export const TagsAutocomplete = (props) => {
const classes = useStyles();
const { tags, onChange } = props;
const { tagsOptions, tags, onChange } = props;

return (
<Autocomplete
Expand All @@ -34,8 +34,8 @@ export const TagsAutocomplete = (props) => {
popupIcon={false}
fullWidth
id="tags-filled"
options={tags}
defaultValue={[]}
value={tags}
options={tagsOptions}
freeSolo
limitTags={3}
renderTags={(value, getTagProps) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,24 @@ import UNORDERED from "../../../assets/images/icons/unordered_list.svg";
import ORDERED from "../../../assets/images/icons/ordered_list.svg";
import {Editor} from "react-draft-wysiwyg";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import {EditorState} from 'draft-js';
import {EditorState, ContentState, convertFromHTML} from 'draft-js';

export const TextEditor = (props) => {
const [editorState, setEditorState] = React.useState(EditorState.createEmpty())
const {onChange, wrapperClassName} = props;
const {value, onChange, wrapperClassName} = props;


const onEditorStateChange = (updatedEditorState) => {
setEditorState(updatedEditorState)
onChange(updatedEditorState)
}

React.useEffect(() => {
if (value) {
setEditorState(EditorState.createWithContent(ContentState.createFromBlockArray(convertFromHTML(value))));
};
}, []);

return (
<Editor
wrapperClassName={wrapperClassName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ export const CreateUpdateExperimentDialog = ({

<Box display="flex" alignItems={"center"} className={classes.formGroup}>
<Typography component="label">Tags</Typography>
<TagsAutocomplete tags={tagsOptions.map(t => t.name)} onChange={handleTagsChange}/>
<TagsAutocomplete tagsOptions={tagsOptions.map(t => t.name)} onChange={handleTagsChange}/>
</Box>

<Box display="flex" alignItems={"center"} className={classes.formGroup}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ 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 { CircularProgress } from "@material-ui/core";
import { getDateFromDateTime } from "../../utils";
import { DeleteExperimentDialog } from "../DeleteExperimentDialog";
import { ExplorationSpinalCordDialog } from "./ExplorationSpinalCordDialog";


const commonStyle = {
Expand Down Expand Up @@ -275,7 +277,6 @@ const ExperimentCard = ({
experiment,
type,
handleDialogToggle,
handleExplorationDialogToggle,
handleShareDialogToggle,
handleShareMultipleDialogToggle,
refreshExperimentList
Expand All @@ -286,8 +287,15 @@ 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);
};

const handleCardActions = (event) => {
setExperimentMenuEl(event.currentTarget);
Expand All @@ -301,6 +309,15 @@ const ExperimentCard = ({
closeFilter();
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}`}>
<Card className={classes.card} elevation={0}>
Expand Down Expand Up @@ -399,6 +416,15 @@ const ExperimentCard = ({
</CardContent>
</CardActionArea>
</Card>
<ExplorationSpinalCordDialog
experimentId={experiment.id}
experiment={experiment}
tagsOptions={tagsOptions}
open={explorationDialogOpen}
handleClose={handleExplorationDialogToggle}
refreshExperimentList={refreshExperimentList}
setExperimentMenuEl={setExperimentMenuEl}
/>
<DeleteExperimentDialog
experimentId={experiment.id} open={openDeleteDialog}
handleClose={() => setOpenDeleteDialog(false)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const ExperimentList = (props) => {
}

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

Expand Down Expand Up @@ -295,7 +295,7 @@ const ExperimentList = (props) => {
{experiments.map( exp => (
<ExperimentCard
key={exp.id} experiment={exp} type={type} handleDialogToggle={handleDialogToggle}
handleExplorationDialogToggle={handleExplorationDialogToggle} handleShareDialogToggle={handleShareDialogToggle}
handleShareDialogToggle={handleShareDialogToggle}
handleShareMultipleDialogToggle={handleShareMultipleDialogToggle}
refreshExperimentList={refreshExperimentList}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,14 @@ import {
Typography,
TextField,
} from "@material-ui/core";
import * as Yup from 'yup';
import { headerButtonBorderColor } from "../../theme";
// @ts-ignore
import WorkspaceService from "../../service/WorkspaceService";
import { common } from "../header/ExperimentDialog/Common";
import Modal from "../common/BaseDialog";
import { TagsAutocomplete } from "../common/ExperimentDialogs/TagsAutocomplete";
import { TextEditor } from "../common/ExperimentDialogs/TextEditor";
import { ExperimentTags } from "../../apiclient/workspaces";

const tags: ExperimentTags[] = [
{ name: 'Project A', id: 1 },
{ name: 'Label B', id: 2},
{ name: 'Label XYZ', id: 3},
{ name: 'Project C', id: 4 },
{ name: 'Project d', id: 5 },
{ name: 'Label e', id: 6},
{ name: 'Label XeYZ', id: 7},
{ name: 'Project Ce', id: 8},
];

const useStyles = makeStyles(() => ({
formGroup: {
Expand All @@ -36,35 +28,157 @@ const useStyles = makeStyles(() => ({
marginBottom: '0.75rem'
},
},
editorWrapper: {
'&:hover': {
borderColor: '#fff'
},
'&:focus-within': {
borderColor: '#7b61ff'
},
},
}));

const NAME_KEY = "name";
const DESCRIPTION_KEY = "description";

export const ExplorationSpinalCordDialog = (props: any) => {
const classes = useStyles();
const { open, handleClose } = props;
const commonClasses = common();
const api = WorkspaceService.getApi();
const { experimentId, experiment, tagsOptions, open, handleClose, refreshExperimentList, setExperimentMenuEl } = props;

const [name, setName] = React.useState<string>(experiment.name);
const [description, setDescription] = React.useState<string>(experiment.description);
const [validationErrors, setValidationErrors] = React.useState(new Set([]));
const [initialTags, setInitialTags] = React.useState(experiment.tags.map((t: any) => t.name));
afonsobspinto marked this conversation as resolved.
Show resolved Hide resolved
const [tags, setTags] = React.useState(experiment.tags.map((t: any) => t.name));

const onTagsChange = (e: any, value: string[]) => {
setTags(value);
};

const validationSchema = experimentId
? Yup.object().shape({
[NAME_KEY]: Yup.string().required(),
[DESCRIPTION_KEY]: Yup.string().required(),
})
: Yup.object().shape({});

const deleteTag = async (tag: string) => {
try {
await api.deleteTagExperiment(experiment.id.toString(), tag);
} catch (error) {
console.error(error);
}
};

const addTags = async (tags: string[]) => {
try {
await api.addTagsExperiment(experiment.id.toString(), tags);
} catch (error) {
console.error(error);
}
}

const handleFormChange = (newValue: any, setState: any, errorKey: string) => {
validationErrors.delete(errorKey);
setValidationErrors(validationErrors);
setState(newValue);
};

const getCurrentFormDataObject = () => {
return {
name,
description,
tags,
}
}

const getValidationErrors = async () => {
const errorsSet = new Set()
try {
await validationSchema.validate(getCurrentFormDataObject(), { strict: true, abortEarly: false })
} catch (exception) {
for (const e of exception.inner) {
errorsSet.add(e.path)
}
}

return errorsSet
}

const handleAction = async () => {

const deletedTags = initialTags.filter((tag: any) => !tags.includes(tag));
const addedTags = tags.filter((tag: any) => !initialTags.includes(tag));

//API calls tags
if (deletedTags.length > 0) {
deletedTags.forEach((tag: any) => deleteTag(tag));
}
if (addedTags.length > 0) {
addTags(addedTags);
}

const errorsSet = await getValidationErrors()
if (errorsSet.size > 0) {
setValidationErrors(errorsSet)
return
}

if(experimentId) {
try {
const res = await api.updateExperiment(experimentId.toString(), name, description);
if (res.status === 200) {
refreshExperimentList()
}
} catch (err) {
console.error(err)
}
}
setInitialTags(tags);
handleClose();
setExperimentMenuEl(null)
};

return (
<Modal
dialogActions={true}
actionText="Create"
actionText="Edit"
disableGutter={true}
open={open}
handleClose={handleClose}
title="Exploration of the spinal cord"
handleAction={handleAction}
title={experiment.name}
>
<Box p={2} pb={5}>
<Box className={classes.formGroup}>
<Typography component="label">Name</Typography>
<TextField fullWidth={true} placeholder="Name" variant="outlined" />
<TextField
fullWidth={true}
placeholder="Name"
variant="outlined"
value={name}
onChange={(e) => handleFormChange(e.target.value, setName, NAME_KEY)}
error={validationErrors.has(NAME_KEY)}
required={true}
/>
</Box>

<Box className={classes.formGroup}>
<Typography component="label">Description</Typography>
<TextEditor />
<TextEditor
value={description}
wrapperClassName={`${validationErrors.has(DESCRIPTION_KEY) ? commonClasses.errorBorder : classes.editorWrapper}`}
onChange={(editorState: any) =>
handleFormChange(editorState.getCurrentContent().getPlainText(), setDescription, DESCRIPTION_KEY)}
required={true}
/>
</Box>

<Box className={classes.formGroup}>
<Typography component="label">Tags</Typography>
<TagsAutocomplete tags={tags} />
<TagsAutocomplete tags={tags} tagsOptions={tagsOptions.map((t: any) => t.name)} onChange={onTagsChange} />
</Box>
</Box>
</Modal>
Expand Down
9 changes: 1 addition & 8 deletions applications/portal/frontend/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@ export default (props: any) => {
setDialogOpen((prevOpen) => !prevOpen);
};

const [explorationDialogOpen, setExplorationDialogOpen] = React.useState(false);

const handleExplorationDialogToggle = () => {
setExplorationDialogOpen((prevOpen) => !prevOpen);
};

const [shareDialogOpen, setShareDialogOpen] = React.useState(false);

const handleShareDialogToggle = () => {
Expand All @@ -97,7 +91,7 @@ export default (props: any) => {
<ExperimentList
experiments={experiments} heading={"My experiments"}
description={`${experiments.length} experiments`} type={EXPERIMENTS_HASH}
handleExplorationDialogToggle={handleExplorationDialogToggle} infoIcon={false}
infoIcon={false}
handleShareDialogToggle={handleShareDialogToggle} handleShareMultipleDialogToggle={handleShareMultipleDialogToggle}
refreshExperimentList={fetchExperiments}
/>
Expand All @@ -118,7 +112,6 @@ export default (props: any) => {
{/*</Box>*/}
</Box>
<CloneExperimentDialog open={dialogOpen} handleClose={handleDialogToggle} user={props?.user} />
<ExplorationSpinalCordDialog open={explorationDialogOpen} handleClose={handleExplorationDialogToggle} />
<ShareExperimentDialog open={shareDialogOpen} handleClose={handleShareDialogToggle} />
<ShareMultipleExperimentDialog open={shareMultipleDialogOpen} handleClose={handleShareMultipleDialogToggle} />
</Box>
Expand Down