blob: c4723c8f1e0627adce6d542cd31504ca698b7801 [file] [log] [blame]
/* eslint-disable camelcase */
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* This file exports all controls available for use in chart plugins internal to Superset.
* It is not recommended to use the controls here for any third-party plugins.
*
* While the React components located in `controls/components` represent different
* types of controls (CheckboxControl, SelectControl, TextControl, ...), the controls here
* represent instances of control types, that can be reused across visualization types.
*
* When controls are reused across viz types, their values are carried over as a user
* changes the chart types.
*
* While the keys defined in the control itself get passed to the controlType as props,
* here's a list of the keys that are common to all controls, and as a result define the
* control interface.
*/
import React from 'react';
import {
t,
getCategoricalSchemeRegistry,
getSequentialSchemeRegistry,
SequentialScheme,
legacyValidateInteger,
validateNonEmpty,
smartDateFormatter,
} from '@superset-ui/core';
import { mainMetric, formatSelectOptions } from '../utils';
import { TIME_FILTER_LABELS } from '../constants';
import {
Metric,
SharedControlConfig,
ColumnMeta,
ExtraControlProps,
SelectControlConfig,
} from '../types';
import { ColumnOption } from '../components/ColumnOption';
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
export const PRIMARY_COLOR = { r: 0, g: 122, b: 135, a: 1 };
// input choices & options
export const D3_FORMAT_OPTIONS = [
['SMART_NUMBER', 'Adaptative formating'],
['~g', 'Original value'],
[',d', ',d (12345.432 => 12,345)'],
['.1s', '.1s (12345.432 => 10k)'],
['.3s', '.3s (12345.432 => 12.3k)'],
[',.1%', ',.1% (12345.432 => 1,234,543.2%)'],
['.3%', '.3% (12345.432 => 1234543.200%)'],
['.4r', '.4r (12345.432 => 12350)'],
[',.3f', ',.3f (12345.432 => 12,345.432)'],
['+,', '+, (12345.432 => +12,345.432)'],
['$,.2f', '$,.2f (12345.432 => $12,345.43)'],
['DURATION', 'Duration in ms (66000 => 1m 6s)'],
['DURATION_SUB', 'Duration in ms (100.40008 => 100ms 400µs 80ns)'],
];
const ROW_LIMIT_OPTIONS = [10, 50, 100, 250, 500, 1000, 5000, 10000, 50000];
const SERIES_LIMITS = [0, 5, 10, 25, 50, 100, 500];
export const D3_FORMAT_DOCS = t('D3 format syntax: https://github.com/d3/d3-format');
export const D3_TIME_FORMAT_OPTIONS = [
['smart_date', t('Adaptative formating')],
['%d/%m/%Y', '%d/%m/%Y | 14/01/2019'],
['%m/%d/%Y', '%m/%d/%Y | 01/14/2019'],
['%Y-%m-%d', '%Y-%m-%d | 2019-01-14'],
['%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S | 2019-01-14 01:32:10'],
['%d-%m-%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S | 14-01-2019 01:32:10'],
['%H:%M:%S', '%H:%M:%S | 01:32:10'],
];
export const D3_TIME_FORMAT_DOCS = t('D3 time format syntax: https://github.com/d3/d3-time-format');
const timeColumnOption = {
verbose_name: t('Time'),
column_name: '__timestamp',
description: t('A reference to the [Time] configuration, taking granularity into account'),
};
type Control = {
savedMetrics?: Metric[] | null;
default?: unknown;
};
const groupByControl: SharedControlConfig<'SelectControl', ColumnMeta> = {
type: 'SelectControl',
label: t('Group by'),
multi: true,
freeForm: true,
clearable: true,
default: [],
includeTime: false,
description: t('One or many columns to group by'),
optionRenderer: c => <ColumnOption showType column={c} />,
valueRenderer: c => <ColumnOption column={c} />,
valueKey: 'column_name',
allowAll: true,
filterOption: ({ data: opt }, text: string) =>
(opt.column_name && opt.column_name.toLowerCase().includes(text.toLowerCase())) ||
(opt.verbose_name && opt.verbose_name.toLowerCase().includes(text.toLowerCase())) ||
false,
promptTextCreator: (label: unknown) => label,
mapStateToProps(state, { includeTime }) {
const newState: ExtraControlProps = {};
if (state.datasource) {
const options = state.datasource.columns.filter(c => c.groupby);
if (includeTime) {
options.unshift(timeColumnOption);
}
newState.options = options;
}
return newState;
},
commaChoosesOption: false,
};
const metrics: SharedControlConfig<'MetricsControl'> = {
type: 'MetricsControl',
multi: true,
label: t('Metrics'),
validators: [validateNonEmpty],
default: (c: Control) => {
const metric = mainMetric(c.savedMetrics);
return metric ? [metric] : null;
},
mapStateToProps: ({ datasource }) => ({
columns: datasource ? datasource.columns : [],
savedMetrics: datasource ? datasource.metrics : [],
datasourceType: datasource?.type,
}),
description: t('One or many metrics to display'),
};
const metric: SharedControlConfig<'MetricsControl'> = {
...metrics,
multi: false,
label: t('Metric'),
description: t('Metric'),
default: (c: Control) => mainMetric(c.savedMetrics),
};
const datasourceControl: SharedControlConfig<'DatasourceControl'> = {
type: 'DatasourceControl',
label: t('Datasource'),
default: null,
description: null,
mapStateToProps: ({ datasource }) => ({
datasource,
}),
};
const viz_type: SharedControlConfig<'VizTypeControl'> = {
type: 'VizTypeControl',
label: t('Visualization Type'),
default: 'table',
description: t('The type of visualization to display'),
};
const color_picker: SharedControlConfig<'ColorPickerControl'> = {
type: 'ColorPickerControl',
label: t('Fixed Color'),
description: t('Use this to define a static color for all circles'),
default: PRIMARY_COLOR,
renderTrigger: true,
};
const metric_2: SharedControlConfig<'MetricsControl'> = {
...metric,
label: t('Right Axis Metric'),
clearable: true,
description: t('Choose a metric for right axis'),
};
const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = {
type: 'ColorSchemeControl',
label: t('Linear Color Scheme'),
choices: () =>
(sequentialSchemeRegistry.values() as SequentialScheme[]).map(value => [value.id, value.label]),
default: sequentialSchemeRegistry.getDefaultKey(),
clearable: false,
description: '',
renderTrigger: true,
schemes: () => sequentialSchemeRegistry.getMap(),
isLinear: true,
};
const secondary_metric: SharedControlConfig<'MetricsControl'> = {
...metric,
label: t('Color Metric'),
default: null,
validators: [],
description: t('A metric to use for color'),
};
const columnsControl: typeof groupByControl = {
...groupByControl,
label: t('Columns'),
description: t('One or many columns to pivot as columns'),
};
const druid_time_origin: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
freeForm: true,
label: TIME_FILTER_LABELS.druid_time_origin,
choices: [
['', 'default'],
['now', 'now'],
],
default: null,
description: t(
'Defines the origin where time buckets start, ' +
'accepts natural dates as in `now`, `sunday` or `1970-01-01`',
),
};
const granularity: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
freeForm: true,
label: TIME_FILTER_LABELS.granularity,
default: 'one day',
choices: [
[null, 'all'],
['PT5S', '5 seconds'],
['PT30S', '30 seconds'],
['PT1M', '1 minute'],
['PT5M', '5 minutes'],
['PT30M', '30 minutes'],
['PT1H', '1 hour'],
['PT6H', '6 hour'],
['P1D', '1 day'],
['P7D', '7 days'],
['P1W', 'week'],
['week_starting_sunday', 'week starting Sunday'],
['week_ending_saturday', 'week ending Saturday'],
['P1M', 'month'],
['P3M', 'quarter'],
['P1Y', 'year'],
],
description: t(
'The time granularity for the visualization. Note that you ' +
'can type and use simple natural language as in `10 seconds`, ' +
'`1 day` or `56 weeks`',
),
};
const granularity_sqla: SharedControlConfig<'SelectControl', ColumnMeta> = {
type: 'SelectControl',
label: TIME_FILTER_LABELS.granularity_sqla,
description: t(
'The time column for the visualization. Note that you ' +
'can define arbitrary expression that return a DATETIME ' +
'column in the table. Also note that the ' +
'filter below is applied against this column or ' +
'expression',
),
default: (c: Control) => c.default,
clearable: false,
optionRenderer: c => <ColumnOption showType column={c} />,
valueRenderer: c => <ColumnOption column={c} />,
valueKey: 'column_name',
mapStateToProps: state => {
const props: Partial<SelectControlConfig<ColumnMeta>> = {};
if (state.datasource) {
props.options = state.datasource.columns.filter(c => c.is_dttm);
props.default = null;
if (state.datasource.main_dttm_col) {
props.default = state.datasource.main_dttm_col;
} else if (props.options && props.options.length > 0) {
props.default = props.options[0].column_name;
}
}
return props;
},
};
const time_grain_sqla: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
label: TIME_FILTER_LABELS.time_grain_sqla,
default: 'P1D',
description: t(
'The time granularity for the visualization. This ' +
'applies a date transformation to alter ' +
'your time column and defines a new time granularity. ' +
'The options here are defined on a per database ' +
'engine basis in the Superset source code.',
),
mapStateToProps: ({ datasource }) => ({
choices: datasource?.time_grain_sqla || null,
}),
};
const time_range: SharedControlConfig<'DateFilterControl'> = {
type: 'DateFilterControl',
freeForm: true,
label: TIME_FILTER_LABELS.time_range,
default: t('Last week'), // this value is translated, but the backend wouldn't understand a translated value?
description: t(
'The time range for the visualization. All relative times, e.g. "Last month", ' +
'"Last 7 days", "now", etc. are evaluated on the server using the server\'s ' +
'local time (sans timezone). All tooltips and placeholder times are expressed ' +
'in UTC (sans timezone). The timestamps are then evaluated by the database ' +
"using the engine's local timezone. Note one can explicitly set the timezone " +
'per the ISO 8601 format if specifying either the start and/or end time.',
),
mapStateToProps: ({ form_data }) => ({
endpoints: form_data?.time_range_endpoints || null,
}),
};
const row_limit: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
freeForm: true,
label: t('Row limit'),
validators: [legacyValidateInteger],
default: 10000,
choices: formatSelectOptions(ROW_LIMIT_OPTIONS),
};
const limit: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
freeForm: true,
label: t('Series limit'),
validators: [legacyValidateInteger],
choices: formatSelectOptions(SERIES_LIMITS),
description: t(
'Limits the number of time series that get displayed. A sub query ' +
'(or an extra phase where sub queries are not supported) is applied to limit ' +
'the number of time series that get fetched and displayed. This feature is useful ' +
'when grouping by high cardinality dimension(s).',
),
};
const timeseries_limit_metric: SharedControlConfig<'MetricsControl'> = {
type: 'MetricsControl',
label: t('Sort By'),
default: null,
description: t('Metric used to define the top series'),
mapStateToProps: ({ datasource }) => ({
columns: datasource?.columns || [],
savedMetrics: datasource?.metrics || [],
datasourceType: datasource?.type,
}),
};
const series: typeof groupByControl = {
...groupByControl,
label: t('Series'),
multi: false,
default: null,
description: t(
'Defines the grouping of entities. ' +
'Each series is shown as a specific color on the chart and ' +
'has a legend toggle',
),
};
const entity: typeof groupByControl = {
...groupByControl,
label: t('Entity'),
default: null,
multi: false,
validators: [validateNonEmpty],
description: t('This defines the element to be plotted on the chart'),
};
const x: SharedControlConfig<'MetricsControl'> = {
...metric,
label: t('X Axis'),
description: t('Metric assigned to the [X] axis'),
default: null,
};
const y: SharedControlConfig<'MetricsControl'> = {
...metric,
label: t('Y Axis'),
default: null,
description: t('Metric assigned to the [Y] axis'),
};
const size: SharedControlConfig<'MetricsControl'> = {
...metric,
label: t('Bubble Size'),
default: null,
};
const y_axis_format: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
freeForm: true,
label: t('Y Axis Format'),
renderTrigger: true,
default: 'SMART_NUMBER',
choices: D3_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
mapStateToProps: state => {
const showWarning = state.controls?.comparison_type?.value === 'percentage';
return {
warning: showWarning
? t(
'When `Calculation type` is set to "Percentage change", the Y ' +
'Axis Format is forced to `.1%`',
)
: null,
disabled: showWarning,
};
},
};
const x_axis_time_format: SharedControlConfig<'SelectControl'> = {
type: 'SelectControl',
freeForm: true,
label: t('Time format'),
renderTrigger: true,
default: smartDateFormatter.id,
choices: D3_TIME_FORMAT_OPTIONS,
description: D3_TIME_FORMAT_DOCS,
};
const adhoc_filters: SharedControlConfig<'AdhocFilterControl'> = {
type: 'AdhocFilterControl',
label: t('Filters'),
default: null,
description: '',
mapStateToProps: ({ datasource }) => ({
columns: datasource?.columns.filter(c => c.filterable) || [],
savedMetrics: datasource?.metrics || [],
datasource,
}),
provideFormDataToProps: true,
};
const color_scheme: SharedControlConfig<'ColorSchemeControl'> = {
type: 'ColorSchemeControl',
label: t('Color Scheme'),
default: categoricalSchemeRegistry.getDefaultKey(),
renderTrigger: true,
choices: () => categoricalSchemeRegistry.keys().map(s => [s, s]),
description: t('The color scheme for rendering chart'),
schemes: () => categoricalSchemeRegistry.getMap(),
};
const label_colors: SharedControlConfig<'ColorMapControl'> = {
type: 'ColorMapControl',
label: t('Color Map'),
default: {},
renderTrigger: true,
mapStateToProps: ({
form_data: { color_namespace: colorNamespace, color_scheme: colorScheme },
}) => ({
colorNamespace,
colorScheme,
}),
};
const sharedControls = {
metrics,
metric,
datasource: datasourceControl,
viz_type,
color_picker,
metric_2,
linear_color_scheme,
secondary_metric,
groupby: groupByControl,
columns: columnsControl,
druid_time_origin,
granularity,
granularity_sqla,
time_grain_sqla,
time_range,
row_limit,
limit,
timeseries_limit_metric,
series,
entity,
x,
y,
size,
y_axis_format,
x_axis_time_format,
adhoc_filters,
color_scheme,
label_colors,
};
export default sharedControls;