You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2022/06/28 17:24:07 UTC
[superset] 16/19: feat(standardized form data): keep all columns and metrics (#20377)
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 2.0
in repository https://gitbox.apache.org/repos/asf/superset.git
commit b8d9208b6e555181d7040161103535ca3ef500e0
Author: Yongjie Zhao <yo...@gmail.com>
AuthorDate: Fri Jun 24 21:46:25 2022 +0800
feat(standardized form data): keep all columns and metrics (#20377)
(cherry picked from commit bbbe102887a524b1ee0ffd4ada8481078dbe5496)
---
.../superset-ui-chart-controls/src/types.ts | 4 +
.../src/BigNumber/BigNumberTotal/controlPanel.ts | 4 +
.../BigNumberWithTrendline/controlPanel.tsx | 4 +
.../src/Funnel/controlPanel.tsx | 4 +
.../src/Gauge/controlPanel.tsx | 4 +
.../src/Graph/controlPanel.tsx | 4 +
.../plugin-chart-echarts/src/Pie/controlPanel.tsx | 4 +
.../plugin-chart-echarts/src/Tree/controlPanel.tsx | 4 +
.../src/Treemap/controlPanel.tsx | 4 +
...mData.test.tsx => standardizedFormData.test.ts} | 116 ++++++++++++----
.../explore/controlUtils/standardizedFormData.ts | 146 +++++++++++++++------
11 files changed, 231 insertions(+), 67 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
index 431450f142..fac5548851 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
@@ -363,6 +363,10 @@ export interface ControlPanelConfig {
standardizedFormData: StandardizedFormDataInterface;
},
) => QueryFormData;
+ updateStandardizedState?: (
+ prevState: StandardizedState,
+ currState: StandardizedState,
+ ) => StandardizedState;
}
export type ControlOverrides = {
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
index c715c8f232..94dd458305 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
@@ -100,4 +100,8 @@ export default {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
} as ControlPanelConfig;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
index c61ec6d046..0ea0a96229 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
@@ -274,6 +274,10 @@ const config: ControlPanelConfig = {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
};
export default config;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
index 39ce57d498..b23cf2ed97 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
@@ -148,6 +148,10 @@ const config: ControlPanelConfig = {
metric: formData.standardizedFormData.standardizedState.metrics[0],
groupby: formData.standardizedFormData.standardizedState.columns,
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
};
export default config;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
index d9dbb3025e..bb727888e5 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
@@ -313,6 +313,10 @@ const config: ControlPanelConfig = {
metric: formData.standardizedFormData.standardizedState.metrics[0],
groupby: formData.standardizedFormData.standardizedState.columns,
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
};
export default config;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Graph/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Graph/controlPanel.tsx
index 39547f683a..218b1d0335 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Graph/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Graph/controlPanel.tsx
@@ -324,6 +324,10 @@ const controlPanel: ControlPanelConfig = {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
};
export default controlPanel;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
index 43e1ab6cba..7b948208a4 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
@@ -260,6 +260,10 @@ const config: ControlPanelConfig = {
row_limit:
ensureIsInt(formData.row_limit, 100) >= 100 ? 100 : formData.row_limit,
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
};
export default config;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx
index 097a882fa3..c115f5c5c2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Tree/controlPanel.tsx
@@ -289,6 +289,10 @@ const controlPanel: ControlPanelConfig = {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
};
export default controlPanel;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
index 8887377d2d..e7cca1af26 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
@@ -142,6 +142,10 @@ const config: ControlPanelConfig = {
metric: formData.standardizedFormData.standardizedState.metrics[0],
groupby: formData.standardizedFormData.standardizedState.columns,
}),
+ updateStandardizedState: (prevState, currState) => ({
+ ...currState,
+ metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
+ }),
};
export default config;
diff --git a/superset-frontend/src/explore/controlUtils/standardizedFormData.test.tsx b/superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts
similarity index 74%
rename from superset-frontend/src/explore/controlUtils/standardizedFormData.test.tsx
rename to superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts
index e048c04498..e7d1c758c4 100644
--- a/superset-frontend/src/explore/controlUtils/standardizedFormData.test.tsx
+++ b/superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts
@@ -28,15 +28,46 @@ import {
import { xAxisControl } from '../../../plugins/plugin-chart-echarts/src/controls';
describe('should collect control values and create SFD', () => {
- const sharedControlsFormData = {};
- Object.entries(sharedControls).forEach(([, names]) => {
- names.forEach(name => {
- sharedControlsFormData[name] = name;
- });
- });
- const publicControlsFormData = Object.fromEntries(
- publicControls.map((name, idx) => [[name], idx]),
- );
+ const sharedControlsFormData = {
+ // metrics
+ metric: 'm1',
+ metrics: ['m2'],
+ metric_2: 'm3',
+ // columns
+ groupby: ['c1'],
+ columns: ['c2'],
+ groupbyColumns: ['c3'],
+ groupbyRows: ['c4'],
+ };
+ const publicControlsFormData = {
+ // time section
+ granularity_sqla: 'time_column',
+ time_grain_sqla: 'P1D',
+ 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,
@@ -90,26 +121,45 @@ describe('should collect control values and create SFD', () => {
});
});
- test('collect sharedControls', () => {
- const sfd = new StandardizedFormData(sourceMockFormData);
+ test('should avoid to overlap', () => {
+ const sharedControlsSet = new Set(Object.keys(sharedControls));
+ const publicControlsSet = new Set(publicControls);
+ expect(
+ [...sharedControlsSet].filter((x: string) => publicControlsSet.has(x)),
+ ).toEqual([]);
+ });
- expect(sfd.dumpSFD().standardizedState.metrics).toEqual(
- sharedControls.metrics.map(controlName => controlName),
- );
- expect(sfd.dumpSFD().standardizedState.columns).toEqual(
- sharedControls.columns.map(controlName => controlName),
+ test('should collect all sharedControls', () => {
+ expect(Object.entries(sharedControlsFormData).length).toBe(
+ Object.entries(sharedControls).length,
);
+ const sfd = new StandardizedFormData(sourceMockFormData);
+ expect(sfd.serialize().standardizedState.metrics).toEqual([
+ 'm1',
+ 'm2',
+ 'm3',
+ ]);
+ expect(sfd.serialize().standardizedState.columns).toEqual([
+ 'c1',
+ 'c2',
+ 'c3',
+ 'c4',
+ ]);
});
- test('should transform all publicControls', () => {
+ 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]) => {
+ Object.entries(publicControlsFormData).forEach(([key, value]) => {
expect(formData).toHaveProperty(key);
+ expect(value).toEqual(publicControlsFormData[key]);
});
- Object.entries(sharedControls).forEach(([key, value]) => {
- expect(formData[key]).toEqual(value);
- });
+ expect(formData.columns).toEqual(['c1', 'c2', 'c3', 'c4']);
+ expect(formData.metrics).toEqual(['m1', 'm2', 'm3']);
});
test('should inherit standardizedFormData and memorizedFormData is LIFO', () => {
@@ -157,6 +207,7 @@ describe('should transform form_data between table and bigNumberTotal', () => {
const tableVizFormData = {
datasource: '30__table',
viz_type: 'table',
+ granularity_sqla: 'ds',
time_grain_sqla: 'P1D',
time_range: 'No filter',
query_mode: 'aggregate',
@@ -172,7 +223,6 @@ describe('should transform form_data between table and bigNumberTotal', () => {
table_timestamp_format: 'smart_date',
show_cell_bars: true,
color_pn: true,
- applied_time_extras: {},
url_params: {
form_data_key:
'p3No_sqDW7k-kMTzlBPAPd9vwp1IXTf6stbyzjlrPPa0ninvdYUUiMC6F1iKit3Y',
@@ -197,7 +247,9 @@ describe('should transform form_data between table and bigNumberTotal', () => {
dataset_id: '30',
},
},
- granularity_sqla: {},
+ granularity_sqla: {
+ value: 'ds',
+ },
time_grain_sqla: {
value: 'P1D',
},
@@ -271,6 +323,22 @@ describe('should transform form_data between table and bigNumberTotal', () => {
);
});
+ 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);
@@ -301,7 +369,7 @@ describe('should transform form_data between table and bigNumberTotal', () => {
);
expect(tblFormData.viz_type).toBe('table');
expect(tblFormData.metrics).toEqual(['sum(sales)']);
- expect(tblFormData.groupby).toEqual([]);
+ expect(tblFormData.groupby).toEqual(['name']);
expect(tblFormData.time_range).toBe('2021 : 2022');
});
});
diff --git a/superset-frontend/src/explore/controlUtils/standardizedFormData.ts b/superset-frontend/src/explore/controlUtils/standardizedFormData.ts
index c42ef95085..99b0220da3 100644
--- a/superset-frontend/src/explore/controlUtils/standardizedFormData.ts
+++ b/superset-frontend/src/explore/controlUtils/standardizedFormData.ts
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { isEmpty, intersection } from 'lodash';
import {
ensureIsArray,
getChartControlPanelRegistry,
@@ -29,38 +30,52 @@ import {
import { getControlsState } from 'src/explore/store';
import { getFormDataFromControls } from './getFormDataFromControls';
-export const sharedControls: Record<keyof StandardizedState, string[]> = {
- metrics: ['metric', 'metrics', 'metric_2'],
- columns: ['groupby', 'columns', 'groupbyColumns', 'groupbyRows'],
+export const sharedControls: Record<string, keyof StandardizedState> = {
+ // metrics
+ metric: 'metrics', // via sharedControls, scalar
+ metrics: 'metrics', // via sharedControls, array
+ metric_2: 'metrics', // via sharedControls, scalar
+ // columns
+ groupby: 'columns', // via sharedControls, array
+ columns: 'columns', // via sharedControls, array
+ groupbyColumns: 'columns', // via pivot table v2, array
+ groupbyRows: 'columns', // via pivot table v2, array
};
+const sharedControlsMap: Record<keyof StandardizedState, string[]> = {
+ metrics: [],
+ columns: [],
+};
+Object.entries(sharedControls).forEach(([key, value]) =>
+ sharedControlsMap[value].push(key),
+);
export const publicControls = [
// time section
- 'granularity_sqla',
- 'time_grain_sqla',
- 'time_range',
+ 'granularity_sqla', // via sharedControls
+ 'time_grain_sqla', // via sharedControls
+ 'time_range', // via sharedControls
// filters
- 'adhoc_filters',
+ 'adhoc_filters', // via sharedControls
// subquery limit(series limit)
- 'limit',
+ 'limit', // via sharedControls
// order by clause
- 'timeseries_limit_metric',
- 'series_limit_metric',
+ 'timeseries_limit_metric', // via sharedControls
+ 'series_limit_metric', // via sharedControls
// desc or asc in order by clause
- 'order_desc',
+ 'order_desc', // via sharedControls
// outer query limit
- 'row_limit',
+ 'row_limit', // via sharedControls
// x asxs column
- 'x_axis',
+ 'x_axis', // via sharedControls
// advanced analytics - rolling window
- 'rolling_type',
- 'rolling_periods',
- 'min_periods',
+ 'rolling_type', // via sections.advancedAnalytics
+ 'rolling_periods', // via sections.advancedAnalytics
+ 'min_periods', // via sections.advancedAnalytics
// advanced analytics - time comparison
- 'time_compare',
- 'comparison_type',
+ 'time_compare', // via sections.advancedAnalytics
+ 'comparison_type', // via sections.advancedAnalytics
// advanced analytics - resample
- 'resample_rule',
- 'resample_method',
+ 'resample_rule', // via sections.advancedAnalytics
+ 'resample_method', // via sections.advancedAnalytics
];
export class StandardizedFormData {
@@ -70,20 +85,10 @@ export class StandardizedFormData {
/*
* Support form_data for smooth switching between different viz
* */
- const standardizedState = {
- metrics: [],
- columns: [],
- };
const formData = Object.freeze(sourceFormData);
- const reversedMap = StandardizedFormData.getReversedMap();
- Object.entries(formData).forEach(([key, value]) => {
- if (reversedMap.has(key)) {
- standardizedState[reversedMap.get(key)].push(...ensureIsArray(value));
- }
- });
-
- const memorizedFormData = Array.isArray(
+ // generates an ordered map, the key is viz_type and the value is form_data. the last item is current viz
+ const memorizedFormData: Map<string, QueryFormData> = Array.isArray(
formData?.standardizedFormData?.memorizedFormData,
)
? new Map(formData.standardizedFormData.memorizedFormData)
@@ -93,25 +98,72 @@ export class StandardizedFormData {
memorizedFormData.delete(vizType);
}
memorizedFormData.set(vizType, formData);
+
+ // calculate sharedControls
+ const standardizedState =
+ StandardizedFormData.getStandardizedState(formData);
+
this.sfd = {
standardizedState,
memorizedFormData,
};
}
- static getReversedMap() {
- const reversedMap = new Map();
- Object.entries(sharedControls).forEach(([key, names]) => {
- names.forEach(name => {
- reversedMap.set(name, key);
- });
+ static getStandardizedState(formData: QueryFormData): StandardizedState {
+ // 1. collect current sharedControls
+ let currState: StandardizedState = {
+ metrics: [],
+ columns: [],
+ };
+ Object.entries(formData).forEach(([key, value]) => {
+ if (key in sharedControls) {
+ currState[sharedControls[key]].push(...ensureIsArray(value));
+ }
});
- return reversedMap;
+
+ // 2. get previous StandardizedState
+ let prevState: StandardizedState = {
+ metrics: [],
+ columns: [],
+ };
+ if (
+ formData?.standardizedFormData?.standardizedState &&
+ Array.isArray(formData.standardizedFormData.standardizedState.metrics) &&
+ Array.isArray(formData.standardizedFormData.standardizedState.columns)
+ ) {
+ prevState = formData.standardizedFormData.standardizedState;
+ }
+ // the initial prevState should equal to currentState
+ if (isEmpty(prevState.metrics) && isEmpty(prevState.columns)) {
+ prevState = currState;
+ }
+
+ // 3. inherit SS from previous state if current viz hasn't columns-like controls or metrics-like controls
+ Object.keys(sharedControlsMap).forEach(key => {
+ if (
+ isEmpty(intersection(Object.keys(formData), sharedControlsMap[key]))
+ ) {
+ currState[key] = prevState[key];
+ }
+ });
+
+ // 4. update hook
+ const controlPanel = getChartControlPanelRegistry().get(formData.viz_type);
+ if (controlPanel?.updateStandardizedState) {
+ currState = controlPanel.updateStandardizedState(prevState, currState);
+ }
+
+ // 5. clear up
+ Object.entries(currState).forEach(([key, value]) => {
+ currState[key] = value.filter(Boolean);
+ });
+
+ return currState;
}
private getLatestFormData(vizType: string): QueryFormData {
- if (this.sfd.memorizedFormData.has(vizType)) {
- return this.sfd.memorizedFormData.get(vizType) as QueryFormData;
+ if (this.has(vizType)) {
+ return this.get(vizType);
}
return this.memorizedFormData.slice(-1)[0][1];
@@ -125,13 +177,21 @@ export class StandardizedFormData {
return Array.from(this.sfd.memorizedFormData.entries());
}
- dumpSFD() {
+ serialize() {
return {
standardizedState: this.standardizedState,
memorizedFormData: this.memorizedFormData,
};
}
+ has(vizType: string): boolean {
+ return this.sfd.memorizedFormData.has(vizType);
+ }
+
+ get(vizType: string): QueryFormData {
+ return this.sfd.memorizedFormData.get(vizType) as QueryFormData;
+ }
+
transform(
targetVizType: string,
exploreState: Record<string, any>,
@@ -162,7 +222,7 @@ export class StandardizedFormData {
});
const targetFormData = {
...getFormDataFromControls(targetControlsState),
- standardizedFormData: this.dumpSFD(),
+ standardizedFormData: this.serialize(),
};
const controlPanel = getChartControlPanelRegistry().get(targetVizType);