diff --git a/app/txs/[...span]/page.tsx b/app/txs/[...span]/page.tsx index c8d6af6..febffd3 100644 --- a/app/txs/[...span]/page.tsx +++ b/app/txs/[...span]/page.tsx @@ -1,6 +1,6 @@ import TxData from "@/components/tx-data"; -export default function Tx({ params }: { params: { span: any[] } }) { +export default function Tx({ params }: { params: { span: string[] } }) { const [txId, spanId] = params.span; return ( diff --git a/components/tx-data.tsx b/components/tx-data.tsx index 72a3141..3b6296a 100644 --- a/components/tx-data.tsx +++ b/components/tx-data.tsx @@ -1,7 +1,9 @@ "use client"; import { sequenceDiagramFromSpans } from "@/lib/mermaid"; +import { Tx, TxSchema } from "@/types/txs"; import { useQuery } from "@tanstack/react-query"; +import { z } from "zod"; import Mermaid from "./mermaid"; type TxDataProps = { @@ -10,11 +12,7 @@ type TxDataProps = { }; export default function TxData({ txId, spanId }: TxDataProps) { - const { - isPending, - error, - data: spans, - } = useQuery({ + const { isPending, error, data } = useQuery({ queryKey: [`tx-${txId}`], queryFn: () => fetch(`http://localhost:4000/api/v1/txs?traceID=${txId}`) @@ -26,14 +24,24 @@ export default function TxData({ txId, spanId }: TxDataProps) { if (error) return "An error has occurred: " + error.message; + const spans: Readonly> = z.array(TxSchema).parse(data); + const mermaidChart = sequenceDiagramFromSpans(spans); - const span = spans.find((span: any) => span._source.spanID === spanId); + const span = spans.find((span: Tx) => span.spanId === spanId); return (
- {span ?
{JSON.stringify(span._source.tags, null, 2)}
: null} + {span ? ( +
+          {JSON.stringify(
+            Object.fromEntries(Array.from(span.tags).sort()),
+            null,
+            2,
+          )}
+        
+ ) : null}
); } diff --git a/components/txs-data.tsx b/components/txs-data.tsx index 5c304a4..1095601 100644 --- a/components/txs-data.tsx +++ b/components/txs-data.tsx @@ -1,8 +1,9 @@ "use client"; -import { Tx } from "@/types/txs"; +import { Tx, TxSchema } from "@/types/txs"; import { useQuery } from "@tanstack/react-query"; import Link from "next/link"; +import { z } from "zod"; export default function TxsData() { const { isPending, error, data } = useQuery({ @@ -10,28 +11,31 @@ export default function TxsData() { queryFn: () => fetch( `http://localhost:4000/api/v1/txs?operationName=execute_tx&tags=${encodeURIComponent('{"tx":"*Bank(Send*"}')}`, - ).then((res) => res.json()), + ) + .then((res) => res.json()) + .then((json) => json.txs), }); if (isPending) return "Loading..."; if (error) return "An error has occurred: " + error.message; + const txs: Readonly> = z.array(TxSchema).parse(data); + return (

Transactions

    - {data.txs.map((tx: Tx) => ( + {txs.map((tx) => (
  • - - {tx._source.traceID} - {tx._source.operationName} -{" "} + + {tx.traceId} - {tx.operationName} -{" "} { - data.txs.filter( - (txToFilter: Tx) => - txToFilter._source.traceID === tx._source.traceID, + txs.filter( + (txToFilter: Tx) => txToFilter.traceId === tx.traceId, ).length }{" "} - spans - {tx._source.tags.length} tags + spans - {tx.tags.size} tags
  • ))} diff --git a/lib/mermaid.ts b/lib/mermaid.ts index aac9d3f..7521d58 100644 --- a/lib/mermaid.ts +++ b/lib/mermaid.ts @@ -1,25 +1,18 @@ +import { Tx } from "@/types/txs"; import { getAddressType } from "./chain"; type TreeNode = { id: string; parentId: string | null; children: Set; - span: any; + span: Tx; }; -export function flowchartFromSpans(spans: any) { +export function flowchartFromSpans(spans: Readonly>) { const spanParentMap = new Map(); for (const span of spans) { - const parentRef = span._source.references.find( - (ref: any) => ref.refType === "CHILD_OF", - ); - - if (parentRef) { - spanParentMap.set(span._source.spanID, parentRef.spanID); - } else { - spanParentMap.set(span._source.spanID, null); - } + spanParentMap.set(span.spanId, span.parentSpanId); } const spanNodes: Array = Array.from(spanParentMap).map( @@ -27,7 +20,7 @@ export function flowchartFromSpans(spans: any) { id, parentId, children: new Set(), - span: spans.find((span: any) => span._source.spanID === id), + span: spans.find((span) => span.spanId === id)!, }), ); @@ -58,42 +51,36 @@ export function flowchartFromSpans(spans: any) { for (const node of sortedSpanNodes) { for (const child of Array.from(node.children)) { - chart += `\n${node.id}[${node.span._source.operationName}] --> ${child.id}[${child.span._source.operationName}]`; + chart += `\n${node.id}[${node.span.operationName}] --> ${child.id}[${child.span.operationName}]`; } } for (const node of sortedSpanNodes) { - chart += `\nclick ${node.id} "/txs/${node.span._source.traceID}/${node.id}"`; + chart += `\nclick ${node.id} "/txs/${node.span.traceId}/${node.id}"`; } - console.log({ chart, spans }); - return chart; } -export function sequenceDiagramFromSpans(spans: any) { +export function sequenceDiagramFromSpans(spans: Readonly>) { let chart = "sequenceDiagram"; - const msgSendSpan = spans.find((span: any) => - span._source.tags.find( - (tag: any) => tag.key === "tx" && tag.value.includes("Bank(Send"), - ), - ); - - if (msgSendSpan) { - const tx = msgSendSpan._source.tags.find( - (tag: any) => tag.key === "tx", - ).value; + for (const span of spans) { + const tx = span.tags.get("tx"); - const sender = tx.match(/sender: (\w+)/)[1]; - const recipient = tx.match(/recipient: (\w+)/)[1]; + if (!tx || !tx.includes("Bank(Send")) { + continue; + } - console.log({ tx, sender, recipient }); + const sender = tx.match(/sender: (\w+)/)?.[1] ?? ""; + const recipient = tx.match(/recipient: (\w+)/)?.[1] ?? ""; chart += `\n${getActorBox(sender)}`; chart += `\n${getActorBox(recipient)}`; - chart += `\n${sender}->>+${recipient}: 🏦 Send`; + chart += `\n${sender}->>+${recipient}: 🏦 Send`; + + break; } return chart; diff --git a/types/txs.ts b/types/txs.ts index 0a209b4..02c1ad7 100644 --- a/types/txs.ts +++ b/types/txs.ts @@ -1,10 +1,36 @@ -export type Tx = { - _id: string; - _source: { - traceID: string; - spanID: string; - operationName: string; - references: Array<{ refType: string; traceID: string; spanID: string }>; - tags: Array<{ key: string; type: string; value: string }>; - }; -}; +import { z } from "zod"; + +export const TxSchema = z + .object({ + _id: z.string(), + _source: z.object({ + traceID: z.string(), + spanID: z.string(), + operationName: z.string(), + references: z.array( + z.object({ + refType: z.string(), + traceID: z.string(), + spanID: z.string(), + }), + ), + tags: z.array( + z.object({ key: z.string(), type: z.string(), value: z.string() }), + ), + }), + }) + .transform((v) => ({ + //NOTE - Using _id for React keyprop for now since I found different spans with same spanId + _id: v._id, + traceId: v._source.traceID, + spanId: v._source.spanID, + operationName: v._source.operationName, + parentSpanId: + v._source.references.find( + (ref) => + ref.traceID === v._source.traceID && ref.refType === "CHILD_OF", + )?.spanID ?? null, + tags: new Map(v._source.tags.map(({ key, value }) => [key, value])), + })); + +export type Tx = Readonly>;