Skip to content

Commit

Permalink
feat(core): add support for non-temporal series limit (apache#1356)
Browse files Browse the repository at this point in the history
* feat(plugin-chart-echarts): add support for non-temporal series limit

* Update plugins/plugin-chart-echarts/src/BoxPlot/controlPanel.ts

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* make whisker type unclearable and also handle null case

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
  • Loading branch information
2 people authored and zhaoyongjie committed Nov 24, 2021
1 parent 34cc86a commit cd603e0
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,20 @@ const limit: SharedControlConfig<'SelectControl'> = {
),
};

const series_limit: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
freeForm: true,
label: t('Series limit'),
validators: [legacyValidateInteger],
choices: formatSelectOptions(SERIES_LIMITS),
description: t(
'Limits the number of series that get displayed. A sub query ' +
'(or an extra phase where sub queries are not supported) is applied to limit ' +
'the number of series that get fetched and displayed. This feature is useful ' +
'when grouping by high cardinality dimension(s).',
),
};

const sort_by: SharedControlConfig<'MetricsControl'> = {
type: 'MetricsControl',
label: t('Sort By'),
Expand Down Expand Up @@ -495,6 +509,9 @@ const sharedControls = {
adhoc_filters: enableExploreDnd ? dnd_adhoc_filters : adhoc_filters,
color_scheme,
label_colors,
series_columns: enableExploreDnd ? dndColumnsControl : columnsControl,
series_limit,
series_limit_metric: enableExploreDnd ? dnd_sort_by : sort_by,
};

export default sharedControls;
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export default function buildQueryObject<T extends QueryFormData>(
granularity,
url_params = {},
custom_params = {},
series_columns,
series_limit,
series_limit_metric,
...residualFormData
} = formData;
const {
Expand Down Expand Up @@ -76,6 +79,9 @@ export default function buildQueryObject<T extends QueryFormData>(
annotation_layers,
row_limit: row_limit == null || Number.isNaN(numericRowLimit) ? undefined : numericRowLimit,
row_offset: row_offset == null || Number.isNaN(numericRowOffset) ? undefined : numericRowOffset,
series_columns,
series_limit,
series_limit_metric,
timeseries_limit: limit ? Number(limit) : 0,
timeseries_limit_metric: timeseries_limit_metric || undefined,
order_desc: typeof order_desc === 'undefined' ? true : order_desc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { DatasourceType } from './Datasource';
import { BinaryOperator, SetOperator, UnaryOperator } from './Operator';
import { AppliedTimeExtras, TimeRange, TimeRangeEndpoints } from './Time';
import { AnnotationLayer } from './AnnotationLayer';
import { QueryFields, QueryFormMetric } from './QueryFormData';
import { QueryFields, QueryFormColumn, QueryFormMetric } from './QueryFormData';
import { Maybe } from '../../types';
import { PostProcessingRule } from './PostProcessing';
import { JsonObject } from '../../connection';
Expand Down Expand Up @@ -121,7 +121,7 @@ export interface QueryObject extends QueryFields, TimeRange, ResidualQueryObject
/** The size of bucket by which to group timeseries data (forthcoming) */
time_grain?: string;

/** Maximum number of series */
/** Maximum number of timeseries */
timeseries_limit?: number;

/** The metric used to sort the returned result. */
Expand All @@ -136,6 +136,11 @@ export interface QueryObject extends QueryFields, TimeRange, ResidualQueryObject

/** Free-form WHERE SQL: multiple clauses are concatenated by AND */
where?: string;

/** Limit number of series */
series_columns?: QueryFormColumn[];
series_limit?: number;
series_limit_metric?: Maybe<QueryFormMetric>;
}

export interface QueryContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ export interface BaseFormData extends TimeRange, FormDataResidual {
annotation_layers?: AnnotationLayer[];
url_params?: Record<string, string>;
custom_params?: Record<string, string>;
/** limit number of series */
series_columns?: QueryFormColumn[];
series_limit?: number;
series_limit_metric?: QueryFormColumn;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import { BoxPlotQueryFormData, BoxPlotQueryObjectWhiskerType } from './types';
const PERCENTILE_REGEX = /(\d+)\/(\d+) percentiles/;

export default function buildQuery(formData: BoxPlotQueryFormData) {
const { whiskerOptions, columns: distributionColumns = [] } = formData;
const { columns = [], granularity_sqla, groupby = [], whiskerOptions } = formData;
return buildQueryContext(formData, baseQueryObject => {
let whiskerType: BoxPlotQueryObjectWhiskerType;
let percentiles: [number, number] | undefined;
const { columns = [], metrics = [] } = baseQueryObject;
const { metrics = [] } = baseQueryObject;
const percentileMatch = PERCENTILE_REGEX.exec(whiskerOptions as string);

if (whiskerOptions === 'Tukey') {
if (whiskerOptions === 'Tukey' || !whiskerOptions) {
whiskerType = 'tukey';
} else if (whiskerOptions === 'Min/max (no outliers)') {
whiskerType = 'min/max';
Expand All @@ -39,17 +39,25 @@ export default function buildQuery(formData: BoxPlotQueryFormData) {
} else {
throw new Error(`Unsupported whisker type: ${whiskerOptions}`);
}
const distributionColumns: string[] = [];

// For now default to using the temporal column as distribution column.
// In the future this control should be made mandatory.
if (!columns.length && granularity_sqla) {
distributionColumns.push(granularity_sqla);
}
return [
{
...baseQueryObject,
is_timeseries: distributionColumns.length === 0,
columns: [...distributionColumns, ...columns, ...groupby],
series_columns: groupby,
post_processing: [
{
operation: 'boxplot',
options: {
whisker_type: whiskerType,
percentiles,
groupby: columns.filter(x => !distributionColumns.includes(x)),
groupby,
metrics: metrics.map(getMetricLabel),
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ const config: ControlPanelConfig = {
['adhoc_filters'],
emitFilterControl,
['groupby'],
['columns'],
['limit'],
['columns'], // TODO: this should be migrated to `series_columns`
['series_limit'],
['series_limit_metric'],
[
{
name: 'whiskerOptions',
config: {
clearable: false,
type: 'SelectControl',
freeForm: true,
label: t('Whisker/outlier options'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,31 @@ import { BoxPlotQueryFormData } from '../../src/BoxPlot/types';

describe('BoxPlot buildQuery', () => {
const formData: BoxPlotQueryFormData = {
emitFilter: false,
columns: [],
datasource: '5__table',
granularity_sqla: 'ds',
time_grain_sqla: 'P1Y',
columns: [],
metrics: ['foo'],
groupby: ['bar'],
metrics: ['foo'],
time_grain_sqla: 'P1Y',
viz_type: 'my_chart',
whiskerOptions: 'Tukey',
yAxisFormat: 'SMART_NUMBER',
viz_type: 'my_chart',
};

it('should build timeseries when columns is empty', () => {
it('should build timeseries when series columns is empty', () => {
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.is_timeseries).toEqual(true);
expect(query.metrics).toEqual(['foo']);
expect(query.columns).toEqual(['bar']);
expect(query.columns).toEqual(['ds', 'bar']);
expect(query.series_columns).toEqual(['bar']);
});

it('should build non-timeseries query object when columns is defined', () => {
const queryContext = buildQuery({ ...formData, columns: ['qwerty'] });
const [query] = queryContext.queries;
expect(query.is_timeseries).toEqual(false);
expect(query.metrics).toEqual(['foo']);
expect(query.columns).toEqual(['qwerty', 'bar']);
expect(query.series_columns).toEqual(['bar']);
});
});

0 comments on commit cd603e0

Please sign in to comment.