diff --git a/src/components/Table/BaseTable.tsx b/src/components/Table/BaseTable.tsx index e18f0805c6..16caeebf0e 100644 --- a/src/components/Table/BaseTable.tsx +++ b/src/components/Table/BaseTable.tsx @@ -1,3 +1,4 @@ +import { css } from "@leafygreen-ui/emotion"; import { Cell, ExpandedContent, @@ -25,38 +26,66 @@ export const BaseTable = ({ table, ...args }: SpruceTableProps & TableProps) => ( - <> - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {flexRender(header.column.columnDef.header, header.getContext())} + {/* @ts-ignore-error */} + {header.column.columnDef?.meta?.filterComponent?.({ + column: header.column, + })} + {/* @ts-ignore-error */} + {header.column.columnDef?.meta?.sortComponent?.({ + column: header.column, + })} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + div { + max-height: unset; + } + `} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + {row.original.renderExpandedContent && } + {row.subRows && + row.subRows.map((subRow) => ( + div[data-state="entered"] { + max-height: unset; + } + `} + > + {subRow.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - {row.original.renderExpandedContent && ( - - )} - - ))} - -
+ + ))} + {table.getRowModel().rows.length === 0 && (emptyComponent || "No data to display")} - + ); diff --git a/src/components/Table/LGFilters.tsx b/src/components/Table/LGFilters.tsx index 28bfcff65d..c557524413 100644 --- a/src/components/Table/LGFilters.tsx +++ b/src/components/Table/LGFilters.tsx @@ -1,36 +1,39 @@ import { LeafyGreenTableRow } from "@leafygreen-ui/table/new"; -import { TableFilterPopover } from "components/TablePopover"; +import { + TableFilterPopover, + TableSearchPopover, + TableSortIcon, +} from "components/TablePopover"; import { TreeDataEntry } from "components/TreeSelect"; type TreeSelectFilterProps = { "data-cy"?: string; tData: TreeDataEntry[]; - title: React.ReactNode; + onFilter?: ({ id, value }: { id: string; value: string[] }) => void; }; export const getColumnTreeSelectFilterProps = ({ "data-cy": dataCy, + onFilter, tData, - title, }: TreeSelectFilterProps) => ({ - header: ({ column }) => { - // Only present options that appear in the table - const options = tData.filter( - ({ value }) => !!column.getFacetedUniqueValues().get(value) - ); - return ( - <> - {title} + meta: { + filterComponent: ({ column }) => { + const filtered = tData.filter( + ({ value }) => !!column.getFacetedUniqueValues().get(value) + ); + return ( { - column.setFilterValue(value); + options={filtered.length ? filtered : tData} + onConfirm={(newValue) => { + column.setFilterValue(newValue); + onFilter?.({ id: column.id, value: newValue }); }} data-cy={dataCy} /> - - ); + ); + }, }, filterFn: ( row: LeafyGreenTableRow, @@ -44,3 +47,62 @@ export const getColumnTreeSelectFilterProps = ({ return filterValue.includes(row.getValue(columnId)); }, }); + +type InputFilterProps = { + "data-cy"?: string; + onFilter?: ({ id, value }: { id: string; value: string }) => void; +}; + +export const getColumnInputFilterProps = ({ + "data-cy": dataCy, + onFilter, +}: InputFilterProps) => ({ + meta: { + filterComponent: ({ column }) => ( + { + column.setFilterValue(newValue); + onFilter?.({ id: column.id, value: newValue }); + }} + data-cy={dataCy} + /> + ), + }, + filterFn: ( + row: LeafyGreenTableRow, + columnId: string, + filterValue: string + ) => { + // If no filter is specified, show all rows + if (!filterValue.length) { + return true; + } + return (row.getValue(columnId) as string) + .toLowerCase() + .includes(filterValue.toLowerCase()); + }, +}); + +type SortProps = { + "data-cy"?: string; + onSort?: ({ id, value }: { id: string; value: string }) => void; +}; + +export const getColumnSortProps = ({ + "data-cy": dataCy, + onSort, +}: SortProps) => ({ + meta: { + sortComponent: ({ column }) => ( + { + column.toggleSorting(); + onSort({ id: column.id, value: newValue }); + }} + data-cy={dataCy} + /> + ), + }, +}); diff --git a/src/components/Table/TablePlaceholder.tsx b/src/components/Table/TablePlaceholder.tsx index 082208530f..a86a0fccff 100644 --- a/src/components/Table/TablePlaceholder.tsx +++ b/src/components/Table/TablePlaceholder.tsx @@ -1,10 +1,7 @@ import styled from "@emotion/styled"; -import { palette } from "@leafygreen-ui/palette"; import Icon, { glyphs } from "components/Icon"; import { size } from "constants/tokens"; -const { gray } = palette; - interface Props { message: string; glyph?: keyof typeof glyphs; @@ -27,7 +24,6 @@ const PlaceholderWrapper = styled.div` flex-direction: column; align-items: center; padding: ${size.l} 0; - background-color: ${gray.light2}; opacity: 50%; `; diff --git a/src/components/TablePopover/TableFilterPopover.tsx b/src/components/TablePopover/TableFilterPopover.tsx index 87fc390e62..47ed93a1fe 100644 --- a/src/components/TablePopover/TableFilterPopover.tsx +++ b/src/components/TablePopover/TableFilterPopover.tsx @@ -25,7 +25,7 @@ export const TableFilterPopover: React.FC = ({ value, }) => { const [active, setActive] = useState(false); - const iconColor = value.length ? blue.light1 : gray.dark2; + const iconColor = value.length ? blue.base : gray.dark2; const buttonRef = useRef(null); const popoverRef = useRef(null); diff --git a/src/components/TablePopover/TableSearchPopover.tsx b/src/components/TablePopover/TableSearchPopover.tsx index 74362b612c..208058a47d 100644 --- a/src/components/TablePopover/TableSearchPopover.tsx +++ b/src/components/TablePopover/TableSearchPopover.tsx @@ -13,21 +13,20 @@ const { blue, gray } = palette; interface TableSearchPopoverProps { value: string; - onChange: (search: string) => void; - onConfirm: () => void; + onConfirm: (search: string) => void; "data-cy"?: string; placeholder?: string; } export const TableSearchPopover: React.FC = ({ "data-cy": dataCy, - onChange, onConfirm, placeholder, value, }) => { + const [input, setInput] = useState(value); const [active, setActive] = useState(false); - const iconColor = value === "" ? gray.dark2 : blue.light1; + const iconColor = input === "" ? gray.dark2 : blue.base; const buttonRef = useRef(null); const popoverRef = useRef(null); @@ -35,8 +34,8 @@ export const TableSearchPopover: React.FC = ({ // Handle onClickOutside useOnClickOutside([buttonRef, popoverRef], () => setActive(false)); - const closePopup = () => { - onConfirm(); + const onEnter = () => { + onConfirm(input); setActive(false); }; @@ -59,9 +58,9 @@ export const TableSearchPopover: React.FC = ({ type="search" aria-label="Search Table" data-cy="input-filter" - value={value} - onChange={(e) => onChange(e.target.value)} - onKeyPress={(e) => e.key === "Enter" && closePopup()} + value={input} + onChange={(e) => setInput(e.target.value)} + onKeyPress={(e) => e.key === "Enter" && onEnter()} autoFocus /> diff --git a/src/components/TablePopover/TableSort.tsx b/src/components/TablePopover/TableSort.tsx new file mode 100644 index 0000000000..81db5d27e5 --- /dev/null +++ b/src/components/TablePopover/TableSort.tsx @@ -0,0 +1,58 @@ +import { useState } from "react"; +import styled from "@emotion/styled"; +import Icon from "@leafygreen-ui/icon"; +import IconButton from "@leafygreen-ui/icon-button"; +import { palette } from "@leafygreen-ui/palette"; +import { size } from "constants/tokens"; + +const { blue, gray } = palette; + +interface TableSortProps { + sort: string; + onToggle: (value: string) => void; + "data-cy"?: string; +} + +export const TableSort: React.VFC = ({ + "data-cy": dataCy, + onToggle, + sort: sortProps, +}) => { + const [sort, setSort] = useState(sortProps); + const iconColor = sort !== "" ? blue.base : gray.dark2; + + const onClick = () => { + let update: string; + if (sort === "") { + update = "DESC"; + } else if (sort === "DESC") { + update = "ASC"; + } else { + update = ""; + } + setSort(update); + onToggle(update); + }; + + return ( + + + + + + ); +}; + +const iconGlyph = { + ASC: "SortAscending", + DESC: "SortDescending", + "": "Unsorted", +}; + +const Wrapper = styled.div` + margin-left: ${size.xxs}; +`; diff --git a/src/components/TablePopover/TableSortIcon.tsx b/src/components/TablePopover/TableSortIcon.tsx new file mode 100644 index 0000000000..d9b130d60f --- /dev/null +++ b/src/components/TablePopover/TableSortIcon.tsx @@ -0,0 +1,63 @@ +import styled from "@emotion/styled"; +import Icon from "@leafygreen-ui/icon"; +import IconButton from "@leafygreen-ui/icon-button"; +import { palette } from "@leafygreen-ui/palette"; +import { size } from "constants/tokens"; + +const { blue, gray } = palette; + +enum SortState { + ASC = "asc", + DESC = "desc", + OFF = "false", +} + +type SortStateType = (typeof SortState)[keyof typeof SortState]; + +const iconGlyph = { + [SortState.ASC]: "SortAscending", + [SortState.DESC]: "SortDescending", + [SortState.OFF]: "Unsorted", +}; + +interface TableSortIconProps { + value: SortStateType; + onToggle: (value: string) => void; + "data-cy"?: string; +} + +export const TableSortIcon: React.VFC = ({ + "data-cy": dataCy, + onToggle, + value, +}) => { + const iconColor = value !== SortState.OFF ? blue.base : gray.dark2; + + const onClick = () => { + let update: string | undefined; + if (value === SortState.OFF) { + update = "DESC"; + } else if (value === SortState.DESC) { + update = "ASC"; + } else { + update = undefined; + } + onToggle(update); + }; + + return ( + + + + + + ); +}; + +const Wrapper = styled.div` + margin-left: ${size.xxs}; +`; diff --git a/src/components/TablePopover/index.tsx b/src/components/TablePopover/index.tsx index 788821a9ce..2450a2b151 100644 --- a/src/components/TablePopover/index.tsx +++ b/src/components/TablePopover/index.tsx @@ -1,2 +1,3 @@ export * from "./TableFilterPopover"; export * from "./TableSearchPopover"; +export * from "./TableSortIcon"; diff --git a/src/gql/generated/types.ts b/src/gql/generated/types.ts index 538313c0c0..f3bdc4d290 100644 --- a/src/gql/generated/types.ts +++ b/src/gql/generated/types.ts @@ -8658,7 +8658,7 @@ export type VersionTaskDurationsQuery = { startTime?: Date | null; status: string; timeTaken?: number | null; - executionTasksFull?: Array<{ + subRows?: Array<{ __typename?: "Task"; buildVariantDisplayName?: string | null; displayName: string; diff --git a/src/gql/queries/version-task-durations.graphql b/src/gql/queries/version-task-durations.graphql index 2745617027..ea697ca710 100644 --- a/src/gql/queries/version-task-durations.graphql +++ b/src/gql/queries/version-task-durations.graphql @@ -10,7 +10,10 @@ query VersionTaskDurations( buildVariantDisplayName displayName execution - executionTasksFull { + id + startTime + status + subRows: executionTasksFull { buildVariantDisplayName displayName execution @@ -19,9 +22,6 @@ query VersionTaskDurations( status timeTaken } - id - startTime - status timeTaken } } diff --git a/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx b/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx index a5db552319..71a1b0d09a 100644 --- a/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx +++ b/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx @@ -149,10 +149,10 @@ const columns = [ const resourceType = getValue(); return resourceTypeToCopy?.[resourceType] ?? resourceType; }, + header: "Type", ...getColumnTreeSelectFilterProps({ "data-cy": "status-filter-popover", tData: resourceTypeTreeData, - title: "Type", }), }, { @@ -180,10 +180,10 @@ const columns = [ }, { accessorKey: "trigger", + header: "Event", ...getColumnTreeSelectFilterProps({ "data-cy": "trigger-filter-popover", tData: triggerTreeData, - title: "Event", }), cell: ({ getValue }) => { const trigger = getValue(); diff --git a/src/pages/version/taskDuration/TaskDurationCell.tsx b/src/pages/version/taskDuration/TaskDurationCell.tsx new file mode 100644 index 0000000000..60831ec577 --- /dev/null +++ b/src/pages/version/taskDuration/TaskDurationCell.tsx @@ -0,0 +1,52 @@ +import styled from "@emotion/styled"; +import { Description } from "@leafygreen-ui/typography"; +import { + mapTaskToBarchartColor, + mapTaskStatusToUmbrellaStatus, +} from "constants/task"; +import { size } from "constants/tokens"; +import { string } from "utils"; + +const { msToDuration } = string; + +interface TaskDurationCellProps { + status: string; + timeTaken: number; + maxTimeTaken: number; +} + +export const TaskDurationCell: React.VFC = ({ + maxTimeTaken, + status, + timeTaken, +}) => { + const barWidth = calculateBarWidth(timeTaken, maxTimeTaken); + const barColor = + mapTaskToBarchartColor[mapTaskStatusToUmbrellaStatus[status]]; + return ( + + + {msToDuration(timeTaken)} + + ); +}; + +const Duration = styled.div` + width: 100%; + padding: ${size.s} 0; +`; + +const calculateBarWidth = (value: number, max: number) => + max ? `${(value / max) * 100}%` : "0%"; + +const DurationBar = styled.div<{ width: string; color: string }>` + width: ${({ width }) => width}; + background-color: ${({ color }) => color}; + border-radius: ${size.m}; + height: 6px; +`; + +const DurationLabel = styled(Description)` + flex-shrink: 0; + font-size: 12px; +`; diff --git a/src/pages/version/taskDuration/TaskDurationRow.tsx b/src/pages/version/taskDuration/TaskDurationRow.tsx deleted file mode 100644 index fabe817e38..0000000000 --- a/src/pages/version/taskDuration/TaskDurationRow.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { forwardRef } from "react"; -import { css } from "@emotion/react"; -import styled from "@emotion/styled"; -import { Row, Cell } from "@leafygreen-ui/table"; -import { Description } from "@leafygreen-ui/typography"; -import { TaskLink } from "components/TasksTable/TaskLink"; -import TaskStatusBadge from "components/TaskStatusBadge"; -import { - mapTaskToBarchartColor, - mapTaskStatusToUmbrellaStatus, -} from "constants/task"; -import { size } from "constants/tokens"; -import { VersionTaskDurationsQuery } from "gql/generated/types"; -import { TaskStatus } from "types/task"; -import { Unpacked } from "types/utils"; -import { string } from "utils"; - -const { msToDuration } = string; - -interface RowProps { - task: Unpacked; - maxTimeTaken: number; - children?: React.ReactNode; - "data-cy"?: string; -} - -export const TaskDurationRow: React.FC = forwardRef( - ( - { children, "data-cy": dataCy, maxTimeTaken, task, ...rest }, - ref: React.Ref - ) => { - const { - buildVariantDisplayName, - displayName, - executionTasksFull, - id, - startTime, - status, - timeTaken, - } = task; - - const barWidth = calculateBarWidth(timeTaken, maxTimeTaken); - const barColor = - mapTaskToBarchartColor[mapTaskStatusToUmbrellaStatus[status]]; - const startedWithZeroTime = - startTime === null && status === TaskStatus.Started; - - return ( - - - - - - - - {buildVariantDisplayName} - - {startedWithZeroTime ? ( - - There is no task duration information for this task at this time. - - ) : ( - <> - - {msToDuration(timeTaken)} - - )} - - - {/* - * LeafyGreen at the moment fails to render nested rows that are not comprised directly of Row and Cell components. - * To render execution tasks, reuse the same Cell structure that comprises the parent task. - * This should be addressed by the table refactor LG-2231. - */} - {executionTasksFull?.map((t) => ( - - - - - - - - {t.buildVariantDisplayName} - - {t.startTime === null && t.status === TaskStatus.Started ? ( - - There is no task duration information for this task at this - time. - - ) : ( - <> - - {msToDuration(t.timeTaken)} - - )} - - - ))} - - ); - } -); -TaskDurationRow.displayName = "Row"; - -const calculateBarWidth = (value: number, max: number) => - max ? `${(value / max) * 100}%` : "0%"; - -const baseCellStyle = css` - word-break: break-all; - vertical-align: middle; -`; - -const TaskNameCell = styled(Cell)` - ${baseCellStyle} -`; -TaskNameCell.displayName = "Cell"; - -const StatusCell = styled(Cell)` - ${baseCellStyle} -`; -StatusCell.displayName = "Cell"; - -const BuildVariantCell = styled(Cell)` - ${baseCellStyle} -`; -BuildVariantCell.displayName = "Cell"; - -const DurationCell = styled(Cell)` - span { - display: block; - width: 100%; - } - vertical-align: middle; -`; -DurationCell.displayName = "Cell"; - -const barHeight = "12px"; - -const DurationBar = styled.div<{ width: string; color: string }>` - width: ${({ width }) => width}; - background-color: ${({ color }) => color}; - border-radius: ${size.m}; - height: ${barHeight}; -`; - -const DurationLabel = styled(Description)` - flex-shrink: 0; - padding-top: ${size.xxs}; - font-size: ${barHeight}; -`; diff --git a/src/pages/version/taskDuration/TaskDurationTable.stories.tsx b/src/pages/version/taskDuration/TaskDurationTable.stories.tsx index 46bbe86612..7e43dcbecc 100644 --- a/src/pages/version/taskDuration/TaskDurationTable.stories.tsx +++ b/src/pages/version/taskDuration/TaskDurationTable.stories.tsx @@ -10,14 +10,143 @@ export const Default: CustomStoryObj = { render: () => , }; +export const LongContent: CustomStoryObj = { + render: () => , +}; + const props = { + tasksLong: [ + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: + "sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion sharding_multiversion", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_display_sharding_multiversion_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:42:53.995Z"), + status: "known-issue", + subRows: [ + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled) long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long", + displayName: "sharding_last_continuous_00-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_continuous_00_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:43:20.938Z"), + status: "success", + timeTaken: 1248955, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_continuous_01-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_continuous_01_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:42:58.249Z"), + status: "failed", + timeTaken: 1417378, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_continuous_02-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_continuous_02_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:42:59.444Z"), + status: "success", + timeTaken: 1367157, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_continuous_03-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_continuous_03_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:42:56.486Z"), + status: "success", + timeTaken: 1852371, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_continuous_04-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_continuous_04_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:43:15.02Z"), + status: "success", + timeTaken: 2012757, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_continuous_05-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_continuous_05_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:43:08.172Z"), + status: "success", + timeTaken: 245107, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_lts_00-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_lts_00_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:43:13.916Z"), + status: "success", + timeTaken: 1301814, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_lts_01-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_lts_01_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:43:10.714Z"), + status: "success", + timeTaken: 1615699, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_lts_02-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_lts_02_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:43:03.408Z"), + status: "success", + timeTaken: 1894981, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_lts_03-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_lts_03_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:43:04.137Z"), + status: "success", + timeTaken: 1280264, + }, + { + buildVariantDisplayName: + "Shared Library Enterprise RHEL 8.0 Query (all feature flags and CQF enabled)", + displayName: "sharding_last_lts_04-linux-enterprise", + execution: 0, + id: "mongodb_mongo_master_enterprise_rhel_80_64_bit_dynamic_all_feature_flags_required_and_cqf_enabled_sharding_last_lts_04_linux_enterprise_patch_5cab129eb5c35c3ad61ed9e5156539556d85dcd1_65241908850e61e75776c5c2_23_10_09_15_17_56", + startTime: new Date("2023-10-09T16:42:53.995Z"), + status: "success", + timeTaken: 1464812, + }, + ], + timeTaken: 15701302, + }, + ], tasks: [ { id: "evg_ubuntu1604_container_test_rest_data_fd73e06c7bc6c5dcdf7a671dece0153916e64212_23_01_04_16_01_18", buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-rest-data", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.242Z"), status: "system-failed", timeTaken: 4840553, @@ -27,7 +156,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-model-distro", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.295Z"), status: "system-failed", timeTaken: 4840547, @@ -37,7 +166,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-agent-internal", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.234Z"), status: "system-failed", timeTaken: 4840526, @@ -47,7 +176,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-evergreen", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.258Z"), status: "system-failed", timeTaken: 4840512, @@ -57,7 +186,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-model-artifact", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.272Z"), status: "system-failed", timeTaken: 4840399, @@ -67,7 +196,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-thirdparty", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.259Z"), status: "system-failed", timeTaken: 4840375, @@ -77,7 +206,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-model-event", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.275Z"), status: "system-failed", timeTaken: 4840347, @@ -87,7 +216,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-model-testresult", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.291Z"), status: "system-failed", timeTaken: 4840319, @@ -97,7 +226,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-model-alertrecord", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.268Z"), status: "system-failed", timeTaken: 4840303, @@ -107,7 +236,7 @@ const props = { buildVariantDisplayName: "Ubuntu 16.04 (Container)", displayName: "test-model-patch", execution: 0, - executionTasksFull: null, + subRows: null, startTime: new Date("2023-01-04T16:01:25.281Z"), status: "system-failed", timeTaken: 4840277, diff --git a/src/pages/version/taskDuration/TaskDurationTable.test.tsx b/src/pages/version/taskDuration/TaskDurationTable.test.tsx index 132717bfb7..e7a8d93db0 100644 --- a/src/pages/version/taskDuration/TaskDurationTable.test.tsx +++ b/src/pages/version/taskDuration/TaskDurationTable.test.tsx @@ -42,7 +42,7 @@ const tasks: VersionTaskDurationsQuery["version"]["tasks"]["data"] = [ displayName: "check_codegen", buildVariantDisplayName: "Ubuntu 16.04", timeTaken: 6000, - executionTasksFull: [ + subRows: [ { id: "spruce_ubuntu1604_check_codegen_patch_345da020487255d1b9fb87bed4ceb98397a0c5a5_624af28fa4cf4714c7a6c19a_22_04_04_13_28_48", execution: 0, @@ -61,7 +61,7 @@ const tasks: VersionTaskDurationsQuery["version"]["tasks"]["data"] = [ status: "success", displayName: "compile", buildVariantDisplayName: "Ubuntu 16.04", - executionTasksFull: null, + subRows: null, timeTaken: 10000, __typename: "Task", }, diff --git a/src/pages/version/taskDuration/TaskDurationTable.tsx b/src/pages/version/taskDuration/TaskDurationTable.tsx index f684977045..47e25df261 100644 --- a/src/pages/version/taskDuration/TaskDurationTable.tsx +++ b/src/pages/version/taskDuration/TaskDurationTable.tsx @@ -1,24 +1,27 @@ -import styled from "@emotion/styled"; -import { palette } from "@leafygreen-ui/palette"; -import { Table, TableHeader } from "@leafygreen-ui/table"; +import { useRef, useState } from "react"; +import { useLeafyGreenTable } from "@leafygreen-ui/table/new"; +import { + SortingState, + ColumnFiltersState, + getFacetedMinMaxValues, +} from "@tanstack/react-table"; import { useParams } from "react-router-dom"; import { useVersionAnalytics } from "analytics"; -import { TablePlaceholder } from "components/Table/TablePlaceholder"; +import { BaseTable } from "components/Table/BaseTable"; import { - TableFilterPopover, - TableSearchPopover, -} from "components/TablePopover"; + getColumnInputFilterProps, + getColumnTreeSelectFilterProps, + getColumnSortProps, +} from "components/Table/LGFilters"; +import { TablePlaceholder } from "components/Table/TablePlaceholder"; +import { TaskLink } from "components/TasksTable/TaskLink"; +import TaskStatusBadge from "components/TaskStatusBadge"; import { VersionTaskDurationsQuery } from "gql/generated/types"; -import { - useTaskStatuses, - useStatusesFilter, - useFilterInputChangeHandler, -} from "hooks"; +import { useTaskStatuses } from "hooks"; +import { useQueryParams } from "hooks/useQueryParam"; import { useUpdateURLQueryParams } from "hooks/useUpdateURLQueryParams"; import { PatchTasksQueryParams } from "types/task"; -import { TaskDurationRow } from "./TaskDurationRow"; - -const { gray } = palette; +import { TaskDurationCell } from "./TaskDurationCell"; interface Props { tasks: VersionTaskDurationsQuery["version"]["tasks"]["data"]; @@ -26,142 +29,141 @@ interface Props { } export const TaskDurationTable: React.FC = ({ loading, tasks }) => { - const { id: versionId } = useParams<{ id: string }>(); - const { sendEvent } = useVersionAnalytics(versionId); - const updateQueryParams = useUpdateURLQueryParams(); - - const { currentStatuses } = useTaskStatuses({ versionId }); - - const filterProps = { - resetPage: true, - sendAnalyticsEvent: (filterBy: string) => - sendEvent({ name: "Filter Tasks", filterBy }), - }; - - const statusesFilter = useStatusesFilter({ - urlParam: PatchTasksQueryParams.Statuses, - ...filterProps, - }); - - const taskFilter = useFilterInputChangeHandler({ - urlParam: PatchTasksQueryParams.TaskName, - ...filterProps, - }); - - const variantFilter = useFilterInputChangeHandler({ - urlParam: PatchTasksQueryParams.Variant, - ...filterProps, + const [queryParams] = useQueryParams(); + const { + [PatchTasksQueryParams.TaskName]: taskName = "", + [PatchTasksQueryParams.Statuses]: statuses = [], + [PatchTasksQueryParams.Variant]: variant = "", + [PatchTasksQueryParams.Duration]: duration = "", + } = queryParams; + + const [columnFilters, setColumnFilters] = useState([ + { id: PatchTasksQueryParams.TaskName, value: taskName }, + { + id: PatchTasksQueryParams.Statuses, + value: Array.isArray(statuses) ? statuses : [statuses], + }, + { id: PatchTasksQueryParams.Variant, value: variant }, + ]); + + const [sorting, setSorting] = useState([ + { + id: PatchTasksQueryParams.Duration, + desc: duration === "DESC", + }, + ]); + + const { columns } = useGetColumns(); + + const tableContainerRef = useRef(null); + const table = useLeafyGreenTable< + VersionTaskDurationsQuery["version"]["tasks"]["data"][0] + >({ + columns, + containerRef: tableContainerRef, + data: tasks ?? [], + state: { + columnFilters, + sorting, + }, + onColumnFiltersChange: setColumnFilters, + onSortingChange: setSorting, + getFacetedMinMaxValues: getFacetedMinMaxValues(), + manualFiltering: true, + manualSorting: true, + manualPagination: true, }); - const handleDurationSort = (direction: string) => { - updateQueryParams({ - [PatchTasksQueryParams.Duration]: direction.toUpperCase(), - [PatchTasksQueryParams.Page]: "0", - }); - }; - - const maxTimeTaken = findMaxTimeTaken(tasks); - return ( - - - Task Name - - - } - />, - - Status - - - } - />, - - Build Variant - - - } - />, - Task Duration} - handleSort={handleDurationSort} - />, - ]} - > - {({ datum }) => ( - - )} -
+ <> + {loading && ( )} {!loading && tasks.length === 0 && ( )} -
+ ); }; -const findMaxTimeTaken = ( - tasks: VersionTaskDurationsQuery["version"]["tasks"]["data"] -) => { - if (tasks && tasks.length) { - const durations = tasks.map((t) => - t.startTime !== null ? t.timeTaken : 0 - ); - return Math.max(...durations); - } - return 0; -}; - -const TableWrapper = styled.div` - border-top: 3px solid ${gray.light2}; - - // LeafyGreen applies overflow-x: auto to the table, which causes an overflow-y scrollbar - // to appear. Since the table container will expand to fit its contents, we will never - // overflow on the Y-axis. Therefore, hide the scroll bar. - > div > div:last-of-type { - overflow-y: hidden; - } -`; +const useGetColumns = () => { + const { id: versionId } = useParams<{ id: string }>(); + const { sendEvent } = useVersionAnalytics(versionId); + const updateQueryParams = useUpdateURLQueryParams(); + const { currentStatuses: statusOptions } = useTaskStatuses({ versionId }); -const StyledTableHeader = styled(TableHeader)` - width: 15%; -`; + const updateUrl = ({ id, value }) => { + updateQueryParams({ [id]: value || undefined, page: "0" }); + sendEvent({ name: "Filter Tasks", filterBy: id }); + }; -const TableHeaderLabel = styled.div` - display: flex; - align-items: center; -`; + return { + columns: [ + { + id: PatchTasksQueryParams.TaskName, + accessorKey: "displayName", + header: "Task Name", + cell: ({ + getValue, + row: { + original: { id }, + }, + }) => , + ...getColumnInputFilterProps({ + "data-cy": "task-name-filter-popover", + onFilter: updateUrl, + }), + size: 300, + }, + { + id: PatchTasksQueryParams.Statuses, + accessorKey: "status", + header: "Status", + cell: ({ getValue }) => , + ...getColumnTreeSelectFilterProps({ + "data-cy": "status-filter-popover", + tData: statusOptions, + onFilter: updateUrl, + }), + }, + { + id: PatchTasksQueryParams.Variant, + accessorKey: "buildVariantDisplayName", + header: "Build Variant", + cell: ({ getValue }) => getValue(), + ...getColumnInputFilterProps({ + "data-cy": "variant-filter-popover", + onFilter: updateUrl, + }), + }, + { + id: PatchTasksQueryParams.Duration, + accessorKey: "timeTaken", + header: "Task Duration", + cell: ({ + column, + getValue, + row: { + original: { status }, + }, + }) => ( + + ), + ...getColumnSortProps({ + "data-cy": "duration-sort-icon", + onSort: updateUrl, + }), + size: 400, + }, + ], + }; +};