mirror of https://github.com/apache/superset.git
refactor: preparation for time section migration (#21766)
This commit is contained in:
parent
bd3166b603
commit
8f61e3c5d9
|
@ -37,4 +37,4 @@ export { legacySortBy } from './shared-controls/legacySortBy';
|
|||
export * from './shared-controls/emitFilterControl';
|
||||
export * from './shared-controls/components';
|
||||
export * from './types';
|
||||
export { xAxisMixin, temporalColumnMixin } from './shared-controls/mixins';
|
||||
export * from './shared-controls/mixins';
|
||||
|
|
|
@ -16,12 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
ContributionType,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { ContributionType, hasGenericChartAxes, t } from '@superset-ui/core';
|
||||
import { ControlPanelSectionConfig } from '../types';
|
||||
import { emitFilterControl } from '../shared-controls/emitFilterControl';
|
||||
|
||||
|
@ -29,12 +24,8 @@ export const echartsTimeSeriesQuery: ControlPanelSectionConfig = {
|
|||
label: t('Query'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? 'x_axis' : null],
|
||||
[
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
? 'time_grain_sqla'
|
||||
: null,
|
||||
],
|
||||
[hasGenericChartAxes ? 'x_axis' : null],
|
||||
[hasGenericChartAxes ? 'time_grain_sqla' : null],
|
||||
['metrics'],
|
||||
['groupby'],
|
||||
[
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
|
||||
import { hasGenericChartAxes, t } from '@superset-ui/core';
|
||||
import { ControlPanelSectionConfig } from '../types';
|
||||
|
||||
// A few standard controls sections that are used internally.
|
||||
|
@ -42,11 +42,7 @@ export const genericTime: ControlPanelSectionConfig = {
|
|||
...baseTimeSection,
|
||||
controlSetRows: [
|
||||
['granularity_sqla'],
|
||||
[
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
? null
|
||||
: 'time_grain_sqla',
|
||||
],
|
||||
[hasGenericChartAxes ? null : 'time_grain_sqla'],
|
||||
['time_range'],
|
||||
],
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
SharedControlConfig,
|
||||
Dataset,
|
||||
Metric,
|
||||
isDataset,
|
||||
} from '../types';
|
||||
import { DATASET_TIME_COLUMN_OPTION, TIME_FILTER_LABELS } from '../constants';
|
||||
import {
|
||||
|
@ -138,8 +139,8 @@ export const dndAdhocFilterControl: SharedControlConfig<
|
|||
default: [],
|
||||
description: '',
|
||||
mapStateToProps: ({ datasource, form_data }) => ({
|
||||
columns: datasource?.columns[0]?.hasOwnProperty('filterable')
|
||||
? (datasource as Dataset)?.columns.filter(c => c.filterable)
|
||||
columns: isDataset(datasource)
|
||||
? datasource.columns.filter(c => c.filterable)
|
||||
: datasource?.columns || [],
|
||||
savedMetrics: defineSavedMetrics(datasource),
|
||||
// current active adhoc metrics
|
||||
|
|
|
@ -35,11 +35,9 @@
|
|||
*/
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
FeatureFlag,
|
||||
t,
|
||||
getCategoricalSchemeRegistry,
|
||||
getSequentialSchemeRegistry,
|
||||
isFeatureEnabled,
|
||||
SequentialScheme,
|
||||
legacyValidateInteger,
|
||||
ComparisionType,
|
||||
|
@ -47,6 +45,8 @@ import {
|
|||
isPhysicalColumn,
|
||||
ensureIsArray,
|
||||
isDefined,
|
||||
hasGenericChartAxes,
|
||||
NO_TIME_RANGE,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
import {
|
||||
|
@ -205,7 +205,7 @@ const time_grain_sqla: SharedControlConfig<'SelectControl'> = {
|
|||
choices: (datasource as Dataset)?.time_grain_sqla || [],
|
||||
}),
|
||||
visibility: ({ controls }) => {
|
||||
if (!isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)) {
|
||||
if (!hasGenericChartAxes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ const time_range: SharedControlConfig<'DateFilterControl'> = {
|
|||
type: 'DateFilterControl',
|
||||
freeForm: true,
|
||||
label: TIME_FILTER_LABELS.time_range,
|
||||
default: t('No filter'), // this value is translated, but the backend wouldn't understand a translated value?
|
||||
default: NO_TIME_RANGE, // this value is an empty filter constant so shouldn't translate it.
|
||||
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 ' +
|
||||
|
@ -236,9 +236,6 @@ const time_range: SharedControlConfig<'DateFilterControl'> = {
|
|||
"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: ({ datasource }) => ({
|
||||
datasource,
|
||||
}),
|
||||
};
|
||||
|
||||
const row_limit: SharedControlConfig<'SelectControl'> = {
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import {
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
QueryFormData,
|
||||
t,
|
||||
validateNonEmpty,
|
||||
|
@ -41,7 +40,7 @@ export const xAxisMixin = {
|
|||
validators: [validateNonEmpty],
|
||||
initialValue: (control: ControlState, state: ControlPanelState | null) => {
|
||||
if (
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) &&
|
||||
hasGenericChartAxes &&
|
||||
state?.form_data?.granularity_sqla &&
|
||||
!state.form_data?.x_axis &&
|
||||
!control?.value
|
||||
|
|
|
@ -23,7 +23,7 @@ export const TestDataset: Dataset = {
|
|||
column_format: {},
|
||||
columns: [
|
||||
{
|
||||
advanced_data_type: null,
|
||||
advanced_data_type: undefined,
|
||||
certification_details: null,
|
||||
certified_by: null,
|
||||
column_name: 'num',
|
||||
|
@ -41,7 +41,7 @@ export const TestDataset: Dataset = {
|
|||
warning_markdown: null,
|
||||
},
|
||||
{
|
||||
advanced_data_type: null,
|
||||
advanced_data_type: undefined,
|
||||
certification_details: null,
|
||||
certified_by: null,
|
||||
column_name: 'gender',
|
||||
|
@ -59,7 +59,7 @@ export const TestDataset: Dataset = {
|
|||
warning_markdown: null,
|
||||
},
|
||||
{
|
||||
advanced_data_type: null,
|
||||
advanced_data_type: undefined,
|
||||
certification_details: null,
|
||||
certified_by: null,
|
||||
column_name: 'state',
|
||||
|
@ -77,7 +77,7 @@ export const TestDataset: Dataset = {
|
|||
warning_markdown: null,
|
||||
},
|
||||
{
|
||||
advanced_data_type: null,
|
||||
advanced_data_type: undefined,
|
||||
certification_details: null,
|
||||
certified_by: null,
|
||||
column_name: 'ds',
|
||||
|
@ -95,7 +95,7 @@ export const TestDataset: Dataset = {
|
|||
warning_markdown: null,
|
||||
},
|
||||
{
|
||||
advanced_data_type: null,
|
||||
advanced_data_type: undefined,
|
||||
certification_details: null,
|
||||
certified_by: null,
|
||||
column_name: 'name',
|
||||
|
|
|
@ -24,7 +24,7 @@ test('get temporal columns from a Dataset', () => {
|
|||
expect(getTemporalColumns(TestDataset)).toEqual({
|
||||
temporalColumns: [
|
||||
{
|
||||
advanced_data_type: null,
|
||||
advanced_data_type: undefined,
|
||||
certification_details: null,
|
||||
certified_by: null,
|
||||
column_name: 'ds',
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
import {
|
||||
ExtraFormDataAppend,
|
||||
ExtraFormDataOverrideExtras,
|
||||
ExtraFormDataOverrideRegular,
|
||||
ExtraFormDataOverride,
|
||||
QueryObject,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
|
@ -24,7 +16,17 @@ import {
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
ExtraFormDataAppend,
|
||||
ExtraFormDataOverrideExtras,
|
||||
ExtraFormDataOverrideRegular,
|
||||
ExtraFormDataOverride,
|
||||
QueryObject,
|
||||
} from './types';
|
||||
|
||||
export const DTTM_ALIAS = '__timestamp';
|
||||
export const DEFAULT_TIME_RANGE = 'No filter'; // TODO: make this configurable per Superset installation
|
||||
export const NO_TIME_RANGE = 'No filter';
|
||||
|
||||
export const EXTRA_FORM_DATA_OVERRIDE_EXTRA_KEYS: (keyof ExtraFormDataOverrideExtras)[] =
|
||||
['relative_start', 'relative_end', 'time_grain_sqla'];
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
import {
|
||||
DTTM_ALIAS,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
getColumnLabel,
|
||||
isQueryFormColumn,
|
||||
QueryFormData,
|
||||
|
@ -26,6 +28,10 @@ import {
|
|||
export const isXAxisSet = (formData: QueryFormData) =>
|
||||
isQueryFormColumn(formData.x_axis);
|
||||
|
||||
export const hasGenericChartAxes = isFeatureEnabled(
|
||||
FeatureFlag.GENERIC_CHART_AXES,
|
||||
);
|
||||
|
||||
export const getXAxis = (formData: QueryFormData): string | undefined => {
|
||||
// The formData should be "raw form_data" -- the snake_case version of formData rather than camelCase.
|
||||
if (!(formData.granularity_sqla || formData.x_axis)) {
|
||||
|
|
|
@ -29,7 +29,7 @@ export { default as getMetricLabel } from './getMetricLabel';
|
|||
export { default as DatasourceKey } from './DatasourceKey';
|
||||
export { default as normalizeOrderBy } from './normalizeOrderBy';
|
||||
export { normalizeTimeColumn } from './normalizeTimeColumn';
|
||||
export { getXAxis, isXAxisSet } from './getXAxis';
|
||||
export { getXAxis, isXAxisSet, hasGenericChartAxes } from './getXAxis';
|
||||
|
||||
export * from './types/AnnotationLayer';
|
||||
export * from './types/QueryFormData';
|
||||
|
|
|
@ -52,6 +52,12 @@ export interface Column {
|
|||
expression?: string | null;
|
||||
database_expression?: string | null;
|
||||
python_date_format?: string | null;
|
||||
|
||||
// used for advanced_data_type
|
||||
optionName?: string;
|
||||
filterBy?: string;
|
||||
value?: string;
|
||||
advanced_data_type?: string;
|
||||
}
|
||||
|
||||
export function isPhysicalColumn(column?: any): column is PhysicalColumn {
|
||||
|
|
|
@ -16,12 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
smartDateFormatter,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { hasGenericChartAxes, smartDateFormatter, t } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
D3_FORMAT_DOCS,
|
||||
|
@ -41,12 +36,8 @@ const config: ControlPanelConfig = {
|
|||
label: t('Query'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? 'x_axis' : null],
|
||||
[
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
? 'time_grain_sqla'
|
||||
: null,
|
||||
],
|
||||
[hasGenericChartAxes ? 'x_axis' : null],
|
||||
[hasGenericChartAxes ? 'time_grain_sqla' : null],
|
||||
['metric'],
|
||||
['adhoc_filters'],
|
||||
],
|
||||
|
|
|
@ -17,12 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { ensureIsArray, hasGenericChartAxes, t } from '@superset-ui/core';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
|
@ -292,7 +287,7 @@ function createAdvancedAnalyticsSection(
|
|||
const config: ControlPanelConfig = {
|
||||
controlPanelSections: [
|
||||
sections.genericTime,
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
hasGenericChartAxes
|
||||
? {
|
||||
label: t('Shared query fields'),
|
||||
expanded: true,
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
Behavior,
|
||||
ChartMetadata,
|
||||
ChartPlugin,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import buildQuery from './buildQuery';
|
||||
|
@ -57,7 +56,7 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t(
|
||||
'Visualize two different series using the same x-axis. Note that both series can be visualized with a different chart type (e.g. 1 using bars and 1 using a line).',
|
||||
)
|
||||
|
@ -70,9 +69,7 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
? t('Mixed Chart')
|
||||
: t('Mixed Time-Series'),
|
||||
name: hasGenericChartAxes ? t('Mixed Chart') : t('Mixed Time-Series'),
|
||||
thumbnail,
|
||||
tags: [
|
||||
t('Advanced-Analytics'),
|
||||
|
|
|
@ -22,8 +22,7 @@ import {
|
|||
ChartPlugin,
|
||||
AnnotationType,
|
||||
Behavior,
|
||||
isFeatureEnabled,
|
||||
FeatureFlag,
|
||||
hasGenericChartAxes,
|
||||
} from '@superset-ui/core';
|
||||
import buildQuery from '../buildQuery';
|
||||
import controlPanel from './controlPanel';
|
||||
|
@ -54,7 +53,7 @@ export default class EchartsAreaChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t(
|
||||
'Area charts are similar to line charts in that they represent variables with the same scale, but area charts stack the metrics on top of each other.',
|
||||
)
|
||||
|
@ -68,7 +67,7 @@ export default class EchartsAreaChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
name: hasGenericChartAxes
|
||||
? t('Area Chart v2')
|
||||
: t('Time-series Area Chart'),
|
||||
tags: [
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
Behavior,
|
||||
ChartMetadata,
|
||||
ChartPlugin,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
|
@ -60,7 +59,7 @@ export default class EchartsTimeseriesBarChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t('Bar Charts are used to show metrics as a series of bars.')
|
||||
: t(
|
||||
'Time-series Bar Charts are used to show the changes in a metric over time as a series of bars.',
|
||||
|
@ -76,7 +75,7 @@ export default class EchartsTimeseriesBarChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
name: hasGenericChartAxes
|
||||
? t('Bar Chart v2')
|
||||
: t('Time-series Bar Chart v2'),
|
||||
tags: [
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
Behavior,
|
||||
ChartMetadata,
|
||||
ChartPlugin,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
|
@ -59,7 +58,7 @@ export default class EchartsTimeseriesLineChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t(
|
||||
'Line chart is used to visualize measurements taken over a given category. Line chart is a type of chart which displays information as a series of data points connected by straight line segments. It is a basic type of chart common in many fields.',
|
||||
)
|
||||
|
@ -73,7 +72,7 @@ export default class EchartsTimeseriesLineChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
name: hasGenericChartAxes
|
||||
? t('Line Chart v2')
|
||||
: t('Time-series Line Chart'),
|
||||
tags: [
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
Behavior,
|
||||
ChartMetadata,
|
||||
ChartPlugin,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
|
@ -58,7 +57,7 @@ export default class EchartsTimeseriesScatterChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t(
|
||||
'Scatter Plot has the horizontal axis in linear units, and the points are connected in order. It shows a statistical relationship between two variables.',
|
||||
)
|
||||
|
@ -72,7 +71,7 @@ export default class EchartsTimeseriesScatterChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
name: hasGenericChartAxes
|
||||
? t('Scatter Plot')
|
||||
: t('Time-series Scatter Plot'),
|
||||
tags: [
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
Behavior,
|
||||
ChartMetadata,
|
||||
ChartPlugin,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
|
@ -58,7 +57,7 @@ export default class EchartsTimeseriesSmoothLineChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t(
|
||||
'Smooth-line is a variation of the line chart. Without angles and hard edges, Smooth-line sometimes looks smarter and more professional.',
|
||||
)
|
||||
|
@ -72,7 +71,7 @@ export default class EchartsTimeseriesSmoothLineChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
name: hasGenericChartAxes
|
||||
? t('Smooth Line')
|
||||
: t('Time-series Smooth Line'),
|
||||
tags: [
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
Behavior,
|
||||
ChartMetadata,
|
||||
ChartPlugin,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
|
@ -49,7 +48,7 @@ export default class EchartsTimeseriesStepChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t(
|
||||
'Stepped-line graph (also called step chart) is a variation of line chart but with the line forming a series of steps between data points. A step chart can be useful when you want to show the changes that occur at irregular intervals.',
|
||||
)
|
||||
|
@ -63,7 +62,7 @@ export default class EchartsTimeseriesStepChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
name: hasGenericChartAxes
|
||||
? t('Stepped Line')
|
||||
: t('Time-series Stepped Line'),
|
||||
tags: [
|
||||
|
|
|
@ -21,8 +21,7 @@ import {
|
|||
Behavior,
|
||||
ChartMetadata,
|
||||
ChartPlugin,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import buildQuery from './buildQuery';
|
||||
|
@ -48,7 +47,7 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
|
|||
behaviors: [Behavior.INTERACTIVE_CHART],
|
||||
category: t('Evolution'),
|
||||
credits: ['https://echarts.apache.org'],
|
||||
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
description: hasGenericChartAxes
|
||||
? t(
|
||||
'Swiss army knife for visualizing data. Choose between step, line, scatter, and bar charts. This viz type has many customization options as well.',
|
||||
)
|
||||
|
@ -62,9 +61,7 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
|
|||
AnnotationType.Interval,
|
||||
AnnotationType.Timeseries,
|
||||
],
|
||||
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
? t('Generic Chart')
|
||||
: t('Time-series Chart'),
|
||||
name: hasGenericChartAxes ? t('Generic Chart') : t('Time-series Chart'),
|
||||
tags: [
|
||||
t('Advanced-Analytics'),
|
||||
t('Aesthetic'),
|
||||
|
|
|
@ -22,8 +22,7 @@ import {
|
|||
AdhocColumn,
|
||||
buildQueryContext,
|
||||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
isPhysicalColumn,
|
||||
QueryFormColumn,
|
||||
QueryFormOrderBy,
|
||||
|
@ -42,7 +41,7 @@ export default function buildQuery(formData: PivotTableQueryFormData) {
|
|||
if (
|
||||
isPhysicalColumn(col) &&
|
||||
formData.time_grain_sqla &&
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) &&
|
||||
hasGenericChartAxes &&
|
||||
formData?.datetime_columns_lookup?.[col]
|
||||
) {
|
||||
return {
|
||||
|
@ -66,7 +65,7 @@ export default function buildQuery(formData: PivotTableQueryFormData) {
|
|||
}
|
||||
return [
|
||||
{
|
||||
...(isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
...(hasGenericChartAxes
|
||||
? omit(baseQueryObject, ['extras.time_grain_sqla'])
|
||||
: baseQueryObject),
|
||||
orderby: orderBy,
|
||||
|
|
|
@ -19,9 +19,8 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
hasGenericChartAxes,
|
||||
isAdhocColumn,
|
||||
isFeatureEnabled,
|
||||
isPhysicalColumn,
|
||||
QueryFormMetric,
|
||||
smartDateFormatter,
|
||||
|
@ -68,7 +67,7 @@ const config: ControlPanelConfig = {
|
|||
},
|
||||
],
|
||||
[
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
hasGenericChartAxes
|
||||
? {
|
||||
name: 'time_grain_sqla',
|
||||
config: {
|
||||
|
@ -98,9 +97,7 @@ const config: ControlPanelConfig = {
|
|||
},
|
||||
}
|
||||
: null,
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
|
||||
? 'datetime_columns_lookup'
|
||||
: null,
|
||||
hasGenericChartAxes ? 'datetime_columns_lookup' : null,
|
||||
],
|
||||
[
|
||||
{
|
||||
|
|
|
@ -20,9 +20,8 @@ import {
|
|||
AdhocColumn,
|
||||
buildQueryContext,
|
||||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
getMetricLabel,
|
||||
isFeatureEnabled,
|
||||
hasGenericChartAxes,
|
||||
isPhysicalColumn,
|
||||
QueryMode,
|
||||
QueryObject,
|
||||
|
@ -104,7 +103,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
|||
if (
|
||||
isPhysicalColumn(col) &&
|
||||
formData.time_grain_sqla &&
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) &&
|
||||
hasGenericChartAxes &&
|
||||
formData?.datetime_columns_lookup?.[col]
|
||||
) {
|
||||
return {
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
GenericDataType,
|
||||
hasGenericChartAxes,
|
||||
isAdhocColumn,
|
||||
isFeatureEnabled,
|
||||
isPhysicalColumn,
|
||||
|
@ -189,7 +190,7 @@ const config: ControlPanelConfig = {
|
|||
},
|
||||
],
|
||||
[
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) && isAggMode
|
||||
hasGenericChartAxes && isAggMode
|
||||
? {
|
||||
name: 'time_grain_sqla',
|
||||
config: {
|
||||
|
@ -217,9 +218,7 @@ const config: ControlPanelConfig = {
|
|||
},
|
||||
}
|
||||
: null,
|
||||
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) && isAggMode
|
||||
? 'datetime_columns_lookup'
|
||||
: null,
|
||||
hasGenericChartAxes && isAggMode ? 'datetime_columns_lookup' : null,
|
||||
],
|
||||
[
|
||||
{
|
||||
|
|
|
@ -25,8 +25,9 @@ import {
|
|||
FilterState,
|
||||
isFeatureEnabled,
|
||||
NativeFilterType,
|
||||
NO_TIME_RANGE,
|
||||
} from '@superset-ui/core';
|
||||
import { NO_TIME_RANGE, TIME_FILTER_MAP } from 'src/explore/constants';
|
||||
import { TIME_FILTER_MAP } from 'src/explore/constants';
|
||||
import { getChartIdsInFilterBoxScope } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import { ChartConfiguration } from 'src/dashboard/reducers/types';
|
||||
import { Layout } from 'src/dashboard/types';
|
||||
|
|
|
@ -26,7 +26,7 @@ import { testWithId } from 'src/utils/testUtils';
|
|||
import { FeatureFlag } from 'src/featureFlags';
|
||||
import { Preset } from '@superset-ui/core';
|
||||
import { TimeFilterPlugin, SelectFilterPlugin } from 'src/filters/components';
|
||||
import { DATE_FILTER_CONTROL_TEST_ID } from 'src/explore/components/controls/DateFilterControl/DateFilterLabel';
|
||||
import { DATE_FILTER_CONTROL_TEST_ID } from 'src/explore/components/controls/DateFilterControl';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import FilterBar, { FILTER_BAR_TEST_ID } from '.';
|
||||
|
|
|
@ -17,17 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import rison from 'rison';
|
||||
import { css, SupersetClient, styled, t, useTheme } from '@superset-ui/core';
|
||||
import {
|
||||
buildTimeRangeString,
|
||||
formatTimeRange,
|
||||
COMMON_RANGE_VALUES_SET,
|
||||
CALENDAR_RANGE_VALUES_SET,
|
||||
FRAME_OPTIONS,
|
||||
customTimeRangeDecode,
|
||||
} from 'src/explore/components/controls/DateFilterControl/utils';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
css,
|
||||
styled,
|
||||
t,
|
||||
useTheme,
|
||||
DEFAULT_TIME_RANGE,
|
||||
NO_TIME_RANGE,
|
||||
} from '@superset-ui/core';
|
||||
import Button from 'src/components/Button';
|
||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||
import Label, { Type } from 'src/components/Label';
|
||||
|
@ -35,14 +32,18 @@ import { Divider } from 'src/components';
|
|||
import Icons from 'src/components/Icons';
|
||||
import Select from 'src/components/Select/Select';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import { DEFAULT_TIME_RANGE } from 'src/explore/constants';
|
||||
import { useDebouncedEffect } from 'src/explore/exploreUtils';
|
||||
import { SLOW_DEBOUNCE } from 'src/constants';
|
||||
import { testWithId } from 'src/utils/testUtils';
|
||||
import { noOp } from 'src/utils/common';
|
||||
import { FrameType } from './types';
|
||||
import ControlPopover from '../ControlPopover/ControlPopover';
|
||||
|
||||
import { DateFilterControlProps, FrameType } from './types';
|
||||
import {
|
||||
fetchTimeRange,
|
||||
FRAME_OPTIONS,
|
||||
getDateFilterControlTestId,
|
||||
guessFrame,
|
||||
} from './utils';
|
||||
import {
|
||||
CommonFrame,
|
||||
CalendarFrame,
|
||||
|
@ -50,42 +51,6 @@ import {
|
|||
AdvancedFrame,
|
||||
} from './components';
|
||||
|
||||
const guessFrame = (timeRange: string): FrameType => {
|
||||
if (COMMON_RANGE_VALUES_SET.has(timeRange)) {
|
||||
return 'Common';
|
||||
}
|
||||
if (CALENDAR_RANGE_VALUES_SET.has(timeRange)) {
|
||||
return 'Calendar';
|
||||
}
|
||||
if (timeRange === 'No filter') {
|
||||
return 'No filter';
|
||||
}
|
||||
if (customTimeRangeDecode(timeRange).matchedFlag) {
|
||||
return 'Custom';
|
||||
}
|
||||
return 'Advanced';
|
||||
};
|
||||
|
||||
const fetchTimeRange = async (timeRange: string) => {
|
||||
const query = rison.encode_uri(timeRange);
|
||||
const endpoint = `/api/v1/time_range/?q=${query}`;
|
||||
try {
|
||||
const response = await SupersetClient.get({ endpoint });
|
||||
const timeRangeString = buildTimeRangeString(
|
||||
response?.json?.result?.since || '',
|
||||
response?.json?.result?.until || '',
|
||||
);
|
||||
return {
|
||||
value: formatTimeRange(timeRangeString),
|
||||
};
|
||||
} catch (response) {
|
||||
const clientError = await getClientErrorObject(response);
|
||||
return {
|
||||
error: clientError.message || clientError.error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const StyledPopover = styled(ControlPopover)``;
|
||||
const StyledRangeType = styled(Select)`
|
||||
width: 272px;
|
||||
|
@ -161,20 +126,6 @@ const IconWrapper = styled.span`
|
|||
}
|
||||
`;
|
||||
|
||||
interface DateFilterControlProps {
|
||||
name: string;
|
||||
onChange: (timeRange: string) => void;
|
||||
value?: string;
|
||||
type?: Type;
|
||||
onOpenPopover?: () => void;
|
||||
onClosePopover?: () => void;
|
||||
}
|
||||
|
||||
export const DATE_FILTER_CONTROL_TEST_ID = 'date-filter-control';
|
||||
export const getDateFilterControlTestId = testWithId(
|
||||
DATE_FILTER_CONTROL_TEST_ID,
|
||||
);
|
||||
|
||||
export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
const {
|
||||
value = DEFAULT_TIME_RANGE,
|
||||
|
@ -195,6 +146,12 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
const [tooltipTitle, setTooltipTitle] = useState<string>(value);
|
||||
|
||||
useEffect(() => {
|
||||
if (value === NO_TIME_RANGE) {
|
||||
setActualTimeRange(NO_TIME_RANGE);
|
||||
setTooltipTitle(NO_TIME_RANGE);
|
||||
setValidTimeRange(true);
|
||||
return;
|
||||
}
|
||||
fetchTimeRange(value).then(({ value: actualRange, error }) => {
|
||||
if (error) {
|
||||
setEvalResponse(error || '');
|
||||
|
@ -235,6 +192,12 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
|
||||
useDebouncedEffect(
|
||||
() => {
|
||||
if (timeRangeValue === NO_TIME_RANGE) {
|
||||
setEvalResponse(NO_TIME_RANGE);
|
||||
setLastFetchedTimeRange(NO_TIME_RANGE);
|
||||
setValidTimeRange(true);
|
||||
return;
|
||||
}
|
||||
if (lastFetchedTimeRange !== timeRangeValue) {
|
||||
fetchTimeRange(timeRangeValue).then(({ value: actualRange, error }) => {
|
||||
if (error) {
|
||||
|
@ -279,11 +242,11 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
}
|
||||
};
|
||||
|
||||
function onChangeFrame(value: string) {
|
||||
if (value === 'No filter') {
|
||||
setTimeRangeValue('No filter');
|
||||
function onChangeFrame(value: FrameType) {
|
||||
if (value === NO_TIME_RANGE) {
|
||||
setTimeRangeValue(NO_TIME_RANGE);
|
||||
}
|
||||
setFrame(value as FrameType);
|
||||
setFrame(value);
|
||||
}
|
||||
|
||||
const theme = useTheme();
|
||||
|
@ -354,10 +317,6 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
</IconWrapper>
|
||||
);
|
||||
|
||||
const overlayStyle = {
|
||||
width: '600px',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ControlHeader {...props} />
|
||||
|
@ -369,7 +328,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
defaultVisible={show}
|
||||
visible={show}
|
||||
onVisibleChange={togglePopover}
|
||||
overlayStyle={overlayStyle}
|
||||
overlayStyle={{ width: '600px' }}
|
||||
>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
<Label className="pointer" data-test="time-range-trigger">
|
||||
|
|
|
@ -24,20 +24,20 @@ import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
|||
import { FrameComponentProps } from 'src/explore/components/controls/DateFilterControl/types';
|
||||
import DateFunctionTooltip from './DateFunctionTooltip';
|
||||
|
||||
export function AdvancedFrame(props: FrameComponentProps) {
|
||||
function getAdvancedRange(value: string): string {
|
||||
if (value.includes(SEPARATOR)) {
|
||||
return value;
|
||||
}
|
||||
if (value.startsWith('Last')) {
|
||||
return [value, ''].join(SEPARATOR);
|
||||
}
|
||||
if (value.startsWith('Next')) {
|
||||
return ['', value].join(SEPARATOR);
|
||||
}
|
||||
return SEPARATOR;
|
||||
function getAdvancedRange(value: string): string {
|
||||
if (value.includes(SEPARATOR)) {
|
||||
return value;
|
||||
}
|
||||
if (value.startsWith('Last')) {
|
||||
return [value, ''].join(SEPARATOR);
|
||||
}
|
||||
if (value.startsWith('Next')) {
|
||||
return ['', value].join(SEPARATOR);
|
||||
}
|
||||
return SEPARATOR;
|
||||
}
|
||||
|
||||
export function AdvancedFrame(props: FrameComponentProps) {
|
||||
const advancedRange = getAdvancedRange(props.value || '');
|
||||
const [since, until] = advancedRange.split(SEPARATOR);
|
||||
if (advancedRange !== props.value) {
|
||||
|
|
|
@ -17,3 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
export { default } from './DateFilterLabel';
|
||||
export {
|
||||
DATE_FILTER_CONTROL_TEST_ID,
|
||||
fetchTimeRange,
|
||||
guessFrame,
|
||||
} from './utils';
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Type } from 'src/components/Label';
|
||||
|
||||
export type SelectOptionType = {
|
||||
value: string;
|
||||
label: string;
|
||||
|
@ -89,3 +91,12 @@ export type FrameComponentProps = {
|
|||
onChange: (timeRange: string) => void;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export interface DateFilterControlProps {
|
||||
name: string;
|
||||
onChange: (timeRange: string) => void;
|
||||
value?: string;
|
||||
type?: Type;
|
||||
onOpenPopover?: () => void;
|
||||
onClosePopover?: () => void;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
CommonRangeType,
|
||||
CalendarRangeType,
|
||||
} from 'src/explore/components/controls/DateFilterControl/types';
|
||||
import { testWithId } from 'src/utils/testUtils';
|
||||
|
||||
export const FRAME_OPTIONS: SelectOptionType[] = [
|
||||
{ value: 'Common', label: t('Last') },
|
||||
|
@ -131,3 +132,8 @@ export const LOCALE_MAPPING = {
|
|||
sl: 'sl_SI',
|
||||
nl: 'nl_NL',
|
||||
};
|
||||
|
||||
export const DATE_FILTER_CONTROL_TEST_ID = 'date-filter-control';
|
||||
export const getDateFilterControlTestId = testWithId(
|
||||
DATE_FILTER_CONTROL_TEST_ID,
|
||||
);
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import { SupersetClient, NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import {
|
||||
COMMON_RANGE_VALUES_SET,
|
||||
CALENDAR_RANGE_VALUES_SET,
|
||||
customTimeRangeDecode,
|
||||
} from '.';
|
||||
import { FrameType } from '../types';
|
||||
|
||||
export const SEPARATOR = ' : ';
|
||||
|
||||
export const buildTimeRangeString = (since: string, until: string): string =>
|
||||
|
@ -24,11 +34,53 @@ export const buildTimeRangeString = (since: string, until: string): string =>
|
|||
const formatDateEndpoint = (dttm: string, isStart?: boolean): string =>
|
||||
dttm.replace('T00:00:00', '') || (isStart ? '-∞' : '∞');
|
||||
|
||||
export const formatTimeRange = (timeRange: string) => {
|
||||
export const formatTimeRange = (
|
||||
timeRange: string,
|
||||
columnPlaceholder = 'col',
|
||||
) => {
|
||||
const splitDateRange = timeRange.split(SEPARATOR);
|
||||
if (splitDateRange.length === 1) return timeRange;
|
||||
return `${formatDateEndpoint(
|
||||
splitDateRange[0],
|
||||
true,
|
||||
)} ≤ col < ${formatDateEndpoint(splitDateRange[1])}`;
|
||||
)} ≤ ${columnPlaceholder} < ${formatDateEndpoint(splitDateRange[1])}`;
|
||||
};
|
||||
|
||||
export const guessFrame = (timeRange: string): FrameType => {
|
||||
if (COMMON_RANGE_VALUES_SET.has(timeRange)) {
|
||||
return 'Common';
|
||||
}
|
||||
if (CALENDAR_RANGE_VALUES_SET.has(timeRange)) {
|
||||
return 'Calendar';
|
||||
}
|
||||
if (timeRange === NO_TIME_RANGE) {
|
||||
return 'No filter';
|
||||
}
|
||||
if (customTimeRangeDecode(timeRange).matchedFlag) {
|
||||
return 'Custom';
|
||||
}
|
||||
return 'Advanced';
|
||||
};
|
||||
|
||||
export const fetchTimeRange = async (
|
||||
timeRange: string,
|
||||
columnPlaceholder = 'col',
|
||||
) => {
|
||||
const query = rison.encode_uri(timeRange);
|
||||
const endpoint = `/api/v1/time_range/?q=${query}`;
|
||||
try {
|
||||
const response = await SupersetClient.get({ endpoint });
|
||||
const timeRangeString = buildTimeRangeString(
|
||||
response?.json?.result?.since || '',
|
||||
response?.json?.result?.until || '',
|
||||
);
|
||||
return {
|
||||
value: formatTimeRange(timeRangeString, columnPlaceholder),
|
||||
};
|
||||
} catch (response) {
|
||||
const clientError = await getClientErrorObject(response);
|
||||
return {
|
||||
error: clientError.message || clientError.error || response.statusText,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger';
|
||||
import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
||||
import { OptionSortType } from 'src/explore/types';
|
||||
import OptionWrapper from './OptionWrapper';
|
||||
|
||||
export interface DndAdhocFilterOptionProps {
|
||||
adhocFilter: AdhocFilter;
|
||||
onFilterEdit: (changedFilter: AdhocFilter) => void;
|
||||
onClickClose: (index: number) => void;
|
||||
onShiftOptions: (dragIndex: number, hoverIndex: number) => void;
|
||||
options: OptionSortType[];
|
||||
datasource: Record<string, any>;
|
||||
partitionColumn?: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export default function DndAdhocFilterOption({
|
||||
adhocFilter,
|
||||
options,
|
||||
datasource,
|
||||
onFilterEdit,
|
||||
onShiftOptions,
|
||||
onClickClose,
|
||||
partitionColumn,
|
||||
index,
|
||||
}: DndAdhocFilterOptionProps) {
|
||||
return (
|
||||
<AdhocFilterPopoverTrigger
|
||||
key={index}
|
||||
adhocFilter={adhocFilter}
|
||||
options={options}
|
||||
datasource={datasource}
|
||||
onFilterEdit={onFilterEdit}
|
||||
partitionColumn={partitionColumn}
|
||||
>
|
||||
<OptionWrapper
|
||||
key={index}
|
||||
index={index}
|
||||
label={adhocFilter.getDefaultLabel()}
|
||||
tooltipTitle={adhocFilter.getTooltipTitle()}
|
||||
clickClose={onClickClose}
|
||||
onShiftOptions={onShiftOptions}
|
||||
type={DndItemType.FilterOption}
|
||||
withCaret
|
||||
isExtra={adhocFilter.isExtra}
|
||||
/>
|
||||
</AdhocFilterPopoverTrigger>
|
||||
);
|
||||
}
|
|
@ -35,7 +35,6 @@ import {
|
|||
import { Datasource, OptionSortType } from 'src/explore/types';
|
||||
import { OptionValueType } from 'src/explore/components/controls/DndColumnSelectControl/types';
|
||||
import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger';
|
||||
import OptionWrapper from 'src/explore/components/controls/DndColumnSelectControl/OptionWrapper';
|
||||
import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel';
|
||||
import AdhocFilter, {
|
||||
CLAUSES,
|
||||
|
@ -50,6 +49,7 @@ import {
|
|||
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import { ControlComponentProps } from 'src/explore/components/Control';
|
||||
import AdhocFilterControl from '../FilterControl/AdhocFilterControl';
|
||||
import DndAdhocFilterOption from './DndAdhocFilterOption';
|
||||
|
||||
const EMPTY_OBJECT = {};
|
||||
const DND_ACCEPTED_TYPES = [
|
||||
|
@ -296,32 +296,18 @@ const DndFilterSelect = (props: DndFilterSelectProps) => {
|
|||
|
||||
const valuesRenderer = useCallback(
|
||||
() =>
|
||||
values.map((adhocFilter: AdhocFilter, index: number) => {
|
||||
const label = adhocFilter.getDefaultLabel();
|
||||
const tooltipTitle = adhocFilter.getTooltipTitle();
|
||||
return (
|
||||
<AdhocFilterPopoverTrigger
|
||||
key={index}
|
||||
adhocFilter={adhocFilter}
|
||||
options={options}
|
||||
datasource={datasource}
|
||||
onFilterEdit={onFilterEdit}
|
||||
partitionColumn={partitionColumn}
|
||||
>
|
||||
<OptionWrapper
|
||||
key={index}
|
||||
index={index}
|
||||
label={label}
|
||||
tooltipTitle={tooltipTitle}
|
||||
clickClose={onClickClose}
|
||||
onShiftOptions={onShiftOptions}
|
||||
type={DndItemType.FilterOption}
|
||||
withCaret
|
||||
isExtra={adhocFilter.isExtra}
|
||||
/>
|
||||
</AdhocFilterPopoverTrigger>
|
||||
);
|
||||
}),
|
||||
values.map((adhocFilter: AdhocFilter, index: number) => (
|
||||
<DndAdhocFilterOption
|
||||
index={index}
|
||||
adhocFilter={adhocFilter}
|
||||
options={options}
|
||||
datasource={datasource}
|
||||
onFilterEdit={onFilterEdit}
|
||||
partitionColumn={partitionColumn}
|
||||
onClickClose={onClickClose}
|
||||
onShiftOptions={onShiftOptions}
|
||||
/>
|
||||
)),
|
||||
[
|
||||
onClickClose,
|
||||
onFilterEdit,
|
||||
|
@ -401,9 +387,7 @@ const DndFilterSelect = (props: DndFilterSelectProps) => {
|
|||
visible={newFilterPopoverVisible}
|
||||
togglePopover={togglePopover}
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<div />
|
||||
</AdhocFilterPopoverTrigger>
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@ import { Tooltip } from 'src/components/Tooltip';
|
|||
import { Input } from 'src/components/Input';
|
||||
import { optionLabel } from 'src/utils/common';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
||||
import useAdvancedDataTypes from './useAdvancedDataTypes';
|
||||
|
||||
const StyledInput = styled(Input)`
|
||||
|
@ -61,20 +62,9 @@ const SelectWithLabel = styled(Select)<{ labelText: string }>`
|
|||
}
|
||||
`;
|
||||
|
||||
export interface SimpleColumnType {
|
||||
id: number;
|
||||
column_name: string;
|
||||
expression?: string;
|
||||
type: string;
|
||||
optionName?: string;
|
||||
filterBy?: string;
|
||||
value?: string;
|
||||
advanced_data_type?: string;
|
||||
}
|
||||
|
||||
export interface SimpleExpressionType {
|
||||
expressionType: keyof typeof EXPRESSION_TYPES;
|
||||
column: SimpleColumnType;
|
||||
column: ColumnMeta;
|
||||
aggregate: keyof typeof AGGREGATES;
|
||||
label: string;
|
||||
}
|
||||
|
@ -89,7 +79,7 @@ export interface MetricColumnType {
|
|||
}
|
||||
|
||||
export type ColumnType =
|
||||
| SimpleColumnType
|
||||
| ColumnMeta
|
||||
| SimpleExpressionType
|
||||
| SQLExpressionType
|
||||
| MetricColumnType;
|
||||
|
@ -100,7 +90,7 @@ export interface Props {
|
|||
options: ColumnType[];
|
||||
datasource: {
|
||||
id: string;
|
||||
columns: SimpleColumnType[];
|
||||
columns: ColumnMeta[];
|
||||
type: string;
|
||||
filter_select: boolean;
|
||||
};
|
||||
|
@ -410,31 +400,35 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
|
|||
}
|
||||
}, [props.adhocFilter.comparator]);
|
||||
|
||||
return (
|
||||
// another name for columns, just for following previous naming.
|
||||
const subjectComponent = (
|
||||
<Select
|
||||
css={(theme: SupersetTheme) => ({
|
||||
marginTop: theme.gridUnit * 4,
|
||||
marginBottom: theme.gridUnit * 4,
|
||||
})}
|
||||
data-test="select-element"
|
||||
options={columns.map(column => ({
|
||||
value:
|
||||
('column_name' in column && column.column_name) ||
|
||||
('optionName' in column && column.optionName) ||
|
||||
'',
|
||||
label:
|
||||
('saved_metric_name' in column && column.saved_metric_name) ||
|
||||
('column_name' in column && column.column_name) ||
|
||||
('label' in column && column.label),
|
||||
key:
|
||||
('id' in column && column.id) ||
|
||||
('optionName' in column && column.optionName) ||
|
||||
undefined,
|
||||
customLabel: renderSubjectOptionLabel(column),
|
||||
}))}
|
||||
{...subjectSelectProps}
|
||||
/>
|
||||
);
|
||||
|
||||
const operatorsAndOperandComponent = (
|
||||
<>
|
||||
<Select
|
||||
css={(theme: SupersetTheme) => ({
|
||||
marginTop: theme.gridUnit * 4,
|
||||
marginBottom: theme.gridUnit * 4,
|
||||
})}
|
||||
data-test="select-element"
|
||||
options={columns.map(column => ({
|
||||
value:
|
||||
('column_name' in column && column.column_name) ||
|
||||
('optionName' in column && column.optionName) ||
|
||||
'',
|
||||
label:
|
||||
('saved_metric_name' in column && column.saved_metric_name) ||
|
||||
('column_name' in column && column.column_name) ||
|
||||
('label' in column && column.label),
|
||||
key:
|
||||
('id' in column && column.id) ||
|
||||
('optionName' in column && column.optionName) ||
|
||||
undefined,
|
||||
customLabel: renderSubjectOptionLabel(column),
|
||||
}))}
|
||||
{...subjectSelectProps}
|
||||
/>
|
||||
<Select
|
||||
css={(theme: SupersetTheme) => ({ marginBottom: theme.gridUnit * 4 })}
|
||||
options={(props.operators ?? OPERATORS_OPTIONS)
|
||||
|
@ -484,6 +478,12 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
|
|||
)}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{subjectComponent}
|
||||
{operatorsAndOperandComponent}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdhocFilterEditPopoverSimpleTabContent;
|
||||
|
|
|
@ -25,7 +25,7 @@ import AdhocFilter, {
|
|||
EXPRESSION_TYPES,
|
||||
CLAUSES,
|
||||
} from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
||||
import AdhocFilterOption from '.';
|
||||
import AdhocFilterOption, { AdhocFilterOptionProps } from '.';
|
||||
|
||||
const simpleAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
|
@ -44,18 +44,18 @@ const options = [
|
|||
const mockedProps = {
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
onFilterEdit: jest.fn(),
|
||||
onRemoveFilter: jest.fn(),
|
||||
options,
|
||||
sections: [],
|
||||
operators: [],
|
||||
datasource: {},
|
||||
partitionColumn: '',
|
||||
onMoveLabel: jest.fn(),
|
||||
onDropLabel: jest.fn(),
|
||||
index: 1,
|
||||
};
|
||||
|
||||
const setup = (props: {
|
||||
adhocFilter: typeof simpleAdhocFilter;
|
||||
onFilterEdit: () => void;
|
||||
options: {
|
||||
type: string;
|
||||
column_name: string;
|
||||
id: number;
|
||||
}[];
|
||||
}) => (
|
||||
const setup = (props: AdhocFilterOptionProps) => (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<AdhocFilterOption {...props} />
|
||||
</DndProvider>
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import adhocMetricType from 'src/explore/components/controls/MetricControl/adhocMetricType';
|
||||
import { OptionControlLabel } from 'src/explore/components/controls/OptionControls';
|
||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import columnType from 'src/explore/components/controls/FilterControl/columnType';
|
||||
import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger';
|
||||
import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
||||
|
||||
const propTypes = {
|
||||
adhocFilter: PropTypes.instanceOf(AdhocFilter).isRequired,
|
||||
onFilterEdit: PropTypes.func.isRequired,
|
||||
onRemoveFilter: PropTypes.func,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([
|
||||
columnType,
|
||||
PropTypes.shape({ saved_metric_name: PropTypes.string.isRequired }),
|
||||
adhocMetricType,
|
||||
]),
|
||||
).isRequired,
|
||||
sections: PropTypes.arrayOf(PropTypes.string),
|
||||
operators: PropTypes.arrayOf(PropTypes.string),
|
||||
datasource: PropTypes.object,
|
||||
partitionColumn: PropTypes.string,
|
||||
onMoveLabel: PropTypes.func,
|
||||
onDropLabel: PropTypes.func,
|
||||
index: PropTypes.number,
|
||||
};
|
||||
|
||||
const AdhocFilterOption = ({
|
||||
adhocFilter,
|
||||
options,
|
||||
datasource,
|
||||
onFilterEdit,
|
||||
onRemoveFilter,
|
||||
partitionColumn,
|
||||
onMoveLabel,
|
||||
onDropLabel,
|
||||
index,
|
||||
sections,
|
||||
operators,
|
||||
}) => (
|
||||
<AdhocFilterPopoverTrigger
|
||||
sections={sections}
|
||||
operators={operators}
|
||||
adhocFilter={adhocFilter}
|
||||
options={options}
|
||||
datasource={datasource}
|
||||
onFilterEdit={onFilterEdit}
|
||||
partitionColumn={partitionColumn}
|
||||
>
|
||||
<OptionControlLabel
|
||||
label={adhocFilter.getDefaultLabel()}
|
||||
tooltipTitle={adhocFilter.getTooltipTitle()}
|
||||
onRemove={onRemoveFilter}
|
||||
onMoveLabel={onMoveLabel}
|
||||
onDropLabel={onDropLabel}
|
||||
index={index}
|
||||
type={DndItemType.FilterOption}
|
||||
withCaret
|
||||
isExtra={adhocFilter.isExtra}
|
||||
/>
|
||||
</AdhocFilterPopoverTrigger>
|
||||
);
|
||||
|
||||
export default AdhocFilterOption;
|
||||
|
||||
AdhocFilterOption.propTypes = propTypes;
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { OptionControlLabel } from 'src/explore/components/controls/OptionControls';
|
||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import AdhocFilterPopoverTrigger from 'src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger';
|
||||
import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter';
|
||||
import { OptionSortType } from 'src/explore/types';
|
||||
import { Operators } from 'src/explore/constants';
|
||||
|
||||
export interface AdhocFilterOptionProps {
|
||||
adhocFilter: AdhocFilter;
|
||||
onFilterEdit: () => void;
|
||||
onRemoveFilter: () => void;
|
||||
options: OptionSortType[];
|
||||
sections: string[];
|
||||
operators: Operators[];
|
||||
datasource: Record<string, any>;
|
||||
partitionColumn: string;
|
||||
onMoveLabel: () => void;
|
||||
onDropLabel: () => void;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export default function AdhocFilterOption({
|
||||
adhocFilter,
|
||||
options,
|
||||
datasource,
|
||||
onFilterEdit,
|
||||
onRemoveFilter,
|
||||
partitionColumn,
|
||||
onMoveLabel,
|
||||
onDropLabel,
|
||||
index,
|
||||
sections,
|
||||
operators,
|
||||
}: AdhocFilterOptionProps) {
|
||||
return (
|
||||
<AdhocFilterPopoverTrigger
|
||||
sections={sections}
|
||||
operators={operators}
|
||||
adhocFilter={adhocFilter}
|
||||
options={options}
|
||||
datasource={datasource}
|
||||
onFilterEdit={onFilterEdit}
|
||||
partitionColumn={partitionColumn}
|
||||
>
|
||||
<OptionControlLabel
|
||||
label={adhocFilter.getDefaultLabel()}
|
||||
tooltipTitle={adhocFilter.getTooltipTitle()}
|
||||
onRemove={onRemoveFilter}
|
||||
onMoveLabel={onMoveLabel}
|
||||
onDropLabel={onDropLabel}
|
||||
index={index}
|
||||
type={DndItemType.FilterOption}
|
||||
withCaret
|
||||
isExtra={adhocFilter.isExtra}
|
||||
/>
|
||||
</AdhocFilterPopoverTrigger>
|
||||
);
|
||||
}
|
|
@ -142,10 +142,6 @@ export const TIME_FILTER_MAP = {
|
|||
granularity: '__granularity',
|
||||
};
|
||||
|
||||
// TODO: make this configurable per Superset installation
|
||||
export const DEFAULT_TIME_RANGE = 'No filter';
|
||||
export const NO_TIME_RANGE = 'No filter';
|
||||
|
||||
export enum FILTER_BOX_MIGRATION_STATES {
|
||||
CONVERTED = 'CONVERTED',
|
||||
NOOP = 'NOOP',
|
||||
|
|
|
@ -29,8 +29,8 @@ import {
|
|||
AdhocFilter,
|
||||
isFreeFormAdhocFilter,
|
||||
isSimpleAdhocFilter,
|
||||
NO_TIME_RANGE,
|
||||
} from '@superset-ui/core';
|
||||
import { NO_TIME_RANGE } from '../constants';
|
||||
|
||||
const simpleFilterToAdhoc = (
|
||||
filterClause: QueryObjectFilterClause,
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { styled } from '@superset-ui/core';
|
||||
import { styled, NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import DateFilterControl from 'src/explore/components/controls/DateFilterControl';
|
||||
import { NO_TIME_RANGE } from 'src/explore/constants';
|
||||
import { PluginFilterTimeProps } from './types';
|
||||
import { FilterPluginStyle } from '../common';
|
||||
|
||||
|
|
Loading…
Reference in New Issue