Skip to content

Commit b4e08bd

Browse files
authored
Fix(webapp): metrics UI improvements (#3063)
- Lots of small UI improvements - Toggle full screen charts with "V" shortcut <img width="3504" height="2286" alt="CleanShot 2026-02-14 at 20 06 30@2x" src="https://github.com/user-attachments/assets/32403661-06b3-4f6c-a074-243b3f43197d" /> <img width="362" height="222" alt="CleanShot 2026-02-14 at 20 06 58@2x" src="https://github.com/user-attachments/assets/ee4f5859-4b71-482e-9636-5abd78d9f623" /> <img width="434" height="360" alt="CleanShot 2026-02-14 at 20 06 53@2x" src="https://github.com/user-attachments/assets/44339348-f3f3-425b-bbf3-bb41ed6a8e59" /> <img width="460" height="310" alt="CleanShot 2026-02-14 at 20 06 48@2x" src="https://github.com/user-attachments/assets/eb3b6af6-b88a-4677-8a48-acfa6b768770" /> <img width="502" height="332" alt="CleanShot 2026-02-14 at 20 06 45@2x" src="https://github.com/user-attachments/assets/b788d78b-8f4a-4b18-94c8-487673f3ec7b" /> <img width="370" height="257" alt="CleanShot 2026-02-14 at 20 10 29@2x" src="https://github.com/user-attachments/assets/a81e8b14-b2eb-402c-bf3b-affd0d4fde26" />
1 parent 5d6085d commit b4e08bd

File tree

16 files changed

+201
-215
lines changed

16 files changed

+201
-215
lines changed

apps/webapp/app/components/Shortcuts.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ function ShortcutContent() {
193193
<ShortcutKey shortcut={{ key: "v" }} variant="medium/bright" />
194194
</Shortcut>
195195
</div>
196+
<div className="space-y-3">
197+
<Header3>Metrics page</Header3>
198+
<Shortcut name="Toggle fullscreen chart">
199+
<ShortcutKey shortcut={{ key: "v" }} variant="medium/bright" />
200+
</Shortcut>
201+
</div>
196202
<div className="space-y-3">
197203
<Header3>Schedules page</Header3>
198204
<Shortcut name="New schedule">

apps/webapp/app/components/code/ChartConfigPanel.tsx

Lines changed: 52 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type { OutputColumnMetadata } from "@internal/clickhouse";
2+
import { IconSortAscending, IconSortDescending } from "@tabler/icons-react";
23
import { BarChart, CheckIcon, LineChart, Plus, XIcon } from "lucide-react";
34
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
45
import { cn } from "~/utils/cn";
56
import { Paragraph } from "../primitives/Paragraph";
67
import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover";
78
import { Select, SelectItem } from "../primitives/Select";
89
import { Switch } from "../primitives/Switch";
10+
import SegmentedControl from "../primitives/SegmentedControl";
911
import { Button } from "../primitives/Buttons";
1012
import {
1113
type AggregationType,
@@ -234,54 +236,38 @@ export function ChartConfigPanel({ columns, config, onChange, className }: Chart
234236
}
235237

236238
return (
237-
<div className={cn("flex flex-col gap-2 p-2", className)}>
239+
<div className={cn("flex flex-col gap-3 p-2", className)}>
238240
{/* Chart Type */}
239241
<div className="flex flex-col gap-3">
240242
<ConfigField label="Type">
241-
<div className="flex items-center">
242-
<Button
243-
type="button"
244-
variant="tertiary/small"
245-
className={cn(
246-
"rounded-r-none border-b pl-1 pr-2",
247-
config.chartType === "bar" ? "border-indigo-500" : "border-transparent"
248-
)}
249-
iconSpacing="gap-x-1"
250-
onClick={() => updateConfig({ chartType: "bar" })}
251-
LeadingIcon={BarChart}
252-
leadingIconClassName={
253-
config.chartType === "bar" ? "text-indigo-500" : "text-text-dimmed"
254-
}
255-
>
256-
<span className={config.chartType === "bar" ? "text-indigo-500" : "text-text-dimmed"}>
257-
Bar
258-
</span>
259-
</Button>
260-
<Button
261-
type="button"
262-
variant="tertiary/small"
263-
className={cn(
264-
"rounded-l-none border-b pl-1 pr-2",
265-
config.chartType === "line" ? "border-indigo-500" : "border-transparent"
266-
)}
267-
iconSpacing="gap-x-1"
268-
onClick={() => updateConfig({ chartType: "line" })}
269-
LeadingIcon={LineChart}
270-
leadingIconClassName={
271-
config.chartType === "line" ? "text-indigo-500" : "text-text-dimmed"
272-
}
273-
>
274-
<span
275-
className={config.chartType === "line" ? "text-indigo-500" : "text-text-dimmed"}
276-
>
277-
Line
278-
</span>
279-
</Button>
280-
</div>
243+
<SegmentedControl
244+
name="chartType"
245+
value={config.chartType}
246+
variant="secondary/small"
247+
options={[
248+
{
249+
label: (
250+
<span className="flex items-center gap-1">
251+
<BarChart className="size-3" /> Bar
252+
</span>
253+
),
254+
value: "bar",
255+
},
256+
{
257+
label: (
258+
<span className="flex items-center gap-1">
259+
<LineChart className="size-3" /> Line
260+
</span>
261+
),
262+
value: "line",
263+
},
264+
]}
265+
onChange={(value) => updateConfig({ chartType: value as "bar" | "line" })}
266+
/>
281267
</ConfigField>
282268
</div>
283269

284-
<div className="flex flex-col gap-2">
270+
<div className="flex flex-col gap-3">
285271
{/* X-Axis */}
286272
<ConfigField label="X-Axis">
287273
<Select
@@ -529,9 +515,29 @@ export function ChartConfigPanel({ columns, config, onChange, className }: Chart
529515
{/* Sort Direction (only when sorting) */}
530516
{config.sortByColumn && (
531517
<ConfigField label="Sort direction">
532-
<SortDirectionToggle
533-
direction={config.sortDirection}
534-
onChange={(direction) => updateConfig({ sortDirection: direction })}
518+
<SegmentedControl
519+
name="sortDirection"
520+
value={config.sortDirection}
521+
variant="secondary/small"
522+
options={[
523+
{
524+
label: (
525+
<span className="flex items-center gap-1">
526+
<IconSortAscending className="size-3" /> Asc
527+
</span>
528+
),
529+
value: "asc",
530+
},
531+
{
532+
label: (
533+
<span className="flex items-center gap-1">
534+
<IconSortDescending className="size-3" /> Desc
535+
</span>
536+
),
537+
value: "desc",
538+
},
539+
]}
540+
onChange={(value) => updateConfig({ sortDirection: value as SortDirection })}
535541
/>
536542
</ConfigField>
537543
)}
@@ -543,51 +549,12 @@ export function ChartConfigPanel({ columns, config, onChange, className }: Chart
543549
function ConfigField({ label, children }: { label: string; children: React.ReactNode }) {
544550
return (
545551
<div className="flex flex-col gap-1">
546-
{label && <span className="text-xs text-text-dimmed">{label}</span>}
552+
{label && <span className="text-xs text-text-bright">{label}</span>}
547553
{children}
548554
</div>
549555
);
550556
}
551557

552-
function SortDirectionToggle({
553-
direction,
554-
onChange,
555-
}: {
556-
direction: SortDirection;
557-
onChange: (direction: SortDirection) => void;
558-
}) {
559-
return (
560-
<div className="flex gap-1">
561-
<button
562-
type="button"
563-
onClick={() => onChange("asc")}
564-
className={cn(
565-
"rounded px-2 py-1 text-xs transition-colors",
566-
direction === "asc"
567-
? "bg-charcoal-700 text-text-bright"
568-
: "text-text-dimmed hover:bg-charcoal-800 hover:text-text-bright"
569-
)}
570-
title="Ascending"
571-
>
572-
Asc
573-
</button>
574-
<button
575-
type="button"
576-
onClick={() => onChange("desc")}
577-
className={cn(
578-
"rounded px-2 py-1 text-xs transition-colors",
579-
direction === "desc"
580-
? "bg-charcoal-700 text-text-bright"
581-
: "text-text-dimmed hover:bg-charcoal-800 hover:text-text-bright"
582-
)}
583-
title="Descending"
584-
>
585-
Desc
586-
</button>
587-
</div>
588-
);
589-
}
590-
591558
function SeriesColorPicker({
592559
color,
593560
onColorChange,

apps/webapp/app/components/code/QueryResultsChart.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { OutputColumnMetadata } from "@internal/clickhouse";
2+
import { BarChart3, LineChart } from "lucide-react";
23
import { memo, useMemo } from "react";
34
import type { ChartConfig } from "~/components/primitives/charts/Chart";
45
import { Chart } from "~/components/primitives/charts/ChartCompound";
5-
import { Paragraph } from "../primitives/Paragraph";
6+
import { ChartBlankState } from "../primitives/charts/ChartBlankState";
67
import type { AggregationType, ChartConfiguration } from "../metrics/QueryWidget";
78
import { aggregateValues } from "../primitives/charts/aggregation";
89
import { getRunStatusHexColor } from "~/components/runs/v3/TaskRunStatus";
@@ -947,20 +948,22 @@ export const QueryResultsChart = memo(function QueryResultsChart({
947948
}, [isDateBased, xAxisTickFormatter, xAxisAngle]);
948949

949950
// Validation — all hooks must be above this point
951+
const chartIcon = chartType === "bar" ? BarChart3 : LineChart;
952+
950953
if (!xAxisColumn) {
951-
return <EmptyState message="Select an X-axis column to display the chart" />;
954+
return <ChartBlankState icon={chartIcon} message="Select an X-axis column to display the chart" />;
952955
}
953956

954957
if (yAxisColumns.length === 0) {
955-
return <EmptyState message="Select a Y-axis column to display the chart" />;
958+
return <ChartBlankState icon={chartIcon} message="Select a Y-axis column to display the chart" />;
956959
}
957960

958961
if (rows.length === 0) {
959-
return <EmptyState message="No data to display" />;
962+
return <ChartBlankState icon={chartIcon} message="No data to display" />;
960963
}
961964

962965
if (data.length === 0) {
963-
return <EmptyState message="Unable to transform data for chart" />;
966+
return <ChartBlankState icon={chartIcon} message="Unable to transform data for chart" />;
964967
}
965968

966969
// Base x-axis props shared by all chart types
@@ -1113,12 +1116,3 @@ function createYAxisFormatter(data: Record<string, unknown>[], series: string[])
11131116
};
11141117
}
11151118

1116-
function EmptyState({ message }: { message: string }) {
1117-
return (
1118-
<div className="flex h-full min-h-[300px] items-center justify-center">
1119-
<Paragraph variant="small" className="text-text-dimmed">
1120-
{message}
1121-
</Paragraph>
1122-
</div>
1123-
);
1124-
}

0 commit comments

Comments
 (0)