Skip to content

Commit f53db6f

Browse files
authored
Query: time limits, performance improvements, styling (#2953)
Summary - Query: add time limits, performance improvements, and styling updates Changes - Add ClickHouse output_text and error_text columns with indexes - Automatically use _text columns for JSON based on query pattern; support JSON column data prefixes - Add idempotency key and scope columns - Add enforcedWhereClause for tenant and time restrictions, instead of the old tenant stuff. - Implement basic time filter limiting and set default time period based on plan; show message when results are clipped - UX: resizable code area (including vertical splits), collapsible sidebar, fix table/chart vertical sizing, max height for chart legend in fullscreen - Styling and UI tweaks: improved chart legend styling, more chart colours, thinner line chart stroke, pricing callout color, improved layout for callouts - Features: generate and save AI titles <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/triggerdotdev/trigger.dev/pull/2953"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end -->
1 parent c0b86ef commit f53db6f

36 files changed

+2982
-793
lines changed

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import { Chart } from "~/components/primitives/charts/ChartCompound";
55
import { Paragraph } from "../primitives/Paragraph";
66
import type { AggregationType, ChartConfiguration } from "./ChartConfigPanel";
77

8-
// Color palette for chart series
8+
// Color palette for chart series - 30 distinct colors for large datasets
99
const CHART_COLORS = [
10-
"#7655fd", // Primary purple
10+
// Primary colors
11+
"#7655fd", // Purple
1112
"#22c55e", // Green
1213
"#f59e0b", // Amber
1314
"#ef4444", // Red
@@ -17,6 +18,28 @@ const CHART_COLORS = [
1718
"#14b8a6", // Teal
1819
"#f97316", // Orange
1920
"#6366f1", // Indigo
21+
// Extended palette
22+
"#84cc16", // Lime
23+
"#0ea5e9", // Sky
24+
"#f43f5e", // Rose
25+
"#a855f7", // Fuchsia
26+
"#eab308", // Yellow
27+
"#10b981", // Emerald
28+
"#3b82f6", // Blue
29+
"#d946ef", // Magenta
30+
"#78716c", // Stone
31+
"#facc15", // Gold
32+
// Additional distinct colors
33+
"#2dd4bf", // Turquoise
34+
"#fb923c", // Light orange
35+
"#a3e635", // Yellow-green
36+
"#38bdf8", // Light blue
37+
"#c084fc", // Light purple
38+
"#4ade80", // Light green
39+
"#fbbf24", // Light amber
40+
"#f472b6", // Light pink
41+
"#67e8f9", // Light cyan
42+
"#818cf8", // Light indigo
2043
];
2144

2245
function getSeriesColor(index: number): string {
@@ -30,6 +53,8 @@ interface QueryResultsChartProps {
3053
fullLegend?: boolean;
3154
/** Callback when "View all" legend button is clicked */
3255
onViewAllLegendItems?: () => void;
56+
/** When true, constrains legend to max 50% height with scrolling */
57+
legendScrollable?: boolean;
3358
}
3459

3560
interface TransformedData {
@@ -702,6 +727,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
702727
config,
703728
fullLegend = false,
704729
onViewAllLegendItems,
730+
legendScrollable = false,
705731
}: QueryResultsChartProps) {
706732
const {
707733
xAxisColumn,
@@ -872,6 +898,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
872898
minHeight="300px"
873899
fillContainer
874900
onViewAllLegendItems={onViewAllLegendItems}
901+
legendScrollable={legendScrollable}
875902
>
876903
<Chart.Bar
877904
xAxisProps={xAxisPropsForBar}
@@ -896,6 +923,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
896923
minHeight="300px"
897924
fillContainer
898925
onViewAllLegendItems={onViewAllLegendItems}
926+
legendScrollable={legendScrollable}
899927
>
900928
<Chart.Line
901929
xAxisProps={xAxisPropsForLine}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,9 @@ function EnvironmentCellValue({ value }: { value: string }) {
672672
}
673673

674674
function JSONCellValue({ value }: { value: unknown }) {
675-
const jsonString = JSON.stringify(value);
675+
// If the value is already a string (e.g., from a textColumn optimization),
676+
// use it directly without double-stringifying
677+
const jsonString = typeof value === "string" ? value : JSON.stringify(value);
676678
const isTruncated = jsonString.length > MAX_STRING_DISPLAY_LENGTH;
677679

678680
if (isTruncated) {
@@ -1137,6 +1139,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
11371139
height: `${rowVirtualizer.getTotalSize()}px`,
11381140
position: "relative",
11391141
}}
1142+
className="bg-background-dimmed divide-y divide-charcoal-700"
11401143
>
11411144
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
11421145
const row = tableRows[virtualRow.index];

apps/webapp/app/components/primitives/AnimatedNumber.tsx

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,64 @@
11
import { animate, motion, useMotionValue, useTransform } from "framer-motion";
2-
import { useEffect } from "react";
2+
import { useEffect, useMemo } from "react";
33

4-
export function AnimatedNumber({ value, duration = 0.5 }: { value: number; duration?: number }) {
4+
/**
5+
* Determines the number of decimal places to display based on the value.
6+
* - For integers or large numbers (>=100), no decimals
7+
* - For numbers >= 10, 1 decimal place
8+
* - For numbers >= 1, 2 decimal places
9+
* - For smaller numbers, up to 4 decimal places
10+
*/
11+
function getDecimalPlaces(value: number): number {
12+
if (Number.isInteger(value)) return 0;
13+
14+
const absValue = Math.abs(value);
15+
if (absValue >= 100) return 0;
16+
if (absValue >= 10) return 1;
17+
if (absValue >= 1) return 2;
18+
if (absValue >= 0.1) return 3;
19+
return 4;
20+
}
21+
22+
/**
23+
* Sanitizes a decimal places value to ensure it's valid for toLocaleString.
24+
* - Coerces to a finite number (handles NaN, Infinity, -Infinity)
25+
* - Rounds to an integer
26+
* - Clamps to the valid 0-20 range for toLocaleString options
27+
*/
28+
function sanitizeDecimals(decimals: number): number {
29+
if (!Number.isFinite(decimals)) {
30+
return 0;
31+
}
32+
return Math.min(20, Math.max(0, Math.round(decimals)));
33+
}
34+
35+
export function AnimatedNumber({
36+
value,
37+
duration = 0.5,
38+
decimalPlaces,
39+
}: {
40+
value: number;
41+
duration?: number;
42+
/** Number of decimal places to display. If not provided, auto-detects based on value. */
43+
decimalPlaces?: number;
44+
}) {
545
const motionValue = useMotionValue(value);
6-
let display = useTransform(motionValue, (current) => Math.round(current).toLocaleString());
46+
47+
// Determine decimal places - use provided value or auto-detect, then sanitize
48+
const safeDecimals = useMemo(() => {
49+
const rawDecimals = decimalPlaces !== undefined ? decimalPlaces : getDecimalPlaces(value);
50+
return sanitizeDecimals(rawDecimals);
51+
}, [decimalPlaces, value]);
52+
53+
const display = useTransform(motionValue, (current) => {
54+
if (safeDecimals === 0) {
55+
return Math.round(current).toLocaleString();
56+
}
57+
return current.toLocaleString(undefined, {
58+
minimumFractionDigits: safeDecimals,
59+
maximumFractionDigits: safeDecimals,
60+
});
61+
});
762

863
useEffect(() => {
964
animate(motionValue, value, {

apps/webapp/app/components/primitives/Callout.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
CreditCardIcon,
23
ExclamationCircleIcon,
34
ExclamationTriangleIcon,
45
InformationCircleIcon,
@@ -60,10 +61,10 @@ export const variantClasses = {
6061
linkClassName: "transition hover:bg-blue-400/20",
6162
},
6263
pricing: {
63-
className: "border-charcoal-700 bg-charcoal-800",
64-
icon: <ChartBarIcon className="h-5 w-5 shrink-0 text-text-dimmed" />,
65-
textColor: "text-text-bright",
66-
linkClassName: "transition hover:bg-charcoal-750",
64+
className: "border-indigo-400/20 bg-indigo-800/30",
65+
icon: <CreditCardIcon className="h-5 w-5 shrink-0 text-indigo-400" />,
66+
textColor: "text-indigo-300",
67+
linkClassName: "transition hover:bg-indigo-400/20",
6768
},
6869
} as const;
6970

apps/webapp/app/components/primitives/Resizable.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,40 @@ const ResizableHandle = ({
2626
}) => (
2727
<PanelResizer
2828
className={cn(
29-
"group relative flex w-0.75 items-center justify-center focus-custom after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 hover:w-0.75 [&[data-panel-group-direction=vertical]>div]:rotate-90",
29+
// Base styles
30+
"group relative flex items-center justify-center focus-custom",
31+
// Horizontal orientation (default)
32+
"w-0.75 after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2",
33+
// Vertical orientation
34+
"data-[handle-orientation=vertical]:h-0.75 data-[handle-orientation=vertical]:w-full",
35+
"data-[handle-orientation=vertical]:after:inset-x-0 data-[handle-orientation=vertical]:after:inset-y-auto",
36+
"data-[handle-orientation=vertical]:after:top-1/2 data-[handle-orientation=vertical]:after:left-0",
37+
"data-[handle-orientation=vertical]:after:h-1 data-[handle-orientation=vertical]:after:w-full",
38+
"data-[handle-orientation=vertical]:after:-translate-y-1/2 data-[handle-orientation=vertical]:after:translate-x-0",
3039
className
3140
)}
3241
size="3px"
3342
{...props}
3443
>
35-
<div className="absolute left-[0.0625rem] top-0 h-full w-px bg-grid-bright transition group-hover:left-0 group-hover:w-0.75 group-hover:bg-lavender-500" />
44+
{/* Horizontal orientation line indicator */}
45+
<div className="absolute left-[0.0625rem] top-0 h-full w-px bg-grid-bright transition group-hover:left-0 group-hover:w-0.75 group-hover:bg-lavender-500 group-data-[handle-orientation=vertical]:hidden" />
46+
{/* Vertical orientation line indicator */}
47+
<div className="absolute left-0 top-[0.0625rem] hidden h-px w-full bg-grid-bright transition group-hover:top-0 group-hover:h-0.75 group-hover:bg-lavender-500 group-data-[handle-orientation=vertical]:block" />
3648
{withHandle && (
37-
<div className="z-10 flex h-5 w-3 flex-col items-center justify-center gap-[0.1875rem] bg-background-dimmed group-hover:hidden">
38-
{Array.from({ length: 3 }).map((_, index) => (
39-
<div key={index} className="h-[0.1875rem] w-0.75 rounded-full bg-charcoal-600" />
40-
))}
41-
</div>
49+
<>
50+
{/* Horizontal orientation dots (vertical arrangement) */}
51+
<div className="z-10 flex h-5 w-0.75 flex-col items-center justify-center gap-[0.1875rem] bg-background-dimmed group-hover:hidden group-data-[handle-orientation=vertical]:hidden">
52+
{Array.from({ length: 3 }).map((_, index) => (
53+
<div key={index} className="h-[0.1875rem] w-0.75 rounded-full bg-charcoal-600" />
54+
))}
55+
</div>
56+
{/* Vertical orientation dots (horizontal arrangement) */}
57+
<div className="z-10 hidden h-0.75 w-5 flex-row items-center justify-center gap-[0.1875rem] bg-background-dimmed group-hover:hidden group-data-[handle-orientation=vertical]:flex">
58+
{Array.from({ length: 3 }).map((_, index) => (
59+
<div key={index} className="h-0.75 w-[0.1875rem] rounded-full bg-charcoal-600" />
60+
))}
61+
</div>
62+
</>
4263
)}
4364
</PanelResizer>
4465
);

apps/webapp/app/components/primitives/charts/Card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const Card = ({ children, className }: { children: ReactNode; className?:
66
return (
77
<div
88
className={cn(
9-
"flex flex-col rounded-lg border border-grid-bright bg-background-bright pb-2 pt-4",
9+
"flex flex-col rounded-lg border border-grid-bright bg-background-bright pb-1.5 pt-3",
1010
className
1111
)}
1212
>
@@ -17,7 +17,7 @@ export const Card = ({ children, className }: { children: ReactNode; className?:
1717

1818
const CardHeader = ({ children }: { children: ReactNode }) => {
1919
return (
20-
<Header3 className="mb-4 flex items-center justify-between gap-2 px-4">{children}</Header3>
20+
<Header3 className="mb-3 flex items-center justify-between gap-2 px-3">{children}</Header3>
2121
);
2222
};
2323

0 commit comments

Comments
 (0)