vmui: refactor Cardinality panel (#2726)

* vmui: refactor Cardinality panel

* vmui: change width of the search panel

* vmui: code cleanup

* vmui: code cleanup

* vmui: fixed vulnerability (npm audit fix)

* wip

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Dmytro Kozlov 2022-06-14 14:39:47 +03:00 committed by GitHub
parent 7b3c9c50a8
commit af3dc91a51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 278 additions and 200 deletions

View File

@ -1,12 +1,12 @@
{
"files": {
"main.css": "./static/css/main.7e6d0c89.css",
"main.js": "./static/js/main.42cb1c78.js",
"main.js": "./static/js/main.a6bca65f.js",
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.7e6d0c89.css",
"static/js/main.42cb1c78.js"
"static/js/main.a6bca65f.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.42cb1c78.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.a6bca65f.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

View File

@ -17808,16 +17808,6 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/svgo/node_modules/nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
"dev": true,
"peer": true,
"dependencies": {
"boolbase": "~1.0.0"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@ -32702,7 +32692,7 @@
"boolbase": "^1.0.0",
"css-what": "^3.2.1",
"domutils": "^1.7.0",
"nth-check": "^1.0.2"
"nth-check": "^2.0.1"
}
},
"css-what": {
@ -32753,16 +32743,6 @@
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
"dev": true,
"peer": true,
"requires": {
"boolbase": "~1.0.0"
}
}
}
},

View File

@ -9,7 +9,7 @@ const BarChart: FC<BarChartProps> = ({
configs}) => {
const uPlotRef = useRef<HTMLDivElement>(null);
const [isPanning, setPanning] = useState(false);
const [isPanning] = useState(false);
const [uPlotInst, setUPlotInst] = useState<uPlot>();
const layoutSize = useResize(container);

View File

@ -20,6 +20,10 @@ export interface CardinalityConfiguratorProps {
query: string;
topN: number;
error?: ErrorTypes | string;
totalSeries: number;
totalLabelValuePairs: number;
date: string | null;
match: string | null;
}
const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
@ -29,7 +33,12 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
onSetHistory,
onRunQuery,
onSetQuery,
onTopNChange }) => {
onTopNChange,
totalSeries,
totalLabelValuePairs,
date,
match
}) => {
const dispatch = useAppDispatch();
const {queryControls: {autocomplete}} = useAppState();
const {queryOptions} = useFetchQueryOptions();
@ -41,36 +50,40 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
return <Box boxShadow="rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;" p={4} pb={2} mb={2}>
<Box>
<Box display="grid" gridTemplateColumns="1fr auto auto" gap="4px" width="100%" mb={0}>
<Box display="grid" gridTemplateColumns="1fr auto auto" gap="4px" width="50%" mb={4}>
<QueryEditor
query={query} index={0} autocomplete={autocomplete} queryOptions={queryOptions}
error={error} setHistoryIndex={onSetHistory} runQuery={onRunQuery} setQuery={onSetQuery}
label={"Arbitrary time series selector"}
/>
<Tooltip title="Execute Query">
<IconButton onClick={onRunQuery} sx={{height: "49px", width: "49px"}}>
<PlayCircleOutlineIcon/>
</IconButton>
</Tooltip>
<Box display="flex" alignItems="center">
<Box ml={2}>
<TextField
label="Number of top entries"
type="number"
size="small"
variant="outlined"
value={topN}
error={topN < 1}
helperText={topN < 1 ? "Number must be bigger than zero" : " "}
onChange={onTopNChange}/>
</Box>
<Tooltip title="Execute Query">
<IconButton onClick={onRunQuery} sx={{height: "49px", width: "49px"}}>
<PlayCircleOutlineIcon/>
</IconButton>
</Tooltip>
<Box>
<FormControlLabel label="Enable autocomplete"
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/>
</Box>
</Box>
</Box>
</Box>
<Box display="flex" alignItems="center" mt={3} mr={"53px"}>
<Box>
<FormControlLabel label="Enable autocomplete"
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/>
</Box>
<Box ml={2}>
<TextField
label="Number of top entries"
type="number"
size="small"
variant="outlined"
value={topN}
error={topN < 1}
helperText={topN < 1 ? "Number must be bigger than zero" : " "}
onChange={onTopNChange}/>
</Box>
<Box>
Analyzed <b>{totalSeries}</b> series and <b>{totalLabelValuePairs}</b> label=value pairs
at <b>{date}</b> {match && <span>for series selector <b>{match}</b></span>}. Show top {topN} entries per table.
</Box>
</Box>;
};

View File

@ -1,26 +1,22 @@
import React, {ChangeEvent, FC, useState} from "react";
import {SyntheticEvent} from "react";
import {Typography, Grid, Alert, Box, Tabs, Tab, Tooltip} from "@mui/material";
import TableChartIcon from "@mui/icons-material/TableChart";
import ShowChartIcon from "@mui/icons-material/ShowChart";
import {Alert} from "@mui/material";
import {useFetchQuery} from "../../hooks/useCardinalityFetch";
import EnhancedTable from "../Table/Table";
import {TSDBStatus, TopHeapEntry, DefaultState, Tabs as TabsType, Containers} from "./types";
import {
defaultHeadCells,
headCellsWithProgress,
LABEL_VALUE_PAIR_CONTENT_TITLE,
LABEL_VALUE_PAIRS_TABLE_HEADERS,
LABEL_WITH_UNIQUE_VALUES_TABLE_HEADERS,
LABELS_CONTENT_TITLE, METRICS_TABLE_HEADERS,
SERIES_CONTENT_TITLE,
SPINNER_TITLE,
spinnerContainerStyles
} from "./consts";
import {defaultProperties, progressCount, queryUpdater, tableTitles} from "./helpers";
import {defaultProperties, queryUpdater} from "./helpers";
import {Data} from "../Table/types";
import BarChart from "../BarChart/BarChart";
import CardinalityConfigurator from "./CardinalityConfigurator/CardinalityConfigurator";
import {barOptions} from "../BarChart/consts";
import Spinner from "../common/Spinner";
import TabPanel from "../TabPanel/TabPanel";
import {useCardinalityDispatch, useCardinalityState} from "../../state/cardinality/CardinalityStateContext";
import {tableCells} from "./TableCells/TableCells";
import MetricsContent from "./MetricsContent/MetricsContent";
const CardinalityPanel: FC = () => {
const cardinalityDispatch = useCardinalityDispatch();
@ -84,72 +80,45 @@ const CardinalityPanel: FC = () => {
</Alert>}
/>}
<CardinalityConfigurator error={configError} query={query} onRunQuery={onRunQuery} onSetQuery={onSetQuery}
onSetHistory={onSetHistory} onTopNChange={onTopNChange} topN={topN} />
onSetHistory={onSetHistory} onTopNChange={onTopNChange} topN={topN} date={date} match={match}
totalSeries={tsdbStatus.totalSeries} totalLabelValuePairs={tsdbStatus.totalLabelValuePairs}/>
{error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>}
{<Box m={2}>
Analyzed <b>{tsdbStatus.totalSeries}</b> series and <b>{tsdbStatus.totalLabelValuePairs}</b> label=value pairs
at <b>{date}</b> {match && <span>for series selector <b>{match}</b></span>}. Show top {topN} entries per table.
</Box>}
{Object.keys(tsdbStatus).map((key ) => {
if (key == "totalSeries" || key == "totalLabelValuePairs") return null;
const tableTitle = tableTitles[key];
const rows = tsdbStatus[key as keyof TSDBStatus] as unknown as Data[];
rows.forEach((row) => {
progressCount(tsdbStatus.totalSeries, key, row);
row.actions = "0";
});
const headerCells = (key == "seriesCountByMetricName" || key == "seriesCountByLabelValuePair") ? headCellsWithProgress : defaultHeadCells;
return (
<>
<Grid container spacing={2} sx={{px: 2}}>
<Grid item xs={12} md={12} lg={12} key={key}>
<Typography gutterBottom variant="h5" component="h5">
{tableTitle}
</Typography>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={stateTabs[key as keyof DefaultState]}
onChange={handleTabChange} aria-label="basic tabs example">
{defaultProps.tabs[key as keyof TabsType].map((title: string, i: number) =>
<Tab
key={title}
label={title}
aria-controls={`tabpanel-${i}`}
id={key}
iconPosition={"start"}
icon={ i === 0 ? <TableChartIcon /> : <ShowChartIcon /> } />
)}
</Tabs>
</Box>
{defaultProps.tabs[key as keyof TabsType].map((_,idx) =>
<div
ref={defaultProps.containerRefs[key as keyof Containers<HTMLDivElement>]}
style={{width: "100%", paddingRight: idx !== 0 ? "40px" : 0 }} key={`${key}-${idx}`}>
<TabPanel value={stateTabs[key as keyof DefaultState]} index={idx}>
{stateTabs[key as keyof DefaultState] === 0 ? <EnhancedTable
rows={rows}
headerCells={headerCells}
defaultSortColumn={"value"}
tableCells={(row) => tableCells(row,date,handleFilterClick(key))}
/>: <BarChart
data={[
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
rows.map((v) => v.name),
rows.map((v) => v.value),
rows.map((_, i) => i % 12 == 0 ? 1 : i % 10 == 0 ? 2 : 0),
]}
container={defaultProps.containerRefs[key as keyof Containers<HTMLDivElement>]?.current}
configs={barOptions}
/>}
</TabPanel>
</div>
)}
</Grid>
</Grid>
</>
);
})}
<MetricsContent
activeTab={stateTabs.seriesCountByMetricName}
rows={tsdbStatus.seriesCountByMetricName as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick("seriesCountByMetricName")}
tabs={defaultProps.tabs.seriesCountByMetricName}
chartContainer={defaultProps.containerRefs.seriesCountByMetricName}
totalSeries={tsdbStatus.totalSeries}
tabId={"seriesCountByMetricName"}
sectionTitle={SERIES_CONTENT_TITLE}
tableHeaderCells={METRICS_TABLE_HEADERS}
/>
<MetricsContent
activeTab={stateTabs.seriesCountByLabelValuePair}
rows={tsdbStatus.seriesCountByLabelValuePair as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick("seriesCountByLabelValuePair")}
tabs={defaultProps.tabs.seriesCountByLabelValuePair}
chartContainer={defaultProps.containerRefs.seriesCountByLabelValuePair}
totalSeries={tsdbStatus.totalSeries}
tabId={"seriesCountByLabelValuePair"}
sectionTitle={LABEL_VALUE_PAIR_CONTENT_TITLE}
tableHeaderCells={LABEL_VALUE_PAIRS_TABLE_HEADERS}
/>
<MetricsContent
activeTab={stateTabs.labelValueCountByLabelName}
rows={tsdbStatus.labelValueCountByLabelName as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick("labelValueCountByLabelName")}
tabs={defaultProps.tabs.labelValueCountByLabelName}
chartContainer={defaultProps.containerRefs.labelValueCountByLabelName}
totalSeries={-1}
tabId={"labelValueCountByLabelName"}
sectionTitle={LABELS_CONTENT_TITLE}
tableHeaderCells={LABEL_WITH_UNIQUE_VALUES_TABLE_HEADERS}
/>
</>
);
};

View File

@ -0,0 +1,96 @@
import {FC} from "react";
import {Box, Grid, Tab, Tabs, Typography} from "@mui/material";
import TableChartIcon from "@mui/icons-material/TableChart";
import ShowChartIcon from "@mui/icons-material/ShowChart";
import TabPanel from "../../TabPanel/TabPanel";
import EnhancedTable from "../../Table/Table";
import TableCells from "../TableCells/TableCells";
import BarChart from "../../BarChart/BarChart";
import {barOptions} from "../../BarChart/consts";
import React, {SyntheticEvent} from "react";
import {Data, HeadCell} from "../../Table/types";
import {MutableRef} from "preact/hooks";
interface MetricsProperties {
rows: Data[];
activeTab: number;
onChange: (e: SyntheticEvent, newValue: number) => void;
onActionClick: (e: SyntheticEvent) => void;
tabs: string[];
chartContainer: MutableRef<HTMLDivElement> | undefined;
totalSeries: number,
tabId: string;
sectionTitle: string;
tableHeaderCells: HeadCell[];
}
const MetricsContent: FC<MetricsProperties> = ({
rows,
activeTab,
onChange,
tabs,
chartContainer,
totalSeries,
tabId,
onActionClick,
sectionTitle,
tableHeaderCells
}) => {
const tableCells = (row: Data) => (
<TableCells
row={row}
totalSeries={totalSeries}
onActionClick={onActionClick}
/>
);
return (
<>
<Grid container spacing={2} sx={{px: 2}}>
<Grid item xs={12} md={12} lg={12}>
<Typography gutterBottom variant="h5" component="h5">{sectionTitle}</Typography>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={activeTab}
onChange={onChange} aria-label="basic tabs example">
{tabs.map((title: string, i: number) =>
<Tab
key={title}
label={title}
aria-controls={`tabpanel-${i}`}
id={tabId}
iconPosition={"start"}
icon={ i === 0 ? <TableChartIcon /> : <ShowChartIcon /> } />
)}
</Tabs>
</Box>
{tabs.map((_,idx) =>
<div
ref={chartContainer}
style={{width: "100%", paddingRight: idx !== 0 ? "40px" : 0 }} key={`chart-${idx}`}>
<TabPanel value={activeTab} index={idx}>
{activeTab === 0 ? <EnhancedTable
rows={rows}
headerCells={tableHeaderCells}
defaultSortColumn={"value"}
tableCells={tableCells}
/>: <BarChart
data={[
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
rows.map((v) => v.name),
rows.map((v) => v.value),
rows.map((_, i) => i % 12 == 0 ? 1 : i % 10 == 0 ? 2 : 0),
]}
container={chartContainer?.current || null}
configs={barOptions}
/>}
</TabPanel>
</div>
)}
</Grid>
</Grid>
</>
);
};
export default MetricsContent;

View File

@ -1,50 +1,39 @@
import {SyntheticEvent} from "react";
import React, {FC} from "preact/compat";
import {TableCell, ButtonGroup} from "@mui/material";
import {Data} from "../../Table/types";
import {BorderLinearProgressWithLabel} from "../../BorderLineProgress/BorderLinearProgress";
import React from "preact/compat";
import IconButton from "@mui/material/IconButton";
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
import Tooltip from "@mui/material/Tooltip";
import {SyntheticEvent} from "react";
import dayjs from "dayjs";
export const tableCells = (
interface CardinalityTableCells {
row: Data,
date: string | null,
onFilterClick: (e: SyntheticEvent) => void) => {
const pathname = window.location.pathname;
const withday = dayjs(date).add(1, "day").toDate();
return Object.keys(row).map((key, idx) => {
if (idx === 0) {
return (<TableCell component="th" scope="row" key={key}>
{row[key as keyof Data]}
</TableCell>);
}
if (key === "progressValue") {
return (
<TableCell key={key}>
<BorderLinearProgressWithLabel
variant="determinate"
value={row[key as keyof Data] as number}
/>
</TableCell>
);
}
if (key === "actions") {
const title = `Filter by ${row.name}`;
return (<TableCell key={key}>
<ButtonGroup variant="contained">
<Tooltip title={title}>
<IconButton
id={row.name}
onClick={onFilterClick}
sx={{height: "20px", width: "20px"}}>
<PlayCircleOutlineIcon/>
</IconButton>
</Tooltip>
</ButtonGroup>
</TableCell>);
}
return (<TableCell key={key}>{row[key as keyof Data]}</TableCell>);
});
totalSeries: number;
onActionClick: (e: SyntheticEvent) => void;
}
const TableCells: FC<CardinalityTableCells> = ({ row, totalSeries, onActionClick }) => {
const progress = totalSeries > 0 ? row.value / totalSeries * 100 : -1;
return <>
<TableCell key={row.name}>{row.name}</TableCell>
<TableCell key={row.value}>{row.value}</TableCell>
{progress > 0 ? <TableCell key={row.progressValue}>
<BorderLinearProgressWithLabel variant="determinate" value={progress} />
</TableCell> : null}
<TableCell key={"action"}>
<ButtonGroup variant="contained">
<Tooltip title={`Filter by ${row.name}`}>
<IconButton
id={row.name}
onClick={onActionClick}
sx={{height: "20px", width: "20px"}}>
<PlayCircleOutlineIcon/>
</IconButton>
</Tooltip>
</ButtonGroup>
</TableCell>
</>;
};
export default TableCells;

View File

@ -1,16 +1,16 @@
import {HeadCell} from "../Table/types";
export const headCellsWithProgress = [
export const METRICS_TABLE_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Name",
label: "Metrics name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Value",
label: "Number of series",
numeric: false,
},
{
@ -25,9 +25,55 @@ export const headCellsWithProgress = [
label: "Action",
numeric: false,
}
] as HeadCell[];
]as HeadCell[];
export const defaultHeadCells = headCellsWithProgress.filter((head) => head.id!=="percentage");
export const LABEL_VALUE_PAIRS_TABLE_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Lable=value pair",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of total label value pairs",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
]as HeadCell[];
export const LABEL_WITH_UNIQUE_VALUES_TABLE_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of unique values",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
export const spinnerContainerStyles = (height: string) => {
return {
@ -41,4 +87,8 @@ export const spinnerContainerStyles = (height: string) => {
};
};
export const SPINNER_TITLE = "Please wait while cardinality stats is calculated. This may take some time if the db contains big number of time series";
export const SPINNER_TITLE = "Please wait while cardinality stats is calculated. " +
"This may take some time if the db contains big number of time series";
export const SERIES_CONTENT_TITLE = "Metric names with the highest number of series";
export const LABEL_VALUE_PAIR_CONTENT_TITLE = "Label=value pairs with the highest number of series";
export const LABELS_CONTENT_TITLE = "Labels with the highest number of unique values";

View File

@ -1,13 +1,6 @@
import {Containers, DefaultState, QueryUpdater, Tabs, TSDBStatus, TypographyFunctions} from "./types";
import {Data} from "../Table/types";
import {Containers, DefaultState, QueryUpdater, Tabs, TSDBStatus} from "./types";
import {useRef} from "preact/compat";
export const tableTitles: {[key: string]: string} = {
"seriesCountByMetricName": "Metric names with the highest number of series",
"seriesCountByLabelValuePair": "Label=value pairs with the highest number of series",
"labelValueCountByLabelName": "Labels with the highest number of unique values",
};
export const queryUpdater: QueryUpdater = {
labelValueCountByLabelName: (query: string): string => `{${query}!=""}`,
seriesCountByLabelValuePair: (query: string): string => {
@ -25,14 +18,6 @@ const getSeriesSelector = (label: string, value: string): string => {
return "{" + label + "=" + JSON.stringify(value) + "}";
};
export const progressCount = (totalSeries: number, key: string, row: Data): Data => {
if (key === "seriesCountByMetricName" || key === "seriesCountByLabelValuePair") {
row.progressValue = row.value / totalSeries * 100;
return row;
}
return row;
};
export const defaultProperties = (tsdbStatus: TSDBStatus) => {
return Object.keys(tsdbStatus).reduce((acc, key) => {
if (key === "totalSeries" || key === "totalLabelValuePairs") return acc;

View File

@ -13,10 +13,6 @@ export interface TopHeapEntry {
count: number;
}
export type TypographyFunctions = {
[key: string]: (value: number) => string,
}
export type QueryUpdater = {
[key: string]: (query: string) => string,
}

View File

@ -1,6 +1,6 @@
import {Box, Paper, Table, TableBody, TableCell, TableContainer, TablePagination, TableRow,} from "@mui/material";
import React, {FC, useState} from "preact/compat";
import {ChangeEvent, MouseEvent, SyntheticEvent} from "react";
import {ChangeEvent, MouseEvent} from "react";
import {Data, Order, TableProps,} from "./types";
import {EnhancedTableHead} from "./TableHead";
import {getComparator, stableSort} from "./helpers";
@ -37,7 +37,7 @@ const EnhancedTable: FC<TableProps> = ({
setSelected([]);
};
const handleClick = (event: SyntheticEvent, name: string) => {
const handleClick = (name: string) => () => {
const selectedIndex = selected.indexOf(name);
let newSelected: readonly string[] = [];
@ -101,7 +101,7 @@ const EnhancedTable: FC<TableProps> = ({
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.name)}
onClick={handleClick(row.name)}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}

View File

@ -10,7 +10,7 @@ export function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
return 0;
}
export function getComparator<Key extends keyof any>(
export function getComparator<Key extends (string | number | symbol)>(
order: Order,
orderBy: Key,
): (

View File

@ -23,7 +23,7 @@ export interface TableProps {
rows: Data[];
headerCells: HeadCell[],
defaultSortColumn: keyof Data,
tableCells: (row: Data) => ReactNode[],
tableCells: (row: Data) => ReactNode,
isPagingEnabled?: boolean,
}