From bc0d1ff59a8152b303ca7f30fa7b2be0b98646c5 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 12 Feb 2026 17:48:02 +0000 Subject: [PATCH] Metrics dashboards (#3019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary - Implemented metrics dashboards with a built-in dashboard and custom dashboards - Added a "Big number” display type What changed - New data format for metric layouts and saving/editing layouts (editing, saving, cancel revert) - QueryWidget usable on Query page and Metrics dashboards - Time filtering, auto-reloading and timeBucket() auto-bin support - Filters added to metrics; widget popover/improved history and blank states - Side menu: - Metrics/Insights section with icons, colors, padding, collapsible behavior and reordering of custom dashboards - Move action logic into service for reuse and API querying; refactor reordering for reuse --- Open with Devin --------- Co-authored-by: James Ritchie --- .vscode/settings.json | 1 - apps/webapp/app/components/AlphaBadge.tsx | 29 + .../app/components/code/AIQueryInput.tsx | 149 +- .../app/components/code/ChartConfigPanel.tsx | 206 ++- .../app/components/code/QueryResultsChart.tsx | 359 ++-- .../webapp/app/components/code/TSQLEditor.tsx | 43 +- .../app/components/code/TSQLResultsTable.tsx | 124 +- .../webapp/app/components/code/chartColors.ts | 183 +++ .../components/code/tsql/tsqlCompletion.ts | 10 + .../app/components/layout/AppLayout.tsx | 2 +- .../app/components/logs/LogsTaskFilter.tsx | 4 +- .../app/components/metrics/QueryWidget.tsx | 496 ++++++ .../app/components/metrics/QueuesFilter.tsx | 212 +++ .../metrics/SaveToDashboardDialog.tsx | 177 ++ .../app/components/metrics/ScopeFilter.tsx | 64 + .../app/components/metrics/TitleWidget.tsx | 125 ++ .../navigation/DashboardDialogs.tsx | 255 +++ .../components/navigation/DashboardList.tsx | 123 ++ .../app/components/navigation/SideMenu.tsx | 691 ++++---- .../components/navigation/SideMenuItem.tsx | 112 +- .../components/navigation/SideMenuSection.tsx | 28 +- .../components/navigation/TreeConnectors.tsx | 29 + .../components/navigation/sideMenuTypes.ts | 7 + .../navigation/useReorderableList.ts | 129 ++ .../components/primitives/AppliedFilter.tsx | 16 +- .../app/components/primitives/ClientTabs.tsx | 3 +- .../app/components/primitives/FormButtons.tsx | 4 +- .../primitives/LoadingBarDivider.tsx | 6 +- .../app/components/primitives/Popover.tsx | 27 +- .../app/components/primitives/Resizable.tsx | 6 +- .../app/components/primitives/Tooltip.tsx | 2 +- .../primitives/charts/BigNumber.tsx | 46 - .../primitives/charts/BigNumberCard.tsx | 171 ++ .../app/components/primitives/charts/Card.tsx | 17 +- .../components/primitives/charts/ChartBar.tsx | 41 +- .../primitives/charts/ChartLegendCompound.tsx | 56 +- .../primitives/charts/ChartLine.tsx | 28 +- .../app/components/query/QueryEditor.tsx | 1457 +++++++++++++++++ .../app/components/runs/v3/SharedFilters.tsx | 103 +- .../app/components/runs/v3/TaskRunStatus.tsx | 39 + apps/webapp/app/env.server.ts | 4 + apps/webapp/app/hooks/useDashboardEditor.ts | 515 ++++++ apps/webapp/app/hooks/useElementVisibility.ts | 35 + apps/webapp/app/hooks/useInterval.ts | 63 + apps/webapp/app/hooks/useOrganizations.ts | 26 + apps/webapp/app/hooks/useRevalidateOnParam.ts | 57 + .../app/models/runtimeEnvironment.server.ts | 23 + .../presenters/v3/BuiltInDashboards.server.ts | 225 +++ .../presenters/v3/LimitsPresenter.server.ts | 45 + .../v3/MetricDashboardPresenter.server.ts | 123 ++ .../route.tsx | 24 +- .../route.tsx | 295 ++++ .../route.tsx | 772 +++++++++ .../AITabContent.tsx | 14 +- .../ExamplesContent.tsx | 13 + .../QueryHistoryPopover.tsx | 51 +- .../TRQLGuideContent.tsx | 5 + .../route.tsx | 960 +---------- .../_app.orgs.$organizationSlug/route.tsx | 51 +- apps/webapp/app/routes/resources.metric.tsx | 283 ++++ ...vParam.dashboards.$dashboardId.widgets.tsx | 492 ++++++ ...tParam.env.$envParam.dashboards.create.tsx | 84 + ...ces.orgs.$organizationSlug.select-plan.tsx | 69 +- .../routes/resources.preferences.sidemenu.tsx | 47 +- .../app/routes/storybook.charts/route.tsx | 8 +- .../app/services/clickhouseInstance.server.ts | 30 +- .../services/dashboardPreferences.server.ts | 95 +- .../app/services/queryService.server.ts | 194 ++- apps/webapp/app/tailwind.css | 40 + apps/webapp/app/utils/pathBuilder.ts | 22 +- apps/webapp/app/v3/querySchemas.ts | 5 + .../app/v3/services/aiQueryService.server.ts | 57 +- apps/webapp/package.json | 4 +- apps/webapp/tailwind.config.js | 6 + .../clickhouse/src/client/tsql.ts | 10 +- .../migration.sql | 25 + .../migration.sql | 3 + .../migration.sql | 5 + .../migration.sql | 2 + .../database/prisma/schema.prisma | 147 +- internal-packages/tsql/src/index.ts | 42 +- .../tsql/src/query/printer.test.ts | 285 +++- internal-packages/tsql/src/query/printer.ts | 117 +- .../tsql/src/query/printer_context.ts | 47 +- internal-packages/tsql/src/query/schema.ts | 18 + .../tsql/src/query/time_buckets.test.ts | 181 ++ .../tsql/src/query/time_buckets.ts | 86 + internal-packages/tsql/src/query/validator.ts | 7 +- pnpm-lock.yaml | 64 +- 89 files changed, 9408 insertions(+), 1948 deletions(-) create mode 100644 apps/webapp/app/components/code/chartColors.ts create mode 100644 apps/webapp/app/components/metrics/QueryWidget.tsx create mode 100644 apps/webapp/app/components/metrics/QueuesFilter.tsx create mode 100644 apps/webapp/app/components/metrics/SaveToDashboardDialog.tsx create mode 100644 apps/webapp/app/components/metrics/ScopeFilter.tsx create mode 100644 apps/webapp/app/components/metrics/TitleWidget.tsx create mode 100644 apps/webapp/app/components/navigation/DashboardDialogs.tsx create mode 100644 apps/webapp/app/components/navigation/DashboardList.tsx create mode 100644 apps/webapp/app/components/navigation/TreeConnectors.tsx create mode 100644 apps/webapp/app/components/navigation/sideMenuTypes.ts create mode 100644 apps/webapp/app/components/navigation/useReorderableList.ts delete mode 100644 apps/webapp/app/components/primitives/charts/BigNumber.tsx create mode 100644 apps/webapp/app/components/primitives/charts/BigNumberCard.tsx create mode 100644 apps/webapp/app/components/query/QueryEditor.tsx create mode 100644 apps/webapp/app/hooks/useDashboardEditor.ts create mode 100644 apps/webapp/app/hooks/useElementVisibility.ts create mode 100644 apps/webapp/app/hooks/useInterval.ts create mode 100644 apps/webapp/app/hooks/useRevalidateOnParam.ts create mode 100644 apps/webapp/app/presenters/v3/BuiltInDashboards.server.ts create mode 100644 apps/webapp/app/presenters/v3/MetricDashboardPresenter.server.ts create mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.metrics.$dashboardKey/route.tsx create mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.metrics.custom.$dashboardId/route.tsx create mode 100644 apps/webapp/app/routes/resources.metric.tsx create mode 100644 apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx create mode 100644 apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.create.tsx create mode 100644 internal-packages/database/prisma/migrations/20260201130503_metrics_dashboard_table_created/migration.sql create mode 100644 internal-packages/database/prisma/migrations/20260202044337_metrics_dashboard_description/migration.sql create mode 100644 internal-packages/database/prisma/migrations/20260202100000_add_friendlyid_to_metrics_dashboard/migration.sql create mode 100644 internal-packages/database/prisma/migrations/20260211120000_make_metrics_dashboard_owner_nullable/migration.sql create mode 100644 internal-packages/tsql/src/query/time_buckets.test.ts create mode 100644 internal-packages/tsql/src/query/time_buckets.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 382a5ae62..fd9f3dcde 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,5 @@ "packages/cli-v3/e2e": true }, "vitest.disableWorkspaceWarning": true, - "typescript.experimental.useTsgo": true, "chat.agent.maxRequests": 10000 } diff --git a/apps/webapp/app/components/AlphaBadge.tsx b/apps/webapp/app/components/AlphaBadge.tsx index 58da1a994..0a1c4a7fc 100644 --- a/apps/webapp/app/components/AlphaBadge.tsx +++ b/apps/webapp/app/components/AlphaBadge.tsx @@ -30,3 +30,32 @@ export function AlphaTitle({ children }: { children: React.ReactNode }) { ); } + +export function BetaBadge({ + inline = false, + className, +}: { + inline?: boolean; + className?: string; +}) { + return ( + + Beta + + } + content="This feature is in Beta." + disableHoverableContent + /> + ); +} + +export function BetaTitle({ children }: { children: React.ReactNode }) { + return ( + <> + {children} + + + ); +} diff --git a/apps/webapp/app/components/code/AIQueryInput.tsx b/apps/webapp/app/components/code/AIQueryInput.tsx index 38d0c9b21..0775ec2c2 100644 --- a/apps/webapp/app/components/code/AIQueryInput.tsx +++ b/apps/webapp/app/components/code/AIQueryInput.tsx @@ -1,7 +1,13 @@ -import { PencilSquareIcon, PlusIcon, SparklesIcon } from "@heroicons/react/20/solid"; +import { CheckIcon, PencilSquareIcon, PlusIcon, XMarkIcon } from "@heroicons/react/20/solid"; import { AnimatePresence, motion } from "framer-motion"; import { Suspense, lazy, useCallback, useEffect, useRef, useState } from "react"; -import { AISparkleIcon } from "~/assets/icons/AISparkleIcon"; +import { Button } from "~/components/primitives/Buttons"; +import { Spinner } from "~/components/primitives/Spinner"; +import { useEnvironment } from "~/hooks/useEnvironment"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; +import type { AITimeFilter } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/types"; +import { cn } from "~/utils/cn"; // Lazy load streamdown components to avoid SSR issues const StreamdownRenderer = lazy(() => @@ -13,13 +19,6 @@ const StreamdownRenderer = lazy(() => ), })) ); -import { Button } from "~/components/primitives/Buttons"; -import { Spinner } from "~/components/primitives/Spinner"; -import { useEnvironment } from "~/hooks/useEnvironment"; -import { useOrganization } from "~/hooks/useOrganizations"; -import { useProject } from "~/hooks/useProject"; -import type { AITimeFilter } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/types"; -import { cn } from "~/utils/cn"; type StreamEventType = | { type: "thinking"; content: string } @@ -179,21 +178,7 @@ export function AIQueryInput({ setThinking((prev) => prev + event.content); break; case "tool_call": - if (event.tool === "setTimeFilter") { - setThinking((prev) => { - if (prev.trimEnd().endsWith("Setting time filter...")) { - return prev; - } - return prev + `\nSetting time filter...\n`; - }); - } else { - setThinking((prev) => { - if (prev.trimEnd().endsWith("Validating query...")) { - return prev; - } - return prev + `\nValidating query...\n`; - }); - } + // Tool calls are handled silently — no UI text needed break; case "time_filter": // Apply time filter immediately when the AI sets it @@ -262,13 +247,13 @@ export function AIQueryInput({ }, [error]); return ( -
+
{/* Gradient border wrapper like the schedules AI input */}
-
+