diff --git a/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts new file mode 100644 index 0000000000..dc129613b9 --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts @@ -0,0 +1,72 @@ +/** + * 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. + */ +describe('AdhocFilters', () => { + beforeEach(() => { + cy.login(); + cy.server(); + cy.route('GET', '/superset/explore_json/**').as('getJson'); + cy.route('POST', '/superset/explore_json/**').as('postJson'); + }); + + it('Set simple adhoc filter', () => { + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@postJson' }); + + cy.get('[data-test=adhoc_filters]').within(() => { + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('name{enter}'); + }); + cy.get('#filter-edit-popover').within(() => { + cy.get('[data-test=adhoc-filter-simple-value]').within(() => { + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('Any{enter}'); + }); + cy.get('button').contains('Save').click(); + }); + + cy.get('button.query').click(); + cy.verifySliceSuccess({ + waitAlias: '@postJson', + chartSelector: 'svg', + }); + }); + + it('Set custom adhoc filter', () => { + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@postJson' }); + + cy.get('[data-test=adhoc_filters]').within(() => { + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('name{enter}'); + }); + + cy.get('#filter-edit-popover').within(() => { + cy.get('#adhoc-filter-edit-tabs-tab-SQL').click(); + cy.get('.ace_content').click(); + cy.get('.ace_text-input').type("'Amy' OR name = 'Bob'"); + cy.get('button').contains('Save').click(); + }); + + cy.get('button.query').click(); + cy.verifySliceSuccess({ + waitAlias: '@postJson', + chartSelector: 'svg', + }); + }); +}); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts new file mode 100644 index 0000000000..ef7dc9b1a1 --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts @@ -0,0 +1,131 @@ +/** + * 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. + */ +describe('AdhocMetrics', () => { + beforeEach(() => { + cy.login(); + cy.server(); + cy.route('GET', '/superset/explore_json/**').as('getJson'); + cy.route('POST', '/superset/explore_json/**').as('postJson'); + }); + + it('Clear metric and set simple adhoc metric', () => { + const metric = 'sum(sum_girls)'; + const metricName = 'Girl Births'; + + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@postJson' }); + + cy.get('[data-test=metrics]').within(() => { + cy.get('.Select__clear-indicator').click(); + cy.get('.Select__control input').type('sum_girls'); + cy.get('.Select__option--is-focused').trigger('mousedown').click(); + }); + + cy.get('#metrics-edit-popover').within(() => { + cy.get('.popover-title').within(() => { + cy.get('span').click(); + cy.get('input').type(metricName); + }); + cy.get('button').contains('Save').click(); + }); + cy.get('.Select__multi-value__label').contains(metricName); + + cy.get('button.query').click(); + cy.verifySliceSuccess({ + waitAlias: '@postJson', + querySubstring: `${metric} AS "${metricName}"`, // SQL statement + chartSelector: 'svg', + }); + }); + + it('Switch from simple to custom sql', () => { + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@postJson' }); + + // select column "num" + cy.get('[data-test=metrics]').within(() => { + cy.get('.Select__clear-indicator').click(); + cy.get('.Select__control').click(); + cy.get('.Select__control input').type('num'); + cy.get('.option-label').contains(/^num$/).click(); + }); + + // add custom SQL + cy.get('#metrics-edit-popover').within(() => { + cy.get('#adhoc-metric-edit-tabs-tab-SQL').click(); + cy.get('.ace_content').click(); + cy.get('.ace_text-input').type('/COUNT(DISTINCT name)', { force: true }); + cy.get('button').contains('Save').click(); + }); + + cy.get('button.query').click(); + + const metric = 'SUM(num)/COUNT(DISTINCT name)'; + cy.verifySliceSuccess({ + waitAlias: '@postJson', + querySubstring: `${metric} AS "${metric}"`, + chartSelector: 'svg', + }); + }); + + it('Switch from custom sql tabs to simple', () => { + cy.get('[data-test=metrics]').within(() => { + cy.get('.Select__dropdown-indicator').click(); + cy.get('input[type=text]').type('sum_girls{enter}'); + }); + + cy.get('#metrics-edit-popover').within(() => { + cy.get('#adhoc-metric-edit-tabs-tab-SQL').click(); + cy.get('.ace_identifier').contains('sum_girls'); + cy.get('.ace_content').click(); + cy.get('.ace_text-input').type('{selectall}{backspace}SUM(num)'); + cy.get('#adhoc-metric-edit-tabs-tab-SIMPLE').click(); + cy.get('.Select__single-value').contains(/^num$/); + cy.get('button').contains('Save').click(); + }); + + cy.get('button.query').click(); + + const metric = 'SUM(num)'; + cy.verifySliceSuccess({ + waitAlias: '@postJson', + querySubstring: `${metric} AS "${metric}"`, + chartSelector: 'svg', + }); + }); + + it('Typing starts with aggregate function name', () => { + // select column "num" + cy.get('[data-test=metrics]').within(() => { + cy.get('.Select__dropdown-indicator').click(); + cy.get('.Select__control input[type=text]').type('avg('); + cy.get('.Select__option').contains('ds'); + cy.get('.Select__option').contains('name'); + cy.get('.Select__option').contains('sum_boys').click(); + }); + + const metric = 'AVG(sum_boys)'; + cy.get('button.query').click(); + cy.verifySliceSuccess({ + waitAlias: '@postJson', + querySubstring: `${metric} AS "${metric}"`, + chartSelector: 'svg', + }); + }); +}); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts new file mode 100644 index 0000000000..4a21f007da --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts @@ -0,0 +1,97 @@ +/** + * 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. + */ +describe('Advanced analytics', () => { + beforeEach(() => { + cy.login(); + cy.server(); + cy.route('GET', '/superset/explore_json/**').as('getJson'); + cy.route('POST', '/superset/explore_json/**').as('postJson'); + }); + + it('Create custom time compare', () => { + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@postJson' }); + + cy.get('.panel-title').contains('Advanced Analytics').click(); + + cy.get('[data-test=time_compare]').within(() => { + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('28 days{enter}'); + + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('364 days{enter}'); + cy.get('.Select__multi-value__label').contains('364 days'); + }); + + cy.get('button.query').click(); + cy.wait('@postJson'); + cy.reload(); + cy.verifySliceSuccess({ + waitAlias: '@postJson', + chartSelector: 'svg', + }); + + cy.get('[data-test=time_compare]').within(() => { + cy.get('.Select__multi-value__label').contains('364 days'); + cy.get('.Select__multi-value__label').contains('28 days'); + }); + }); +}); + +describe('Annotations', () => { + beforeEach(() => { + cy.login(); + cy.server(); + cy.route('GET', '/superset/explore_json/**').as('getJson'); + cy.route('POST', '/superset/explore_json/**').as('postJson'); + }); + + it('Create formula annotation y-axis goal line', () => { + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@postJson' }); + + cy.get('[data-test=annotation_layers]').within(() => { + cy.get('button').click(); + }); + + cy.get('.popover-content').within(() => { + cy.get('[data-test=annotation-layer-name-header]') + .siblings() + .first() + .within(() => { + cy.get('input').type('Goal line'); + }); + cy.get('[data-test=annotation-layer-value-header]') + .siblings() + .first() + .within(() => { + cy.get('input').type('y=1400000'); + }); + cy.get('button').contains('OK').click(); + }); + + cy.get('button.query').click(); + cy.verifySliceSuccess({ + waitAlias: '@postJson', + chartSelector: 'svg', + }); + + cy.get('.nv-legend-text').should('have.length', 2); + }); +}); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/control.test.js b/superset-frontend/cypress-base/cypress/integration/explore/control.test.js index b012a13d0c..e2da3a6f6f 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.js @@ -40,255 +40,6 @@ describe('Groupby', () => { }); }); -describe('AdhocMetrics', () => { - beforeEach(() => { - cy.login(); - cy.server(); - cy.route('GET', '/superset/explore_json/**').as('getJson'); - cy.route('POST', '/superset/explore_json/**').as('postJson'); - }); - - it('Clear metric and set simple adhoc metric', () => { - const metric = 'sum(sum_girls)'; - const metricName = 'Girl Births'; - - cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); - - cy.get('[data-test=metrics]').within(() => { - cy.get('.Select__clear-indicator').click(); - cy.get('.Select__control input').type('sum_girls'); - cy.get('.Select__option--is-focused').trigger('mousedown').click(); - }); - - cy.get('#metrics-edit-popover').within(() => { - cy.get('.popover-title').within(() => { - cy.get('span').click(); - cy.get('input').type(metricName); - }); - cy.get('button').contains('Save').click(); - }); - cy.get('.Select__multi-value__label').contains(metricName); - - cy.get('button.query').click(); - cy.verifySliceSuccess({ - waitAlias: '@postJson', - querySubstring: `${metric} AS "${metricName}"`, // SQL statement - chartSelector: 'svg', - }); - }); - - it('Switch from simple to custom sql', () => { - cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); - - // select column "num" - cy.get('[data-test=metrics]').within(() => { - cy.get('.Select__clear-indicator').click(); - cy.get('.Select__control').click(); - cy.get('.Select__control input').type('num'); - cy.get('.option-label').contains(/^num$/).click(); - }); - - // add custom SQL - cy.get('#metrics-edit-popover').within(() => { - cy.get('#adhoc-metric-edit-tabs-tab-SQL').click(); - cy.get('.ace_content').click(); - cy.get('.ace_text-input').type('/COUNT(DISTINCT name)', { force: true }); - cy.get('button').contains('Save').click(); - }); - - cy.get('button.query').click(); - - const metric = 'SUM(num)/COUNT(DISTINCT name)'; - cy.verifySliceSuccess({ - waitAlias: '@postJson', - querySubstring: `${metric} AS "${metric}"`, - chartSelector: 'svg', - }); - }); - - it('Switch from custom sql tabs to simple', () => { - cy.get('[data-test=metrics]').within(() => { - cy.get('.Select__dropdown-indicator').click(); - cy.get('input[type=text]').type('sum_girls{enter}'); - }); - - cy.get('#metrics-edit-popover').within(() => { - cy.get('#adhoc-metric-edit-tabs-tab-SQL').click(); - cy.get('.ace_identifier').contains('sum_girls'); - cy.get('.ace_content').click(); - cy.get('.ace_text-input').type('{selectall}{backspace}SUM(num)'); - cy.get('#adhoc-metric-edit-tabs-tab-SIMPLE').click(); - cy.get('.Select__single-value').contains(/^num$/); - cy.get('button').contains('Save').click(); - }); - - cy.get('button.query').click(); - - const metric = 'SUM(num)'; - cy.verifySliceSuccess({ - waitAlias: '@postJson', - querySubstring: `${metric} AS "${metric}"`, - chartSelector: 'svg', - }); - }); - - it('Typing starts with aggregate function name', () => { - // select column "num" - cy.get('[data-test=metrics]').within(() => { - cy.get('.Select__dropdown-indicator').click(); - cy.get('.Select__control input[type=text]').type('avg('); - cy.get('.Select__option').contains('ds'); - cy.get('.Select__option').contains('name'); - cy.get('.Select__option').contains('sum_boys').click(); - }); - - const metric = 'AVG(sum_boys)'; - cy.get('button.query').click(); - cy.verifySliceSuccess({ - waitAlias: '@postJson', - querySubstring: `${metric} AS "${metric}"`, - chartSelector: 'svg', - }); - }); -}); - -describe('AdhocFilters', () => { - beforeEach(() => { - cy.login(); - cy.server(); - cy.route('GET', '/superset/explore_json/**').as('getJson'); - cy.route('POST', '/superset/explore_json/**').as('postJson'); - }); - - it('Set simple adhoc filter', () => { - cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); - - cy.get('[data-test=adhoc_filters]').within(() => { - cy.get('.Select__control').click(); - cy.get('input[type=text]').type('name{enter}'); - }); - cy.get('#filter-edit-popover').within(() => { - cy.get('[data-test=adhoc-filter-simple-value]').within(() => { - cy.get('.Select__control').click(); - cy.get('input[type=text]').type('Any{enter}'); - }); - cy.get('button').contains('Save').click(); - }); - - cy.get('button.query').click(); - cy.verifySliceSuccess({ - waitAlias: '@postJson', - chartSelector: 'svg', - }); - }); - - it('Set custom adhoc filter', () => { - cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); - - cy.get('[data-test=adhoc_filters]').within(() => { - cy.get('.Select__control').click(); - cy.get('input[type=text]').type('name{enter}'); - }); - - cy.get('#filter-edit-popover').within(() => { - cy.get('#adhoc-filter-edit-tabs-tab-SQL').click(); - cy.get('.ace_content').click(); - cy.get('.ace_text-input').type("'Amy' OR name = 'Bob'"); - cy.get('button').contains('Save').click(); - }); - - cy.get('button.query').click(); - cy.verifySliceSuccess({ - waitAlias: '@postJson', - chartSelector: 'svg', - }); - }); -}); - -describe('Advanced analytics', () => { - beforeEach(() => { - cy.login(); - cy.server(); - cy.route('GET', '/superset/explore_json/**').as('getJson'); - cy.route('POST', '/superset/explore_json/**').as('postJson'); - }); - - it('Create custom time compare', () => { - cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); - - cy.get('.panel-title').contains('Advanced Analytics').click(); - - cy.get('[data-test=time_compare]').within(() => { - cy.get('.Select__control').click(); - cy.get('input[type=text]').type('28 days{enter}'); - - cy.get('.Select__control').click(); - cy.get('input[type=text]').type('364 days{enter}'); - cy.get('.Select__multi-value__label').contains('364 days'); - }); - - cy.get('button.query').click(); - cy.wait('@postJson'); - cy.reload(); - cy.verifySliceSuccess({ - waitAlias: '@postJson', - chartSelector: 'svg', - }); - - cy.get('[data-test=time_compare]').within(() => { - cy.get('.Select__multi-value__label').contains('364 days'); - cy.get('.Select__multi-value__label').contains('28 days'); - }); - }); -}); - -describe('Annotations', () => { - beforeEach(() => { - cy.login(); - cy.server(); - cy.route('GET', '/superset/explore_json/**').as('getJson'); - cy.route('POST', '/superset/explore_json/**').as('postJson'); - }); - - it('Create formula annotation y-axis goal line', () => { - cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); - - cy.get('[data-test=annotation_layers]').within(() => { - cy.get('button').click(); - }); - - cy.get('.popover-content').within(() => { - cy.get('[data-test=annotation-layer-name-header]') - .siblings() - .first() - .within(() => { - cy.get('input').type('Goal line'); - }); - cy.get('[data-test=annotation-layer-value-header]') - .siblings() - .first() - .within(() => { - cy.get('input').type('y=1400000'); - }); - cy.get('button').contains('OK').click(); - }); - - cy.get('button.query').click(); - cy.verifySliceSuccess({ - waitAlias: '@postJson', - chartSelector: 'svg', - }); - - cy.get('.nv-legend-text').should('have.length', 2); - }); -}); - describe('Time range filter', () => { beforeEach(() => { cy.login(); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js index 5314a016d5..229c7f4e49 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js @@ -27,9 +27,26 @@ describe('Visualization > Line', () => { cy.route('POST', '/superset/explore_json/**').as('getJson'); }); + it('should show validator error when no metric', () => { + const formData = { ...LINE_CHART_DEFAULTS, metrics: [] }; + cy.visitChartByParams(JSON.stringify(formData)); + cy.get('.alert-warning').contains(`"Metrics" cannot be empty`); + }); + + it('should not show validator error when metric added', () => { + const formData = { ...LINE_CHART_DEFAULTS, metrics: [] }; + cy.visitChartByParams(JSON.stringify(formData)); + cy.get('.alert-warning').contains(`"Metrics" cannot be empty`); + cy.get('.text-danger').contains('Metrics'); + cy.get('.metrics-select .Select__input input:eq(0)') + .focus() + .type('SUM(num){enter}'); + cy.get('.text-danger').should('not.exist'); + cy.get('.alert-warning').should('not.exist'); + }); + it('should work with adhoc metric', () => { const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] }; - cy.visitChartByParams(JSON.stringify(formData)); cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts similarity index 95% rename from superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.js rename to superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts index e95fb4886c..b7db68e627 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts @@ -30,9 +30,7 @@ describe('Visualization > Table', () => { }); it('Test table with adhoc metric', () => { - const formData = { ...VIZ_DEFAULTS, metrics: NUM_METRIC }; - - cy.visitChartByParams(JSON.stringify(formData)); + cy.visitChartByParams({ ...VIZ_DEFAULTS, metrics: NUM_METRIC }); cy.verifySliceSuccess({ waitAlias: '@getJson', querySubstring: NUM_METRIC.label, @@ -41,16 +39,14 @@ describe('Visualization > Table', () => { }); it('Test table with groupby', () => { - const formData = { + cy.visitChartByParams({ ...VIZ_DEFAULTS, metrics: NUM_METRIC, groupby: ['name'], - }; - - cy.visitChartByParams(JSON.stringify(formData)); + }); cy.verifySliceSuccess({ waitAlias: '@getJson', - querySubstring: formData.groupby[0], + querySubstring: /groupby.*name/, chartSelector: 'table', }); }); @@ -62,7 +58,6 @@ describe('Visualization > Table', () => { metrics: [], groupby: ['name'], }; - cy.visitChartByParams(JSON.stringify(formData)); cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' }); }); @@ -74,23 +69,19 @@ describe('Visualization > Table', () => { groupby: ['name'], order_desc: true, }; - cy.visitChartByParams(JSON.stringify(formData)); cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' }); }); it('Test table with groupby and limit', () => { const limit = 10; - const formData = { ...VIZ_DEFAULTS, metrics: NUM_METRIC, groupby: ['name'], row_limit: limit, }; - cy.visitChartByParams(JSON.stringify(formData)); - cy.wait('@getJson').then(async xhr => { cy.verifyResponseCodes(xhr); cy.verifySliceContainer('table'); diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts b/superset-frontend/cypress-base/cypress/support/index.d.ts index 80a936ef40..0c21db731a 100644 --- a/superset-frontend/cypress-base/cypress/support/index.d.ts +++ b/superset-frontend/cypress-base/cypress/support/index.d.ts @@ -30,6 +30,10 @@ declare namespace Cypress { */ login(): void; + visitChartByParams(params: string | object): cy; + visitChartByName(name: string): cy; + visitChartById(id: number): cy; + /** * Verify a waitXHR response and parse response JSON. */ @@ -46,14 +50,10 @@ declare namespace Cypress { /** * Verify slice successfully loaded. */ - verifySliceSuccess({ - waitAlias, - querySubString, - chartSelector, - }: { + verifySliceSuccess(options: { waitAlias: string; - querySubString: string; - chartSelector: JQuery.Selector; + querySubstring?: string | RegExp; + chartSelector?: JQuery.Selector; }): cy; } } diff --git a/superset-frontend/cypress-base/cypress/support/index.ts b/superset-frontend/cypress-base/cypress/support/index.ts index ad70b732e5..759539c5a6 100644 --- a/superset-frontend/cypress-base/cypress/support/index.ts +++ b/superset-frontend/cypress-base/cypress/support/index.ts @@ -42,7 +42,9 @@ Cypress.Commands.add('visitChartById', chartId => { }); Cypress.Commands.add('visitChartByParams', params => { - return cy.visit(`${BASE_EXPLORE_URL}${params}`); + const queryString = + typeof params === 'string' ? params : JSON.stringify(params); + return cy.visit(`${BASE_EXPLORE_URL}${queryString}`); }); Cypress.Commands.add('verifyResponseCodes', (xhr: XMLHttpRequest, callback) => { @@ -78,17 +80,21 @@ Cypress.Commands.add( chartSelector, }: { waitAlias: string; - querySubstring: string; chartSelector: JQuery.Selector; + querySubstring?: string | RegExp; }) => { cy.wait(waitAlias).then(xhr => { cy.verifySliceContainer(chartSelector); cy.verifyResponseCodes(xhr, responseBody => { if (querySubstring) { - type QueryResponse = { query: string }; - expect( - responseBody && (responseBody as QueryResponse).query, - ).contains(querySubstring); + const query = responseBody + ? (responseBody as { query: string }).query + : ''; + if (querySubstring instanceof RegExp) { + expect(query).to.match(querySubstring); + } else { + expect(query).to.contain(querySubstring); + } } }); }); diff --git a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx index 2b18063346..946517d89f 100644 --- a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx @@ -189,15 +189,15 @@ describe('controlUtils', () => { it('removes the mapStateToProps key from the object', () => { let control = getControlConfig('all_columns', 'table'); control = applyMapStateToPropsToControl(control, state); - expect(control.mapStateToProps).toBe(undefined); + expect(control.mapStateToProps[0]).toBe(undefined); }); }); describe('getControlState', () => { - it('to be function free', () => { - const control = getControlState('all_columns', 'table', state, ['a']); - expect(control.mapStateToProps).toBe(undefined); - expect(control.validators).toBe(undefined); + it('to still have the functions', () => { + const control = getControlState('metrics', 'table', state, ['a']); + expect(typeof control.mapStateToProps).toBe('function'); + expect(typeof control.validators[0]).toBe('function'); }); it('to fix multi with non-array values', () => { diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx index 22b948f409..77aa5a7be6 100644 --- a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx +++ b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx @@ -76,13 +76,6 @@ class ControlPanelsContainer extends React.Component { // apply current value in formData value: formData[name], }; - const { mapStateToProps: mapFn } = controlData; - if (mapFn) { - Object.assign( - controlData, - mapFn(exploreState, controlData, actions) || {}, - ); - } const { validationErrors, provideFormDataToProps, diff --git a/superset-frontend/src/explore/controlUtils.js b/superset-frontend/src/explore/controlUtils.js index daa395e002..41c5de08e1 100644 --- a/superset-frontend/src/explore/controlUtils.js +++ b/superset-frontend/src/explore/controlUtils.js @@ -45,7 +45,6 @@ export function validateControl(control, processedState) { validationErrors.push(v); } }); - delete validatedControl.validators; return { ...validatedControl, validationErrors }; } return control; @@ -95,7 +94,6 @@ export function applyMapStateToPropsToControl(control, state) { if (state) { Object.assign(appliedControl, control.mapStateToProps(state, control)); } - delete appliedControl.mapStateToProps; return appliedControl; } return control; @@ -141,6 +139,7 @@ export function getControlStateFromControlConfig(controlConfig, state, value) { // If a choice control went from multi=false to true, wrap value in array const controlValue = controlConfig.multi && value && !Array.isArray(value) ? [value] : value; + controlState.value = typeof controlValue === 'undefined' ? controlState.default : controlValue; diff --git a/superset-frontend/src/explore/reducers/exploreReducer.js b/superset-frontend/src/explore/reducers/exploreReducer.js index 5e1df2c3ab..83cd62e321 100644 --- a/superset-frontend/src/explore/reducers/exploreReducer.js +++ b/superset-frontend/src/explore/reducers/exploreReducer.js @@ -99,24 +99,34 @@ export default function exploreReducer(state = {}, action) { }, [actions.SET_FIELD_VALUE]() { const new_form_data = state.form_data; - new_form_data[action.controlName] = action.value; + const { controlName, value, validationErrors } = action; + new_form_data[controlName] = value; - // These errors are reported from the Control components - let errors = action.validationErrors || []; const vizType = new_form_data.viz_type; + // Use the processed control config (with overrides and everything) // if `controlName` does not existing in current controls, const controlConfig = state.controls[action.controlName] || getControlConfig(action.controlName, vizType) || {}; + + // will call validators again const control = { ...getControlStateFromControlConfig(controlConfig, state, action.value), }; - // These errors are based on control config `validators` - errors = errors.concat(control.validationErrors || []); + // combine newly detected errors with errors from `onChange` event of + // each control component (passed via reducer action). + const errors = control.validationErrors || []; + (validationErrors || []).forEach(err => { + // skip duplicated errors + if (!errors.includes(err)) { + errors.push(err); + } + }); const hasErrors = errors && errors.length > 0; + return { ...state, form_data: new_form_data,