superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts
/**
* 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 {
AdhocColumn,
AdhocMetricSimple,
AdhocMetricSQL,
getChartControlPanelRegistry,
QueryFormData,
TimeGranularity,
} from '@superset-ui/core';
import TableChartPlugin from '@superset-ui/plugin-chart-table';
import { BigNumberTotalChartPlugin } from '@superset-ui/plugin-chart-echarts';
import { sections } from '@superset-ui/chart-controls';
import {
StandardizedFormData,
sharedMetricsKey,
sharedColumnsKey,
publicControls,
} from './standardizedFormData';
const adhocColumn: AdhocColumn = {
expressionType: 'SQL',
label: 'country',
optionName: 'country',
sqlExpression: 'country',
};
const adhocMetricSQL: AdhocMetricSQL = {
expressionType: 'SQL',
label: 'count',
optionName: 'count',
sqlExpression: 'count(*)',
};
const adhocMetricSimple: AdhocMetricSimple = {
expressionType: 'SIMPLE',
column: {
id: 1,
column_name: 'sales',
columnName: 'sales',
verbose_name: 'sales',
},
aggregate: 'SUM',
label: 'count',
optionName: 'count',
};
const tableVizFormData = {
datasource: '30__table',
viz_type: 'table',
granularity_sqla: 'ds',
time_grain_sqla: TimeGranularity.DAY,
time_range: 'No filter',
query_mode: 'aggregate',
groupby: ['name', 'gender', adhocColumn],
metrics: ['count', 'avg(sales)', adhocMetricSimple, adhocMetricSQL],
all_columns: [],
percent_metrics: [],
adhoc_filters: [],
order_by_cols: [],
row_limit: 10000,
server_page_length: 10,
order_desc: true,
table_timestamp_format: 'smart_date',
show_cell_bars: true,
color_pn: true,
url_params: {
form_data_key:
'p3No_sqDW7k-kMTzlBPAPd9vwp1IXTf6stbyzjlrPPa0ninvdYUUiMC6F1iKit3Y',
dataset_id: '30',
},
};
const tableVizStore = {
form_data: tableVizFormData,
controls: {
datasource: {
value: '30__table',
},
viz_type: {
value: 'table',
},
slice_id: {},
cache_timeout: {},
url_params: {
value: {
form_data_key:
'p3No_sqDW7k-kMTzlBPAPd9vwp1IXTf6stbyzjlrPPa0ninvdYUUiMC6F1iKit3Y',
dataset_id: '30',
},
},
granularity_sqla: {
value: 'ds',
},
time_grain_sqla: {
value: 'P1D',
},
time_range: {
value: 'No filter',
},
query_mode: {
value: 'aggregate',
},
groupby: {
value: ['name', 'gender', adhocColumn],
},
metrics: {
value: ['count', 'avg(sales)', adhocMetricSimple, adhocMetricSQL],
},
all_columns: {
value: [],
},
percent_metrics: {
value: [],
},
adhoc_filters: {
value: [],
},
timeseries_limit_metric: {},
order_by_cols: {
value: [],
},
server_pagination: {},
row_limit: {
value: 10000,
},
server_page_length: {
value: 10,
},
include_time: {},
order_desc: {
value: true,
},
show_totals: {},
table_timestamp_format: {
value: 'smart_date',
},
page_length: {},
include_search: {},
show_cell_bars: {
value: true,
},
align_pn: {},
color_pn: {
value: true,
},
column_config: {},
conditional_formatting: {},
},
datasource: {
type: 'table',
columns: [],
},
};
describe('should collect control values and create SFD', () => {
const sharedKey = [...sharedMetricsKey, ...sharedColumnsKey];
const sharedControlsFormData = {
// metrics
metric: 'm1',
metrics: ['m2'],
metric_2: 'm3',
size: 'm4',
x: 'm5',
y: 'm6',
secondary_metric: 'm7',
// columns
groupby: ['c1'],
columns: ['c2'],
groupbyColumns: ['c3'],
groupbyRows: ['c4'],
series: 'c5',
entity: 'c6',
series_columns: ['c7'],
};
const publicControlsFormData = {
// time section
granularity_sqla: 'time_column',
time_grain_sqla: TimeGranularity.DAY,
time_range: '2000 : today',
// filters
adhoc_filters: [],
// subquery limit(series limit)
limit: 5,
// order by clause
timeseries_limit_metric: 'orderby_metric',
series_limit_metric: 'orderby_metric',
// desc or asc in order by clause
order_desc: true,
// outer query limit
row_limit: 100,
// x asxs column
x_axis: 'x_axis_column',
// advanced analytics - rolling window
rolling_type: 'sum',
rolling_periods: 1,
min_periods: 0,
// advanced analytics - time comparison
time_compare: '1 year ago',
comparison_type: 'values',
// advanced analytics - resample
resample_rule: '1D',
resample_method: 'zerofill',
};
const sourceMockFormData: QueryFormData = {
...sharedControlsFormData,
...publicControlsFormData,
datasource: '100__table',
viz_type: 'source_viz',
};
const sourceMockStore = {
form_data: sourceMockFormData,
controls: Object.fromEntries(
Object.entries(sourceMockFormData).map(([key, value]) => [
key,
{ value },
]),
),
datasource: {
type: 'table',
columns: [],
},
};
beforeAll(() => {
getChartControlPanelRegistry().registerValue('source_viz', {
controlPanelSections: [
sections.advancedAnalyticsControls,
{
label: 'transform controls',
controlSetRows: publicControls.map(control => [control]),
},
{
label: 'axis column',
controlSetRows: [['x_axis']],
},
],
});
getChartControlPanelRegistry().registerValue('target_viz', {
controlPanelSections: [
sections.advancedAnalyticsControls,
{
label: 'transform controls',
controlSetRows: publicControls.map(control => [control]),
},
{
label: 'axis column',
controlSetRows: [['x_axis']],
},
],
formDataOverrides: (formData: QueryFormData) => ({
...formData,
columns: formData.standardizedFormData.controls.columns,
metrics: formData.standardizedFormData.controls.metrics,
}),
});
});
test('should avoid to overlap', () => {
const sharedControlsSet = new Set(Object.keys(sharedKey));
const publicControlsSet = new Set(publicControls);
expect(
[...sharedControlsSet].filter((x: string) => publicControlsSet.has(x)),
).toEqual([]);
});
test('should collect all sharedControls', () => {
expect(Object.entries(sharedControlsFormData).length).toBe(
Object.entries(sharedKey).length,
);
const sfd = new StandardizedFormData(sourceMockFormData);
expect(sfd.serialize().controls.metrics).toEqual([
'm1',
'm2',
'm3',
'm4',
'm5',
'm6',
'm7',
]);
expect(sfd.serialize().controls.columns).toEqual([
'c1',
'c2',
'c3',
'c4',
'c5',
'c6',
'c7',
]);
});
test('should transform all publicControls and sharedControls', () => {
expect(Object.entries(publicControlsFormData).length).toBe(
publicControls.length,
);
const sfd = new StandardizedFormData(sourceMockFormData);
const { formData } = sfd.transform('target_viz', sourceMockStore);
Object.entries(publicControlsFormData).forEach(([key, value]) => {
expect(formData).toHaveProperty(key);
expect(value).toEqual(publicControlsFormData[key]);
});
expect(formData.columns).toEqual([
'c1',
'c2',
'c3',
'c4',
'c5',
'c6',
'c7',
]);
expect(formData.metrics).toEqual([
'm1',
'm2',
'm3',
'm4',
'm5',
'm6',
'm7',
]);
});
test('should inherit standardizedFormData and memorizedFormData is LIFO', () => {
// from source_viz to target_viz
const sfd = new StandardizedFormData(sourceMockFormData);
const { formData, controlsState } = sfd.transform(
'target_viz',
sourceMockStore,
);
expect(
formData.standardizedFormData.memorizedFormData.map(
(fd: [string, QueryFormData]) => fd[0],
),
).toEqual(['source_viz']);
// from target_viz to source_viz
const sfd2 = new StandardizedFormData(formData);
const { formData: fd2, controlsState: cs2 } = sfd2.transform('source_viz', {
...sourceMockStore,
form_data: formData,
controls: controlsState,
});
expect(
fd2.standardizedFormData.memorizedFormData.map(
(fd: [string, QueryFormData]) => fd[0],
),
).toEqual(['source_viz', 'target_viz']);
// from source_viz to target_viz
const sfd3 = new StandardizedFormData(fd2);
const { formData: fd3 } = sfd3.transform('target_viz', {
...sourceMockStore,
form_data: fd2,
controls: cs2,
});
expect(
fd3.standardizedFormData.memorizedFormData.map(
(fd: [string, QueryFormData]) => fd[0],
),
).toEqual(['target_viz', 'source_viz']);
});
});
describe('should transform form_data between table and bigNumberTotal', () => {
beforeAll(() => {
getChartControlPanelRegistry().registerValue(
'big_number_total',
new BigNumberTotalChartPlugin().controlPanel,
);
getChartControlPanelRegistry().registerValue(
'table',
new TableChartPlugin().controlPanel,
);
});
test('get and has', () => {
// table -> bigNumberTotal
const sfd = new StandardizedFormData(tableVizFormData);
const { formData: bntFormData } = sfd.transform(
'big_number_total',
tableVizStore,
);
// bigNumberTotal -> table
const sfd2 = new StandardizedFormData(bntFormData);
expect(sfd2.has('big_number_total')).toBeTruthy();
expect(sfd2.has('table')).toBeTruthy();
expect(sfd2.get('big_number_total').viz_type).toBe('big_number_total');
expect(sfd2.get('table').viz_type).toBe('table');
});
test('transform', () => {
// table -> bigNumberTotal
const sfd = new StandardizedFormData(tableVizFormData);
const { formData: bntFormData, controlsState: bntControlsState } =
sfd.transform('big_number_total', tableVizStore);
expect(Object.keys(bntFormData).sort()).toEqual(
[...Object.keys(bntControlsState), 'standardizedFormData'].sort(),
);
expect(bntFormData.viz_type).toBe('big_number_total');
expect(bntFormData.metric).toBe('count');
// change control values on bigNumber
bntFormData.metric = 'sum(sales)';
bntControlsState.metric.value = 'sum(sales)';
// bigNumberTotal -> table
const sfd2 = new StandardizedFormData(bntFormData);
const { formData: tblFormData, controlsState: tblControlsState } =
sfd2.transform('table', {
...tableVizStore,
form_data: bntFormData,
controls: bntControlsState,
});
expect(Object.keys(tblFormData).sort()).toEqual(
[...Object.keys(tblControlsState), 'standardizedFormData'].sort(),
);
expect(tblFormData.viz_type).toBe('table');
expect(tblFormData.metrics).toEqual([
'sum(sales)',
'avg(sales)',
adhocMetricSimple,
adhocMetricSQL,
]);
expect(tblFormData.groupby).toEqual(['name', 'gender', adhocColumn]);
});
});
describe('initial SFD between different datasource', () => {
beforeAll(() => {
getChartControlPanelRegistry().registerValue(
'big_number_total',
new BigNumberTotalChartPlugin().controlPanel,
);
getChartControlPanelRegistry().registerValue(
'table',
new TableChartPlugin().controlPanel,
);
});
test('initial SFD between different datasource', () => {
const sfd = new StandardizedFormData(tableVizFormData);
// table -> big number
const { formData: bntFormData, controlsState: bntControlsState } =
sfd.transform('big_number_total', tableVizStore);
const sfd2 = new StandardizedFormData(bntFormData);
// big number -> table
const { formData: tblFormData } = sfd2.transform('table', {
...tableVizStore,
form_data: bntFormData,
controls: bntControlsState,
});
expect(
tblFormData.standardizedFormData.memorizedFormData.map(
(mfd: [string, QueryFormData][]) => mfd[0],
),
).toEqual(['table', 'big_number_total']);
const newDatasourceFormData = { ...tblFormData, datasource: '20__table' };
const newDatasourceSFD = new StandardizedFormData(newDatasourceFormData);
expect(
newDatasourceSFD
.serialize()
.memorizedFormData.map(([vizType]) => vizType),
).toEqual(['table']);
expect(newDatasourceSFD.get('table')).not.toHaveProperty(
'standardizedFormData',
);
});
});