@@ -38,7 +38,7 @@ import { executeQuery } from '../../api/queries';
3838import ChartTypeSelector from './ChartTypeSelector' ;
3939import ColumnMapper from './ColumnMapper' ;
4040import ChartPreview from './ChartPreview' ;
41- import { getEffectiveQuery } from '../../utils/sqlTransformations' ;
41+ import { computeEffectiveQuery } from '../../utils/sqlTransformations' ;
4242import type { ChartType , VisualizationConfig , QueryResult , VisualizationCreate , Visualization } from '../../types' ;
4343
4444const { Text } = Typography ;
@@ -118,13 +118,16 @@ export default function VisualizationEditor({
118118 }
119119 setDataLoading ( true ) ;
120120 setDataError ( null ) ;
121+ console . debug ( '[VizEditor:fetchData] sending base query, defaultSchema=%s, sql=%s' ,
122+ visualization ?. defaultSchema , editedSql . substring ( 0 , 120 ) ) ;
121123 try {
122124 const result = await executeQuery ( {
123125 query : editedSql ,
124126 queryType : 'SQL' ,
125127 autoLimitRowCount : 10000 ,
126128 defaultSchema : visualization ?. defaultSchema ,
127129 } ) ;
130+ console . debug ( '[VizEditor:fetchData] SUCCESS rows=%d cols=%s' , result . rows ?. length , result . columns ) ;
128131 setBaseData ( result ) ;
129132 setSqlDirty ( false ) ;
130133 } catch ( err : unknown ) {
@@ -148,7 +151,7 @@ export default function VisualizationEditor({
148151 } , [ open , visualization , editedSql , fetchData ] ) ;
149152
150153 // Extract column info from base data (always the full column set)
151- const columns = useMemo ( ( ) => {
154+ const baseColumns = useMemo ( ( ) => {
152155 if ( ! baseData || ! baseData . columns || ! baseData . metadata ) {
153156 return [ ] ;
154157 }
@@ -158,6 +161,45 @@ export default function VisualizationEditor({
158161 } ) ) ;
159162 } , [ baseData ] ) ;
160163
164+ // Fallback columns: when the base query fails, derive columns from
165+ // the aggregated query result or the saved config so the ColumnMapper
166+ // (and time grain selector) can still render.
167+ const columns = useMemo ( ( ) => {
168+ if ( baseColumns . length > 0 ) {
169+ return baseColumns ;
170+ }
171+ // Try aggregated data first — it has real type metadata
172+ if ( aggregatedData ?. columns && aggregatedData ?. metadata ) {
173+ return aggregatedData . columns . map ( ( name , idx ) => ( {
174+ name,
175+ type : aggregatedData . metadata [ idx ] || 'VARCHAR' ,
176+ } ) ) ;
177+ }
178+ // Last resort: reconstruct from saved config with assumed types
179+ const cols : { name : string ; type : string } [ ] = [ ] ;
180+ if ( config . xAxis ) {
181+ cols . push ( { name : config . xAxis , type : 'DATE' } ) ;
182+ }
183+ if ( config . yAxis ) {
184+ cols . push ( { name : config . yAxis , type : 'DOUBLE' } ) ;
185+ }
186+ if ( config . metrics ) {
187+ config . metrics . forEach ( m => {
188+ if ( ! cols . some ( c => c . name === m ) ) {
189+ cols . push ( { name : m , type : 'DOUBLE' } ) ;
190+ }
191+ } ) ;
192+ }
193+ if ( config . dimensions ) {
194+ config . dimensions . forEach ( d => {
195+ if ( ! cols . some ( c => c . name === d ) ) {
196+ cols . push ( { name : d , type : 'VARCHAR' } ) ;
197+ }
198+ } ) ;
199+ }
200+ return cols ;
201+ } , [ baseColumns , aggregatedData , config . xAxis , config . yAxis , config . metrics , config . dimensions ] ) ;
202+
161203 // Detect stale column mappings after data changes
162204 useEffect ( ( ) => {
163205 if ( columns . length === 0 ) {
@@ -182,34 +224,33 @@ export default function VisualizationEditor({
182224 setStaleMapping ( hasStale ) ;
183225 } , [ columns , config . xAxis , config . yAxis , config . metrics , config . dimensions ] ) ;
184226
185- const [ effectiveQuery , setEffectiveQuery ] = useState < string > ( '' ) ;
186-
187- // Compute effective query asynchronously — gated on configReady so we don't
188- // fire with stale/incomplete config before the saved visualization is loaded.
189- useEffect ( ( ) => {
227+ // Compute effective query synchronously — gated on configReady so we don't
228+ // compute with stale/incomplete config before the saved visualization is loaded.
229+ const effectiveQuery = useMemo ( ( ) => {
190230 if ( ! configReady || ! editedSql ) {
191- setEffectiveQuery ( '' ) ;
192- return ;
231+ return '' ;
193232 }
194- let cancelled = false ;
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- } ) ;
207- return ( ) => { cancelled = true ; } ;
233+ return computeEffectiveQuery ( editedSql , config ) ;
208234 } , [ configReady , editedSql , config ] ) ;
209235
210236 // Data for chart preview: prefer aggregated data when available
211237 const previewData = aggregatedData || baseData ;
212238
239+ // Loading state for chart: when aggregation is active, only wait for the
240+ // aggregated query (don't block the chart on the base-data fetch which is
241+ // only needed for ColumnMapper's column list). Matches VisualizationBuilder.
242+ const chartLoading = ( effectiveQuery && effectiveQuery !== editedSql )
243+ ? aggregatedLoading
244+ : dataLoading ;
245+
246+ console . debug ( '[VizEditor] render — chartType=%s, xAxis=%s, metrics=%s, dims=%s, timeGrain=%s, dataLoading=%s, aggLoading=%s, chartLoading=%s, previewData=%s, rows=%d, effectiveQuery=%s' ,
247+ chartType , config . xAxis , JSON . stringify ( config . metrics ) , JSON . stringify ( config . dimensions ) ,
248+ config . chartOptions ?. timeGrain || '(none)' ,
249+ dataLoading , aggregatedLoading , chartLoading ,
250+ aggregatedData ? 'aggregated' : baseData ? 'base' : 'none' ,
251+ previewData ?. rows ?. length ?? 0 ,
252+ effectiveQuery ? effectiveQuery . substring ( 0 , 80 ) : '(empty)' ) ;
253+
213254 // Fetch aggregated data when the effective query differs from the base SQL
214255 useEffect ( ( ) => {
215256 if ( ! open || ! editedSql ) {
@@ -344,28 +385,30 @@ export default function VisualizationEditor({
344385
345386 { /* Data Mapping Section */ }
346387 < Card size = "small" title = "Data Mapping" style = { { marginBottom : 12 } } >
347- { dataLoading ? (
388+ { dataLoading && columns . length === 0 ? (
348389 < div style = { { padding : 16 , textAlign : 'center' } } >
349390 < Spin size = "small" />
350391 < div style = { { marginTop : 8 } } >
351392 < Text type = "secondary" > Loading columns...</ Text >
352393 </ div >
353394 </ div >
354- ) : dataError ? (
355- < Alert
356- type = "warning"
357- message = "Could not load data"
358- description = { dataError }
359- showIcon
360- style = { { marginBottom : 8 } }
361- action = {
362- < Button size = "small" icon = { < ReloadOutlined /> } onClick = { fetchData } >
363- Retry
364- </ Button >
365- }
366- />
367395 ) : (
368396 < >
397+ { dataError && (
398+ < Alert
399+ type = "warning"
400+ message = "Could not load base data"
401+ description = { dataError }
402+ showIcon
403+ closable
404+ style = { { marginBottom : 8 } }
405+ action = {
406+ < Button size = "small" icon = { < ReloadOutlined /> } onClick = { fetchData } >
407+ Retry
408+ </ Button >
409+ }
410+ />
411+ ) }
369412 { staleMapping && (
370413 < Alert
371414 type = "warning"
@@ -476,7 +519,7 @@ export default function VisualizationEditor({
476519 chartType = { chartType }
477520 config = { { ...config , colorScheme : selectedColorScheme } }
478521 data = { previewData }
479- loading = { dataLoading || aggregatedLoading }
522+ loading = { chartLoading }
480523 height = "100%"
481524 />
482525 </ div >
@@ -505,7 +548,7 @@ export default function VisualizationEditor({
505548 < WarningOutlined /> SQL modified but not yet run. Click Run to refresh the preview.
506549 </ Text >
507550 ) }
508- { effectiveQuery !== editedSql && editedSql && (
551+ { effectiveQuery && effectiveQuery !== editedSql && editedSql && (
509552 < >
510553 < Text type = "secondary" style = { { fontSize : 11 , display : 'block' , marginBottom : 4 } } >
511554 Effective Query (with aggregation)
0 commit comments