Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRDCDH-2265 Support multi-select status selection #606

Merged
merged 5 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 42 additions & 23 deletions src/components/DataSubmissions/DataSubmissionListFilters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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",
Expand All @@ -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("");
Expand All @@ -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: "",
Expand Down Expand Up @@ -647,7 +633,7 @@ describe("DataSubmissionListFilters Component", () => {
expect(mockOnChange).toHaveBeenCalledWith(
expect.objectContaining({
organization: "Org1",
status: "Submitted",
status: ["Submitted"],
dataCommons: "DataCommon1",
name: "Test",
dbGaPID: "123",
Expand All @@ -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(
<TestParent initialEntries={initialEntries} userRole="Admin">
<DataSubmissionListFilters
columns={columns}
organizations={organizations}
submitterNames={submitterNames}
dataCommons={dataCommons}
columnVisibilityModel={columnVisibilityModel}
onColumnVisibilityModelChange={mockOnColumnVisibilityModelChange}
onChange={mockOnChange}
/>
</TestParent>
);

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 = ["/"];

Expand All @@ -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("");
Expand All @@ -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: "",
Expand Down Expand Up @@ -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: "",
Expand Down
53 changes: 26 additions & 27 deletions src/components/DataSubmissions/DataSubmissionListFilters.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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: "",
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -229,9 +233,14 @@ const DataSubmissionListFilters = ({
} else if (orgFilter === "All") {
newSearchParams.delete("program");
}
if (statusFilter && statusFilter !== "All") {
newSearchParams.set("status", statusFilter);
} else if (statusFilter === "All") {
if (statusFilter && statusFilter.length > 0) {
newSearchParams.delete("status");
if (!isArrayEqual(statusFilter, defaultValues.status)) {
statusFilter.forEach((status) => {
newSearchParams.append("status", status);
});
}
} else if (!statusFilter || !statusFilter.length) {
amattu2 marked this conversation as resolved.
Show resolved Hide resolved
newSearchParams.delete("status");
}
if (dataCommonsFilter && dataCommonsFilter !== "All") {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -349,6 +345,8 @@ const DataSubmissionListFilters = ({
reset({ ...defaultValues });
};

const isArrayEqual = (a1: string[], a2: string[]) => isEqual(sortBy(a1), sortBy(a2));

return (
<StyledFilters data-testid="data-submission-list-filters">
<Stack direction="row" alignItems="center" gap="12px">
Expand Down Expand Up @@ -405,18 +403,19 @@ const DataSubmissionListFilters = ({
render={({ field }) => (
<StyledSelect
{...field}
value={field.value}
value={field.value || []}
MenuProps={{ disablePortal: true }}
inputProps={{ id: "status-filter", "data-testid": "status-select-input" }}
data-testid="status-select"
onChange={(e) => {
field.onChange(e);
handleFilterChange("status");
}}
renderValue={(selected: string[]) =>
selected?.length > 1 ? `${selected.length} statuses selected` : selected
}
multiple
>
<MenuItem value="All" data-testid="status-option-All">
All
</MenuItem>
{statusValues.map((value) => (
<MenuItem
key={`submission_status_${value}`}
Expand Down
14 changes: 3 additions & 11 deletions src/content/dataSubmissions/DataSubmissionsListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import TruncatedText from "../../components/TruncatedText";
import StyledTooltip from "../../components/StyledFormComponents/StyledTooltip";
import { useColumnVisibility } from "../../hooks/useColumnVisibility";
import DataSubmissionListFilters, {
defaultValues,
FilterForm,
} from "../../components/DataSubmissions/DataSubmissionListFilters";
import NavigatorLink from "../../components/DataSubmissions/NavigatorLink";
Expand Down Expand Up @@ -244,14 +245,7 @@ const ListingView: FC = () => {
const [dataCommons, setDataCommons] = useState<string[]>([]);
const [totalData, setTotalData] = useState<number>(0);
const tableRef = useRef<TableMethods>(null);
const filtersRef = useRef<FilterForm>({
organization: "All",
status: "All",
dataCommons: "All",
name: "",
dbGaPID: "",
submitterName: "All",
});
const filtersRef = useRef<FilterForm>({ ...defaultValues });

const [listSubmissions, { refetch }] = useLazyQuery<ListSubmissionsResp, ListSubmissionsInput>(
LIST_SUBMISSIONS,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -366,8 +360,6 @@ const ListingView: FC = () => {
padding="57px 0 0 25px"
body={
<StyledBannerBody direction="row" alignItems="center" justifyContent="flex-end">
{/* NOTE For MVP-2: Organization Owners are just Users */}
{/* Create a submission only available to org owners and submitters that have organizations assigned */}
<CreateDataSubmissionDialog onCreate={handleOnCreateSubmission} />
</StyledBannerBody>
}
Expand Down
7 changes: 4 additions & 3 deletions src/graphql/listSubmissions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TypedDocumentNode } from "@apollo/client";
import gql from "graphql-tag";

export const query = gql`
export const query: TypedDocumentNode<Response, Input> = gql`
query listSubmissions(
$organization: String
$status: String
$status: [String]
$dataCommons: String
$name: String
$dbGaPID: String
Expand Down Expand Up @@ -57,7 +58,7 @@ export const query = gql`

export type Input = {
organization?: string;
status?: SubmissionStatus | "All";
status?: SubmissionStatus[];
dataCommons?: string;
name?: string;
dbGaPID?: string;
Expand Down