Skip to content

Commit e8e59f9

Browse files
committed
Port debugger to TS
1 parent 5aa8021 commit e8e59f9

File tree

4 files changed

+153
-61
lines changed

4 files changed

+153
-61
lines changed

rust/cubesql/cubesql/egraph-debug-template/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
"scripts": {
1212
"start": "GENERATE_SOURCEMAP=false && react-scripts start",
1313
"build": "react-scripts build",
14+
"check": "tsc",
1415
"reformat": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
1516
"test": "react-scripts test --env=jsdom",
1617
"eject": "react-scripts eject"
1718
},
1819
"devDependencies": {
20+
"@tsconfig/strictest": "2.0.5",
1921
"@types/node": "20.16.12",
2022
"@types/react": "18.0.38",
2123
"@types/react-dom": "18.0.11",

rust/cubesql/cubesql/egraph-debug-template/src/index.js renamed to rust/cubesql/cubesql/egraph-debug-template/src/index.tsx

Lines changed: 126 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import states from './states.json';
21
import { createRoot } from 'react-dom/client';
32
import ELK from 'elkjs/lib/elk.bundled.js';
4-
import React, { useCallback, useState, useEffect, useMemo } from 'react';
3+
import type { ElkNode, LayoutOptions } from 'elkjs';
4+
import { useCallback, useState, useEffect, useMemo } from 'react';
55
import ReactFlow, {
66
ReactFlowProvider,
77
Panel,
@@ -11,26 +11,69 @@ import ReactFlow, {
1111
Handle,
1212
Position,
1313
} from 'reactflow';
14-
14+
import type {
15+
Edge as ReactFlowEdge,
16+
FitView,
17+
Node as ReactFlowNode,
18+
NodeProps,
19+
} from 'reactflow';
1520
import 'reactflow/dist/style.css';
1621

22+
import statesData from './states.json';
23+
24+
type InputNodeData = {
25+
id: string;
26+
label: string;
27+
comboId: string;
28+
};
29+
type InputEdgeData = {
30+
source: string;
31+
target: string;
32+
};
33+
type InputComboData = {
34+
id: string;
35+
label: string;
36+
};
37+
type StateData = {
38+
nodes: Array<InputNodeData>;
39+
removedNodes: Array<InputNodeData>;
40+
edges: Array<InputEdgeData>;
41+
removedEdges: Array<InputEdgeData>;
42+
combos: Array<InputComboData>;
43+
removedCombos: Array<InputComboData>;
44+
appliedRules: Array<string>;
45+
};
46+
type InputData = Array<StateData>;
47+
48+
type NodeData = {
49+
label: string;
50+
};
51+
type Node = ReactFlowNode<NodeData>;
52+
type Edge = ReactFlowEdge<null>;
53+
54+
// TODO proper parsing here
55+
const states = statesData as InputData;
56+
1757
// First is initial state
1858
const totalIterations = states.length - 1;
1959
const data = {
2060
nodes: states[0].nodes,
2161
edges: states[0].edges,
2262
combos: states[0].combos,
2363
};
24-
const sizeByNode = (n) => [60 + n.label.length * 5, 30];
25-
const toGroupNode = (n) => ({
64+
const sizeByNode = (n: InputNodeData): [number, number] => [
65+
60 + n.label.length * 5,
66+
30,
67+
];
68+
const toGroupNode = (n: InputComboData): Node => ({
2669
...n,
2770
type: 'group',
2871
data: { label: n.label },
2972
position: { x: 0, y: 0 },
3073
width: 200,
3174
height: 200,
3275
});
33-
const toRegularNode = (n) => ({
76+
const toRegularNode = (n: InputNodeData): Node => ({
3477
...n,
3578
type: 'default',
3679
extent: 'parent',
@@ -41,7 +84,7 @@ const toRegularNode = (n) => ({
4184
draggable: false,
4285
connectable: false,
4386
});
44-
const toEdge = (n) => ({
87+
const toEdge = (n: InputEdgeData): Edge => ({
4588
...n,
4689
id: `${n.source}->${n.target}`,
4790
style:
@@ -55,15 +98,15 @@ const initialNodes = data.combos
5598
const initialEdges = data.edges.map(toEdge);
5699

57100
async function layout(
58-
options,
59-
nodes,
60-
edges,
61-
setNodes,
62-
setEdges,
63-
fitView,
64-
navHistory,
65-
showOnlySelected,
66-
abortSignal,
101+
options: LayoutOptions,
102+
nodes: Array<Node>,
103+
edges: Array<Edge>,
104+
setNodes: (nodes: Array<Node>) => void,
105+
setEdges: (nodes: Array<Edge>) => void,
106+
fitView: FitView,
107+
navHistory: NavHistoryState,
108+
showOnlySelected: boolean,
109+
abortSignal: AbortSignal,
67110
) {
68111
const defaultOptions = {
69112
'elk.algorithm': 'layered',
@@ -76,13 +119,21 @@ async function layout(
76119

77120
nodes.forEach((n) => {
78121
if (n.style && n.style.width && n.style.height) {
122+
if (typeof n.style.width === 'string') {
123+
throw new Error('Unexpeted CSS width');
124+
}
125+
if (typeof n.style.height === 'string') {
126+
throw new Error('Unexpeted CSS height');
127+
}
79128
n.width = n.style.width;
80129
n.height = n.style.height;
81130
}
82131
});
83132
nodes = nodes.filter((n) => !isHiddenNode(showOnlySelected, navHistory, n));
84133
edges = edges.filter((e) => !isHiddenEdge(showOnlySelected, navHistory, e));
85134

135+
type ElkNodeWithChildren = ElkNode & { children: Array<ElkNode> };
136+
86137
const nodesMap = new Map(
87138
nodes.map((node) => [
88139
node.id,
@@ -93,7 +144,7 @@ async function layout(
93144
width: node.width ?? undefined,
94145
height: node.height ?? undefined,
95146
children: [],
96-
},
147+
} as ElkNodeWithChildren,
97148
},
98149
]),
99150
);
@@ -105,7 +156,8 @@ async function layout(
105156
if (node.parentNode === undefined) {
106157
return;
107158
}
108-
nodesMap.get(node.parentNode).elkNode.children.push(elkNode);
159+
// Safety: we've just inserted every node from nodes to map
160+
nodesMap.get(node.parentNode)!.elkNode.children.push(elkNode);
109161
}
110162

111163
// Primitive edges are deprecated in ELK, so we should use ElkExtendedEdge, that use arrays, essentially hyperedges
@@ -115,7 +167,7 @@ async function layout(
115167
targets: [edge.target],
116168
}));
117169

118-
const graph = {
170+
const graph: ElkNode = {
119171
id: 'root',
120172
layoutOptions,
121173
children: [...nodesMap.values()]
@@ -124,17 +176,24 @@ async function layout(
124176
edges: elkEdges,
125177
};
126178

127-
function elk2flow(elkNode, flatChildren) {
128-
const node = nodesMap.get(elkNode.id).node;
179+
function elk2flow(elkNode: ElkNode, flatChildren: Array<Node>): void {
180+
const nodePair = nodesMap.get(elkNode.id);
181+
if (nodePair === undefined) {
182+
throw new Error('Unexpected node id from ELK');
183+
}
184+
const node = nodePair.node;
129185

186+
if (elkNode.x === undefined || elkNode.y === undefined) {
187+
throw new Error('Unexpected position from ELK');
188+
}
130189
node.position = { x: elkNode.x, y: elkNode.y };
131190
node.style = {
132191
...node.style,
133192
width: elkNode.width,
134193
height: elkNode.height,
135194
};
136-
node.width = elkNode.width;
137-
node.height = elkNode.height;
195+
node.width = elkNode.width ?? null;
196+
node.height = elkNode.height ?? null;
138197
flatChildren.push(node);
139198
(elkNode.children ?? []).forEach((child) => {
140199
elk2flow(child, flatChildren);
@@ -150,9 +209,9 @@ async function layout(
150209

151210
// By mutating the children in-place we saves ourselves from creating a
152211
// needless copy of the nodes array.
153-
const flatChildren = [];
212+
const flatChildren: Array<Node> = [];
154213

155-
children.forEach((elkNode) => {
214+
(children ?? []).forEach((elkNode) => {
156215
elk2flow(elkNode, flatChildren);
157216
});
158217

@@ -176,14 +235,18 @@ async function layout(
176235
const highlightColor = 'rgba(170,255,170,0.71)';
177236
const selectColor = 'rgba(170,187,255,0.71)';
178237

179-
const zoomTo = (fitView, classId) => {
238+
const zoomTo = (fitView: FitView, classId: Array<string>): void => {
180239
if (!classId) {
181240
return;
182241
}
183242
fitView({ duration: 600, nodes: classId.map((id) => ({ id: `c${id}` })) });
184243
};
185244

186-
function isHiddenNode(showOnlySelected, navHistory, n) {
245+
function isHiddenNode(
246+
showOnlySelected: boolean,
247+
navHistory: NavHistoryState,
248+
n: Node,
249+
): boolean {
187250
return (
188251
showOnlySelected &&
189252
navHistory.indexOf(
@@ -192,7 +255,11 @@ function isHiddenNode(showOnlySelected, navHistory, n) {
192255
);
193256
}
194257

195-
const nodeStyles = (nodes, navHistory, showOnlySelected) => {
258+
const nodeStyles = (
259+
nodes: Array<Node>,
260+
navHistory: NavHistoryState,
261+
showOnlySelected: boolean,
262+
): Array<Node> => {
196263
return nodes.map((n) => {
197264
return {
198265
...n,
@@ -208,15 +275,23 @@ const nodeStyles = (nodes, navHistory, showOnlySelected) => {
208275
});
209276
};
210277

211-
function isHiddenEdge(showOnlySelected, navHistory, e) {
278+
function isHiddenEdge(
279+
showOnlySelected: boolean,
280+
navHistory: NavHistoryState,
281+
e: Edge,
282+
): boolean {
212283
return (
213284
showOnlySelected &&
214285
(navHistory.indexOf(e.source.replace(/^(\d+)(-?).*$/, '$1')) === -1 ||
215286
navHistory.indexOf(e.target.replace(/^(\d+)(-?).*$/, '$1')) === -1)
216287
);
217288
}
218289

219-
const edgeStyles = (edges, navHistory, showOnlySelected) => {
290+
const edgeStyles = (
291+
edges: Array<Edge>,
292+
navHistory: NavHistoryState,
293+
showOnlySelected: boolean,
294+
): Array<Edge> => {
220295
return edges.map((e) => {
221296
return {
222297
...e,
@@ -225,7 +300,7 @@ const edgeStyles = (edges, navHistory, showOnlySelected) => {
225300
});
226301
};
227302

228-
const splitLabel = (label) => {
303+
const splitLabel = (label: string): Array<string> => {
229304
const result = [''];
230305
let isDigit = false;
231306
for (let i = 0; i < label.length; i++) {
@@ -245,8 +320,9 @@ const splitLabel = (label) => {
245320
};
246321

247322
const ChildrenNode =
248-
({ navigate /*, nodes*/ }) =>
249-
({ data: { label } }) => {
323+
({ navigate /*, nodes*/ }: { navigate: (id: string) => void }) =>
324+
(props: NodeProps<NodeData>) => {
325+
const { label } = props.data;
250326
return (
251327
<div>
252328
<Handle type="target" position={Position.Top} />
@@ -276,12 +352,19 @@ const ChildrenNode =
276352
);
277353
};
278354

279-
function jsonClone(t) {
355+
type PreNodesState = {
356+
preNodes: Array<Node>;
357+
preEdges: Array<Edge>;
358+
};
359+
360+
function jsonClone<T>(t: T): T {
280361
return JSON.parse(JSON.stringify(t));
281362
}
282363

364+
type NavHistoryState = Array<string>;
365+
283366
const LayoutFlow = () => {
284-
const [{ preNodes, preEdges }, setPreNodesEdges] = useState({
367+
const [{ preNodes, preEdges }, setPreNodesEdges] = useState<PreNodesState>({
285368
preNodes: initialNodes,
286369
preEdges: initialEdges,
287370
});
@@ -295,8 +378,8 @@ const LayoutFlow = () => {
295378
const { fitView } = useReactFlow();
296379

297380
const [navigateTo, setNavigateTo] = useState('');
298-
const [navHistory, setNavHistory] = useState([]);
299-
const [showOnlySelected, setShowOnlySelected] = useState(false);
381+
const [navHistory, setNavHistory] = useState<NavHistoryState>([]);
382+
const [showOnlySelected, setShowOnlySelected] = useState<boolean>(false);
300383

301384
const prevState = () => {
302385
if (stateIdx === 0) {
@@ -305,7 +388,7 @@ const LayoutFlow = () => {
305388
let newNodes = preNodes;
306389
let newEdges = preEdges;
307390
const toRemove = states[stateIdx];
308-
let toRemoveNodeIds = toRemove.nodes
391+
let toRemoveNodeIds = (toRemove.nodes as Array<{ id: string }>)
309392
.concat(toRemove.combos)
310393
.map((n) => n.id);
311394
let toRemoveEdgeIds = toRemove.edges.map((n) => toEdge(n).id);
@@ -317,14 +400,14 @@ const LayoutFlow = () => {
317400
newNodes = newNodes.concat(
318401
(toRemove.removedNodes || []).map(toRegularNode),
319402
);
320-
const edgeMap = (toRemove.removedEdges || [])
403+
const edgeMap: Record<string, Edge> = (toRemove.removedEdges || [])
321404
.map(toEdge)
322405
.reduce((acc, val) => ({ ...acc, [val.id]: val }), {});
323406
newEdges = newEdges.concat(
324407
Object.keys(edgeMap).map((key) => edgeMap[key]),
325408
);
326409
const toHighlight = states[stateIdx - 1];
327-
const toHighlightNodeIds = toHighlight.nodes
410+
const toHighlightNodeIds = (toHighlight.nodes as Array<{ id: string }>)
328411
.concat(toHighlight.combos)
329412
.map((n) => n.id);
330413
newNodes = newNodes.map((n) => ({
@@ -348,7 +431,7 @@ const LayoutFlow = () => {
348431
let newEdges = preEdges;
349432
setStateIdx(stateIdx + 1);
350433
const toAdd = states[stateIdx + 1];
351-
let toRemoveNodeIds = toAdd.removedNodes
434+
let toRemoveNodeIds = (toAdd.removedNodes as Array<{ id: string }>)
352435
.concat(toAdd.removedCombos)
353436
.map((n) => n.id);
354437
let toRemoveEdgeIds = toAdd.removedEdges.map((n) => toEdge(n).id);
@@ -367,7 +450,7 @@ const LayoutFlow = () => {
367450
style: { ...n.style, backgroundColor: highlightColor },
368451
})),
369452
);
370-
const edgeMap = (toAdd.edges || [])
453+
const edgeMap: Record<string, Edge> = (toAdd.edges || [])
371454
.map(toEdge)
372455
.reduce((acc, val) => ({ ...acc, [val.id]: val }), {});
373456
newEdges = newEdges.concat(
@@ -378,7 +461,7 @@ const LayoutFlow = () => {
378461
};
379462

380463
const navigate = useCallback(
381-
(id) => {
464+
(id: string): void => {
382465
zoomTo(fitView, [id]);
383466
if (!navHistory.includes(id)) {
384467
setNavHistory(navHistory.concat(id));

0 commit comments

Comments
 (0)