Skip to content

Commit d236f8a

Browse files
committed
WIP Time Grain
1 parent b74dbe7 commit d236f8a

7 files changed

Lines changed: 440 additions & 46 deletions

File tree

exec/java-exec/src/main/resources/webapp/src/api/queries.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,21 @@ export async function executeQuery(request: QueryRequest): Promise<QueryResult>
2525
// Strip trailing semicolons — Drill's SQL parser doesn't accept them
2626
// (the CLI strips them automatically, but the REST API passes them through)
2727
const query = request.query.replace(/;\s*$/, '');
28-
const response = await apiClient.post<QueryResult>('/query.json', {
28+
const body: Record<string, unknown> = {
2929
query,
3030
queryType: request.queryType || 'SQL',
3131
autoLimit: request.autoLimitRowCount ? String(request.autoLimitRowCount) : '',
32-
userName: request.userName,
33-
defaultSchema: request.defaultSchema,
34-
options: request.options,
35-
});
32+
};
33+
if (request.userName) {
34+
body.userName = request.userName;
35+
}
36+
if (request.defaultSchema) {
37+
body.defaultSchema = request.defaultSchema;
38+
}
39+
if (request.options) {
40+
body.options = request.options;
41+
}
42+
const response = await apiClient.post<QueryResult>('/query.json', body);
3643

3744
// Drill returns HTTP 200 even for failed queries — check queryState
3845
if (response.data.queryState === 'FAILED') {

exec/java-exec/src/main/resources/webapp/src/components/visualization/ChartPreview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface ChartPreviewProps {
2828
config: VisualizationConfig;
2929
data: QueryResult | null;
3030
loading?: boolean;
31-
height?: number;
31+
height?: number | string;
3232
mini?: boolean;
3333
}
3434

@@ -762,7 +762,7 @@ export default function ChartPreview({
762762
<Table
763763
dataSource={data.rows.map((row, idx) => ({ ...row, key: idx }))}
764764
columns={tableColumns}
765-
scroll={{ y: height - 100 }}
765+
scroll={{ y: typeof height === 'number' ? height - 100 : 'calc(100% - 100px)' }}
766766
size="small"
767767
pagination={{ pageSize: 50, showSizeChanger: true }}
768768
/>

exec/java-exec/src/main/resources/webapp/src/components/visualization/VisualizationBuilder.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ export default function VisualizationBuilder({
162162
return;
163163
}
164164
const sourceSql = sql;
165-
if (!sourceSql || !hasCompleteAggregationConfig(config.chartOptions, config.metrics)) {
165+
const timeGrain = config.chartOptions?.timeGrain;
166+
const hasAgg = hasCompleteAggregationConfig(config.chartOptions, config.metrics);
167+
if (!sourceSql || (!hasAgg && !(timeGrain && config.xAxis))) {
166168
setAggregatedData(null);
167169
setAggregatedError(null);
168170
return;

exec/java-exec/src/main/resources/webapp/src/components/visualization/VisualizationEditor.tsx

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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) */}

exec/java-exec/src/main/resources/webapp/src/pages/VisualizationsPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ function MiniVizPreview({ viz }: { viz: Visualization }) {
107107
const [loading, setLoading] = useState(false);
108108
const [failed, setFailed] = useState(false);
109109

110+
// Serialize config to a stable string so the effect re-fires when config changes
111+
// (e.g. time grain or aggregation settings updated)
112+
const configKey = JSON.stringify(viz.config || {});
113+
110114
useEffect(() => {
111115
if (!viz.sql) {
112116
setFailed(true);
@@ -145,7 +149,7 @@ function MiniVizPreview({ viz }: { viz: Visualization }) {
145149
return () => {
146150
cancelled = true;
147151
};
148-
}, [viz.id, viz.sql, viz.defaultSchema]);
152+
}, [viz.id, viz.sql, viz.defaultSchema, configKey]);
149153

150154
// Fall back to icon on failure or no SQL
151155
if (failed || (!loading && !queryResult)) {

0 commit comments

Comments
 (0)