Skip to content

Commit

Permalink
feat(explorer): query execution time (#3444)
Browse files Browse the repository at this point in the history
  • Loading branch information
karooolis authored Jan 17, 2025
1 parent 54e5c06 commit 05c7298
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-poets-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/explorer": patch
---

SQL query execution time in Explore table is now measured and displayed.
1 change: 1 addition & 0 deletions packages/explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.6",
"@radix-ui/themes": "^3.0.5",
"@rainbow-me/rainbowkit": "^2.1.5",
"@tanstack/react-query": "^5.51.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function Explorer() {
const { worldAddress } = useParams();
const { id: chainId } = useChain();
const indexer = indexerForChainId(chainId);
const [isLiveQuery, setIsLiveQuery] = useState(true);
const [isLiveQuery, setIsLiveQuery] = useState(false);
const [query, setQuery] = useQueryState("query", parseAsString.withDefault(""));
const [selectedTableId] = useQueryState("tableId");
const prevSelectedTableId = usePrevious(selectedTableId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { Table } from "@latticexyz/config";
import Editor from "@monaco-editor/react";
import { Tooltip } from "../../../../../../components/Tooltip";
import { Button } from "../../../../../../components/ui/Button";
import { Form, FormField } from "../../../../../../components/ui/Form";
import { cn } from "../../../../../../utils";
import { useTableDataQuery } from "../../../../queries/useTableDataQuery";
import { monacoOptions } from "./consts";
import { useMonacoSuggestions } from "./useMonacoSuggestions";
import { useQueryValidator } from "./useQueryValidator";
Expand All @@ -26,6 +28,11 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
const [isFocused, setIsFocused] = useState(false);
const [query, setQuery] = useQueryState("query", { defaultValue: "" });
const validateQuery = useQueryValidator(table);
const { data: tableData } = useTableDataQuery({
table,
query,
isLiveQuery,
});
useMonacoSuggestions(table);

const form = useForm({
Expand Down Expand Up @@ -107,15 +114,33 @@ export function SQLEditor({ table, isLiveQuery, setIsLiveQuery }: Props) {
) : null}
</div>

<div className="flex justify-end gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => setIsLiveQuery(!isLiveQuery)}
title={isLiveQuery ? "Pause live query" : "Start live query"}
>
{isLiveQuery ? <PlayIcon className="h-4 w-4" /> : <PauseIcon className="h-4 w-4" />}
</Button>
<div className="flex justify-end gap-4">
{tableData ? (
<>
<span className="flex items-center gap-1.5 text-xs text-white/60">
<Tooltip text="Execution time for the SQL query">
<span className="flex items-center gap-1.5">
<span
className={cn("inline-block h-[6px] w-[6px] rounded-full bg-success", {
"animate-pulse": isLiveQuery,
})}
/>
<span>{tableData ? Math.round(tableData.queryDuration) : 0}ms</span>
</span>
</Tooltip>
·
<span>
{tableData?.rows.length ?? 0} row{tableData?.rows.length !== 1 ? "s" : ""}
</span>
</span>

<Tooltip text={isLiveQuery ? "Pause live query" : "Start live query"}>
<Button variant="outline" size="icon" onClick={() => setIsLiveQuery(!isLiveQuery)}>
{isLiveQuery ? <PauseIcon className="h-4 w-4" /> : <PlayIcon className="h-4 w-4" />}
</Button>
</Tooltip>
</>
) : null}

<Button className="flex gap-2 pl-4 pr-3" type="submit">
Run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import { Button } from "../../../../../../components/ui/Button";
import { Input } from "../../../../../../components/ui/Input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../../../../components/ui/Table";
import { cn } from "../../../../../../utils";
import { useChain } from "../../../../hooks/useChain";
import { TData, TDataRow, useTableDataQuery } from "../../../../queries/useTableDataQuery";
import { indexerForChainId } from "../../../../utils/indexerForChainId";
import { EditableTableCell } from "./EditableTableCell";
import { ExportButton } from "./ExportButton";
import { typeSortingFn } from "./utils/typeSortingFn";
Expand All @@ -33,6 +35,8 @@ type Props = {
};

export function TablesViewer({ table, query, isLiveQuery }: Props) {
const { id: chainId } = useChain();
const indexer = indexerForChainId(chainId);
const {
data: tableData,
isLoading: isTDataLoading,
Expand Down Expand Up @@ -110,7 +114,11 @@ export function TablesViewer({ table, query, isLiveQuery }: Props) {
});

return (
<div className="!-mt-10 space-y-4">
<div
className={cn("space-y-4", {
"!-mt-10": indexer.type === "hosted",
})}
>
<div className="flex w-1/2 items-center gap-4">
<Input
placeholder="Filter..."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@ export type TDataRow = Record<string, unknown>;
export type TData = {
columns: string[];
rows: TDataRow[];
queryDuration: number;
};

export function useTableDataQuery({ table, query, isLiveQuery }: Props) {
const { chainName, worldAddress } = useParams();
const { id: chainId } = useChain();
const decodedQuery = decodeURIComponent(query ?? "");

return useQuery<DozerResponse, Error, TData | undefined>({
return useQuery<DozerResponse & { queryDuration: number }, Error, TData | undefined>({
queryKey: ["tableData", chainName, worldAddress, decodedQuery],
queryFn: async () => {
const startTime = performance.now();
const indexer = indexerForChainId(chainId);
const response = await fetch(indexer.url, {
method: "POST",
Expand All @@ -41,13 +43,15 @@ export function useTableDataQuery({ table, query, isLiveQuery }: Props) {
});

const data = await response.json();
const queryDuration = performance.now() - startTime;

if (!response.ok) {
throw new Error(data.msg || "Network response was not ok");
}

return data;
return { ...data, queryDuration };
},
select: (data: DozerResponse): TData | undefined => {
select: (data: DozerResponse & { queryDuration: number }): TData | undefined => {
if (!table || !data?.result?.[0]) return undefined;

const indexer = indexerForChainId(chainId);
Expand Down Expand Up @@ -76,13 +80,14 @@ export function useTableDataQuery({ table, query, isLiveQuery }: Props) {
return {
columns,
rows,
queryDuration: data.queryDuration,
};
},
retry: false,
enabled: !!table && !!query,
refetchInterval: (query) => {
if (query.state.error) return false;
else if (isLiveQuery) return false;
else if (!isLiveQuery) return false;
return 1000;
},
});
Expand Down
19 changes: 19 additions & 0 deletions packages/explorer/src/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Tooltip as RadixTooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/Tooltip";

type Props = {
text: string;
children: React.ReactNode;
};

export function Tooltip({ text, children }: Props) {
return (
<TooltipProvider>
<RadixTooltip>
<TooltipTrigger>{children}</TooltipTrigger>
<TooltipContent>
<p className="text-xs">{text}</p>
</TooltipContent>
</RadixTooltip>
</TooltipProvider>
);
}
50 changes: 50 additions & 0 deletions packages/explorer/src/components/ui/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "../../utils";

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
[
"z-50",
"overflow-hidden",
"rounded-md",
"border",
"bg-popover",
"text-popover-foreground",
"px-3",
"py-1.5",
"text-sm",
"shadow-md",
"animate-in",
"fade-in-0",
"zoom-in-95",
"data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0",
"data-[state=closed]:zoom-out-95",
"data-[side=bottom]:slide-in-from-top-2",
"data-[side=left]:slide-in-from-right-2",
"data-[side=right]:slide-in-from-left-2",
"data-[side=top]:slide-in-from-bottom-2",
].join(" "),
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
Loading

0 comments on commit 05c7298

Please sign in to comment.