This commit is contained in:
Vadim Ogievetsky 2024-10-03 01:00:46 -07:00 committed by GitHub
parent 13a52de07f
commit e4d1848aba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 154 additions and 83 deletions

View File

@ -35,9 +35,17 @@
& > .issue { & > .issue {
position: absolute; position: absolute;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.loader {
position: absolute;
top: 0;
left: 0;
}
} }
.tile-content { .tile-content {

View File

@ -22,6 +22,7 @@ import { Button, Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons'; import { IconNames } from '@blueprintjs/icons';
import type { Column, QueryResult, SqlExpression } from '@druid-toolkit/query'; import type { Column, QueryResult, SqlExpression } from '@druid-toolkit/query';
import { QueryRunner, SqlQuery } from '@druid-toolkit/query'; import { QueryRunner, SqlQuery } from '@druid-toolkit/query';
import type { CancelToken } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
@ -79,27 +80,33 @@ const queryRunner = new QueryRunner({
}, },
}); });
async function runSqlQuery(query: string | SqlQuery): Promise<QueryResult> { async function runSqlQuery(
query: string | SqlQuery,
cancelToken?: CancelToken,
): Promise<QueryResult> {
try { try {
return await queryRunner.runQuery({ return await queryRunner.runQuery({
query, query,
defaultQueryContext: { defaultQueryContext: {
sqlStringifyArrays: false, sqlStringifyArrays: false,
}, },
cancelToken,
}); });
} catch (e) { } catch (e) {
throw new DruidError(e); throw new DruidError(e);
} }
} }
async function introspectSource(source: string): Promise<QuerySource> { async function introspectSource(source: string, cancelToken?: CancelToken): Promise<QuerySource> {
const query = SqlQuery.parse(source); const query = SqlQuery.parse(source);
const introspectResult = await runSqlQuery(QuerySource.makeLimitZeroIntrospectionQuery(query)); const introspectResult = await runSqlQuery(QuerySource.makeLimitZeroIntrospectionQuery(query));
cancelToken?.throwIfRequested();
const baseIntrospectResult = QuerySource.isSingleStarQuery(query) const baseIntrospectResult = QuerySource.isSingleStarQuery(query)
? introspectResult ? introspectResult
: await runSqlQuery( : await runSqlQuery(
QuerySource.makeLimitZeroIntrospectionQuery(QuerySource.stripToBaseSource(query)), QuerySource.makeLimitZeroIntrospectionQuery(QuerySource.stripToBaseSource(query)),
cancelToken,
); );
return QuerySource.fromIntrospectResult( return QuerySource.fromIntrospectResult(
@ -238,11 +245,15 @@ export const ExploreView = React.memo(function ExploreView() {
const querySource = querySourceState.getSomeData(); const querySource = querySourceState.getSomeData();
const runSqlPlusQuery = useMemo(() => { const runSqlPlusQuery = useMemo(() => {
return async (query: string | SqlQuery) => { return async (query: string | SqlQuery, cancelToken?: CancelToken) => {
if (!querySource) throw new Error('no querySource'); if (!querySource) throw new Error('no querySource');
return await runSqlQuery( const parsedQuery = SqlQuery.parse(query);
await rewriteMaxDataTime(rewriteAggregate(SqlQuery.parse(query), querySource.measures)), return (
); await runSqlQuery(
await rewriteMaxDataTime(rewriteAggregate(parsedQuery, querySource.measures)),
cancelToken,
)
).attachQuery({ query: '' }, parsedQuery);
}; };
}, [querySource]); }, [querySource]);

View File

@ -44,31 +44,6 @@ export class QuerySource {
); );
} }
static materializeStarIfNeeded(query: SqlQuery, columns: readonly Column[]): SqlQuery {
let columnsToExpand = columns.map(c => c.name);
const selectExpressions = query.getSelectExpressionsArray();
let starCount = 0;
for (const selectExpression of selectExpressions) {
if (selectExpression instanceof SqlStar) {
starCount++;
continue;
}
const outputName = selectExpression.getOutputName();
if (!outputName) continue;
columnsToExpand = columnsToExpand.filter(c => c !== outputName);
}
if (starCount === 0) return query;
if (starCount > 1) throw new Error('can not handle multiple stars');
return query
.changeSelectExpressions(
selectExpressions.flatMap(selectExpression =>
selectExpression instanceof SqlStar ? columnsToExpand.map(c => C(c)) : selectExpression,
),
)
.prettify();
}
static isSingleStarQuery(query: SqlQuery): boolean { static isSingleStarQuery(query: SqlQuery): boolean {
const selectExpressions = query.getSelectExpressionsArray(); const selectExpressions = query.getSelectExpressionsArray();
return selectExpressions.length === 1 && selectExpressions[0] instanceof SqlStar; return selectExpressions.length === 1 && selectExpressions[0] instanceof SqlStar;
@ -151,6 +126,35 @@ export class QuerySource {
}; };
} }
private materializeStarIfNeeded(): SqlQuery {
const { query, columns, measures } = this;
let columnsToExpand = columns.map(c => c.name);
const selectExpressions = query.getSelectExpressionsArray();
let starCount = 0;
for (const selectExpression of selectExpressions) {
if (selectExpression instanceof SqlStar) {
starCount++;
continue;
}
const outputName = selectExpression.getOutputName();
if (!outputName) continue;
columnsToExpand = columnsToExpand.filter(c => c !== outputName);
}
if (starCount === 0) return query;
if (starCount > 1) throw new Error('can not handle multiple stars');
return Measure.addMeasuresToQuery(
query
.changeSelectExpressions(
selectExpressions.flatMap(selectExpression =>
selectExpression instanceof SqlStar ? columnsToExpand.map(c => C(c)) : selectExpression,
),
)
.prettify(),
measures,
);
}
public getFirstAggregateMeasure(): Measure | undefined { public getFirstAggregateMeasure(): Measure | undefined {
return this.measures[0]?.toAggregateBasedMeasure(); return this.measures[0]?.toAggregateBasedMeasure();
} }
@ -226,12 +230,12 @@ export class QuerySource {
} }
public addColumn(newExpression: SqlExpression): SqlQuery { public addColumn(newExpression: SqlExpression): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return noStarQuery.addSelect(newExpression); return noStarQuery.addSelect(newExpression);
} }
public addColumnAfter(neighborName: string, ...newExpressions: SqlExpression[]): SqlQuery { public addColumnAfter(neighborName: string, ...newExpressions: SqlExpression[]): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return noStarQuery.changeSelectExpressions( return noStarQuery.changeSelectExpressions(
noStarQuery noStarQuery
.getSelectExpressionsArray() .getSelectExpressionsArray()
@ -240,7 +244,7 @@ export class QuerySource {
} }
public changeColumn(oldName: string, newExpression: SqlExpression): SqlQuery { public changeColumn(oldName: string, newExpression: SqlExpression): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return noStarQuery.changeSelectExpressions( return noStarQuery.changeSelectExpressions(
noStarQuery noStarQuery
.getSelectExpressionsArray() .getSelectExpressionsArray()
@ -249,7 +253,7 @@ export class QuerySource {
} }
public deleteColumn(outputName: string): SqlQuery { public deleteColumn(outputName: string): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return noStarQuery.changeSelectExpressions( return noStarQuery.changeSelectExpressions(
noStarQuery.getSelectExpressionsArray().filter(ex => ex.getOutputName() !== outputName), noStarQuery.getSelectExpressionsArray().filter(ex => ex.getOutputName() !== outputName),
); );
@ -260,7 +264,7 @@ export class QuerySource {
} }
public applyColumnNameMap(columnNameMap: Map<string, string>): SqlQuery { public applyColumnNameMap(columnNameMap: Map<string, string>): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return noStarQuery.changeSelectExpressions( return noStarQuery.changeSelectExpressions(
noStarQuery.getSelectExpressionsArray().map(ex => { noStarQuery.getSelectExpressionsArray().map(ex => {
const outputName = ex.getOutputName(); const outputName = ex.getOutputName();
@ -275,12 +279,12 @@ export class QuerySource {
// ------------------------------------ // ------------------------------------
public addMeasure(measure: Measure): SqlQuery { public addMeasure(measure: Measure): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return Measure.addMeasuresToQuery(noStarQuery, this.measures.concat(measure)); return Measure.addMeasuresToQuery(noStarQuery, this.measures.concat(measure));
} }
public addMeasureAfter(neighborName: string, newMeasure: Measure): SqlQuery { public addMeasureAfter(neighborName: string, newMeasure: Measure): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return Measure.addMeasuresToQuery( return Measure.addMeasuresToQuery(
noStarQuery, noStarQuery,
this.measures.flatMap(m => (m.name === neighborName ? [m, newMeasure] : m)), this.measures.flatMap(m => (m.name === neighborName ? [m, newMeasure] : m)),
@ -288,7 +292,7 @@ export class QuerySource {
} }
public changeMeasure(oldName: string, newMeasure: Measure): SqlQuery { public changeMeasure(oldName: string, newMeasure: Measure): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return Measure.addMeasuresToQuery( return Measure.addMeasuresToQuery(
noStarQuery, noStarQuery,
this.measures.map(m => (m.name === oldName ? newMeasure : m)), this.measures.map(m => (m.name === oldName ? newMeasure : m)),
@ -296,7 +300,7 @@ export class QuerySource {
} }
public deleteMeasure(measureName: string): SqlQuery { public deleteMeasure(measureName: string): SqlQuery {
const noStarQuery = QuerySource.materializeStarIfNeeded(this.query, this.columns); const noStarQuery = this.materializeStarIfNeeded();
return Measure.addMeasuresToQuery( return Measure.addMeasuresToQuery(
noStarQuery, noStarQuery,
this.measures.filter(m => m.name !== measureName), this.measures.filter(m => m.name !== measureName),

View File

@ -17,6 +17,7 @@
*/ */
import type { QueryResult, SqlExpression, SqlQuery } from '@druid-toolkit/query'; import type { QueryResult, SqlExpression, SqlQuery } from '@druid-toolkit/query';
import type { CancelToken } from 'axios';
import type { ParameterDefinition, QuerySource, Stage } from '../models'; import type { ParameterDefinition, QuerySource, Stage } from '../models';
@ -34,7 +35,7 @@ interface ModuleComponentProps<P> {
setWhere(where: SqlExpression): void; setWhere(where: SqlExpression): void;
parameterValues: P; parameterValues: P;
setParameterValues: (parameters: Partial<P>) => void; setParameterValues: (parameters: Partial<P>) => void;
runSqlQuery(query: string | SqlQuery): Promise<QueryResult>; runSqlQuery(query: string | SqlQuery, cancelToken?: CancelToken): Promise<QueryResult>;
} }
export class ModuleRepository { export class ModuleRepository {

View File

@ -21,6 +21,7 @@ import type { ECharts } from 'echarts';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { Loader } from '../../../components';
import { useQueryManager } from '../../../hooks'; import { useQueryManager } from '../../../hooks';
import { formatEmpty } from '../../../utils'; import { formatEmpty } from '../../../utils';
import { Issue } from '../components'; import { Issue } from '../components';
@ -90,10 +91,10 @@ ModuleRepository.registerModule<BarChartParameterValues>({
.changeLimitValue(limit); .changeLimitValue(limit);
}, [querySource, where, splitColumn, measure, measureToSort, limit]); }, [querySource, where, splitColumn, measure, measureToSort, limit]);
const [sourceDataState] = useQueryManager({ const [sourceDataState, queryManager] = useQueryManager({
query: dataQuery, query: dataQuery,
processQuery: async (query: SqlQuery) => { processQuery: async (query, cancelToken) => {
return (await runSqlQuery(query)).toObjectArray(); return (await runSqlQuery(query, cancelToken)).toObjectArray();
}, },
}); });
@ -203,6 +204,9 @@ ModuleRepository.registerModule<BarChartParameterValues>({
}} }}
/> />
{errorMessage && <Issue issue={errorMessage} />} {errorMessage && <Issue issue={errorMessage} />}
{sourceDataState.loading && (
<Loader cancelText="Cancel query" onCancel={() => queryManager.cancelCurrent()} />
)}
</div> </div>
); );
}, },

View File

@ -17,7 +17,7 @@
*/ */
import { Button } from '@blueprintjs/core'; import { Button } from '@blueprintjs/core';
import type { SqlOrderByDirection } from '@druid-toolkit/query'; import type { SqlExpression, SqlOrderByDirection } from '@druid-toolkit/query';
import { C, F, SqlQuery } from '@druid-toolkit/query'; import { C, F, SqlQuery } from '@druid-toolkit/query';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
@ -43,6 +43,11 @@ import './grouping-table-module.scss';
// when ordering on non __time is more robust // when ordering on non __time is more robust
const NEEDS_GROUPING_TO_ORDER = true; const NEEDS_GROUPING_TO_ORDER = true;
interface QueryAndMore {
originalWhere: SqlExpression;
queryAndHints: QueryAndHints;
}
interface GroupingTableParameterValues { interface GroupingTableParameterValues {
splitColumns: ExpressionMeta[]; splitColumns: ExpressionMeta[];
timeBucket: string; timeBucket: string;
@ -216,14 +221,14 @@ ModuleRepository.registerModule<GroupingTableParameterValues>({
.changeLimitValue(maxPivotValues); .changeLimitValue(maxPivotValues);
}, [querySource.query, parameterValues]); }, [querySource.query, parameterValues]);
const [pivotValueState] = useQueryManager({ const [pivotValueState, queryManager] = useQueryManager({
query: pivotValueQuery, query: pivotValueQuery,
processQuery: async (pivotValueQuery: SqlQuery) => { processQuery: async (pivotValueQuery: SqlQuery) => {
return (await runSqlQuery(pivotValueQuery)).getColumnByName('v') as string[]; return (await runSqlQuery(pivotValueQuery)).getColumnByName('v') as string[];
}, },
}); });
const queryAndHints = useMemo((): QueryAndHints | undefined => { const queryAndMore = useMemo((): QueryAndMore | undefined => {
const pivotValues = pivotValueState.data; const pivotValues = pivotValueState.data;
if (parameterValues.pivotColumn && !pivotValues) return; if (parameterValues.pivotColumn && !pivotValues) return;
const { orderByColumn, orderByDirection } = parameterValues; const { orderByColumn, orderByDirection } = parameterValues;
@ -231,32 +236,43 @@ ModuleRepository.registerModule<GroupingTableParameterValues>({
? C(orderByColumn).toOrderByExpression(orderByDirection) ? C(orderByColumn).toOrderByExpression(orderByDirection)
: undefined; : undefined;
return makeTableQueryAndHints({ return {
source: querySource.query, originalWhere: where,
where, queryAndHints: makeTableQueryAndHints({
splitColumns: parameterValues.splitColumns, source: querySource.query,
timeBucket: parameterValues.timeBucket, where,
showColumns: parameterValues.showColumns, splitColumns: parameterValues.splitColumns,
multipleValueMode: parameterValues.multipleValueMode, timeBucket: parameterValues.timeBucket,
pivotColumn: parameterValues.pivotColumn, showColumns: parameterValues.showColumns,
pivotValues, multipleValueMode: parameterValues.multipleValueMode,
measures: parameterValues.measures, pivotColumn: parameterValues.pivotColumn,
compares: parameterValues.compares || [], pivotValues,
compareStrategy: parameterValues.compareStrategy, measures: parameterValues.measures,
compareTypes: parameterValues.compareTypes, compares: parameterValues.compares || [],
restrictTop: parameterValues.restrictTop, compareStrategy: parameterValues.compareStrategy,
maxRows: parameterValues.maxRows, compareTypes: parameterValues.compareTypes,
orderBy, restrictTop: parameterValues.restrictTop,
useGroupingToOrderSubQueries: NEEDS_GROUPING_TO_ORDER, maxRows: parameterValues.maxRows,
}); orderBy,
useGroupingToOrderSubQueries: NEEDS_GROUPING_TO_ORDER,
}),
};
}, [querySource.query, where, parameterValues, pivotValueState.data]); }, [querySource.query, where, parameterValues, pivotValueState.data]);
const [resultState] = useQueryManager({ const [resultState] = useQueryManager({
query: queryAndHints, query: queryAndMore,
processQuery: async (queryAndHints: QueryAndHints) => { processQuery: async (queryAndMore, cancelToken) => {
const { originalWhere, queryAndHints } = queryAndMore;
const { query, columnHints } = queryAndHints; const { query, columnHints } = queryAndHints;
let result = await runSqlQuery(query, cancelToken);
if (result.sqlQuery) {
result = result.attachQuery(
{ query: '' },
result.sqlQuery.changeWhereExpression(originalWhere),
);
}
return { return {
result: await runSqlQuery(query), result,
columnHints, columnHints,
}; };
}, },
@ -297,7 +313,9 @@ ModuleRepository.registerModule<GroupingTableParameterValues>({
initPageSize={calculateInitPageSize(stage.height)} initPageSize={calculateInitPageSize(stage.height)}
/> />
) : undefined} ) : undefined}
{resultState.loading && <Loader />} {resultState.loading && (
<Loader cancelText="Cancel query" onCancel={() => queryManager.cancelCurrent()} />
)}
</div> </div>
); );
}, },

View File

@ -21,6 +21,7 @@ import type { ECharts } from 'echarts';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { Loader } from '../../../components';
import { useQueryManager } from '../../../hooks'; import { useQueryManager } from '../../../hooks';
import { import {
formatInteger, formatInteger,
@ -91,16 +92,16 @@ ModuleRepository.registerModule<MultiAxisChartParameterValues>({
direction: 'ASC', direction: 'ASC',
}) })
.applyForEach(measures, (q, measure) => q.addSelect(measure.expression.as(measure.name))); .applyForEach(measures, (q, measure) => q.addSelect(measure.expression.as(measure.name)));
}, [querySource, where, timeGranularity, measures]); }, [querySource, where, timeColumnName, timeGranularity, measures]);
const [sourceDataState] = useQueryManager({ const [sourceDataState, queryManager] = useQueryManager({
query: dataQuery, query: dataQuery,
processQuery: async (query: SqlQuery) => { processQuery: async (query: SqlQuery, cancelToken) => {
if (!timeColumnName) { if (!timeColumnName) {
throw new Error(`Must have a column of type TIMESTAMP for the multi-axis chart to work`); throw new Error(`Must have a column of type TIMESTAMP for the multi-axis chart to work`);
} }
return (await runSqlQuery(query)).toObjectArray(); return (await runSqlQuery(query, cancelToken)).toObjectArray();
}, },
}); });
@ -327,6 +328,9 @@ ModuleRepository.registerModule<MultiAxisChartParameterValues>({
}} }}
/> />
{errorMessage && <Issue issue={errorMessage} />} {errorMessage && <Issue issue={errorMessage} />}
{sourceDataState.loading && (
<Loader cancelText="Cancel query" onCancel={() => queryManager.cancelCurrent()} />
)}
</div> </div>
); );
}, },

View File

@ -21,6 +21,7 @@ import type { ECharts } from 'echarts';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { Loader } from '../../../components';
import { useQueryManager } from '../../../hooks'; import { useQueryManager } from '../../../hooks';
import { formatEmpty, formatNumber } from '../../../utils'; import { formatEmpty, formatNumber } from '../../../utils';
import { Issue } from '../components'; import { Issue } from '../components';
@ -113,10 +114,10 @@ ModuleRepository.registerModule<PieChartParameterValues>({
}; };
}, [querySource, where, splitColumn, measure, limit, showOthers]); }, [querySource, where, splitColumn, measure, limit, showOthers]);
const [sourceDataState] = useQueryManager({ const [sourceDataState, queryManager] = useQueryManager({
query: dataQueries, query: dataQueries,
processQuery: async ({ mainQuery, splitExpression, othersPartialQuery }) => { processQuery: async ({ mainQuery, splitExpression, othersPartialQuery }, cancelToken) => {
const result = await runSqlQuery(mainQuery); const result = await runSqlQuery(mainQuery, cancelToken);
const data = result.toObjectArray(); const data = result.toObjectArray();
if (splitExpression && othersPartialQuery) { if (splitExpression && othersPartialQuery) {
@ -251,6 +252,9 @@ ModuleRepository.registerModule<PieChartParameterValues>({
}} }}
/> />
{errorMessage && <Issue issue={errorMessage} />} {errorMessage && <Issue issue={errorMessage} />}
{sourceDataState.loading && (
<Loader cancelText="Cancel query" onCancel={() => queryManager.cancelCurrent()} />
)}
</div> </div>
); );
}, },

View File

@ -79,7 +79,7 @@ ModuleRepository.registerModule<RecordTableParameterValues>({
.toString(); .toString();
}, [querySource, where, parameterValues]); }, [querySource, where, parameterValues]);
const [resultState] = useQueryManager({ const [resultState, queryManager] = useQueryManager({
query: query, query: query,
processQuery: runSqlQuery, processQuery: runSqlQuery,
}); });
@ -110,7 +110,9 @@ ModuleRepository.registerModule<RecordTableParameterValues>({
initPageSize={calculateInitPageSize(stage.height)} initPageSize={calculateInitPageSize(stage.height)}
/> />
) : undefined} ) : undefined}
{resultState.loading && <Loader />} {resultState.loading && (
<Loader cancelText="Cancel query" onCancel={() => queryManager.cancelCurrent()} />
)}
</div> </div>
); );
}, },

View File

@ -21,6 +21,7 @@ import type { ECharts } from 'echarts';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { Loader } from '../../../components';
import { useQueryManager } from '../../../hooks'; import { useQueryManager } from '../../../hooks';
import { import {
formatInteger, formatInteger,
@ -141,9 +142,12 @@ ModuleRepository.registerModule<TimeChartParameterValues>({
}; };
}, [querySource, where, measure, splitColumn, numberToStack, showOthers]); }, [querySource, where, measure, splitColumn, numberToStack, showOthers]);
const [sourceDataState] = useQueryManager({ const [sourceDataState, queryManager] = useQueryManager({
query: dataQuery, query: dataQuery,
processQuery: async ({ baseQuery, measure, splitExpression, numberToStack, showOthers }) => { processQuery: async (
{ baseQuery, measure, splitExpression, numberToStack, showOthers },
cancelToken,
) => {
if (!timeColumnName) { if (!timeColumnName) {
throw new Error(`Must have a column of type TIMESTAMP for the time chart to work`); throw new Error(`Must have a column of type TIMESTAMP for the time chart to work`);
} }
@ -155,10 +159,13 @@ ModuleRepository.registerModule<TimeChartParameterValues>({
.addSelect(splitExpression.as('v'), { addToGroupBy: 'end' }) .addSelect(splitExpression.as('v'), { addToGroupBy: 'end' })
.changeOrderByExpression(measure.expression.toOrderByExpression('DESC')) .changeOrderByExpression(measure.expression.toOrderByExpression('DESC'))
.changeLimitValue(numberToStack), .changeLimitValue(numberToStack),
cancelToken,
) )
).getColumnByIndex(0)! ).getColumnByIndex(0)!
: undefined; : undefined;
cancelToken.throwIfRequested();
const dataset = ( const dataset = (
await runSqlQuery( await runSqlQuery(
baseQuery baseQuery
@ -181,6 +188,7 @@ ModuleRepository.registerModule<TimeChartParameterValues>({
); );
}) })
.addSelect(measure.expression.as(METRIC_NAME)), .addSelect(measure.expression.as(METRIC_NAME)),
cancelToken,
) )
).toObjectArray(); ).toObjectArray();
@ -430,6 +438,9 @@ ModuleRepository.registerModule<TimeChartParameterValues>({
}} }}
/> />
{errorMessage && <Issue issue={errorMessage} />} {errorMessage && <Issue issue={errorMessage} />}
{sourceDataState.loading && (
<Loader cancelText="Cancel query" onCancel={() => queryManager.cancelCurrent()} />
)}
</div> </div>
); );
}, },

View File

@ -55,7 +55,7 @@ export function rewriteAggregate(query: SqlQuery, measures: Measure[]): SqlQuery
filterMap(queryMeasures, queryMeasure => filterMap(queryMeasures, queryMeasure =>
usedMeasures.get(queryMeasure.name) ? queryMeasure.expression : undefined, usedMeasures.get(queryMeasure.name) ? queryMeasure.expression : undefined,
).flatMap(ex => ex.getUsedColumnNames()), ).flatMap(ex => ex.getUsedColumnNames()),
).filter(columnName => !ex.getSelectIndexForOutputColumn(columnName)), ).filter(columnName => ex.getSelectIndexForOutputColumn(columnName) === -1),
(q, columnName) => q.addSelect(C(columnName)), (q, columnName) => q.addSelect(C(columnName)),
); );
} }

View File

@ -32,8 +32,12 @@ function friendlyErrorFormatter(e) {
module.exports = env => { module.exports = env => {
let druidUrl = (env || {}).druid_host || process.env.druid_host || 'localhost'; let druidUrl = (env || {}).druid_host || process.env.druid_host || 'localhost';
if (!druidUrl.startsWith('http')) druidUrl = 'http://' + druidUrl; if (!druidUrl.startsWith('http')) {
if (!/:\d+$/.test(druidUrl)) druidUrl += ':8888'; druidUrl = (druidUrl.endsWith(':9088') ? 'https://' : 'http://') + druidUrl;
}
if (!/:\d+$/.test(druidUrl)) {
druidUrl += druidUrl.startsWith('https://') ? ':9088' : ':8888';
}
const proxyTarget = { const proxyTarget = {
target: druidUrl, target: druidUrl,