From 33f292e53b46aac637fa70d6df323f651a814310 Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Thu, 18 Jan 2024 11:44:14 -0500 Subject: [PATCH] DEVPROD-1977: Update tests table to use LeafyGreen (#2213) --- cypress/integration/task/test_table.ts | 50 ++-- cypress/utils/index.ts | 4 +- src/analytics/task/useTaskAnalytics.ts | 8 +- src/components/Table/BaseTable.tsx | 6 +- src/constants/routes.ts | 2 +- src/hooks/useTableSort.ts | 4 +- src/pages/Task.tsx | 13 - src/pages/task/TaskTabs.tsx | 38 ++- src/pages/task/taskTabs/TestsTable.tsx | 229 +++++++++++------- .../testsTable/getColumnsTemplate.tsx | 128 ++++------ src/types/task.ts | 14 ++ 11 files changed, 266 insertions(+), 230 deletions(-) diff --git a/cypress/integration/task/test_table.ts b/cypress/integration/task/test_table.ts index 0488dd733a..3589f9fa91 100644 --- a/cypress/integration/task/test_table.ts +++ b/cypress/integration/task/test_table.ts @@ -11,62 +11,65 @@ describe("Tests Table", () => { .should("be.visible") .should("not.have.attr", "data-loading", "true"); }; + beforeEach(() => { visitAndWait(TESTS_ROUTE); }); + it("Test count should update to reflect filtered values", () => { - cy.contains(TABLE_SORT_SELECTOR, "Name").click(); + const nameSortControl = "button[aria-label='Sort by Name']"; + cy.get(nameSortControl).click(); cy.dataCy("filtered-count").contains(20); cy.dataCy("total-count").contains(20); - cy.toggleTableFilter(2); - - cy.get(".cy-checkbox").contains("Fail").click({ force: true }); + cy.dataCy("status-treeselect").click(); + cy.getInputByLabel("Fail").check({ force: true }); cy.dataCy("filtered-count").contains(1); cy.dataCy("total-count").contains(20); - cy.toggleTableFilter(1); - cy.dataCy("testname-input-wrapper") - .find("input") - .as("testnameInputWrapper") - .focus(); - cy.get("@testnameInputWrapper").type("hello{enter}"); + cy.dataCy("test-name-filter").click(); + cy.dataCy("test-name-filter-input-filter").type("hello{enter}"); cy.dataCy("filtered-count").contains(0); cy.dataCy("total-count").contains(20); }); it("Adjusts query params when table headers are clicked", () => { - cy.contains(TABLE_SORT_SELECTOR, "Name").click(); + const nameSortControl = "button[aria-label='Sort by Name']"; + const statusSortControl = "button[aria-label='Sort by Status']"; + const durationSortControl = "button[aria-label='Sort by Time']"; + + cy.get(nameSortControl).click(); + cy.location().should((loc) => { expect(loc.pathname).to.equal(TESTS_ROUTE); expect(loc.search).to.include("sortBy=TEST_NAME"); expect(loc.search).to.include(ASCEND_PARAM); }); - cy.contains(TABLE_SORT_SELECTOR, "Status").click(); + cy.get(statusSortControl).click(); cy.location().should((loc) => { expect(loc.pathname).to.equal(TESTS_ROUTE); expect(loc.search).to.include("sortBy=STATUS"); expect(loc.search).to.include(ASCEND_PARAM); }); - cy.contains(TABLE_SORT_SELECTOR, "Status").click(); + cy.get(statusSortControl).click(); cy.location().should((loc) => { expect(loc.pathname).to.equal(TESTS_ROUTE); expect(loc.search).to.include("sortBy=STATUS"); expect(loc.search).to.include(DESCEND_PARAM); }); - cy.contains(TABLE_SORT_SELECTOR, "Time").click(); + cy.get(durationSortControl).click(); cy.location().should((loc) => { expect(loc.pathname).to.equal(TESTS_ROUTE); expect(loc.search).to.include("sortBy=DURATION"); expect(loc.search).to.include(ASCEND_PARAM); }); - cy.contains(TABLE_SORT_SELECTOR, "Time").click(); + cy.get(durationSortControl).click(); cy.location().should((loc) => { expect(loc.pathname).to.equal(TESTS_ROUTE); expect(loc.search).to.include("sortBy=DURATION"); @@ -82,10 +85,10 @@ describe("Tests Table", () => { it("Clicking on 'All' checkbox adds all statuses to URL", () => { clickingCheckboxUpdatesUrlAndRendersFetchedResults({ checkboxDisplayName: "All", - pathname: TESTS_ROUTE, + openFilter: () => cy.dataCy("status-treeselect").click(), paramName: "statuses", + pathname: TESTS_ROUTE, search: "all,pass,fail,skip,silentfail", - openFilter: () => cy.toggleTableFilter(2), }); }); @@ -97,12 +100,14 @@ describe("Tests Table", () => { ]; it("Checking multiple statuses adds them all to the URL", () => { - cy.toggleTableFilter(2); + cy.dataCy("status-treeselect").click(); statuses.forEach(({ display }) => { - cy.get(".cy-checkbox").contains(display).click({ force: true }); + cy.getInputByLabel(display).check({ force: true }); }); cy.location().should((loc) => { - expect(loc.search).to.include("statuses=pass,silentfail,fail,skip,all"); + expect(decodeURIComponent(loc.search)).to.include( + `statuses=${statuses.map(({ key }) => key).join(",")}` + ); }); }); }); @@ -112,8 +117,8 @@ describe("Tests Table", () => { it("Typing in test name filter updates testname query param", () => { visitAndWait(TESTS_ROUTE); - cy.toggleTableFilter(1); - cy.dataCy("testname-input-wrapper") + cy.dataCy("test-name-filter").click(); + cy.dataCy("test-name-filter-wrapper") .find("input") .as("testnameInputWrapper") .focus(); @@ -179,7 +184,6 @@ describe("Tests Table", () => { }); }); -const TABLE_SORT_SELECTOR = ".ant-table-column-sorters"; const DESCEND_PARAM = "sortDir=DESC"; const ASCEND_PARAM = "sortDir=ASC"; const TESTS_ROUTE = diff --git a/cypress/utils/index.ts b/cypress/utils/index.ts index 9482ad0497..5d460de58a 100644 --- a/cypress/utils/index.ts +++ b/cypress/utils/index.ts @@ -22,9 +22,9 @@ export const urlSearchParamsAreUpdated = ({ paramName, pathname, search }) => { cy.location().should((loc) => { expect(loc.pathname).to.equal(pathname); if (search === null) { - expect(loc.search).to.not.include(paramName); + expect(decodeURIComponent(loc.search)).to.not.include(paramName); } else { - expect(loc.search).to.include(search); + expect(decodeURIComponent(loc.search)).to.include(search); } }); }; diff --git a/src/analytics/task/useTaskAnalytics.ts b/src/analytics/task/useTaskAnalytics.ts index 1d78d77f9b..cbeecf6556 100644 --- a/src/analytics/task/useTaskAnalytics.ts +++ b/src/analytics/task/useTaskAnalytics.ts @@ -15,14 +15,10 @@ import { RequiredQueryParams, LogTypes } from "types/task"; type LogViewer = "raw" | "html" | "parsley" | "lobster"; type Action = - | { name: "Filter Tests"; filterBy: string } + | { name: "Filter Tests"; filterBy: string | string[] } | { name: "Sort Tests Table"; - sortBy: - | TestSortCategory.TestName - | TestSortCategory.Status - | TestSortCategory.BaseStatus - | TestSortCategory.Duration; + sortBy: TestSortCategory | TestSortCategory[]; } | { name: "Sort Execution Tasks Table"; diff --git a/src/components/Table/BaseTable.tsx b/src/components/Table/BaseTable.tsx index c9a4a14951..8e8f9b9837 100644 --- a/src/components/Table/BaseTable.tsx +++ b/src/components/Table/BaseTable.tsx @@ -156,6 +156,8 @@ export const BaseTable = forwardRef( }, ); +const cellPaddingStyle = { paddingBottom: size.xxs, paddingTop: size.xxs }; + const RenderableRow = ({ row, virtualRow, @@ -174,7 +176,7 @@ const RenderableRow = ({ virtualRow={virtualRow} > {row.getVisibleCells().map((cell) => ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} @@ -192,7 +194,7 @@ const RenderableRow = ({ virtualRow={virtualRow} > {subRow.getVisibleCells().map((cell) => ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} diff --git a/src/constants/routes.ts b/src/constants/routes.ts index fbd1ab2255..6d9375b038 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -174,7 +174,7 @@ export const getAllHostsRoute = (options?: GetAllHostsRouteOptions) => { return `${paths.hosts}?${queryParams}`; }; -interface GetTaskRouteOptions { +export interface GetTaskRouteOptions { tab?: TaskTab; execution?: number; [key: string]: any; diff --git a/src/hooks/useTableSort.ts b/src/hooks/useTableSort.ts index 02c562ab86..224a32fcd9 100644 --- a/src/hooks/useTableSort.ts +++ b/src/hooks/useTableSort.ts @@ -4,7 +4,7 @@ import { SortDirection } from "gql/generated/types"; import { useQueryParams } from "hooks/useQueryParam"; interface Props { - sendAnalyticsEvents?: () => void; + sendAnalyticsEvents?: (sorter?: SortingState) => void; } type CallbackType = (sorter: SortingState) => void; @@ -19,7 +19,7 @@ export const useTableSort = (props?: Props): CallbackType => { const [queryParams, setQueryParams] = useQueryParams(); const tableChangeHandler = ((sorter: SortingState) => { - props?.sendAnalyticsEvents?.(); + props?.sendAnalyticsEvents?.(sorter); const nextQueryParams = { ...queryParams, diff --git a/src/pages/Task.tsx b/src/pages/Task.tsx index a5699e7235..fc734e11a9 100644 --- a/src/pages/Task.tsx +++ b/src/pages/Task.tsx @@ -1,4 +1,3 @@ -import { useEffect } from "react"; import { useQuery } from "@apollo/client"; import styled from "@emotion/styled"; import { useParams, useLocation } from "react-router-dom"; @@ -68,18 +67,6 @@ export const Task = () => { const attributed = annotation?.issues?.length > 0; const isDisplayTask = executionTasksFull != null; - useEffect(() => { - if ( - id === task?.id && - Number.isNaN(selectedExecution) && - latestExecution !== undefined - ) { - updateQueryParams({ - execution: `${latestExecution}`, - }); - } - }, [id, selectedExecution, latestExecution, updateQueryParams, task?.id]); - if (error) { return ; } diff --git a/src/pages/task/TaskTabs.tsx b/src/pages/task/TaskTabs.tsx index e0e6d886a5..5196470aa1 100644 --- a/src/pages/task/TaskTabs.tsx +++ b/src/pages/task/TaskTabs.tsx @@ -5,7 +5,7 @@ import { useTaskAnalytics } from "analytics"; import { TrendChartsPlugin } from "components/PerfPlugin"; import { StyledTabs } from "components/styles/StyledTabs"; import { TabLabelWithBadge } from "components/TabLabelWithBadge"; -import { getTaskRoute } from "constants/routes"; +import { getTaskRoute, GetTaskRouteOptions } from "constants/routes"; import { TaskQuery } from "gql/generated/types"; import { usePrevious } from "hooks"; import { useTabShortcut } from "hooks/useTabShortcut"; @@ -175,19 +175,33 @@ export const TaskTabs: React.FC = ({ isDisplayTask, task }) => { }); useEffect(() => { - const query = parseQueryString(location.search); - const newRoute = getTaskRoute(id, { - tab: activeTabs[selectedTab], - ...query, - }); - navigate(newRoute, { replace: true }); - if (previousTab !== undefined && previousTab !== selectedTab) { - taskAnalytics.sendEvent({ - name: "Change Tab", + if (previousTab !== selectedTab) { + const query = parseQueryString(location.search); + const params: GetTaskRouteOptions = { tab: activeTabs[selectedTab], - }); + ...query, + }; + + // Introduce execution query parameter if none is set. + if ( + id === task?.id && + query.execution === undefined && + task.latestExecution !== undefined + ) { + params.execution = task.latestExecution; + } + + const newRoute = getTaskRoute(id, params); + navigate(newRoute, { replace: true }); + + if (previousTab !== undefined) { + taskAnalytics.sendEvent({ + name: "Change Tab", + tab: activeTabs[selectedTab], + }); + } } - }, [selectedTab, execution]); // eslint-disable-line react-hooks/exhaustive-deps + }, [selectedTab]); // eslint-disable-line react-hooks/exhaustive-deps return ( = ({ task }) => { const { pathname, search } = useLocation(); const updateQueryParams = useUpdateURLQueryParams(); - const taskAnalytics = useTaskAnalytics(); - const sendFilterTestsEvent = (filterBy: string) => - taskAnalytics.sendEvent({ name: "Filter Tests", filterBy }); + const { sendEvent } = useTaskAnalytics(); const queryVariables = getQueryVariables(search, task.id); - const { limitNum, pageNum, sort } = queryVariables; - const cat = sort?.[0]?.sortBy; - const dir = sort?.[0]?.direction; + const [queryParams, setQueryParams] = useQueryParams(); + const { execution, limitNum, pageNum, sort } = queryVariables; + const sortBy = sort?.[0]?.sortBy; const appliedDefaultSort = useRef(null); useEffect(() => { + // Avoid race condition where this hook overwrites TaskTabs setting a default execution. + if (execution == null) { + return; + } + if ( - cat === undefined && + sortBy === undefined && updateQueryParams && appliedDefaultSort.current !== pathname ) { appliedDefaultSort.current = pathname; updateQueryParams({ - [RequiredQueryParams.Category]: TestSortCategory.Status, - [RequiredQueryParams.Sort]: SortDirection.Asc, + [TableQueryParams.SortBy]: TestSortCategory.Status, + [TableQueryParams.SortDir]: SortDirection.Asc, }); } }, [pathname, updateQueryParams]); // eslint-disable-line react-hooks/exhaustive-deps - const statusesFilter = useStatusesFilter({ - urlParam: RequiredQueryParams.Statuses, - resetPage: false, - sendAnalyticsEvent: sendFilterTestsEvent, - }); - - const statusSelectorProps = { - state: statusesFilter.inputValue, - tData: testStatusesFilterTreeData, - onChange: statusesFilter.setAndSubmitInputValue, - }; - - const testNameFilterInputChangeHandler = useFilterInputChangeHandler({ - urlParam: RequiredQueryParams.TestName, - resetPage: true, - sendAnalyticsEvent: sendFilterTestsEvent, - }); - - const testNameInputProps = { - "data-cy": "testname-input", - placeholder: "Test name regex", - value: testNameFilterInputChangeHandler.inputValue, - onChange: ({ target }) => - testNameFilterInputChangeHandler.setInputValue(target.value), - onFilter: testNameFilterInputChangeHandler.submitInputValue, - }; - - const columns = getColumnsTemplate({ - onColumnHeaderClick: (sortField) => - taskAnalytics.sendEvent({ name: "Sort Tests Table", sortBy: sortField }), - statusSelectorProps, - testNameInputProps, - task, - }).map((column) => ({ - ...column, - ...(column.key === cat && { - sortOrder: (dir === SortDirection.Asc - ? "ascend" - : "descend") as SortOrder, - }), - })); - const { data, loading, refetch, startPolling, stopPolling } = useQuery< TaskTestsQuery, TaskTestsQueryVariables @@ -113,39 +83,81 @@ export const TestsTable: React.FC = ({ task }) => { }); usePolling({ startPolling, stopPolling, refetch }); - // update url query params when user event triggers change - const tableChangeHandler: TableOnChange = (...[, , sorter]) => { - const { columnKey, order } = Array.isArray(sorter) ? sorter[0] : sorter; - let queryParams = { - [RequiredQueryParams.Category]: undefined, - [RequiredQueryParams.Sort]: undefined, - [RequiredQueryParams.Page]: "0", - }; - if (order !== undefined) { - queryParams = { - ...queryParams, - [RequiredQueryParams.Category]: `${columnKey}`, - [RequiredQueryParams.Sort]: - order === "ascend" ? SortDirection.Asc : SortDirection.Desc, - }; - } - updateQueryParams(queryParams); + const clearQueryParams = () => { + table.resetColumnFilters(true); }; - const clearQueryParams = () => { - updateQueryParams({ - [RequiredQueryParams.Category]: undefined, - [RequiredQueryParams.Sort]: undefined, - [RequiredQueryParams.Page]: "0", - [RequiredQueryParams.TestName]: undefined, - [RequiredQueryParams.Statuses]: undefined, + const updateFilters = (filterState: ColumnFiltersState) => { + const updatedParams = { + ...queryParams, + page: "0", + ...emptyFilterQueryParams, + }; + + filterState.forEach(({ id, value }) => { + const key = mapIdToFilterParam[id]; + updatedParams[key] = value; }); + + setQueryParams(updatedParams); + sendEvent({ name: "Filter Tests", filterBy: Object.keys(filterState) }); }; + const tableSortHandler = useTableSort({ + sendAnalyticsEvents: (sorter: SortingState) => + sendEvent({ + name: "Sort Tests Table", + sortBy: sorter.map(({ id }) => id as TestSortCategory), + }), + }); + const { task: taskData } = data ?? {}; const { tests } = taskData ?? {}; const { filteredTestCount, testResults, totalTestCount } = tests ?? {}; + const { initialFilters, initialSorting } = useMemo( + () => getInitialState(queryParams), + [] // eslint-disable-line react-hooks/exhaustive-deps + ); + + const setSorting = (s: SortingState) => + getDefaultSorting(table).onSortingChange(s); + + const setFilters = (f: ColumnFiltersState) => + getDefaultFiltering(table).onColumnFiltersChange(f); + + const columns = useMemo(() => getColumnsTemplate({ task }), [task]); + + const tableContainerRef = useRef(null); + const table = useLeafyGreenTable({ + columns, + containerRef: tableContainerRef, + data: testResults ?? [], + defaultColumn: { + enableColumnFilter: false, + enableSorting: false, + size: "auto" as unknown as number, + // Handle bug in sorting order + // https://github.com/TanStack/table/issues/4289 + sortDescFirst: false, + }, + initialState: { + columnFilters: initialFilters, + sorting: initialSorting, + }, + manualFiltering: true, + manualSorting: true, + manualPagination: true, + onColumnFiltersChange: onChangeHandler( + setFilters, + updateFilters + ), + onSortingChange: onChangeHandler( + setSorting, + tableSortHandler + ), + }); + return ( = ({ task }) => { label="tests" onClear={clearQueryParams} onPageSizeChange={() => { - taskAnalytics.sendEvent({ name: "Change Page Size" }); + sendEvent({ name: "Change Page Size" }); }} /> } shouldShowBottomTableControl={filteredTestCount > 10} > - trigger} - onChange={tableChangeHandler} + data-loading={loading} loading={loading} + loadingRows={limitNum} + shouldAlternateRowColor + table={table} /> ); }; -export const rowKey = ({ id }: { id: string }): string => id; +const emptyFilterQueryParams = { + [RequiredQueryParams.TestName]: undefined, + [RequiredQueryParams.Statuses]: undefined, +}; + +const getInitialState = (queryParams: { + [key: string]: any; +}): { + initialFilters: ColumnFiltersState; + initialSorting: SortingState; +} => { + const { + [TableQueryParams.SortBy]: sortBy, + [TableQueryParams.SortDir]: sortDir, + } = queryParams; + + return { + initialSorting: + sortBy && sortDir + ? [{ id: sortBy, desc: sortDir === SortDirection.Desc }] + : [{ id: TestSortCategory.Status, desc: false }], + initialFilters: Object.entries(mapFilterParamToId).reduce( + (accum, [param, id]) => { + if (queryParams[param]?.length) { + return [...accum, { id, value: queryParams[param] }]; + } + return accum; + }, + [] + ), + }; +}; const getQueryVariables = ( search: string, diff --git a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx index db6236a42f..6b0972a222 100644 --- a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx +++ b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx @@ -1,12 +1,6 @@ -import { ColumnProps } from "antd/es/table"; import { WordBreak } from "components/styles"; -import { - InputFilterProps, - getColumnSearchFilterProps, - getColumnTreeSelectFilterProps, -} from "components/Table/Filters"; -import { TreeSelectProps } from "components/TreeSelect"; -import { TestSortCategory, TestResult, TaskQuery } from "gql/generated/types"; +import { testStatusesFilterTreeData } from "constants/test"; +import { TestSortCategory, TaskQuery } from "gql/generated/types"; import { string } from "utils"; import { LogsColumn } from "./LogsColumn"; import { TestStatusBadge } from "./TestStatusBadge"; @@ -14,89 +8,73 @@ import { TestStatusBadge } from "./TestStatusBadge"; const { msToDuration } = string; interface GetColumnsTemplateParams { - onColumnHeaderClick?: (sortField) => void; - statusSelectorProps: TreeSelectProps; - testNameInputProps: InputFilterProps; task: TaskQuery["task"]; } -export const getColumnsTemplate = ({ - onColumnHeaderClick = () => undefined, - statusSelectorProps, - task, - testNameInputProps, -}: GetColumnsTemplateParams): ColumnProps[] => [ +export const getColumnsTemplate = ({ task }: GetColumnsTemplateParams) => [ { - title: Name, - dataIndex: "testFile", - key: TestSortCategory.TestName, - onHeaderCell: () => ({ - onClick: () => { - onColumnHeaderClick(TestSortCategory.TestName); + header: "Name", + accessorKey: "testFile", + id: TestSortCategory.TestName, + cell: ({ getValue }) => {getValue()}, + enableColumnFilter: true, + enableSorting: true, + meta: { + search: { + "data-cy": "test-name-filter", + placeholder: "Test name regex", }, - }), - width: "40%", - render: (testFile) => {testFile}, - sorter: true, - ...getColumnSearchFilterProps(testNameInputProps), + width: "50%", + }, }, { - title: Status, - dataIndex: "status", - key: TestSortCategory.Status, - onHeaderCell: () => ({ - onClick: () => { - onColumnHeaderClick(TestSortCategory.Status); + header: "Status", + accessorKey: "status", + id: TestSortCategory.Status, + enableColumnFilter: true, + enableSorting: true, + cell: ({ getValue }) => , + meta: { + treeSelect: { + "data-cy": "status-treeselect", + options: testStatusesFilterTreeData, }, - }), - sorter: true, - className: "data-cy-status-column", - render: (status: string): JSX.Element => ( - - ), - ...getColumnTreeSelectFilterProps({ - ...statusSelectorProps, - "data-cy": "status-treeselect", - }), + width: "10%", + }, }, { - title: ( - - {task.versionMetadata.isPatch ? "Base" : "Previous"} Status - - ), - dataIndex: "baseStatus", - key: TestSortCategory.BaseStatus, - onHeaderCell: () => ({ - onClick: () => { - onColumnHeaderClick(TestSortCategory.BaseStatus); - }, - }), - sorter: true, - render: (status: string): JSX.Element => - status && , + header: () => + `${task.versionMetadata.isPatch ? "Base" : "Previous"} Status`, + accessorKey: "baseStatus", + id: TestSortCategory.BaseStatus, + enableSorting: true, + cell: ({ getValue }) => { + const status = getValue(); + return status && ; + }, + meta: { + width: "10%", + }, }, { - title: Time, - dataIndex: "duration", - key: TestSortCategory.Duration, - onHeaderCell: () => ({ - onClick: () => { - onColumnHeaderClick(TestSortCategory.Duration); - }, - }), - sorter: true, - render: (text: number): string => { - const ms = text * 1000; + header: "Time", + accessorKey: "duration", + id: TestSortCategory.Duration, + enableSorting: true, + cell: ({ getValue }): string => { + const ms = getValue() * 1000; return msToDuration(Math.trunc(ms)); }, + meta: { + width: "10%", + }, }, { - title: Logs, - width: 230, - dataIndex: "logs", - key: "logs", + header: "Logs", sorter: false, - render: (a, b): JSX.Element => , + cell: ({ row }) => , + meta: { + width: "20%", + }, }, ]; diff --git a/src/types/task.ts b/src/types/task.ts index 37cf9fbfbc..b28d91308a 100644 --- a/src/types/task.ts +++ b/src/types/task.ts @@ -1,4 +1,5 @@ import { TableProps } from "antd/es/table"; +import { TestSortCategory } from "gql/generated/types"; export enum RequiredQueryParams { Sort = "sortDir", @@ -24,6 +25,19 @@ export enum PatchTasksQueryParams { Duration = "duration", } +export const mapFilterParamToId = { + [RequiredQueryParams.Statuses]: TestSortCategory.Status, + [RequiredQueryParams.TestName]: TestSortCategory.TestName, +} as const; + +export const mapIdToFilterParam = Object.entries(mapFilterParamToId).reduce( + (accum, [id, param]) => ({ + ...accum, + [param]: id, + }), + {} +); + export type TableOnChange = TableProps["onChange"]; export enum TaskTab {