mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Completely refactor DataTable, show query infos and warnings
Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
0f951774b8
commit
f30a58afaf
|
@ -7,6 +7,7 @@ export type SuccessAPIResponse<T> = {
|
||||||
status: "success";
|
status: "success";
|
||||||
data: T;
|
data: T;
|
||||||
warnings?: string[];
|
warnings?: string[];
|
||||||
|
infos?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ErrorAPIResponse = {
|
export type ErrorAPIResponse = {
|
||||||
|
|
|
@ -1,17 +1,8 @@
|
||||||
import {
|
import { FC, ReactNode, useState } from "react";
|
||||||
FC,
|
|
||||||
ReactNode,
|
|
||||||
useEffect,
|
|
||||||
useId,
|
|
||||||
useLayoutEffect,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Alert,
|
Alert,
|
||||||
Skeleton,
|
|
||||||
Box,
|
Box,
|
||||||
LoadingOverlay,
|
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Group,
|
Group,
|
||||||
|
@ -26,7 +17,6 @@ import {
|
||||||
RangeSamples,
|
RangeSamples,
|
||||||
} from "../../api/responseTypes/query";
|
} from "../../api/responseTypes/query";
|
||||||
import SeriesName from "./SeriesName";
|
import SeriesName from "./SeriesName";
|
||||||
import { useAPIQuery } from "../../api/api";
|
|
||||||
import classes from "./DataTable.module.css";
|
import classes from "./DataTable.module.css";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
@ -35,13 +25,10 @@ import HistogramChart from "./HistogramChart";
|
||||||
import { Histogram } from "../../types/types";
|
import { Histogram } from "../../types/types";
|
||||||
import { bucketRangeString } from "./HistogramHelpers";
|
import { bucketRangeString } from "./HistogramHelpers";
|
||||||
import { useSettings } from "../../state/settingsSlice";
|
import { useSettings } from "../../state/settingsSlice";
|
||||||
import { useAppDispatch, useAppSelector } from "../../state/hooks";
|
|
||||||
import { setVisualizer } from "../../state/queryPageSlice";
|
|
||||||
import TimeInput from "./TimeInput";
|
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
const maxFormattableSeries = 1000;
|
const maxFormattableSeries = 10;
|
||||||
const maxDisplayableSeries = 10000;
|
const maxDisplayableSeries = 100;
|
||||||
|
|
||||||
const limitSeries = <S extends InstantSample | RangeSamples>(
|
const limitSeries = <S extends InstantSample | RangeSamples>(
|
||||||
series: S[],
|
series: S[],
|
||||||
|
@ -54,256 +41,139 @@ const limitSeries = <S extends InstantSample | RangeSamples>(
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface DataTableProps {
|
export interface DataTableProps {
|
||||||
panelIdx: number;
|
data: InstantQueryResult;
|
||||||
retriggerIdx: number;
|
limitResults: boolean;
|
||||||
|
setLimitResults: (limit: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataTable: FC<DataTableProps> = ({ panelIdx, retriggerIdx }) => {
|
const DataTable: FC<DataTableProps> = ({
|
||||||
|
data,
|
||||||
|
limitResults,
|
||||||
|
setLimitResults,
|
||||||
|
}) => {
|
||||||
const [scale, setScale] = useState<string>("exponential");
|
const [scale, setScale] = useState<string>("exponential");
|
||||||
const [limitResults, setLimitResults] = useState<boolean>(true);
|
|
||||||
const [responseTime, setResponseTime] = useState<number>(0);
|
|
||||||
|
|
||||||
const { expr, visualizer } = useAppSelector(
|
|
||||||
(state) => state.queryPage.panels[panelIdx]
|
|
||||||
);
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const { endTime, range } = visualizer;
|
|
||||||
|
|
||||||
const { data, error, isFetching, isLoading, refetch } =
|
|
||||||
useAPIQuery<InstantQueryResult>({
|
|
||||||
key: useId(),
|
|
||||||
path: "/query",
|
|
||||||
params: {
|
|
||||||
query: expr,
|
|
||||||
time: `${(endTime !== null ? endTime : Date.now()) / 1000}`,
|
|
||||||
},
|
|
||||||
enabled: expr !== "",
|
|
||||||
recordResponseTime: setResponseTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
expr !== "" && refetch();
|
|
||||||
}, [retriggerIdx, refetch, expr, endTime]);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
setLimitResults(true);
|
|
||||||
}, [data, isFetching]);
|
|
||||||
|
|
||||||
const { useLocalTime } = useSettings();
|
const { useLocalTime } = useSettings();
|
||||||
|
|
||||||
// Show a skeleton only on the first load, not on subsequent ones.
|
const { result, resultType } = data;
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
{Array.from(Array(5), (_, i) => (
|
|
||||||
<Skeleton key={i} height={30} mb={15} />
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<Alert
|
|
||||||
color="red"
|
|
||||||
title="Error executing query"
|
|
||||||
icon={<IconAlertTriangle size={14} />}
|
|
||||||
>
|
|
||||||
{error.message}
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data === undefined) {
|
|
||||||
return <Alert variant="transparent">No data queried yet</Alert>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result, resultType } = data.data;
|
|
||||||
|
|
||||||
if (result.length === 0) {
|
|
||||||
return (
|
|
||||||
<Alert title="Empty query result" icon={<IconInfoCircle size={14} />}>
|
|
||||||
This query returned no data.
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const doFormat = result.length <= maxFormattableSeries;
|
const doFormat = result.length <= maxFormattableSeries;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg" mt="sm">
|
<Stack gap="lg" mt={0}>
|
||||||
{isLoading ? (
|
{limitResults &&
|
||||||
<Box>
|
["vector", "matrix"].includes(resultType) &&
|
||||||
{Array.from(Array(5), (_, i) => (
|
result.length > maxDisplayableSeries && (
|
||||||
<Skeleton key={i} height={30} mb={15} />
|
<Alert
|
||||||
))}
|
color="orange"
|
||||||
</Box>
|
icon={<IconAlertTriangle size={14} />}
|
||||||
) : data === undefined ? (
|
title="Showing limited results"
|
||||||
<Alert variant="transparent">No data queried yet</Alert>
|
>
|
||||||
) : result.length === 0 ? (
|
Fetched {data.result.length} metrics, only displaying first{" "}
|
||||||
<Alert title="Empty query result" icon={<IconInfoCircle size={14} />}>
|
{maxDisplayableSeries} for performance reasons.
|
||||||
This query returned no data.
|
<Anchor ml="md" fz="1em" onClick={() => setLimitResults(false)}>
|
||||||
</Alert>
|
Show all results
|
||||||
) : (
|
</Anchor>
|
||||||
<>
|
</Alert>
|
||||||
<Group justify="space-between">
|
)}
|
||||||
<TimeInput
|
|
||||||
time={endTime}
|
|
||||||
range={range}
|
|
||||||
description="Evaluation time"
|
|
||||||
onChangeTime={(time) =>
|
|
||||||
dispatch(
|
|
||||||
setVisualizer({
|
|
||||||
idx: panelIdx,
|
|
||||||
visualizer: { ...visualizer, endTime: time },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Text size="xs" c="gray">
|
|
||||||
Load time: {responseTime}ms   Result series: {result.length}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
{limitResults &&
|
{!doFormat && (
|
||||||
["vector", "matrix"].includes(resultType) &&
|
<Alert
|
||||||
result.length > maxDisplayableSeries && (
|
title="Formatting turned off"
|
||||||
|
icon={<IconInfoCircle size={14} />}
|
||||||
|
>
|
||||||
|
Showing more than {maxFormattableSeries} series, turning off label
|
||||||
|
formatting to improve rendering performance.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box pos="relative" className={classes.tableWrapper}>
|
||||||
|
<Table fz="xs">
|
||||||
|
<Table.Tbody>
|
||||||
|
{resultType === "vector" ? (
|
||||||
|
limitSeries<InstantSample>(result, limitResults).map((s, idx) => (
|
||||||
|
<Table.Tr key={idx}>
|
||||||
|
<Table.Td>
|
||||||
|
<SeriesName labels={s.metric} format={doFormat} />
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td className={classes.numberCell}>
|
||||||
|
{s.value && s.value[1]}
|
||||||
|
{s.histogram && (
|
||||||
|
<Stack>
|
||||||
|
<HistogramChart
|
||||||
|
histogram={s.histogram[1]}
|
||||||
|
index={idx}
|
||||||
|
scale={scale}
|
||||||
|
/>
|
||||||
|
<Group justify="space-between" align="center" p={10}>
|
||||||
|
<Group align="center" gap="1rem">
|
||||||
|
<span>
|
||||||
|
<strong>Count:</strong> {s.histogram[1].count}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<strong>Sum:</strong> {s.histogram[1].sum}
|
||||||
|
</span>
|
||||||
|
</Group>
|
||||||
|
<Group align="center" gap="1rem">
|
||||||
|
<span>x-axis scale:</span>
|
||||||
|
<SegmentedControl
|
||||||
|
size={"xs"}
|
||||||
|
value={scale}
|
||||||
|
onChange={setScale}
|
||||||
|
data={["exponential", "linear"]}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
{histogramTable(s.histogram[1])}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))
|
||||||
|
) : resultType === "matrix" ? (
|
||||||
|
limitSeries<RangeSamples>(result, limitResults).map((s, idx) => (
|
||||||
|
<Table.Tr key={idx}>
|
||||||
|
<Table.Td>
|
||||||
|
<SeriesName labels={s.metric} format={doFormat} />
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td className={classes.numberCell}>
|
||||||
|
{s.values &&
|
||||||
|
s.values.map((v, idx) => (
|
||||||
|
<div key={idx}>
|
||||||
|
{v[1]}{" "}
|
||||||
|
<Text
|
||||||
|
span
|
||||||
|
c="gray.7"
|
||||||
|
size="1em"
|
||||||
|
title={formatTimestamp(v[0], useLocalTime)}
|
||||||
|
>
|
||||||
|
@ {v[0]}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))
|
||||||
|
) : resultType === "scalar" ? (
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td>Scalar value</Table.Td>
|
||||||
|
<Table.Td className={classes.numberCell}>{result[1]}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
) : resultType === "string" ? (
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td>String value</Table.Td>
|
||||||
|
<Table.Td>{result[1]}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
) : (
|
||||||
<Alert
|
<Alert
|
||||||
color="red"
|
color="red"
|
||||||
|
title="Invalid query response"
|
||||||
icon={<IconAlertTriangle size={14} />}
|
icon={<IconAlertTriangle size={14} />}
|
||||||
title="Showing limited results"
|
|
||||||
>
|
>
|
||||||
Fetched {data.data.result.length} metrics, only displaying first{" "}
|
Invalid result value type
|
||||||
{maxDisplayableSeries} for performance reasons.
|
|
||||||
<Anchor ml="md" fz="1em" onClick={() => setLimitResults(false)}>
|
|
||||||
Show all results
|
|
||||||
</Anchor>
|
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
{!doFormat && (
|
</Table>
|
||||||
<Alert
|
</Box>
|
||||||
title="Formatting turned off"
|
|
||||||
icon={<IconInfoCircle size={14} />}
|
|
||||||
>
|
|
||||||
Showing more than {maxFormattableSeries} series, turning off label
|
|
||||||
formatting for performance reasons.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box pos="relative" className={classes.tableWrapper}>
|
|
||||||
<LoadingOverlay
|
|
||||||
visible={isFetching}
|
|
||||||
zIndex={1000}
|
|
||||||
overlayProps={{ radius: "sm", blur: 1 }}
|
|
||||||
loaderProps={{
|
|
||||||
children: <Skeleton m={0} w="100%" h="100%" />,
|
|
||||||
}}
|
|
||||||
styles={{ loader: { width: "100%", height: "100%" } }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Table fz="xs">
|
|
||||||
<Table.Tbody>
|
|
||||||
{resultType === "vector" ? (
|
|
||||||
limitSeries<InstantSample>(result, limitResults).map(
|
|
||||||
(s, idx) => (
|
|
||||||
<Table.Tr key={idx}>
|
|
||||||
<Table.Td>
|
|
||||||
<SeriesName labels={s.metric} format={doFormat} />
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td className={classes.numberCell}>
|
|
||||||
{s.value && s.value[1]}
|
|
||||||
{s.histogram && (
|
|
||||||
<Stack>
|
|
||||||
<HistogramChart
|
|
||||||
histogram={s.histogram[1]}
|
|
||||||
index={idx}
|
|
||||||
scale={scale}
|
|
||||||
/>
|
|
||||||
<Group
|
|
||||||
justify="space-between"
|
|
||||||
align="center"
|
|
||||||
p={10}
|
|
||||||
>
|
|
||||||
<Group align="center" gap="1rem">
|
|
||||||
<span>
|
|
||||||
<strong>Count:</strong>{" "}
|
|
||||||
{s.histogram[1].count}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<strong>Sum:</strong> {s.histogram[1].sum}
|
|
||||||
</span>
|
|
||||||
</Group>
|
|
||||||
<Group align="center" gap="1rem">
|
|
||||||
<span>x-axis scale:</span>
|
|
||||||
<SegmentedControl
|
|
||||||
size={"xs"}
|
|
||||||
value={scale}
|
|
||||||
onChange={setScale}
|
|
||||||
data={["exponential", "linear"]}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
{histogramTable(s.histogram[1])}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) : resultType === "matrix" ? (
|
|
||||||
limitSeries<RangeSamples>(result, limitResults).map(
|
|
||||||
(s, idx) => (
|
|
||||||
<Table.Tr key={idx}>
|
|
||||||
<Table.Td>
|
|
||||||
<SeriesName labels={s.metric} format={doFormat} />
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td className={classes.numberCell}>
|
|
||||||
{s.values &&
|
|
||||||
s.values.map((v, idx) => (
|
|
||||||
<div key={idx}>
|
|
||||||
{v[1]}{" "}
|
|
||||||
<Text
|
|
||||||
span
|
|
||||||
c="gray.7"
|
|
||||||
size="1em"
|
|
||||||
title={formatTimestamp(v[0], useLocalTime)}
|
|
||||||
>
|
|
||||||
@ {v[0]}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) : resultType === "scalar" ? (
|
|
||||||
<Table.Tr>
|
|
||||||
<Table.Td>Scalar value</Table.Td>
|
|
||||||
<Table.Td className={classes.numberCell}>
|
|
||||||
{result[1]}
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
) : resultType === "string" ? (
|
|
||||||
<Table.Tr>
|
|
||||||
<Table.Td>String value</Table.Td>
|
|
||||||
<Table.Td>{result[1]}</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
) : (
|
|
||||||
<Alert
|
|
||||||
color="red"
|
|
||||||
title="Invalid query response"
|
|
||||||
icon={<IconAlertTriangle size={14} />}
|
|
||||||
>
|
|
||||||
Invalid result value type
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</Table.Tbody>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,12 +23,12 @@ import {
|
||||||
setExpr,
|
setExpr,
|
||||||
setVisualizer,
|
setVisualizer,
|
||||||
} from "../../state/queryPageSlice";
|
} from "../../state/queryPageSlice";
|
||||||
import DataTable from "./DataTable";
|
|
||||||
import TimeInput from "./TimeInput";
|
import TimeInput from "./TimeInput";
|
||||||
import RangeInput from "./RangeInput";
|
import RangeInput from "./RangeInput";
|
||||||
import ExpressionInput from "./ExpressionInput";
|
import ExpressionInput from "./ExpressionInput";
|
||||||
import Graph from "./Graph";
|
import Graph from "./Graph";
|
||||||
import ResolutionInput from "./ResolutionInput";
|
import ResolutionInput from "./ResolutionInput";
|
||||||
|
import TableTab from "./TableTab";
|
||||||
|
|
||||||
export interface PanelProps {
|
export interface PanelProps {
|
||||||
idx: number;
|
idx: number;
|
||||||
|
@ -84,7 +84,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Panel pt="sm" value="table">
|
<Tabs.Panel pt="sm" value="table">
|
||||||
<DataTable panelIdx={idx} retriggerIdx={retriggerIdx} />
|
<TableTab panelIdx={idx} retriggerIdx={retriggerIdx} />
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
<Tabs.Panel
|
<Tabs.Panel
|
||||||
pt="sm"
|
pt="sm"
|
||||||
|
|
131
web/ui/mantine-ui/src/pages/query/TableTab.tsx
Normal file
131
web/ui/mantine-ui/src/pages/query/TableTab.tsx
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import { FC, useEffect, useId, useLayoutEffect, useState } from "react";
|
||||||
|
import { Alert, Skeleton, Box, Group, Stack, Text } from "@mantine/core";
|
||||||
|
import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react";
|
||||||
|
import { InstantQueryResult } from "../../api/responseTypes/query";
|
||||||
|
import { useAPIQuery } from "../../api/api";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../state/hooks";
|
||||||
|
import { setVisualizer } from "../../state/queryPageSlice";
|
||||||
|
import TimeInput from "./TimeInput";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
|
export interface TableTabProps {
|
||||||
|
panelIdx: number;
|
||||||
|
retriggerIdx: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableTab: FC<TableTabProps> = ({ panelIdx, retriggerIdx }) => {
|
||||||
|
const [responseTime, setResponseTime] = useState<number>(0);
|
||||||
|
const [limitResults, setLimitResults] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const { expr, visualizer } = useAppSelector(
|
||||||
|
(state) => state.queryPage.panels[panelIdx]
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { endTime, range } = visualizer;
|
||||||
|
|
||||||
|
const { data, error, isFetching, refetch } = useAPIQuery<InstantQueryResult>({
|
||||||
|
key: useId(),
|
||||||
|
path: "/query",
|
||||||
|
params: {
|
||||||
|
query: expr,
|
||||||
|
time: `${(endTime !== null ? endTime : Date.now()) / 1000}`,
|
||||||
|
},
|
||||||
|
enabled: expr !== "",
|
||||||
|
recordResponseTime: setResponseTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
expr !== "" && refetch();
|
||||||
|
}, [retriggerIdx, refetch, expr, endTime]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setLimitResults(true);
|
||||||
|
}, [data, isFetching]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="lg" mt="sm">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<TimeInput
|
||||||
|
time={endTime}
|
||||||
|
range={range}
|
||||||
|
description="Evaluation time"
|
||||||
|
onChangeTime={(time) =>
|
||||||
|
dispatch(
|
||||||
|
setVisualizer({
|
||||||
|
idx: panelIdx,
|
||||||
|
visualizer: { ...visualizer, endTime: time },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{!isFetching && data !== undefined && (
|
||||||
|
<Text size="xs" c="gray">
|
||||||
|
Load time: {responseTime}ms   Result series:{" "}
|
||||||
|
{data.data.result.length}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
{isFetching ? (
|
||||||
|
<Box>
|
||||||
|
{Array.from(Array(5), (_, i) => (
|
||||||
|
<Skeleton key={i} height={30} mb={15} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
) : error !== null ? (
|
||||||
|
<Alert
|
||||||
|
color="red"
|
||||||
|
title="Error executing query"
|
||||||
|
icon={<IconAlertTriangle size={14} />}
|
||||||
|
>
|
||||||
|
{error.message}
|
||||||
|
</Alert>
|
||||||
|
) : data === undefined ? (
|
||||||
|
<Alert variant="transparent">No data queried yet</Alert>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{data.data.result.length === 0 && (
|
||||||
|
<Alert
|
||||||
|
title="Empty query result"
|
||||||
|
icon={<IconInfoCircle size={14} />}
|
||||||
|
>
|
||||||
|
This query returned no data.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.warnings?.map((w, idx) => (
|
||||||
|
<Alert
|
||||||
|
key={idx}
|
||||||
|
color="red"
|
||||||
|
title="Query warning"
|
||||||
|
icon={<IconAlertTriangle size={14} />}
|
||||||
|
>
|
||||||
|
{w}
|
||||||
|
</Alert>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{data.infos?.map((w, idx) => (
|
||||||
|
<Alert
|
||||||
|
key={idx}
|
||||||
|
color="yellow"
|
||||||
|
title="Query notice"
|
||||||
|
icon={<IconInfoCircle size={14} />}
|
||||||
|
>
|
||||||
|
{w}
|
||||||
|
</Alert>
|
||||||
|
))}
|
||||||
|
<DataTable
|
||||||
|
data={data.data}
|
||||||
|
limitResults={limitResults}
|
||||||
|
setLimitResults={setLimitResults}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableTab;
|
Loading…
Reference in a new issue