Skip to content

Commit

Permalink
Add types to queries (#11)
Browse files Browse the repository at this point in the history
* Type catch-all params

* Add tx schema and inferred type

* Use Tx type in Mermaid

* Use tx schema and type in txs

* Use tx schema and type in tx detail
  • Loading branch information
abefernan authored Oct 2, 2024
1 parent 8bcfaa2 commit 350617f
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 58 deletions.
2 changes: 1 addition & 1 deletion app/txs/[...span]/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down
22 changes: 15 additions & 7 deletions components/tx-data.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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}`)
Expand All @@ -26,14 +24,24 @@ export default function TxData({ txId, spanId }: TxDataProps) {

if (error) return "An error has occurred: " + error.message;

const spans: Readonly<Array<Tx>> = 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 (
<div>
<Mermaid chart={mermaidChart} />
{span ? <pre>{JSON.stringify(span._source.tags, null, 2)}</pre> : null}
{span ? (
<pre>
{JSON.stringify(
Object.fromEntries(Array.from(span.tags).sort()),
null,
2,
)}
</pre>
) : null}
</div>
);
}
22 changes: 13 additions & 9 deletions components/txs-data.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
"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({
queryKey: ["txs"],
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<Array<Tx>> = z.array(TxSchema).parse(data);

return (
<div>
<h1>Transactions</h1>
<ul>
{data.txs.map((tx: Tx) => (
{txs.map((tx) => (
<li key={tx._id}>
<Link href={`/txs/${tx._source.traceID}`}>
{tx._source.traceID} - {tx._source.operationName} -{" "}
<Link href={`/txs/${tx.traceId}`}>
{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
</Link>
</li>
))}
Expand Down
49 changes: 18 additions & 31 deletions lib/mermaid.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
import { Tx } from "@/types/txs";
import { getAddressType } from "./chain";

type TreeNode = {
id: string;
parentId: string | null;
children: Set<TreeNode>;
span: any;
span: Tx;
};

export function flowchartFromSpans(spans: any) {
export function flowchartFromSpans(spans: Readonly<Array<Tx>>) {
const spanParentMap = new Map<string, string | null>();

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<TreeNode> = Array.from(spanParentMap).map(
([id, parentId]) => ({
id,
parentId,
children: new Set(),
span: spans.find((span: any) => span._source.spanID === id),
span: spans.find((span) => span.spanId === id)!,
}),
);

Expand Down Expand Up @@ -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<Array<Tx>>) {
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}: <a href="/txs/${msgSendSpan._source.traceID}/${msgSendSpan._source.spanID}">🏦 Send</a>`;
chart += `\n${sender}->>+${recipient}: <a href="/txs/${span.traceId}/${span.spanId}">🏦 Send</a>`;

break;
}

return chart;
Expand Down
46 changes: 36 additions & 10 deletions types/txs.ts
Original file line number Diff line number Diff line change
@@ -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<z.infer<typeof TxSchema>>;

0 comments on commit 350617f

Please sign in to comment.