diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.js b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.js index 77cf0fb51..f73979555 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.js @@ -53,13 +53,11 @@ export default () => }); it('should apply filter', () => { - cy.wait(10); - - cy.get('.Select-placeholder') + cy.get('.Select__control') .contains('Select [region]') - .click() - .next() - .find('input') + .click({ force: true }); + cy.get('.Select__control input[type=text]') + .first() .type('South Asia{enter}', { force: true }); // wait again after applied filters diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.js b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.js index c62bc2d5d..130609fba 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.js @@ -128,10 +128,11 @@ export default () => cy.wait('@treemapRequest'); // apply filter - cy.get('.Select-control') - .first() - .find('input') + cy.get('.Select__control').first().should('be.visible'); + cy.get('.Select__control').first().click({ force: true }); + cy.get('.Select__control input[type=text]') .first() + .should('be.visible') .type('South Asia{enter}', { force: true }); // send new query from same tab @@ -182,8 +183,11 @@ export default () => .find('ul.nav.nav-tabs li') .first() .click(); - cy.get('.tab-content ul.nav.nav-tabs li').first().click(); - cy.get('span.Select-clear').click(); + cy.get('.tab-content ul.nav.nav-tabs li') + .first() + .should('be.visible') + .click(); + cy.get('.Select__clear-indicator').click(); // trigger 1 new query cy.wait('@treemapRequest'); 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 689c70e2c..2839fe22a 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/chart.test.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/chart.test.js @@ -17,7 +17,6 @@ * under the License. */ import { FORM_DATA_DEFAULTS, NUM_METRIC } from './visualizations/shared.helper'; -import readResponseBlob from '../../utils/readResponseBlob'; describe('No Results', () => { beforeEach(() => { @@ -39,7 +38,6 @@ describe('No Results', () => { comparator: ['Fake State'], clause: 'WHERE', sqlExpression: null, - fromFormData: true, }, ], }; 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 a4faaaaad..b012a13d0 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.js @@ -32,9 +32,8 @@ describe('Groupby', () => { cy.verifySliceSuccess({ waitAlias: '@postJson' }); cy.get('[data-test=groupby]').within(() => { - cy.get('.Select-control').click(); - cy.get('input.select-input').type('state', { force: true }); - cy.get('.VirtualizedSelectFocusedOption').click(); + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('state{enter}'); }); cy.get('button.query').click(); cy.verifySliceSuccess({ waitAlias: '@postJson', chartSelector: 'svg' }); @@ -50,16 +49,16 @@ describe('AdhocMetrics', () => { }); 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').click(); - cy.get('.Select-control').click({ force: true }); - cy.get('input').type('sum_girls', { force: true }); - cy.get('.VirtualizedSelectFocusedOption').trigger('mousedown').click(); + 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(() => { @@ -69,30 +68,29 @@ describe('AdhocMetrics', () => { }); cy.get('button').contains('Save').click(); }); + cy.get('.Select__multi-value__label').contains(metricName); cy.get('button.query').click(); cy.verifySliceSuccess({ waitAlias: '@postJson', - querySubstring: metricName, + querySubstring: `${metric} AS "${metricName}"`, // SQL statement chartSelector: 'svg', }); }); - it('Clear metric and set custom sql adhoc metric', () => { - const metric = 'SUM(num)/COUNT(DISTINCT name)'; - + 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').click(); - cy.get('.Select-control').click({ force: true }); - cy.get('input').type('num', { force: true }); - cy.get('.VirtualizedSelectOption[data-test=_col_num]') - .trigger('mousedown') - .click(); + 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(); @@ -101,39 +99,56 @@ describe('AdhocMetrics', () => { }); cy.get('button.query').click(); + + const metric = 'SUM(num)/COUNT(DISTINCT name)'; cy.verifySliceSuccess({ waitAlias: '@postJson', - querySubstring: metric, + querySubstring: `${metric} AS "${metric}"`, chartSelector: 'svg', }); }); - it('Switch between simple and custom sql tabs', () => { - cy.visitChartByName('Num Births Trend'); - cy.verifySliceSuccess({ waitAlias: '@postJson' }); - + it('Switch from custom sql tabs to simple', () => { cy.get('[data-test=metrics]').within(() => { - cy.get('.select-clear').click(); - cy.get('.Select-control').click({ force: true }); - cy.get('input').type('sum_girls', { force: true }); - cy.get('.VirtualizedSelectFocusedOption').trigger('mousedown').click(); + 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)', { - force: true, - }); + cy.get('.ace_text-input').type('{selectall}{backspace}SUM(num)'); cy.get('#adhoc-metric-edit-tabs-tab-SIMPLE').click(); - cy.get('.select-value-label').contains('num'); + 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', }); }); @@ -152,16 +167,13 @@ describe('AdhocFilters', () => { cy.verifySliceSuccess({ waitAlias: '@postJson' }); cy.get('[data-test=adhoc_filters]').within(() => { - cy.get('.Select-control').click({ force: true }); - cy.get('input').type('name', { force: true }); - cy.get('.VirtualizedSelectFocusedOption').trigger('mousedown').click(); + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('name{enter}'); }); - cy.get('.adhoc-filter-option').click({ force: true }); cy.get('#filter-edit-popover').within(() => { cy.get('[data-test=adhoc-filter-simple-value]').within(() => { - cy.get('div.select-input').click({ force: true }); - cy.get('input.select-input').type('Amy', { force: true }); - cy.get('.VirtualizedSelectFocusedOption').trigger('mousedown').click(); + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('Any{enter}'); }); cy.get('button').contains('Save').click(); }); @@ -178,16 +190,14 @@ describe('AdhocFilters', () => { cy.verifySliceSuccess({ waitAlias: '@postJson' }); cy.get('[data-test=adhoc_filters]').within(() => { - cy.get('.Select-control').click({ force: true }); - cy.get('input').type('name', { force: true }); - cy.get('.VirtualizedSelectFocusedOption').trigger('mousedown').click(); + cy.get('.Select__control').click(); + cy.get('input[type=text]').type('name{enter}'); }); - cy.get('.adhoc-filter-option').click({ force: true }); 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'", { force: true }); + cy.get('.ace_text-input').type("'Amy' OR name = 'Bob'"); cy.get('button').contains('Save').click(); }); @@ -211,17 +221,15 @@ describe('Advanced analytics', () => { cy.visitChartByName('Num Births Trend'); cy.verifySliceSuccess({ waitAlias: '@postJson' }); - cy.get('span') - .contains('Advanced Analytics') - .parent() - .siblings() - .first() - .click(); + cy.get('.panel-title').contains('Advanced Analytics').click(); cy.get('[data-test=time_compare]').within(() => { - cy.get('.Select-control').click({ force: true }); - cy.get('input').type('364 days', { force: true }); - cy.get('.VirtualizedSelectOption').trigger('mousedown').click(); + 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(); @@ -233,7 +241,8 @@ describe('Advanced analytics', () => { }); cy.get('[data-test=time_compare]').within(() => { - cy.get('.select-value-label').contains('364 days'); + cy.get('.Select__multi-value__label').contains('364 days'); + cy.get('.Select__multi-value__label').contains('28 days'); }); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/link.test.js b/superset-frontend/cypress-base/cypress/integration/explore/link.test.js index 4ba56b510..e8bc56ced 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/link.test.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/link.test.js @@ -105,7 +105,7 @@ describe('Test explore links', () => { cy.verifySliceSuccess({ waitAlias: '@postJson' }); cy.get('[data-test=groupby]').within(() => { - cy.get('span.select-clear-zone').click(); + cy.get('.Select__clear-indicator').click(); }); cy.get('button[data-target="#save_modal"]').click(); cy.get('.modal-content').within(() => { @@ -149,11 +149,11 @@ describe('Test explore links', () => { cy.get('.modal-content').within(() => { cy.get('input[name=new_slice_name]').type(chartName); cy.get('input[data-test=add-to-existing-dashboard]').check(); - cy.get('.select.save-modal-selector') + cy.get('.save-modal-selector') .click() .within(() => { - cy.get('input').type(dashboardTitle, { force: true }); - cy.get('.select-option.is-focused').trigger('mousedown'); + cy.get('input').type(dashboardTitle); + cy.get('.Select__option--is-focused').trigger('mousedown'); }); cy.get('button#btn_modal_save').click(); }); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.js index a3c5a33ce..229b05b0d 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/area.js @@ -91,7 +91,6 @@ export default () => comparator: ['South Asia', 'North America'], clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.js index 3fa45cf79..fca86278d 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/big_number_total.js @@ -53,7 +53,6 @@ export default () => comparator: ['Aaron', 'Amy', 'Andrea'], clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_4y6teao56zs_ebjsvwy48c', }, ]; diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.js index 86a0ca22f..5d4b8f376 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/box_plot.js @@ -63,7 +63,6 @@ export default () => comparator: 'South Asia', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.js index 563b45330..d9d735e45 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/bubble.js @@ -74,7 +74,6 @@ export default () => comparator: 'South+Asia', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_b2tfg1rs8y_8kmrcyxvsqd', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.js index 2693eb267..c28ed9b39 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/compare.js @@ -82,7 +82,6 @@ export default () => comparator: 'boy', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_tqx1en70hh_7nksse7nqic', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.js index b86b0b4d5..da302d19e 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/dual_line.js @@ -62,7 +62,6 @@ export default () => comparator: 'girl', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_1ep6q50g8vk_48jj6qxdems', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.js index ec1957f0e..c732691a2 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/histogram.js @@ -72,7 +72,6 @@ export default () => comparator: 'boy', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_tqx1en70hh_7nksse7nqic', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.js index 832b6743f..39ae873f6 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pie.js @@ -67,7 +67,6 @@ export default () => comparator: 'boy', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_tqx1en70hh_7nksse7nqic', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.js index 88746086e..7157d8ab1 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/pivot_table.js @@ -50,7 +50,6 @@ export default () => }, aggregate: 'SUM', hasCustomLabel: false, - fromFormData: false, label: 'SUM(sum_boys)', optionName: 'metric_gvpdjt0v2qf_6hkf56o012', }; diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.js index 45b81bc84..0d1de9422 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sankey.js @@ -60,7 +60,6 @@ export default () => subject: null, operator: null, comparator: null, - fromFormData: false, filterOptionName: 'filter_jbdwe0hayaj_h9jfer8fy58', }, { @@ -70,7 +69,6 @@ export default () => comparator: 'Energy', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_8e0otka9uif_vmqri4gmbqc', }, ], 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 8f0161cfa..77c3ee5f5 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 @@ -59,7 +59,6 @@ export const NUM_METRIC = { aggregate: 'SUM', sqlExpression: null, hasCustomLabel: false, - fromFormData: false, label: 'Sum(num)', optionName: 'metric_1de0s4viy5d_ly7y8k6ghvk', }; @@ -71,6 +70,5 @@ export const SIMPLE_FILTER = { comparator: ['Aaron', 'Amy', 'Andrea'], clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_4y6teao56zs_ebjsvwy48c', }; diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.js index fca07731b..adaaaf82b 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/sunburst.js @@ -76,7 +76,6 @@ export default () => comparator: ['South Asia', 'North America'], clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.js index b9294ceae..c5e5b0419 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.js @@ -152,7 +152,6 @@ export default () => column: null, aggregate: null, hasCustomLabel: true, - fromFormData: true, label: '%+Girls', optionName: 'metric_6qwzgc8bh2v_zox7hil1mzs', }; diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.js index 16560aa0e..c865262ba 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/treemap.js @@ -70,7 +70,6 @@ export default () => comparator: 'South Asia', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l', }, ], diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.js b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.js index 6db348b2d..0b60857c1 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.js +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/world_map.js @@ -63,7 +63,6 @@ export default () => comparator: 'South Asia', clause: 'WHERE', sqlExpression: null, - fromFormData: true, filterOptionName: 'filter_8aqxcf5co1a_x7lm2d1fq0l', }, ], diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index ccd71fa18..9f8653b27 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -4465,6 +4465,18 @@ "requires": { "prop-types": "^15.5.10", "react-select": "^1.2.1" + }, + "dependencies": { + "react-select": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-1.3.0.tgz", + "integrity": "sha512-g/QAU1HZrzSfxkwMAo/wzi6/ezdWye302RGZevsATec07hI/iSxcpB1hejFIp7V63DJ8mwuign6KmB3VjdlinQ==", + "requires": { + "classnames": "^2.2.4", + "prop-types": "^15.5.8", + "react-input-autosize": "^2.1.2" + } + } } }, "@data-ui/histogram": { @@ -7508,9 +7520,9 @@ "dev": true }, "@types/react": { - "version": "16.9.23", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.23.tgz", - "integrity": "sha512-SsGVT4E7L2wLN3tPYLiF20hmZTPGuzaayVunfgXzUn1x4uHVsKH6QDJQ/TdpHqwsTLd4CwrmQ2vOgxN7gE24gw==", + "version": "16.9.34", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz", + "integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" @@ -7525,10 +7537,9 @@ } }, "@types/react-dom": { - "version": "16.9.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz", - "integrity": "sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==", - "dev": true, + "version": "16.9.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.7.tgz", + "integrity": "sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA==", "requires": { "@types/react": "*" } @@ -7590,11 +7601,13 @@ } }, "@types/react-select": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-1.3.4.tgz", - "integrity": "sha512-0BwjswNzKBszG5O4xq72W54NrrbmOZvJfaM/Dwru3F6DhvFO9nihMP1IRzXSOJ1qGRCS3VCu9FnBYJ+25lSldw==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-3.0.12.tgz", + "integrity": "sha512-3NVEc1sbaNtI1b06smzr9dlNKTkYWttL27CdEsorMvd2EgTOM/PJmrzkClaVQmBDg52MzQO05xVwNZruEUKpHw==", "requires": { - "@types/react": "*" + "@types/react": "*", + "@types/react-dom": "*", + "@types/react-transition-group": "*" } }, "@types/react-table": { @@ -7606,6 +7619,14 @@ "@types/react": "*" } }, + "@types/react-transition-group": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz", + "integrity": "sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==", + "requires": { + "@types/react": "*" + } + }, "@types/react-ultimate-pagination": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/react-ultimate-pagination/-/react-ultimate-pagination-1.2.0.tgz", @@ -7615,6 +7636,14 @@ "@types/react": "*" } }, + "@types/react-window": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.2.tgz", + "integrity": "sha512-gP1xam68Wc4ZTAee++zx6pTdDAH08rAkQrWm4B4F/y6hhmlT9Mgx2q8lTCXnrPHXsr15XjRN9+K2DLKcz44qEQ==", + "requires": { + "@types/react": "*" + } + }, "@types/rison": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/rison/-/rison-0.0.6.tgz", @@ -8897,6 +8926,11 @@ "es-abstract": "^1.7.0" } }, + "array-move": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/array-move/-/array-move-2.2.1.tgz", + "integrity": "sha512-qQpEHBnVT6HAFgEVUwRdHVd8TYJThrZIT5wSXpEUTPwBaYhPLclw12mEpyUvRWVdl1VwPOqnIy6LqTFN3cSeUQ==" + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -20582,6 +20616,11 @@ } } }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, "memory-fs": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", @@ -24206,12 +24245,20 @@ } }, "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "prop-types-exact": { @@ -24686,9 +24733,9 @@ } }, "react-input-autosize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.1.tgz", - "integrity": "sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", + "integrity": "sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw==", "requires": { "prop-types": "^15.5.8" } @@ -25005,13 +25052,53 @@ } }, "react-select": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-1.2.1.tgz", - "integrity": "sha512-vaCgT2bEl+uTyE/uKOEgzE5Dc/wLtzhnBvoHCeuLoJWc4WuadN6WQDhoL42DW+TziniZK2Gaqe/wUXydI3NSaQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.1.0.tgz", + "integrity": "sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g==", "requires": { - "classnames": "^2.2.4", - "prop-types": "^15.5.8", - "react-input-autosize": "^2.1.2" + "@babel/runtime": "^7.4.4", + "@emotion/cache": "^10.0.9", + "@emotion/core": "^10.0.9", + "@emotion/css": "^10.0.9", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.0", + "react-input-autosize": "^2.2.2", + "react-transition-group": "^4.3.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "dom-helpers": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", + "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^2.6.7" + } + }, + "react-transition-group": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } } }, "react-select-fast-filter-options": { @@ -25023,12 +25110,12 @@ } }, "react-sortable-hoc": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-0.8.4.tgz", - "integrity": "sha512-J9AFEQAJ7u2YWdVzkU5E3ewrG82xQ4xF1ZPrZYKliDwlVBDkmjri+iKFAEt6NCDIRiBZ4hiN5vzI8pwy/dGPHw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz", + "integrity": "sha512-v1CDCvdfoR3zLGNp6qsBa4J1BWMEVH25+UKxF/RvQRh+mrB+emqtVHMgZ+WreUiKJoEaiwYoScaueIKhMVBHUg==", "requires": { - "babel-runtime": "^6.11.6", - "invariant": "^2.2.1", + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", "prop-types": "^15.5.7" } }, @@ -25155,6 +25242,27 @@ "prop-types": "^15.5.8", "react-select": "^1.0.0-rc.2", "react-virtualized": "^9.0.0" + }, + "dependencies": { + "react-select": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-1.3.0.tgz", + "integrity": "sha512-g/QAU1HZrzSfxkwMAo/wzi6/ezdWye302RGZevsATec07hI/iSxcpB1hejFIp7V63DJ8mwuign6KmB3VjdlinQ==", + "requires": { + "classnames": "^2.2.4", + "prop-types": "^15.5.8", + "react-input-autosize": "^2.1.2" + } + } + } + }, + "react-window": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.5.tgz", + "integrity": "sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==", + "requires": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" } }, "react-with-styles": { diff --git a/superset-frontend/package.json b/superset-frontend/package.json index b089bf814..c41c506e4 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -99,12 +99,15 @@ "@superset-ui/translation": "^0.13.3", "@superset-ui/validator": "^0.13.3", "@types/classnames": "^2.2.9", + "@types/react-bootstrap": "^0.32.21", "@types/react-json-tree": "^0.6.11", - "@types/react-select": "^1.2.1", + "@types/react-select": "^3.0.12", + "@types/react-window": "^1.8.2", "@types/rison": "0.0.6", "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "aphrodite": "^2.3.1", + "array-move": "^2.2.1", "bootstrap": "^3.4.1", "bootstrap-slider": "^10.0.0", "brace": "^0.11.1", @@ -130,7 +133,7 @@ "mousetrap": "^1.6.1", "mustache": "^2.2.1", "omnibar": "^2.1.1", - "prop-types": "^15.6.0", + "prop-types": "^15.7.2", "re-resizable": "^4.3.1", "react": "^16.13.0", "react-ace": "^5.10.0", @@ -151,9 +154,9 @@ "react-redux": "^5.0.2", "react-router-dom": "^5.1.2", "react-search-input": "^0.11.3", - "react-select": "1.2.1", + "react-select": "^3.1.0", "react-select-fast-filter-options": "^0.2.1", - "react-sortable-hoc": "^0.8.3", + "react-sortable-hoc": "^1.11.0", "react-split": "^2.0.4", "react-sticky": "^6.0.2", "react-syntax-highlighter": "^7.0.4", @@ -161,7 +164,9 @@ "react-transition-group": "^2.5.3", "react-ultimate-pagination": "^1.2.0", "react-virtualized": "9.19.1", + "react-virtualized-auto-sizer": "^1.0.2", "react-virtualized-select": "^3.1.3", + "react-window": "^1.8.5", "reactable-arc": "0.14.42", "redux": "^3.5.2", "redux-localstorage": "^0.4.1", @@ -192,8 +197,8 @@ "@types/dom-to-image": "^2.6.0", "@types/jest": "^25.1.4", "@types/jquery": "^3.3.32", - "@types/react": "^16.9.23", - "@types/react-dom": "^16.9.5", + "@types/react": "^16.9.34", + "@types/react-dom": "^16.9.6", "@types/react-json-tree": "^0.6.11", "@types/react-redux": "^7.1.7", "@types/react-table": "^7.0.2", diff --git a/superset-frontend/spec/javascripts/addSlice/AddSliceContainer_spec.jsx b/superset-frontend/spec/javascripts/addSlice/AddSliceContainer_spec.jsx index 9ca11cbaa..6955c7c8b 100644 --- a/superset-frontend/spec/javascripts/addSlice/AddSliceContainer_spec.jsx +++ b/superset-frontend/spec/javascripts/addSlice/AddSliceContainer_spec.jsx @@ -19,8 +19,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Button } from 'react-bootstrap'; -import Select from 'react-virtualized-select'; - +import Select from 'src/components/Select'; import AddSliceContainer from 'src/addSlice/AddSliceContainer'; import VizTypeControl from 'src/explore/components/controls/VizTypeControl'; diff --git a/superset-frontend/spec/javascripts/components/AsyncSelect_spec.jsx b/superset-frontend/spec/javascripts/components/AsyncSelect_spec.jsx index 6721a045d..18970c11f 100644 --- a/superset-frontend/spec/javascripts/components/AsyncSelect_spec.jsx +++ b/superset-frontend/spec/javascripts/components/AsyncSelect_spec.jsx @@ -17,10 +17,9 @@ * under the License. */ import React from 'react'; -import Select from 'react-select'; import { shallow } from 'enzyme'; import fetchMock from 'fetch-mock'; - +import Select from 'src/components/Select'; import AsyncSelect from 'src/components/AsyncSelect'; describe('AsyncSelect', () => { diff --git a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx index 6004e14df..d06175a69 100644 --- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx +++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx @@ -20,7 +20,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { MenuItem, Pagination } from 'react-bootstrap'; -import Select from 'react-select'; +import Select from 'src/components/Select'; import { QueryParamProvider } from 'use-query-params'; import ListView from 'src/components/ListView/ListView'; diff --git a/superset-frontend/spec/javascripts/components/OnPasteSelect_spec.jsx b/superset-frontend/spec/javascripts/components/OnPasteSelect_spec.jsx index 0c3c6cbed..ec9cb953c 100644 --- a/superset-frontend/spec/javascripts/components/OnPasteSelect_spec.jsx +++ b/superset-frontend/spec/javascripts/components/OnPasteSelect_spec.jsx @@ -20,14 +20,16 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import VirtualizedSelect from 'react-virtualized-select'; -import Select, { Creatable } from 'react-select'; - -import OnPasteSelect from 'src/components/OnPasteSelect'; +import { + Select, + AsyncSelect, + OnPasteSelect, + CreatableSelect, +} from 'src/components/Select'; const defaultProps = { onChange: sinon.spy(), - multi: true, + isMulti: true, isValidNewOption: sinon.spy(s => !!s.label), value: [], options: [ @@ -60,17 +62,16 @@ describe('OnPasteSelect', () => { }); it('renders the supplied selectWrap component', () => { - const select = wrapper.find(Select); + const select = wrapper.findWhere(x => x.type() === Select); expect(select).toHaveLength(1); }); it('renders custom selectWrap components', () => { - props.selectWrap = Creatable; + props.selectWrap = CreatableSelect; wrapper = shallow(); - expect(wrapper.find(Creatable)).toHaveLength(1); - props.selectWrap = VirtualizedSelect; - wrapper = shallow(); - expect(wrapper.find(VirtualizedSelect)).toHaveLength(1); + expect(wrapper.findWhere(x => x.type() === CreatableSelect)).toHaveLength( + 1, + ); }); describe('onPaste', () => { diff --git a/superset-frontend/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx b/superset-frontend/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx deleted file mode 100644 index d93343afa..000000000 --- a/superset-frontend/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx +++ /dev/null @@ -1,120 +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. - */ -/* eslint-disable no-unused-expressions */ -import React from 'react'; -import sinon from 'sinon'; -import PropTypes from 'prop-types'; -import { shallow } from 'enzyme'; - -import VirtualizedRendererWrap from 'src/components/VirtualizedRendererWrap'; - -const defaultProps = { - focusedOption: { label: 'focusedOn', value: 'focusedOn' }, - focusOption: sinon.spy(), - key: 'key1', - option: { label: 'option1', value: 'option1' }, - selectValue: sinon.spy(), - valueArray: [], -}; - -function TestOption({ option }) { - return {option.label}; -} -TestOption.propTypes = { - option: PropTypes.object.isRequired, -}; - -const defaultRenderer = opt => ; -const RendererWrap = VirtualizedRendererWrap(defaultRenderer); - -describe('VirtualizedRendererWrap', () => { - let wrapper; - let props; - beforeEach(() => { - wrapper = shallow(); - props = { ...defaultProps }; - }); - - it('uses the provided renderer', () => { - const option = wrapper.find(TestOption); - expect(option).toHaveLength(1); - }); - - it('renders nothing when no option is provided', () => { - props.option = null; - wrapper = shallow(); - const option = wrapper.find(TestOption); - expect(option).toHaveLength(0); - }); - - it('renders unfocused, unselected options with the default class', () => { - const optionDiv = wrapper.find('div'); - expect(optionDiv).toHaveLength(1); - expect(optionDiv.props().className).toBe('VirtualizedSelectOption'); - }); - - it('renders focused option with the correct class', () => { - props.option = props.focusedOption; - wrapper = shallow(); - const optionDiv = wrapper.find('div'); - expect(optionDiv.props().className).toBe( - 'VirtualizedSelectOption VirtualizedSelectFocusedOption', - ); - }); - - it('renders disabled option with the correct class', () => { - props.option.disabled = true; - wrapper = shallow(); - const optionDiv = wrapper.find('div'); - expect(optionDiv.props().className).toBe( - 'VirtualizedSelectOption VirtualizedSelectDisabledOption', - ); - props.option.disabled = false; - }); - - it('renders selected option with the correct class', () => { - props.valueArray = [props.option, props.focusedOption]; - wrapper = shallow(); - const optionDiv = wrapper.find('div'); - expect(optionDiv.props().className).toBe( - 'VirtualizedSelectOption VirtualizedSelectSelectedOption', - ); - }); - - it('renders options with custom classes', () => { - props.option.className = 'CustomClass'; - wrapper = shallow(); - const optionDiv = wrapper.find('div'); - expect(optionDiv.props().className).toBe( - 'VirtualizedSelectOption CustomClass', - ); - }); - - it('calls focusedOption on its own option onMouseEnter', () => { - const optionDiv = wrapper.find('div'); - optionDiv.simulate('mouseEnter'); - expect(props.focusOption.calledWith(props.option)).toBe(true); - }); - - it('calls selectValue on its own option onClick', () => { - const optionDiv = wrapper.find('div'); - optionDiv.simulate('click'); - expect(props.selectValue.calledWith(props.option)).toBe(true); - }); -}); diff --git a/superset-frontend/spec/javascripts/explore/AdhocFilter_spec.js b/superset-frontend/spec/javascripts/explore/AdhocFilter_spec.js index 3e56d3e78..7c0016646 100644 --- a/superset-frontend/spec/javascripts/explore/AdhocFilter_spec.js +++ b/superset-frontend/spec/javascripts/explore/AdhocFilter_spec.js @@ -39,13 +39,14 @@ describe('AdhocFilter', () => { clause: CLAUSES.WHERE, filterOptionName: adhocFilter.filterOptionName, sqlExpression: null, - fromFormData: false, isExtra: false, + isNew: false, }); }); it('can create altered duplicates', () => { const adhocFilter1 = new AdhocFilter({ + isNew: true, expressionType: EXPRESSION_TYPES.SIMPLE, subject: 'value', operator: '>', @@ -61,6 +62,10 @@ describe('AdhocFilter', () => { expect(adhocFilter1.operator).toBe('>'); expect(adhocFilter2.operator).toBe('<'); + + // duplicated clone should not be new + expect(adhocFilter1.isNew).toBe(true); + expect(adhocFilter2.isNew).toStrictEqual(false); }); it('can verify equality', () => { diff --git a/superset-frontend/spec/javascripts/explore/AdhocMetric_spec.js b/superset-frontend/spec/javascripts/explore/AdhocMetric_spec.js index 696495d5e..0dbb2d9cd 100644 --- a/superset-frontend/spec/javascripts/explore/AdhocMetric_spec.js +++ b/superset-frontend/spec/javascripts/explore/AdhocMetric_spec.js @@ -32,11 +32,11 @@ describe('AdhocMetric', () => { expressionType: EXPRESSION_TYPES.SIMPLE, column: valueColumn, aggregate: AGGREGATES.SUM, - fromFormData: false, label: 'SUM(value)', hasCustomLabel: false, optionName: adhocMetric.optionName, sqlExpression: null, + isNew: false, }); }); @@ -44,6 +44,7 @@ describe('AdhocMetric', () => { const adhocMetric1 = new AdhocMetric({ column: valueColumn, aggregate: AGGREGATES.SUM, + isNew: true, }); const adhocMetric2 = adhocMetric1.duplicateWith({ aggregate: AGGREGATES.AVG, @@ -54,6 +55,10 @@ describe('AdhocMetric', () => { expect(adhocMetric1.aggregate).toBe(AGGREGATES.SUM); expect(adhocMetric2.aggregate).toBe(AGGREGATES.AVG); + + // duplicated clone should not be new + expect(adhocMetric1.isNew).toBe(true); + expect(adhocMetric2.isNew).toStrictEqual(false); }); it('can verify equality', () => { diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx index 0fe614edd..7b8c9c228 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx @@ -21,6 +21,7 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; +import OnPasteSelect from 'src/components/Select/OnPasteSelect'; import AdhocFilter, { EXPRESSION_TYPES, CLAUSES, @@ -28,7 +29,6 @@ import AdhocFilter, { import AdhocFilterControl from 'src/explore/components/controls/AdhocFilterControl'; import AdhocMetric from 'src/explore/AdhocMetric'; import { AGGREGATES, OPERATORS } from 'src/explore/constants'; -import OnPasteSelect from 'src/components/OnPasteSelect'; const simpleAdhocFilter = new AdhocFilter({ expressionType: EXPRESSION_TYPES.SIMPLE, diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx index ea0295a20..3938abd09 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx @@ -95,58 +95,50 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => { .instance() .onSubjectChange({ type: 'VARCHAR(255)', column_name: 'source' }); expect(onChange.calledOnce).toBe(true); - expect( - onChange.lastCall.args[0].equals( - simpleAdhocFilter.duplicateWith({ subject: 'source' }), - ), - ).toBe(true); + expect(onChange.lastCall.args[0]).toEqual( + simpleAdhocFilter.duplicateWith({ subject: 'source' }), + ); }); it('may alter the clause in onSubjectChange if the old clause is not appropriate', () => { const { wrapper, onChange } = setup(); wrapper.instance().onSubjectChange(sumValueAdhocMetric); expect(onChange.calledOnce).toBe(true); - expect( - onChange.lastCall.args[0].equals( - simpleAdhocFilter.duplicateWith({ - subject: sumValueAdhocMetric.label, - clause: CLAUSES.HAVING, - }), - ), - ).toBe(true); + expect(onChange.lastCall.args[0]).toEqual( + simpleAdhocFilter.duplicateWith({ + subject: sumValueAdhocMetric.label, + clause: CLAUSES.HAVING, + }), + ); }); it('will convert from individual comparator to array if the operator changes to multi', () => { const { wrapper, onChange } = setup(); - wrapper.instance().onOperatorChange({ operator: 'in' }); + wrapper.instance().onOperatorChange('in'); expect(onChange.calledOnce).toBe(true); - expect(onChange.lastCall.args[0].comparator).toHaveLength(1); - expect(onChange.lastCall.args[0].comparator[0]).toBe('10'); - expect(onChange.lastCall.args[0].operator).toBe('in'); + expect(onChange.lastCall.args[0]).toEqual( + simpleAdhocFilter.duplicateWith({ operator: 'in', comparator: ['10'] }), + ); }); it('will convert from array to individual comparators if the operator changes from multi', () => { const { wrapper, onChange } = setup({ adhocFilter: simpleMultiAdhocFilter, }); - wrapper.instance().onOperatorChange({ operator: '<' }); + wrapper.instance().onOperatorChange('<'); expect(onChange.calledOnce).toBe(true); - expect( - onChange.lastCall.args[0].equals( - simpleAdhocFilter.duplicateWith({ operator: '<', comparator: '10' }), - ), - ).toBe(true); + expect(onChange.lastCall.args[0]).toEqual( + simpleMultiAdhocFilter.duplicateWith({ operator: '<', comparator: '10' }), + ); }); it('passes the new adhocFilter to onChange after onComparatorChange', () => { const { wrapper, onChange } = setup(); wrapper.instance().onComparatorChange('20'); expect(onChange.calledOnce).toBe(true); - expect( - onChange.lastCall.args[0].equals( - simpleAdhocFilter.duplicateWith({ comparator: '20' }), - ), - ).toBe(true); + expect(onChange.lastCall.args[0]).toEqual( + simpleAdhocFilter.duplicateWith({ comparator: '20' }), + ); }); it('will filter operators for table datasources', () => { @@ -195,20 +187,17 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => { partitionColumn: 'ds', }); - wrapper.instance().onOperatorChange({ operator: 'LATEST PARTITION' }); - expect( - onChange.lastCall.args[0].equals( - testAdhocFilter.duplicateWith({ - subject: 'ds', - operator: 'LATEST PARTITION', - comparator: null, - clause: 'WHERE', - expressionType: 'SQL', - sqlExpression: - "ds = '{{ presto.latest_partition('schema.table1') }}' ", - }), - ), - ).toBe(true); + wrapper.instance().onOperatorChange('LATEST PARTITION'); + expect(onChange.lastCall.args[0]).toEqual( + testAdhocFilter.duplicateWith({ + subject: 'ds', + operator: 'LATEST PARTITION', + comparator: null, + clause: 'WHERE', + expressionType: 'SQL', + sqlExpression: "ds = '{{ presto.latest_partition('schema.table1') }}' ", + }), + ); }); it('expands when its multi comparator input field expands', () => { diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx index eb5452b73..02607c5e4 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx @@ -52,7 +52,15 @@ function setup(overrides) { describe('AdhocFilterOption', () => { it('renders an overlay trigger wrapper for the label', () => { const { wrapper } = setup(); - expect(wrapper.find(OverlayTrigger)).toHaveLength(1); + const overlay = wrapper.find(OverlayTrigger); + expect(overlay).toHaveLength(1); + expect(overlay.props().defaultOverlayShown).toBe(false); expect(wrapper.find(Label)).toHaveLength(1); }); + it('should open new filter popup by default', () => { + const { wrapper } = setup({ + adhocFilter: simpleAdhocFilter.duplicateWith({ isNew: true }), + }); + expect(wrapper.find(OverlayTrigger).props().defaultOverlayShown).toBe(true); + }); }); diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx index 250e5d047..892874f83 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx @@ -75,7 +75,7 @@ describe('AdhocMetricEditPopover', () => { it('overwrites the adhocMetric in state with onAggregateChange', () => { const { wrapper } = setup(); - wrapper.instance().onAggregateChange({ aggregate: AGGREGATES.AVG }); + wrapper.instance().onAggregateChange(AGGREGATES.AVG); expect(wrapper.state('adhocMetric')).toEqual( sumValueAdhocMetric.duplicateWith({ aggregate: AGGREGATES.AVG }), ); diff --git a/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx index 7ba120bd0..3409543fc 100644 --- a/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx @@ -55,4 +55,11 @@ describe('AdhocMetricOption', () => { expect(wrapper.find(OverlayTrigger)).toHaveLength(1); expect(wrapper.find(Label)).toHaveLength(1); }); + + it('overlay should open if metric is new', () => { + const { wrapper } = setup({ + adhocMetric: sumValueAdhocMetric.duplicateWith({ isNew: true }), + }); + expect(wrapper.find(OverlayTrigger).props().defaultOverlayShown).toBe(true); + }); }); diff --git a/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx index 2913775db..4cf618b16 100644 --- a/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx @@ -19,7 +19,7 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { mount } from 'enzyme'; -import { Creatable } from 'react-select'; +import Creatable from 'react-select/creatable'; import { getCategoricalSchemeRegistry } from '@superset-ui/color'; import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl'; diff --git a/superset-frontend/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx b/superset-frontend/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx index 66a1751a2..25f16a1b7 100644 --- a/superset-frontend/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx @@ -18,8 +18,8 @@ */ import React from 'react'; import { mount } from 'enzyme'; -import { DisplayQueryButton } from 'src/explore/components/DisplayQueryButton'; import ModalTrigger from 'src/components/ModalTrigger'; +import { DisplayQueryButton } from 'src/explore/components/DisplayQueryButton'; describe('DisplayQueryButton', () => { const defaultProps = { diff --git a/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx index 674287794..c810d3ff1 100644 --- a/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx @@ -23,7 +23,7 @@ import { shallow } from 'enzyme'; import MetricsControl from 'src/explore/components/controls/MetricsControl'; import { AGGREGATES } from 'src/explore/constants'; -import OnPasteSelect from 'src/components/OnPasteSelect'; +import OnPasteSelect from 'src/components/Select/OnPasteSelect'; import AdhocMetric, { EXPRESSION_TYPES } from 'src/explore/AdhocMetric'; const defaultProps = { @@ -138,11 +138,11 @@ describe('MetricsControl', () => { expressionType: EXPRESSION_TYPES.SIMPLE, column: { type: 'double', column_name: 'value' }, aggregate: AGGREGATES.SUM, - fromFormData: true, label: 'SUM(value)', hasCustomLabel: false, optionName: 'blahblahblah', sqlExpression: null, + isNew: false, }, 'avg__value', ]); @@ -163,7 +163,8 @@ describe('MetricsControl', () => { select.simulate('change', [valueColumn]); const adhocMetric = onChange.lastCall.args[0][0]; - expect(adhocMetric instanceof AdhocMetric).toBe(true); + expect(adhocMetric).toBeInstanceOf(AdhocMetric); + expect(adhocMetric.isNew).toBe(true); expect(onChange.lastCall.args).toEqual([ [ { @@ -171,35 +172,46 @@ describe('MetricsControl', () => { column: valueColumn, aggregate: AGGREGATES.SUM, label: 'SUM(value)', - fromFormData: false, hasCustomLabel: false, optionName: adhocMetric.optionName, sqlExpression: null, + isNew: true, }, ], ]); }); - it('handles aggregates being selected', () => { + it('handles aggregates being selected', done => { const { wrapper, onChange } = setup(); const select = wrapper.find(OnPasteSelect); // mock out the Select ref - const setInputSpy = sinon.spy(); - const handleInputSpy = sinon.spy(); - wrapper.instance().select = { - setInputValue: setInputSpy, - handleInputChange: handleInputSpy, - input: { input: {} }, - }; + const instance = wrapper.instance(); + const handleInputChangeSpy = jest.fn(); + const focusInputSpy = jest.fn(); + // simulate react-select StateManager + instance.selectRef({ + select: { + handleInputChange: handleInputChangeSpy, + inputRef: { value: '' }, + focusInput: focusInputSpy, + }, + }); select.simulate('change', [{ aggregate_name: 'SUM', optionName: 'SUM' }]); - expect(setInputSpy.calledWith('SUM()')).toBe(true); - expect(handleInputSpy.calledWith({ target: { value: 'SUM()' } })).toBe( - true, - ); - expect(onChange.lastCall.args).toEqual([[]]); + expect(instance.select.inputRef.value).toBe('SUM()'); + expect(handleInputChangeSpy).toHaveBeenCalledWith({ + currentTarget: { value: 'SUM()' }, + }); + expect(onChange.calledOnceWith([])).toBe(true); + expect(focusInputSpy).toHaveBeenCalledTimes(0); + setTimeout(() => { + expect(focusInputSpy).toHaveBeenCalledTimes(1); + expect(instance.select.inputRef.selectionStart).toBe(4); + expect(instance.select.inputRef.selectionEnd).toBe(4); + done(); + }); }); it('preserves existing selected AdhocMetrics', () => { @@ -255,9 +267,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - metric_name: 'a_metric', - optionName: 'a_metric', - expression: 'SUM(FANCY(metric))', + data: { + metric_name: 'a_metric', + optionName: 'a_metric', + expression: 'SUM(FANCY(metric))', + }, }, 'a', ), @@ -270,9 +284,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - metric_name: 'avg__metric', - optionName: 'avg__metric', - expression: 'AVG(metric)', + data: { + metric_name: 'avg__metric', + optionName: 'avg__metric', + expression: 'AVG(metric)', + }, }, 'a', ), @@ -285,9 +301,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - type: 'VARCHAR(255)', - column_name: 'source', - optionName: '_col_source', + data: { + type: 'VARCHAR(255)', + column_name: 'source', + optionName: '_col_source', + }, }, 'sou', ), @@ -297,7 +315,7 @@ describe('MetricsControl', () => { !!wrapper .instance() .selectFilterOption( - { aggregate_name: 'AVG', optionName: '_aggregate_AVG' }, + { data: { aggregate_name: 'AVG', optionName: '_aggregate_AVG' } }, 'av', ), ).toBe(true); @@ -309,9 +327,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - metric_name: 'sum__num', - verbose_name: 'babies', - optionName: '_col_sum_num', + data: { + metric_name: 'sum__num', + verbose_name: 'babies', + optionName: '_col_sum_num', + }, }, 'bab', ), @@ -324,9 +344,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - metric_name: 'avg__metric', - optionName: 'avg__metric', - expression: 'AVG(metric)', + data: { + metric_name: 'avg__metric', + optionName: 'avg__metric', + expression: 'AVG(metric)', + }, }, 'a', ), @@ -339,9 +361,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - metric_name: 'my_fancy_sum_metric', - optionName: 'my_fancy_sum_metric', - expression: 'SUM(value)', + data: { + metric_name: 'my_fancy_sum_metric', + optionName: 'my_fancy_sum_metric', + expression: 'SUM(value)', + }, }, 'sum', ), @@ -354,9 +378,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - metric_name: 'sum__value', - optionName: 'sum__value', - expression: 'SUM(value)', + data: { + metric_name: 'sum__value', + optionName: 'sum__value', + expression: 'SUM(value)', + }, }, 'sum', ), @@ -365,9 +391,11 @@ describe('MetricsControl', () => { expect( !!wrapper.instance().selectFilterOption( { - metric_name: 'sum__value', - optionName: 'sum__value', - expression: 'SUM("table"."value")', + data: { + metric_name: 'sum__value', + optionName: 'sum__value', + expression: 'SUM("table"."value")', + }, }, 'sum', ), @@ -379,12 +407,12 @@ describe('MetricsControl', () => { wrapper.setState({ aggregateInInput: true }); expect( - !!wrapper - .instance() - .selectFilterOption( - { metric_name: 'metric', expression: 'SUM(FANCY(metric))' }, - 'SUM(', - ), + !!wrapper.instance().selectFilterOption( + { + data: { metric_name: 'metric', expression: 'SUM(FANCY(metric))' }, + }, + 'SUM(', + ), ).toBe(false); }); @@ -395,7 +423,10 @@ describe('MetricsControl', () => { expect( !!wrapper .instance() - .selectFilterOption({ type: 'DOUBLE', column_name: 'value' }, 'SUM('), + .selectFilterOption( + { data: { type: 'DOUBLE', column_name: 'value' } }, + 'SUM(', + ), ).toBe(true); }); diff --git a/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx index bc7ffa7c5..37f576572 100644 --- a/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/SelectControl_spec.jsx @@ -18,12 +18,10 @@ */ /* eslint-disable no-unused-expressions */ import React from 'react'; -import Select, { Creatable } from 'react-select'; -import VirtualizedSelect from 'react-virtualized-select'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import OnPasteSelect from 'src/components/OnPasteSelect'; -import VirtualizedRendererWrap from 'src/components/VirtualizedRendererWrap'; +import { Select, CreatableSelect } from 'src/components/Select'; +import OnPasteSelect from 'src/components/Select/OnPasteSelect'; import SelectControl from 'src/explore/components/controls/SelectControl'; const defaultProps = { @@ -47,16 +45,42 @@ describe('SelectControl', () => { beforeEach(() => { wrapper = shallow(); - wrapper.setProps(defaultProps); }); - it('renders an OnPasteSelect', () => { + it('renders with Select by default', () => { + expect(wrapper.find(OnPasteSelect)).toHaveLength(0); + expect(wrapper.findWhere(x => x.type() === Select)).toHaveLength(1); + }); + + it('renders with OnPasteSelect when multi', () => { + wrapper.setProps({ multi: true }); expect(wrapper.find(OnPasteSelect)).toHaveLength(1); + expect(wrapper.findWhere(x => x.type() === Select)).toHaveLength(0); }); - it('calls onChange when toggled', () => { + it('renders with Creatable when freeForm', () => { + wrapper.setProps({ freeForm: true }); + expect(wrapper.find(OnPasteSelect)).toHaveLength(0); + expect(wrapper.findWhere(x => x.type() === CreatableSelect)).toHaveLength( + 1, + ); + }); + + it('uses Select in onPasteSelect when freeForm=false', () => { + wrapper = shallow(); const select = wrapper.find(OnPasteSelect); - select.simulate('change', { value: 50 }); + expect(select.props().selectWrap).toBe(Select); + }); + + it('uses Creatable in onPasteSelect when freeForm=true', () => { + wrapper = shallow(); + const select = wrapper.find(OnPasteSelect); + expect(select.props().selectWrap).toBe(CreatableSelect); + }); + + it('calls props.onChange when select', () => { + const select = wrapper.instance(); + select.onChange({ value: 50 }); expect(defaultProps.onChange.calledWith(50)).toBe(true); }); @@ -72,36 +96,10 @@ describe('SelectControl', () => { onChange: sinon.spy(), }; wrapper.setProps(selectAllProps); - const select = wrapper.find(OnPasteSelect); - select.simulate('change', [{ meta: true, value: 'Select All' }]); + wrapper.instance().onChange([{ meta: true, value: 'Select All' }]); expect(selectAllProps.onChange.calledWith(expectedValues)).toBe(true); }); - it('passes VirtualizedSelect as selectWrap', () => { - const select = wrapper.find(OnPasteSelect); - expect(select.props().selectWrap).toBe(VirtualizedSelect); - }); - - it('passes Creatable as selectComponent when freeForm=true', () => { - wrapper = shallow(); - const select = wrapper.find(OnPasteSelect); - expect(select.props().selectComponent).toBe(Creatable); - }); - - it('passes Select as selectComponent when freeForm=false', () => { - const select = wrapper.find(OnPasteSelect); - expect(select.props().selectComponent).toBe(Select); - }); - - it('wraps optionRenderer in a VirtualizedRendererWrap', () => { - const select = wrapper.find(OnPasteSelect); - const defaultOptionRenderer = SelectControl.defaultProps.optionRenderer; - const wrappedRenderer = VirtualizedRendererWrap(defaultOptionRenderer); - expect(typeof select.props().optionRenderer).toBe('function'); - // different instances of wrapper with same inner renderer are unequal - expect(select.props().optionRenderer.name).toBe(wrappedRenderer.name); - }); - describe('getOptions', () => { it('returns the correct options', () => { wrapper.setProps(defaultProps); @@ -132,15 +130,18 @@ describe('SelectControl', () => { value: ['one', 'two'], name: 'row_limit', label: 'Row Limit', - valueKey: 'value', + valueKey: 'custom_value_key', onChange: sinon.spy(), }; - const newOptions = [ - { value: 'one', label: 'one' }, - { value: 'two', label: 'two' }, + // the last added option is at the top + const expectedNewOptions = [ + { custom_value_key: 'two', label: 'two' }, + { custom_value_key: 'one', label: 'one' }, ]; wrapper.setProps(freeFormProps); - expect(wrapper.instance().getOptions(freeFormProps)).toEqual(newOptions); + expect(wrapper.instance().getOptions(freeFormProps)).toEqual( + expectedNewOptions, + ); }); }); diff --git a/superset-frontend/spec/javascripts/sqllab/QuerySearch_spec.jsx b/superset-frontend/spec/javascripts/sqllab/QuerySearch_spec.jsx index f2c71f15a..f7af89cd6 100644 --- a/superset-frontend/spec/javascripts/sqllab/QuerySearch_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/QuerySearch_spec.jsx @@ -17,11 +17,11 @@ * under the License. */ import React from 'react'; -import Select from 'react-select'; import { Button } from 'react-bootstrap'; import { shallow } from 'enzyme'; import sinon from 'sinon'; +import Select from 'src/components/Select'; import QuerySearch from 'src/SqlLab/components/QuerySearch'; describe('QuerySearch', () => { @@ -39,7 +39,7 @@ describe('QuerySearch', () => { }); it('should have three Select', () => { - expect(wrapper.find(Select)).toHaveLength(3); + expect(wrapper.findWhere(x => x.type() === Select)).toHaveLength(3); }); it('updates fromTime on user selects from time', () => { diff --git a/superset-frontend/src/SqlLab/components/QuerySearch.jsx b/superset-frontend/src/SqlLab/components/QuerySearch.jsx index d895d54a9..ee5660bdb 100644 --- a/superset-frontend/src/SqlLab/components/QuerySearch.jsx +++ b/superset-frontend/src/SqlLab/components/QuerySearch.jsx @@ -19,7 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Button } from 'react-bootstrap'; -import Select from 'react-select'; +import Select from 'src/components/Select'; import { t } from '@superset-ui/translation'; import { SupersetClient } from '@superset-ui/connection'; diff --git a/superset-frontend/src/SqlLab/main.less b/superset-frontend/src/SqlLab/main.less index ade020aa3..a995a7416 100644 --- a/superset-frontend/src/SqlLab/main.less +++ b/superset-frontend/src/SqlLab/main.less @@ -383,17 +383,17 @@ div.tablePopover { font-family: @font-family-monospace; } -.Select-menu-outer { +.Select__menu-outer { min-width: 100%; width: inherit; z-index: @z-index-dropdown; } -.Select-clear { +.Select__clear-indicator { margin-top: -2px; } -.Select-arrow { +.Select__arrow { margin-top: 5px; } diff --git a/superset-frontend/src/addSlice/AddSliceContainer.jsx b/superset-frontend/src/addSlice/AddSliceContainer.jsx index f304d6695..69fe29688 100644 --- a/superset-frontend/src/addSlice/AddSliceContainer.jsx +++ b/superset-frontend/src/addSlice/AddSliceContainer.jsx @@ -19,7 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Button, Panel } from 'react-bootstrap'; -import Select from 'react-virtualized-select'; +import Select from 'src/components/Select'; import { t } from '@superset-ui/translation'; import VizTypeControl from '../explore/components/controls/VizTypeControl'; diff --git a/superset-frontend/src/components/AsyncSelect.jsx b/superset-frontend/src/components/AsyncSelect.jsx index d7de3984f..2e3d717f6 100644 --- a/superset-frontend/src/components/AsyncSelect.jsx +++ b/superset-frontend/src/components/AsyncSelect.jsx @@ -18,7 +18,8 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import Select from 'react-select'; +// TODO: refactor this with `import { AsyncSelect } from src/components/Select` +import { Select } from 'src/components/Select'; import { t } from '@superset-ui/translation'; import { SupersetClient } from '@superset-ui/connection'; import getClientErrorObject from '../utils/getClientErrorObject'; diff --git a/superset-frontend/src/components/ColumnOption.jsx b/superset-frontend/src/components/ColumnOption.jsx index 0fed0b271..06f663ae4 100644 --- a/superset-frontend/src/components/ColumnOption.jsx +++ b/superset-frontend/src/components/ColumnOption.jsx @@ -42,14 +42,14 @@ export default function ColumnOption({ column, showType }) { } return ( - +
{showType && columnType && } - + {column.verbose_name || column.column_name} {column.description && ( )} - +
); } ColumnOption.propTypes = propTypes; diff --git a/superset-frontend/src/components/ExpandableList.tsx b/superset-frontend/src/components/ExpandableList.tsx index 89332d23c..f827c98af 100644 --- a/superset-frontend/src/components/ExpandableList.tsx +++ b/superset-frontend/src/components/ExpandableList.tsx @@ -17,8 +17,6 @@ * under the License. */ import React, { ReactNode, useState } from 'react'; -// @ts-ignore -import { css } from '@emotion/core'; import Button from 'src/components/Button'; interface Props { diff --git a/superset-frontend/src/components/ListView/Filters.tsx b/superset-frontend/src/components/ListView/Filters.tsx index 13edfd829..a97e4988d 100644 --- a/superset-frontend/src/components/ListView/Filters.tsx +++ b/superset-frontend/src/components/ListView/Filters.tsx @@ -16,19 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import styled from '@superset-ui/style'; import { withTheme } from 'emotion-theming'; -import StyledSelect, { AsyncStyledSelect } from 'src/components/StyledSelect'; +import { + Select, + AsyncSelect, + PartialThemeConfig, + PartialStylesConfig, +} from 'src/components/Select'; import SearchInput from 'src/components/SearchInput'; -import { Filter, Filters, FilterValue, InternalFilter } from './types'; +import { + Filter, + Filters, + FilterValue, + InternalFilter, + SelectOption, +} from './types'; interface BaseFilter { Header: string; initialValue: any; } interface SelectFilterProps extends BaseFilter { + name?: string; onSelect: (selected: any) => any; selects: Filter['selects']; emptyLabel?: string; @@ -37,13 +49,38 @@ interface SelectFilterProps extends BaseFilter { const FilterContainer = styled.div` display: inline-flex; - margin-right: 8px; + margin-right: 2em; `; -const Title = styled.span` +const FilterTitle = styled.label` font-weight: bold; + line-height: 27px; + margin: 0 0.4em 0 0; `; +const filterSelectTheme: PartialThemeConfig = { + spacing: { + baseUnit: 2, + minWidth: '5em', + }, +}; + +const filterSelectStyles: PartialStylesConfig = { + container: (provider, { getValue }) => ({ + ...provider, + // dynamic width based on label string length + minWidth: `${Math.min( + 12, + Math.max(5, 3 + getValue()[0].label.length / 2), + )}em`, + }), + control: provider => ({ + ...provider, + borderWidth: 0, + boxShadow: 'none', + }), +}; + const CLEAR_SELECT_FILTER_VALUE = 'CLEAR_SELECT_FILTER_VALUE'; function SelectFilter({ @@ -59,46 +96,60 @@ function SelectFilter({ value: CLEAR_SELECT_FILTER_VALUE, }; - const options = React.useMemo(() => [clearFilterSelect, ...selects], [ - emptyLabel, - selects, - ]); - - const [value, setValue] = useState( - typeof initialValue === 'undefined' - ? clearFilterSelect.value - : initialValue, + const options = [clearFilterSelect, ...selects]; + const optionsCache: React.MutableRefObject = useRef( + null, ); - const onChange = (selected: { label: string; value: any } | null) => { + + const [selectedOption, setSelectedOption] = useState(clearFilterSelect); + const onChange = (selected: SelectOption | null) => { if (selected === null) return; - setValue(selected.value); onSelect( selected.value === CLEAR_SELECT_FILTER_VALUE ? undefined : selected.value, ); + setSelectedOption(selected); }; - const fetchAndFormatSelects = async () => { - if (!fetchSelects) return { options: [clearFilterSelect] }; - const selectValues = await fetchSelects(); - return { options: [clearFilterSelect, ...selectValues] }; + const fetchAndFormatSelects = async (inputValue: string) => { + // only include clear filter when filter value exists + let result = inputValue ? [] : [clearFilterSelect]; + // only call fetch once + // TODO: allow real async search with `inputValue` + if (optionsCache.current) return optionsCache.current; + if (fetchSelects) { + const selectValues = await fetchSelects(); + // update matching option at initial load + const matchingOption = result.find(x => x.value === initialValue); + if (matchingOption) { + setSelectedOption(matchingOption); + } + result = [...result, ...selectValues]; + } + optionsCache.current = result; + return result; }; return ( - {Header}: + {Header} {fetchSelects ? ( - 'Loading...'} clearable={false} /> ) : ( - {filters.map( - ({ Header, input, selects, unfilteredLabel, fetchSelects }, index) => { + ( + { Header, id, input, selects, unfilteredLabel, fetchSelects }, + index, + ) => { const initialValue = internalFilters[index] && internalFilters[index].value; if (input === 'select') { return ( updateFilterValue(index, value)} diff --git a/superset-frontend/src/components/ListView/LegacyFilters.tsx b/superset-frontend/src/components/ListView/LegacyFilters.tsx index 1b0d40b31..3721a0d03 100644 --- a/superset-frontend/src/components/ListView/LegacyFilters.tsx +++ b/superset-frontend/src/components/ListView/LegacyFilters.tsx @@ -25,13 +25,9 @@ import { FormControl, MenuItem, Row, - // @ts-ignore } from 'react-bootstrap'; -// @ts-ignore -import SelectComponent from 'react-select'; -// @ts-ignore -import VirtualizedSelect from 'react-virtualized-select'; -import { Filters, InternalFilter, Select } from './types'; +import { Select } from 'src/components/Select'; +import { Filters, InternalFilter, SelectOption } from './types'; import { extractInputValue, getDefaultFilterOperator } from './utils'; export const FilterMenu = ({ @@ -68,9 +64,9 @@ export const FilterMenu = ({ key={ft.id} eventKey={ft} // @ts-ignore - onSelect={(fltr: typeof ft) => - setInternalFilters([...internalFilters, fltr]) - } + onSelect={(fltr: typeof ft) => { + setInternalFilters([...internalFilters, fltr]); + }} > {ft.Header} @@ -98,6 +94,7 @@ export const FilterInputs = ({ {internalFilters.map((ft, i) => { const filter = filters.find(f => f.id === ft.id); if (!filter) { + // eslint-disable-next-line no-console console.error(`could not find filter for ${ft.id}`); return null; } @@ -120,26 +117,27 @@ export const FilterInputs = ({ }); }} > - {(filter.operators || []).map(({ label, value }: Select) => ( - - ))} + {(filter.operators || []).map( + ({ label, value }: SelectOption) => ( + + ), + )} {filter.input === 'select' && ( - { + value={ft.value as SelectOption['value'][] | undefined} + onChange={(e: SelectOption[] | null) => { updateInternalFilter(i, { operator: ft.operator || getDefaultFilterOperator(filter), value: e ? e.map(s => s.value) : e, @@ -152,8 +150,9 @@ export const FilterInputs = ({ ) => { e.persist(); updateInternalFilter(i, { diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index 34e65a295..d9160a03b 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -18,17 +18,7 @@ */ import { t } from '@superset-ui/translation'; import React, { FunctionComponent } from 'react'; -import { - Col, - DropdownButton, - MenuItem, - Row, - // @ts-ignore -} from 'react-bootstrap'; -// @ts-ignore -import SelectComponent from 'react-select'; -// @ts-ignore -import VirtualizedSelect from 'react-virtualized-select'; +import { Col, DropdownButton, MenuItem, Row } from 'react-bootstrap'; import IndeterminateCheckbox from '../IndeterminateCheckbox'; import TableCollection from './TableCollection'; import Pagination from './Pagination'; @@ -208,9 +198,9 @@ const ListView: FunctionComponent = ({ {bulkActions.map(action => ( // @ts-ignore { action.onSelect( selectedRows.map((r: any) => r.original), diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts index f8e08a930..0a1cc241b 100644 --- a/superset-frontend/src/components/ListView/types.ts +++ b/superset-frontend/src/components/ListView/types.ts @@ -23,7 +23,7 @@ export interface SortColumn { export type SortColumns = SortColumn[]; -export interface Select { +export interface SelectOption { label: string; value: any; } @@ -31,13 +31,13 @@ export interface Select { export interface Filter { Header: string; id: string; - operators?: Select[]; + operators?: SelectOption[]; operator?: string; input?: 'text' | 'textarea' | 'select' | 'checkbox' | 'search'; unfilteredLabel?: string; - selects?: Select[]; + selects?: SelectOption[]; onFilterOpen?: () => void; - fetchSelects?: () => Promise; + fetchSelects?: () => Promise; } export type Filters = Filter[]; diff --git a/superset-frontend/src/components/MetricOption.jsx b/superset-frontend/src/components/MetricOption.jsx index 06be76323..de5657932 100644 --- a/superset-frontend/src/components/MetricOption.jsx +++ b/superset-frontend/src/components/MetricOption.jsx @@ -50,12 +50,12 @@ export default function MetricOption({ verbose ); return ( -
+
{showType && } - {link} + {link} {metric.description && ( { - if (this.props.refFunc) { - this.props.refFunc(ref); - } - this.pasteInput = ref; - }; - const inputProps = { onPaste: this.onPaste.bind(this) }; - return ( - - ); + const { selectWrap: SelectComponent, ...restProps } = this.props; + return ; } } OnPasteSelect.propTypes = { separator: PropTypes.array.isRequired, - selectWrap: PropTypes.func.isRequired, - refFunc: PropTypes.func, + selectWrap: PropTypes.elementType, + selectRef: PropTypes.func, onChange: PropTypes.func.isRequired, valueKey: PropTypes.string.isRequired, labelKey: PropTypes.string.isRequired, options: PropTypes.array, - multi: PropTypes.bool.isRequired, + isMulti: PropTypes.bool, value: PropTypes.any, isValidNewOption: PropTypes.func, noResultsText: PropTypes.string, }; OnPasteSelect.defaultProps = { - separator: [',', '\n', '\t'], + separator: [',', '\n', '\t', ';'], selectWrap: Select, valueKey: 'value', labelKey: 'label', options: [], - multi: false, + isMulti: false, }; diff --git a/superset-frontend/src/components/Select/SupersetStyledSelect.tsx b/superset-frontend/src/components/Select/SupersetStyledSelect.tsx new file mode 100644 index 000000000..a59d2a64d --- /dev/null +++ b/superset-frontend/src/components/Select/SupersetStyledSelect.tsx @@ -0,0 +1,289 @@ +/** + * 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, { SyntheticEvent, MutableRefObject } from 'react'; +import { merge } from 'lodash'; +import BasicSelect, { + OptionTypeBase, + MultiValueProps, + FormatOptionLabelMeta, + ValueType, + SelectComponentsConfig, + components as defaultComponents, + createFilter, +} from 'react-select'; +import Async from 'react-select/async'; +import Creatable from 'react-select/creatable'; +import AsyncCreatable from 'react-select/async-creatable'; + +import { SelectComponents } from 'react-select/src/components'; +import { + SortableContainer, + SortableElement, + SortableContainerProps, +} from 'react-sortable-hoc'; +import arrayMove from 'array-move'; +import { + WindowedSelectComponentType, + WindowedSelectProps, + WindowedSelect, + WindowedAsyncSelect, + WindowedCreatableSelect, + WindowedAsyncCreatableSelect, +} from './WindowedSelect'; +import { + DEFAULT_CLASS_NAME, + DEFAULT_CLASS_NAME_PREFIX, + DEFAULT_STYLES, + DEFAULT_THEME, + DEFAULT_COMPONENTS, + VALUE_LABELED_STYLES, + PartialThemeConfig, + PartialStylesConfig, +} from './styles'; +import { findValue } from './utils'; + +type AnyReactSelect = + | BasicSelect + | Async + | Creatable + | AsyncCreatable; + +export type SupersetStyledSelectProps< + OptionType extends OptionTypeBase, + T extends WindowedSelectProps = WindowedSelectProps +> = T & { + // additional props for easier usage or backward compatibility + labelKey?: string; + valueKey?: string; + multi?: boolean; + clearable?: boolean; + sortable?: boolean; + ignoreAccents?: boolean; + creatable?: boolean; + selectRef?: + | React.RefCallback> + | MutableRefObject>; + getInputValue?: (selectBalue: ValueType) => string | undefined; + optionRenderer?: (option: OptionType) => React.ReactNode; + valueRenderer?: (option: OptionType) => React.ReactNode; + valueRenderedAsLabel?: boolean; + // callback for paste event + onPaste?: (e: SyntheticEvent) => void; + // for simplier theme overrides + themeConfig?: PartialThemeConfig; + stylesConfig?: PartialStylesConfig; +}; + +function styled< + OptionType extends OptionTypeBase, + SelectComponentType extends WindowedSelectComponentType< + OptionType + > = WindowedSelectComponentType +>(SelectComponent: SelectComponentType) { + type SelectProps = SupersetStyledSelectProps; + type Components = SelectComponents; + + const SortableSelectComponent = SortableContainer(SelectComponent, { + withRef: true, + }); + + // default components for the given OptionType + const supersetDefaultComponents: SelectComponentsConfig = DEFAULT_COMPONENTS; + + const getSortableMultiValue = (MultiValue: Components['MultiValue']) => { + return SortableElement((props: MultiValueProps) => { + const onMouseDown = (e: SyntheticEvent) => { + e.preventDefault(); + e.stopPropagation(); + }; + const innerProps = { onMouseDown }; + return ; + }); + }; + + /** + * Superset styled `Select` component. Apply Superset themed stylesheets and + * consolidate props API for backward compatibility with react-select v1. + */ + function StyledSelect(selectProps: SelectProps) { + let stateManager: AnyReactSelect; // reference to react-select StateManager + const { + // additional props for Superset Select + selectRef, + labelKey = 'label', + valueKey = 'value', + themeConfig, + stylesConfig = {}, + optionRenderer, + valueRenderer, + // whether value is rendered as `option-label` in input, + // useful for AdhocMetric and AdhocFilter + valueRenderedAsLabel: valueRenderedAsLabel_, + onPaste, + multi = false, // same as `isMulti`, used for backward compatibility + clearable, // same as `isClearable` + sortable = true, // whether to enable drag & drop sorting + + // react-select props + className = DEFAULT_CLASS_NAME, + classNamePrefix = DEFAULT_CLASS_NAME_PREFIX, + options, + value: value_, + components: components_, + isMulti: isMulti_, + isClearable: isClearable_, + minMenuHeight = 100, // apply different defaults + maxMenuHeight = 220, + filterOption, + ignoreAccents = false, // default is `true`, but it is slow + + getOptionValue = option => + typeof option === 'string' ? option : option[valueKey], + + getOptionLabel = option => + typeof option === 'string' + ? option + : option[labelKey] || option[valueKey], + + formatOptionLabel = ( + option: OptionType, + { context }: FormatOptionLabelMeta, + ) => { + if (context === 'value') { + return valueRenderer ? valueRenderer(option) : getOptionLabel(option); + } + return optionRenderer ? optionRenderer(option) : getOptionLabel(option); + }, + + ...restProps + } = selectProps; + + // `value` may be rendered values (strings), we want option objects + const value: OptionType[] = findValue(value_, options || [], valueKey); + + // Add backward compability to v1 API + const isMulti = isMulti_ === undefined ? multi : isMulti_; + const isClearable = isClearable_ === undefined ? clearable : isClearable_; + + // Sort is only applied when there are multiple selected values + const shouldAllowSort = + isMulti && sortable && Array.isArray(value) && value.length > 1; + + const MaybeSortableSelect = shouldAllowSort + ? SortableSelectComponent + : SelectComponent; + const components = { ...supersetDefaultComponents, ...components_ }; + + // Make multi-select sortable as per https://react-select.netlify.app/advanced + if (shouldAllowSort) { + components.MultiValue = getSortableMultiValue( + components.MultiValue || defaultComponents.MultiValue, + ); + + const sortableContainerProps: Partial = { + getHelperDimensions: ({ node }) => node.getBoundingClientRect(), + axis: 'xy', + onSortEnd: ({ oldIndex, newIndex }) => { + const newValue = arrayMove(value, oldIndex, newIndex); + if (restProps.onChange) { + restProps.onChange(newValue, { action: 'set-value' }); + } + }, + distance: 4, + }; + Object.assign(restProps, sortableContainerProps); + } + + // When values are rendered as labels, adjust valueContainer padding + const valueRenderedAsLabel = + valueRenderedAsLabel_ === undefined ? isMulti : valueRenderedAsLabel_; + if (valueRenderedAsLabel && !stylesConfig.valueContainer) { + Object.assign(stylesConfig, VALUE_LABELED_STYLES); + } + + // Handle onPaste event + if (onPaste) { + const Input = components.Input || defaultComponents.Input; + // @ts-ignore (needed for passing `onPaste`) + components.Input = props => ; + } + // for CreaTable + if (SelectComponent === WindowedCreatableSelect) { + restProps.getNewOptionData = (inputValue: string, label: string) => ({ + label: label || inputValue, + [valueKey]: inputValue, + isNew: true, + }); + } + + // Make sure always return StateManager for the refs. + // To get the real `Select` component, keep tap into `obj.select`: + // - for normal - + )} diff --git a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx index 5d0703e99..39b094953 100644 --- a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx +++ b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx @@ -24,15 +24,13 @@ import 'brace/mode/sql'; import 'brace/theme/github'; import 'brace/ext/language_tools'; import { FormGroup } from 'react-bootstrap'; -import VirtualizedSelect from 'react-virtualized-select'; +import Select from 'src/components/Select'; import { t } from '@superset-ui/translation'; import sqlKeywords from '../../SqlLab/utils/sqlKeywords'; import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../AdhocFilter'; import adhocMetricType from '../propTypes/adhocMetricType'; import columnType from '../propTypes/columnType'; -import OnPasteSelect from '../../components/OnPasteSelect'; -import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap'; const propTypes = { adhocFilter: PropTypes.instanceOf(AdhocFilter).isRequired, @@ -59,12 +57,11 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component this.handleAceEditorRef = this.handleAceEditorRef.bind(this); this.selectProps = { - multi: false, + isMulti: false, name: 'select-column', labelKey: 'label', autosize: false, clearable: false, - selectWrap: VirtualizedSelect, }; if (langTools) { @@ -123,18 +120,15 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component const clauseSelectProps = { placeholder: t('choose WHERE or HAVING...'), - options: Object.keys(CLAUSES).map(clause => ({ clause })), + options: Object.keys(CLAUSES), value: adhocFilter.clause, onChange: this.onSqlExpressionClauseChange, - optionRenderer: VirtualizedRendererWrap(clause => clause.clause), - valueRenderer: clause => {clause.clause}, - valueKey: 'clause', }; return ( - ); return ( - -
- {adhocFilter.isExtra && ( - e.stopPropagation()}> + {adhocFilter.isExtra && ( + + )} + +
-
+ +
); } } diff --git a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx index 2da91873c..3df48f5fd 100644 --- a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx +++ b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx @@ -26,7 +26,7 @@ import { Tab, Tabs, } from 'react-bootstrap'; -import VirtualizedSelect from 'react-virtualized-select'; +import Select from 'src/components/Select'; import ace from 'brace'; import AceEditor from 'react-ace'; import 'brace/mode/sql'; @@ -34,9 +34,7 @@ import 'brace/theme/github'; import 'brace/ext/language_tools'; import { t } from '@superset-ui/translation'; -import { AGGREGATES } from '../constants'; -import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap'; -import OnPasteSelect from '../../components/OnPasteSelect'; +import { AGGREGATES, AGGREGATES_OPTIONS } from '../constants'; import AdhocMetricEditPopoverTitle from './AdhocMetricEditPopoverTitle'; import columnType from '../propTypes/columnType'; import AdhocMetric, { EXPRESSION_TYPES } from '../AdhocMetric'; @@ -80,12 +78,10 @@ export default class AdhocMetricEditPopover extends React.Component { height: startingHeight, }; this.selectProps = { - multi: false, - name: 'select-column', labelKey: 'label', + isMulti: false, autosize: false, clearable: true, - selectWrap: VirtualizedSelect, }; if (langTools) { const words = sqlKeywords.concat( @@ -129,7 +125,7 @@ export default class AdhocMetricEditPopover extends React.Component { // we construct this object explicitly to overwrite the value in the case aggregate is null this.setState({ adhocMetric: this.state.adhocMetric.duplicateWith({ - aggregate: aggregate && aggregate.aggregate, + aggregate, expressionType: EXPRESSION_TYPES.SIMPLE, }), }); @@ -189,6 +185,10 @@ export default class AdhocMetricEditPopover extends React.Component { setTimeout(() => this.aceEditorRef.editor.resize(), 0); } + renderColumnOption(option) { + return ; + } + render() { const { adhocMetric: propsAdhocMetric, @@ -209,26 +209,20 @@ export default class AdhocMetricEditPopover extends React.Component { (adhocMetric.column && adhocMetric.column.column_name) || adhocMetric.inferSqlExpressionColumn(), onChange: this.onColumnChange, - optionRenderer: VirtualizedRendererWrap(option => ( - - )), - valueRenderer: column => column.column_name, + optionRenderer: this.renderColumnOption, valueKey: 'column_name', }; const aggregateSelectProps = { - placeholder: t('%s aggregates(s)', Object.keys(AGGREGATES).length), - options: Object.keys(AGGREGATES).map(aggregate => ({ aggregate })), + placeholder: t('%s aggregates(s)', AGGREGATES_OPTIONS.length), + options: AGGREGATES_OPTIONS, value: adhocMetric.aggregate || adhocMetric.inferSqlExpressionAggregate(), onChange: this.onAggregateChange, - optionRenderer: VirtualizedRendererWrap(aggregate => aggregate.aggregate), - valueRenderer: aggregate => aggregate.aggregate, - valueKey: 'aggregate', }; if (this.props.datasourceType === 'druid') { aggregateSelectProps.options = aggregateSelectProps.options.filter( - option => option.aggregate !== 'AVG', + aggregate => aggregate !== 'AVG', ); } @@ -260,16 +254,21 @@ export default class AdhocMetricEditPopover extends React.Component { column - +