@@ -24,6 +24,7 @@ import {
2424 type ParsedSource ,
2525} from "./smartColumnData" ;
2626import { SmartColumnSample } from "./SmartColumnSample" ;
27+ import { isNumericSmartDisplay , SmartCellContent } from "./smartColumnCell" ;
2728import type { loader as sampleLoader } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.smart-column-sample" ;
2829
2930type AddSmartColumnDialogProps = {
@@ -98,37 +99,40 @@ export function AddSmartColumnDialog({
9899 const sampleLoaded = sample . data !== undefined && sample . state === "idle" ;
99100 const sampleData = sample . data ;
100101
101- const { usable, anyOffloaded, runCount } = useMemo ( ( ) => {
102+ const { perRun , usable, anyOffloaded, runCount } = useMemo ( ( ) => {
102103 const runs = sampleData ?. runs ?? [ ] ;
103- const parsed = runs . map ( ( run ) => {
104- switch ( source ) {
105- case "payload" :
106- return parseSource ( { data : run . payload , dataType : run . payloadType } ) ;
107- case "metadata" :
108- return parseSource ( { data : run . metadata , dataType : run . metadataType } ) ;
109- case "output" :
110- return parseSource ( { data : run . output , dataType : run . outputType } ) ;
111- }
112- } ) ;
104+ const perRun = runs . map ( ( run ) => ( {
105+ hasFinished : run . hasFinished ,
106+ parsed :
107+ source === "payload"
108+ ? parseSource ( { data : run . payload , dataType : run . payloadType } )
109+ : source === "metadata"
110+ ? parseSource ( { data : run . metadata , dataType : run . metadataType } )
111+ : parseSource ( { data : run . output , dataType : run . outputType } ) ,
112+ } ) ) ;
113113 return {
114114 runCount : runs . length ,
115- anyOffloaded : parsed . some ( ( p ) => p . state === "offloaded" ) ,
116- usable : parsed . filter (
117- ( p ) : p is Extract < ParsedSource , { state : "parsed" } > => p . state === "parsed"
115+ perRun,
116+ anyOffloaded : perRun . some ( ( r ) => r . parsed . state === "offloaded" ) ,
117+ usable : perRun . filter (
118+ ( r ) : r is { hasFinished : boolean ; parsed : Extract < ParsedSource , { state : "parsed" } > } =>
119+ r . parsed . state === "parsed"
118120 ) ,
119121 } ;
120122 } , [ sampleData , source ] ) ;
121123
122124 const activeIndex = usable . length > 0 ? Math . min ( sampleIndex , usable . length - 1 ) : 0 ;
123- const activeSample = usable [ activeIndex ] ;
124-
125- const resolved = useMemo ( ( ) => {
126- if ( ! activeSample || path . trim ( ) . length === 0 ) return undefined ;
127- return extractSmartValue ( activeSample , path ) ;
128- } , [ activeSample , path ] ) ;
125+ const activeSample = usable [ activeIndex ] ?. parsed ;
129126
130127 const canSubmit = path . trim ( ) . length > 0 ;
131128
129+ const previewDef : SmartColumnDef = {
130+ source,
131+ path : path . trim ( ) ,
132+ label : effectiveLabel ,
133+ displayAs,
134+ } ;
135+
132136 const handleSubmit = ( ) => {
133137 if ( ! canSubmit ) return ;
134138 onSubmit ( { source, path : path . trim ( ) , label : effectiveLabel . trim ( ) || path . trim ( ) , displayAs } ) ;
@@ -137,15 +141,15 @@ export function AddSmartColumnDialog({
137141
138142 return (
139143 < Dialog open = { open } onOpenChange = { onOpenChange } >
140- < DialogContent className = "sm:max-w-[820px ]!" >
144+ < DialogContent className = "sm:max-w-[1040px ]!" >
141145 < DialogHeader > { editing ? "Edit smart column" : "Add smart column" } </ DialogHeader >
142146 < div className = "flex flex-col gap-5 p-1" >
143147 < Callout variant = "info" >
144148 Display only. A smart column shows you a value from a run, but you can't sort or filter
145149 the list by it. To narrow the list, use tags or the query editor.
146150 </ Callout >
147151
148- < div className = "grid grid-cols-1 gap-6 md:grid-cols-[1fr_300px ]" >
152+ < div className = "grid grid-cols-1 gap-5 md:grid-cols-[minmax(0,1fr)_260px_220px ]" >
149153 < div className = "flex flex-col gap-5" >
150154 < div className = "flex flex-col gap-1.5" >
151155 < Label > Source</ Label >
@@ -253,10 +257,11 @@ export function AddSmartColumnDialog({
253257 No recent run has a { source } value to sample.
254258 </ Paragraph >
255259 ) }
256- < Paragraph variant = "extra-extra-small/dimmed/caps" className = "mt-2" >
257- Resolves to
258- </ Paragraph >
259- < SmartColumnResolvedPreview label = { effectiveLabel } resolved = { resolved } />
260+ </ div >
261+
262+ < div className = "flex flex-col gap-1.5 self-start" >
263+ < Paragraph variant = "extra-extra-small/dimmed/caps" > Preview</ Paragraph >
264+ < SmartColumnPreview rows = { perRun } def = { previewDef } loaded = { sampleLoaded } />
260265 </ div >
261266 </ div >
262267 </ div >
@@ -347,27 +352,50 @@ function SourceCard({
347352 ) ;
348353}
349354
350- function SmartColumnResolvedPreview ( {
351- label,
352- resolved,
355+ function SmartColumnPreview ( {
356+ rows,
357+ def,
358+ loaded,
353359} : {
354- label : string ;
355- resolved : ReturnType < typeof extractSmartValue > | undefined ;
360+ rows : { hasFinished : boolean ; parsed : ParsedSource } [ ] ;
361+ def : SmartColumnDef ;
362+ loaded : boolean ;
356363} ) {
357- let value : string ;
358- if ( ! resolved ) value = "–" ;
359- else if ( resolved . state === "offloaded" ) value = "Too large" ;
360- else if ( resolved . state === "empty" ) value = "–" ;
361- else if ( typeof resolved . value === "object" ) value = JSON . stringify ( resolved . value ) ;
362- else value = String ( resolved . value ) ;
364+ const numeric = isNumericSmartDisplay ( def . displayAs ) ;
365+ const alignClass = numeric ? "justify-end text-right tabular-nums" : "justify-start text-left" ;
363366
364367 return (
365- < div className = "rounded border border-grid-dimmed" >
366- < div className = "flex items-center gap-1 border-b border-grid-dimmed px-2 py-1" >
368+ < div className = "overflow-hidden rounded-lg border border-grid-dimmed" >
369+ < div className = "flex items-center gap-1 border-b border-grid-dimmed bg-background-dimmed px-2.5 py-1.5 " >
367370 < BoltIcon className = "size-3.5 flex-none text-text-dimmed" />
368- < span className = "truncate text-xs text-text-bright" > { label || "Column" } </ span >
371+ < span className = "truncate text-xs font-medium text-text-bright" >
372+ { def . label || "Column" }
373+ </ span >
374+ </ div >
375+ < div className = "max-h-80 overflow-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control" >
376+ { ! loaded ? (
377+ < div className = "px-2.5 py-2 text-xs text-text-dimmed" > Loading…</ div >
378+ ) : rows . length === 0 ? (
379+ < div className = "px-2.5 py-2 text-xs text-text-dimmed" > No runs</ div >
380+ ) : (
381+ rows . map ( ( row , index ) => {
382+ const cell = def . path
383+ ? extractSmartValue ( row . parsed , def . path )
384+ : ( { state : "empty" } as const ) ;
385+ return (
386+ < div
387+ key = { index }
388+ className = { cn (
389+ "flex h-8 items-center border-b border-grid-dimmed/60 px-2.5 text-sm last:border-b-0" ,
390+ alignClass
391+ ) }
392+ >
393+ < SmartCellContent cell = { cell } def = { def } provisional = { ! row . hasFinished } />
394+ </ div >
395+ ) ;
396+ } )
397+ ) }
369398 </ div >
370- < div className = "px-2 py-1.5 text-right text-sm tabular-nums text-text-bright" > { value } </ div >
371399 </ div >
372400 ) ;
373401}
0 commit comments