Skip to content

Commit 087087f

Browse files
committed
Clip columns to 64 characters max
1 parent 22c1775 commit 087087f

File tree

1 file changed

+102
-17
lines changed

1 file changed

+102
-17
lines changed

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

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,29 @@ import { useOrganization } from "~/hooks/useOrganizations";
2929
import { useProject } from "~/hooks/useProject";
3030
import { QueueName } from "../runs/v3/QueueName";
3131

32+
const MAX_STRING_DISPLAY_LENGTH = 64;
33+
34+
/**
35+
* Truncate a string for display, adding ellipsis if it exceeds max length
36+
*/
37+
function truncateString(value: string, maxLength: number = MAX_STRING_DISPLAY_LENGTH): string {
38+
if (value.length <= maxLength) {
39+
return value;
40+
}
41+
return value.slice(0, maxLength) + "…";
42+
}
43+
44+
/**
45+
* Convert any value to a string suitable for copying
46+
* Objects and arrays are JSON stringified, primitives use String()
47+
*/
48+
function valueToString(value: unknown): string {
49+
if (value === null) return "NULL";
50+
if (value === undefined) return "UNDEFINED";
51+
if (typeof value === "object") return JSON.stringify(value);
52+
return String(value);
53+
}
54+
3255
/**
3356
* Check if a ClickHouse type is a DateTime type
3457
*/
@@ -76,9 +99,25 @@ function CellValue({
7699
column: OutputColumnMetadata;
77100
prettyFormatting?: boolean;
78101
}) {
79-
// Plain text mode - render everything as monospace text
102+
// Plain text mode - render everything as monospace text with truncation
80103
if (!prettyFormatting) {
81-
return <pre className="font-mono text-xs">{value === null ? "NULL" : String(value)}</pre>;
104+
const plainValue = value === null ? "NULL" : String(value);
105+
const isTruncated = plainValue.length > MAX_STRING_DISPLAY_LENGTH;
106+
107+
if (isTruncated) {
108+
return (
109+
<SimpleTooltip
110+
content={
111+
<pre className="max-w-sm whitespace-pre-wrap break-all font-mono text-xs">
112+
{plainValue}
113+
</pre>
114+
}
115+
button={<pre className="font-mono text-xs">{truncateString(plainValue)}</pre>}
116+
/>
117+
);
118+
}
119+
120+
return <pre className="font-mono text-xs">{plainValue}</pre>;
82121
}
83122

84123
if (value === null) {
@@ -201,15 +240,51 @@ function CellValue({
201240

202241
// JSON type
203242
if (type === "JSON") {
204-
return <span className="font-mono text-xs text-text-dimmed">{JSON.stringify(value)}</span>;
243+
const jsonString = JSON.stringify(value);
244+
const isTruncated = jsonString.length > MAX_STRING_DISPLAY_LENGTH;
245+
246+
if (isTruncated) {
247+
return (
248+
<SimpleTooltip
249+
content={
250+
<pre className="max-w-sm whitespace-pre-wrap break-all font-mono text-xs">
251+
{jsonString}
252+
</pre>
253+
}
254+
button={
255+
<span className="font-mono text-xs text-text-dimmed">{truncateString(jsonString)}</span>
256+
}
257+
/>
258+
);
259+
}
260+
return <span className="font-mono text-xs text-text-dimmed">{jsonString}</span>;
205261
}
206262

207263
// Array types
208264
if (type.startsWith("Array")) {
209-
return <span className="font-mono text-xs text-text-dimmed">{JSON.stringify(value)}</span>;
265+
const arrayString = JSON.stringify(value);
266+
const isTruncated = arrayString.length > MAX_STRING_DISPLAY_LENGTH;
267+
268+
if (isTruncated) {
269+
return (
270+
<SimpleTooltip
271+
content={
272+
<pre className="max-w-sm whitespace-pre-wrap break-all font-mono text-xs">
273+
{arrayString}
274+
</pre>
275+
}
276+
button={
277+
<span className="font-mono text-xs text-text-dimmed">
278+
{truncateString(arrayString)}
279+
</span>
280+
}
281+
/>
282+
);
283+
}
284+
return <span className="font-mono text-xs text-text-dimmed">{arrayString}</span>;
210285
}
211286

212-
// Boolean-like types (UInt8 is commonly used for booleans in ClickHouse)
287+
// Boolean types
213288
if (isBooleanType(type)) {
214289
if (typeof value === "boolean") {
215290
return <span className="text-text-dimmed">{value ? "true" : "false"}</span>;
@@ -220,16 +295,32 @@ function CellValue({
220295
return <span>{String(value)}</span>;
221296
}
222297

223-
// Numeric types (excluding UInt8 which is handled as boolean above)
224-
if (isNumericType(type) && type !== "UInt8" && type !== "Nullable(UInt8)") {
298+
// Numeric types
299+
if (isNumericType(type)) {
225300
if (typeof value === "number") {
226301
return <span className="tabular-nums">{formatNumber(value)}</span>;
227302
}
228303
return <span>{String(value)}</span>;
229304
}
230305

231-
// Default to string rendering
232-
return <span>{String(value)}</span>;
306+
// Default to string rendering with truncation for long values
307+
const stringValue = String(value);
308+
const isTruncated = stringValue.length > MAX_STRING_DISPLAY_LENGTH;
309+
310+
if (isTruncated) {
311+
return (
312+
<SimpleTooltip
313+
content={
314+
<pre className="max-w-sm whitespace-pre-wrap break-all font-mono text-xs">
315+
{stringValue}
316+
</pre>
317+
}
318+
button={<span>{truncateString(stringValue)}</span>}
319+
/>
320+
);
321+
}
322+
323+
return <span>{stringValue}</span>;
233324
}
234325

235326
function ProjectCellValue({ value }: { value: string }) {
@@ -268,13 +359,7 @@ function isRightAlignedColumn(column: OutputColumnMetadata): boolean {
268359
return true;
269360
}
270361

271-
// Check for numeric types (excluding UInt8 which is often used for booleans)
272-
const { type } = column;
273-
if (type === "UInt8" || type === "Nullable(UInt8)") {
274-
return false;
275-
}
276-
277-
return isNumericType(type);
362+
return isNumericType(column.type);
278363
}
279364

280365
export function TSQLResultsTable({
@@ -331,7 +416,7 @@ export function TSQLResultsTable({
331416
<CopyableTableCell
332417
key={col.name}
333418
alignment={isRightAlignedColumn(col) ? "right" : "left"}
334-
value={String(row[col.name])}
419+
value={valueToString(row[col.name])}
335420
>
336421
<span className="flex-1">
337422
<CellValue

0 commit comments

Comments
 (0)