This is an automated email from the ASF dual-hosted git repository.
kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new b30f6a5db1 chore(explore): Get Explore data from endpoint instead of
bootstrap_data (#20519)
b30f6a5db1 is described below
commit b30f6a5db1c9d79d4837529b671090f4a908c056
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Thu Jun 30 21:06:51 2022 +0200
chore(explore): Get Explore data from endpoint instead of bootstrap_data
(#20519)
* feat(explore): Use v1/explore endpoint data instead of bootstrapData
* Add tests
* Fix ci
* Remove redundant dependency
* Use form_data_key in cypress tests
* Add auth headers to for data request
* Address comments
* Remove displaying danger toast
* Conditionally add auth headers
* Address comments
* Fix typing bug
* fix
* Fix opening dataset
* Fix sqllab chart create
* Run queries in parallel
* Fix dashboard id autofill
* Fix lint
* Fix test
---
.../cypress/integration/explore/chart.test.js | 2 +-
.../cypress/integration/explore/control.test.ts | 10 +-
.../integration/explore/explore.applitools.test.ts | 2 +-
.../cypress/integration/explore/filter_box.test.js | 2 +-
.../cypress/integration/explore/link.test.ts | 2 +-
.../explore/visualizations/area.test.js | 34 +++--
.../explore/visualizations/big_number.test.js | 6 +-
.../explore/visualizations/box_plot.test.js | 2 +-
.../explore/visualizations/bubble.test.js | 8 +-
.../explore/visualizations/compare.test.js | 2 +-
.../explore/visualizations/dist_bar.test.js | 8 +-
.../explore/visualizations/download_chart.test.js | 2 +-
.../explore/visualizations/dual_line.test.js | 4 +-
.../explore/visualizations/gauge.test.js | 2 +-
.../explore/visualizations/graph.test.ts | 2 +-
.../explore/visualizations/histogram.test.ts | 2 +-
.../explore/visualizations/line.test.ts | 38 +++---
.../integration/explore/visualizations/pie.test.js | 2 +-
.../explore/visualizations/pivot_table.test.js | 4 +-
.../explore/visualizations/sankey.test.js | 6 +-
.../explore/visualizations/shared.helper.js | 4 +-
.../explore/visualizations/sunburst.test.js | 2 +-
.../explore/visualizations/table.test.ts | 8 +-
.../explore/visualizations/time_table.js | 10 +-
.../explore/visualizations/treemap.test.js | 2 +-
.../explore/visualizations/world_map.test.js | 2 +-
.../cypress-base/cypress/support/index.ts | 46 ++++++-
.../superset-ui-chart-controls/src/types.ts | 10 ++
.../SqlLab/components/SaveDatasetModal/index.tsx | 75 +++++++----
superset-frontend/src/SqlLab/types.ts | 10 +-
.../src/addSlice/AddSliceContainer.test.tsx | 9 +-
.../src/addSlice/AddSliceContainer.tsx | 31 ++---
.../src/components/Chart/chartReducer.ts | 3 +-
superset-frontend/src/constants.ts | 4 +
superset-frontend/src/explore/App.jsx | 6 +-
superset-frontend/src/explore/ExplorePage.tsx | 68 ++++++++++
.../src/explore/actions/datasourcesActions.ts | 2 +-
.../src/explore/actions/hydrateExplore.test.ts | 92 +++++++++++++
.../src/explore/actions/hydrateExplore.ts | 146 +++++++++++++++++++++
.../explore/components/ControlPanelsContainer.tsx | 3 +-
.../DatasourcePanel/DatasourcePanel.test.tsx | 4 +-
.../components/ExploreViewContainer/index.jsx | 16 +--
.../controls/DatasourceControl/index.jsx | 2 +-
.../controls/VizTypeControl/FastVizSwitcher.tsx | 2 +-
.../exploreUtils/getParsedExploreURLParams.test.ts | 62 +++++++++
.../exploreUtils/getParsedExploreURLParams.ts | 117 +++++++++++++++++
.../src/explore/exploreUtils/index.js | 3 +-
superset-frontend/src/explore/fixtures.tsx | 59 ++++++++-
superset-frontend/src/explore/index.jsx | 22 +++-
.../src/explore/reducers/datasourcesReducer.ts | 6 +-
.../src/explore/reducers/exploreReducer.js | 7 +-
.../src/explore/reducers/getInitialState.ts | 146 ---------------------
.../src/explore/reducers/saveModalReducer.js | 4 +
superset-frontend/src/explore/store.js | 2 +-
superset-frontend/src/explore/types.ts | 47 ++++++-
55 files changed, 845 insertions(+), 327 deletions(-)
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/chart.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/chart.test.js
index c9f4a1c9f5..ca37bf9690 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/chart.test.js
+++ b/superset-frontend/cypress-base/cypress/integration/explore/chart.test.js
@@ -41,7 +41,7 @@ describe('No Results', () => {
],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.wait('@getJson').its('response.statusCode').should('eq', 200);
cy.get('div.chart-container').contains(
'No results were returned for this query',
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
index 18bf8859a8..95bdd514bf 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
@@ -148,7 +148,7 @@ describe('Time range filter', () => {
metrics: [NUM_METRIC],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@chartData' });
cy.get('[data-test=time-range-trigger]')
@@ -172,7 +172,7 @@ describe('Time range filter', () => {
time_range: 'Last year',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@chartData' });
cy.get('[data-test=time-range-trigger]')
@@ -192,7 +192,7 @@ describe('Time range filter', () => {
time_range: 'previous calendar month',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@chartData' });
cy.get('[data-test=time-range-trigger]')
@@ -212,7 +212,7 @@ describe('Time range filter', () => {
time_range: 'DATEADD(DATETIME("today"), -7, day) : today',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@chartData' });
cy.get('[data-test=time-range-trigger]')
@@ -235,7 +235,7 @@ describe('Time range filter', () => {
time_range: 'No filter',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@chartData' });
cy.get('[data-test=time-range-trigger]')
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/explore.applitools.test.ts
b/superset-frontend/cypress-base/cypress/integration/explore/explore.applitools.test.ts
index 96b0d66847..64d77b4d68 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/explore.applitools.test.ts
+++
b/superset-frontend/cypress-base/cypress/integration/explore/explore.applitools.test.ts
@@ -31,7 +31,7 @@ describe('explore view', () => {
it('should load Explore', () => {
const LINE_CHART_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'line' };
const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
cy.eyesOpen({
testName: 'Explore page',
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
index 921377c45f..b9844274e2 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
@@ -22,7 +22,7 @@ describe('Edit FilterBox Chart', () => {
const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'filter_box' };
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts
b/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts
index 9f07e9c10b..fb3445fc63 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts
@@ -74,7 +74,7 @@ describe('Test explore links', () => {
};
const newChartName = `Test chart [${shortid.generate()}]`;
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@tableChartData' });
cy.url().then(() => {
cy.get('[data-test="query-save-button"]').click();
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.test.js
index 86b5a789c2..59b8beabe3 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.test.js
@@ -51,7 +51,7 @@ describe('Visualization > Area', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
@@ -75,23 +75,21 @@ describe('Visualization > Area', () => {
});
it('should work with groupby and filter', () => {
- cy.visitChartByParams(
- JSON.stringify({
- ...AREA_FORM_DATA,
- groupby: ['region'],
- adhoc_filters: [
- {
- expressionType: 'SIMPLE',
- subject: 'region',
- operator: 'IN',
- comparator: ['South Asia', 'North America'],
- clause: 'WHERE',
- sqlExpression: null,
- filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo',
- },
- ],
- }),
- );
+ cy.visitChartByParams({
+ ...AREA_FORM_DATA,
+ groupby: ['region'],
+ adhoc_filters: [
+ {
+ expressionType: 'SIMPLE',
+ subject: 'region',
+ operator: 'IN',
+ comparator: ['South Asia', 'North America'],
+ clause: 'WHERE',
+ sqlExpression: null,
+ filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo',
+ },
+ ],
+ });
cy.wait('@getJson').then(async ({ response }) => {
const responseBody = response?.body;
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.test.js
index 30e7716b73..ede7ed47f9 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number.test.js
@@ -25,11 +25,11 @@ describe('Visualization > Big Number with Trendline', () =>
{
slice_id: 42,
granularity_sqla: 'year',
time_grain_sqla: 'P1D',
- time_range: '2000+:+2014-01-02',
+ time_range: '2000 : 2014-01-02',
metric: 'sum__SP_POP_TOTL',
adhoc_filters: [],
compare_lag: '10',
- compare_suffix: 'over+10Y',
+ compare_suffix: 'over 10Y',
y_axis_format: '.3s',
show_trend_line: true,
start_y_axis_at_zero: true,
@@ -42,7 +42,7 @@ describe('Visualization > Big Number with Trendline', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({
waitAlias: '@chartData',
chartSelector: '.superset-legacy-chart-big-number',
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.test.js
index 432815b869..6a4afba97a 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.test.js
@@ -33,7 +33,7 @@ describe('Visualization > Box Plot', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.test.js
index 9bd91f37c5..9c824e5d9f 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.test.js
@@ -23,7 +23,7 @@ describe('Visualization > Bubble', () => {
slice_id: 46,
granularity_sqla: 'year',
time_grain_sqla: 'P1D',
- time_range: '2011-01-01+:+2011-01-02',
+ time_range: '2011-01-01 : 2011-01-02',
series: 'region',
entity: 'country_name',
x: 'sum__SP_RUR_TOTL_ZS',
@@ -47,7 +47,7 @@ describe('Visualization > Bubble', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
@@ -60,7 +60,7 @@ describe('Visualization > Bubble', () => {
// Since main functionality is already covered in filter test below,
// skip this test until we find a solution.
it.skip('should work', () => {
- cy.visitChartByParams(JSON.stringify(BUBBLE_FORM_DATA)).then(() => {
+ cy.visitChartByParams(BUBBLE_FORM_DATA).then(() => {
cy.wait('@getJson').then(xhr => {
let expectedBubblesNumber = 0;
xhr.responseBody.data.forEach(element => {
@@ -86,7 +86,7 @@ describe('Visualization > Bubble', () => {
expressionType: 'SIMPLE',
subject: 'region',
operator: '==',
- comparator: 'South+Asia',
+ comparator: 'South Asia',
clause: 'WHERE',
sqlExpression: null,
filterOptionName: 'filter_b2tfg1rs8y_8kmrcyxvsqd',
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.test.js
index 83b37f889f..35f56754a8 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.test.js
@@ -47,7 +47,7 @@ describe('Visualization > Compare', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.test.js
index bec718367e..6bd7c82f43 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dist_bar.test.js
@@ -33,7 +33,7 @@ describe('Visualization > Distribution bar chart', () => {
groupby: ['state'],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({
waitAlias: '@getJson',
querySubstring: NUM_METRIC.label,
@@ -49,7 +49,7 @@ describe('Visualization > Distribution bar chart', () => {
columns: ['gender'],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -61,7 +61,7 @@ describe('Visualization > Distribution bar chart', () => {
row_limit: 10,
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -74,7 +74,7 @@ describe('Visualization > Distribution bar chart', () => {
contribution: true,
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
});
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/download_chart.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/download_chart.test.js
index ce4a871f8e..029ead3110 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/download_chart.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/download_chart.test.js
@@ -33,7 +33,7 @@ describe('Download Chart > Distribution bar chart', () => {
groupby: ['state'],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.get('.header-with-actions .ant-dropdown-trigger').click();
cy.get(':nth-child(1) > .ant-dropdown-menu-submenu-title').click();
cy.get(
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.test.js
index 641b2925d7..3a3eb334fb 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.test.js
@@ -23,7 +23,7 @@ describe('Visualization > Dual Line', () => {
slice_id: 58,
granularity_sqla: 'ds',
time_grain_sqla: 'P1D',
- time_range: '100+years+ago+:+now',
+ time_range: '100 years ago : now',
color_scheme: 'bnbColors',
x_axis_format: 'smart_date',
metric: 'sum__num',
@@ -35,7 +35,7 @@ describe('Visualization > Dual Line', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/gauge.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/gauge.test.js
index 8b5b2ffd0b..c4735b7b8a 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/gauge.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/gauge.test.js
@@ -27,7 +27,7 @@ describe('Visualization > Gauge', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/graph.test.ts
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/graph.test.ts
index 47adb075bd..c01d9c5099 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/graph.test.ts
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/graph.test.ts
@@ -46,7 +46,7 @@ describe('Visualization > Graph', () => {
function verify(formData: {
[name: string]: string | boolean | number | Array<adhocFilter>;
}): void {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.test.ts
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.test.ts
index 67cbba3f96..ff6355319a 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.test.ts
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.test.ts
@@ -39,7 +39,7 @@ describe('Visualization > Histogram', () => {
};
function verify(formData: QueryFormData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
index e8998b4bef..da20cfab85 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
@@ -28,7 +28,7 @@ describe('Visualization > Line', () => {
it('should show validator error when no metric', () => {
const formData = { ...LINE_CHART_DEFAULTS, metrics: [] };
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.get('.panel-body').contains(
`Add required control values to preview chart`,
);
@@ -36,7 +36,7 @@ describe('Visualization > Line', () => {
it('should not show validator error when metric added', () => {
const formData = { ...LINE_CHART_DEFAULTS, metrics: [] };
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.get('.panel-body').contains(
`Add required control values to preview chart`,
);
@@ -61,7 +61,7 @@ describe('Visualization > Line', () => {
it('should allow negative values in Y bounds', () => {
const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.get('#controlSections-tab-display').click();
cy.get('span').contains('Y Axis Bounds').scrollIntoView();
cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 });
@@ -81,7 +81,7 @@ describe('Visualization > Line', () => {
it('should work with adhoc metric', () => {
const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -89,7 +89,7 @@ describe('Visualization > Line', () => {
const metrics = ['count'];
const groupby = ['gender'];
const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby };
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -101,7 +101,7 @@ describe('Visualization > Line', () => {
metrics,
adhoc_filters: filters,
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -113,7 +113,7 @@ describe('Visualization > Line', () => {
groupby: ['name'],
timeseries_limit_metric: NUM_METRIC,
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -126,7 +126,7 @@ describe('Visualization > Line', () => {
timeseries_limit_metric: NUM_METRIC,
order_desc: true,
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -138,7 +138,7 @@ describe('Visualization > Line', () => {
rolling_type: 'mean',
rolling_periods: 10,
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -147,12 +147,12 @@ describe('Visualization > Line', () => {
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
- time_compare: ['1+year'],
+ time_compare: ['1 year'],
comparison_type: 'values',
groupby: ['gender'],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
// Offset color should match original line color
@@ -190,10 +190,10 @@ describe('Visualization > Line', () => {
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
- time_compare: ['1+year'],
+ time_compare: ['1 year'],
comparison_type: 'ratio',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -202,10 +202,10 @@ describe('Visualization > Line', () => {
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
- time_compare: ['1+year'],
+ time_compare: ['1 year'],
comparison_type: 'percentage',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -214,7 +214,7 @@ describe('Visualization > Line', () => {
...LINE_CHART_DEFAULTS,
metrics: ['count'],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
cy.get('text.nv-legend-text').contains('COUNT(*)');
});
@@ -225,7 +225,7 @@ describe('Visualization > Line', () => {
metrics: ['count'],
annotation_layers: [
{
- name: 'Goal+line',
+ name: 'Goal line',
annotationType: 'FORMULA',
sourceType: '',
value: 'y=140000',
@@ -245,7 +245,7 @@ describe('Visualization > Line', () => {
},
],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
cy.get('.slice_container').within(() => {
// Goal line annotation doesn't show up in legend
@@ -281,7 +281,7 @@ describe('Visualization > Line', () => {
},
],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
},
);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.test.js
index fb083de615..3b28128e68 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.test.js
@@ -37,7 +37,7 @@ describe('Visualization > Pie', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.test.js
index 14de08da79..ef62beb381 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.test.js
@@ -23,7 +23,7 @@ describe('Visualization > Pivot Table', () => {
slice_id: 61,
granularity_sqla: 'ds',
time_grain_sqla: 'P1D',
- time_range: '100+years+ago+:+now',
+ time_range: '100 years ago : now',
metrics: ['sum__num'],
adhoc_filters: [],
groupby: ['name'],
@@ -54,7 +54,7 @@ describe('Visualization > Pivot Table', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.test.js
index 257ec00c1f..747e8a06f2 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.test.js
@@ -24,7 +24,7 @@ describe('Visualization > Sankey', () => {
url_params: {},
granularity_sqla: null,
time_grain_sqla: 'P1D',
- time_range: 'Last+week',
+ time_range: 'Last week',
groupby: ['source', 'target'],
metric: 'sum__value',
adhoc_filters: [],
@@ -33,7 +33,7 @@ describe('Visualization > Sankey', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
@@ -53,7 +53,7 @@ describe('Visualization > Sankey', () => {
adhoc_filters: [
{
expressionType: 'SQL',
- sqlExpression: 'SUM(value)+>+0',
+ sqlExpression: 'SUM(value) > 0',
clause: 'HAVING',
subject: null,
operator: null,
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/shared.helper.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/shared.helper.js
index 78a659fc91..bfd50e66d3 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/shared.helper.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/shared.helper.js
@@ -24,7 +24,7 @@ export const FORM_DATA_DEFAULTS = {
datasource: '3__table',
granularity_sqla: 'ds',
time_grain_sqla: null,
- time_range: '100+years+ago+:+now',
+ time_range: '100 years ago : now',
adhoc_filters: [],
groupby: [],
limit: null,
@@ -37,7 +37,7 @@ export const HEALTH_POP_FORM_DATA_DEFAULTS = {
datasource: '2__table',
granularity_sqla: 'ds',
time_grain_sqla: 'P1D',
- time_range: '1960-01-01+:+2014-01-02',
+ time_range: '1960-01-01 : 2014-01-02',
};
export const NUM_METRIC = {
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.test.js
index 99cbb1e407..e7ccacac03 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.test.js
@@ -32,7 +32,7 @@ describe('Visualization > Sunburst', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
index 6361d93d18..f11b23d595 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
@@ -174,7 +174,7 @@ describe('Visualization > Table', () => {
groupby: ['name'],
row_limit: limit,
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.wait('@chartData').then(({ response }) => {
cy.verifySliceContainer('table');
expect(response?.body.result[0].data.length).to.eq(limit);
@@ -219,7 +219,7 @@ describe('Visualization > Table', () => {
order_by_cols: ['["num", false]'],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.wait('@chartData').then(({ response }) => {
cy.verifySliceContainer('table');
const records = response?.body.result[0].data;
@@ -233,7 +233,7 @@ describe('Visualization > Table', () => {
const formData = { ...VIZ_DEFAULTS, metrics, adhoc_filters: filters };
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@chartData', chartSelector: 'table' });
});
@@ -244,7 +244,7 @@ describe('Visualization > Table', () => {
groupby: ['state'],
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({
waitAlias: '@chartData',
querySubstring: /group by.*state/i,
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js
index 7da9002785..eb81d17a3b 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/time_table.js
@@ -33,7 +33,7 @@ describe('Visualization > Time TableViz', () => {
column_collection: [
{
key: '9g4K-B-YL',
- label: 'Last+Year',
+ label: 'Last Year',
colType: 'time',
timeLag: '1',
comparisonType: 'value',
@@ -42,7 +42,7 @@ describe('Visualization > Time TableViz', () => {
url: '',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({
waitAlias: '@getJson',
querySubstring: NUM_METRIC.label,
@@ -61,7 +61,7 @@ describe('Visualization > Time TableViz', () => {
column_collection: [
{
key: '9g4K-B-YL',
- label: 'Last+Year',
+ label: 'Last Year',
colType: 'time',
timeLag: '1',
comparisonType: 'value',
@@ -70,7 +70,7 @@ describe('Visualization > Time TableViz', () => {
url: '',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({
waitAlias: '@getJson',
querySubstring: NUM_METRIC.label,
@@ -107,7 +107,7 @@ describe('Visualization > Time TableViz', () => {
url: '',
};
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({
waitAlias: '@getJson',
querySubstring: NUM_METRIC.label,
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.test.js
index 6ebe06274f..1be85e9e4c 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.test.js
@@ -38,7 +38,7 @@ describe('Visualization > Treemap', () => {
const level2 = '.chart-container rect[style="fill: rgb(0, 122, 135);"]';
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
diff --git
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.test.js
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.test.js
index ed9d3e4214..0fab65519d 100644
---
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.test.js
+++
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.test.js
@@ -35,7 +35,7 @@ describe('Visualization > World Map', () => {
};
function verify(formData) {
- cy.visitChartByParams(JSON.stringify(formData));
+ cy.visitChartByParams(formData);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
}
diff --git a/superset-frontend/cypress-base/cypress/support/index.ts
b/superset-frontend/cypress-base/cypress/support/index.ts
index 38e1ad5f6c..9d77f98acc 100644
--- a/superset-frontend/cypress-base/cypress/support/index.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.ts
@@ -55,12 +55,46 @@ Cypress.Commands.add('visitChartById', chartId =>
cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${chartId}}`),
);
-Cypress.Commands.add('visitChartByParams', params => {
- const queryString =
- typeof params === 'string' ? params : JSON.stringify(params);
- const url = `${BASE_EXPLORE_URL}${queryString}`;
- return cy.visit(url);
-});
+Cypress.Commands.add(
+ 'visitChartByParams',
+ (formData: {
+ datasource?: string;
+ datasource_id?: number;
+ datasource_type?: string;
+ [key: string]: unknown;
+ }) => {
+ let datasource_id;
+ let datasource_type;
+ if (formData.datasource_id && formData.datasource_type) {
+ ({ datasource_id, datasource_type } = formData);
+ } else {
+ [datasource_id, datasource_type] = formData.datasource?.split('__') ||
[];
+ }
+ const accessToken = window.localStorage.getItem('access_token');
+ cy.request({
+ method: 'POST',
+ url: 'api/v1/explore/form_data',
+ body: {
+ datasource_id,
+ datasource_type,
+ form_data: JSON.stringify(formData),
+ },
+ headers: {
+ ...(accessToken && {
+ Cookie: `csrf_access_token=${accessToken}`,
+ 'X-CSRFToken': accessToken,
+ }),
+ ...(TokenName && { Authorization: `Bearer ${TokenName}` }),
+ 'Content-Type': 'application/json',
+ Referer: `${Cypress.config().baseUrl}/`,
+ },
+ }).then(response => {
+ const formDataKey = response.body.key;
+ const url = `/superset/explore/?form_data_key=${formDataKey}`;
+ cy.visit(url);
+ });
+ },
+);
Cypress.Commands.add('verifySliceContainer', chartSelector => {
// After a wait response check for valid slice container
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 1de492d518..5ae2f26c4f 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
@@ -50,6 +50,14 @@ export type SharedControlComponents = typeof
sharedControlComponents;
/** ----------------------------------------------
* Input data/props while rendering
* ---------------------------------------------*/
+export interface Owner {
+ first_name: string;
+ id: number;
+ last_name: string;
+ username: string;
+ email?: string;
+}
+
export type ColumnMeta = Omit<Column, 'id'> & {
id?: number;
} & AnyDict;
@@ -67,8 +75,10 @@ export interface Dataset {
time_grain_sqla?: string;
granularity_sqla?: string;
datasource_name: string | null;
+ name?: string;
description: string | null;
uid?: string;
+ owners?: Owner[];
}
export interface ControlPanelState {
diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
index 9a8366ba56..4a87d52c18 100644
--- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
@@ -46,8 +46,11 @@ import {
SqlLabExploreRootState,
getInitialState,
ExploreDatasource,
+ SqlLabRootState,
} from 'src/SqlLab/types';
-import { exploreChart } from 'src/explore/exploreUtils';
+import { mountExploreUrl } from 'src/explore/exploreUtils';
+import { postFormData } from 'src/explore/exploreUtils/formData';
+import { URL_PARAMS } from 'src/constants';
interface SaveDatasetModalProps {
visible: boolean;
@@ -115,6 +118,9 @@ export const SaveDatasetModal:
FunctionComponent<SaveDatasetModalProps> = ({
modalDescription,
datasource,
}) => {
+ const defaultVizType = useSelector<SqlLabRootState, string>(
+ state => state.common?.conf?.DEFAULT_VIZ_TYPE || 'table',
+ );
const query = datasource as QueryResponse;
const getDefaultDatasetName = () =>
`${query.tab} ${moment().format('MM/DD/YYYY HH:mm:ss')}`;
@@ -137,30 +143,40 @@ export const SaveDatasetModal:
FunctionComponent<SaveDatasetModalProps> = ({
const dispatch = useDispatch<(dispatch: any) => Promise<JsonObject>>();
const handleOverwriteDataset = async () => {
- await updateDataset(
- query.dbId,
- datasetToOverwrite.datasetId,
- query.sql,
- query.results.selected_columns.map(
- (d: { name: string; type: string; is_dttm: boolean }) => ({
- column_name: d.name,
- type: d.type,
- is_dttm: d.is_dttm,
- }),
+ const [, key] = await Promise.all([
+ updateDataset(
+ query.dbId,
+ datasetToOverwrite.datasetId,
+ query.sql,
+ query.results.selected_columns.map(
+ (d: { name: string; type: string; is_dttm: boolean }) => ({
+ column_name: d.name,
+ type: d.type,
+ is_dttm: d.is_dttm,
+ }),
+ ),
+ datasetToOverwrite.owners.map((o: DatasetOwner) => o.id),
+ true,
),
- datasetToOverwrite.owners.map((o: DatasetOwner) => o.id),
- true,
- );
+ postFormData(datasetToOverwrite.datasetId, 'table', {
+ ...EXPLORE_CHART_DEFAULT,
+ datasource: `${datasetToOverwrite.datasetId}__table`,
+ ...(defaultVizType === 'table' && {
+ all_columns: query.results.selected_columns.map(
+ column => column.name,
+ ),
+ }),
+ }),
+ ]);
+
+ const url = mountExploreUrl(null, {
+ [URL_PARAMS.formDataKey.name]: key,
+ });
+ window.open(url, '_blank', 'noreferrer');
setShouldOverwriteDataset(false);
setDatasetToOverwrite({});
setDatasetName(getDefaultDatasetName());
-
- exploreChart({
- ...EXPLORE_CHART_DEFAULT,
- datasource: `${datasetToOverwrite.datasetId}__table`,
- selected_columns: query.results.selected_columns,
- });
};
const getUserDatasets = async (searchText = '') => {
@@ -235,15 +251,20 @@ export const SaveDatasetModal:
FunctionComponent<SaveDatasetModalProps> = ({
columns: selectedColumns,
}),
)
- .then((data: { table_id: number }) => {
- exploreChart({
+ .then((data: { table_id: number }) =>
+ postFormData(data.table_id, 'table', {
+ ...EXPLORE_CHART_DEFAULT,
datasource: `${data.table_id}__table`,
- metrics: [],
- groupby: [],
- time_range: 'No filter',
- selectedColumns,
- row_limit: 1000,
+ ...(defaultVizType === 'table' && {
+ all_columns: selectedColumns.map(column => column.name),
+ }),
+ }),
+ )
+ .then((key: string) => {
+ const url = mountExploreUrl(null, {
+ [URL_PARAMS.formDataKey.name]: key,
});
+ window.open(url, '_blank', 'noreferrer');
})
.catch(() => {
addDangerToast(t('An error occurred saving dataset'));
diff --git a/superset-frontend/src/SqlLab/types.ts
b/superset-frontend/src/SqlLab/types.ts
index 9a6198b864..1b7b1d495c 100644
--- a/superset-frontend/src/SqlLab/types.ts
+++ b/superset-frontend/src/SqlLab/types.ts
@@ -16,11 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { Dataset } from '@superset-ui/chart-controls';
+import { JsonObject, Query, QueryResponse } from '@superset-ui/core';
import { SupersetError } from 'src/components/ErrorMessage/types';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import { ToastType } from 'src/components/MessageToasts/types';
-import { Dataset } from '@superset-ui/chart-controls';
-import { Query, QueryResponse } from '@superset-ui/core';
import { ExploreRootState } from 'src/explore/types';
export type ExploreDatasource = Dataset | QueryResponse;
@@ -68,7 +68,10 @@ export type SqlLabRootState = {
};
localStorageUsageInKilobytes: number;
messageToasts: toastState[];
- common: {};
+ common: {
+ flash_messages: string[];
+ conf: JsonObject;
+ };
};
export type SqlLabExploreRootState = SqlLabRootState | ExploreRootState;
@@ -96,6 +99,7 @@ export const EXPLORE_CHART_DEFAULT = {
metrics: [],
groupby: [],
time_range: 'No filter',
+ row_limit: 1000,
};
export interface DatasetOwner {
diff --git a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx
b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx
index f05a349ad7..bc58e925e4 100644
--- a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx
+++ b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx
@@ -104,7 +104,7 @@ test('renders an enabled button if datasource and viz type
are selected', async
const wrapper = await getWrapper();
wrapper.setState({
datasource,
- visType: 'table',
+ vizType: 'table',
});
expect(
wrapper.find(Button).find({ disabled: true }).hostNodes(),
@@ -125,7 +125,7 @@ test('double-click viz type submits if datasource is
selected', async () => {
wrapper.update();
wrapper.setState({
datasource,
- visType: 'table',
+ vizType: 'table',
});
wrapper.instance().onVizTypeDoubleClick();
@@ -136,9 +136,8 @@ test('formats Explore url', async () => {
const wrapper = await getWrapper();
wrapper.setState({
datasource,
- visType: 'table',
+ vizType: 'table',
});
- const formattedUrl =
-
'/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221%22%7D';
+ const formattedUrl = '/superset/explore/?viz_type=table&datasource=1';
expect(wrapper.instance().exploreUrl()).toBe(formattedUrl);
});
diff --git a/superset-frontend/src/addSlice/AddSliceContainer.tsx
b/superset-frontend/src/addSlice/AddSliceContainer.tsx
index a99c75e2af..71981fdef9 100644
--- a/superset-frontend/src/addSlice/AddSliceContainer.tsx
+++ b/superset-frontend/src/addSlice/AddSliceContainer.tsx
@@ -45,7 +45,7 @@ export type AddSliceContainerProps = {
export type AddSliceContainerState = {
datasource?: { label: string; value: string };
- visType: string | null;
+ vizType: string | null;
canCreateDataset: boolean;
};
@@ -208,7 +208,7 @@ export default class AddSliceContainer extends
React.PureComponent<
constructor(props: AddSliceContainerProps) {
super(props);
this.state = {
- visType: null,
+ vizType: null,
canCreateDataset: findPermission(
'can_write',
'Dataset',
@@ -217,7 +217,7 @@ export default class AddSliceContainer extends
React.PureComponent<
};
this.changeDatasource = this.changeDatasource.bind(this);
- this.changeVisType = this.changeVisType.bind(this);
+ this.changeVizType = this.changeVizType.bind(this);
this.gotoSlice = this.gotoSlice.bind(this);
this.newLabel = this.newLabel.bind(this);
this.loadDatasources = this.loadDatasources.bind(this);
@@ -226,14 +226,11 @@ export default class AddSliceContainer extends
React.PureComponent<
exploreUrl() {
const dashboardId = getUrlParam(URL_PARAMS.dashboardId);
- const formData = encodeURIComponent(
- JSON.stringify({
- viz_type: this.state.visType,
- datasource: this.state.datasource?.value,
- ...(!isNullish(dashboardId) && { dashboardId }),
- }),
- );
- return `/superset/explore/?form_data=${formData}`;
+ let url =
`/superset/explore/?viz_type=${this.state.vizType}&datasource=${this.state.datasource?.value}`;
+ if (!isNullish(dashboardId)) {
+ url += `&dashboard_id=${dashboardId}`;
+ }
+ return url;
}
gotoSlice() {
@@ -244,12 +241,12 @@ export default class AddSliceContainer extends
React.PureComponent<
this.setState({ datasource });
}
- changeVisType(visType: string | null) {
- this.setState({ visType });
+ changeVizType(vizType: string | null) {
+ this.setState({ vizType });
}
isBtnDisabled() {
- return !(this.state.datasource?.value && this.state.visType);
+ return !(this.state.datasource?.value && this.state.vizType);
}
onVizTypeDoubleClick() {
@@ -369,14 +366,14 @@ export default class AddSliceContainer extends
React.PureComponent<
/>
<Steps.Step
title={<StyledStepTitle>{t('Choose chart type')}</StyledStepTitle>}
- status={this.state.visType ? 'finish' : 'process'}
+ status={this.state.vizType ? 'finish' : 'process'}
description={
<StyledStepDescription>
<VizTypeGallery
className="viz-gallery"
- onChange={this.changeVisType}
+ onChange={this.changeVizType}
onDoubleClick={this.onVizTypeDoubleClick}
- selectedViz={this.state.visType}
+ selectedViz={this.state.vizType}
/>
</StyledStepDescription>
}
diff --git a/superset-frontend/src/components/Chart/chartReducer.ts
b/superset-frontend/src/components/Chart/chartReducer.ts
index 010140584c..11b498290f 100644
--- a/superset-frontend/src/components/Chart/chartReducer.ts
+++ b/superset-frontend/src/components/Chart/chartReducer.ts
@@ -22,6 +22,7 @@ import { HYDRATE_DASHBOARD } from
'src/dashboard/actions/hydrate';
import { DatasourcesAction } from 'src/dashboard/actions/datasources';
import { ChartState } from 'src/explore/types';
import { getFormDataFromControls } from 'src/explore/controlUtils';
+import { HYDRATE_EXPLORE } from 'src/explore/actions/hydrateExplore';
import { now } from 'src/utils/dates';
import * as actions from './chartAction';
@@ -194,7 +195,7 @@ export default function chartReducer(
delete charts[key];
return charts;
}
- if (action.type === HYDRATE_DASHBOARD) {
+ if (action.type === HYDRATE_DASHBOARD || action.type === HYDRATE_EXPLORE) {
return { ...action.data.charts };
}
if (action.type === DatasourcesAction.SET_DATASOURCES) {
diff --git a/superset-frontend/src/constants.ts
b/superset-frontend/src/constants.ts
index 60668ddcb8..84e809532e 100644
--- a/superset-frontend/src/constants.ts
+++ b/superset-frontend/src/constants.ts
@@ -91,6 +91,10 @@ export const URL_PARAMS = {
name: 'permalink_key',
type: 'string',
},
+ vizType: {
+ name: 'viz_type',
+ type: 'string',
+ },
} as const;
export const RESERVED_CHART_URL_PARAMS: string[] = [
diff --git a/superset-frontend/src/explore/App.jsx
b/superset-frontend/src/explore/App.jsx
index c440b784e7..995cf3d9e9 100644
--- a/superset-frontend/src/explore/App.jsx
+++ b/superset-frontend/src/explore/App.jsx
@@ -27,10 +27,10 @@ import { DynamicPluginProvider } from
'src/components/DynamicPlugins';
import ToastContainer from 'src/components/MessageToasts/ToastContainer';
import setupApp from 'src/setup/setupApp';
import setupPlugins from 'src/setup/setupPlugins';
+import { theme } from 'src/preamble';
+import { ExplorePage } from './ExplorePage';
import './main.less';
import '../assets/stylesheets/reactable-pagination.less';
-import { theme } from 'src/preamble';
-import ExploreViewContainer from './components/ExploreViewContainer';
setupApp();
setupPlugins();
@@ -41,7 +41,7 @@ const App = ({ store }) => (
<ThemeProvider theme={theme}>
<GlobalStyles />
<DynamicPluginProvider>
- <ExploreViewContainer />
+ <ExplorePage />
<ToastContainer />
</DynamicPluginProvider>
</ThemeProvider>
diff --git a/superset-frontend/src/explore/ExplorePage.tsx
b/superset-frontend/src/explore/ExplorePage.tsx
new file mode 100644
index 0000000000..50982924ee
--- /dev/null
+++ b/superset-frontend/src/explore/ExplorePage.tsx
@@ -0,0 +1,68 @@
+/**
+ * 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, { useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { makeApi, t } from '@superset-ui/core';
+import Loading from 'src/components/Loading';
+import { getParsedExploreURLParams } from
'./exploreUtils/getParsedExploreURLParams';
+import { hydrateExplore } from './actions/hydrateExplore';
+import ExploreViewContainer from './components/ExploreViewContainer';
+import { ExploreResponsePayload } from './types';
+import { fallbackExploreInitialData } from './fixtures';
+import { addDangerToast } from '../components/MessageToasts/actions';
+import { isNullish } from '../utils/common';
+
+const loadErrorMessage = t('Failed to load chart data.');
+
+const fetchExploreData = () => {
+ const exploreUrlParams = getParsedExploreURLParams();
+ return makeApi<{}, ExploreResponsePayload>({
+ method: 'GET',
+ endpoint: 'api/v1/explore/',
+ })(exploreUrlParams);
+};
+
+export const ExplorePage = () => {
+ const [isLoaded, setIsLoaded] = useState(false);
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ fetchExploreData()
+ .then(({ result }) => {
+ if (isNullish(result.dataset?.id) && isNullish(result.dataset?.uid)) {
+ dispatch(hydrateExplore(fallbackExploreInitialData));
+ dispatch(addDangerToast(loadErrorMessage));
+ } else {
+ dispatch(hydrateExplore(result));
+ }
+ })
+ .catch(() => {
+ dispatch(hydrateExplore(fallbackExploreInitialData));
+ dispatch(addDangerToast(loadErrorMessage));
+ })
+ .finally(() => {
+ setIsLoaded(true);
+ });
+ }, [dispatch]);
+
+ if (!isLoaded) {
+ return <Loading />;
+ }
+ return <ExploreViewContainer />;
+};
diff --git a/superset-frontend/src/explore/actions/datasourcesActions.ts
b/superset-frontend/src/explore/actions/datasourcesActions.ts
index baf6f75ccc..4fc3bce96a 100644
--- a/superset-frontend/src/explore/actions/datasourcesActions.ts
+++ b/superset-frontend/src/explore/actions/datasourcesActions.ts
@@ -20,7 +20,7 @@
import { Dispatch } from 'redux';
import { Dataset } from '@superset-ui/chart-controls';
import { updateFormDataByDatasource } from './exploreActions';
-import { ExplorePageState } from '../reducers/getInitialState';
+import { ExplorePageState } from '../types';
export const SET_DATASOURCE = 'SET_DATASOURCE';
export interface SetDatasource {
diff --git a/superset-frontend/src/explore/actions/hydrateExplore.test.ts
b/superset-frontend/src/explore/actions/hydrateExplore.test.ts
new file mode 100644
index 0000000000..9cc7b883e9
--- /dev/null
+++ b/superset-frontend/src/explore/actions/hydrateExplore.test.ts
@@ -0,0 +1,92 @@
+/**
+ * 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 { hydrateExplore, HYDRATE_EXPLORE } from './hydrateExplore';
+import { exploreInitialData } from '../fixtures';
+
+test('creates hydrate action from initial data', () => {
+ const dispatch = jest.fn();
+ const getState = jest.fn(() => ({
+ user: {},
+ charts: {},
+ datasources: {},
+ common: {},
+ explore: {},
+ }));
+ // ignore type check - we dont need exact explore state for this test
+ // @ts-ignore
+ hydrateExplore(exploreInitialData)(dispatch, getState);
+ expect(dispatch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: HYDRATE_EXPLORE,
+ data: {
+ charts: {
+ 371: {
+ id: 371,
+ chartAlert: null,
+ chartStatus: null,
+ chartStackTrace: null,
+ chartUpdateEndTime: null,
+ chartUpdateStartTime: 0,
+ latestQueryFormData: {
+ cache_timeout: undefined,
+ datasource: '8__table',
+ slice_id: 371,
+ url_params: undefined,
+ viz_type: 'table',
+ },
+ sliceFormData: {
+ cache_timeout: undefined,
+ datasource: '8__table',
+ slice_id: 371,
+ url_params: undefined,
+ viz_type: 'table',
+ },
+ queryController: null,
+ queriesResponse: null,
+ triggerQuery: false,
+ lastRendered: 0,
+ },
+ },
+ datasources: {
+ '8__table': exploreInitialData.dataset,
+ },
+ saveModal: {
+ dashboards: [],
+ saveModalAlert: null,
+ },
+ explore: {
+ can_add: false,
+ can_download: false,
+ can_overwrite: false,
+ isDatasourceMetaLoading: false,
+ isStarred: false,
+ triggerRender: false,
+ datasource: exploreInitialData.dataset,
+ controls: expect.any(Object),
+ form_data: exploreInitialData.form_data,
+ slice: exploreInitialData.slice,
+ controlsTransferred: [],
+ standalone: null,
+ force: null,
+ },
+ },
+ }),
+ );
+});
diff --git a/superset-frontend/src/explore/actions/hydrateExplore.ts
b/superset-frontend/src/explore/actions/hydrateExplore.ts
new file mode 100644
index 0000000000..ec49c0cdd5
--- /dev/null
+++ b/superset-frontend/src/explore/actions/hydrateExplore.ts
@@ -0,0 +1,146 @@
+/**
+ * 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 { ControlStateMapping } from '@superset-ui/chart-controls';
+
+import {
+ ChartState,
+ ExplorePageInitialData,
+ ExplorePageState,
+} from 'src/explore/types';
+import { getChartKey } from 'src/explore/exploreUtils';
+import { getControlsState } from 'src/explore/store';
+import { Dispatch } from 'redux';
+import { ensureIsArray } from '@superset-ui/core';
+import {
+ getFormDataFromControls,
+ applyMapStateToPropsToControl,
+} from 'src/explore/controlUtils';
+import { getDatasourceUid } from 'src/utils/getDatasourceUid';
+import { getUrlParam } from 'src/utils/urlUtils';
+import { URL_PARAMS } from 'src/constants';
+import { findPermission } from 'src/utils/findPermission';
+
+export const HYDRATE_EXPLORE = 'HYDRATE_EXPLORE';
+export const hydrateExplore =
+ ({ form_data, slice, dataset }: ExplorePageInitialData) =>
+ (dispatch: Dispatch, getState: () => ExplorePageState) => {
+ const { user, datasources, charts, sliceEntities, common } = getState();
+
+ const sliceId = getUrlParam(URL_PARAMS.sliceId);
+ const dashboardId = getUrlParam(URL_PARAMS.dashboardId);
+ const fallbackSlice = sliceId ? sliceEntities?.slices?.[sliceId] : null;
+ const initialSlice = slice ?? fallbackSlice;
+ const initialFormData = form_data ?? initialSlice?.form_data;
+ if (!initialFormData.viz_type) {
+ const defaultVizType = common?.conf.DEFAULT_VIZ_TYPE || 'table';
+ initialFormData.viz_type =
+ getUrlParam(URL_PARAMS.vizType) || defaultVizType;
+ }
+ if (dashboardId) {
+ initialFormData.dashboardId = dashboardId;
+ }
+ const initialDatasource =
+ datasources?.[initialFormData.datasource] ?? dataset;
+
+ const initialExploreState = {
+ form_data: initialFormData,
+ slice: initialSlice,
+ datasource: initialDatasource,
+ };
+ const initialControls = getControlsState(
+ initialExploreState,
+ initialFormData,
+ ) as ControlStateMapping;
+
+ const exploreState = {
+ // note this will add `form_data` to state,
+ // which will be manipulable by future reducers.
+ can_add: findPermission('can_write', 'Chart', user?.roles),
+ can_download: findPermission('can_csv', 'Superset', user?.roles),
+ can_overwrite: ensureIsArray(slice?.owners).includes(
+ user?.userId as number,
+ ),
+ isDatasourceMetaLoading: false,
+ isStarred: false,
+ triggerRender: false,
+ // duplicate datasource in exploreState - it's needed by getControlsState
+ datasource: initialDatasource,
+ // Initial control state will skip `control.mapStateToProps`
+ // because `bootstrapData.controls` is undefined.
+ controls: initialControls,
+ form_data: initialFormData,
+ slice: initialSlice,
+ controlsTransferred: [],
+ standalone: getUrlParam(URL_PARAMS.standalone),
+ force: getUrlParam(URL_PARAMS.force),
+ };
+
+ // apply initial mapStateToProps for all controls, must execute AFTER
+ // bootstrapState has initialized `controls`. Order of execution is not
+ // guaranteed, so controls shouldn't rely on each other's mapped state.
+ Object.entries(exploreState.controls).forEach(([key, controlState]) => {
+ exploreState.controls[key] = applyMapStateToPropsToControl(
+ controlState,
+ exploreState,
+ );
+ });
+ const sliceFormData = initialSlice
+ ? getFormDataFromControls(initialControls)
+ : null;
+
+ const chartKey: number = getChartKey(initialExploreState);
+ const chart: ChartState = {
+ id: chartKey,
+ chartAlert: null,
+ chartStatus: null,
+ chartStackTrace: null,
+ chartUpdateEndTime: null,
+ chartUpdateStartTime: 0,
+ latestQueryFormData: getFormDataFromControls(exploreState.controls),
+ sliceFormData,
+ queryController: null,
+ queriesResponse: null,
+ triggerQuery: false,
+ lastRendered: 0,
+ };
+
+ return dispatch({
+ type: HYDRATE_EXPLORE,
+ data: {
+ charts: {
+ ...charts,
+ [chartKey]: chart,
+ },
+ datasources: {
+ ...datasources,
+ [getDatasourceUid(initialDatasource)]: initialDatasource,
+ },
+ saveModal: {
+ dashboards: [],
+ saveModalAlert: null,
+ },
+ explore: exploreState,
+ },
+ });
+ };
+
+export type HydrateExplore = {
+ type: typeof HYDRATE_EXPLORE;
+ data: ExplorePageState;
+};
diff --git
a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
index a02917e2fc..4160fbbd89 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
@@ -54,8 +54,7 @@ import Loading from 'src/components/Loading';
import { usePrevious } from 'src/hooks/usePrevious';
import { getSectionsToRender } from 'src/explore/controlUtils';
import { ExploreActions } from 'src/explore/actions/exploreActions';
-import { ExplorePageState } from 'src/explore/reducers/getInitialState';
-import { ChartState } from 'src/explore/types';
+import { ChartState, ExplorePageState } from 'src/explore/types';
import { Tooltip } from 'src/components/Tooltip';
import { rgba } from 'emotion-rgba';
diff --git
a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
index 4b19c5b2f3..a9495175d9 100644
---
a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
+++
b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
@@ -47,7 +47,9 @@ const datasource = {
main_dttm_col: 'None',
datasource_name: 'table1',
description: 'desc',
- owners: [{ username: 'admin', userId: 1 }],
+ owners: [
+ { first_name: 'admin', last_name: 'admin', username: 'admin', id: 1 },
+ ],
};
const mockUser = {
diff --git
a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
index d8d2662371..0155e3c378 100644
--- a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
+++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
@@ -694,17 +694,9 @@ function ExploreViewContainer(props) {
ExploreViewContainer.propTypes = propTypes;
function mapStateToProps(state) {
- const {
- explore,
- charts,
- common,
- impressionId,
- dataMask,
- reports,
- datasources,
- user,
- } = state;
- const { controls, slice } = explore;
+ const { explore, charts, common, impressionId, dataMask, reports, user } =
+ state;
+ const { controls, slice, datasource } = explore;
const form_data = getFormDataFromControls(controls);
const slice_id = form_data.slice_id ?? slice?.slice_id ?? 0; // 0 - unsaved
chart
form_data.extra_form_data = mergeExtraFormData(
@@ -720,8 +712,6 @@ function mapStateToProps(state) {
dashboardId = undefined;
}
- const datasource = datasources[form_data.datasource];
-
return {
isDatasourceMetaLoading: explore.isDatasourceMetaLoading,
datasource,
diff --git
a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
index 3e559c4bbf..254ebe0902 100644
---
a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
+++
b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
@@ -237,7 +237,7 @@ class DatasourceControl extends React.PureComponent {
const isSqlSupported = datasource.type === 'table';
const { user } = this.props;
const allowEdit = datasource.owners
- .map(o => o.id || o.value)
+ ?.map(o => o.id || o.value)
.includes(user.userId);
isUserAdmin(user);
diff --git
a/superset-frontend/src/explore/components/controls/VizTypeControl/FastVizSwitcher.tsx
b/superset-frontend/src/explore/components/controls/VizTypeControl/FastVizSwitcher.tsx
index ce19ba2fa4..c31b632b5f 100644
---
a/superset-frontend/src/explore/components/controls/VizTypeControl/FastVizSwitcher.tsx
+++
b/superset-frontend/src/explore/components/controls/VizTypeControl/FastVizSwitcher.tsx
@@ -29,8 +29,8 @@ import { css, SupersetTheme, t, useTheme } from
'@superset-ui/core';
import { usePluginContext } from 'src/components/DynamicPlugins';
import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
-import { ExplorePageState } from 'src/explore/reducers/getInitialState';
import { getChartKey } from 'src/explore/exploreUtils';
+import { ExplorePageState } from 'src/explore/types';
export interface VizMeta {
icon: ReactElement;
diff --git
a/superset-frontend/src/explore/exploreUtils/getParsedExploreURLParams.test.ts
b/superset-frontend/src/explore/exploreUtils/getParsedExploreURLParams.test.ts
new file mode 100644
index 0000000000..8d5a8ee09c
--- /dev/null
+++
b/superset-frontend/src/explore/exploreUtils/getParsedExploreURLParams.test.ts
@@ -0,0 +1,62 @@
+/**
+ * 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 { getParsedExploreURLParams } from './getParsedExploreURLParams';
+
+const EXPLORE_BASE_URL = 'http://localhost:9000/superset/explore/';
+const setupLocation = (newUrl: string) => {
+ delete (window as any).location;
+ // @ts-ignore
+ window.location = new URL(newUrl);
+};
+
+test('get form_data_key and slice_id from search params - url when moving from
dashboard to explore', () => {
+ setupLocation(
+
`${EXPLORE_BASE_URL}?form_data_key=yrLXmyE9fmhQ11lM1KgaD1PoPSBpuLZIJfqdyIdw9GoBwhPFRZHeIgeFiNZljbpd&slice_id=56`,
+ );
+ expect(getParsedExploreURLParams().toString()).toEqual(
+
'slice_id=56&form_data_key=yrLXmyE9fmhQ11lM1KgaD1PoPSBpuLZIJfqdyIdw9GoBwhPFRZHeIgeFiNZljbpd',
+ );
+});
+
+test('get slice_id from form_data search param - url on Chart List', () => {
+ setupLocation(`${EXPLORE_BASE_URL}?form_data=%7B%22slice_id%22%3A%2056%7D`);
+ expect(getParsedExploreURLParams().toString()).toEqual('slice_id=56');
+});
+
+test('get datasource and viz type from form_data search param - url when
creating new chart', () => {
+ setupLocation(
+
`${EXPLORE_BASE_URL}?form_data=%7B%22viz_type%22%3A%22big_number%22%2C%22datasource%22%3A%222__table%22%7D`,
+ );
+ expect(getParsedExploreURLParams().toString()).toEqual(
+ 'viz_type=big_number&dataset_id=2&dataset_type=table',
+ );
+});
+
+test('get permalink key from path params', () => {
+ setupLocation(`${EXPLORE_BASE_URL}p/kpOqweaMY9R/`);
+ expect(getParsedExploreURLParams().toString()).toEqual(
+ 'permalink_key=kpOqweaMY9R',
+ );
+});
+
+test('get dataset id from path params', () => {
+ setupLocation(`${EXPLORE_BASE_URL}table/42/`);
+ expect(getParsedExploreURLParams().toString()).toEqual('dataset_id=42');
+});
diff --git
a/superset-frontend/src/explore/exploreUtils/getParsedExploreURLParams.ts
b/superset-frontend/src/explore/exploreUtils/getParsedExploreURLParams.ts
new file mode 100644
index 0000000000..042b8f7f88
--- /dev/null
+++ b/superset-frontend/src/explore/exploreUtils/getParsedExploreURLParams.ts
@@ -0,0 +1,117 @@
+/**
+ * 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.
+ */
+
+// mapping { url_param: v1_explore_request_param }
+const EXPLORE_URL_SEARCH_PARAMS = {
+ form_data: {
+ name: 'form_data',
+ parser: (formData: string) => {
+ const formDataObject = JSON.parse(formData);
+ if (formDataObject.datasource) {
+ const [dataset_id, dataset_type] =
+ formDataObject.datasource.split('__');
+ formDataObject.dataset_id = dataset_id;
+ formDataObject.dataset_type = dataset_type;
+ delete formDataObject.datasource;
+ }
+ return formDataObject;
+ },
+ },
+ slice_id: {
+ name: 'slice_id',
+ },
+ dataset_id: {
+ name: 'dataset_id',
+ },
+ dataset_type: {
+ name: 'dataset_type',
+ },
+ datasource: {
+ name: 'datasource',
+ parser: (datasource: string) => {
+ const [dataset_id, dataset_type] = datasource.split('__');
+ return { dataset_id, dataset_type };
+ },
+ },
+ form_data_key: {
+ name: 'form_data_key',
+ },
+ permalink_key: {
+ name: 'permalink_key',
+ },
+ viz_type: {
+ name: 'viz_type',
+ },
+ dashboard_id: {
+ name: 'dashboard_id',
+ },
+};
+
+const EXPLORE_URL_PATH_PARAMS = {
+ p: 'permalink_key', // permalink
+ table: 'dataset_id',
+};
+
+// search params can be placed in form_data object
+// we need to "flatten" the search params to use them with /v1/explore endpoint
+const getParsedExploreURLSearchParams = () => {
+ const urlSearchParams = new URLSearchParams(window.location.search);
+ return Object.keys(EXPLORE_URL_SEARCH_PARAMS).reduce((acc, currentParam) => {
+ const paramValue = urlSearchParams.get(currentParam);
+ if (paramValue === null) {
+ return acc;
+ }
+ let parsedParamValue;
+ try {
+ parsedParamValue =
+ EXPLORE_URL_SEARCH_PARAMS[currentParam].parser?.(paramValue) ??
+ paramValue;
+ } catch {
+ parsedParamValue = paramValue;
+ }
+ if (typeof parsedParamValue === 'object') {
+ return { ...acc, ...parsedParamValue };
+ }
+ return {
+ ...acc,
+ [EXPLORE_URL_SEARCH_PARAMS[currentParam].name]: parsedParamValue,
+ };
+ }, {});
+};
+
+// path params need to be transformed to search params to use them with
/v1/explore endpoint
+const getParsedExploreURLPathParams = () =>
+ Object.keys(EXPLORE_URL_PATH_PARAMS).reduce((acc, currentParam) => {
+ const re = new RegExp(`/(${currentParam})/(\\w+)`);
+ const pathGroups = window.location.pathname.match(re);
+ if (pathGroups && pathGroups[2]) {
+ return { ...acc, [EXPLORE_URL_PATH_PARAMS[currentParam]]: pathGroups[2]
};
+ }
+ return acc;
+ }, {});
+
+export const getParsedExploreURLParams = () =>
+ new URLSearchParams(
+ Object.entries({
+ ...getParsedExploreURLSearchParams(),
+ ...getParsedExploreURLPathParams(),
+ })
+ .map(entry => entry.join('='))
+ .join('&'),
+ );
diff --git a/superset-frontend/src/explore/exploreUtils/index.js
b/superset-frontend/src/explore/exploreUtils/index.js
index 73d1fe088b..506de032e5 100644
--- a/superset-frontend/src/explore/exploreUtils/index.js
+++ b/superset-frontend/src/explore/exploreUtils/index.js
@@ -267,11 +267,12 @@ export const exportChart = ({
SupersetClient.postForm(url, { form_data: safeStringify(payload) });
};
-export const exploreChart = formData => {
+export const exploreChart = (formData, requestParams) => {
const url = getExploreUrl({
formData,
endpointType: 'base',
allowDomainSharding: false,
+ requestParams,
});
SupersetClient.postForm(url, { form_data: safeStringify(formData) });
};
diff --git a/superset-frontend/src/explore/fixtures.tsx
b/superset-frontend/src/explore/fixtures.tsx
index 78579b82e4..755985be2b 100644
--- a/superset-frontend/src/explore/fixtures.tsx
+++ b/superset-frontend/src/explore/fixtures.tsx
@@ -18,13 +18,14 @@
*/
import React from 'react';
-import { t } from '@superset-ui/core';
+import { DatasourceType, t } from '@superset-ui/core';
import {
ColumnMeta,
ColumnOption,
ControlConfig,
ControlPanelSectionConfig,
} from '@superset-ui/chart-controls';
+import { ExplorePageInitialData } from './types';
export const controlPanelSectionsChartOptions: (ControlPanelSectionConfig |
null)[] =
[
@@ -108,3 +109,59 @@ export const controlPanelSectionsChartOptionsTable:
ControlPanelSectionConfig[]
],
},
];
+
+export const exploreInitialData: ExplorePageInitialData = {
+ form_data: {
+ datasource: '8__table',
+ metric: 'count',
+ slice_id: 371,
+ time_range: 'No filter',
+ viz_type: 'table',
+ },
+ slice: {
+ cache_timeout: null,
+ description: null,
+ slice_id: 371,
+ slice_name: 'Age distribution of respondents',
+ is_managed_externally: false,
+ form_data: {
+ datasource: '8__table',
+ metric: 'count',
+ slice_id: 371,
+ time_range: 'No filter',
+ viz_type: 'table',
+ },
+ },
+ dataset: {
+ id: 8,
+ type: DatasourceType.Table,
+ columns: [{ column_name: 'a' }],
+ metrics: [{ metric_name: 'first' }, { metric_name: 'second' }],
+ column_format: {},
+ verbose_map: {},
+ main_dttm_col: '',
+ datasource_name: '8__table',
+ description: null,
+ },
+};
+
+export const fallbackExploreInitialData: ExplorePageInitialData = {
+ form_data: {
+ datasource: '0__table',
+ viz_type: 'table',
+ },
+ dataset: {
+ id: 0,
+ type: DatasourceType.Table,
+ columns: [],
+ metrics: [],
+ column_format: {},
+ verbose_map: {},
+ main_dttm_col: '',
+ owners: [],
+ datasource_name: 'missing_datasource',
+ name: 'missing_datasource',
+ description: null,
+ },
+ slice: null,
+};
diff --git a/superset-frontend/src/explore/index.jsx
b/superset-frontend/src/explore/index.jsx
index cc99666e02..0af6b02747 100644
--- a/superset-frontend/src/explore/index.jsx
+++ b/superset-frontend/src/explore/index.jsx
@@ -20,10 +20,11 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
-import logger from '../middleware/loggerMiddleware';
-import { initFeatureFlags } from '../featureFlags';
-import { initEnhancer } from '../reduxUtils';
-import getInitialState from './reducers/getInitialState';
+import shortid from 'shortid';
+import getToastsFromPyFlashMessages from
'src/components/MessageToasts/getToastsFromPyFlashMessages';
+import logger from 'src/middleware/loggerMiddleware';
+import { initFeatureFlags } from 'src/featureFlags';
+import { initEnhancer } from 'src/reduxUtils';
import rootReducer from './reducers/index';
import App from './App';
@@ -31,11 +32,18 @@ const exploreViewContainer = document.getElementById('app');
const bootstrapData = JSON.parse(
exploreViewContainer.getAttribute('data-bootstrap'),
);
-initFeatureFlags(bootstrapData.common.feature_flags);
-const initState = getInitialState(bootstrapData);
+
+const user = { ...bootstrapData.user };
+const common = { ...bootstrapData.common };
+initFeatureFlags(common.feature_flags);
const store = createStore(
rootReducer,
- initState,
+ {
+ user,
+ common,
+ impressionId: shortid.generate(),
+ messageToasts: getToastsFromPyFlashMessages(common?.flash_messages || []),
+ },
compose(applyMiddleware(thunk, logger), initEnhancer(false)),
);
diff --git a/superset-frontend/src/explore/reducers/datasourcesReducer.ts
b/superset-frontend/src/explore/reducers/datasourcesReducer.ts
index e3050feea0..50393dbd24 100644
--- a/superset-frontend/src/explore/reducers/datasourcesReducer.ts
+++ b/superset-frontend/src/explore/reducers/datasourcesReducer.ts
@@ -22,11 +22,12 @@ import {
AnyDatasourcesAction,
SET_DATASOURCE,
} from '../actions/datasourcesActions';
+import { HYDRATE_EXPLORE, HydrateExplore } from '../actions/hydrateExplore';
export default function datasourcesReducer(
// TODO: change type to include other datasource types
datasources: { [key: string]: Dataset },
- action: AnyDatasourcesAction,
+ action: AnyDatasourcesAction | HydrateExplore,
) {
if (action.type === SET_DATASOURCE) {
return {
@@ -34,5 +35,8 @@ export default function datasourcesReducer(
[getDatasourceUid(action.datasource)]: action.datasource,
};
}
+ if (action.type === HYDRATE_EXPLORE) {
+ return { ...(action as HydrateExplore).data.datasources };
+ }
return datasources || {};
}
diff --git a/superset-frontend/src/explore/reducers/exploreReducer.js
b/superset-frontend/src/explore/reducers/exploreReducer.js
index 706104b765..f99ab9437c 100644
--- a/superset-frontend/src/explore/reducers/exploreReducer.js
+++ b/superset-frontend/src/explore/reducers/exploreReducer.js
@@ -28,6 +28,7 @@ import {
StandardizedFormData,
} from 'src/explore/controlUtils';
import * as actions from 'src/explore/actions/exploreActions';
+import { HYDRATE_EXPLORE } from '../actions/hydrateExplore';
export default function exploreReducer(state = {}, action) {
const actionHandlers = {
@@ -247,8 +248,12 @@ export default function exploreReducer(state = {}, action)
{
force: action.force,
};
},
+ [HYDRATE_EXPLORE]() {
+ return {
+ ...action.data.explore,
+ };
+ },
};
-
if (action.type in actionHandlers) {
return actionHandlers[action.type]();
}
diff --git a/superset-frontend/src/explore/reducers/getInitialState.ts
b/superset-frontend/src/explore/reducers/getInitialState.ts
deleted file mode 100644
index 4d4970237e..0000000000
--- a/superset-frontend/src/explore/reducers/getInitialState.ts
+++ /dev/null
@@ -1,146 +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 shortid from 'shortid';
-import {
- DatasourceType,
- ensureIsArray,
- JsonObject,
- QueryFormData,
-} from '@superset-ui/core';
-import { ControlStateMapping, Dataset } from '@superset-ui/chart-controls';
-import {
- CommonBootstrapData,
- UserWithPermissionsAndRoles,
-} from 'src/types/bootstrapTypes';
-import getToastsFromPyFlashMessages from
'src/components/MessageToasts/getToastsFromPyFlashMessages';
-
-import { ChartState, Slice } from 'src/explore/types';
-import { getChartKey } from 'src/explore/exploreUtils';
-import { getControlsState } from 'src/explore/store';
-import {
- getFormDataFromControls,
- applyMapStateToPropsToControl,
-} from 'src/explore/controlUtils';
-import { findPermission } from 'src/utils/findPermission';
-import { getDatasourceUid } from 'src/utils/getDatasourceUid';
-import { getUrlParam } from 'src/utils/urlUtils';
-import { URL_PARAMS } from 'src/constants';
-
-export interface ExplorePageBootstrapData extends JsonObject {
- can_add: boolean;
- can_download: boolean;
- can_overwrite: boolean;
- common: CommonBootstrapData;
- datasource: Dataset;
- datasource_id: number;
- datasource_type: DatasourceType;
- forced_height: string | null;
- form_data: QueryFormData;
- slice: Slice | null;
- standalone: boolean;
- force: boolean;
- user: UserWithPermissionsAndRoles;
-}
-
-export default function getInitialState(
- bootstrapData: ExplorePageBootstrapData,
-) {
- const {
- form_data: initialFormData,
- common,
- user,
- datasource,
- slice,
- } = bootstrapData;
-
- const exploreState = {
- // note this will add `form_data` to state,
- // which will be manipulatable by future reducers.
- can_add: findPermission('can_write', 'Chart', user?.roles),
- can_download: findPermission('can_csv', 'Superset', user?.roles),
- can_overwrite: ensureIsArray(slice?.owners).includes(
- user?.userId as number,
- ),
- isDatasourceMetaLoading: false,
- isStarred: false,
- triggerRender: false,
- // duplicate datasource in exploreState - it's needed by getControlsState
- datasource,
- // Initial control state will skip `control.mapStateToProps`
- // because `bootstrapData.controls` is undefined.
- controls: getControlsState(
- bootstrapData,
- initialFormData,
- ) as ControlStateMapping,
- form_data: initialFormData,
- slice,
- controlsTransferred: [],
- standalone: getUrlParam(URL_PARAMS.standalone),
- force: getUrlParam(URL_PARAMS.force),
- };
-
- // apply initial mapStateToProps for all controls, must execute AFTER
- // bootstrapState has initialized `controls`. Order of execution is not
- // guaranteed, so controls shouldn't rely on each other's mapped state.
- Object.entries(exploreState.controls).forEach(([key, controlState]) => {
- exploreState.controls[key] = applyMapStateToPropsToControl(
- controlState,
- exploreState,
- );
- });
- const sliceFormData = slice
- ? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
- : null;
-
- const chartKey: number = getChartKey(bootstrapData);
- const chart: ChartState = {
- id: chartKey,
- chartAlert: null,
- chartStatus: null,
- chartStackTrace: null,
- chartUpdateEndTime: null,
- chartUpdateStartTime: 0,
- latestQueryFormData: getFormDataFromControls(exploreState.controls),
- sliceFormData,
- queryController: null,
- queriesResponse: null,
- triggerQuery: false,
- lastRendered: 0,
- };
-
- return {
- common: common || {},
- user: user || {},
- charts: {
- [chartKey]: chart,
- },
- datasources: { [getDatasourceUid(datasource)]: datasource },
- saveModal: {
- dashboards: [],
- saveModalAlert: null,
- },
- explore: exploreState,
- impressionId: shortid.generate(),
- messageToasts: getToastsFromPyFlashMessages(
- (bootstrapData.common || {}).flash_messages || [],
- ),
- };
-}
-
-export type ExplorePageState = ReturnType<typeof getInitialState>;
diff --git a/superset-frontend/src/explore/reducers/saveModalReducer.js
b/superset-frontend/src/explore/reducers/saveModalReducer.js
index eee4197991..85d4cf0e32 100644
--- a/superset-frontend/src/explore/reducers/saveModalReducer.js
+++ b/superset-frontend/src/explore/reducers/saveModalReducer.js
@@ -18,6 +18,7 @@
*/
/* eslint camelcase: 0 */
import * as actions from '../actions/saveModalActions';
+import { HYDRATE_EXPLORE } from '../actions/hydrateExplore';
export default function saveModalReducer(state = {}, action) {
const actionHandlers = {
@@ -39,6 +40,9 @@ export default function saveModalReducer(state = {}, action) {
[actions.REMOVE_SAVE_MODAL_ALERT]() {
return { ...state, saveModalAlert: null };
},
+ [HYDRATE_EXPLORE]() {
+ return { ...action.data.saveModal };
+ },
};
if (action.type in actionHandlers) {
diff --git a/superset-frontend/src/explore/store.js
b/superset-frontend/src/explore/store.js
index 80ad75e3e5..8bd0477087 100644
--- a/superset-frontend/src/explore/store.js
+++ b/superset-frontend/src/explore/store.js
@@ -42,7 +42,7 @@ export function getControlsState(state, inputFormData) {
// Getting a list of active control names for the current viz
const formData = { ...inputFormData };
const vizType =
- formData.viz_type || state.common.conf.DEFAULT_VIZ_TYPE || 'table';
+ formData.viz_type || state.common?.conf.DEFAULT_VIZ_TYPE || 'table';
handleDeprecatedControls(formData);
diff --git a/superset-frontend/src/explore/types.ts
b/superset-frontend/src/explore/types.ts
index 4d50b449c5..85b161058a 100644
--- a/superset-frontend/src/explore/types.ts
+++ b/superset-frontend/src/explore/types.ts
@@ -21,13 +21,17 @@ import {
QueryFormData,
AnnotationData,
AdhocMetric,
+ JsonObject,
} from '@superset-ui/core';
-import { ColumnMeta, Dataset } from '@superset-ui/chart-controls';
+import {
+ ColumnMeta,
+ ControlStateMapping,
+ Dataset,
+} from '@superset-ui/chart-controls';
import { DatabaseObject } from 'src/views/CRUD/types';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import { toastState } from 'src/SqlLab/types';
-
-export { Slice, Chart } from 'src/types/Chart';
+import { Slice } from 'src/types/Chart';
export type ChartStatus =
| 'loading'
@@ -90,3 +94,40 @@ export type ExploreRootState = {
messageToasts: toastState[];
common: {};
};
+
+export interface ExplorePageInitialData {
+ dataset: Dataset;
+ form_data: QueryFormData;
+ slice: Slice | null;
+}
+
+export interface ExploreResponsePayload {
+ result: ExplorePageInitialData & { message: string };
+}
+
+export interface ExplorePageState {
+ user: UserWithPermissionsAndRoles;
+ common: {
+ flash_messages: string[];
+ conf: JsonObject;
+ };
+ charts: { [key: number]: ChartState };
+ datasources: { [key: string]: Dataset };
+ explore: {
+ can_add: boolean;
+ can_download: boolean;
+ can_overwrite: boolean;
+ isDatasourceMetaLoading: boolean;
+ isStarred: boolean;
+ triggerRender: boolean;
+ // duplicate datasource in exploreState - it's needed by getControlsState
+ datasource: Dataset;
+ controls: ControlStateMapping;
+ form_data: QueryFormData;
+ slice: Slice;
+ controlsTransferred: string[];
+ standalone: boolean;
+ force: boolean;
+ };
+ sliceEntities?: JsonObject; // propagated from Dashboard view
+}