From dc2a8f3e9d09475cebeead3809c0ac6c4e0b741a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 19 Dec 2025 13:10:50 +0000 Subject: [PATCH] Better help and added byte seconds stat --- apps/webapp/app/components/AlphaBadge.tsx | 32 +++++++ .../app/components/navigation/SideMenu.tsx | 2 + .../route.tsx | 94 +++++++++++-------- apps/webapp/app/v3/querySchemas.ts | 5 + .../clickhouse/src/client/client.ts | 7 ++ .../clickhouse/src/client/noop.ts | 1 + .../clickhouse/src/client/types.ts | 1 + 7 files changed, 104 insertions(+), 38 deletions(-) create mode 100644 apps/webapp/app/components/AlphaBadge.tsx diff --git a/apps/webapp/app/components/AlphaBadge.tsx b/apps/webapp/app/components/AlphaBadge.tsx new file mode 100644 index 000000000..58da1a994 --- /dev/null +++ b/apps/webapp/app/components/AlphaBadge.tsx @@ -0,0 +1,32 @@ +import { cn } from "~/utils/cn"; +import { Badge } from "./primitives/Badge"; +import { SimpleTooltip } from "./primitives/Tooltip"; + +export function AlphaBadge({ + inline = false, + className, +}: { + inline?: boolean; + className?: string; +}) { + return ( + + Alpha + + } + content="This feature is in Alpha." + disableHoverableContent + /> + ); +} + +export function AlphaTitle({ children }: { children: React.ReactNode }) { + return ( + <> + {children} + + + ); +} diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx index 719a82695..8f29e5fab 100644 --- a/apps/webapp/app/components/navigation/SideMenu.tsx +++ b/apps/webapp/app/components/navigation/SideMenu.tsx @@ -97,6 +97,7 @@ import { HelpAndFeedback } from "./HelpAndFeedbackPopover"; import { SideMenuHeader } from "./SideMenuHeader"; import { SideMenuItem } from "./SideMenuItem"; import { SideMenuSection } from "./SideMenuSection"; +import { AlphaBadge } from "../AlphaBadge"; type SideMenuUser = Pick & { isImpersonating: boolean }; export type SideMenuProject = Pick< @@ -278,6 +279,7 @@ export function SideMenu({ activeIconColor="text-purple-500" to={queryPath(organization, project, environment)} data-action="query" + badge={} /> )} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx index 7a2321056..23074b605 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx @@ -1,4 +1,5 @@ import { LightBulbIcon } from "@heroicons/react/20/solid"; +import { ColumnSchema } from "@internal/tsql"; import { Form, useNavigation } from "@remix-run/react"; import { type ActionFunctionArgs, @@ -9,11 +10,14 @@ import { useState } from "react"; import { typedjson, useTypedActionData, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { ExitIcon } from "~/assets/icons/ExitIcon"; +import { AlphaTitle } from "~/components/AlphaBadge"; import { TSQLEditor } from "~/components/code/TSQLEditor"; import { TSQLResultsTable } from "~/components/code/TSQLResultsTable"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; +import { Badge } from "~/components/primitives/Badge"; import { Button } from "~/components/primitives/Buttons"; +import { CopyableText } from "~/components/primitives/CopyableText"; import { Header2, Header3 } from "~/components/primitives/Headers"; import { NavBar, PageTitle } from "~/components/primitives/PageHeader"; import { Paragraph } from "~/components/primitives/Paragraph"; @@ -194,7 +198,7 @@ export default function Page() { return ( - + Query} /> @@ -211,6 +215,13 @@ export default function Page() { showClearButton={true} minHeight="200px" className="min-h-[200px]" + additionalActions={ + showHelpSidebar ? null : ( + + ) + } />
@@ -345,43 +356,9 @@ function QueryHelpSidebar({ onClose }: { onClose: () => void }) { )} -
+
{Object.values(table.columns).map((col) => ( -
-
- {col.name} - {col.type} -
- {col.description && ( - - {col.description} - - )} - {col.example && ( -
- Example: - {col.example} -
- )} - {col.allowedValues && col.allowedValues.length > 0 && ( -
- Allowed values: -
- {col.allowedValues.map((value) => ( - - {col.valueMap?.[value] ?? value} - - ))} -
-
- )} -
+ ))}
@@ -391,6 +368,43 @@ function QueryHelpSidebar({ onClose }: { onClose: () => void }) { ); } +function ColumnHelpItem({ col }: { col: ColumnSchema }) { + return ( +
+
+ + {col.type} +
+ {col.description && ( + + {col.description} + + )} + {col.example && ( +
+ Example: + +
+ )} + {col.allowedValues && col.allowedValues.length > 0 && ( +
+ Available options: + {col.allowedValues.map((value) => ( + + ))} +
+ )} +
+ ); +} + function ScopeItem({ scope }: { scope: QueryScope }) { const organization = useOrganization(); const project = useProject(); @@ -412,17 +426,21 @@ function formatQueryStats(stats: { read_rows: string; read_bytes: string; elapsed_ns: string; + byte_seconds: string; }): string { const readRows = parseInt(stats.read_rows, 10); const readBytes = parseInt(stats.read_bytes, 10); const elapsedNs = parseInt(stats.elapsed_ns, 10); + const byteSeconds = parseFloat(stats.byte_seconds); const elapsedMs = elapsedNs / 1_000_000; const formattedTime = elapsedMs < 1000 ? `${elapsedMs.toFixed(1)}ms` : `${(elapsedMs / 1000).toFixed(2)}s`; const formattedBytes = formatBytes(readBytes); - return `${readRows.toLocaleString()} rows read · ${formattedBytes} · ${formattedTime}`; + return `${readRows.toLocaleString()} rows read · ${formattedBytes} · ${formattedTime} · ${formatBytes( + byteSeconds + )}s`; } function formatBytes(bytes: number): string { diff --git a/apps/webapp/app/v3/querySchemas.ts b/apps/webapp/app/v3/querySchemas.ts index d7fd9e39f..404827112 100644 --- a/apps/webapp/app/v3/querySchemas.ts +++ b/apps/webapp/app/v3/querySchemas.ts @@ -137,6 +137,11 @@ export const runsSchema: TableSchema = { name: "idempotency_key", ...column("String", { description: "Idempotency key", example: "user-123-action-456" }), }, + region: { + name: "region", + clickhouseName: "region", + ...column("String", { description: "Region", example: "us-east-1" }), + }, // Timing created_at: { diff --git a/internal-packages/clickhouse/src/client/client.ts b/internal-packages/clickhouse/src/client/client.ts index 4d4ff1347..9bc6da776 100644 --- a/internal-packages/clickhouse/src/client/client.ts +++ b/internal-packages/clickhouse/src/client/client.ts @@ -354,10 +354,16 @@ export class ClickhouseClient implements ClickhouseReader, ClickhouseWriter { result_rows: "0", result_bytes: "0", elapsed_ns: "0", + byte_seconds: "0", }; if (typeof summaryHeader === "string") { const parsedSummary = JSON.parse(summaryHeader); + this.logger.log("parsedSummary", parsedSummary); + const readBytes = parsedSummary.read_bytes ? parseInt(parsedSummary.read_bytes, 10) : 0; + const elapsedNs = parsedSummary.elapsed_ns ? parseInt(parsedSummary.elapsed_ns, 10) : 0; + const elapsedSeconds = elapsedNs / 1_000_000_000; + const byteSeconds = readBytes / elapsedSeconds; stats = { read_rows: parsedSummary.read_rows ?? "0", read_bytes: parsedSummary.read_bytes ?? "0", @@ -367,6 +373,7 @@ export class ClickhouseClient implements ClickhouseReader, ClickhouseWriter { result_rows: parsedSummary.result_rows ?? "0", result_bytes: parsedSummary.result_bytes ?? "0", elapsed_ns: parsedSummary.elapsed_ns ?? "0", + byte_seconds: byteSeconds.toString(), }; span.setAttributes({ ...flattenAttributes(parsedSummary, "clickhouse.summary"), diff --git a/internal-packages/clickhouse/src/client/noop.ts b/internal-packages/clickhouse/src/client/noop.ts index a5ace14b4..3509297f9 100644 --- a/internal-packages/clickhouse/src/client/noop.ts +++ b/internal-packages/clickhouse/src/client/noop.ts @@ -77,6 +77,7 @@ export class NoopClient implements ClickhouseReader, ClickhouseWriter { result_rows: "0", result_bytes: "0", elapsed_ns: "0", + byte_seconds: "0", }, }, ]; diff --git a/internal-packages/clickhouse/src/client/types.ts b/internal-packages/clickhouse/src/client/types.ts index bcc6f22f3..25cd2efde 100644 --- a/internal-packages/clickhouse/src/client/types.ts +++ b/internal-packages/clickhouse/src/client/types.ts @@ -25,6 +25,7 @@ export interface QueryStats { result_rows: string; result_bytes: string; elapsed_ns: string; + byte_seconds: string; } /**