From fa90a41f986cff344ea3c77dfad93be9b2d1e707 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Thu, 31 Oct 2024 10:13:13 -0400 Subject: [PATCH 01/16] Updated listSubmissions query and response type --- src/graphql/listSubmissions.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/graphql/listSubmissions.ts b/src/graphql/listSubmissions.ts index b95a1505..9ab8af3b 100644 --- a/src/graphql/listSubmissions.ts +++ b/src/graphql/listSubmissions.ts @@ -46,6 +46,10 @@ export const query = gql` updatedAt intention } + organizations { + _id + name + } submitterNames dataCommons } @@ -69,6 +73,7 @@ export type Response = { listSubmissions: { total: number; submissions: Omit[]; + organizations: Organization[]; submitterNames: string[]; dataCommons: string[]; }; From b9b5c71ac559a74ed59439be932494713a90113a Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Thu, 31 Oct 2024 10:14:22 -0400 Subject: [PATCH 02/16] Update filters to use org from listSubmissions API instead of retrieving all orgs --- .../DataSubmissionListFilters.tsx | 61 +++++++++---------- .../DataSubmissionsListView.tsx | 3 + 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.tsx index cc4407c9..1fabb873 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.tsx @@ -6,10 +6,7 @@ import { Controller, useForm } from "react-hook-form"; import StyledSelectFormComponent from "../StyledFormComponents/StyledSelect"; import StyledTextFieldFormComponent from "../StyledFormComponents/StyledOutlinedInput"; import ColumnVisibilityButton from "../GenericTable/ColumnVisibilityButton"; -import { useOrganizationListContext } from "../Contexts/OrganizationListContext"; import { ListSubmissionsInput, ListSubmissionsResp } from "../../graphql"; -import { useAuthContext } from "../Contexts/AuthContext"; -import { canViewOtherOrgRoles } from "../../config/AuthRoles"; import { Column } from "../GenericTable"; import { useSearchParamsContext } from "../Contexts/SearchParamsContext"; import Tooltip from "../Tooltip"; @@ -118,6 +115,7 @@ type TouchedState = { [K in FilterFormKey]: boolean }; type Props = { columns: Column[]; + organizations: Organization[]; submitterNames: string[]; dataCommons: string[]; columnVisibilityModel: ColumnVisibilityModel; @@ -127,14 +125,13 @@ type Props = { const DataSubmissionListFilters = ({ columns, + organizations, submitterNames, dataCommons, columnVisibilityModel, onColumnVisibilityModelChange, onChange, }: Props) => { - const { user } = useAuthContext(); - const { activeOrganizations } = useOrganizationListContext(); const { searchParams, setSearchParams } = useSearchParamsContext(); const { control, register, watch, reset, setValue, getValues } = useForm({ defaultValues, @@ -150,7 +147,6 @@ const DataSubmissionListFilters = ({ const [touchedFilters, setTouchedFilters] = useState(initialTouchedFields); - const canViewOtherOrgs = canViewOtherOrgRoles.includes(user?.role); const debounceAfter3CharsInputs: FilterFormKey[] = ["name", "dbGaPID"]; const debouncedOnChangeRef = useRef( debounce((form: FilterForm) => handleFormChange(form), 500) @@ -172,7 +168,23 @@ const DataSubmissionListFilters = ({ }, [submitterNames, submitterNameFilter, touchedFilters]); useEffect(() => { - if (!activeOrganizations?.length) { + // Reset organization filter if it is no longer a valid option + // due to other filters changing + const organizationIds = organizations?.map((org) => org._id); + if ( + orgFilter !== "All" && + Object.values(touchedFilters).some((filter) => filter) && + !organizationIds?.includes(orgFilter) + ) { + const newSearchParams = new URLSearchParams(searchParams); + newSearchParams.delete("organization"); + setSearchParams(newSearchParams); + setValue("organization", "All"); + } + }, [organizations, orgFilter, touchedFilters]); + + useEffect(() => { + if (!organizations?.length) { return; } @@ -202,13 +214,7 @@ const DataSubmissionListFilters = ({ if (Object.values(touchedFilters).every((filter) => !filter)) { handleFormChange(getValues()); } - }, [ - activeOrganizations, - submitterNames, - dataCommons, - canViewOtherOrgs, - searchParams?.toString(), - ]); + }, [organizations, submitterNames, dataCommons, searchParams?.toString()]); useEffect(() => { if (Object.values(touchedFilters).every((filter) => !filter)) { @@ -217,7 +223,7 @@ const DataSubmissionListFilters = ({ const newSearchParams = new URLSearchParams(searchParams); - if (canViewOtherOrgs && orgFilter && orgFilter !== "All") { + if (orgFilter && orgFilter !== "All") { newSearchParams.set("organization", orgFilter); } else if (orgFilter === "All") { newSearchParams.delete("organization"); @@ -293,8 +299,7 @@ const DataSubmissionListFilters = ({ }; }, [watch, debouncedOnChangeRef]); - const isValidOrg = (orgId: string) => - orgId && !!activeOrganizations?.find((org) => org._id === orgId); + const isValidOrg = (orgId: string) => orgId && !!organizations?.find((org) => org._id === orgId); const isStatusFilterOption = (status: string): status is FilterForm["status"] => ["All", ...statusValues].includes(status); @@ -304,13 +309,7 @@ const DataSubmissionListFilters = ({ return; } - if ( - !canViewOtherOrgs && - isValidOrg(user?.organization?.orgID) && - user?.organization?.orgID !== orgFilter - ) { - setValue("organization", user?.organization?.orgID); - } else if (canViewOtherOrgs && isValidOrg(organizationId)) { + if (isValidOrg(organizationId)) { setValue("organization", organizationId); } }; @@ -352,10 +351,7 @@ const DataSubmissionListFilters = ({ searchParams.delete("dbGaPID"); searchParams.delete("submitterName"); setSearchParams(newSearchParams); - reset({ - ...defaultValues, - organization: canViewOtherOrgs ? "All" : user?.organization?.orgID, - }); + reset({ ...defaultValues }); }; return ( @@ -371,14 +367,17 @@ const DataSubmissionListFilters = ({ render={({ field }) => ( org._id)?.includes(field.value) + ? field.value + : "All" + } MenuProps={{ disablePortal: true }} inputProps={{ id: "organization-filter", "data-testid": "organization-select-input", }} data-testid="organization-select" - readOnly={!canViewOtherOrgs} onChange={(e) => { field.onChange(e); handleFilterChange("organization"); @@ -387,7 +386,7 @@ const DataSubmissionListFilters = ({ All - {activeOrganizations?.map((org) => ( + {organizations?.map((org) => ( { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [data, setData] = useState([]); + const [organizations, setOrganizations] = useState([]); const [submitterNames, setSubmitterNames] = useState([]); const [dataCommons, setDataCommons] = useState([]); const [totalData, setTotalData] = useState(0); @@ -304,6 +305,7 @@ const ListingView: FC = () => { } setData(d.listSubmissions.submissions); + setOrganizations(d.listSubmissions.organizations?.filter((org) => !!org.name.trim())); setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim())); setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim())); setTotalData(d.listSubmissions.total); @@ -376,6 +378,7 @@ const ListingView: FC = () => { Date: Thu, 31 Oct 2024 10:15:07 -0400 Subject: [PATCH 03/16] Update tests for filters --- .../DataSubmissionListFilters.test.tsx | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx index a1e859c8..7cb09b8d 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx @@ -10,7 +10,6 @@ import { ContextState as AuthContextState, Status as AuthContextStatus, } from "../Contexts/AuthContext"; -import { useOrganizationListContext } from "../Contexts/OrganizationListContext"; import { Column } from "../GenericTable"; import { ListSubmissionsResp } from "../../graphql"; @@ -82,28 +81,15 @@ describe("DataSubmissionListFilters Component", () => { const submitterNames = ["Submitter1", "Submitter2"]; const dataCommons = ["DataCommon1", "DataCommon2"]; + const organizations: Organization[] = [ + { _id: "Org1", name: "Organization 1" } as Organization, + { _id: "Org2", name: "Organization 2" } as Organization, + ]; const columnVisibilityModel = { name: true, status: true }; const mockOnChange = jest.fn(); const mockOnColumnVisibilityModelChange = jest.fn(); - beforeEach(() => { - // Set up default mock for useOrganizationListContext - (useOrganizationListContext as jest.Mock).mockReturnValue({ - activeOrganizations: [ - { _id: "Org1", name: "Organization 1" }, - { _id: "Org2", name: "Organization 2" }, - ], - status: "LOADED", - data: { - activeOrganizations: [ - { _id: "Org1", name: "Organization 1" }, - { _id: "Org2", name: "Organization 2" }, - ], - }, - }); - }); - afterEach(() => { jest.clearAllMocks(); jest.useRealTimers(); @@ -114,6 +100,7 @@ describe("DataSubmissionListFilters Component", () => { { { { { }); }); - it("prevents non-admin users from changing the organization select", async () => { + it("should allow non-admin users from changing the organization select", async () => { const { getByTestId } = render( { const organizationSelectInput = getByTestId("organization-select"); const button = within(organizationSelectInput).getByRole("button"); - expect(button).toHaveClass("Mui-readOnly"); + expect(button).not.toHaveClass("Mui-readOnly"); }); it("resets all filters and clears URL searchParams when reset button is clicked", async () => { @@ -250,6 +241,7 @@ describe("DataSubmissionListFilters Component", () => { { { { { { { { { { { { { { { Date: Thu, 31 Oct 2024 10:24:50 -0400 Subject: [PATCH 04/16] Add additional tests --- .../DataSubmissionListFilters.test.tsx | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx index 7cb09b8d..89d27cfa 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx @@ -211,7 +211,7 @@ describe("DataSubmissionListFilters Component", () => { }); }); - it("should allow non-admin users from changing the organization select", async () => { + it("allows non-admin users to select an organization", async () => { const { getByTestId } = render( { }) ); }); + + it("sets organization select to 'All' when organizations prop is empty", async () => { + const mockOnChange = jest.fn(); + const mockOnColumnVisibilityModelChange = jest.fn(); + + const { getByTestId, getByRole } = render( + + + + ); + + await waitFor(() => { + expect(getByTestId("organization-select-input")).toHaveValue("All"); + }); + + const organizationSelect = within(getByTestId("organization-select")).getByRole("button"); + userEvent.click(organizationSelect); + + const organizationList = within(getByRole("listbox", { hidden: true })); + + await waitFor(() => { + expect(organizationList.getByTestId("organization-option-All")).toBeInTheDocument(); + expect(organizationList.queryByTestId("organization-option-Org1")).not.toBeInTheDocument(); + expect(organizationList.queryByTestId("organization-option-Org2")).not.toBeInTheDocument(); + }); + }); + + it("sets organization select to field.value when organizations prop is non-empty", async () => { + const mockOnChange = jest.fn(); + const mockOnColumnVisibilityModelChange = jest.fn(); + + const { getByTestId, getByRole } = render( + + + + ); + + await waitFor(() => { + expect(getByTestId("organization-select-input")).toHaveValue("All"); + }); + + const organizationSelect = within(getByTestId("organization-select")).getByRole("button"); + userEvent.click(organizationSelect); + + const organizationList = within(getByRole("listbox", { hidden: true })); + + await waitFor(() => { + expect(organizationList.getByTestId("organization-option-Org1")).toBeInTheDocument(); + expect(organizationList.getByTestId("organization-option-Org2")).toBeInTheDocument(); + }); + + userEvent.click(getByTestId("organization-option-Org1")); + + await waitFor(() => { + expect(getByTestId("organization-select-input")).toHaveValue("Org1"); + }); + + expect(mockOnChange).toHaveBeenCalledWith( + expect.objectContaining({ + organization: "Org1", + }) + ); + }); }); From 607acf459976cd6b441101ab0bb060c0f088bd54 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Mon, 4 Nov 2024 12:12:29 -0500 Subject: [PATCH 05/16] Sort orgs by name --- src/content/dataSubmissions/DataSubmissionsListView.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index 7c3c29ad..4287109f 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -305,7 +305,11 @@ const ListingView: FC = () => { } setData(d.listSubmissions.submissions); - setOrganizations(d.listSubmissions.organizations?.filter((org) => !!org.name.trim())); + setOrganizations( + d.listSubmissions.organizations + ?.filter((org) => !!org.name.trim()) + ?.sort((a, b) => a.name?.localeCompare(b.name)) + ); setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim())); setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim())); setTotalData(d.listSubmissions.total); From 5698b8644219a303a7b4d4d8575d87e7a57c7cd3 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Mon, 4 Nov 2024 12:52:22 -0500 Subject: [PATCH 06/16] Remove any mention of orgs from Data Submission List page --- .../DataSubmissions/DataSubmissionListFilters.tsx | 4 ---- src/content/dataSubmissions/Controller.tsx | 15 +++++---------- .../dataSubmissions/DataSubmissionsListView.tsx | 15 ++------------- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.tsx index 1fabb873..0ce6b137 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.tsx @@ -184,10 +184,6 @@ const DataSubmissionListFilters = ({ }, [organizations, orgFilter, touchedFilters]); useEffect(() => { - if (!organizations?.length) { - return; - } - const organizationId = searchParams.get("organization"); const status = searchParams.get("status"); const dataCommon = searchParams.get("dataCommons"); diff --git a/src/content/dataSubmissions/Controller.tsx b/src/content/dataSubmissions/Controller.tsx index e592fc4d..3a7459ba 100644 --- a/src/content/dataSubmissions/Controller.tsx +++ b/src/content/dataSubmissions/Controller.tsx @@ -2,15 +2,14 @@ import React, { memo } from "react"; import { useParams } from "react-router-dom"; import DataSubmission from "./DataSubmission"; import ListView from "./DataSubmissionsListView"; -import { OrganizationProvider } from "../../components/Contexts/OrganizationListContext"; import { SubmissionProvider } from "../../components/Contexts/SubmissionContext"; /** - * A memoized version of OrganizationProvider + * A memoized version of SubmissionProvider * * @see OrganizationProvider */ -const MemorizedProvider = memo(OrganizationProvider); +const MemorizedProvider = memo(SubmissionProvider); /** * Render the correct view based on the URL @@ -23,17 +22,13 @@ const DataSubmissionController = () => { if (submissionId) { return ( - + - + ); } - return ( - - - - ); + return ; }; export default DataSubmissionController; diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index 4287109f..c97f6544 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -10,10 +10,6 @@ import { FormatDate } from "../../utils"; import { useAuthContext, Status as AuthStatus } from "../../components/Contexts/AuthContext"; import usePageTitle from "../../hooks/usePageTitle"; import CreateDataSubmissionDialog from "../../components/DataSubmissions/CreateDataSubmissionDialog"; -import { - Status as OrgStatus, - useOrganizationListContext, -} from "../../components/Contexts/OrganizationListContext"; import GenericTable, { Column } from "../../components/GenericTable"; import { LIST_SUBMISSIONS, ListSubmissionsInput, ListSubmissionsResp } from "../../graphql"; import TruncatedText from "../../components/TruncatedText"; @@ -230,7 +226,6 @@ const ListingView: FC = () => { const { state } = useLocation(); const { status: authStatus } = useAuthContext(); const { enqueueSnackbar } = useSnackbar(); - const { status: orgStatus, activeOrganizations } = useOrganizationListContext(); // Only org owners/submitters with organizations assigned can create data submissions const { columnVisibilityModel, setColumnVisibilityModel, visibleColumns } = useColumnVisibility< @@ -271,7 +266,7 @@ const ListingView: FC = () => { try { setLoading(true); - if (!activeOrganizations?.length || !filtersRef.current) { + if (!filtersRef.current) { return; } @@ -325,10 +320,6 @@ const ListingView: FC = () => { }; const handleOnCreateSubmission = async () => { - if (!activeOrganizations?.length) { - return; - } - try { setLoading(true); @@ -395,9 +386,7 @@ const ListingView: FC = () => { columns={visibleColumns} data={data || []} total={totalData || 0} - loading={ - loading || orgStatus === OrgStatus.LOADING || authStatus === AuthStatus.LOADING - } + loading={loading || authStatus === AuthStatus.LOADING} defaultRowsPerPage={20} defaultOrder="desc" disableUrlParams={false} From 26265adc0863e3b4b1b037801843e61b83f413f7 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Mon, 4 Nov 2024 14:27:06 -0500 Subject: [PATCH 07/16] Fix collaborators property height --- src/components/DataSubmissions/SubmissionHeaderProperty.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx index af4cf999..9b09fcc2 100644 --- a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx +++ b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx @@ -32,7 +32,7 @@ const SubmissionHeaderProperty = ({ label, value, truncateAfter = 16 }: Props) = {label} - + {typeof value === "string" ? ( {truncateAfter && truncateAfter > 0 ? ( @@ -41,6 +41,7 @@ const SubmissionHeaderProperty = ({ label, value, truncateAfter = 16 }: Props) = maxCharacters={truncateAfter} underline={false} ellipsis + wrapperStyles={{ lineHeight: "19.6px" }} /> ) : ( value From 8b6a6d9f7311f673ac66c36c1f24e4eee0a995d6 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 12:02:09 -0500 Subject: [PATCH 08/16] Fixed resetting table page and filters race condition when page is not the first page --- .../DataSubmissionListFilters.tsx | 16 +++------------- .../dataSubmissions/DataSubmissionsListView.tsx | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.tsx index 0ce6b137..dbfc7aca 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.tsx @@ -192,8 +192,10 @@ const DataSubmissionListFilters = ({ const submitterName = searchParams.get("submitterName"); handleStatusChange(status); - handleOrganizationChange(organizationId); + if (organizationId && organizationId !== orgFilter) { + setValue("organization", organizationId); + } if (dataCommon && dataCommon !== dataCommonsFilter) { setValue("dataCommons", dataCommon); } @@ -295,21 +297,9 @@ const DataSubmissionListFilters = ({ }; }, [watch, debouncedOnChangeRef]); - const isValidOrg = (orgId: string) => orgId && !!organizations?.find((org) => org._id === orgId); - const isStatusFilterOption = (status: string): status is FilterForm["status"] => ["All", ...statusValues].includes(status); - const handleOrganizationChange = (organizationId: string) => { - if (organizationId === orgFilter) { - return; - } - - if (isValidOrg(organizationId)) { - setValue("organization", organizationId); - } - }; - const handleStatusChange = (status: string) => { if (status === statusFilter) { return; diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index c97f6544..f334a54c 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -1,4 +1,4 @@ -import React, { FC, useRef, useState } from "react"; +import React, { FC, useEffect, useRef, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { Alert, Container, Stack, styled, TableCell, TableHead, Box } from "@mui/material"; import { isEqual } from "lodash"; @@ -252,6 +252,7 @@ const ListingView: FC = () => { dbGaPID: "", submitterName: "All", }); + const previousFilters = useRef(filtersRef.current); const [listSubmissions, { refetch }] = useLazyQuery( LIST_SUBMISSIONS, @@ -340,13 +341,23 @@ const ListingView: FC = () => { }); }; + useEffect(() => { + if (isEqual(filtersRef.current, previousFilters.current)) { + return; + } + + // Necessary to avoid race-condition while filters are resetting + // and current page is not the first page + previousFilters.current = { ...filtersRef.current }; + setTablePage(0); + }, [filtersRef.current]); + const handleOnFiltersChange = (data: FilterForm) => { if (isEqual(data, filtersRef.current)) { return; } filtersRef.current = { ...data }; - setTablePage(0); }; return ( From 2d9e417e000d3f001c3af0b185e97f3d3249509c Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 12:28:40 -0500 Subject: [PATCH 09/16] Remove useEffect and replace with linear logic --- .../dataSubmissions/DataSubmissionsListView.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index f334a54c..65fcd6e8 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -341,7 +341,13 @@ const ListingView: FC = () => { }); }; - useEffect(() => { + const handleOnFiltersChange = (data: FilterForm) => { + if (isEqual(data, filtersRef.current)) { + return; + } + + filtersRef.current = { ...data }; + if (isEqual(filtersRef.current, previousFilters.current)) { return; } @@ -350,14 +356,6 @@ const ListingView: FC = () => { // and current page is not the first page previousFilters.current = { ...filtersRef.current }; setTablePage(0); - }, [filtersRef.current]); - - const handleOnFiltersChange = (data: FilterForm) => { - if (isEqual(data, filtersRef.current)) { - return; - } - - filtersRef.current = { ...data }; }; return ( From cbbfa1df2f148937994ad00540ffc36a9c882717 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 12:30:42 -0500 Subject: [PATCH 10/16] Remove unused import --- src/content/dataSubmissions/DataSubmissionsListView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index 65fcd6e8..56a6da27 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useRef, useState } from "react"; +import React, { FC, useRef, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { Alert, Container, Stack, styled, TableCell, TableHead, Box } from "@mui/material"; import { isEqual } from "lodash"; From ad318979e32271c59cea1b61148151cfeee43067 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 12:36:56 -0500 Subject: [PATCH 11/16] Update additional state on refetch after creating a new submission --- src/content/dataSubmissions/DataSubmissionsListView.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index 56a6da27..ce83850d 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -329,6 +329,13 @@ const ListingView: FC = () => { throw new Error("Unable to retrieve Data Submission List results."); } setData(d.listSubmissions.submissions); + setOrganizations( + d.listSubmissions.organizations + ?.filter((org) => !!org.name.trim()) + ?.sort((a, b) => a.name?.localeCompare(b.name)) + ); + setSubmitterNames(d.listSubmissions.submitterNames?.filter((sn) => !!sn.trim())); + setDataCommons(d.listSubmissions.dataCommons?.filter((dc) => !!dc.trim())); setTotalData(d.listSubmissions.total); } catch (err) { setError(true); From 2eb7c136b83a2457079fcad21caf4e65516ad6ee Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 14:33:13 -0500 Subject: [PATCH 12/16] Undo previous change, didn't fix bug --- .../dataSubmissions/DataSubmissionsListView.tsx | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index ce83850d..92674415 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -1,7 +1,7 @@ -import React, { FC, useRef, useState } from "react"; +import React, { FC, useEffect, useRef, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { Alert, Container, Stack, styled, TableCell, TableHead, Box } from "@mui/material"; -import { isEqual } from "lodash"; +import { debounce, isEqual } from "lodash"; import { useSnackbar } from "notistack"; import { useLazyQuery } from "@apollo/client"; import bannerSvg from "../../assets/banner/submission_banner.png"; @@ -252,7 +252,6 @@ const ListingView: FC = () => { dbGaPID: "", submitterName: "All", }); - const previousFilters = useRef(filtersRef.current); const [listSubmissions, { refetch }] = useLazyQuery( LIST_SUBMISSIONS, @@ -354,14 +353,6 @@ const ListingView: FC = () => { } filtersRef.current = { ...data }; - - if (isEqual(filtersRef.current, previousFilters.current)) { - return; - } - - // Necessary to avoid race-condition while filters are resetting - // and current page is not the first page - previousFilters.current = { ...filtersRef.current }; setTablePage(0); }; From d32e03b79c051244543729163ac4dcffc5a9c98a Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 14:33:59 -0500 Subject: [PATCH 13/16] Remove unused imports --- src/content/dataSubmissions/DataSubmissionsListView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index 92674415..d8e9b713 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -1,7 +1,7 @@ -import React, { FC, useEffect, useRef, useState } from "react"; +import React, { FC, useRef, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { Alert, Container, Stack, styled, TableCell, TableHead, Box } from "@mui/material"; -import { debounce, isEqual } from "lodash"; +import { isEqual } from "lodash"; import { useSnackbar } from "notistack"; import { useLazyQuery } from "@apollo/client"; import bannerSvg from "../../assets/banner/submission_banner.png"; From efdb01f7666f014d89ebdd7068164eb67a2817c2 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 14:40:25 -0500 Subject: [PATCH 14/16] Bandage fix for debouncing specific dropdowns --- .../DataSubmissions/DataSubmissionListFilters.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.tsx index dbfc7aca..dbc85802 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.tsx @@ -151,6 +151,9 @@ const DataSubmissionListFilters = ({ const debouncedOnChangeRef = useRef( debounce((form: FilterForm) => handleFormChange(form), 500) ).current; + const debouncedDropdownRef = useRef( + debounce((form: FilterForm) => handleFormChange(form), 0) + ).current; useEffect(() => { // Reset submitterName filter if it is no longer a valid option @@ -268,6 +271,12 @@ const DataSubmissionListFilters = ({ useEffect(() => { const subscription = watch((formValue: FilterForm, { name }) => { + const isDebouncedDropdown = ["submitterName", "dataCommons", "organization"].includes(name); + if (isDebouncedDropdown) { + debouncedDropdownRef(formValue); + return; + } + // Add debounce for input fields const isDebounceField = debounceAfter3CharsInputs.includes(name as FilterFormKey); // Debounce if value has at least 3 characters From b12f2ba8d02d4c29c1a0689f302596af722fe2a6 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 14:53:50 -0500 Subject: [PATCH 15/16] Add waitFor to tests --- .../DataSubmissionListFilters.test.tsx | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx index 89d27cfa..a86a8aa2 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.test.tsx @@ -813,13 +813,12 @@ describe("DataSubmissionListFilters Component", () => { await waitFor(() => { expect(getByTestId("data-commons-select-input")).toHaveValue("DataCommon1"); + expect(mockOnChange).toHaveBeenCalledWith( + expect.objectContaining({ + dataCommons: "DataCommon1", + }) + ); }); - - expect(mockOnChange).toHaveBeenCalledWith( - expect.objectContaining({ - dataCommons: "DataCommon1", - }) - ); }); it("sets submitterNames select to 'All' when submitterNames prop is empty", async () => { @@ -896,13 +895,12 @@ describe("DataSubmissionListFilters Component", () => { await waitFor(() => { expect(getByTestId("submitter-name-select-input")).toHaveValue("Submitter1"); + expect(mockOnChange).toHaveBeenCalledWith( + expect.objectContaining({ + submitterName: "Submitter1", + }) + ); }); - - expect(mockOnChange).toHaveBeenCalledWith( - expect.objectContaining({ - submitterName: "Submitter1", - }) - ); }); it("sets organization select to 'All' when organizations prop is empty", async () => { @@ -975,12 +973,11 @@ describe("DataSubmissionListFilters Component", () => { await waitFor(() => { expect(getByTestId("organization-select-input")).toHaveValue("Org1"); + expect(mockOnChange).toHaveBeenCalledWith( + expect.objectContaining({ + organization: "Org1", + }) + ); }); - - expect(mockOnChange).toHaveBeenCalledWith( - expect.objectContaining({ - organization: "Org1", - }) - ); }); }); From 99a458b2d8e1a512ce26fe037fc351052e1e76e4 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 5 Nov 2024 17:02:20 -0500 Subject: [PATCH 16/16] Update org type --- src/components/DataSubmissions/DataSubmissionListFilters.tsx | 2 +- src/content/dataSubmissions/DataSubmissionsListView.tsx | 2 +- src/graphql/listSubmissions.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionListFilters.tsx b/src/components/DataSubmissions/DataSubmissionListFilters.tsx index dbc85802..fbaa9e4a 100644 --- a/src/components/DataSubmissions/DataSubmissionListFilters.tsx +++ b/src/components/DataSubmissions/DataSubmissionListFilters.tsx @@ -115,7 +115,7 @@ type TouchedState = { [K in FilterFormKey]: boolean }; type Props = { columns: Column[]; - organizations: Organization[]; + organizations: Pick[]; submitterNames: string[]; dataCommons: string[]; columnVisibilityModel: ColumnVisibilityModel; diff --git a/src/content/dataSubmissions/DataSubmissionsListView.tsx b/src/content/dataSubmissions/DataSubmissionsListView.tsx index d8e9b713..006989d0 100644 --- a/src/content/dataSubmissions/DataSubmissionsListView.tsx +++ b/src/content/dataSubmissions/DataSubmissionsListView.tsx @@ -239,7 +239,7 @@ const ListingView: FC = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [data, setData] = useState([]); - const [organizations, setOrganizations] = useState([]); + const [organizations, setOrganizations] = useState[]>([]); const [submitterNames, setSubmitterNames] = useState([]); const [dataCommons, setDataCommons] = useState([]); const [totalData, setTotalData] = useState(0); diff --git a/src/graphql/listSubmissions.ts b/src/graphql/listSubmissions.ts index 9ab8af3b..67fca56e 100644 --- a/src/graphql/listSubmissions.ts +++ b/src/graphql/listSubmissions.ts @@ -73,7 +73,7 @@ export type Response = { listSubmissions: { total: number; submissions: Omit[]; - organizations: Organization[]; + organizations: Pick[]; submitterNames: string[]; dataCommons: string[]; };