diff --git a/cypress/integration/app.spec.js b/cypress/integration/app.spec.js index 272ea6cd..4f609ddc 100644 --- a/cypress/integration/app.spec.js +++ b/cypress/integration/app.spec.js @@ -8,14 +8,12 @@ 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") 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 d6b9392e..f5193f21 100644 --- a/cypress/integration/draft.spec.js +++ b/cypress/integration/draft.spec.js @@ -3,13 +3,12 @@ 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") 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/draftTemplates.spec.js b/cypress/integration/draftTemplates.spec.js index fde3c737..1cb3390b 100644 --- a/cypress/integration/draftTemplates.spec.js +++ b/cypress/integration/draftTemplates.spec.js @@ -3,17 +3,14 @@ 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") 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") @@ -47,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") @@ -62,7 +59,8 @@ 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(() => { cy.get("input[type='checkbox']").first().check() @@ -78,68 +76,98 @@ 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", () => { - // 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") - - // Submit Study form - cy.get("button[type=submit]").contains("Submit").click() - cy.get(".MuiListItem-container", { timeout: 10000 }).should("have.length", 1) - - // Create another Study draft form - cy.get("button").contains("New form").click() - cy.get("input[name='descriptor.studyTitle']").type("Study draft title") - - // Save a draft - cy.get("button[type=button]").contains("Save as Draft").click() - cy.get("div[role=alert]", { timeout: 10000 }).contains("Draft saved with") - - // 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("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") - - // Navigate to summary - 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("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") + + // 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) }) - 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/editForm.spec.js b/cypress/integration/editForm.spec.js index 052bd41b..b06c6c2c 100644 --- a/cypress/integration/editForm.spec.js +++ b/cypress/integration/editForm.spec.js @@ -3,13 +3,12 @@ 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") 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 f58fb4a0..64c1f2ff 100644 --- a/cypress/integration/emptyForm.spec.js +++ b/cypress/integration/emptyForm.spec.js @@ -4,13 +4,11 @@ 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") 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 d0619e61..64fe04c8 100644 --- a/cypress/integration/home.spec.js +++ b/cypress/integration/home.spec.js @@ -13,15 +13,13 @@ 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") 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") @@ -57,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") @@ -118,14 +117,11 @@ 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") 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() @@ -138,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 72772144..ec05bb97 100644 --- a/cypress/integration/linkingAccessionIds.spec.js +++ b/cypress/integration/linkingAccessionIds.spec.js @@ -7,14 +7,11 @@ 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") 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/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/objectLinksAttributes.spec.js b/cypress/integration/objectLinksAttributes.spec.js index 8ce4a7bc..0e3c21db 100644 --- a/cypress/integration/objectLinksAttributes.spec.js +++ b/cypress/integration/objectLinksAttributes.spec.js @@ -4,12 +4,11 @@ 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") 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 9b3dbe79..c2384b1d 100644 --- a/cypress/integration/objectTitles.spec.js +++ b/cypress/integration/objectTitles.spec.js @@ -2,12 +2,12 @@ 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") cy.get("button[type=button]").contains("Next").click() + cy.wait(500) }) it("should show correct Submitted object's displayTitle", () => { @@ -43,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") diff --git a/cypress/integration/pagination.spec.js b/cypress/integration/pagination.spec.js index 5021b892..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: [], @@ -405,21 +405,25 @@ 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("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("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-schema='draft-study'] > span").should("have.length", "15") + 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") + 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='template-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("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/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 aa54ef18..4c35d326 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 { formatDisplayObjectType, getDraftObjects, getItemPrimaryText, Pagination } from "utils" +import { setTemplateAccessionIds } from "features/templatesSlice" +import { formatDisplayObjectType, getUserTemplates, 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}`, @@ -70,70 +64,112 @@ const useStyles = makeStyles(theme => ({ const UserDraftTemplates = (): React$Element => { const classes = useStyles() const user = useSelector(state => state.user) - const objectsArray = useSelector(state => state.objectsArray) - const draftObjects = user.drafts ? getDraftObjects(user.drafts, objectsArray) : [] + const objectTypesArray = useSelector(state => state.objectTypesArray) + const templateAccessionIds = useSelector(state => state.templateAccessionIds) + + const dispatch = useDispatch() + const templates = user.templates ? getUserTemplates(user.templates, objectTypesArray) : [] + + 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(templateAccessionIds) + + const handleChange = () => { + const checkedItems = Object.values(methods.getValues()).filter(item => item) + setCheckedItems(checkedItems) + dispatch(setTemplateAccessionIds(checkedItems)) + } + + return ( + +
+ + {({ register }) => { + return templates.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,11 +183,12 @@ const UserDraftTemplates = (): React$Element => { return ( - {draftObjects?.length > 0 ? : } + {templates?.length > 0 ? : } ) } 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/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/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/userSlice.js b/src/features/userSlice.js index 7ea2c80e..55552e5c 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, } @@ -28,12 +29,13 @@ 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 = { 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 +49,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/features/wizardSubmissionFolderSlice.js b/src/features/wizardSubmissionFolderSlice.js index 3f9acdfb..f27e5ac4 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) @@ -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) => { diff --git a/src/rootReducer.js b/src/rootReducer.js index e09fcab3..46797b0b 100644 --- a/src/rootReducer.js +++ b/src/rootReducer.js @@ -6,9 +6,10 @@ 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 selectedFolderReducer from "features/selectedFolderSlice" +import templatesReducer from "features/templatesSlice" import totalFoldersReducer from "features/totalFoldersSlice" import unpublishedFoldersReducer from "features/unpublishedFoldersSlice" import userReducer from "features/userSlice" @@ -33,9 +34,10 @@ const rootReducer: any = combineReducers({ unpublishedFolders: unpublishedFoldersReducer, publishedFolders: publishedFoldersReducer, selectedFolder: selectedFolderReducer, - objectsArray: objectsArrayReducer, + objectTypesArray: objectTypesArrayReducer, totalFolders: totalFoldersReducer, clearForm: clearFormReducer, + templateAccessionIds: templatesReducer, }) export default rootReducer diff --git a/src/services/templateAPI.js b/src/services/templateAPI.js new file mode 100644 index 00000000..06079c8a --- /dev/null +++ b/src/services/templateAPI.js @@ -0,0 +1,23 @@ +//@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) +} + +const getTemplateByAccessionId = async (objectType: string, accessionId: string): Promise => { + return await api.get(`/${objectType}/${accessionId}`) +} + +export default { + createTemplatesFromJSON, + getTemplateByAccessionId, +} 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..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) { @@ -194,3 +207,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) 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 (