diff --git a/src/components/Canary/CanaryTableColumns.tsx b/src/components/Canary/CanaryTableColumns.tsx new file mode 100644 index 000000000..6768ea776 --- /dev/null +++ b/src/components/Canary/CanaryTableColumns.tsx @@ -0,0 +1,145 @@ +import { CellContext, ColumnDef } from "@tanstack/react-table"; +import { HealthCheck } from "../../api/types/health"; +import { IoChevronForwardOutline } from "react-icons/io5"; +import { + LastTransistionCell, + LatencyCell, + TitleCell, + UptimeCell +} from "./Columns"; +import { Status } from "../Status"; +import { dateDiff } from "../../utils/date"; + +function ExpandArrow({ row }: CellContext) { + return row.getCanExpand() ? ( +
+
+ +
+
+ ) : null; +} + +type TableColumnsProps = { + showNamespaceTags: boolean; + hideNamespacePrefix: boolean; +}; + +export function getCanaryTableColumns({ + showNamespaceTags, + hideNamespacePrefix +}: TableColumnsProps): ColumnDef[] { + return [ + { + id: "expander", + cell: ExpandArrow, + enableSorting: false, + meta: { cellClassName: "py-2" } + }, + { + header: "Checks", + accessorKey: "name", + id: "name", + meta: { + cellClassName: "py-2 px-2 overflow-hidden overflow-ellipsis relative" + }, + enableSorting: true, + cell: ({ ...props }: CellContext) => ( + + ) + }, + { + header: "Health", + accessorKey: "status", + meta: { + cellClassName: "w-36 overflow-hidden overflow-ellipsis relative" + }, + enableSorting: true, + cell: ({ getValue, row }: CellContext) => { + const value = getValue(); + if (!value) { + return null; + } + if (typeof value === "object") { + return ; + } + const date = new Date().toISOString().split(".")[0]; + const lastRutime = row.original.lastRuntime; + const showTime = dateDiff(date, lastRutime, "minute") > 15; + return ( +
+ + {showTime && ( + + )} +
+ ); + } + }, + { + header: "Last Transition", + accessorKey: "last_transition_time", + meta: { + cellClassName: "w-36 overflow-hidden overflow-ellipsis relative" + }, + enableSorting: false, + cell: ({ getValue, row }: CellContext) => { + const value = getValue(); + if (!value) { + return null; + } + return ; + } + }, + { + header: "Uptime", + accessorKey: "uptime", + meta: { + cellClassName: "w-28 overflow-hidden overflow-ellipsis relative" + }, + enableSorting: true, + cell: ({ getValue, row }: CellContext) => { + if (row.getCanExpand()) { + const subRows = row.subRows; + const passed = subRows.filter((r) => r.original.uptime.passed).length; + + return ( + <> + {passed}/{subRows.length} + + ); + } + + const value = getValue(); + if (!value) { + return null; + } + const newValue = value?.uptime ?? value; + return ; + } + }, + { + header: "Latency", + accessorKey: "latency", + meta: { + cellClassName: "w-28 overflow-hidden overflow-ellipsis relative" + }, + enableSorting: true, + cell: ({ getValue }: CellContext) => { + const value = getValue(); + if (!value) { + return null; + } + return ; + } + } + ]; +} diff --git a/src/components/Canary/Columns/index.js b/src/components/Canary/Columns/index.tsx similarity index 78% rename from src/components/Canary/Columns/index.js rename to src/components/Canary/Columns/index.tsx index a70ade761..dcc836fbb 100644 --- a/src/components/Canary/Columns/index.js +++ b/src/components/Canary/Columns/index.tsx @@ -11,12 +11,14 @@ import style from "../index.module.css"; import { Status } from "../../Status"; import { dateDiff, relativeDateTime } from "../../../utils/date"; import clsx from "clsx"; +import { CellContext } from "@tanstack/react-table"; +import { HealthCheck } from "../../../api/types/health"; -export function Cell({ state, value, row, column }) { +export function Cell({ state, value, row, column }: any) { const { pivotCellType } = state; if (column.id === "name") { - return TitleCell({ row }); + return TitleCell({ row } as any); } if (pivotCellType === "uptime" || column.id === "uptime") { if (value == null) { @@ -66,7 +68,7 @@ export function Cell({ state, value, row, column }) { return null; } -export function LastTransistionCell({ value }) { +export function LastTransistionCell({ value }: any) { const hasAgoString = relativeDateTime(value).indexOf("ago") > -1; const hasYesterdayString = relativeDateTime(value).indexOf("yesterday") > -1; return ( @@ -89,27 +91,37 @@ export function LastTransistionCell({ value }) { ); } -export function HealthCell({ value }) { +export function HealthCell({ value }: any) { return ; } -export function UptimeCell({ value }) { +export function UptimeCell({ value }: any) { return ( ); } -export function LatencyCell({ value }) { +export function LatencyCell({ value }: any) { return ; } -export function TitleCell({ row }) { +type TitleCellProps = { + hideNamespacePrefix?: boolean; + showNamespaceTags?: boolean; +} & CellContext; + +export function TitleCell({ + row, + hideNamespacePrefix = false, + showNamespaceTags = false +}: TitleCellProps) { const rowValues = - row.original?.pivoted === true - ? row.original[row.original.valueLookup] ?? null + (row.original as any)?.pivoted === true + ? // @ts-expect-error + row.original[row.original.valueLookup] ?? null : row.original; let title = GetName(rowValues); - if (row.hideNamespacePrefix) { + if (hideNamespacePrefix) { title = removeNamespacePrefix(title, rowValues); } @@ -124,20 +136,22 @@ export function TitleCell({ row }) { - {row.canExpand && rowValues.subRows && rowValues?.subRows.length > 1 && ( - <span className="ml-1 flex items-center"> - <Badge - className="ml-1" - colorClass="bg-gray-200 text-gray-800" - roundedClass="rounded-xl" - text={rowValues?.subRows.length} - size="xs" - /> - </span> - )} - {row.showNamespaceTags ? ( + {row.getCanExpand() && + rowValues.subRows && + rowValues?.subRows.length > 1 && ( + <span className="ml-1 flex items-center"> + <Badge + className="ml-1" + colorClass="bg-gray-200 text-gray-800" + roundedClass="rounded-xl" + text={rowValues?.subRows.length} + size="xs" + /> + </span> + )} + {showNamespaceTags ? ( rowValues.namespaces ? ( <Badge className="ml-2" @@ -160,13 +174,13 @@ export function TitleCell({ row }) { ); } -function getSortString(original) { +function getSortString(original: any) { // case insensitive name sorting, with namespace as a secondary sort const sortString = `${original.sortKey?.toLowerCase()}${original.namespace?.toLowerCase()}`; return sortString; } -function getPivotSortValueOrDefault(a, b, pivotAccessor) { +function getPivotSortValueOrDefault(a: any, b: any, pivotAccessor: any) { if (pivotAccessor === "name") { const aValue = a.original?.pivoted === true @@ -195,9 +209,9 @@ function getPivotSortValueOrDefault(a, b, pivotAccessor) { return { aValue, bValue }; } -export function getSortType(pivotCellTypeOrAccessor, pivotAccessor) { - const sortTypes = { - latency: (a, b) => { +export function getSortType(pivotCellTypeOrAccessor: any, pivotAccessor: any) { + const sortTypes: any = { + latency: (a: any, b: any) => { const { aValue, bValue } = getPivotSortValueOrDefault( a, b, @@ -206,7 +220,7 @@ export function getSortType(pivotCellTypeOrAccessor, pivotAccessor) { return getLatency(aValue) < getLatency(bValue) ? -1 : 1; }, - uptime: (a, b) => { + uptime: (a: any, b: any) => { const { aValue, bValue } = getPivotSortValueOrDefault( a, b, @@ -214,7 +228,7 @@ export function getSortType(pivotCellTypeOrAccessor, pivotAccessor) { ); return getUptimeScore(aValue) < getUptimeScore(bValue) ? 1 : -1; }, - checkStatuses: (a, b) => { + checkStatuses: (a: any, b: any) => { const { aValue, bValue } = getPivotSortValueOrDefault( a, b, @@ -224,7 +238,7 @@ export function getSortType(pivotCellTypeOrAccessor, pivotAccessor) { ? 1 : -1; }, - name: (a, b) => { + name: (a: any, b: any) => { const { aValue, bValue } = getPivotSortValueOrDefault( a, b, @@ -234,7 +248,7 @@ export function getSortType(pivotCellTypeOrAccessor, pivotAccessor) { } }; return pivotAccessor === "name" - ? sortTypes[pivotAccessor] + ? sortTypes[pivotAccessor as any] : sortTypes[pivotCellTypeOrAccessor] ?? "alphanumeric"; } @@ -243,7 +257,7 @@ export function makeColumnsForPivot({ pivotCellType, firstColumns, cellClass = `px-5 py-2` -} = {}) { +}: any = {}) { // For syntax https://stackoverflow.com/a/26578323/15581317 const columns = []; for (const [, value] of pivotSet.entries()) { @@ -262,9 +276,12 @@ export function makeColumnsForPivot({ return [...firsts, ...columns]; } -export function getColumns({ columnObject, pivotCellType = null }) { - return Object.entries(columnObject).reduce((acc, [k, v], i) => { - const { Header, accessor, cellClass, Cell: IncomingCell, id } = v; +export function getColumns({ + columnObject, + pivotCellType = null +}: Record<any, any>) { + return Object.entries(columnObject).reduce((acc: any, [k, v], i) => { + const { Header, accessor, cellClass, Cell: IncomingCell, id } = v as any; const pivotAccessor = accessor == null || typeof accessor === "function" ? id == null diff --git a/src/components/Canary/Rows/lib.js b/src/components/Canary/Rows/lib.ts similarity index 85% rename from src/components/Canary/Rows/lib.js rename to src/components/Canary/Rows/lib.ts index 6c70dc926..11598bcf3 100644 --- a/src/components/Canary/Rows/lib.js +++ b/src/components/Canary/Rows/lib.ts @@ -1,9 +1,16 @@ -import { GetName } from "../data"; -import { removeNamespacePrefix } from "../utils"; +import { HealthCheck } from "../../../api/types/health"; import { isPlainObject } from "../../../lib/isPlainObject"; import { aggregate } from "../aggregate"; +import { GetName } from "../data"; +import { removeNamespacePrefix } from "../utils"; -export function makeRow({ row, hideNamespacePrefix = true } = {}) { +export function makeRow({ + row = {}, + hideNamespacePrefix = true +}: { + row: any; + hideNamespacePrefix?: boolean; +}) { return { ...row, name: GetName(row), @@ -14,9 +21,9 @@ export function makeRow({ row, hideNamespacePrefix = true } = {}) { }; } -export function filterRowsByPivotSet(rows, pivotSet) { - return Array.from(pivotSet).reduce((accPivots, pivot) => { - const filteredRows = rows.reduce((acc, row) => { +export function filterRowsByPivotSet(rows: any, pivotSet: any) { + return Array.from(pivotSet).reduce((accPivots: any, pivot: any) => { + const filteredRows = rows.reduce((acc: any, row: any) => { if (row[pivot] != null) { acc[acc.length] = row[pivot]; } @@ -26,18 +33,23 @@ export function filterRowsByPivotSet(rows, pivotSet) { accPivots[pivot] = aggregate(pivot, filteredRows); } return accPivots; - }, {}); + }, {} as Record<string, any>); } export function prepareRows({ - tableData, + tableData = [], hideNamespacePrefix = true, pivotBy = null, pivotLookup = null +}: { + tableData?: HealthCheck[]; + hideNamespacePrefix?: boolean; + pivotBy?: string | null; + pivotLookup?: string | null; }) { if (pivotBy == null || pivotBy === "none") { const values = tableData?.reduce( - (acc, row, i) => { + (acc: any, row, i) => { acc.rows[i] = makeRow({ row, hideNamespacePrefix }); return acc; }, @@ -48,7 +60,7 @@ export function prepareRows({ } if (pivotBy != null && pivotBy !== "none") { const values = tableData.reduce( - (acc, row) => { + (acc: any, row: any) => { const { rows: { length } } = acc; @@ -70,11 +82,11 @@ export function prepareRows({ if (subRows.rows.length === 0) { return acc; } - subRows.meta.pivotSet.forEach((value) => { + subRows.meta.pivotSet.forEach((value: any) => { acc.meta.pivotSet.add(value); }); const newRow = { ...row, subRows: subRows.rows }; - const aggregateFilters = filterRowsByPivotSet( + const aggregateFilters: any = filterRowsByPivotSet( subRows.rows, acc.meta.pivotSet ); @@ -110,7 +122,7 @@ export function prepareRows({ } return keyObjectAcc; }, - {} + {} as Record<string, any> ); if (Object.keys(keyObject).length === 0) { return acc; diff --git a/src/components/Canary/grouping.js b/src/components/Canary/grouping.js deleted file mode 100644 index 45d534e9a..000000000 --- a/src/components/Canary/grouping.js +++ /dev/null @@ -1,48 +0,0 @@ -// process table groupings, given a list of checks and a 'groupBy' object -export function getGroupedChecks(checks, groupBy) { - if ( - groupBy === "name" || - groupBy === "description" || - groupBy === "canary_name" - ) { - const groupedChecks = {}; - const groupNames = []; - checks.forEach((check) => { - const value = check[groupBy] || "(none)"; - if (groupNames.indexOf(value) === -1) { - groupNames.push(value); - groupedChecks[value] = []; - } - groupedChecks[value].push(check); - }); - return groupedChecks; - } - const groupedChecks = { Others: [] }; - const groupNames = ["Others"]; - checks.forEach((check) => { - let hasValidGroup = false; - let groupName; - - if (check.labels) { - const labelKeys = Object.keys(check.labels); - labelKeys.forEach((key) => { - // if current item has a matching label with the currently selected groupBy - if (key === groupBy) { - hasValidGroup = true; - groupName = check.labels[key]; - - if (groupNames.indexOf(groupName) === -1) { - groupNames.push(groupName); - groupedChecks[groupName] = []; - } - } - }); - } - if (hasValidGroup) { - groupedChecks[groupName].push(check); - } else { - groupedChecks.Others.push(check); - } - }); - return groupedChecks; -} diff --git a/src/components/Canary/grouping.ts b/src/components/Canary/grouping.ts new file mode 100644 index 000000000..2589a6997 --- /dev/null +++ b/src/components/Canary/grouping.ts @@ -0,0 +1,103 @@ +import { update } from "lodash"; +import { HealthCheck } from "../../api/types/health"; + +// process table groupings, given a list of checks and a 'groupBy' object +export function getGroupedChecks(checks: HealthCheck[] = [], groupBy?: string) { + if (groupBy === "description" || groupBy === "canary_name") { + const groupedChecks: Record<string, HealthCheck[]> = {}; + const groupNames: string[] = []; + checks.forEach((check) => { + const value = check[groupBy] || "(none)"; + if (groupNames.indexOf(value) === -1) { + groupNames.push(value); + groupedChecks[value] = []; + } + groupedChecks[value].push(check); + }); + return groupedChecks; + } + + if (groupBy === "name") { + // when grouping by name, we want to split name by /, and group each part + const groupedChecks: Record<string, HealthCheck[]> = {}; + checks.forEach((check) => { + const value = check[groupBy] || "(none)"; + if (!value.includes("/")) { + update(groupedChecks, value, (current) => { + if (!current) { + return [check]; + } + return [...current, check]; + }); + return; + } + const path = value.split("/")[0]; + update(groupedChecks, path, (current) => { + const updatedCheck = { + ...check, + // if the name is a path, we want to show the last part of the path + name: value.split("/", 1)[1], + originalName: value + }; + if (!current) { + return [updatedCheck]; + } + return [...current, updatedCheck]; + }); + }); + + const normalizedGroupedChecks: Record<string, HealthCheck[]> = {}; + + Object.entries(groupedChecks).forEach(([key, value]) => { + // if there are multiple checks with the same name, we show the name with + // the prefix of the path + if (value.length > 1) { + normalizedGroupedChecks[key] = value; + return; + } + // if the name is a path, and there is only one check with that name, we + // want to show the full name as it won't be grouped + if ((value?.[0] as any)?.originalName?.includes("/")) { + normalizedGroupedChecks[key] = value.map((check) => ({ + ...check, + // replace the name with the full path + name: (check as any).originalName + })); + return; + } + // if the name is not a path, we do nothing + normalizedGroupedChecks[key] = value; + }); + + return normalizedGroupedChecks; + } + + const groupedChecks: Record<string, HealthCheck[]> = { Others: [] }; + const groupNames = ["Others"]; + checks.forEach((check) => { + let hasValidGroup = false; + let groupName: any; + + if (check.labels) { + const labelKeys = Object.keys(check.labels); + labelKeys.forEach((key) => { + // if current item has a matching label with the currently selected groupBy + if (key === groupBy) { + hasValidGroup = true; + groupName = check.labels[key]; + + if (groupNames.indexOf(groupName) === -1) { + groupNames.push(groupName); + groupedChecks[groupName] = []; + } + } + }); + } + if (hasValidGroup) { + groupedChecks[groupName!].push(check); + } else { + groupedChecks.Others.push(check); + } + }); + return groupedChecks; +} diff --git a/src/components/Canary/table.tsx b/src/components/Canary/table.tsx index 11decb088..42dbb6ed1 100644 --- a/src/components/Canary/table.tsx +++ b/src/components/Canary/table.tsx @@ -1,20 +1,22 @@ +import { + SortingState, + flexRender, + getCoreRowModel, + getExpandedRowModel, + getGroupedRowModel, + getSortedRowModel, + useReactTable +} from "@tanstack/react-table"; +import clsx from "clsx"; import React, { useEffect, useMemo, useState } from "react"; import { TiArrowSortedDown, TiArrowSortedUp } from "react-icons/ti"; -import { useTable, useSortBy, useExpanded } from "react-table"; -import { getAggregatedGroupedChecks } from "./aggregate"; -import { getGroupedChecks } from "./grouping"; -import { getColumns, makeColumnsForPivot } from "./Columns"; -import { - decodeUrlSearchParams, - encodeObjectToUrlSearchParams, - useUpdateParams -} from "./url"; -import { columnObject, firstColumns } from "./Columns/columns"; -import { prepareRows } from "./Rows/lib"; -import { useCheckSetEqualityForPreviousVsCurrent } from "../Hooks/useCheckSetEqualityForPreviousVsCurrent"; import { useSearchParams } from "react-router-dom"; import { HealthCheck } from "../../api/types/health"; -import clsx from "clsx"; +import { getCanaryTableColumns } from "./CanaryTableColumns"; +import { useCheckSetEqualityForPreviousVsCurrent } from "../Hooks/useCheckSetEqualityForPreviousVsCurrent"; +import { prepareRows } from "./Rows/lib"; +import { getAggregatedGroupedChecks } from "./aggregate"; +import { getGroupedChecks } from "./grouping"; const styles = { outerDivClass: "border-l border-r border-gray-300 overflow-y-auto", @@ -31,13 +33,6 @@ const styles = { expandArrowIconClass: "ml-6 flex" }; -const sortByValidValues = new Map([ - ["name", null], - ["checkStatuses", null], - ["uptime", null], - ["latency", null] -]); - type CanaryChecksProps = { checks?: HealthCheck[]; labels?: string[]; @@ -82,7 +77,6 @@ export function CanaryTable({ }, [params, checks, groupBy, groupSingleItems]); const { rows, meta } = useMemo( - // @ts-expect-error () => prepareRows({ tableData, hideNamespacePrefix, pivotBy, pivotLookup }), [hideNamespacePrefix, tableData, pivotBy, pivotLookup] ); @@ -95,33 +89,14 @@ export function CanaryTable({ pivotBy != null && pivotBy !== "none"; - const columns = useMemo(() => { - if (shouldPivot) { - return makeColumnsForPivot({ - pivotSet: meta.pivotSet, - pivotCellType, - firstColumns - }); - } - return getColumns({ - columnObject, - pivotCellType: null - }); - // isNewPivotSet checks the old pivotSet against the new on, so it's a much better test - // than 'meta.pivotSet', which will probably be referentially new on every fetch of data. - // Testing the set directly meant we won't have to rebuild the columns, which is predicted to - // be an expensive operation. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pivotCellType, isNewPivotSet, pivotBy, pivotLookup]); - return ( <Table data={rows} - columns={columns} labels={labels} pivotCellType={shouldPivot ? pivotCellType : null} - onUnexpandableRowClick={onCheckClick} + onHealthCheckClick={onCheckClick} hasGrouping={groupBy !== "no-group"} + groupBy={groupBy} showNamespaceTags={showNamespaceTags} hideNamespacePrefix={hideNamespacePrefix} theadStyle={theadStyle} @@ -132,155 +107,127 @@ export function CanaryTable({ type TableProps = { data: any[]; - columns: any[]; labels?: string[]; pivotCellType: string | null; hasGrouping: boolean; - onUnexpandableRowClick: (check: HealthCheck) => void; + onHealthCheckClick: (check: HealthCheck) => void; showNamespaceTags?: boolean; hideNamespacePrefix?: boolean; theadStyle?: React.CSSProperties; + groupBy?: string; }; export function Table({ data, - columns, + labels, pivotCellType, hasGrouping = false, - onUnexpandableRowClick, + onHealthCheckClick, showNamespaceTags = false, hideNamespacePrefix = false, theadStyle = {}, + groupBy = "canary_name", ...rest }: TableProps) { + const [params, setParams] = useSearchParams(); + + const sortByValue = params.get("sortBy") || "name"; + const sortDesc = params.get("sortDesc") === "true" || false; + const rowFinder = (row: any) => { const rowValues = row?.pivoted === true ? row[row.valueLookup] ?? null : row; return rowValues?.subRows || []; }; - const { - state: tableState, - getTableProps, - getTableBodyProps, - headerGroups, - rows, - // @ts-expect-error - setSortBy, - prepareRow, - toggleHideColumn, - // @ts-expect-error - toggleRowExpanded - } = useTable( - { - columns, - data, - // @ts-expect-error - disableMultiSort: true, - autoResetSortBy: false, - autoResetExpanded: false, - getSubRows: rowFinder, - useControlledState: (state) => - useMemo( - () => ({ - ...state, - pivotCellType - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [state, pivotCellType] - ) - }, - useSortBy, - useExpanded - ); + // ensure sorting works when state isn't managed by caller + const [sortBy, setSortBy] = useState<SortingState>(); - // @ts-expect-error - const tableSortState = tableState?.sortBy; - - // Hide expander column if there is no grouping useEffect(() => { - toggleHideColumn("expander", !hasGrouping); - }, [hasGrouping, toggleHideColumn]); - - // Set table's sort state according to url params on page load or url change - useEffect(() => { - const searchParams = window.location.search; - const decodedParams = decodeUrlSearchParams(searchParams); - const { sortBy, sortDesc } = decodedParams; - - // check for validity, else reset to default values - const sortByChecked = sortByValidValues.has(sortBy) ? sortBy : "name"; - const sortDescChecked = typeof sortDesc === "boolean" ? sortDesc : false; - - setSortBy([{ id: sortByChecked, desc: sortDescChecked }]); - }, [setSortBy]); - - const updateParams = useUpdateParams(); + setSortBy([{ id: sortByValue, desc: sortDesc }]); + }, [setSortBy, sortByValue, sortDesc]); - // Table-state changes will trigger url changes - useEffect(() => { - const updateURLBySortState = (sortBy: string, sortDesc: boolean) => { - const searchParams = window.location.search; - const decodedParams = decodeUrlSearchParams(searchParams); - - // check for validity, else reset to default values - const sortByChecked = sortByValidValues.has(sortBy) ? sortBy : "name"; - const sortDescChecked = typeof sortDesc === "boolean" ? sortDesc : false; + const columns = useMemo( + () => + getCanaryTableColumns({ + showNamespaceTags, + hideNamespacePrefix + }), + [] + ); - const newFormState = { - ...decodedParams, - sortBy: sortByChecked, - sortDesc: sortDescChecked - }; - const encoded = encodeObjectToUrlSearchParams(newFormState); - if (window.location.search !== `?${encoded}`) { - updateParams(newFormState); + const table = useReactTable<HealthCheck>({ + columns: columns, + data, + enableMultiSort: false, + autoResetAll: false, + getSubRows: rowFinder, + getCoreRowModel: getCoreRowModel(), + getExpandedRowModel: hasGrouping ? getExpandedRowModel() : undefined, + getGroupedRowModel: hasGrouping ? getGroupedRowModel() : undefined, + getSortedRowModel: getSortedRowModel(), + enableSortingRemoval: true, + onSortingChange: (sorting) => { + if (typeof sorting === "function") { + const newSortBy = sorting(sortBy ?? []); + setSortBy(newSortBy); + if (newSortBy.length === 0) { + params.delete("sortBy"); + params.delete("sortDesc"); + } else { + params.set("sortBy", newSortBy[0].id); + params.set("sortDesc", newSortBy[0].desc.toString()); + } + setParams(params); + return; } - }; - - if (tableSortState.length > 0) { - updateURLBySortState(tableSortState[0].id, tableSortState[0].desc); + setSortBy(sorting); + if (sorting.length === 0) { + params.delete("sortBy"); + params.delete("sortDesc"); + } else { + params.set("sortBy", sorting[0].id); + params.set("sortDesc", sorting[0].desc.toString()); + } + setParams(params); + }, + enableHiding: true, + state: { + columnVisibility: hasGrouping ? { expander: true } : undefined, + sorting: sortBy } - }, [tableSortState, updateParams]); + }); return ( <div className={styles.outerDivClass} {...rest}> <div className={styles.topBgClass} /> - <table - className={styles.tableClass} - style={{ borderSpacing: "0" }} - {...getTableProps()} - > + <table className={styles.tableClass} style={{ borderSpacing: "0" }}> <thead className={styles.theadClass} style={theadStyle}> - {headerGroups.map((headerGroup) => ( - <tr - className={styles.theadRowClass} - {...headerGroup.getHeaderGroupProps()} - > + {table.getHeaderGroups().map((headerGroup) => ( + <tr className={styles.theadRowClass} key={headerGroup.id}> {headerGroup.headers.map((column) => ( <th className={clsx(styles.theadHeaderClass)} - // @ts-expect-error - {...column.getHeaderProps(column.getSortByToggleProps())} + key={column.id} // Table header onClick sorting override: // sortDesc cannot be null, only either true/false - onClick={() => - // @ts-expect-error - column.toggleSortBy( - // @ts-expect-error - column.isSortedDesc != null ? !column.isSortedDesc : false - ) - } + onClick={column.column.getToggleSortingHandler()} > - {/* @ts-expect-error */} - <div className={clsx("flex select-none", column.cellClass)}> - {column.render("Header")} + <div + className={clsx( + "flex select-none", + (column.column.columnDef?.meta as Record<string, string>) + ?.cellClass + )} + > + {flexRender( + column.column.columnDef.header, + column.getContext() + )} <span> - {/* @ts-expect-error */} - {column.isSorted ? ( - // @ts-expect-error - column.isSortedDesc ? ( + {column.column.getIsSorted() ? ( + column.column.getIsSorted() ? ( <TiArrowSortedUp /> ) : ( <TiArrowSortedDown /> @@ -295,42 +242,36 @@ export function Table({ </tr> ))} </thead> - <tbody className={styles.tbodyClass} {...getTableBodyProps()}> - {rows.length > 0 && - rows.map((row) => { - prepareRow(row); - // @ts-expect-error - row.showNamespaceTags = showNamespaceTags; - // @ts-expect-error - row.hideNamespacePrefix = hideNamespacePrefix; + <tbody className={styles.tbodyClass}> + {table.getRowModel().rows.length > 0 && + table.getRowModel().rows.map((row) => { return ( <tr - // @ts-expect-error key={row.id} className={`${styles.tbodyRowClass} ${ - // @ts-expect-error - row.canExpand ? styles.tbodyRowExpandableClass : "" + row.getCanExpand() ? styles.tbodyRowExpandableClass : "" }`} style={{}} onClick={ - // @ts-expect-error - row.canExpand - ? () => toggleRowExpanded(row.id) - : () => onUnexpandableRowClick(row.original) + row.getCanExpand() + ? () => row.getToggleExpandedHandler()() + : () => onHealthCheckClick(row.original) } - {...row.getRowProps()} > - {row.cells.map((cell) => ( + {row.getVisibleCells().map((cell) => ( <td - // @ts-expect-error - key={cell.column.Header} + key={cell.column.id} className={`${styles.tbodyDataClass} ${ - // @ts-expect-error - cell.column.cellClass || "" + cell.column || "" + } ${ + (cell.column.columnDef?.meta as Record<string, any>) + ?.cellClassName || "" }`} - {...cell.getCellProps()} > - {cell.render("Cell")} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} </td> ))} </tr> @@ -338,7 +279,7 @@ export function Table({ })} </tbody> </table> - {rows.length <= 0 && ( + {table.getRowModel().rows.length <= 0 && ( <div className="flex items-center justify-center py-20 px-2 border-b border-gray-300 text-center text-gray-400"> No data available </div>