Clarify explain view, add tree view close button, fix callback bug

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-09-08 15:17:00 +02:00
parent 7e0cd2e0b4
commit 8b4291537b
4 changed files with 69 additions and 58 deletions

View file

@ -11,32 +11,35 @@ import classes from "./ExplainView.module.css";
import SelectorExplainView from "./Selector"; import SelectorExplainView from "./Selector";
import AggregationExplainView from "./Aggregation"; import AggregationExplainView from "./Aggregation";
import BinaryExprExplainView from "./BinaryExpr/BinaryExpr"; import BinaryExprExplainView from "./BinaryExpr/BinaryExpr";
import { IconInfoCircle } from "@tabler/icons-react";
interface ExplainViewProps { interface ExplainViewProps {
node: ASTNode | null; node: ASTNode | null;
treeShown: boolean; treeShown: boolean;
setShowTree: () => void; showTree: () => void;
} }
const ExplainView: FC<ExplainViewProps> = ({ const ExplainView: FC<ExplainViewProps> = ({
node, node,
treeShown, treeShown,
setShowTree, showTree: setShowTree,
}) => { }) => {
if (node === null) { if (node === null) {
return ( return (
<Alert> <Alert title="How to use the Explain view" icon={<IconInfoCircle />}>
<> This tab can help you understand the behavior of individual components
To use the Explain view,{" "} of a query.
{!treeShown && ( <br />
<> <br />
<Anchor fz="unset" onClick={setShowTree}> To use the Explain view,{" "}
enable the query tree view {!treeShown && (
</Anchor>{" "} <>
(also available via the expression input dropdown) and then <Anchor fz="unset" onClick={setShowTree}>
</> enable the query tree view
)}{" "} </Anchor>{" "}
select a node in the tree above. (also available via the expression input menu) and then
</> </>
)}{" "}
select a node in the tree above.
</Alert> </Alert>
); );
} }

View file

@ -6,16 +6,13 @@ import {
Box, Box,
SegmentedControl, SegmentedControl,
Stack, Stack,
Button,
Skeleton, Skeleton,
} from "@mantine/core"; } from "@mantine/core";
import { import {
IconChartAreaFilled, IconChartAreaFilled,
IconChartLine, IconChartLine,
IconCheckbox,
IconGraph, IconGraph,
IconInfoCircle, IconInfoCircle,
IconSquare,
IconTable, IconTable,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import { FC, Suspense, useCallback, useMemo, useState } from "react"; import { FC, Suspense, useCallback, useMemo, useState } from "react";
@ -129,6 +126,10 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
retriggerIdx={retriggerIdx} retriggerIdx={retriggerIdx}
selectedNode={selectedNode} selectedNode={selectedNode}
setSelectedNode={setSelectedNode} setSelectedNode={setSelectedNode}
closeTreeView={() => {
dispatch(setShowTree({ idx, showTree: false }));
setSelectedNode(null);
}}
/> />
</Suspense> </Suspense>
</ErrorBoundary> </ErrorBoundary>
@ -165,11 +166,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
<Tabs.Panel pt="sm" value="table"> <Tabs.Panel pt="sm" value="table">
<TableTab expr={expr} panelIdx={idx} retriggerIdx={retriggerIdx} /> <TableTab expr={expr} panelIdx={idx} retriggerIdx={retriggerIdx} />
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel <Tabs.Panel pt="sm" value="graph">
pt="sm"
value="graph"
// style={{ border: "1px solid lightgrey", borderTop: "none" }}
>
<Group mt="xs" justify="space-between"> <Group mt="xs" justify="space-between">
<Group> <Group>
<RangeInput <RangeInput
@ -214,7 +211,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
</Group> </Group>
<Group gap="lg"> <Group gap="lg">
<Button {/* <Button
variant="subtle" variant="subtle"
color="gray.9" color="gray.9"
size="xs" size="xs"
@ -249,7 +246,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
} }
> >
Show exemplars Show exemplars
</Button> </Button> */}
<SegmentedControl <SegmentedControl
onChange={(value) => onChange={(value) =>
@ -325,7 +322,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
<ExplainView <ExplainView
node={selectedNode?.node ?? null} node={selectedNode?.node ?? null}
treeShown={panel.showTree} treeShown={panel.showTree}
setShowTree={() => { showTree={() => {
dispatch(setShowTree({ idx, showTree: true })); dispatch(setShowTree({ idx, showTree: true }));
}} }}
/> />

View file

@ -1,5 +1,6 @@
import { import {
FC, FC,
useCallback,
useEffect, useEffect,
useLayoutEffect, useLayoutEffect,
useMemo, useMemo,
@ -57,8 +58,10 @@ const TreeNode: FC<{
selectedNode: { id: string; node: ASTNode } | null; selectedNode: { id: string; node: ASTNode } | null;
setSelectedNode: (Node: { id: string; node: ASTNode } | null) => void; setSelectedNode: (Node: { id: string; node: ASTNode } | null) => void;
parentRef?: React.RefObject<HTMLDivElement>; parentRef?: React.RefObject<HTMLDivElement>;
reportNodeState?: (state: NodeState) => void; reportNodeState?: (childIdx: number, state: NodeState) => void;
reverse: boolean; reverse: boolean;
// The index of this node in its parent's children.
childIdx: number;
}> = ({ }> = ({
node, node,
selectedNode, selectedNode,
@ -66,6 +69,7 @@ const TreeNode: FC<{
parentRef, parentRef,
reportNodeState, reportNodeState,
reverse, reverse,
childIdx,
}) => { }) => {
const nodeID = useId(); const nodeID = useId();
const nodeRef = useRef<HTMLDivElement>(null); const nodeRef = useRef<HTMLDivElement>(null);
@ -130,21 +134,32 @@ const TreeNode: FC<{
useEffect(() => { useEffect(() => {
if (mergedChildState === "error") { if (mergedChildState === "error") {
reportNodeState && reportNodeState("error"); reportNodeState && reportNodeState(childIdx, "error");
} }
}, [mergedChildState, reportNodeState]); }, [mergedChildState, reportNodeState, childIdx]);
useEffect(() => { useEffect(() => {
if (error) { if (error) {
reportNodeState && reportNodeState("error"); reportNodeState && reportNodeState(childIdx, "error");
} }
}, [error, reportNodeState]); }, [error, reportNodeState, childIdx]);
useEffect(() => { useEffect(() => {
if (isFetching) { if (isFetching) {
reportNodeState && reportNodeState("running"); reportNodeState && reportNodeState(childIdx, "running");
} }
}, [isFetching, reportNodeState]); }, [isFetching, reportNodeState, childIdx]);
const childReportNodeState = useCallback(
(childIdx: number, state: NodeState) => {
setChildStates((prev) => {
const newStates = [...prev];
newStates[childIdx] = state;
return newStates;
});
},
[setChildStates]
);
// Update the size and position of tree connector lines based on the node's and its parent's position. // Update the size and position of tree connector lines based on the node's and its parent's position.
useLayoutEffect(() => { useLayoutEffect(() => {
@ -185,7 +200,7 @@ const TreeNode: FC<{
return; return;
} }
reportNodeState && reportNodeState("success"); reportNodeState && reportNodeState(childIdx, "success");
let resultSeries = 0; let resultSeries = 0;
const labelValuesByName: Record<string, Record<string, number>> = {}; const labelValuesByName: Record<string, Record<string, number>> = {};
@ -228,7 +243,7 @@ const TreeNode: FC<{
), ),
labelExamples, labelExamples,
}); });
}, [data, reportNodeState]); }, [data, reportNodeState, childIdx]);
const innerNode = ( const innerNode = (
<Group <Group
@ -367,13 +382,8 @@ const TreeNode: FC<{
setSelectedNode={setSelectedNode} setSelectedNode={setSelectedNode}
parentRef={nodeRef} parentRef={nodeRef}
reverse={true} reverse={true}
reportNodeState={(state: NodeState) => { childIdx={0}
setChildStates((prev) => { reportNodeState={childReportNodeState}
const newStates = [...prev];
newStates[0] = state;
return newStates;
});
}}
/> />
</Box> </Box>
{innerNode} {innerNode}
@ -384,13 +394,8 @@ const TreeNode: FC<{
setSelectedNode={setSelectedNode} setSelectedNode={setSelectedNode}
parentRef={nodeRef} parentRef={nodeRef}
reverse={false} reverse={false}
reportNodeState={(state: NodeState) => { childIdx={1}
setChildStates((prev) => { reportNodeState={childReportNodeState}
const newStates = [...prev];
newStates[1] = state;
return newStates;
});
}}
/> />
</Box> </Box>
</div> </div>
@ -407,13 +412,8 @@ const TreeNode: FC<{
setSelectedNode={setSelectedNode} setSelectedNode={setSelectedNode}
parentRef={nodeRef} parentRef={nodeRef}
reverse={false} reverse={false}
reportNodeState={(state: NodeState) => { childIdx={idx}
setChildStates((prev) => { reportNodeState={childReportNodeState}
const newStates = [...prev];
newStates[idx] = state;
return newStates;
});
}}
/> />
</Box> </Box>
))} ))}

View file

@ -3,7 +3,7 @@ import { useSuspenseAPIQuery } from "../../api/api";
import { useAppSelector } from "../../state/hooks"; import { useAppSelector } from "../../state/hooks";
import ASTNode from "../../promql/ast"; import ASTNode from "../../promql/ast";
import TreeNode from "./TreeNode"; import TreeNode from "./TreeNode";
import { Card } from "@mantine/core"; import { Card, CloseButton } from "@mantine/core";
const TreeView: FC<{ const TreeView: FC<{
panelIdx: number; panelIdx: number;
@ -19,7 +19,8 @@ const TreeView: FC<{
node: ASTNode; node: ASTNode;
} | null } | null
) => void; ) => void;
}> = ({ panelIdx, selectedNode, setSelectedNode }) => { closeTreeView: () => void;
}> = ({ panelIdx, selectedNode, setSelectedNode, closeTreeView }) => {
const { expr } = useAppSelector((state) => state.queryPage.panels[panelIdx]); const { expr } = useAppSelector((state) => state.queryPage.panels[panelIdx]);
const { data } = useSuspenseAPIQuery<ASTNode>({ const { data } = useSuspenseAPIQuery<ASTNode>({
@ -32,7 +33,17 @@ const TreeView: FC<{
return ( return (
<Card withBorder fz="sm" style={{ overflowX: "auto" }} pl="sm"> <Card withBorder fz="sm" style={{ overflowX: "auto" }} pl="sm">
<CloseButton
aria-label="Close tree view"
title="Close tree view"
pos="absolute"
top={7}
size="sm"
right={7}
onClick={closeTreeView}
/>
<TreeNode <TreeNode
childIdx={0}
node={data.data} node={data.data}
selectedNode={selectedNode} selectedNode={selectedNode}
setSelectedNode={setSelectedNode} setSelectedNode={setSelectedNode}