From 7fd63f9280c26943e9d1f41bb4966b37879517ce Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Tue, 12 Dec 2023 13:17:42 -0500 Subject: [PATCH 1/5] Add tests table --- src/analytics/task/useTaskAnalytics.ts | 8 +- src/hooks/useTableSort.ts | 4 +- src/pages/task/taskTabs/TestsTable.tsx | 165 +++++++++--------- .../testsTable/getColumnsTemplate.tsx | 116 +++++------- src/types/task.ts | 6 + 5 files changed, 136 insertions(+), 163 deletions(-) diff --git a/src/analytics/task/useTaskAnalytics.ts b/src/analytics/task/useTaskAnalytics.ts index dbb7057dd2..13c1d07e65 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/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/taskTabs/TestsTable.tsx b/src/pages/task/taskTabs/TestsTable.tsx index 9c28dec229..4d617d1d4c 100644 --- a/src/pages/task/taskTabs/TestsTable.tsx +++ b/src/pages/task/taskTabs/TestsTable.tsx @@ -1,13 +1,14 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useQuery } from "@apollo/client"; -import { Table } from "antd"; -import { SortOrder } from "antd/es/table/interface"; +import { useLeafyGreenTable } from "@leafygreen-ui/table"; +import { ColumnFiltersState, SortingState } from "@tanstack/react-table"; import { useLocation } from "react-router-dom"; import { useTaskAnalytics } from "analytics"; +import { BaseTable } from "components/Table/BaseTable"; import TableControl from "components/Table/TableControl"; import TableWrapper from "components/Table/TableWrapper"; +import { onChangeHandler } from "components/Table/utils"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; -import { testStatusesFilterTreeData } from "constants/test"; import { TaskTestsQuery, TaskTestsQueryVariables, @@ -18,12 +19,18 @@ import { } from "gql/generated/types"; import { TASK_TESTS } from "gql/queries"; import { + useTableSort, useUpdateURLQueryParams, usePolling, useStatusesFilter, useFilterInputChangeHandler, } from "hooks"; -import { RequiredQueryParams, TableOnChange } from "types/task"; +import { useQueryParams } from "hooks/useQueryParam"; +import { + RequiredQueryParams, + TableOnChange, + mapIdToFilterParam, +} from "types/task"; import { TestStatus } from "types/test"; import { queryString, url } from "utils"; import { getColumnsTemplate } from "./testsTable/getColumnsTemplate"; @@ -34,19 +41,19 @@ const { parseQueryString, queryParamAsNumber } = queryString; interface TestsTableProps { task: TaskQuery["task"]; } + export const TestsTable: React.FC = ({ 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 [, setQueryParams] = useQueryParams(); const { limitNum, pageNum, sort } = queryVariables; const cat = sort?.[0]?.sortBy; const dir = sort?.[0]?.direction; - const appliedDefaultSort = useRef(null); + /* const appliedDefaultSort = useRef(null); useEffect(() => { if ( cat === undefined && @@ -59,49 +66,7 @@ export const TestsTable: React.FC = ({ task }) => { [RequiredQueryParams.Sort]: 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, - }), - })); + }, [pathname, updateQueryParams]); // eslint-disable-line react-hooks/exhaustive-deps */ const { data, loading, refetch, startPolling, stopPolling } = useQuery< TaskTestsQuery, @@ -113,25 +78,6 @@ 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 = () => { updateQueryParams({ [RequiredQueryParams.Category]: undefined, @@ -142,10 +88,71 @@ export const TestsTable: React.FC = ({ task }) => { }); }; + const updateFilters = (filterState: ColumnFiltersState) => { + const updatedParams = { page: "0" }; + + 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 ?? {}; + // TODO: Get initial filters and sorting + const [filters, setFilters] = useState([]); + const [sorting, setSorting] = useState([]); + + const columns = useMemo(() => getColumnsTemplate({ task }), [task]); + + const tableContainerRef = useRef(null); + const table = useLeafyGreenTable({ + columns, + containerRef: tableContainerRef, + data: testResults ?? [], + defaultColumn: { + enableColumnFilter: false, + enableSorting: false, + // Handle bug in sorting order + // https://github.com/TanStack/table/issues/4289 + sortDescFirst: false, + }, + state: { + columnFilters: filters, + sorting, + }, + manualFiltering: true, + manualSorting: true, + manualPagination: true, + onColumnFiltersChange: onChangeHandler( + setFilters, + (updatedState) => { + updateFilters(updatedState); + table.resetRowSelection(); + } + ), + onSortingChange: onChangeHandler( + setSorting, + (updatedState) => { + tableSortHandler(updatedState); + table.resetRowSelection(); + } + ), + }); + 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={limit} + shouldAlternateRowColor + table={table} /> ); }; -export const rowKey = ({ id }: { id: string }): string => id; - const getQueryVariables = ( search: string, taskId: string diff --git a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx index db6236a42f..b04cd2d0f0 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,63 @@ 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: { + placeholder: "Test name regex", }, - }), - width: "40%", - render: (testFile) => {testFile}, - sorter: true, - ...getColumnSearchFilterProps(testNameInputProps), + width: "40%", + }, }, { - 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", - }), + }, }, { - title: ( - - {task.versionMetadata.isPatch ? "Base" : "Previous"} Status - + header: () => ( + <>{task.versionMetadata.isPatch ? "Base" : "Previous"} Status ), - dataIndex: "baseStatus", - key: TestSortCategory.BaseStatus, - onHeaderCell: () => ({ - onClick: () => { - onColumnHeaderClick(TestSortCategory.BaseStatus); - }, - }), - sorter: true, - render: (status: string): JSX.Element => - status && , + accessorKey: "baseStatus", + id: TestSortCategory.BaseStatus, + enableSorting: true, + cell: ({ getValue }) => { + const status = getValue(); + return status && ; + }, }, { - 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)); }, }, { - title: Logs, - width: 230, - dataIndex: "logs", - key: "logs", + header: "Logs", sorter: false, - render: (a, b): JSX.Element => , + cell: ({ row }) => , }, ]; diff --git a/src/types/task.ts b/src/types/task.ts index 37cf9fbfbc..79d6f707e8 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,11 @@ export enum PatchTasksQueryParams { Duration = "duration", } +export const mapIdToFilterParam = { + [TestSortCategory.Status]: RequiredQueryParams.Statuses, + [TestSortCategory.TestName]: RequiredQueryParams.TestName, +} as const; + export type TableOnChange = TableProps["onChange"]; export enum TaskTab { From d88841146834c7c84993377a72bd7fb1b3e9b5cb Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Fri, 5 Jan 2024 11:49:08 -0500 Subject: [PATCH 2/5] Get initial filter/sort --- src/components/Table/BaseTable.tsx | 5 +- src/pages/task/taskTabs/TestsTable.tsx | 103 ++++++++++++++----------- src/types/task.ts | 14 +++- 3 files changed, 73 insertions(+), 49 deletions(-) diff --git a/src/components/Table/BaseTable.tsx b/src/components/Table/BaseTable.tsx index 06fb85aa67..3eaedeabf3 100644 --- a/src/components/Table/BaseTable.tsx +++ b/src/components/Table/BaseTable.tsx @@ -135,7 +135,10 @@ export const BaseTable = ({ `} > {row.getVisibleCells().map((cell) => ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} diff --git a/src/pages/task/taskTabs/TestsTable.tsx b/src/pages/task/taskTabs/TestsTable.tsx index 4d617d1d4c..3eb9530de9 100644 --- a/src/pages/task/taskTabs/TestsTable.tsx +++ b/src/pages/task/taskTabs/TestsTable.tsx @@ -1,13 +1,18 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useMemo, useRef } from "react"; import { useQuery } from "@apollo/client"; import { useLeafyGreenTable } from "@leafygreen-ui/table"; -import { ColumnFiltersState, SortingState } from "@tanstack/react-table"; +import { + ColumnFiltersState, + Filters, + Sorting, + SortingState, +} from "@tanstack/react-table"; import { useLocation } from "react-router-dom"; import { useTaskAnalytics } from "analytics"; import { BaseTable } from "components/Table/BaseTable"; import TableControl from "components/Table/TableControl"; import TableWrapper from "components/Table/TableWrapper"; -import { onChangeHandler } from "components/Table/utils"; +import { onChangeHandler, TableQueryParams } from "components/Table/utils"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; import { TaskTestsQuery, @@ -18,17 +23,11 @@ import { TaskQuery, } from "gql/generated/types"; import { TASK_TESTS } from "gql/queries"; -import { - useTableSort, - useUpdateURLQueryParams, - usePolling, - useStatusesFilter, - useFilterInputChangeHandler, -} from "hooks"; +import { useTableSort, useUpdateURLQueryParams, usePolling } from "hooks"; import { useQueryParams } from "hooks/useQueryParam"; import { RequiredQueryParams, - TableOnChange, + mapFilterParamToId, mapIdToFilterParam, } from "types/task"; import { TestStatus } from "types/test"; @@ -37,36 +36,21 @@ import { getColumnsTemplate } from "./testsTable/getColumnsTemplate"; const { getLimitFromSearch, getPageFromSearch } = url; const { parseQueryString, queryParamAsNumber } = queryString; +const { getDefaultOptions: getDefaultFiltering } = Filters; +const { getDefaultOptions: getDefaultSorting } = Sorting; interface TestsTableProps { task: TaskQuery["task"]; } export const TestsTable: React.FC = ({ task }) => { - const { pathname, search } = useLocation(); + const { search } = useLocation(); const updateQueryParams = useUpdateURLQueryParams(); const { sendEvent } = useTaskAnalytics(); const queryVariables = getQueryVariables(search, task.id); - const [, setQueryParams] = useQueryParams(); - const { limitNum, pageNum, sort } = queryVariables; - const cat = sort?.[0]?.sortBy; - const dir = sort?.[0]?.direction; - - /* const appliedDefaultSort = useRef(null); - useEffect(() => { - if ( - cat === undefined && - updateQueryParams && - appliedDefaultSort.current !== pathname - ) { - appliedDefaultSort.current = pathname; - updateQueryParams({ - [RequiredQueryParams.Category]: TestSortCategory.Status, - [RequiredQueryParams.Sort]: SortDirection.Asc, - }); - } - }, [pathname, updateQueryParams]); // eslint-disable-line react-hooks/exhaustive-deps */ + const [queryParams, setQueryParams] = useQueryParams(); + const { limitNum, pageNum } = queryVariables; const { data, loading, refetch, startPolling, stopPolling } = useQuery< TaskTestsQuery, @@ -112,9 +96,16 @@ export const TestsTable: React.FC = ({ task }) => { const { tests } = taskData ?? {}; const { filteredTestCount, testResults, totalTestCount } = tests ?? {}; - // TODO: Get initial filters and sorting - const [filters, setFilters] = useState([]); - const [sorting, setSorting] = useState([]); + 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]); @@ -130,26 +121,20 @@ export const TestsTable: React.FC = ({ task }) => { // https://github.com/TanStack/table/issues/4289 sortDescFirst: false, }, - state: { - columnFilters: filters, - sorting, + initialState: { + columnFilters: initialFilters, + sorting: initialSorting, }, manualFiltering: true, manualSorting: true, manualPagination: true, onColumnFiltersChange: onChangeHandler( setFilters, - (updatedState) => { - updateFilters(updatedState); - table.resetRowSelection(); - } + updateFilters ), onSortingChange: onChangeHandler( setSorting, - (updatedState) => { - tableSortHandler(updatedState); - table.resetRowSelection(); - } + tableSortHandler ), }); @@ -182,6 +167,34 @@ export const TestsTable: React.FC = ({ task }) => { ); }; +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 }] + : [], + initialFilters: Object.entries(mapFilterParamToId).reduce( + (accum, [param, id]) => { + if (queryParams[param]?.length) { + return [...accum, { id, value: queryParams[param] }]; + } + return accum; + }, + [] + ), + }; +}; + const getQueryVariables = ( search: string, taskId: string diff --git a/src/types/task.ts b/src/types/task.ts index 79d6f707e8..b28d91308a 100644 --- a/src/types/task.ts +++ b/src/types/task.ts @@ -25,11 +25,19 @@ export enum PatchTasksQueryParams { Duration = "duration", } -export const mapIdToFilterParam = { - [TestSortCategory.Status]: RequiredQueryParams.Statuses, - [TestSortCategory.TestName]: RequiredQueryParams.TestName, +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 { From 250e711bae7c04528e90af1a8cb92c4b00d911d9 Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Tue, 9 Jan 2024 17:13:31 -0500 Subject: [PATCH 3/5] Fix tests --- cypress/integration/task/test_table.ts | 50 +++++++++-------- cypress/utils/index.ts | 4 +- src/constants/routes.ts | 2 +- src/gql/generated/types.ts | 2 + src/pages/Task.tsx | 13 ----- src/pages/task/TaskTabs.tsx | 37 ++++++++----- src/pages/task/taskTabs/TestsTable.tsx | 54 +++++++++++++++---- .../testsTable/getColumnsTemplate.tsx | 1 + 8 files changed, 101 insertions(+), 62 deletions(-) diff --git a/cypress/integration/task/test_table.ts b/cypress/integration/task/test_table.ts index 743bb6818d..3c5e99fe22 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 f2b7048e2d..53b951fdd3 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/constants/routes.ts b/src/constants/routes.ts index 34bf04c19c..c298c488bd 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/gql/generated/types.ts b/src/gql/generated/types.ts index ae8b0f91b9..cebd10db42 100644 --- a/src/gql/generated/types.ts +++ b/src/gql/generated/types.ts @@ -2786,11 +2786,13 @@ export type TestFilterOptions = { export type TestLog = { __typename?: "TestLog"; lineNum?: Maybe; + renderingType?: Maybe; url?: Maybe; /** @deprecated Use urlParsley instead */ urlLobster?: Maybe; urlParsley?: Maybe; urlRaw?: Maybe; + version?: Maybe; }; export type TestResult = { diff --git a/src/pages/Task.tsx b/src/pages/Task.tsx index 3c5c11a024..b1e8e98914 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 68646edb51..04f11f247e 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,32 @@ 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, + }; + + 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 { search } = useLocation(); + const { pathname, search } = useLocation(); const updateQueryParams = useUpdateURLQueryParams(); const { sendEvent } = useTaskAnalytics(); const queryVariables = getQueryVariables(search, task.id); const [queryParams, setQueryParams] = useQueryParams(); - const { limitNum, pageNum } = queryVariables; + const { execution, limitNum, pageNum, sort } = queryVariables; + const cat = sort?.[0]?.sortBy; + + const appliedDefaultSort = useRef(null); + useEffect(() => { + // Avoid race condition where this hook overwrites TaskTabs setting aa default execution. + if (execution == null) { + return; + } + + if ( + cat === undefined && + updateQueryParams && + appliedDefaultSort.current !== pathname + ) { + appliedDefaultSort.current = pathname; + updateQueryParams({ + [TableQueryParams.SortBy]: TestSortCategory.Status, + [TableQueryParams.SortDir]: SortDirection.Asc, + }); + } + }, [pathname, updateQueryParams]); // eslint-disable-line react-hooks/exhaustive-deps const { data, loading, refetch, startPolling, stopPolling } = useQuery< TaskTestsQuery, @@ -63,17 +84,23 @@ export const TestsTable: React.FC = ({ task }) => { usePolling({ startPolling, stopPolling, refetch }); const clearQueryParams = () => { - updateQueryParams({ - [RequiredQueryParams.Category]: undefined, - [RequiredQueryParams.Sort]: undefined, - [RequiredQueryParams.Page]: "0", - [RequiredQueryParams.TestName]: undefined, - [RequiredQueryParams.Statuses]: undefined, + table.resetColumnFilters(); + table.resetSorting(); + setQueryParams({ + ...queryParams, + ...emptyFilterQueryParams, + [TableQueryParams.Page]: "0", + [TableQueryParams.SortBy]: undefined, + [TableQueryParams.SortDir]: undefined, }); }; const updateFilters = (filterState: ColumnFiltersState) => { - const updatedParams = { page: "0" }; + const updatedParams = { + ...queryParams, + page: "0", + ...emptyFilterQueryParams, + }; filterState.forEach(({ id, value }) => { const key = mapIdToFilterParam[id]; @@ -167,6 +194,11 @@ export const TestsTable: React.FC = ({ task }) => { ); }; +const emptyFilterQueryParams = { + [RequiredQueryParams.TestName]: undefined, + [RequiredQueryParams.Statuses]: undefined, +}; + const getInitialState = (queryParams: { [key: string]: any; }): { @@ -182,7 +214,7 @@ const getInitialState = (queryParams: { 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) { diff --git a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx index b04cd2d0f0..6c79f6cb40 100644 --- a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx +++ b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx @@ -21,6 +21,7 @@ export const getColumnsTemplate = ({ task }: GetColumnsTemplateParams) => [ enableSorting: true, meta: { search: { + "data-cy": "test-name-filter", placeholder: "Test name regex", }, width: "40%", From f8b725f14bd6616f556e45257ca7d1c2eeb133e1 Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Tue, 9 Jan 2024 17:48:19 -0500 Subject: [PATCH 4/5] Small fixes --- src/pages/task/TaskTabs.tsx | 1 + src/pages/task/taskTabs/TestsTable.tsx | 5 +++-- src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/task/TaskTabs.tsx b/src/pages/task/TaskTabs.tsx index 04f11f247e..55557276cd 100644 --- a/src/pages/task/TaskTabs.tsx +++ b/src/pages/task/TaskTabs.tsx @@ -182,6 +182,7 @@ export const TaskTabs: React.FC = ({ isDisplayTask, task }) => { ...query, }; + // Introduce execution query parameter if none is set. if ( id === task?.id && query.execution === undefined && diff --git a/src/pages/task/taskTabs/TestsTable.tsx b/src/pages/task/taskTabs/TestsTable.tsx index 00c579b6e3..5751abe96d 100644 --- a/src/pages/task/taskTabs/TestsTable.tsx +++ b/src/pages/task/taskTabs/TestsTable.tsx @@ -55,7 +55,7 @@ export const TestsTable: React.FC = ({ task }) => { const appliedDefaultSort = useRef(null); useEffect(() => { - // Avoid race condition where this hook overwrites TaskTabs setting aa default execution. + // Avoid race condition where this hook overwrites TaskTabs setting a default execution. if (execution == null) { return; } @@ -144,6 +144,7 @@ export const TestsTable: React.FC = ({ task }) => { 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, @@ -186,7 +187,7 @@ export const TestsTable: React.FC = ({ task }) => { data-cy="tests-table" data-loading={loading} loading={loading} - // loadingRows={limit} + loadingRows={limitNum} shouldAlternateRowColor table={table} /> diff --git a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx index 6c79f6cb40..9afb06659f 100644 --- a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx +++ b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx @@ -24,7 +24,6 @@ export const getColumnsTemplate = ({ task }: GetColumnsTemplateParams) => [ "data-cy": "test-name-filter", placeholder: "Test name regex", }, - width: "40%", }, }, { From c22b01fda61b127b00fe8620341c69a044ec3a69 Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Thu, 18 Jan 2024 10:57:53 -0500 Subject: [PATCH 5/5] Address CR comments --- src/pages/task/taskTabs/TestsTable.tsx | 14 +++----------- .../taskTabs/testsTable/getColumnsTemplate.tsx | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/pages/task/taskTabs/TestsTable.tsx b/src/pages/task/taskTabs/TestsTable.tsx index 5751abe96d..00f5a3bdfa 100644 --- a/src/pages/task/taskTabs/TestsTable.tsx +++ b/src/pages/task/taskTabs/TestsTable.tsx @@ -51,7 +51,7 @@ export const TestsTable: React.FC = ({ task }) => { const queryVariables = getQueryVariables(search, task.id); const [queryParams, setQueryParams] = useQueryParams(); const { execution, limitNum, pageNum, sort } = queryVariables; - const cat = sort?.[0]?.sortBy; + const sortBy = sort?.[0]?.sortBy; const appliedDefaultSort = useRef(null); useEffect(() => { @@ -61,7 +61,7 @@ export const TestsTable: React.FC = ({ task }) => { } if ( - cat === undefined && + sortBy === undefined && updateQueryParams && appliedDefaultSort.current !== pathname ) { @@ -84,15 +84,7 @@ export const TestsTable: React.FC = ({ task }) => { usePolling({ startPolling, stopPolling, refetch }); const clearQueryParams = () => { - table.resetColumnFilters(); - table.resetSorting(); - setQueryParams({ - ...queryParams, - ...emptyFilterQueryParams, - [TableQueryParams.Page]: "0", - [TableQueryParams.SortBy]: undefined, - [TableQueryParams.SortDir]: undefined, - }); + table.resetColumnFilters(true); }; const updateFilters = (filterState: ColumnFiltersState) => { diff --git a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx index 9afb06659f..6b0972a222 100644 --- a/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx +++ b/src/pages/task/taskTabs/testsTable/getColumnsTemplate.tsx @@ -24,6 +24,7 @@ export const getColumnsTemplate = ({ task }: GetColumnsTemplateParams) => [ "data-cy": "test-name-filter", placeholder: "Test name regex", }, + width: "50%", }, }, { @@ -38,12 +39,12 @@ export const getColumnsTemplate = ({ task }: GetColumnsTemplateParams) => [ "data-cy": "status-treeselect", options: testStatusesFilterTreeData, }, + width: "10%", }, }, { - header: () => ( - <>{task.versionMetadata.isPatch ? "Base" : "Previous"} Status - ), + header: () => + `${task.versionMetadata.isPatch ? "Base" : "Previous"} Status`, accessorKey: "baseStatus", id: TestSortCategory.BaseStatus, enableSorting: true, @@ -51,6 +52,9 @@ export const getColumnsTemplate = ({ task }: GetColumnsTemplateParams) => [ const status = getValue(); return status && ; }, + meta: { + width: "10%", + }, }, { header: "Time", @@ -61,10 +65,16 @@ export const getColumnsTemplate = ({ task }: GetColumnsTemplateParams) => [ const ms = getValue() * 1000; return msToDuration(Math.trunc(ms)); }, + meta: { + width: "10%", + }, }, { header: "Logs", sorter: false, cell: ({ row }) => , + meta: { + width: "20%", + }, }, ];