diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx index e045ce83..1611e66c 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx @@ -267,22 +267,6 @@ describe("DataSubmissionListFilters Component", () => { userEvent.click(orgSelectList.getByTestId("organization-option-Org2")); - const statusSelect = within(getByTestId("status-select")).getByRole("button"); - - userEvent.click(statusSelect); - - const statusSelectList = within(statusSelect.parentElement).getByRole("listbox", { - hidden: true, - }); - - await waitFor(() => { - expect(within(statusSelectList).getByTestId("status-option-All")).toBeInTheDocument(); - expect(within(statusSelectList).getByTestId("status-option-New")).toBeInTheDocument(); - expect(within(statusSelectList).getByTestId("status-option-Submitted")).toBeInTheDocument(); - }); - - userEvent.click(within(statusSelectList).getByTestId("status-option-Submitted")); - const dataCommonsSelect = within(getByTestId("data-commons-select")).getByRole("button"); userEvent.click(dataCommonsSelect); @@ -334,7 +318,7 @@ describe("DataSubmissionListFilters Component", () => { expect(mockOnChange).toHaveBeenCalledWith( expect.objectContaining({ organization: "Org2", - status: "Submitted", + status: expect.arrayContaining(["New", "Submitted"]), name: "Test Submission", dbGaPID: "12345", submitterName: "Submitter1", @@ -347,7 +331,9 @@ describe("DataSubmissionListFilters Component", () => { await waitFor(() => { expect(getByTestId("organization-select-input")).toHaveValue("All"); - expect(getByTestId("status-select-input")).toHaveValue("All"); + expect(getByTestId("status-select-input")).toHaveValue( + ["New", "In Progress", "Submitted", "Withdrawn", "Released", "Rejected"].join(",") + ); expect(getByTestId("data-commons-select-input")).toHaveValue("All"); expect(getByTestId("submission-name-input")).toHaveValue(""); expect(getByTestId("dbGaPID-input")).toHaveValue(""); @@ -357,7 +343,7 @@ describe("DataSubmissionListFilters Component", () => { expect(mockOnChange).toHaveBeenCalledWith( expect.objectContaining({ organization: "All", - status: "All", + status: ["New", "In Progress", "Submitted", "Withdrawn", "Released", "Rejected"], dataCommons: "All", name: "", dbGaPID: "", @@ -647,7 +633,7 @@ describe("DataSubmissionListFilters Component", () => { expect(mockOnChange).toHaveBeenCalledWith( expect.objectContaining({ organization: "Org1", - status: "Submitted", + status: ["Submitted"], dataCommons: "DataCommon1", name: "Test", dbGaPID: "123", @@ -659,6 +645,37 @@ describe("DataSubmissionListFilters Component", () => { userEvent.clear(getByTestId("submission-name-input")); }); + it("initializes study field based on searchParams and ignores invalid options", async () => { + const initialEntries = ["/?status=Deleted&status=RandomFakeStatus"]; + + const mockOnChange = jest.fn(); + const mockOnColumnVisibilityModelChange = jest.fn(); + + const { getByTestId } = render( + + + + ); + + await waitFor(() => { + expect(getByTestId("status-select-input")).toHaveValue("Deleted"); + }); + + expect(mockOnChange).toHaveBeenCalledWith( + expect.objectContaining({ + status: ["Deleted"], + }) + ); + }); + it("initializes form fields to default when searchParams are empty", async () => { const initialEntries = ["/"]; @@ -681,7 +698,9 @@ describe("DataSubmissionListFilters Component", () => { await waitFor(() => { expect(getByTestId("organization-select-input")).toHaveValue("All"); - expect(getByTestId("status-select-input")).toHaveValue("All"); + expect(getByTestId("status-select-input")).toHaveValue( + ["New", "In Progress", "Submitted", "Withdrawn", "Released", "Rejected"].join(",") + ); expect(getByTestId("data-commons-select-input")).toHaveValue("All"); expect(getByTestId("submission-name-input")).toHaveValue(""); expect(getByTestId("dbGaPID-input")).toHaveValue(""); @@ -691,7 +710,7 @@ describe("DataSubmissionListFilters Component", () => { expect(mockOnChange).toHaveBeenCalledWith( expect.objectContaining({ organization: "All", - status: "All", + status: ["New", "In Progress", "Submitted", "Withdrawn", "Released", "Rejected"], dataCommons: "All", name: "", dbGaPID: "", @@ -724,7 +743,7 @@ describe("DataSubmissionListFilters Component", () => { expect(mockOnChange).toHaveBeenCalledWith( expect.objectContaining({ organization: "All", - status: "All", + status: ["New", "In Progress", "Submitted", "Withdrawn", "Released", "Rejected"], dataCommons: "All", name: "", dbGaPID: "", diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.tsx index 8c1a1065..00d862ba 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.tsx @@ -1,6 +1,6 @@ import { memo, useEffect, useRef, useState } from "react"; import { FormControl, IconButton, MenuItem, Grid, Box, styled, Stack } from "@mui/material"; -import { debounce, isEqual } from "lodash"; +import { debounce, isEqual, sortBy } from "lodash"; import RefreshIcon from "@mui/icons-material/Refresh"; import { Controller, useForm } from "react-hook-form"; import StyledSelectFormComponent from "../StyledFormComponents/StyledSelect"; @@ -85,17 +85,17 @@ const statusValues: SubmissionStatus[] = [ "New", "In Progress", "Submitted", - "Released", "Withdrawn", + "Released", "Rejected", "Completed", "Canceled", "Deleted", ]; -const defaultValues: FilterForm = { +export const defaultValues: FilterForm = { organization: "All", - status: "All", + status: ["New", "In Progress", "Submitted", "Withdrawn", "Released", "Rejected"], dataCommons: "All", name: "", dbGaPID: "", @@ -188,17 +188,21 @@ const DataSubmissionListFilters = ({ useEffect(() => { const organizationId = searchParams.get("program"); - const status = searchParams.get("status"); + const statuses = searchParams.getAll("status"); const dataCommon = searchParams.get("dataCommons"); const name = searchParams.get("name"); const dbGaPID = searchParams.get("dbGaPID"); const submitterName = searchParams.get("submitterName"); - handleStatusChange(status); - if (organizationId && organizationId !== orgFilter) { setValue("organization", organizationId); } + if (statuses.length > 0 && !isArrayEqual(statuses, statusFilter)) { + const validStatuses = statuses.filter((status) => + statusValues.includes(status as SubmissionStatus) + ) as SubmissionStatus[]; + setValue("status", validStatuses); + } if (dataCommon && dataCommon !== dataCommonsFilter) { setValue("dataCommons", dataCommon); } @@ -229,10 +233,15 @@ const DataSubmissionListFilters = ({ } else if (orgFilter === "All") { newSearchParams.delete("program"); } - if (statusFilter && statusFilter !== "All") { - newSearchParams.set("status", statusFilter); - } else if (statusFilter === "All") { + if (statusFilter?.length > 0) { newSearchParams.delete("status"); + if (!isArrayEqual(statusFilter, defaultValues.status)) { + statusFilter.forEach((status) => { + newSearchParams.append("status", status); + }); + } + } else { + newSearchParams.set("status", ""); } if (dataCommonsFilter && dataCommonsFilter !== "All") { newSearchParams.set("dataCommons", dataCommonsFilter); @@ -306,19 +315,6 @@ const DataSubmissionListFilters = ({ }; }, [watch, debouncedOnChangeRef]); - const isStatusFilterOption = (status: string): status is FilterForm["status"] => - ["All", ...statusValues].includes(status); - - const handleStatusChange = (status: string) => { - if (status === statusFilter) { - return; - } - - if (isStatusFilterOption(status)) { - setValue("status", status); - } - }; - const handleFormChange = (form: FilterForm) => { if (!onChange || !form) { return; @@ -349,6 +345,8 @@ const DataSubmissionListFilters = ({ reset({ ...defaultValues }); }; + const isArrayEqual = (a1: string[], a2: string[]) => isEqual(sortBy(a1), sortBy(a2)); + return ( @@ -405,7 +403,7 @@ const DataSubmissionListFilters = ({ render={({ field }) => ( + selected?.length > 1 ? `${selected.length} statuses selected` : selected + } + multiple > - - All - {statusValues.map((value) => ( { const [dataCommons, setDataCommons] = useState([]); const [totalData, setTotalData] = useState(0); const tableRef = useRef(null); - const filtersRef = useRef({ - organization: "All", - status: "All", - dataCommons: "All", - name: "", - dbGaPID: "", - submitterName: "All", - }); + const filtersRef = useRef({ ...defaultValues }); const [listSubmissions, { refetch }] = useLazyQuery( LIST_SUBMISSIONS, @@ -282,7 +276,7 @@ const ListingView: FC = () => { const { data: d, error } = await listSubmissions({ variables: { organization: organization ?? "All", - status: status ?? "All", + status, dataCommons: dc ?? "All", submitterName: submitterName ?? "All", name: name || undefined, @@ -366,8 +360,6 @@ const ListingView: FC = () => { padding="57px 0 0 25px" body={ - {/* NOTE For MVP-2: Organization Owners are just Users */} - {/* Create a submission only available to org owners and submitters that have organizations assigned */} } diff --git a/src/graphql/listSubmissions.ts b/src/graphql/listSubmissions.ts index 5682ceb1..88c14b45 100644 --- a/src/graphql/listSubmissions.ts +++ b/src/graphql/listSubmissions.ts @@ -1,9 +1,10 @@ +import { TypedDocumentNode } from "@apollo/client"; import gql from "graphql-tag"; -export const query = gql` +export const query: TypedDocumentNode = gql` query listSubmissions( $organization: String - $status: String + $status: [String] $dataCommons: String $name: String $dbGaPID: String @@ -57,7 +58,7 @@ export const query = gql` export type Input = { organization?: string; - status?: SubmissionStatus | "All"; + status?: SubmissionStatus[]; dataCommons?: string; name?: string; dbGaPID?: string;