From 3df14adfe2ebfae6389db1d5e7e817e05c9e2d4e Mon Sep 17 00:00:00 2001 From: HangLe Date: Fri, 27 Aug 2021 10:16:22 +0300 Subject: [PATCH 01/12] Remove WizardFrontPageStep from Create Submission steps --- src/components/Nav.js | 2 +- .../WizardComponents/WizardFooter.js | 2 +- .../WizardSteps/WizardFrontpageStep.js | 83 ------------------- src/views/NewDraftWizard.js | 7 +- 4 files changed, 4 insertions(+), 90 deletions(-) delete mode 100644 src/components/NewDraftWizard/WizardSteps/WizardFrontpageStep.js diff --git a/src/components/Nav.js b/src/components/Nav.js index 99b4a19a..3dc3d03d 100644 --- a/src/components/Nav.js +++ b/src/components/Nav.js @@ -77,7 +77,7 @@ const Menu = () => { Submissions - + - - - -
- - Or do you want to submit object? - - - - -
-
- - ) -} - -export default WizardFrontpageStep diff --git a/src/views/NewDraftWizard.js b/src/views/NewDraftWizard.js index 5465b6ca..916d0de4 100644 --- a/src/views/NewDraftWizard.js +++ b/src/views/NewDraftWizard.js @@ -11,7 +11,6 @@ import WizardFooter from "components/NewDraftWizard/WizardComponents/WizardFoote import WizardStatusMessageHandler from "components/NewDraftWizard/WizardForms/WizardStatusMessageHandler" import WizardAddObjectStep from "components/NewDraftWizard/WizardSteps/WizardAddObjectStep" import WizardCreateFolderStep from "components/NewDraftWizard/WizardSteps/WizardCreateFolderStep" -import WizardFrontpageStep from "components/NewDraftWizard/WizardSteps/WizardFrontpageStep" import WizardShowSummaryStep from "components/NewDraftWizard/WizardSteps/WizardShowSummaryStep" import type { CreateFolderFormRef } from "types" import { useQuery } from "utils" @@ -44,8 +43,6 @@ const useStyles = makeStyles(theme => ({ */ const getStepContent = (wizardStep: number, createFolderFormRef: CreateFolderFormRef) => { switch (wizardStep) { - case -1: - return case 0: return case 1: @@ -80,8 +77,8 @@ const NewDraftWizard = (): React$Element => { const folder = useSelector(state => state.submissionFolder) if (!folder && (wizardStep === 1 || wizardStep === 2)) { - wizardStep = -1 - history.push({ pathname: "/newdraft" }) + wizardStep = 0 + history.push({ pathname: "/newdraft", search: "step=0" }) } return ( From 7da74b7587664b59b8c060a459033b28b107b3e9 Mon Sep 17 00:00:00 2001 From: HangLe Date: Fri, 27 Aug 2021 13:27:54 +0300 Subject: [PATCH 02/12] Update e2e test after removing WizardFrontPageStep --- cypress/integration/app.spec.js | 3 --- cypress/integration/draft.spec.js | 2 -- cypress/integration/draftTemplates.spec.js | 2 -- cypress/integration/editForm.spec.js | 2 -- cypress/integration/emptyForm.spec.js | 3 --- cypress/integration/home.spec.js | 6 ------ 6 files changed, 18 deletions(-) diff --git a/cypress/integration/app.spec.js b/cypress/integration/app.spec.js index 272ea6cd..6a01381a 100644 --- a/cypress/integration/app.spec.js +++ b/cypress/integration/app.spec.js @@ -8,9 +8,6 @@ describe("Basic e2e", function () { cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]").contains("New folder").click() - // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") diff --git a/cypress/integration/draft.spec.js b/cypress/integration/draft.spec.js index d6b9392e..395e4ca4 100644 --- a/cypress/integration/draft.spec.js +++ b/cypress/integration/draft.spec.js @@ -3,8 +3,6 @@ describe("Draft operations", function () { cy.login() cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]", { timeout: 10000 }).contains("New folder").click() // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") diff --git a/cypress/integration/draftTemplates.spec.js b/cypress/integration/draftTemplates.spec.js index fde3c737..7eab91ff 100644 --- a/cypress/integration/draftTemplates.spec.js +++ b/cypress/integration/draftTemplates.spec.js @@ -3,8 +3,6 @@ describe("draft selections and templates", function () { cy.login() cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]", { timeout: 10000 }).contains("New folder").click() // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") diff --git a/cypress/integration/editForm.spec.js b/cypress/integration/editForm.spec.js index 052bd41b..260def83 100644 --- a/cypress/integration/editForm.spec.js +++ b/cypress/integration/editForm.spec.js @@ -3,8 +3,6 @@ describe("Populate form and render form elements by object data", function () { cy.login() cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]", { timeout: 10000 }).contains("New folder").click() // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") diff --git a/cypress/integration/emptyForm.spec.js b/cypress/integration/emptyForm.spec.js index f58fb4a0..75f813fc 100644 --- a/cypress/integration/emptyForm.spec.js +++ b/cypress/integration/emptyForm.spec.js @@ -4,9 +4,6 @@ describe("empty form should not be alerted or saved", function () { cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]").contains("New folder").click() - // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") diff --git a/cypress/integration/home.spec.js b/cypress/integration/home.spec.js index d0619e61..e9412757 100644 --- a/cypress/integration/home.spec.js +++ b/cypress/integration/home.spec.js @@ -13,9 +13,6 @@ describe("Home e2e", function () { // Create a new Unpublished folder cy.get("button").contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]").contains("New folder").click() - // Add folder name & description, navigate to editing folder cy.get("input[name='name']").type("Test unpublished folder") cy.get("textarea[name='description']").type("Test description") @@ -118,9 +115,6 @@ describe("Home e2e", function () { // Create a new Published folder cy.get("button").contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]").contains("New folder").click() - // Add folder name & description, navigate to editing folder cy.get("input[name='name']").type("Test published folder") cy.get("textarea[name='description']").type("Test description") From 2d952773971ba6b2ce08d735504d3277b7703ad7 Mon Sep 17 00:00:00 2001 From: HangLe Date: Wed, 1 Sep 2021 17:00:00 +0300 Subject: [PATCH 03/12] Add possibility to select draft templates to reuse --- .../integration/linkingAccessionIds.spec.js | 3 - .../integration/objectLinksAttributes.spec.js | 2 - cypress/integration/objectTitles.spec.js | 3 +- src/components/Home/UserDraftTemplates.js | 209 +++++++++++------- .../WizardSteps/WizardCreateFolderStep.js | 48 ++++ src/features/reuseDraftsSlice.js | 16 ++ src/rootReducer.js | 2 + 7 files changed, 190 insertions(+), 93 deletions(-) create mode 100644 src/features/reuseDraftsSlice.js diff --git a/cypress/integration/linkingAccessionIds.spec.js b/cypress/integration/linkingAccessionIds.spec.js index 72772144..598ccb03 100644 --- a/cypress/integration/linkingAccessionIds.spec.js +++ b/cypress/integration/linkingAccessionIds.spec.js @@ -7,9 +7,6 @@ describe("Linking Accession Ids", function () { cy.wait(1000) cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]").contains("New folder").click() - // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") diff --git a/cypress/integration/objectLinksAttributes.spec.js b/cypress/integration/objectLinksAttributes.spec.js index 8ce4a7bc..422e82a5 100644 --- a/cypress/integration/objectLinksAttributes.spec.js +++ b/cypress/integration/objectLinksAttributes.spec.js @@ -4,8 +4,6 @@ describe("render objects' links and attributes ", function () { cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]").contains("New folder").click() // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") diff --git a/cypress/integration/objectTitles.spec.js b/cypress/integration/objectTitles.spec.js index 9b3dbe79..959cd92f 100644 --- a/cypress/integration/objectTitles.spec.js +++ b/cypress/integration/objectTitles.spec.js @@ -2,8 +2,7 @@ describe("draft and submitted objects' titles", function () { beforeEach(() => { cy.login() cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - // Navigate to folder creation - cy.get("button[type=button]").contains("New folder").click() + // Add folder name & description, navigate to submissions cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") diff --git a/src/components/Home/UserDraftTemplates.js b/src/components/Home/UserDraftTemplates.js index aa54ef18..5cd72b9d 100644 --- a/src/components/Home/UserDraftTemplates.js +++ b/src/components/Home/UserDraftTemplates.js @@ -4,23 +4,25 @@ import React, { useState } from "react" import Card from "@material-ui/core/Card" import CardContent from "@material-ui/core/CardContent" import CardHeader from "@material-ui/core/CardHeader" +import Checkbox from "@material-ui/core/Checkbox" import Collapse from "@material-ui/core/Collapse" -import IconButton from "@material-ui/core/IconButton" +import FormControl from "@material-ui/core/FormControl" +import FormControlLabel from "@material-ui/core/FormControlLabel" +import FormLabel from "@material-ui/core/FormLabel" import ListItemText from "@material-ui/core/ListItemText" import { makeStyles } from "@material-ui/core/styles" -import Table from "@material-ui/core/Table" -import TableBody from "@material-ui/core/TableBody" -import TableCell from "@material-ui/core/TableCell" -import TableRow from "@material-ui/core/TableRow" import Typography from "@material-ui/core/Typography" import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown" import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp" -import { useSelector } from "react-redux" +import { useForm, FormProvider, useFormContext, Controller } from "react-hook-form" +import { useSelector, useDispatch } from "react-redux" +import { setReuseDrafts } from "features/reuseDraftsSlice" import { formatDisplayObjectType, getDraftObjects, getItemPrimaryText, Pagination } from "utils" const useStyles = makeStyles(theme => ({ card: { + width: "100%", height: "100%", display: "flex", flexDirection: "column", @@ -31,33 +33,25 @@ const useStyles = makeStyles(theme => ({ fontSize: "0.5em", padding: 0, marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), }, - table: { padding: 0, margin: theme.spacing(1, 0) }, - schemaTitle: { - color: theme.palette.grey[900], - padding: theme.spacing(1), - textTransform: "capitalize", - display: "flex", - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - border: `0.1rem solid ${theme.palette.secondary.main}`, - borderRadius: "0.125rem", - margin: theme.spacing(1, 0), + form: { display: "flex", flexDirection: "column" }, + formControl: { + margin: theme.spacing(1), + borderBottom: `0.1rem solid ${theme.palette.secondary.main}`, boxShadow: "0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)", - "&:hover": { - cursor: "pointer", - }, - }, - listItems: { - border: "none", }, + formLabel: { display: "flex", justifyContent: "space-between", padding: theme.spacing(2) }, collapse: { - border: `0.1rem solid ${theme.palette.secondary.main}`, + borderTop: `0.125rem solid ${theme.palette.secondary.main}`, padding: theme.spacing(1), borderRadius: "0.125rem", + display: "flex", + flexDirection: "column", }, - listItemText: { + formControlLabel: { + display: "flex", + flex: "1 0 auto", padding: 0, margin: theme.spacing(1, 0), borderBottom: `solid 0.1rem ${theme.palette.secondary.main}`, @@ -71,69 +65,111 @@ const UserDraftTemplates = (): React$Element => { const classes = useStyles() const user = useSelector(state => state.user) const objectsArray = useSelector(state => state.objectsArray) + const reuseDrafts = useSelector(state => state.reuseDrafts) + + const dispatch = useDispatch() const draftObjects = user.drafts ? getDraftObjects(user.drafts, objectsArray) : [] + const methods = useForm() + + const ConnectForm = ({ children }) => { + const methods = useFormContext() + return children({ ...methods }) + } + // Render when there is user's draft template(s) - const DraftList = () => ( - - - {draftObjects.map(draft => { - const schema = Object.keys(draft)[0] - const [open, setOpen] = useState(false) - - // Control Pagination - const [page, setPage] = useState(0) - const [itemsPerPage, setItemsPerPage] = useState(10) - - const handleChangePage = (e: any, page: number) => { - setPage(page) - } - - const handleItemsPerPageChange = (e: any) => { - setItemsPerPage(e.target.value) - setPage(0) - } - - return ( - - setOpen(!open)}> - - - {formatDisplayObjectType(schema)} - - - {open ? : } - - - - - - - {draft[schema].slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage).map(item => ( - { + const [checkedItems, setCheckedItems] = useState(reuseDrafts) + + const handleChange = () => { + const checkedItems = Object.values(methods.getValues()).filter(item => item) + setCheckedItems(checkedItems) + dispatch(setReuseDrafts(checkedItems)) + } + + return ( + +
+ + {({ register }) => { + return draftObjects.map(draft => { + const schema = Object.keys(draft)[0] + const [open, setOpen] = useState(true) + // Control Pagination + const [page, setPage] = useState(0) + const [itemsPerPage, setItemsPerPage] = useState(10) + + const handleChangePage = (e: any, page: number) => { + setPage(page) + } + + const handleItemsPerPageChange = (e: any) => { + setItemsPerPage(e.target.value) + setPage(0) + } + + return ( + + setOpen(!open)}> + + {formatDisplayObjectType(schema)} + + {open ? : } + + + + { + return draft[schema] + .slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage) + .map(item => { + const { ref, ...rest } = register(item.accessionId) + return ( + element === item.accessionId) !== undefined} + color="primary" + name={item.accessionId} + value={item.accessionId} + inputRef={ref} + {...rest} + /> + } + label={ + + } + /> + ) + }) + }} /> - ))} - - -
-
-
- ) - })} -
-
- ) + + + + + ) + }) + }} + + + + ) + } // Renders when user has no draft templates const EmptyList = () => ( @@ -147,9 +183,10 @@ const UserDraftTemplates = (): React$Element => { return ( {draftObjects?.length > 0 ? : } diff --git a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js index 039f7602..ef22fc64 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js @@ -1,8 +1,13 @@ //@flow import React, { useState } from "react" +import Accordion from "@material-ui/core/Accordion" +import AccordionDetails from "@material-ui/core/AccordionDetails" +import AccordionSummary from "@material-ui/core/AccordionSummary" import { makeStyles } from "@material-ui/core/styles" import MuiTextField from "@material-ui/core/TextField" +import Typography from "@material-ui/core/Typography" +import ExpandMoreIcon from "@material-ui/icons/ExpandMore" import { useForm, Controller } from "react-hook-form" import { useDispatch, useSelector } from "react-redux" import { useHistory } from "react-router-dom" @@ -11,6 +16,7 @@ import WizardHeader from "../WizardComponents/WizardHeader" import WizardStepper from "../WizardComponents/WizardStepper" import WizardStatusMessageHandler from "../WizardForms/WizardStatusMessageHandler" +import UserDraftTemplates from "components/Home/UserDraftTemplates" import { WizardStatus } from "constants/wizardStatus" import { createNewDraftFolder, updateNewDraftFolder } from "features/wizardSubmissionFolderSlice" import type { FolderDataFromForm, CreateFolderFormRef } from "types" @@ -22,6 +28,33 @@ const useStyles = makeStyles(theme => ({ }, padding: theme.spacing(2), }, + accordion: { + "&.MuiAccordion-root": { + "&:before": { + display: "none", + border: "none", + }, + marginTop: "1rem", + }, + "&.MuiPaper-elevation1": { + boxShadow: "none", + }, + width: "100%", + }, + accordionSummary: { + width: "20%", + margin: "0 auto", + borderRadius: "0.375rem", + border: "1px solid #bdbdbd", + marginBottom: "1rem", + }, + accordionDetails: { + width: "70%", + margin: "0 auto", + borderRadius: "0.375rem", + // border: "1px solid #bdbdbd", + boxShadow: "0 0.1875rem 0.375rem rgba(0, 0, 0, 0.16)", + }, })) /** @@ -97,6 +130,21 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create rules={{ required: true, validate: { description: value => value.length > 0 } }} /> + + } + aria-controls="user-drafts-content" + id="user-drafts-header" + > + + Saved Draft Templates + + + + + + {connError && ( )} diff --git a/src/features/reuseDraftsSlice.js b/src/features/reuseDraftsSlice.js new file mode 100644 index 00000000..007d1244 --- /dev/null +++ b/src/features/reuseDraftsSlice.js @@ -0,0 +1,16 @@ +//@flow +import { createSlice } from "@reduxjs/toolkit" + +const initialState: [] = [] + +const reuseDrafts: any = createSlice({ + name: "userDraft", + initialState, + reducers: { + setReuseDrafts: (state, action) => action.payload, + resetReuseDrafts: () => initialState, + }, +}) + +export const { setReuseDrafts, resetReuseDrafts } = reuseDrafts.actions +export default reuseDrafts.reducer diff --git a/src/rootReducer.js b/src/rootReducer.js index e09fcab3..8d9226fc 100644 --- a/src/rootReducer.js +++ b/src/rootReducer.js @@ -8,6 +8,7 @@ import focusReducer from "features/focusSlice" import loadingReducer from "features/loadingSlice" import objectsArrayReducer from "features/objectsArraySlice" import publishedFoldersReducer from "features/publishedFoldersSlice" +import reuseDraftsReducer from "features/reuseDraftsSlice" import selectedFolderReducer from "features/selectedFolderSlice" import totalFoldersReducer from "features/totalFoldersSlice" import unpublishedFoldersReducer from "features/unpublishedFoldersSlice" @@ -36,6 +37,7 @@ const rootReducer: any = combineReducers({ objectsArray: objectsArrayReducer, totalFolders: totalFoldersReducer, clearForm: clearFormReducer, + reuseDrafts: reuseDraftsReducer, }) export default rootReducer From 333f593c4acf78812b91b9f67b5775de47d4ddde Mon Sep 17 00:00:00 2001 From: HangLe Date: Fri, 3 Sep 2021 08:27:15 +0300 Subject: [PATCH 04/12] Update e2e test for the feature --- cypress/integration/draftTemplates.spec.js | 84 +++++++++---------- cypress/integration/home.spec.js | 1 + cypress/integration/login.spec.js | 11 +-- cypress/integration/pagination.spec.js | 18 ++-- src/components/Home/UserDraftTemplates.js | 2 +- .../WizardSteps/WizardCreateFolderStep.js | 8 +- src/features/wizardSubmissionFolderSlice.js | 4 +- 7 files changed, 65 insertions(+), 63 deletions(-) diff --git a/cypress/integration/draftTemplates.spec.js b/cypress/integration/draftTemplates.spec.js index 7eab91ff..594e99e1 100644 --- a/cypress/integration/draftTemplates.spec.js +++ b/cypress/integration/draftTemplates.spec.js @@ -8,10 +8,9 @@ describe("draft selections and templates", function () { cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() - }) - it("should show the list of drafts before folder is published, and show saved drafts in Home page", () => { // Fill a Study form + cy.wait(500) cy.get("div[role=button]", { timeout: 10000 }).contains("Study").click() cy.get("div[role=button]").contains("Fill Form").click() cy.get("input[name='descriptor.studyTitle']").type("Study test title") @@ -61,6 +60,7 @@ describe("draft selections and templates", function () { "Objects in this folder will be published. Please choose the drafts you would like to save, unsaved drafts will be removed from this folder." ) + it("should show the list of drafts before folder is published, and show saved drafts in Home page", () => { // Select drafts inside the dialog cy.get("form").within(() => { cy.get("input[type='checkbox']").first().check() @@ -90,54 +90,50 @@ describe("draft selections and templates", function () { }) it("should open the correct draft when clicking View button", () => { - // Fill a Study form - cy.get("div[role=button]").contains("Study").click() - cy.get("div[role=button]").contains("Fill Form").click() - cy.get("input[name='descriptor.studyTitle']").type("Study test title") - cy.get("input[name='descriptor.studyTitle']").should("have.value", "Study test title") - cy.get("select[name='descriptor.studyType']").select("Metagenomics") + // Select drafts inside the dialog + cy.get("form").within(() => { + cy.get("button[aria-label='View draft']").last().click() + }) + cy.get("h1", { timeout: 10000 }).contains("Sample").should("be.visible") + cy.get("[data-testid='title']").should("have.value", "Sample draft title ") + }) - // Submit Study form - cy.get("button[type=submit]").contains("Submit").click() - cy.get(".MuiListItem-container", { timeout: 10000 }).should("have.length", 1) + it("should be able to select draft templates and reuse them when creating a new folder", () => { + // Select drafts inside the dialog + cy.get("form").within(() => { + cy.get("input[type='checkbox']").first().check() + cy.get("input[type='checkbox']").last().check() - // Create another Study draft form - cy.get("button").contains("New form").click() - cy.get("input[name='descriptor.studyTitle']").type("Study draft title") + // Publish folder + cy.get('button[aria-label="Publish folder contents and move to frontpage"]').contains("Publish").click() + }) + // Navigate back to home page + cy.get("div", { timeout: 10000 }).contains("Logged in as:") + // Check if the drafts have been saved as user's templates in Home page + cy.contains("Your Draft Templates", { timeout: 10000 }).should("be.visible") - // Save a draft - cy.get("button[type=button]").contains("Save as Draft").click() - cy.get("div[role=alert]", { timeout: 10000 }).contains("Draft saved with") + // Select some drafts to reuse + cy.get("[data-testid='form-draft-study']").within(() => { + cy.get("input").first().check() + }) - // Create and Save another draft - Sample draft - cy.get("div[role=button]", { timeout: 10000 }).contains("Sample").click({ force: true }) - cy.wait(500) - cy.get("div[aria-expanded='true']") - .siblings() - .within(() => - cy - .get("div[role=button]") - .contains("Fill Form", { timeout: 10000 }) - .should("be.visible") - .then($btn => $btn.click()) - ) + cy.get("[data-testid='form-draft-sample']").within(() => { + cy.get("input").first().click() + }) - cy.get("input[name='title']").type("Sample draft title") - cy.get("input[name='sampleName.taxonId']").type(123) - cy.get("button[type=button]").contains("Save as Draft").click() - cy.get("div[role=alert]", { timeout: 10000 }).contains("Draft saved with") + // Check that selected drafts exist + cy.get("button", { timeout: 10000 }).contains("Create Submission").click() + cy.get("[data-testid='toggle-user-drafts']", { timeout: 10000 }).click() + cy.get("[data-testid='form-draft-study']").within(() => { + cy.get("input").first().should("be.checked") + }) + cy.get("[data-testid='form-draft-sample']").within(() => { + cy.get("input").first().should("be.checked") + }) - // Navigate to summary + // Create a new submission with selected drafts + cy.get("input[name='name']").type("Test name") + cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() - - // Navigate to publish button at the bottom - cy.get("button[type=button]").contains("Publish").click() - - // Select drafts inside the dialog - cy.get("form").within(() => { - cy.get("button[aria-label='View draft']").last().click() - }) - cy.get("h1", { timeout: 10000 }).contains("Sample").should("be.visible") - cy.get("input[name='title']").should("have.value", "Sample draft title") }) }) diff --git a/cypress/integration/home.spec.js b/cypress/integration/home.spec.js index e9412757..2d6e90cb 100644 --- a/cypress/integration/home.spec.js +++ b/cypress/integration/home.spec.js @@ -19,6 +19,7 @@ describe("Home e2e", function () { cy.get("button[type=button]").contains("Next").click() // Fill a Study form + cy.wait(500) cy.get("div[role=button]", { timeout: 10000 }).contains("Study").click() cy.get("div[role=button]").contains("Fill Form").click() cy.get("input[name='descriptor.studyTitle']").type("Test title") diff --git a/cypress/integration/login.spec.js b/cypress/integration/login.spec.js index 1bf4c40c..7c9e2d1e 100644 --- a/cypress/integration/login.spec.js +++ b/cypress/integration/login.spec.js @@ -3,23 +3,18 @@ describe("Login e2e", function () { cy.setMockUser("frontend.test", "FrontendTest", "E2EUser") }) - const baseUrl = "http://localhost:" + Cypress.env("port") + "/" - it("should contain session cookie", () => { - cy.visit(baseUrl) - cy.get('[alt="CSC Login"]').click() + cy.login() cy.getCookie("MTD_SESSION").should("exist") }) it("should contain the test user name", () => { - cy.visit(baseUrl) - cy.get('[alt="CSC Login"]').click() + cy.login() cy.get("div").contains("Logged in as: E2EUser FrontendTest") }) it("should go to main page on logout", () => { - cy.visit(baseUrl) - cy.get('[alt="CSC Login"]').click() + cy.login() cy.contains("Log out").click() cy.location().should(loc => { expect(loc.pathname).to.eq("/") diff --git a/cypress/integration/pagination.spec.js b/cypress/integration/pagination.spec.js index 5021b892..f7443cb8 100644 --- a/cypress/integration/pagination.spec.js +++ b/cypress/integration/pagination.spec.js @@ -405,20 +405,24 @@ describe("unpublished folders, published folders, and user's draft templates pag cy.login() // Check Draft-study pagination - cy.get("h6").contains("Draft-study").click() + cy.get("div[data-schema='draft-study'] > span").should("have.length", "10") cy.get("p").contains("1-10 of 15").should("be.visible") cy.get("[data-testid='page info']").contains("1 of 2 pages").should("be.visible") - cy.get("button[aria-label='next page']").click() - cy.get("p").contains("11-15 of 15", { timeout: 10000 }).should("be.visible") - cy.get("[data-testid='page info']").contains("2 of 2 pages").should("be.visible") - cy.get("div[aria-haspopup='listbox']", { timeout: 10000 }).contains(10).click() - cy.get("li[data-value='15']").click() + cy.get("div[data-testid='form-draft-study']").within(() => { + cy.get("button[aria-label='next page']").click() + cy.get("p").contains("11-15 of 15", { timeout: 10000 }).should("be.visible") + cy.get("[data-testid='page info']").contains("2 of 2 pages").should("be.visible") + cy.get("div[aria-haspopup='listbox']", { timeout: 10000 }).contains(10).click() + }) + cy.get("li[data-value='15']") + .should("be.visible") + .then($el => $el.click()) cy.get("div[data-schema='draft-study'] > span").should("have.length", "15") // Check Draft-sample pagination - cy.get("h6").contains("Draft-sample").click() + cy.get("div[data-schema='draft-sample'] > span").should("have.length", "1") cy.get("p").contains("1-1 of 1").should("be.visible") }) diff --git a/src/components/Home/UserDraftTemplates.js b/src/components/Home/UserDraftTemplates.js index 5cd72b9d..79a791d9 100644 --- a/src/components/Home/UserDraftTemplates.js +++ b/src/components/Home/UserDraftTemplates.js @@ -109,7 +109,7 @@ const UserDraftTemplates = (): React$Element => { } return ( - + setOpen(!open)}> {formatDisplayObjectType(schema)} diff --git a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js index ef22fc64..8032130f 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js @@ -8,6 +8,7 @@ import { makeStyles } from "@material-ui/core/styles" import MuiTextField from "@material-ui/core/TextField" import Typography from "@material-ui/core/Typography" import ExpandMoreIcon from "@material-ui/icons/ExpandMore" +// import { merge } from "lodash" import { useForm, Controller } from "react-hook-form" import { useDispatch, useSelector } from "react-redux" import { useHistory } from "react-router-dom" @@ -64,6 +65,9 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create const classes = useStyles() const dispatch = useDispatch() const folder = useSelector(state => state.submissionFolder) + const user = useSelector(state => state.user) + const reuseDrafts = useSelector(state => state.reuseDrafts) + const [connError, setConnError] = useState(false) const [responseError, setResponseError] = useState({}) const { @@ -81,7 +85,8 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) .catch(() => setConnError(true)) } else { - dispatch(createNewDraftFolder(data)) + const reuseDraftsDetails = user.drafts?.filter(draft => reuseDrafts.includes(draft.accessionId)) + dispatch(createNewDraftFolder(data, reuseDraftsDetails)) .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) .catch(error => { setConnError(true) @@ -136,6 +141,7 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create expandIcon={} aria-controls="user-drafts-content" id="user-drafts-header" + data-testid="toggle-user-drafts" > Saved Draft Templates diff --git a/src/features/wizardSubmissionFolderSlice.js b/src/features/wizardSubmissionFolderSlice.js index 3f9acdfb..a70f436e 100644 --- a/src/features/wizardSubmissionFolderSlice.js +++ b/src/features/wizardSubmissionFolderSlice.js @@ -57,13 +57,13 @@ export const { export default wizardSubmissionFolderSlice.reducer export const createNewDraftFolder = - (folderDetails: FolderDataFromForm): ((dispatch: (any) => void) => Promise) => + (folderDetails: FolderDataFromForm, drafts?: any): ((dispatch: (any) => void) => Promise) => async (dispatch: any => void) => { const folderForBackend: FolderDetails = { ...folderDetails, published: false, metadataObjects: [], - drafts: [], + drafts: drafts ? drafts : [], } const response = await folderAPIService.createNewFolder(folderForBackend) From 32c5210c0a091cc29430a05a67abdad829328c25 Mon Sep 17 00:00:00 2001 From: HangLe Date: Thu, 30 Sep 2021 11:56:25 +0300 Subject: [PATCH 05/12] Save selected drafts as templates when publishing folder --- src/App.js | 6 +- src/__tests__/WizardAddObjectStep.test.js | 4 +- src/__tests__/WizardObjectIndex.test.js | 2 +- src/__tests__/WizardShowSummaryStep.test.js | 2 +- src/components/Home/UserDraftTemplates.js | 2 +- .../WizardComponents/WizardAlert.js | 2 +- .../WizardComponents/WizardDraftSelections.js | 6 +- .../WizardComponents/WizardFooter.js | 107 +++++++++++++----- .../WizardComponents/WizardObjectIndex.js | 2 +- .../WizardHooks/WizardSubmitObjectHook.js | 1 - .../WizardSteps/WizardShowSummaryStep.js | 2 +- src/features/objectTypesArraySlice.js | 16 +++ src/features/objectsArraySlice.js | 16 --- src/features/userSlice.js | 7 +- src/rootReducer.js | 4 +- src/services/errorMonitor.js | 2 +- src/services/templateAPI.js | 18 +++ src/setupProxy.js | 1 + src/utils/index.js | 5 + src/views/Home.js | 1 - 20 files changed, 137 insertions(+), 69 deletions(-) create mode 100644 src/features/objectTypesArraySlice.js delete mode 100644 src/features/objectsArraySlice.js create mode 100644 src/services/templateAPI.js diff --git a/src/App.js b/src/App.js index ce6e0928..4f53b489 100644 --- a/src/App.js +++ b/src/App.js @@ -11,7 +11,7 @@ import SelectedFolderDetails from "components/Home/SelectedFolderDetails" import SubmissionFolderList from "components/Home/SubmissionFolderList" import Nav from "components/Nav" import { ObjectTypes } from "constants/wizardObject" -import { setObjectsArray } from "features/objectsArraySlice" +import { setObjectTypesArray } from "features/objectTypesArraySlice" import schemaAPIService from "services/schemaAPI" import Page400 from "views/ErrorPages/Page400" import Page401 from "views/ErrorPages/Page401" @@ -81,10 +81,10 @@ const App = (): React$Element => { const schemas = response.data .filter(schema => schema.title !== "Project" && schema.title !== "Submission") .map(schema => schema.title.toLowerCase()) - dispatch(setObjectsArray(schemas)) + dispatch(setObjectTypesArray(schemas)) } else { dispatch( - setObjectsArray([ + setObjectTypesArray([ ObjectTypes.study, ObjectTypes.sample, ObjectTypes.experiment, diff --git a/src/__tests__/WizardAddObjectStep.test.js b/src/__tests__/WizardAddObjectStep.test.js index a9fdeda8..cdb7ef24 100644 --- a/src/__tests__/WizardAddObjectStep.test.js +++ b/src/__tests__/WizardAddObjectStep.test.js @@ -21,7 +21,7 @@ describe("WizardAddObjectStep", () => { it("should not render any cards if no selected object type", () => { const store = mockStore({ objectType: "", - objectsArray: [ + objectTypesArray: [ ObjectTypes.study, ObjectTypes.sample, ObjectTypes.experiment, @@ -58,7 +58,7 @@ describe("WizardAddObjectStep", () => { ObjectSubmissionsArray.forEach(typeName => { const store = mockStore({ objectType: ObjectTypes.study, - objectsArray: [ + objectTypesArray: [ ObjectTypes.study, ObjectTypes.sample, ObjectTypes.experiment, diff --git a/src/__tests__/WizardObjectIndex.test.js b/src/__tests__/WizardObjectIndex.test.js index 50df097d..c97f37c5 100644 --- a/src/__tests__/WizardObjectIndex.test.js +++ b/src/__tests__/WizardObjectIndex.test.js @@ -14,7 +14,7 @@ const mockStore = configureStore([]) describe("WizardObjectIndex", () => { it("should render badge with number correctly", async () => { const store = mockStore({ - objectsArray: [ + objectTypesArray: [ ObjectTypes.study, ObjectTypes.sample, ObjectTypes.experiment, diff --git a/src/__tests__/WizardShowSummaryStep.test.js b/src/__tests__/WizardShowSummaryStep.test.js index f7ed3c01..51dbb9bf 100644 --- a/src/__tests__/WizardShowSummaryStep.test.js +++ b/src/__tests__/WizardShowSummaryStep.test.js @@ -23,7 +23,7 @@ describe("WizardShowSummaryStep", () => { beforeEach(() => { store = mockStore({ - objectsArray: [ + objectTypesArray: [ ObjectTypes.study, ObjectTypes.sample, ObjectTypes.experiment, diff --git a/src/components/Home/UserDraftTemplates.js b/src/components/Home/UserDraftTemplates.js index 79a791d9..80198fd4 100644 --- a/src/components/Home/UserDraftTemplates.js +++ b/src/components/Home/UserDraftTemplates.js @@ -64,7 +64,7 @@ const useStyles = makeStyles(theme => ({ const UserDraftTemplates = (): React$Element => { const classes = useStyles() const user = useSelector(state => state.user) - const objectsArray = useSelector(state => state.objectsArray) + const objectsArray = useSelector(state => state.objectTypesArray) const reuseDrafts = useSelector(state => state.reuseDrafts) const dispatch = useDispatch() diff --git a/src/components/NewDraftWizard/WizardComponents/WizardAlert.js b/src/components/NewDraftWizard/WizardComponents/WizardAlert.js index aced2de7..8e6d739e 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardAlert.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardAlert.js @@ -302,7 +302,7 @@ const WizardAlert = ({ parentLocation, alertType, }: { - onAlert: (boolean, formData?: Array) => void, + onAlert: (boolean, formData?: Array) => any, parentLocation: string, alertType: string, }): React$Element => { diff --git a/src/components/NewDraftWizard/WizardComponents/WizardDraftSelections.js b/src/components/NewDraftWizard/WizardComponents/WizardDraftSelections.js index 05f20d63..359ec910 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardDraftSelections.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardDraftSelections.js @@ -21,7 +21,7 @@ import { updateStatus } from "features/wizardStatusMessageSlice" import { setSubmissionType } from "features/wizardSubmissionTypeSlice" import draftAPIService from "services/draftAPI" import type { ObjectInsideFolderWithTags } from "types" -import { getItemPrimaryText, getDraftObjects } from "utils" +import { getItemPrimaryText, getDraftObjects, getOrigObjectType } from "utils" const useStyles = makeStyles(theme => ({ formComponent: { @@ -95,7 +95,7 @@ const WizardDraftSelections = (props: WizardDraftSelectionsProps): React$Element const classes = useStyles() const dispatch = useDispatch() const folder = useSelector(state => state.submissionFolder) - const objectsArray = useSelector(state => state.objectsArray) + const objectsArray = useSelector(state => state.objectTypesArray) const history = useHistory() const draftObjects = getDraftObjects(folder.drafts, objectsArray) @@ -112,7 +112,7 @@ const WizardDraftSelections = (props: WizardDraftSelectionsProps): React$Element } const handleViewButton = async (draftSchema: string, draftId: string) => { - const objectType = draftSchema.slice(draftSchema.indexOf("-") + 1) + const objectType = getOrigObjectType(draftSchema) const response = await draftAPIService.getObjectByAccessionId(objectType, draftId) if (response.ok) { diff --git a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js index 013a1c4a..da47ec5a 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js @@ -4,19 +4,21 @@ import React, { useState } from "react" import Button from "@material-ui/core/Button" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" +import { omit } from "lodash" import { useDispatch, useSelector } from "react-redux" import { useHistory, Link as RouterLink } from "react-router-dom" -import WizardStatusMessageHandler from "../WizardForms/WizardStatusMessageHandler" - import WizardAlert from "./WizardAlert" +import { OmitObjectValues } from "constants/wizardObject" import { WizardStatus } from "constants/wizardStatus" -import { addDraftsToUser } from "features/userSlice" import { resetObjectType } from "features/wizardObjectTypeSlice" -import { deleteFolderAndContent, publishFolderContent, resetFolder } from "features/wizardSubmissionFolderSlice" +import { updateStatus } from "features/wizardStatusMessageSlice" +import { publishFolderContent, deleteFolderAndContent, resetFolder } from "features/wizardSubmissionFolderSlice" +import draftAPIService from "services/draftAPI" +import templateAPIService from "services/templateAPI" import type { ObjectInsideFolderWithTags } from "types" -import { useQuery } from "utils" +import { useQuery, getOrigObjectType } from "utils" const useStyles = makeStyles(theme => ({ footerRow: { @@ -55,9 +57,6 @@ const WizardFooter = (): React$Element => { const [dialogOpen, setDialogOpen] = useState(false) const [alertType, setAlertType] = useState("") - const [connError, setConnError] = useState(false) - const [responseError, setResponseError] = useState({}) - const [errorPrefix, setErrorPrefix] = useState("") let history = useHistory() @@ -71,34 +70,87 @@ const WizardFooter = (): React$Element => { dispatch(resetFolder()) } - const handleAlert = (alertWizard: boolean, formData?: Array) => { - setConnError(false) + const handleAlert = async (alertWizard: boolean, formData?: Array) => { if (alertWizard && alertType === "cancel") { dispatch(deleteFolderAndContent(folder)) .then(() => resetDispatch()) .catch(error => { - setConnError(true) - setResponseError(JSON.parse(error.response)) - setErrorPrefix(error.message) + dispatch( + updateStatus({ + successStatus: WizardStatus.error, + response: error, + errorPrefix: "", + }) + ) }) } else if (alertWizard && alertType === "save") { resetDispatch() } else if (alertWizard && alertType === "publish") { + if (formData && formData?.length > 0) { + // Filter unique draft-schemas existing in formData + const draftSchemas = formData.map(item => item.schema).filter((val, ind, arr) => arr.indexOf(val) === ind) + + // Group the data according to their schemas aka objectTypes + const groupedData = draftSchemas.map(draftSchema => { + const schema = getOrigObjectType(draftSchema) + return { + [schema]: formData.filter(el => el.schema === draftSchema), + } + }) + + // Fetch drafts' values and add to draft templates based on their objectTypes + for (let i = 0; i < groupedData.length; i += 1) { + const objectType = Object.keys(groupedData[i])[0] + const draftsByObjectType = groupedData[i][objectType] + + const draftsArr = [] + for (let j = 0; j < draftsByObjectType.length; j += 1) { + // Fetch drafts' values + const draftResponse = await draftAPIService.getObjectByAccessionId( + objectType, + draftsByObjectType[j].accessionId + ) + if (draftResponse.ok) { + // Remove unnecessary values such as "date" + // Add drafts' values of the same objectType to an array + draftsArr.push(omit(draftResponse.data, OmitObjectValues)) + } else { + dispatch( + updateStatus({ + successStatus: WizardStatus.error, + response: draftResponse, + errorPrefix: "Error when getting the drafts' details", + }) + ) + } + } + + if (draftsArr.length > 0) { + // POST selected drafts to save as templates based on the same objectType + const templateResponse = await templateAPIService.createTemplatesFromJSON(objectType, draftsArr) + if (!templateResponse.ok) + dispatch( + updateStatus({ + successStatus: WizardStatus.error, + response: templateResponse, + errorPrefix: "Cannot save selected draft(s) as template(s)", + }) + ) + } + } + } + // Publish the folder dispatch(publishFolderContent(folder)) .then(() => resetDispatch()) .catch(error => { - setConnError(true) - setResponseError(JSON.parse(error)) - setErrorPrefix(`Couldn't publish folder with id ${folder.folderId}`) + dispatch( + updateStatus({ + successStatus: WizardStatus.error, + response: error, + errorPrefix: `Couldn't publish folder with id ${folder.folderId}`, + }) + ) }) - - formData && formData?.length > 0 - ? dispatch(addDraftsToUser("current", formData)).catch(error => { - setConnError(true) - setResponseError(JSON.parse(error)) - setErrorPrefix("Can't save drafts for user") - }) - : null } else { setDialogOpen(false) } @@ -172,13 +224,6 @@ const WizardFooter = (): React$Element => { )} {dialogOpen && } - {connError && ( - - )} ) } diff --git a/src/components/NewDraftWizard/WizardComponents/WizardObjectIndex.js b/src/components/NewDraftWizard/WizardComponents/WizardObjectIndex.js index f2803a71..66bb307b 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardObjectIndex.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardObjectIndex.js @@ -224,7 +224,7 @@ const WizardObjectIndex = (): React$Element => { const [clickedSubmissionType, setClickedSubmissionType] = useState("") const [cancelFormOpen, setCancelFormOpen] = useState(false) - const objectsArray = useSelector(state => state.objectsArray) + const objectsArray = useSelector(state => state.objectTypesArray) const currentObjectType = useSelector(state => state.objectType) const currentSubmissionType = useSelector(state => state.submissionType) const draftStatus = useSelector(state => state.draftStatus) diff --git a/src/components/NewDraftWizard/WizardHooks/WizardSubmitObjectHook.js b/src/components/NewDraftWizard/WizardHooks/WizardSubmitObjectHook.js index ebaafcdb..6c8ec6e8 100644 --- a/src/components/NewDraftWizard/WizardHooks/WizardSubmitObjectHook.js +++ b/src/components/NewDraftWizard/WizardHooks/WizardSubmitObjectHook.js @@ -23,7 +23,6 @@ const submitObjectHook = async (formData: any, folderId: string, objectType: str }, 5000) const cleanedValues = JSONSchemaParser.cleanUpFormValues(formData) - const response = await objectAPIService.createFromJSON(objectType, cleanedValues) if (response.ok) { diff --git a/src/components/NewDraftWizard/WizardSteps/WizardShowSummaryStep.js b/src/components/NewDraftWizard/WizardSteps/WizardShowSummaryStep.js index 8bee82cf..9309eccd 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardShowSummaryStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardShowSummaryStep.js @@ -54,7 +54,7 @@ type GroupedBySchema = {| [Schema]: Array |} const WizardShowSummaryStep = (): React$Element => { const folder = useSelector(state => state.submissionFolder) const { metadataObjects } = folder - const objectsArray = useSelector(state => state.objectsArray) + const objectsArray = useSelector(state => state.objectTypesArray) const groupedObjects: Array = objectsArray.map((schema: string) => { return { [(schema: string)]: metadataObjects.filter(object => object.schema.toLowerCase() === schema.toLowerCase()), diff --git a/src/features/objectTypesArraySlice.js b/src/features/objectTypesArraySlice.js new file mode 100644 index 00000000..21583db5 --- /dev/null +++ b/src/features/objectTypesArraySlice.js @@ -0,0 +1,16 @@ +//@flow +import { createSlice } from "@reduxjs/toolkit" + +const initialState: [] | Array = [] + +const objectTypesArraySlice: any = createSlice({ + name: "objectsArraySlice", + initialState, + reducers: { + setObjectTypesArray: (state, action) => action.payload, + resetObjectTypesArray: () => initialState, + }, +}) + +export const { setObjectTypesArray } = objectTypesArraySlice.actions +export default objectTypesArraySlice.reducer diff --git a/src/features/objectsArraySlice.js b/src/features/objectsArraySlice.js deleted file mode 100644 index b5e30ca7..00000000 --- a/src/features/objectsArraySlice.js +++ /dev/null @@ -1,16 +0,0 @@ -//@flow -import { createSlice } from "@reduxjs/toolkit" - -const initialState: [] | Array = [] - -const objectsArraySlice: any = createSlice({ - name: "objectsArraySlice", - initialState, - reducers: { - setObjectsArray: (state, action) => action.payload, - resetObjectsArray: () => initialState, - }, -}) - -export const { setObjectsArray } = objectsArraySlice.actions -export default objectsArraySlice.reducer diff --git a/src/features/userSlice.js b/src/features/userSlice.js index 7ea2c80e..05514183 100644 --- a/src/features/userSlice.js +++ b/src/features/userSlice.js @@ -2,11 +2,12 @@ import { createSlice } from "@reduxjs/toolkit" import userAPIService from "services/usersAPI" +import type { ObjectInsideFolderWithTags } from "types" type User = { id: string, name: string, - drafts: Array, + templates: Array, folders: Array, } @@ -33,7 +34,7 @@ export const fetchUserById = const user: User = { id: response.data.userId, name: response.data.name, - drafts: response.data.drafts, + templates: response.data.templates, folders: response.data.folders, } dispatch(setUser(user)) @@ -47,7 +48,7 @@ export const fetchUserById = export const addDraftsToUser = (userId: string, drafts: any): ((dispatch: (any) => void) => Promise) => async () => { - const changes = [{ op: "add", path: "/drafts/-", value: drafts }] + const changes = [{ op: "add", path: "/templates/-", value: drafts }] const response = await userAPIService.patchUserById("current", changes) return new Promise((resolve, reject) => { diff --git a/src/rootReducer.js b/src/rootReducer.js index 8d9226fc..ca66aa88 100644 --- a/src/rootReducer.js +++ b/src/rootReducer.js @@ -6,7 +6,7 @@ import clearFormReducer from "features/clearFormSlice" import draftStatusReducer from "features/draftStatusSlice" import focusReducer from "features/focusSlice" import loadingReducer from "features/loadingSlice" -import objectsArrayReducer from "features/objectsArraySlice" +import objectTypesArrayReducer from "features/objectTypesArraySlice" import publishedFoldersReducer from "features/publishedFoldersSlice" import reuseDraftsReducer from "features/reuseDraftsSlice" import selectedFolderReducer from "features/selectedFolderSlice" @@ -34,7 +34,7 @@ const rootReducer: any = combineReducers({ unpublishedFolders: unpublishedFoldersReducer, publishedFolders: publishedFoldersReducer, selectedFolder: selectedFolderReducer, - objectsArray: objectsArrayReducer, + objectTypesArray: objectTypesArrayReducer, totalFolders: totalFoldersReducer, clearForm: clearFormReducer, reuseDrafts: reuseDraftsReducer, diff --git a/src/services/errorMonitor.js b/src/services/errorMonitor.js index 08ddb614..526afe1e 100644 --- a/src/services/errorMonitor.js +++ b/src/services/errorMonitor.js @@ -3,7 +3,7 @@ export const errorMonitor = res => { if (!res.ok) { switch (res.status) { case 400: - window.location = "/error400" + // window.location = "/error400" break case 401: window.location = "/error401" diff --git a/src/services/templateAPI.js b/src/services/templateAPI.js new file mode 100644 index 00000000..ac48760b --- /dev/null +++ b/src/services/templateAPI.js @@ -0,0 +1,18 @@ +//@flow +import { create } from "apisauce" +// import { omit } from "lodash" + +import { errorMonitor } from "./errorMonitor" + +// import { OmitObjectValues } from "constants/wizardObject" + +const api = create({ baseURL: "/templates" }) +api.addMonitor(errorMonitor) + +const createTemplatesFromJSON = async (objectType: string, JSONContent: any): Promise => { + return await api.post(`/${objectType}`, JSONContent) +} + +export default { + createTemplatesFromJSON, +} diff --git a/src/setupProxy.js b/src/setupProxy.js index a4b586b2..6ff7971c 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -19,6 +19,7 @@ module.exports = function (app) { "/callback", "/logout", "/users", + "/templates", ], createProxyMiddleware({ target: `http://${proxy}`, diff --git a/src/utils/index.js b/src/utils/index.js index 8fd010ca..12bf20b4 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -194,3 +194,8 @@ export const getAccessionIds = ( } return [] } + +export const getOrigObjectType = (schema: string): string => { + const objectType = schema.slice(schema.indexOf("-") + 1) + return objectType +} diff --git a/src/views/Home.js b/src/views/Home.js index 56814e40..9068c3cf 100644 --- a/src/views/Home.js +++ b/src/views/Home.js @@ -34,7 +34,6 @@ const useStyles = makeStyles(theme => ({ const Home = (): React$Element => { const dispatch = useDispatch() const user = useSelector(state => state.user) - const unpublishedFolders = useSelector(state => state.unpublishedFolders) const publishedFolders = useSelector(state => state.publishedFolders) From 569bc4c338a3e4263ac29da3ad1a30e1c94d3c7a Mon Sep 17 00:00:00 2001 From: HangLe Date: Thu, 30 Sep 2021 16:10:41 +0300 Subject: [PATCH 06/12] Render User templates instead of drafts --- src/components/Home/UserDraftTemplates.js | 10 +++++----- src/utils/index.js | 19 ++++++++++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/components/Home/UserDraftTemplates.js b/src/components/Home/UserDraftTemplates.js index 80198fd4..90c51596 100644 --- a/src/components/Home/UserDraftTemplates.js +++ b/src/components/Home/UserDraftTemplates.js @@ -18,7 +18,7 @@ import { useForm, FormProvider, useFormContext, Controller } from "react-hook-fo import { useSelector, useDispatch } from "react-redux" import { setReuseDrafts } from "features/reuseDraftsSlice" -import { formatDisplayObjectType, getDraftObjects, getItemPrimaryText, Pagination } from "utils" +import { formatDisplayObjectType, getUserTemplates, getItemPrimaryText, Pagination } from "utils" const useStyles = makeStyles(theme => ({ card: { @@ -64,11 +64,11 @@ const useStyles = makeStyles(theme => ({ const UserDraftTemplates = (): React$Element => { const classes = useStyles() const user = useSelector(state => state.user) - const objectsArray = useSelector(state => state.objectTypesArray) + const objectTypesArray = useSelector(state => state.objectTypesArray) const reuseDrafts = useSelector(state => state.reuseDrafts) const dispatch = useDispatch() - const draftObjects = user.drafts ? getDraftObjects(user.drafts, objectsArray) : [] + const templates = user.templates ? getUserTemplates(user.templates, objectTypesArray) : [] const methods = useForm() @@ -92,7 +92,7 @@ const UserDraftTemplates = (): React$Element => {
{({ register }) => { - return draftObjects.map(draft => { + return templates.map(draft => { const schema = Object.keys(draft)[0] const [open, setOpen] = useState(true) // Control Pagination @@ -188,7 +188,7 @@ const UserDraftTemplates = (): React$Element => { titleTypographyProps={{ variant: "subtitle1", fontWeight: "fontWeightBold" }} subheader="You could choose which draft(s) you would like to reuse when creating a new folder." /> - {draftObjects?.length > 0 ? : } + {templates?.length > 0 ? : } ) } diff --git a/src/utils/index.js b/src/utils/index.js index 12bf20b4..2a69dcc2 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -58,16 +58,29 @@ export const formatDisplayObjectType = (objectType: string): string => { } // draftObjects contains an array of objects and each has a schema and the related draft(s) array if there is any -export const getDraftObjects = (drafts: Array, objectsArray: Array): any => { - const draftObjects = objectsArray.flatMap((schema: string) => { +export const getDraftObjects = (drafts: Array, objectTypesArray: Array): any => { + const draftObjects = objectTypesArray.flatMap((schema: string) => { const draftSchema = `draft-${schema}` const draftArray = drafts.filter(draft => draft.schema.toLowerCase() === draftSchema.toLowerCase()) - return draftArray.length > 0 ? [{ [`draft-${schema}`]: draftArray }] : [] + return draftArray.length > 0 ? [{ [draftSchema]: draftArray }] : [] }) return draftObjects } +export const getUserTemplates = ( + templates: Array, + objectTypesArray: Array +): any => { + const userTemplates = objectTypesArray.flatMap((schema: string) => { + const templateSchema = `template-${schema}` + const templatesArray = templates.filter(template => template.schema.toLowerCase() === templateSchema.toLowerCase()) + return templatesArray.length > 0 ? [{ [templateSchema]: templatesArray }] : [] + }) + + return userTemplates +} + // Get "rowsPerPageOptions" of TablePagination export const getRowsPerPageOptions = (totalItems?: number): Array => { if (totalItems) { From 0513fe592deaf14e294efcde384da0f72b4ea146 Mon Sep 17 00:00:00 2001 From: HangLe Date: Fri, 1 Oct 2021 18:24:41 +0300 Subject: [PATCH 07/12] Add selected templates when creating a new folder --- src/components/Home/UserDraftTemplates.js | 8 +-- .../WizardSteps/WizardCreateFolderStep.js | 52 ++++++++++++++++--- src/features/reuseDraftsSlice.js | 16 ------ src/features/templatesSlice.js | 16 ++++++ src/features/wizardSubmissionFolderSlice.js | 2 + src/rootReducer.js | 4 +- src/services/errorMonitor.js | 6 +-- src/services/templateAPI.js | 5 ++ 8 files changed, 78 insertions(+), 31 deletions(-) delete mode 100644 src/features/reuseDraftsSlice.js create mode 100644 src/features/templatesSlice.js diff --git a/src/components/Home/UserDraftTemplates.js b/src/components/Home/UserDraftTemplates.js index 90c51596..4c35d326 100644 --- a/src/components/Home/UserDraftTemplates.js +++ b/src/components/Home/UserDraftTemplates.js @@ -17,7 +17,7 @@ import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp" import { useForm, FormProvider, useFormContext, Controller } from "react-hook-form" import { useSelector, useDispatch } from "react-redux" -import { setReuseDrafts } from "features/reuseDraftsSlice" +import { setTemplateAccessionIds } from "features/templatesSlice" import { formatDisplayObjectType, getUserTemplates, getItemPrimaryText, Pagination } from "utils" const useStyles = makeStyles(theme => ({ @@ -65,7 +65,7 @@ const UserDraftTemplates = (): React$Element => { const classes = useStyles() const user = useSelector(state => state.user) const objectTypesArray = useSelector(state => state.objectTypesArray) - const reuseDrafts = useSelector(state => state.reuseDrafts) + const templateAccessionIds = useSelector(state => state.templateAccessionIds) const dispatch = useDispatch() const templates = user.templates ? getUserTemplates(user.templates, objectTypesArray) : [] @@ -79,12 +79,12 @@ const UserDraftTemplates = (): React$Element => { // Render when there is user's draft template(s) const DraftList = () => { - const [checkedItems, setCheckedItems] = useState(reuseDrafts) + const [checkedItems, setCheckedItems] = useState(templateAccessionIds) const handleChange = () => { const checkedItems = Object.values(methods.getValues()).filter(item => item) setCheckedItems(checkedItems) - dispatch(setReuseDrafts(checkedItems)) + dispatch(setTemplateAccessionIds(checkedItems)) } return ( diff --git a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js index 8032130f..7977143c 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js @@ -8,7 +8,7 @@ import { makeStyles } from "@material-ui/core/styles" import MuiTextField from "@material-ui/core/TextField" import Typography from "@material-ui/core/Typography" import ExpandMoreIcon from "@material-ui/icons/ExpandMore" -// import { merge } from "lodash" +// import { omit } from "lodash" import { useForm, Controller } from "react-hook-form" import { useDispatch, useSelector } from "react-redux" import { useHistory } from "react-router-dom" @@ -16,11 +16,17 @@ import { useHistory } from "react-router-dom" import WizardHeader from "../WizardComponents/WizardHeader" import WizardStepper from "../WizardComponents/WizardStepper" import WizardStatusMessageHandler from "../WizardForms/WizardStatusMessageHandler" +// import saveDraftHook from "../WizardHooks/WizardSaveDraftHook" import UserDraftTemplates from "components/Home/UserDraftTemplates" +// import { OmitObjectValues } from "constants/wizardObject" import { WizardStatus } from "constants/wizardStatus" +import { updateStatus } from "features/wizardStatusMessageSlice" import { createNewDraftFolder, updateNewDraftFolder } from "features/wizardSubmissionFolderSlice" +import draftAPIService from "services/draftAPI" +import templateAPIService from "services/templateAPI" import type { FolderDataFromForm, CreateFolderFormRef } from "types" +import { getOrigObjectType, getObjectDisplayTitle } from "utils" const useStyles = makeStyles(theme => ({ root: { @@ -66,8 +72,7 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create const dispatch = useDispatch() const folder = useSelector(state => state.submissionFolder) const user = useSelector(state => state.user) - const reuseDrafts = useSelector(state => state.reuseDrafts) - + const templateAccessionIds = useSelector(state => state.templateAccessionIds) const [connError, setConnError] = useState(false) const [responseError, setResponseError] = useState({}) const { @@ -78,15 +83,50 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create const history = useHistory() - const onSubmit = (data: FolderDataFromForm) => { + const onSubmit = async (data: FolderDataFromForm) => { setConnError(false) if (folder && folder?.folderId) { dispatch(updateNewDraftFolder(folder.folderId, Object.assign({ ...data, folder }))) .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) .catch(() => setConnError(true)) } else { - const reuseDraftsDetails = user.drafts?.filter(draft => reuseDrafts.includes(draft.accessionId)) - dispatch(createNewDraftFolder(data, reuseDraftsDetails)) + const userTemplates = user.templates.map(template => ({ + ...template, + schema: getOrigObjectType(template.schema), + })) + + const templateDetails = userTemplates?.filter(item => templateAccessionIds.includes(item.accessionId)) + + let draftsArray = [] + for (let i = 0; i < templateDetails.length; i += 1) { + try { + // Get full details of template + const templateResponse = await templateAPIService.getTemplateByAccessionId( + templateDetails[i].schema, + templateDetails[i].accessionId + ) + // Create a draft based on the selected template + const draftResponse = await draftAPIService.createFromJSON(templateDetails[i].schema, templateResponse.data) + + // Draft details to be added when creating a new folder + const draftDetails = { + accessionId: draftResponse.data.accessionId, + schema: "draft-" + templateDetails[i].schema, + tags: { displayTitle: getObjectDisplayTitle(templateDetails[i].schema, templateResponse.data) }, + } + draftsArray.push(draftDetails) + } catch (err) { + dispatch( + updateStatus({ + successStatus: WizardStatus.err, + response: err, + errorPrefix: "Error fetching the template(s)", + }) + ) + } + } + // Create a new folder with selected templates as drafts + dispatch(createNewDraftFolder(data, draftsArray)) .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) .catch(error => { setConnError(true) diff --git a/src/features/reuseDraftsSlice.js b/src/features/reuseDraftsSlice.js deleted file mode 100644 index 007d1244..00000000 --- a/src/features/reuseDraftsSlice.js +++ /dev/null @@ -1,16 +0,0 @@ -//@flow -import { createSlice } from "@reduxjs/toolkit" - -const initialState: [] = [] - -const reuseDrafts: any = createSlice({ - name: "userDraft", - initialState, - reducers: { - setReuseDrafts: (state, action) => action.payload, - resetReuseDrafts: () => initialState, - }, -}) - -export const { setReuseDrafts, resetReuseDrafts } = reuseDrafts.actions -export default reuseDrafts.reducer diff --git a/src/features/templatesSlice.js b/src/features/templatesSlice.js new file mode 100644 index 00000000..330c3db6 --- /dev/null +++ b/src/features/templatesSlice.js @@ -0,0 +1,16 @@ +//@flow +import { createSlice } from "@reduxjs/toolkit" + +const initialState: [] = [] + +const templates: any = createSlice({ + name: "templates", + initialState, + reducers: { + setTemplateAccessionIds: (state, action) => action.payload, + resetTemplateAccessionIds: () => initialState, + }, +}) + +export const { setTemplateAccessionIds, resetTemplateAccessionIds } = templates.actions +export default templates.reducer diff --git a/src/features/wizardSubmissionFolderSlice.js b/src/features/wizardSubmissionFolderSlice.js index a70f436e..a4bd6775 100644 --- a/src/features/wizardSubmissionFolderSlice.js +++ b/src/features/wizardSubmissionFolderSlice.js @@ -11,6 +11,8 @@ import folderAPIService from "services/folderAPI" import publishAPIService from "services/publishAPI" import type { FolderDetails, FolderDetailsWithId, FolderDataFromForm, ObjectInsideFolderWithTags } from "types" +import { getObjectDisplayTitle } from "utils" + const initialState: null | FolderDetailsWithId = null const wizardSubmissionFolderSlice: any = createSlice({ diff --git a/src/rootReducer.js b/src/rootReducer.js index ca66aa88..46ecd0e8 100644 --- a/src/rootReducer.js +++ b/src/rootReducer.js @@ -8,7 +8,7 @@ import focusReducer from "features/focusSlice" import loadingReducer from "features/loadingSlice" import objectTypesArrayReducer from "features/objectTypesArraySlice" import publishedFoldersReducer from "features/publishedFoldersSlice" -import reuseDraftsReducer from "features/reuseDraftsSlice" +import templatesReducer from "features/templatesSlice" import selectedFolderReducer from "features/selectedFolderSlice" import totalFoldersReducer from "features/totalFoldersSlice" import unpublishedFoldersReducer from "features/unpublishedFoldersSlice" @@ -37,7 +37,7 @@ const rootReducer: any = combineReducers({ objectTypesArray: objectTypesArrayReducer, totalFolders: totalFoldersReducer, clearForm: clearFormReducer, - reuseDrafts: reuseDraftsReducer, + templateAccessionIds: templatesReducer, }) export default rootReducer diff --git a/src/services/errorMonitor.js b/src/services/errorMonitor.js index 526afe1e..f6be1812 100644 --- a/src/services/errorMonitor.js +++ b/src/services/errorMonitor.js @@ -6,13 +6,13 @@ export const errorMonitor = res => { // window.location = "/error400" break case 401: - window.location = "/error401" + // window.location = "/error401" break case 403: - window.location = "/error403" + // window.location = "/error403" break case 500: - window.location = "/error500" + // window.location = "/error500" break default: break diff --git a/src/services/templateAPI.js b/src/services/templateAPI.js index ac48760b..06079c8a 100644 --- a/src/services/templateAPI.js +++ b/src/services/templateAPI.js @@ -13,6 +13,11 @@ const createTemplatesFromJSON = async (objectType: string, JSONContent: any): Pr return await api.post(`/${objectType}`, JSONContent) } +const getTemplateByAccessionId = async (objectType: string, accessionId: string): Promise => { + return await api.get(`/${objectType}/${accessionId}`) +} + export default { createTemplatesFromJSON, + getTemplateByAccessionId, } From 8ce1e2cb313b9c9ae4a29b5e5b827b878ba867b3 Mon Sep 17 00:00:00 2001 From: HangLe Date: Fri, 1 Oct 2021 19:21:17 +0300 Subject: [PATCH 08/12] Create Hook files for different functions --- .../WizardComponents/WizardFooter.js | 59 +--------------- .../WizardHooks/WizardSaveTemplatesHook.js | 68 +++++++++++++++++++ .../WizardTransformTemplatesToDrafts.js | 53 +++++++++++++++ .../WizardSteps/WizardCreateFolderStep.js | 46 +------------ src/services/errorMonitor.js | 8 +-- 5 files changed, 131 insertions(+), 103 deletions(-) create mode 100644 src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js create mode 100644 src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js diff --git a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js index da47ec5a..5ba9e799 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js @@ -4,21 +4,18 @@ import React, { useState } from "react" import Button from "@material-ui/core/Button" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" -import { omit } from "lodash" import { useDispatch, useSelector } from "react-redux" import { useHistory, Link as RouterLink } from "react-router-dom" import WizardAlert from "./WizardAlert" -import { OmitObjectValues } from "constants/wizardObject" +import saveDraftsAsTemplates from "components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook" import { WizardStatus } from "constants/wizardStatus" import { resetObjectType } from "features/wizardObjectTypeSlice" import { updateStatus } from "features/wizardStatusMessageSlice" import { publishFolderContent, deleteFolderAndContent, resetFolder } from "features/wizardSubmissionFolderSlice" -import draftAPIService from "services/draftAPI" -import templateAPIService from "services/templateAPI" import type { ObjectInsideFolderWithTags } from "types" -import { useQuery, getOrigObjectType } from "utils" +import { useQuery } from "utils" const useStyles = makeStyles(theme => ({ footerRow: { @@ -87,57 +84,7 @@ const WizardFooter = (): React$Element => { resetDispatch() } else if (alertWizard && alertType === "publish") { if (formData && formData?.length > 0) { - // Filter unique draft-schemas existing in formData - const draftSchemas = formData.map(item => item.schema).filter((val, ind, arr) => arr.indexOf(val) === ind) - - // Group the data according to their schemas aka objectTypes - const groupedData = draftSchemas.map(draftSchema => { - const schema = getOrigObjectType(draftSchema) - return { - [schema]: formData.filter(el => el.schema === draftSchema), - } - }) - - // Fetch drafts' values and add to draft templates based on their objectTypes - for (let i = 0; i < groupedData.length; i += 1) { - const objectType = Object.keys(groupedData[i])[0] - const draftsByObjectType = groupedData[i][objectType] - - const draftsArr = [] - for (let j = 0; j < draftsByObjectType.length; j += 1) { - // Fetch drafts' values - const draftResponse = await draftAPIService.getObjectByAccessionId( - objectType, - draftsByObjectType[j].accessionId - ) - if (draftResponse.ok) { - // Remove unnecessary values such as "date" - // Add drafts' values of the same objectType to an array - draftsArr.push(omit(draftResponse.data, OmitObjectValues)) - } else { - dispatch( - updateStatus({ - successStatus: WizardStatus.error, - response: draftResponse, - errorPrefix: "Error when getting the drafts' details", - }) - ) - } - } - - if (draftsArr.length > 0) { - // POST selected drafts to save as templates based on the same objectType - const templateResponse = await templateAPIService.createTemplatesFromJSON(objectType, draftsArr) - if (!templateResponse.ok) - dispatch( - updateStatus({ - successStatus: WizardStatus.error, - response: templateResponse, - errorPrefix: "Cannot save selected draft(s) as template(s)", - }) - ) - } - } + saveDraftsAsTemplates(formData, dispatch) } // Publish the folder dispatch(publishFolderContent(folder)) diff --git a/src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js b/src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js new file mode 100644 index 00000000..402014b2 --- /dev/null +++ b/src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js @@ -0,0 +1,68 @@ +//@flow + +import { omit } from "lodash" + +import { OmitObjectValues } from "constants/wizardObject" +import { WizardStatus } from "constants/wizardStatus" +import { updateStatus } from "features/wizardStatusMessageSlice" +import draftAPIService from "services/draftAPI" +import templateAPIService from "services/templateAPI" +import { getOrigObjectType } from "utils" + +const saveDraftsAsTemplates = async (formData: any, dispatch: any): any => { + // Filter unique draft-schemas existing in formData + const draftSchemas = formData.map(item => item.schema).filter((val, ind, arr) => arr.indexOf(val) === ind) + + // Group the data according to their schemas aka objectTypes + const groupedData = draftSchemas.map(draftSchema => { + const schema = getOrigObjectType(draftSchema) + return { + [schema]: formData.filter(el => el.schema === draftSchema), + } + }) + + // Fetch drafts' values and add to draft templates based on their objectTypes + for (let i = 0; i < groupedData.length; i += 1) { + const objectType = Object.keys(groupedData[i])[0] + const draftsByObjectType = groupedData[i][objectType] + + const draftsArr = [] + for (let j = 0; j < draftsByObjectType.length; j += 1) { + try { + // Fetch drafts' values + const draftResponse = await draftAPIService.getObjectByAccessionId( + objectType, + draftsByObjectType[j].accessionId + ) + // Remove unnecessary values such as "date" + // Add drafts' values of the same objectType to an array + draftsArr.push(omit(draftResponse.data, OmitObjectValues)) + } catch (err) { + dispatch( + updateStatus({ + successStatus: WizardStatus.error, + response: err, + errorPrefix: "Error when getting the drafts' details", + }) + ) + } + } + + if (draftsArr.length > 0) { + try { + // POST selected drafts to save as templates based on the same objectType + await templateAPIService.createTemplatesFromJSON(objectType, draftsArr) + } catch (err) { + dispatch( + updateStatus({ + successStatus: WizardStatus.error, + response: err, + errorPrefix: "Cannot save selected draft(s) as template(s)", + }) + ) + } + } + } +} + +export default saveDraftsAsTemplates diff --git a/src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js b/src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js new file mode 100644 index 00000000..7a553a80 --- /dev/null +++ b/src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js @@ -0,0 +1,53 @@ +//@flow + +import { WizardStatus } from "constants/wizardStatus" +import { updateStatus } from "features/wizardStatusMessageSlice" +import draftAPIService from "services/draftAPI" +import templateAPIService from "services/templateAPI" +import type { ObjectInsideFolder } from "types" +import { getObjectDisplayTitle, getOrigObjectType } from "utils" + +const transformTemplatesToDrafts = async ( + templateAccessionIds: Array, + templates: Array, + dispatch: any +): any => { + const userTemplates = templates.map(template => ({ + ...template, + schema: getOrigObjectType(template.schema), + })) + + const templateDetails = userTemplates?.filter(item => templateAccessionIds.includes(item.accessionId)) + + let draftsArray = [] + for (let i = 0; i < templateDetails.length; i += 1) { + try { + // Get full details of template + const templateResponse = await templateAPIService.getTemplateByAccessionId( + templateDetails[i].schema, + templateDetails[i].accessionId + ) + // Create a draft based on the selected template + const draftResponse = await draftAPIService.createFromJSON(templateDetails[i].schema, templateResponse.data) + + // Draft details to be added when creating a new folder + const draftDetails = { + accessionId: draftResponse.data.accessionId, + schema: "draft-" + templateDetails[i].schema, + tags: { displayTitle: getObjectDisplayTitle(templateDetails[i].schema, templateResponse.data) }, + } + draftsArray.push(draftDetails) + } catch (err) { + dispatch( + updateStatus({ + successStatus: WizardStatus.err, + response: err, + errorPrefix: "Error fetching the template(s)", + }) + ) + } + } + return draftsArray +} + +export default transformTemplatesToDrafts diff --git a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js index 7977143c..ed2c61ce 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js @@ -8,7 +8,6 @@ import { makeStyles } from "@material-ui/core/styles" import MuiTextField from "@material-ui/core/TextField" import Typography from "@material-ui/core/Typography" import ExpandMoreIcon from "@material-ui/icons/ExpandMore" -// import { omit } from "lodash" import { useForm, Controller } from "react-hook-form" import { useDispatch, useSelector } from "react-redux" import { useHistory } from "react-router-dom" @@ -16,17 +15,12 @@ import { useHistory } from "react-router-dom" import WizardHeader from "../WizardComponents/WizardHeader" import WizardStepper from "../WizardComponents/WizardStepper" import WizardStatusMessageHandler from "../WizardForms/WizardStatusMessageHandler" -// import saveDraftHook from "../WizardHooks/WizardSaveDraftHook" import UserDraftTemplates from "components/Home/UserDraftTemplates" -// import { OmitObjectValues } from "constants/wizardObject" +import transformTemplatesToDrafts from "components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts" import { WizardStatus } from "constants/wizardStatus" -import { updateStatus } from "features/wizardStatusMessageSlice" import { createNewDraftFolder, updateNewDraftFolder } from "features/wizardSubmissionFolderSlice" -import draftAPIService from "services/draftAPI" -import templateAPIService from "services/templateAPI" import type { FolderDataFromForm, CreateFolderFormRef } from "types" -import { getOrigObjectType, getObjectDisplayTitle } from "utils" const useStyles = makeStyles(theme => ({ root: { @@ -59,7 +53,6 @@ const useStyles = makeStyles(theme => ({ width: "70%", margin: "0 auto", borderRadius: "0.375rem", - // border: "1px solid #bdbdbd", boxShadow: "0 0.1875rem 0.375rem rgba(0, 0, 0, 0.16)", }, })) @@ -90,41 +83,8 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) .catch(() => setConnError(true)) } else { - const userTemplates = user.templates.map(template => ({ - ...template, - schema: getOrigObjectType(template.schema), - })) - - const templateDetails = userTemplates?.filter(item => templateAccessionIds.includes(item.accessionId)) - - let draftsArray = [] - for (let i = 0; i < templateDetails.length; i += 1) { - try { - // Get full details of template - const templateResponse = await templateAPIService.getTemplateByAccessionId( - templateDetails[i].schema, - templateDetails[i].accessionId - ) - // Create a draft based on the selected template - const draftResponse = await draftAPIService.createFromJSON(templateDetails[i].schema, templateResponse.data) - - // Draft details to be added when creating a new folder - const draftDetails = { - accessionId: draftResponse.data.accessionId, - schema: "draft-" + templateDetails[i].schema, - tags: { displayTitle: getObjectDisplayTitle(templateDetails[i].schema, templateResponse.data) }, - } - draftsArray.push(draftDetails) - } catch (err) { - dispatch( - updateStatus({ - successStatus: WizardStatus.err, - response: err, - errorPrefix: "Error fetching the template(s)", - }) - ) - } - } + // Get an array of drafts which have proper values to be added to new folder + const draftsArray = transformTemplatesToDrafts(templateAccessionIds, user.templates, dispatch) // Create a new folder with selected templates as drafts dispatch(createNewDraftFolder(data, draftsArray)) .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) diff --git a/src/services/errorMonitor.js b/src/services/errorMonitor.js index f6be1812..08ddb614 100644 --- a/src/services/errorMonitor.js +++ b/src/services/errorMonitor.js @@ -3,16 +3,16 @@ export const errorMonitor = res => { if (!res.ok) { switch (res.status) { case 400: - // window.location = "/error400" + window.location = "/error400" break case 401: - // window.location = "/error401" + window.location = "/error401" break case 403: - // window.location = "/error403" + window.location = "/error403" break case 500: - // window.location = "/error500" + window.location = "/error500" break default: break From 21be63075ad791782308164fca0cc20617529211 Mon Sep 17 00:00:00 2001 From: HangLe Date: Tue, 5 Oct 2021 09:40:35 +0300 Subject: [PATCH 09/12] Update POST request when saving drafts as templates --- .../NewDraftWizard/WizardComponents/WizardFooter.js | 2 +- .../WizardHooks/WizardSaveTemplatesHook.js | 9 ++++++--- .../NewDraftWizard/WizardSteps/WizardCreateFolderStep.js | 3 ++- src/features/userSlice.js | 1 + src/features/wizardSubmissionFolderSlice.js | 2 -- src/rootReducer.js | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js index 5ba9e799..0b14102b 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js @@ -84,7 +84,7 @@ const WizardFooter = (): React$Element => { resetDispatch() } else if (alertWizard && alertType === "publish") { if (formData && formData?.length > 0) { - saveDraftsAsTemplates(formData, dispatch) + await saveDraftsAsTemplates(formData, dispatch) } // Publish the folder dispatch(publishFolderContent(folder)) diff --git a/src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js b/src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js index 402014b2..cfdf71b7 100644 --- a/src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js +++ b/src/components/NewDraftWizard/WizardHooks/WizardSaveTemplatesHook.js @@ -7,7 +7,7 @@ import { WizardStatus } from "constants/wizardStatus" import { updateStatus } from "features/wizardStatusMessageSlice" import draftAPIService from "services/draftAPI" import templateAPIService from "services/templateAPI" -import { getOrigObjectType } from "utils" +import { getOrigObjectType, getObjectDisplayTitle } from "utils" const saveDraftsAsTemplates = async (formData: any, dispatch: any): any => { // Filter unique draft-schemas existing in formData @@ -34,9 +34,12 @@ const saveDraftsAsTemplates = async (formData: any, dispatch: any): any => { objectType, draftsByObjectType[j].accessionId ) + // "tags" object to be added to JSONContent + const draftTags = { tags: { displayTitle: getObjectDisplayTitle(objectType, draftResponse.data) } } + // Remove unnecessary values such as "date" - // Add drafts' values of the same objectType to an array - draftsArr.push(omit(draftResponse.data, OmitObjectValues)) + // Add the object in the form of {template: draft's values, tags: {displayTitle}} to the array + draftsArr.push({ template: { ...omit(draftResponse.data, OmitObjectValues) }, ...draftTags }) } catch (err) { dispatch( updateStatus({ diff --git a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js index ed2c61ce..ca8221f9 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js @@ -84,7 +84,8 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create .catch(() => setConnError(true)) } else { // Get an array of drafts which have proper values to be added to new folder - const draftsArray = transformTemplatesToDrafts(templateAccessionIds, user.templates, dispatch) + const draftsArray = await transformTemplatesToDrafts(templateAccessionIds, user.templates, dispatch) + // Create a new folder with selected templates as drafts dispatch(createNewDraftFolder(data, draftsArray)) .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) diff --git a/src/features/userSlice.js b/src/features/userSlice.js index 05514183..55552e5c 100644 --- a/src/features/userSlice.js +++ b/src/features/userSlice.js @@ -29,6 +29,7 @@ export const fetchUserById = (userId: string): ((dispatch: (any) => void) => Promise) => async (dispatch: any => void) => { const response = await userAPIService.getUserById(userId) + return new Promise((resolve, reject) => { if (response.ok) { const user: User = { diff --git a/src/features/wizardSubmissionFolderSlice.js b/src/features/wizardSubmissionFolderSlice.js index a4bd6775..a70f436e 100644 --- a/src/features/wizardSubmissionFolderSlice.js +++ b/src/features/wizardSubmissionFolderSlice.js @@ -11,8 +11,6 @@ import folderAPIService from "services/folderAPI" import publishAPIService from "services/publishAPI" import type { FolderDetails, FolderDetailsWithId, FolderDataFromForm, ObjectInsideFolderWithTags } from "types" -import { getObjectDisplayTitle } from "utils" - const initialState: null | FolderDetailsWithId = null const wizardSubmissionFolderSlice: any = createSlice({ diff --git a/src/rootReducer.js b/src/rootReducer.js index 46ecd0e8..46797b0b 100644 --- a/src/rootReducer.js +++ b/src/rootReducer.js @@ -8,8 +8,8 @@ import focusReducer from "features/focusSlice" import loadingReducer from "features/loadingSlice" import objectTypesArrayReducer from "features/objectTypesArraySlice" import publishedFoldersReducer from "features/publishedFoldersSlice" -import templatesReducer from "features/templatesSlice" import selectedFolderReducer from "features/selectedFolderSlice" +import templatesReducer from "features/templatesSlice" import totalFoldersReducer from "features/totalFoldersSlice" import unpublishedFoldersReducer from "features/unpublishedFoldersSlice" import userReducer from "features/userSlice" From e4ad4ca9817e3abe3a49b6fc9286d8381398ed22 Mon Sep 17 00:00:00 2001 From: HangLe Date: Tue, 5 Oct 2021 11:20:43 +0300 Subject: [PATCH 10/12] Update e2e test for checking template worked as draft --- cypress/integration/draftTemplates.spec.js | 125 ++++++++++++--------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/cypress/integration/draftTemplates.spec.js b/cypress/integration/draftTemplates.spec.js index 594e99e1..6c4f7eaf 100644 --- a/cypress/integration/draftTemplates.spec.js +++ b/cypress/integration/draftTemplates.spec.js @@ -44,7 +44,7 @@ describe("draft selections and templates", function () { .then($btn => $btn.click()) ) - cy.get("input[name='title']").type("Sample draft title ") + cy.get("input[name='title']").type("Sample draft title") cy.get("input[name='sampleName.taxonId']").type(123) cy.get("button[type=button]").contains("Save as Draft").click() cy.get("div[role=alert]", { timeout: 10000 }).contains("Draft saved with") @@ -59,7 +59,7 @@ describe("draft selections and templates", function () { "have.text", "Objects in this folder will be published. Please choose the drafts you would like to save, unsaved drafts will be removed from this folder." ) - + }) it("should show the list of drafts before folder is published, and show saved drafts in Home page", () => { // Select drafts inside the dialog cy.get("form").within(() => { @@ -76,64 +76,81 @@ describe("draft selections and templates", function () { // Check if the drafts have been saved as user's templates in Home page cy.contains("Your Draft Templates", { timeout: 10000 }).should("be.visible") // Check saved Study draft - cy.get("h6").contains("Draft-study").as("studyObject") + cy.get("h6").contains("Template-study").as("studyObject") cy.get("@studyObject") .should("be.visible") .then($el => $el.click()) - cy.get("div[data-schema='draft-study']").contains("Study draft title").should("be.visible") + cy.get("div[data-schema='template-study']").contains("Study draft title").should("be.visible") // Check saved Sample draft - cy.get("h6").contains("Draft-sample").as("sampleObject") + cy.get("h6").contains("Template-sample").as("sampleObject") cy.get("@sampleObject") .should("be.visible") .then($el => $el.click()) - cy.get("div[data-schema='draft-sample']").contains("Sample draft title").should("be.visible") - }) - - it("should open the correct draft when clicking View button", () => { - // Select drafts inside the dialog - cy.get("form").within(() => { - cy.get("button[aria-label='View draft']").last().click() + cy.get("div[data-schema='template-sample']").contains("Sample draft title").should("be.visible") + }), + it("should open the correct draft when clicking View button", () => { + // Select drafts inside the dialog + cy.get("form").within(() => { + cy.get("button[aria-label='View draft']").last().click() + }) + cy.get("h1", { timeout: 10000 }).contains("Sample").should("be.visible") + cy.get("[data-testid='title']").should("have.value", "Sample draft title") + }), + it("should be able to select draft templates and reuse them when creating a new folder", () => { + // Select drafts inside the dialog + cy.get("form").within(() => { + cy.get("input[type='checkbox']").first().check() + cy.get("input[type='checkbox']").last().check() + + // Publish folder + cy.get('button[aria-label="Publish folder contents and move to frontpage"]').contains("Publish").click() + }) + // Navigate back to home page + cy.get("div", { timeout: 10000 }).contains("Logged in as:") + // Check if the drafts have been saved as user's templates in Home page + cy.contains("Your Draft Templates", { timeout: 10000 }).should("be.visible") + + // Select some drafts to reuse + cy.get("[data-testid='form-template-study']").within(() => { + cy.get("input").first().check() + }) + + cy.get("[data-testid='form-template-sample']").within(() => { + cy.get("input").first().click() + }) + + // Check that selected drafts exist + cy.get("button", { timeout: 10000 }).contains("Create Submission").click() + cy.get("[data-testid='toggle-user-drafts']", { timeout: 10000 }).click() + cy.get("[data-testid='form-template-study']").within(() => { + cy.get("input").first().should("be.checked") + }) + cy.get("[data-testid='form-template-sample']").within(() => { + cy.get("input").first().should("be.checked") + }) + + // Create a new submission with selected drafts + cy.get("input[name='name']").type("Test name") + cy.get("textarea[name='description']").type("Test description") + cy.get("button[type=button]").contains("Next").click() + + cy.wait(500) + + // Check that selected templates exist in the folder + cy.clickFillForm("Study") + cy.get("[data-testid='Draft-objects']").children().should("have.length", 1) + + cy.clickFillForm("Sample") + cy.get("[data-testid='Draft-objects']").children().should("have.length", 1) + + // Check if the draft can be used + cy.get("[aria-label='Edit submission']").click() + cy.get("[data-testid='title']").should("have.value", "Sample draft title") + cy.get("[data-testid='title']").type(" edited") + cy.get("button[type=button]").contains("Update draft").click() + + // Check that the draft works normlly as its title should be updated + cy.get("[aria-label='Edit submission']").click() + cy.get("[data-testid='title']").should("have.value", "Sample draft title edited") }) - cy.get("h1", { timeout: 10000 }).contains("Sample").should("be.visible") - cy.get("[data-testid='title']").should("have.value", "Sample draft title ") - }) - - it("should be able to select draft templates and reuse them when creating a new folder", () => { - // Select drafts inside the dialog - cy.get("form").within(() => { - cy.get("input[type='checkbox']").first().check() - cy.get("input[type='checkbox']").last().check() - - // Publish folder - cy.get('button[aria-label="Publish folder contents and move to frontpage"]').contains("Publish").click() - }) - // Navigate back to home page - cy.get("div", { timeout: 10000 }).contains("Logged in as:") - // Check if the drafts have been saved as user's templates in Home page - cy.contains("Your Draft Templates", { timeout: 10000 }).should("be.visible") - - // Select some drafts to reuse - cy.get("[data-testid='form-draft-study']").within(() => { - cy.get("input").first().check() - }) - - cy.get("[data-testid='form-draft-sample']").within(() => { - cy.get("input").first().click() - }) - - // Check that selected drafts exist - cy.get("button", { timeout: 10000 }).contains("Create Submission").click() - cy.get("[data-testid='toggle-user-drafts']", { timeout: 10000 }).click() - cy.get("[data-testid='form-draft-study']").within(() => { - cy.get("input").first().should("be.checked") - }) - cy.get("[data-testid='form-draft-sample']").within(() => { - cy.get("input").first().should("be.checked") - }) - - // Create a new submission with selected drafts - cy.get("input[name='name']").type("Test name") - cy.get("textarea[name='description']").type("Test description") - cy.get("button[type=button]").contains("Next").click() - }) }) From 89161507d231461cd63f1c7e0e65ec37a44da84b Mon Sep 17 00:00:00 2001 From: HangLe Date: Tue, 5 Oct 2021 12:51:52 +0300 Subject: [PATCH 11/12] Update e2e test for editing a folder with templates --- cypress/integration/draftTemplates.spec.js | 17 ++++++++ cypress/integration/pagination.spec.js | 42 +++++++++---------- .../WizardTransformTemplatesToDrafts.js | 4 +- .../WizardSteps/WizardCreateFolderStep.js | 21 ++++++---- src/features/wizardSubmissionFolderSlice.js | 14 ++++++- 5 files changed, 66 insertions(+), 32 deletions(-) diff --git a/cypress/integration/draftTemplates.spec.js b/cypress/integration/draftTemplates.spec.js index 6c4f7eaf..1cb3390b 100644 --- a/cypress/integration/draftTemplates.spec.js +++ b/cypress/integration/draftTemplates.spec.js @@ -152,5 +152,22 @@ describe("draft selections and templates", function () { // Check that the draft works normlly as its title should be updated cy.get("[aria-label='Edit submission']").click() cy.get("[data-testid='title']").should("have.value", "Sample draft title edited") + + // Check that editing a draft folder with adding a new template also works + // Navigate to home & find folder + cy.findDraftFolder("Test name") + cy.get('button[aria-label="Edit current folder"]').contains("Edit").click() + + // Select a new Study template + cy.get("[data-testid='toggle-user-drafts']", { timeout: 10000 }).click() + cy.get("[data-testid='form-template-study']").within(() => { + cy.get("input").first().check() + }) + cy.get("button[type=button]").contains("Next").click() + + cy.wait(500) + // Check that the new template is added as a draft + cy.clickFillForm("Study") + cy.get("[data-testid='Draft-objects']").children().should("have.length", 2) }) }) diff --git a/cypress/integration/pagination.spec.js b/cypress/integration/pagination.spec.js index f7443cb8..e25c1cb4 100644 --- a/cypress/integration/pagination.spec.js +++ b/cypress/integration/pagination.spec.js @@ -325,70 +325,70 @@ describe("unpublished folders, published folders, and user's draft templates pag cy.login() // Mock response for GET user const userResponse = { - drafts: [ + templates: [ { accessionId: "TESTID1", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID2", - schema: `draft-sample`, + schema: `template-sample`, }, { accessionId: "TESTD3", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID4", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID5", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID6", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID7", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID8", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID9", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID10", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID11", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID12", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID13", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID14", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID15", - schema: `draft-study`, + schema: `template-study`, }, { accessionId: "TESTID1", - schema: `draft-study`, + schema: `template-study`, }, ], folders: [], @@ -406,11 +406,11 @@ describe("unpublished folders, published folders, and user's draft templates pag cy.login() // Check Draft-study pagination - cy.get("div[data-schema='draft-study'] > span").should("have.length", "10") + cy.get("div[data-schema='template-study'] > span").should("have.length", "10") cy.get("p").contains("1-10 of 15").should("be.visible") cy.get("[data-testid='page info']").contains("1 of 2 pages").should("be.visible") - cy.get("div[data-testid='form-draft-study']").within(() => { + cy.get("div[data-testid='form-template-study']").within(() => { cy.get("button[aria-label='next page']").click() cy.get("p").contains("11-15 of 15", { timeout: 10000 }).should("be.visible") cy.get("[data-testid='page info']").contains("2 of 2 pages").should("be.visible") @@ -419,11 +419,11 @@ describe("unpublished folders, published folders, and user's draft templates pag cy.get("li[data-value='15']") .should("be.visible") .then($el => $el.click()) - cy.get("div[data-schema='draft-study'] > span").should("have.length", "15") + cy.get("div[data-schema='template-study'] > span").should("have.length", "15") // Check Draft-sample pagination - cy.get("div[data-schema='draft-sample'] > span").should("have.length", "1") + cy.get("div[data-schema='template-sample'] > span").should("have.length", "1") cy.get("p").contains("1-1 of 1").should("be.visible") }) }) diff --git a/src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js b/src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js index 7a553a80..1ae08e37 100644 --- a/src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js +++ b/src/components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts.js @@ -4,14 +4,14 @@ import { WizardStatus } from "constants/wizardStatus" import { updateStatus } from "features/wizardStatusMessageSlice" import draftAPIService from "services/draftAPI" import templateAPIService from "services/templateAPI" -import type { ObjectInsideFolder } from "types" +import type { ObjectInsideFolder, ObjectInsideFolderWithTags } from "types" import { getObjectDisplayTitle, getOrigObjectType } from "utils" const transformTemplatesToDrafts = async ( templateAccessionIds: Array, templates: Array, dispatch: any -): any => { +): Promise> => { const userTemplates = templates.map(template => ({ ...template, schema: getOrigObjectType(template.schema), diff --git a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js index ca8221f9..defb5c8b 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js @@ -19,6 +19,7 @@ import WizardStatusMessageHandler from "../WizardForms/WizardStatusMessageHandle import UserDraftTemplates from "components/Home/UserDraftTemplates" import transformTemplatesToDrafts from "components/NewDraftWizard/WizardHooks/WizardTransformTemplatesToDrafts" import { WizardStatus } from "constants/wizardStatus" +import { resetTemplateAccessionIds } from "features/templatesSlice" import { createNewDraftFolder, updateNewDraftFolder } from "features/wizardSubmissionFolderSlice" import type { FolderDataFromForm, CreateFolderFormRef } from "types" @@ -78,17 +79,23 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create const onSubmit = async (data: FolderDataFromForm) => { setConnError(false) + // Transform the format of templates to drafts with proper values to be added to current folder or new folder + const selectedDraftsArray = await transformTemplatesToDrafts(templateAccessionIds, user.templates, dispatch) + if (folder && folder?.folderId) { - dispatch(updateNewDraftFolder(folder.folderId, Object.assign({ ...data, folder }))) - .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) + dispatch(updateNewDraftFolder(folder.folderId, Object.assign({ ...data, folder, selectedDraftsArray }))) + .then(() => { + history.push({ pathname: "/newdraft", search: "step=1" }) + dispatch(resetTemplateAccessionIds()) + }) .catch(() => setConnError(true)) } else { - // Get an array of drafts which have proper values to be added to new folder - const draftsArray = await transformTemplatesToDrafts(templateAccessionIds, user.templates, dispatch) - // Create a new folder with selected templates as drafts - dispatch(createNewDraftFolder(data, draftsArray)) - .then(() => history.push({ pathname: "/newdraft", search: "step=1" })) + dispatch(createNewDraftFolder(data, selectedDraftsArray)) + .then(() => { + history.push({ pathname: "/newdraft", search: "step=1" }) + dispatch(resetTemplateAccessionIds()) + }) .catch(error => { setConnError(true) setResponseError(JSON.parse(error)) diff --git a/src/features/wizardSubmissionFolderSlice.js b/src/features/wizardSubmissionFolderSlice.js index a70f436e..f27e5ac4 100644 --- a/src/features/wizardSubmissionFolderSlice.js +++ b/src/features/wizardSubmissionFolderSlice.js @@ -84,17 +84,27 @@ export const createNewDraftFolder = export const updateNewDraftFolder = ( folderId: string, - folderDetails: FolderDataFromForm & { folder: Object } + folderDetails: FolderDataFromForm & { folder: Object } & { selectedDraftsArray: Array } ): ((dispatch: (any) => void) => Promise) => async (dispatch: any => void) => { + const { selectedDraftsArray } = folderDetails + // Add templates as selectedDrafts to current drafts in case there is any + const updatedDrafts = + selectedDraftsArray.length > 0 + ? folderDetails.folder.drafts.concat(selectedDraftsArray) + : folderDetails.folder.drafts + const updatedFolder = _extend( { ...folderDetails.folder }, - { name: folderDetails.name, description: folderDetails.description } + { name: folderDetails.name, description: folderDetails.description, drafts: updatedDrafts } ) + const changes = [ { op: "add", path: "/name", value: folderDetails.name }, { op: "add", path: "/description", value: folderDetails.description }, + { op: "add", path: "/drafts/-", value: updatedDrafts }, ] + const response = await folderAPIService.patchFolderById(folderId, changes) return new Promise((resolve, reject) => { From 106e5bddad5a0a8cc62797d9b7e515becb052214 Mon Sep 17 00:00:00 2001 From: HangLe Date: Tue, 5 Oct 2021 14:22:19 +0300 Subject: [PATCH 12/12] Add waiting time for cypress tests --- cypress/integration/app.spec.js | 1 + cypress/integration/draft.spec.js | 1 + cypress/integration/editForm.spec.js | 1 + cypress/integration/emptyForm.spec.js | 1 + cypress/integration/home.spec.js | 5 +++-- cypress/integration/linkingAccessionIds.spec.js | 2 +- cypress/integration/objectLinksAttributes.spec.js | 1 + cypress/integration/objectTitles.spec.js | 3 ++- 8 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cypress/integration/app.spec.js b/cypress/integration/app.spec.js index 6a01381a..4f609ddc 100644 --- a/cypress/integration/app.spec.js +++ b/cypress/integration/app.spec.js @@ -13,6 +13,7 @@ describe("Basic e2e", function () { cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) // Skip-link cy.get("div[role=button]").contains("Study").click() cy.get("div[role=button]").contains("Fill Form").type("{enter}") diff --git a/cypress/integration/draft.spec.js b/cypress/integration/draft.spec.js index 395e4ca4..f5193f21 100644 --- a/cypress/integration/draft.spec.js +++ b/cypress/integration/draft.spec.js @@ -8,6 +8,7 @@ describe("Draft operations", function () { cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) }) it("should create new folder, save, delete and continue draft", () => { diff --git a/cypress/integration/editForm.spec.js b/cypress/integration/editForm.spec.js index 260def83..b06c6c2c 100644 --- a/cypress/integration/editForm.spec.js +++ b/cypress/integration/editForm.spec.js @@ -8,6 +8,7 @@ describe("Populate form and render form elements by object data", function () { cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) }) it("should submit Sample form and display all form values when editing the form", () => { diff --git a/cypress/integration/emptyForm.spec.js b/cypress/integration/emptyForm.spec.js index 75f813fc..64c1f2ff 100644 --- a/cypress/integration/emptyForm.spec.js +++ b/cypress/integration/emptyForm.spec.js @@ -8,6 +8,7 @@ describe("empty form should not be alerted or saved", function () { cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) }) it("should have New form button and Clear form button emptied the form", () => { diff --git a/cypress/integration/home.spec.js b/cypress/integration/home.spec.js index 2d6e90cb..64fe04c8 100644 --- a/cypress/integration/home.spec.js +++ b/cypress/integration/home.spec.js @@ -55,6 +55,7 @@ describe("Home e2e", function () { cy.get('button[aria-label="Edit current folder"]').contains("Edit").click() cy.get("input[name='name']").clear().type("Edited unpublished folder") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) // Navigate to home and delete object cy.findDraftFolder("Edited unpublished folder") @@ -120,7 +121,7 @@ describe("Home e2e", function () { cy.get("input[name='name']").type("Test published folder") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() - + cy.wait(500) // Fill a Study form cy.get("div[role=button]").contains("Study").click() cy.get("div[role=button]").contains("Fill Form").click() @@ -133,7 +134,7 @@ describe("Home e2e", function () { // Navigate to summary cy.get("button[type=button]").contains("Next").click() - + cy.wait(500) // Check the amount of submitted objects in Study cy.get("h6").contains("Study").parent("div").children().eq(1).should("have.text", 1) diff --git a/cypress/integration/linkingAccessionIds.spec.js b/cypress/integration/linkingAccessionIds.spec.js index 598ccb03..ec05bb97 100644 --- a/cypress/integration/linkingAccessionIds.spec.js +++ b/cypress/integration/linkingAccessionIds.spec.js @@ -11,7 +11,7 @@ describe("Linking Accession Ids", function () { cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() - + cy.wait(500) // Upload a Study xml file. cy.get("div[role=button]").contains("Study").click() cy.get("div[role=button]").contains("Upload XML File").click() diff --git a/cypress/integration/objectLinksAttributes.spec.js b/cypress/integration/objectLinksAttributes.spec.js index 422e82a5..0e3c21db 100644 --- a/cypress/integration/objectLinksAttributes.spec.js +++ b/cypress/integration/objectLinksAttributes.spec.js @@ -8,6 +8,7 @@ describe("render objects' links and attributes ", function () { cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) // Focus on the Study title input in the Study form (not type anything) cy.get("div[role=button]").contains("Study").click() cy.get("div[role=button]").contains("Fill Form").click() diff --git a/cypress/integration/objectTitles.spec.js b/cypress/integration/objectTitles.spec.js index 959cd92f..c2384b1d 100644 --- a/cypress/integration/objectTitles.spec.js +++ b/cypress/integration/objectTitles.spec.js @@ -7,6 +7,7 @@ describe("draft and submitted objects' titles", function () { cy.get("input[name='name']").type("Test name") cy.get("textarea[name='description']").type("Test description") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) }) it("should show correct Submitted object's displayTitle", () => { @@ -42,7 +43,7 @@ describe("draft and submitted objects' titles", function () { // Navigate to summary cy.get("button[type=button]").contains("Next").click() - + cy.wait(500) // Check the submitted object has correct displayTitle cy.get("h6").contains("Study").parent("div").children().eq(1).should("have.text", 1) cy.get("div[data-schema='study']", { timeout: 10000 }).should("contain.text", "Test title 2")