@@ -83,6 +83,9 @@ export default function VisualizationEditor({
8383 const [ sqlDirty , setSqlDirty ] = useState ( false ) ;
8484 const [ staleMapping , setStaleMapping ] = useState ( false ) ;
8585
86+ // Guard: prevent effective query from firing before the saved config is loaded
87+ const [ configReady , setConfigReady ] = useState ( false ) ;
88+
8689 // Watch colorScheme from form so preview updates live
8790 const selectedColorScheme = Form . useWatch ( 'colorScheme' , form ) || 'default' ;
8891
@@ -100,6 +103,10 @@ export default function VisualizationEditor({
100103 colorScheme : visualization . config ?. colorScheme || 'default' ,
101104 isPublic : visualization . isPublic || false ,
102105 } ) ;
106+ // Signal that the saved config has been fully loaded — the effective
107+ // query effect is gated on this flag to avoid computing queries with
108+ // stale/incomplete config (e.g. missing timeGrain).
109+ setConfigReady ( true ) ;
103110 }
104111 } , [ open , visualization , form ] ) ;
105112
@@ -177,20 +184,28 @@ export default function VisualizationEditor({
177184
178185 const [ effectiveQuery , setEffectiveQuery ] = useState < string > ( '' ) ;
179186
180- // Compute effective query asynchronously (time grain via backend)
187+ // Compute effective query asynchronously — gated on configReady so we don't
188+ // fire with stale/incomplete config before the saved visualization is loaded.
181189 useEffect ( ( ) => {
182- if ( ! editedSql ) {
190+ if ( ! configReady || ! editedSql ) {
183191 setEffectiveQuery ( '' ) ;
184192 return ;
185193 }
186194 let cancelled = false ;
187- getEffectiveQuery ( editedSql , config ) . then ( ( result ) => {
188- if ( ! cancelled ) {
189- setEffectiveQuery ( result ) ;
190- }
191- } ) ;
195+ getEffectiveQuery ( editedSql , config )
196+ . then ( ( result ) => {
197+ if ( ! cancelled ) {
198+ setEffectiveQuery ( result ) ;
199+ }
200+ } )
201+ . catch ( ( err ) => {
202+ console . error ( '[VisualizationEditor] getEffectiveQuery failed:' , err ) ;
203+ if ( ! cancelled ) {
204+ setEffectiveQuery ( editedSql ) ;
205+ }
206+ } ) ;
192207 return ( ) => { cancelled = true ; } ;
193- } , [ editedSql , config ] ) ;
208+ } , [ configReady , editedSql , config ] ) ;
194209
195210 // Data for chart preview: prefer aggregated data when available
196211 const previewData = aggregatedData || baseData ;
@@ -251,6 +266,7 @@ export default function VisualizationEditor({
251266 } ) ;
252267
253268 const handleClose = ( ) => {
269+ setConfigReady ( false ) ;
254270 setChartType ( 'bar' ) ;
255271 setConfig ( { } ) ;
256272 setBaseData ( null ) ;
@@ -439,7 +455,7 @@ export default function VisualizationEditor({
439455 </ div >
440456
441457 { /* Chart Preview */ }
442- < div style = { { flex : 1 , padding : 16 , overflow : 'hidden' } } >
458+ < div style = { { flex : 1 , padding : 16 , overflow : 'hidden' , display : 'flex' , flexDirection : 'column' } } >
443459 { aggregatedError && (
444460 < Alert
445461 message = "Aggregation Error"
@@ -452,16 +468,18 @@ export default function VisualizationEditor({
452468 showIcon
453469 closable
454470 onClose = { ( ) => setAggregatedError ( null ) }
455- style = { { marginBottom : 8 } }
471+ style = { { marginBottom : 8 , flexShrink : 0 } }
456472 />
457473 ) }
458- < ChartPreview
459- chartType = { chartType }
460- config = { { ...config , colorScheme : selectedColorScheme } }
461- data = { previewData }
462- loading = { dataLoading || aggregatedLoading }
463- height = { showSql ? 250 : 450 }
464- />
474+ < div style = { { flex : 1 , minHeight : 0 } } >
475+ < ChartPreview
476+ chartType = { chartType }
477+ config = { { ...config , colorScheme : selectedColorScheme } }
478+ data = { previewData }
479+ loading = { dataLoading || aggregatedLoading }
480+ height = "100%"
481+ />
482+ </ div >
465483 </ div >
466484
467485 { /* SQL Panel (collapsible) */ }
0 commit comments