From bf76d20ee93e8694468ad64ec2080d1d7fc003c1 Mon Sep 17 00:00:00 2001 From: michellethomas Date: Wed, 15 Aug 2018 11:25:06 -0700 Subject: [PATCH 01/35] Allow users to publish dashboards --- .gitignore | 46 +- .pylintrc | 2 +- .travis.yml | 2 + CONTRIBUTING.md | 94 +- README.md | 3 + UPDATING.md | 20 +- docs/conf.py | 1 - docs/faq.rst | 60 +- docs/index.rst | 13 + docs/installation.rst | 37 +- docs/requirements.txt | 1 - docs/sqllab.rst | 2 + requirements-dev.txt | 2 +- superset/assets/.babelrc | 2 +- superset/assets/.eslintrc | 5 +- .../branding/Superset_Logo_Gradient_Text.png | Bin 0 -> 6973 bytes .../branding/Superset_Logo_Gradient_Text.svg | 14 + .../Superset_Logo_Gradient_Text@2x.png | Bin 0 -> 16664 bytes .../Superset_Logo_Horizontal_Lockup.png | Bin 0 -> 8350 bytes .../Superset_Logo_Horizontal_Lockup.svg | 20 + .../Superset_Logo_Horizontal_Lockup@2x.png | Bin 0 -> 20448 bytes .../assets/branding/Superset_Logo_No_Text.png | Bin 0 -> 5990 bytes .../assets/branding/Superset_Logo_No_Text.svg | 16 + .../branding/Superset_Logo_No_Text@2x.png | Bin 0 -> 17786 bytes .../Superset_Logo_Vertical_Lockup.png | Bin 0 -> 8061 bytes .../Superset_Logo_Vertical_Lockup.svg | 20 + .../Superset_Logo_Vertical_Lockup@2x.png | Bin 0 -> 20639 bytes superset/assets/cypress.json | 5 + superset/assets/cypress/.eslintrc | 8 + .../integration/dashboard/dashboard_tests.js | 26 + .../integration/explore/control_tests.js | 62 + .../explore/visualizations/big_number.js | 60 + .../explore/visualizations/line.js | 157 + .../explore/visualizations/shared.helper.js | 40 + superset/assets/cypress/plugins/index.js | 17 + superset/assets/cypress/support/commands.js | 73 + superset/assets/cypress/support/index.js | 20 + superset/assets/cypress_build.sh | 16 + .../images/viz_thumbnails/big_number.png | Bin 10101 -> 103045 bytes superset/assets/package.json | 54 +- superset/assets/spec/.eslintrc | 8 + .../spec/helpers/{browser.js => shim.js} | 10 +- .../javascripts/CRUD/CollectionTable_spec.jsx | 1 - .../addSlice/AddSliceContainer_spec.jsx | 1 - .../spec/javascripts/chart/Chart_spec.jsx | 3 - .../components/AlteredSliceTag_spec.jsx | 1 - .../components/AsyncSelect_spec.jsx | 2 - .../components/CachedLabel_spec.jsx | 1 - .../javascripts/components/Checkbox_spec.jsx | 1 - .../components/ColumnOption_spec.jsx | 1 - .../components/ColumnTypeLabel_spec.jsx | 1 - .../components/CopyToClipboard_spec.jsx | 1 - .../FilterableTable/FilterableTable_spec.jsx | 1 - .../components/MetricOption_spec.jsx | 1 - .../components/ModalTrigger_spec.jsx | 1 - .../components/OnPasteSelect_spec.jsx | 1 - .../components/OptionDescription_spec.jsx | 1 - .../components/PopoverSection_spec.jsx | 1 - .../components/URLShortLinkButton_spec.jsx | 1 - .../components/URLShortLinkModal_spec.jsx | 1 - .../VirtualizedRendererWrap_spec.jsx | 1 - .../spec/javascripts/dashboard/.eslintrc | 1 - .../dashboard/actions/dashboardLayout_spec.js | 1 - .../dashboard/components/CodeModal_spec.jsx | 1 - .../dashboard/components/CssEditor_spec.jsx | 1 - .../components/DashboardBuilder_spec.jsx | 1 - .../components/DashboardGrid_spec.jsx | 1 - .../dashboard/components/Dashboard_spec.jsx | 9 - .../components/HeaderActionsDropdown_spec.jsx | 1 - .../dashboard/components/Header_spec.jsx | 1 - .../components/MissingChart_spec.jsx | 1 - .../components/RefreshIntervalModal_spec.jsx | 1 - .../dashboard/components/SliceAdder_spec.jsx | 1 - .../components/dnd/DragDroppable_spec.jsx | 1 - .../gridComponents/ChartHolder_spec.jsx | 1 - .../components/gridComponents/Chart_spec.jsx | 1 - .../components/gridComponents/Column_spec.jsx | 1 - .../gridComponents/Divider_spec.jsx | 1 - .../components/gridComponents/Header_spec.jsx | 1 - .../gridComponents/Markdown_spec.jsx | 4 +- .../components/gridComponents/Row_spec.jsx | 1 - .../components/gridComponents/Tab_spec.jsx | 19 +- .../components/gridComponents/Tabs_spec.jsx | 1 - .../new/DraggableNewComponent_spec.jsx | 1 - .../gridComponents/new/NewColumn_spec.jsx | 1 - .../gridComponents/new/NewDivider_spec.jsx | 1 - .../gridComponents/new/NewHeader_spec.jsx | 1 - .../gridComponents/new/NewRow_spec.jsx | 1 - .../gridComponents/new/NewTabs_spec.jsx | 1 - .../components/menu/HoverMenu_spec.jsx | 1 - .../components/menu/WithPopoverMenu_spec.jsx | 1 - .../resizable/ResizableContainer_spec.jsx | 1 - .../resizable/ResizableHandle_spec.jsx | 1 - .../reducers/dashboardLayout_spec.js | 1 - .../dashboard/reducers/dashboardState_spec.js | 1 - .../dashboard/reducers/sliceEntities_spec.js | 1 - .../util/componentIsResizable_spec.js | 1 - .../dashboard/util/dnd-reorder_spec.js | 1 - .../util/dropOverflowsParent_spec.js | 1 - .../util/findFirstParentContainer_spec.js | 1 - .../dashboard/util/findParentId_spec.js | 1 - .../util/getChartIdsFromLayout_spec.js | 1 - .../dashboard/util/getDashboardUrl_spec.js | 1 - .../util/getDetailedComponentWidth_spec.js | 1 - .../dashboard/util/getDropPosition_spec.js | 1 - .../util/getFormDataWithExtraFilters_spec.js | 1 - .../dashboard/util/isValidChild_spec.js | 1 - .../util/newComponentFactory_spec.js | 1 - .../util/newEntitiesFromDrop_spec.js | 1 - .../datasource/DatasourceEditor_spec.jsx | 1 - .../datasource/DatasourceModal_spec.jsx | 1 - .../javascripts/explore/AdhocFilter_spec.js | 1 - .../javascripts/explore/AdhocMetric_spec.js | 1 - .../javascripts/explore/chartActions_spec.js | 1 - .../components/AdhocFilterControl_spec.jsx | 1 - ...FilterEditPopoverSimpleTabContent_spec.jsx | 1 - ...hocFilterEditPopoverSqlTabContent_spec.jsx | 1 - .../AdhocFilterEditPopover_spec.jsx | 3 +- .../components/AdhocFilterOption_spec.jsx | 1 - .../AdhocMetricEditPopoverTitle_spec.jsx | 3 +- .../AdhocMetricEditPopover_spec.jsx | 1 - .../components/AdhocMetricOption_spec.jsx | 1 - .../AdhocMetricStaticOption_spec.jsx | 1 - .../components/AggregateOption_spec.jsx | 1 - .../explore/components/BoundsControl_spec.jsx | 1 - .../components/CheckboxControl_spec.jsx | 1 - .../components/ColorPickerControl_spec.jsx | 1 - .../explore/components/ColorScheme_spec.jsx | 5 +- .../components/ControlPanelSection_spec.jsx | 1 - .../ControlPanelsContainer_spec.jsx | 1 - .../explore/components/ControlRow_spec.jsx | 1 - .../components/DatasourceControl_spec.jsx | 1 - .../components/DateFilterControl_spec.jsx | 19 +- .../components/DisplayQueryButton_spec.jsx | 3 - .../components/EmbedCodeButton_spec.jsx | 1 - .../components/ExploreActionButtons_spec.jsx | 1 - .../components/ExploreChartHeader_spec.jsx | 1 - .../components/ExploreChartPanel_spec.js | 3 +- .../components/ExploreViewContainer_spec.js | 3 +- .../FilterDefinitionOption_spec.jsx | 1 - .../components/FixedOrMetricControl_spec.jsx | 1 - .../MetricDefinitionOption_spec.jsx | 1 - .../components/MetricDefinitionValue_spec.jsx | 1 - .../components/MetricsControl_spec.jsx | 1 - .../components/QueryAndSaveBtns_spec.jsx | 1 - .../explore/components/RowCountLabel_spec.jsx | 1 - .../components/RunQueryActionButton_spec.jsx | 1 - .../explore/components/SaveModal_spec.jsx | 1 - .../explore/components/SelectControl_spec.jsx | 1 - .../explore/components/TextArea_spec.jsx | 1 - .../TimeSeriesColumnControl_spec.jsx | 1 - .../components/ViewportControl_spec.jsx | 1 - .../components/VizTypeControl_spec.jsx | 1 - .../explore/exploreActions_spec.js | 1 - .../spec/javascripts/explore/utils_spec.jsx | 1 - .../assets/spec/javascripts/logger_spec.js | 1 - .../spec/javascripts/messageToasts/.eslintrc | 1 - .../components/ToastPresenter_spec.jsx | 1 - .../messageToasts/components/Toast_spec.jsx | 1 - .../reducers/messageToasts_spec.js | 1 - .../getToastsFromPyFlashMessages_spec.js | 1 - .../modules/CategoricalColorNameSpace_spec.js | 130 + .../modules/CategoricalColorScale_spec.js | 96 + .../modules/ColorSchemeManager_spec.js | 141 + .../spec/javascripts/modules/colors_spec.jsx | 25 +- .../spec/javascripts/modules/dates_spec.js | 3 +- .../spec/javascripts/modules/geo_spec.jsx | 1 - .../spec/javascripts/modules/sandbox_spec.jsx | 1 - .../spec/javascripts/modules/time_spec.js | 103 +- .../spec/javascripts/modules/utils_spec.jsx | 10 +- .../spec/javascripts/profile/App_spec.jsx | 1 - .../profile/CreatedContent_spec.jsx | 1 - .../profile/EditableTitle_spec.jsx | 1 - .../javascripts/profile/Favorites_spec.jsx | 1 - .../profile/RecentActivity_spec.jsx | 1 - .../javascripts/profile/Security_spec.jsx | 1 - .../javascripts/profile/UserInfo_spec.jsx | 1 - .../spec/javascripts/sqllab/App_spec.jsx | 1 - .../javascripts/sqllab/ColumnElement_spec.jsx | 1 - .../sqllab/CopyQueryTabUrl_spec.jsx | 1 - .../sqllab/ExploreResultsButton_spec.jsx | 1 - .../sqllab/HighlightedSql_spec.jsx | 1 - .../spec/javascripts/sqllab/Link_spec.jsx | 1 - .../javascripts/sqllab/QuerySearch_spec.jsx | 1 - .../sqllab/QueryStateLabel_spec.jsx | 1 - .../javascripts/sqllab/QueryTable_spec.jsx | 1 - .../javascripts/sqllab/ResultSet_spec.jsx | 1 - .../javascripts/sqllab/SaveQuery_spec.jsx | 1 - .../sqllab/SqlEditorLeftBar_spec.jsx | 1 - .../javascripts/sqllab/SqlEditor_spec.jsx | 1 - .../javascripts/sqllab/TabStatusIcon_spec.jsx | 1 - .../sqllab/TabbedSqlEditors_spec.jsx | 1 - .../javascripts/sqllab/TableElement_spec.jsx | 9 +- .../spec/javascripts/sqllab/Timer_spec.jsx | 1 - .../spec/javascripts/sqllab/actions_spec.js | 10 +- .../spec/javascripts/sqllab/reducers_spec.js | 1 - .../spec/javascripts/utils/common_spec.jsx | 1 - .../{nvd3_viz_spec.jsx => nvd3/utils_spec.js} | 15 +- .../javascripts/visualizations/table_spec.jsx | 100 + .../welcome/DashboardTable_spec.jsx | 2 +- .../{App_spec.jsx => Welcome_spec.jsx} | 9 +- superset/assets/src/CRUD/CollectionTable.jsx | 4 +- superset/assets/src/CRUD/utils.js | 2 +- superset/assets/src/SqlLab/App.jsx | 51 + superset/assets/src/SqlLab/actions.js | 12 +- .../src/SqlLab/components/CopyQueryTabUrl.jsx | 7 +- .../components/ExploreResultsButton.jsx | 1 - .../src/SqlLab/components/QuerySearch.jsx | 1 - .../src/SqlLab/components/SouthPane.jsx | 2 +- .../src/SqlLab/components/SqlEditor.jsx | 6 +- .../SqlLab/components/SqlEditorLeftBar.jsx | 18 +- .../SqlLab/components/TabbedSqlEditors.jsx | 66 +- .../src/SqlLab/components/TableElement.jsx | 9 +- .../components/TemplateParamsEditor.jsx | 2 +- superset/assets/src/SqlLab/getInitialState.js | 1 - superset/assets/src/SqlLab/index.jsx | 46 +- superset/assets/src/SqlLab/main.less | 23 + superset/assets/src/SqlLab/reducers.js | 3 +- superset/assets/src/addSlice/App.jsx | 15 + superset/assets/src/addSlice/index.jsx | 12 +- superset/assets/src/chart/Chart.jsx | 25 +- superset/assets/src/chart/ChartBody.jsx | 21 - superset/assets/src/chart/chartAction.js | 33 +- superset/assets/src/chart/chartReducer.js | 6 + superset/assets/src/common.js | 22 +- .../src/components/BootstrapSliderWrapper.css | 8 + .../src/components/BootstrapSliderWrapper.jsx | 12 + .../assets/src/components/ModalTrigger.jsx | 45 +- .../src/components/StackTraceMessage.jsx | 8 +- .../components/VirtualizedRendererWrap.jsx | 2 +- superset/assets/src/dashboard/.eslintrc | 1 - superset/assets/src/dashboard/App.jsx | 36 + .../src/dashboard/actions/dashboardState.js | 35 + .../src/dashboard/components/AddSliceCard.jsx | 6 +- .../components/BuilderComponentPane.jsx | 4 +- .../src/dashboard/components/Dashboard.jsx | 1 - .../components/DeleteComponentModal.jsx | 62 + .../src/dashboard/components/Header.jsx | 12 + .../components/HeaderActionsDropdown.jsx | 1 - .../dashboard/components/PublishedStatus.jsx | 50 + .../components/gridComponents/ChartHolder.jsx | 11 +- .../components/gridComponents/Tab.jsx | 10 +- .../gridComponents/new/NewColumn.jsx | 3 +- .../gridComponents/new/NewDivider.jsx | 3 +- .../gridComponents/new/NewHeader.jsx | 3 +- .../components/gridComponents/new/NewRow.jsx | 3 +- .../components/gridComponents/new/NewTabs.jsx | 3 +- .../components/menu/WithPopoverMenu.jsx | 23 +- .../dashboard/containers/DashboardHeader.jsx | 5 + superset/assets/src/dashboard/index.jsx | 34 +- .../src/dashboard/reducers/dashboardState.js | 66 +- .../src/dashboard/reducers/getInitialState.js | 4 +- .../stylesheets/components/chart.less | 8 + .../stylesheets/components/markdown.less | 11 + .../src/dashboard/stylesheets/dashboard.less | 36 +- .../src/dashboard/stylesheets/variables.less | 1 + .../src/datasource/DatasourceEditor.jsx | 33 +- .../assets/src/datasource/DatasourceModal.jsx | 2 +- superset/assets/src/explore/AdhocFilter.js | 2 +- superset/assets/src/explore/App.jsx | 91 + .../src/explore/actions/exploreActions.js | 6 + ...AdhocFilterEditPopoverSimpleTabContent.jsx | 1 + .../AdhocFilterEditPopoverSqlTabContent.jsx | 1 + .../components/AdhocMetricEditPopover.jsx | 13 +- .../assets/src/explore/components/Control.jsx | 9 +- .../components/ControlPanelsContainer.jsx | 5 +- .../explore/components/DisplayQueryButton.jsx | 2 +- .../components/ExploreViewContainer.jsx | 6 - .../src/explore/components/SaveModal.jsx | 2 +- .../components/controls/AnnotationLayer.jsx | 36 +- .../controls/ColorSchemeControl.jsx | 26 +- .../components/controls/DatasourceControl.jsx | 1 - .../components/controls/DateFilterControl.css | 3 + .../components/controls/DateFilterControl.jsx | 292 +- .../components/controls/HiddenControl.jsx | 1 + .../controls/SelectAsyncControl.jsx | 1 - .../components/controls/SliderControl.jsx | 35 + .../components/controls/SpatialControl.jsx | 18 +- .../components/controls/VizTypeControl.jsx | 2 +- .../src/explore/components/controls/index.js | 2 + superset/assets/src/explore/controls.jsx | 90 +- superset/assets/src/explore/index.jsx | 89 +- superset/assets/src/explore/store.js | 7 - superset/assets/src/explore/visTypes.jsx | 83 +- superset/assets/src/messageToasts/.eslintrc | 1 - .../src/modules/CategoricalColorNamespace.js | 60 + .../src/modules/CategoricalColorScale.js | 64 + .../assets/src/modules/ColorSchemeManager.js | 86 + .../assets/src/modules/colorSchemes/airbnb.js | 25 + .../src/modules/colorSchemes/categorical.js | 42 + .../assets/src/modules/colorSchemes/lyft.js | 14 + .../src/modules/colorSchemes/sequential.js | 433 ++ superset/assets/src/modules/colors.js | 593 +-- superset/assets/src/modules/time.js | 104 +- superset/assets/src/modules/utils.js | 34 - superset/assets/src/profile/App.jsx | 17 + superset/assets/src/profile/index.jsx | 17 +- superset/assets/src/reduxUtils.js | 10 +- superset/assets/src/utils/common.js | 9 +- .../BigNumber.css} | 0 .../{ => BigNumber}/BigNumber.jsx | 73 +- .../src/visualizations/BigNumber/adaptor.jsx | 86 + .../src/visualizations/BigNumber/index.js | 5 + .../assets/src/visualizations/Histogram.jsx | 138 + .../src/visualizations/HorizonChart.css | 18 + .../src/visualizations/HorizonChart.jsx | 103 + .../assets/src/visualizations/HorizonRow.jsx | 182 + superset/assets/src/visualizations/Legend.jsx | 1 + .../src/visualizations/MapBox/MapBox.css | 3 + .../src/visualizations/MapBox/MapBox.jsx | 209 + .../ScatterPlotGlowOverlay.jsx} | 297 +- .../PairedTTest.css} | 10 +- .../PairedTTest/PairedTTest.jsx | 83 + .../TTestTable.jsx} | 87 +- .../assets/src/visualizations/PlaySlider.css | 16 +- .../assets/src/visualizations/PlaySlider.jsx | 39 +- .../TimeTable/FormattedNumber.jsx | 27 + .../{ => TimeTable}/SparklineCell.jsx | 4 +- .../visualizations/TimeTable/TimeTable.css | 3 + .../visualizations/TimeTable/TimeTable.jsx | 327 ++ .../assets/src/visualizations/WithLegend.css | 4 + .../assets/src/visualizations/WithLegend.jsx | 123 + .../assets/src/visualizations/cal_heatmap.css | 4 - .../assets/src/visualizations/cal_heatmap.js | 120 +- superset/assets/src/visualizations/chord.jsx | 129 +- .../visualizations/countries/myanmar.geojson | 21 + .../countries/timorleste.geojson | 20 + .../assets/src/visualizations/country_map.css | 32 +- .../assets/src/visualizations/country_map.js | 213 +- .../deckgl/AnimatableDeckGLContainer.jsx | 36 +- .../deckgl/CategoricalDeckGLContainer.jsx | 160 + .../visualizations/deckgl/DeckGLContainer.jsx | 3 +- .../src/visualizations/deckgl/layers/arc.jsx | 45 +- .../visualizations/deckgl/layers/common.js | 13 +- .../visualizations/deckgl/layers/geojson.jsx | 4 +- .../visualizations/deckgl/layers/polygon.jsx | 39 +- .../visualizations/deckgl/layers/scatter.jsx | 174 +- .../deckgl/layers/screengrid.jsx | 7 +- .../src/visualizations/deckgl/multi.jsx | 9 +- .../src/visualizations/directed_force.js | 150 +- .../assets/src/visualizations/filter_box.jsx | 208 +- .../assets/src/visualizations/heatmap.css | 19 +- superset/assets/src/visualizations/heatmap.js | 255 +- .../assets/src/visualizations/histogram.css | 16 - .../assets/src/visualizations/histogram.js | 177 - .../assets/src/visualizations/horizon.css | 17 - superset/assets/src/visualizations/horizon.js | 227 - superset/assets/src/visualizations/iframe.js | 28 +- superset/assets/src/visualizations/index.js | 20 +- superset/assets/src/visualizations/mapbox.css | 16 - superset/assets/src/visualizations/markup.js | 52 +- .../{line_multi.js => nvd3/LineMulti.js} | 6 +- .../{nvd3_vis.css => nvd3/NVD3Vis.css} | 0 .../{nvd3_vis.js => nvd3/NVD3Vis.js} | 897 ++-- .../src/visualizations/nvd3/PropTypes.js | 63 + .../assets/src/visualizations/nvd3/utils.js | 206 + .../visualizations/parallel_coordinates.js | 165 +- .../assets/src/visualizations/partition.css | 21 +- .../assets/src/visualizations/partition.js | 302 +- .../assets/src/visualizations/pivot_table.js | 99 +- superset/assets/src/visualizations/rose.js | 97 +- superset/assets/src/visualizations/sankey.js | 94 +- .../assets/src/visualizations/sunburst.css | 19 +- .../assets/src/visualizations/sunburst.js | 168 +- superset/assets/src/visualizations/table.css | 28 +- superset/assets/src/visualizations/table.js | 260 +- .../assets/src/visualizations/time_table.css | 3 - .../assets/src/visualizations/time_table.jsx | 207 - superset/assets/src/visualizations/treemap.js | 300 +- .../assets/src/visualizations/word_cloud.js | 63 - .../src/visualizations/wordcloud/WordCloud.js | 120 + .../assets/src/visualizations/world_map.js | 92 +- superset/assets/src/welcome/App.jsx | 79 +- .../assets/src/welcome/DashboardTable.jsx | 6 +- superset/assets/src/welcome/Welcome.jsx | 71 + superset/assets/src/welcome/index.jsx | 18 +- superset/assets/stylesheets/superset.less | 6 + .../assets/vendor/cal-heatmap/cal-heatmap.css | 5 - .../parallel_coordinates/d3.parcoords.js | 2 - superset/assets/webpack.config.js | 109 +- superset/assets/yarn.lock | 4296 ++++++++++++----- superset/cli.py | 66 + superset/connectors/base/models.py | 3 +- superset/connectors/druid/models.py | 11 +- superset/connectors/druid/views.py | 4 +- superset/connectors/sqla/models.py | 113 +- superset/data/__init__.py | 8 + superset/dataframe.py | 31 +- superset/db_engine_specs.py | 127 +- superset/jinja_context.py | 45 +- superset/migrations/versions/4736ec66ce19_.py | 36 +- ...dd_metadata_column_to_annotation_model_.py | 22 + ...bdd4_add_published_column_to_dashboards.py | 22 + superset/models/annotations.py | 1 + superset/models/core.py | 7 +- superset/models/helpers.py | 51 - superset/security.py | 44 + superset/sql_lab.py | 43 +- superset/sql_parse.py | 8 +- superset/stats_logger.py | 11 + superset/templates/superset/base.html | 7 + superset/templates/superset/basic.html | 3 + .../templates/superset/import_dashboards.html | 27 +- .../superset/models/database/macros.html | 2 +- superset/templates/superset/no_data.html | 5 - superset/utils.py | 32 +- superset/views/annotations.py | 10 +- superset/views/base.py | 11 +- superset/views/core.py | 150 +- superset/views/datasource.py | 2 + superset/views/sql_lab.py | 6 + superset/viz.py | 223 +- tests/base_tests.py | 78 +- tests/celery_tests.py | 19 +- tests/core_tests.py | 69 +- tests/dashboard_tests.py | 80 +- tests/dataframe_test.py | 10 +- tests/dict_import_export_tests.py | 3 +- tests/fixtures/datasource.py | 2 +- tests/form_tests.py | 2 +- tests/macro_tests.py | 65 + tests/model_tests.py | 7 +- tests/sql_parse_tests.py | 65 + tests/sqllab_tests.py | 3 +- tests/viz_tests.py | 153 +- tox.ini | 16 +- yarn.lock | 4 - 427 files changed, 12507 insertions(+), 6096 deletions(-) create mode 100644 superset/assets/branding/Superset_Logo_Gradient_Text.png create mode 100644 superset/assets/branding/Superset_Logo_Gradient_Text.svg create mode 100644 superset/assets/branding/Superset_Logo_Gradient_Text@2x.png create mode 100644 superset/assets/branding/Superset_Logo_Horizontal_Lockup.png create mode 100644 superset/assets/branding/Superset_Logo_Horizontal_Lockup.svg create mode 100644 superset/assets/branding/Superset_Logo_Horizontal_Lockup@2x.png create mode 100644 superset/assets/branding/Superset_Logo_No_Text.png create mode 100644 superset/assets/branding/Superset_Logo_No_Text.svg create mode 100644 superset/assets/branding/Superset_Logo_No_Text@2x.png create mode 100644 superset/assets/branding/Superset_Logo_Vertical_Lockup.png create mode 100644 superset/assets/branding/Superset_Logo_Vertical_Lockup.svg create mode 100644 superset/assets/branding/Superset_Logo_Vertical_Lockup@2x.png create mode 100644 superset/assets/cypress.json create mode 100644 superset/assets/cypress/.eslintrc create mode 100644 superset/assets/cypress/integration/dashboard/dashboard_tests.js create mode 100644 superset/assets/cypress/integration/explore/control_tests.js create mode 100644 superset/assets/cypress/integration/explore/visualizations/big_number.js create mode 100644 superset/assets/cypress/integration/explore/visualizations/line.js create mode 100644 superset/assets/cypress/integration/explore/visualizations/shared.helper.js create mode 100644 superset/assets/cypress/plugins/index.js create mode 100644 superset/assets/cypress/support/commands.js create mode 100644 superset/assets/cypress/support/index.js create mode 100755 superset/assets/cypress_build.sh create mode 100644 superset/assets/spec/.eslintrc rename superset/assets/spec/helpers/{browser.js => shim.js} (81%) create mode 100644 superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js create mode 100644 superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js create mode 100644 superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js rename superset/assets/spec/javascripts/visualizations/{nvd3_viz_spec.jsx => nvd3/utils_spec.js} (68%) create mode 100644 superset/assets/spec/javascripts/visualizations/table_spec.jsx rename superset/assets/spec/javascripts/welcome/{App_spec.jsx => Welcome_spec.jsx} (67%) create mode 100644 superset/assets/src/SqlLab/App.jsx create mode 100644 superset/assets/src/addSlice/App.jsx create mode 100644 superset/assets/src/components/BootstrapSliderWrapper.css create mode 100644 superset/assets/src/components/BootstrapSliderWrapper.jsx create mode 100644 superset/assets/src/dashboard/App.jsx create mode 100644 superset/assets/src/dashboard/components/DeleteComponentModal.jsx create mode 100644 superset/assets/src/dashboard/components/PublishedStatus.jsx create mode 100644 superset/assets/src/explore/App.jsx create mode 100644 superset/assets/src/explore/components/controls/DateFilterControl.css create mode 100644 superset/assets/src/explore/components/controls/SliderControl.jsx create mode 100644 superset/assets/src/modules/CategoricalColorNamespace.js create mode 100644 superset/assets/src/modules/CategoricalColorScale.js create mode 100644 superset/assets/src/modules/ColorSchemeManager.js create mode 100644 superset/assets/src/modules/colorSchemes/airbnb.js create mode 100644 superset/assets/src/modules/colorSchemes/categorical.js create mode 100644 superset/assets/src/modules/colorSchemes/lyft.js create mode 100644 superset/assets/src/modules/colorSchemes/sequential.js create mode 100644 superset/assets/src/profile/App.jsx rename superset/assets/src/visualizations/{big_number.css => BigNumber/BigNumber.css} (100%) rename superset/assets/src/visualizations/{ => BigNumber}/BigNumber.jsx (70%) create mode 100644 superset/assets/src/visualizations/BigNumber/adaptor.jsx create mode 100644 superset/assets/src/visualizations/BigNumber/index.js create mode 100644 superset/assets/src/visualizations/Histogram.jsx create mode 100644 superset/assets/src/visualizations/HorizonChart.css create mode 100644 superset/assets/src/visualizations/HorizonChart.jsx create mode 100644 superset/assets/src/visualizations/HorizonRow.jsx create mode 100644 superset/assets/src/visualizations/MapBox/MapBox.css create mode 100644 superset/assets/src/visualizations/MapBox/MapBox.jsx rename superset/assets/src/visualizations/{mapbox.jsx => MapBox/ScatterPlotGlowOverlay.jsx} (52%) rename superset/assets/src/visualizations/{paired_ttest.css => PairedTTest/PairedTTest.css} (98%) create mode 100644 superset/assets/src/visualizations/PairedTTest/PairedTTest.jsx rename superset/assets/src/visualizations/{paired_ttest.jsx => PairedTTest/TTestTable.jsx} (79%) create mode 100644 superset/assets/src/visualizations/TimeTable/FormattedNumber.jsx rename superset/assets/src/visualizations/{ => TimeTable}/SparklineCell.jsx (97%) create mode 100644 superset/assets/src/visualizations/TimeTable/TimeTable.css create mode 100644 superset/assets/src/visualizations/TimeTable/TimeTable.jsx create mode 100644 superset/assets/src/visualizations/WithLegend.css create mode 100644 superset/assets/src/visualizations/WithLegend.jsx create mode 100644 superset/assets/src/visualizations/countries/myanmar.geojson create mode 100644 superset/assets/src/visualizations/countries/timorleste.geojson create mode 100644 superset/assets/src/visualizations/deckgl/CategoricalDeckGLContainer.jsx delete mode 100644 superset/assets/src/visualizations/histogram.css delete mode 100644 superset/assets/src/visualizations/histogram.js delete mode 100644 superset/assets/src/visualizations/horizon.css delete mode 100644 superset/assets/src/visualizations/horizon.js delete mode 100644 superset/assets/src/visualizations/mapbox.css rename superset/assets/src/visualizations/{line_multi.js => nvd3/LineMulti.js} (94%) rename superset/assets/src/visualizations/{nvd3_vis.css => nvd3/NVD3Vis.css} (100%) rename superset/assets/src/visualizations/{nvd3_vis.js => nvd3/NVD3Vis.js} (50%) create mode 100644 superset/assets/src/visualizations/nvd3/PropTypes.js create mode 100644 superset/assets/src/visualizations/nvd3/utils.js delete mode 100644 superset/assets/src/visualizations/time_table.css delete mode 100644 superset/assets/src/visualizations/time_table.jsx delete mode 100644 superset/assets/src/visualizations/word_cloud.js create mode 100644 superset/assets/src/visualizations/wordcloud/WordCloud.js create mode 100644 superset/assets/src/welcome/Welcome.jsx create mode 100644 superset/migrations/versions/55e910a74826_add_metadata_column_to_annotation_model_.py create mode 100644 superset/migrations/versions/d6ffdf31bdd4_add_published_column_to_dashboards.py delete mode 100644 superset/templates/superset/no_data.html create mode 100644 tests/macro_tests.py delete mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 11929a9b7e828..84100aa2259db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,43 @@ +*.bak +*.db *.pyc -yarn-error.log -_modules -superset/assets/coverage/* -changelog.sh -.DS_Store +*.sqllite +*.swp .coverage +.DS_Store +.eggs +.idea +.python-version +.tox +.vscode _build -_static _images _modules -superset/bin/supersetc +_static +build +app.db +changelog.sh +dist +dump.rdb +env env_py3 envpy3 -.eggs -build -*.db -tmp -superset_config.py local_config.py -env -dist +superset_config.py superset.egg-info/ -app.db -*.bak -.idea -*.sqllite -.vscode -.python-version -.tox -dump.rdb +superset/bin/supersetc +tmp # Node.js, webpack artifacts *.entry.js *.js.map node_modules npm-debug.log* +superset/assets/coverage/* +superset/assets/cypress/screenshots +superset/assets/cypress/videos superset/assets/version_info.json +yarn-error.log # IntelliJ *.iml diff --git a/.pylintrc b/.pylintrc index 016b04e367e45..eb913c2026958 100644 --- a/.pylintrc +++ b/.pylintrc @@ -23,7 +23,7 @@ persistent=yes load-plugins= # Use multiple processes to speed up Pylint. -jobs=1 +jobs=2 # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. diff --git a/.travis.yml b/.travis.yml index 16cdbb35bf3a5..a02d009f85252 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,8 @@ matrix: env: TOXENV=py36-sqlite - python: 3.6 env: TOXENV=pylint + - python: 3.6 + env: TOXENV=cypress exclude: - python: 2.7 - python: 3.6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 685d1f9098cd6..3aeb68b4f49a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,9 @@ whether as part of the official Superset docs, in docstrings, `docs/*.rst` or even on the web as blog posts or articles. +To build the docs, simply run ``./build.sh`` from the ``docs/`` folder. +The rendered docs are accessible at `http://localhost:{PORT}/static/assets/docs/faq.html`. + ### Submit Feedback The best way to send feedback is to file an issue on GitHub. @@ -200,7 +203,7 @@ Check the [OS dependencies](https://superset.incubator.apache.org/installation.h superset runserver -d -### Logging to the browser console +### Logging to the browser console (Python 3 only) When debugging your application, you can have the server logs sent directly to the browser console: @@ -258,23 +261,52 @@ To parse and generate bundled files for superset, run either of the following commands. The `dev` flag will keep the npm script running and re-run it upon any changes within the assets directory. -``` +```bash # Copies a conf file from the frontend to the backend npm run sync-backend # Compiles the production / optimized js & css npm run prod -# Start a web server that manages and updates your assets as you modify them +# Start a watcher that rebundle your assets as you modify them npm run dev + +# Start a web server that manages and updates your assets as you modify them +npm run dev-server ``` -For every development session you will have to start a flask dev server -as well as an npm watcher +For every development session you will have to -``` +1. Start a flask dev server + +```bash +superset runserver -d +# or specify port superset runserver -d -p 8081 -npm run dev +``` + +2. Start webpack dev server + +```bash +npm run dev-server +``` + +This will start `webpack-dev-server` at port 9000 and you can access Superset at localhost:9000. +By default, `webpack-dev-server` is configured for flask running at port 8088. + +If you start flask server at another port (e.g. 8081), you have to pass an extra argument +`supersetPort` to `webpack-dev-server` + +```bash +npm run dev-server -- --supersetPort=8081 +``` + +You can also specify port for `webpack-dev-server` + +```bash +npm run dev-server -- --port=9001 +# or with both dev-server port and superset port +npm run dev-server -- --port=9001 --supersetPort=8081 ``` #### Upgrading npm packages @@ -311,6 +343,21 @@ We use [Mocha](https://mochajs.org/), [Chai](http://chaijs.com/) and [Enzyme](ht npm i npm run test +We use [Cypress](https://www.cypress.io/) for integration tests. Tests can be run by `tox -e cypress`. To open Cypress and explore tests first setup and run test server: + + export SUPERSET_CONFIG=tests.superset_test_config + superset load_test_users + superset db upgrade + superset init + superset load_examples + superset runserver + +Open Cypress tests: + + cd /superset/superset/assets + npm run build + npm run cypress run + ## Linting Lint the project with: @@ -523,3 +570,36 @@ migration hashes. Then run `superset db merge {PASTE_SHA1_HERE} {PASTE_SHA2_HERE}`. This will create a new merge migration. You can then `superset db upgrade` to this new checkpoint. + + +## Running DB migration + +1. First alter the model you want to change. For example I want to add a `Column` Annotations model. + +https://github.com/apache/incubator-superset/commit/6c25f549384d7c2fc288451222e50493a7b14104 + + +2. superset db migrate -m "this_will_be_in_the_migration_filename" + +For our example we'll be running this command: +``` +superset db migrate -m "add_metadata_column_to_annotation_model.py" +``` + +This will generate a file in `superset/migrations/version/{SHA}_this_will_be_in_the_migration_filename.py` + +https://github.com/apache/incubator-superset/commit/d3e83b0fd572c9d6c1297543d415a332858e262 + +3. Run `superset db upgrade` + +The output should look like this: +``` +INFO [alembic.runtime.migration] Context impl SQLiteImpl. +INFO [alembic.runtime.migration] Will assume transactional DDL. +INFO [alembic.runtime.migration] Running upgrade 1a1d627ebd8e -> 40a0a483dd12, add_metadata_column_to_annotation_model.py +``` + +4. Add column to view +Since there is a new column, we need to add it to the AppBuilder Model view. + +https://github.com/apache/incubator-superset/pull/5745/commits/6220966e2a0a0cf3e6d87925491f8920fe8a3458 diff --git a/README.md b/README.md index 48b040e056ca4..59811fb2e526a 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Superset can be used to visualize data out of most databases: * Snowflake * Redshift * Clickhouse +* Apache Kylin * **more!** look for the availability of a SQLAlchemy dialect for your database to find out whether it will work with Superset @@ -130,6 +131,7 @@ Resources * [Docker image](https://hub.docker.com/r/amancevice/superset/) (community contributed) * [Slides from Strata (March 2016)](https://drive.google.com/open?id=0B5PVE0gzO81oOVJkdF9aNkJMSmM) * [Stackoverflow tag](https://stackoverflow.com/questions/tagged/apache-superset) +* [Join our Slack](https://join.slack.com/t/apache-superset/shared_invite/enQtNDMxMDY5NjM4MDU0LTc2Y2QwYjE4NGYwNzQyZWUwYTExZTdiZDMzMWQwZjc2YmJmM2QyMDkwMGVjZTA4N2I2MzUxZTk2YmE5MWRhZWE) * [DEPRECATED Google Group](https://groups.google.com/forum/#!forum/airbnb_superset) @@ -167,6 +169,7 @@ the world know they are using Superset. Join our growing community! - [Lime](https://www.limebike.com/) - [Lyft](https://www.lyft.com/) - [Maieutical Labs](https://maieuticallabs.it) + - [Myra Labs] (http://www.myralabs.com/) - [PeopleDoc](https://www.people-doc.com) - [Ona](https://ona.io) - [Pronto Tools](http://www.prontotools.io) diff --git a/UPDATING.md b/UPDATING.md index 753ec05d0ebc8..eec22c23153f5 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -3,11 +3,21 @@ This file documents any backwards-incompatible changes in Superset and assists people when migrating to a new version. +## Superset 0.28.0 +* Superset 0.28 deprecates the previous dashboard layout. While 0.27 + offered a migration workflow to users and allowed them to validate and + publish their migrated dashboards individually, 0.28 forces + the migration of all + dashboards through an automated db migration script. We + do recommend that you take a backup prior to this migration. + +* Superset 0.28 deprecates the `median` cluster label aggregator for mapbox visualizations. This particular aggregation is not supported on mapbox visualizations going forward. + ## Superset 0.27.0 -* Superset 0.27 start to use nested layout for dashboard builder, which is not +* Superset 0.27 start to use nested layout for dashboard builder, which is not backward-compatible with earlier dashboard grid data. We provide migration script -to automatically convert dashboard grid to nested layout data. To be safe, please -take a database backup prior to this upgrade. It's the only way people could go +to automatically convert dashboard grid to nested layout data. To be safe, please +take a database backup prior to this upgrade. It's the only way people could go back to a previous state. @@ -30,7 +40,7 @@ The PRs bellow have more information around the breaking changes: * [4565](https://github.com/apache/incubator-superset/pull/4565) : we've changed the security model a bit where in the past you would have to define your authentication scheme by inheriting from Flask - App Builder's + App Builder's `from flask_appbuilder.security.sqla.manager import SecurityManager`, you now have to derive Superset's own derivative `superset.security.SupersetSecurityManager`. This @@ -38,7 +48,7 @@ The PRs bellow have more information around the breaking changes: permissions to another system as needed. For all implementation, you simply have to import and derive `SupersetSecurityManager` in place of the `SecurityManager` -* [4835](https://github.com/apache/incubator-superset/pull/4835) : +* [4835](https://github.com/apache/incubator-superset/pull/4835) : our `setup.py` now only pins versions where required, giving you more latitude in using versions of libraries as needed. We do now provide a `requirements.txt` with pinned versions if you want to run diff --git a/docs/conf.py b/docs/conf.py index f78a9a420c5a8..d1c72a9e3a69a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,7 +33,6 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', - 'sphinxcontrib.youtube', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/faq.rst b/docs/faq.rst index 21e4e74905f18..3b69044563336 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -114,8 +114,8 @@ never be affected by any dashboard level filtering. "filter_immune_slices": [324, 65, 92], "expanded_slices": {}, "filter_immune_slice_fields": { - "177": ["country_name", "__from", "__to"], - "32": ["__from", "__to"] + "177": ["country_name", "__time_range"], + "32": ["__time_range"] }, "timed_refresh_immune_slices": [324] } @@ -127,8 +127,8 @@ Now note the ``filter_immune_slice_fields`` key. This one allows you to be more specific and define for a specific slice_id, which filter fields should be disregarded. -Note the use of the ``__from`` and ``__to`` keywords, those are reserved -for dealing with the time boundary filtering mentioned above. +Note the use of the ``__time_range`` keyword, which is reserved for dealing +with the time boundary filtering mentioned above. But what happens with filtering when dealing with slices coming from different tables or databases? If the column name is shared, the filter will @@ -246,3 +246,55 @@ labels to colors in the ``JSON Metadata`` attribute using the "Boys": "#ADD8E6" } } + +Does Superset work with [insert database engine here]? +------------------------------------------------------ + +The community over time has curated a list of databases that work well with +Superset in the :ref:`ref_database_deps` section of the docs. Database +engines not listed in this page may work too. We rely on the +community to contribute to this knowledge base. + +.. _SQLAlchemy dialect: http://docs.sqlalchemy.org/en/latest/dialects/ +.. _DBAPI driver: https://www.python.org/dev/peps/pep-0249/ + +For a database engine to be supported in Superset through the +SQLAlchemy connector, it requires having a Python compliant +`SQLAlchemy dialect`_ as well as a +`DBAPI driver`_ defined. +Database that have limited SQL support may +work as well. For instance it's possible to connect +to Druid through the SQLAlchemy connector even though Druid does not support +joins and subqueries. Another key element for a database to be supported is through +the Superset `Database Engine Specification +`_ +interface. This interface allows for defining database-specific configurations +and logic +that go beyond the SQLAlchemy and DBAPI scope. This includes features like: + + +* date-related SQL function that allow Superset to fetch different + time granularities when running time-series queries +* whether the engine supports subqueries. If false, Superset may run 2-phase + queries to compensate for the limitation +* methods around processing logs and inferring the percentage of completion + of a query +* technicalities as to how to handle cursors and connections if the driver + is not standard DBAPI +* more, read the code for more details + +Beyond the SQLAlchemy connector, it's also possible, though much more +involved, to extend Superset and write +your own connector. The only example of this at the moment is the Druid +connector, which is getting superseded by Druid's growing SQL support and +the recent availability of a DBAPI and SQLAlchemy driver. If the database +you are considering integrating has any kind of of SQL support, it's probably +preferable to go the SQLAlchemy route. Note that for a native connector to +be possible the database needs to have support for running OLAP-type queries +and should be able to things that are typical in basic SQL: + +- aggregate data +- apply filters (==, !=, >, <, >=, <=, IN, ...) +- apply HAVING-type filters +- be schema-aware, expose columns and types + diff --git a/docs/index.rst b/docs/index.rst index 370f51647a760..8aa2c4938604a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,6 +24,19 @@ intelligence web application the code, it does indicate that the project has yet to be fully endorsed by the ASF. +Resources +========= +- `Superset's Github `_, note + that `we use Github for issue tracking `_ +- Superset's + `contribution guidelines `_ + and + `code of conduct `_ + on Github. +- Our `mailing list archives `_. + To subscribe, send an email to ``dev-subscribe@superset.apache.org`` +- `Join our Slack `_ + Overview ======== diff --git a/docs/installation.rst b/docs/installation.rst index 008a2648f1a1b..963d273773ac1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -4,9 +4,9 @@ Installation & Configuration Getting Started --------------- -Superset is tested against Python ``2.7`` and Python ``3.6``. -Airbnb currently uses 2.7.* in production. We do not plan on supporting -Python ``2.6``. +Superset is currently tested against Python ``2.7`` and Python ``3.6``. +Python 3.6 is preferred. Support for Python ``<=3.6`` is planned to get +phased out. Cloud-native! ------------- @@ -203,7 +203,8 @@ workers this creates a lot of contention and race conditions when defining permissions and views. To alleviate this issue, the automatic updating of permissions can be disabled -by setting the :envvar:`SUPERSET_UPDATE_PERMS` environment variable to `0`. +by setting the environment variable +`SUPERSET_UPDATE_PERMS` environment variable to `0`. The value `1` enables it, `0` disables it. Note if undefined the functionality is enabled to maintain backwards compatibility. @@ -298,6 +299,9 @@ auth postback endpoint, you can add them to *WTF_CSRF_EXEMPT_LIST* WTF_CSRF_EXEMPT_LIST = [''] + +.. _ref_database_deps: + Database dependencies --------------------- @@ -350,6 +354,8 @@ Here's a list of some of the recommended packages. +---------------+-------------------------------------+-------------------------------------------------+ | BigQuery | ``pip install pybigquery`` | ``bigquery://`` | +---------------+-------------------------------------+-------------------------------------------------+ +| Teradata | ``pip install sqlalchemy-teradata`` | ``teradata://`` | ++---------------+-------------------------------------+-------------------------------------------------+ Note that many other database are supported, the main criteria being the existence of a functional SqlAlchemy dialect and Python driver. Googling @@ -389,8 +395,28 @@ Make sure the user has privileges to access and use all required databases/schemas/tables/views/warehouses, as the Snowflake SQLAlchemy engine does not test for user rights during engine creation. +*Note*: At the time of writing, there is a regression in the current stable version (1.1.2) of +snowflake-sqlalchemy package that causes problems when used with Superset. It is recommended to +use version 1.1.0 or try a newer version. + See `Snowflake SQLAlchemy `_. +Teradata +--------- + +The connection string for Teradata looks like this :: + + teradata://{user}:{password}@{host} + +*Note*: Its required to have Teradata ODBC drivers installed and environment variables configured for proper work of sqlalchemy dialect. Teradata ODBC Drivers available here: https://downloads.teradata.com/download/connectivity/odbc-driver/linux + +Required environment variables: :: + + export ODBCINI=/.../teradata/client/ODBC_64/odbc.ini + export ODBCINST=/.../teradata/client/ODBC_64/odbcinst.ini + +See `Teradata SQLAlchemy `_. + Caching ------- @@ -704,7 +730,7 @@ Note that it's also possible to implement you own logger by deriving Install Superset with helm in Kubernetes --------------- +---------------------------------------- You can install Superset into Kubernetes with Helm . The chart is located in ``install/helm``. @@ -727,7 +753,6 @@ The first step: Configure authorization in Superset ``superset_config.py``. .. code-block:: python AUTH_TYPE = AUTH_OAUTH - OAUTH_PROVIDERS = [ { 'name':'egaSSO', 'token_key':'access_token', # Name of the token in the response of access_token_url diff --git a/docs/requirements.txt b/docs/requirements.txt index 748df23c7ce5d..99f31cac658e8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,2 @@ sphinx==1.7.1 sphinx-rtd-theme==0.2.4 -sphinxcontrib.youtube==0.1.2 diff --git a/docs/sqllab.rst b/docs/sqllab.rst index 9230b2c73ab77..2ba9ac2c986be 100644 --- a/docs/sqllab.rst +++ b/docs/sqllab.rst @@ -67,6 +67,8 @@ Superset's Jinja context: .. autofunction:: superset.jinja_context.url_param +.. autofunction:: superset.jinja_context.filter_values + Extending macros '''''''''''''''' diff --git a/requirements-dev.txt b/requirements-dev.txt index fdb627d96207d..0c3ea6b5ca42c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ flake8-quotes==1.0.0 flake8==3.5.0 flask-cors==3.0.3 ipdb==0.11 -mysqlclient==1.3.12 +mysqlclient==1.3.13 psycopg2-binary==2.7.5 pycodestyle==2.3.1 pylint==1.9.2 diff --git a/superset/assets/.babelrc b/superset/assets/.babelrc index bb026bc1b36c2..0c426be741d9a 100644 --- a/superset/assets/.babelrc +++ b/superset/assets/.babelrc @@ -1,4 +1,4 @@ { "presets" : ["airbnb", "react", "env"], - "plugins": ["syntax-dynamic-import"], + "plugins": ["syntax-dynamic-import", "react-hot-loader/babel"] } diff --git a/superset/assets/.eslintrc b/superset/assets/.eslintrc index e49a4e0f1bbb4..c85c957c79ca9 100644 --- a/superset/assets/.eslintrc +++ b/superset/assets/.eslintrc @@ -6,8 +6,8 @@ "experimentalObjectRestSpread": true } }, - "globals": { - "document": true, + "env": { + "browser": true }, "rules": { "prefer-template": 0, @@ -26,7 +26,6 @@ "no-mixed-operators": 0, "no-continue": 0, "no-bitwise": 0, - "no-undef": 0, "no-multi-assign": 0, "react/no-array-index-key": 0, "no-restricted-properties": 0, diff --git a/superset/assets/branding/Superset_Logo_Gradient_Text.png b/superset/assets/branding/Superset_Logo_Gradient_Text.png new file mode 100644 index 0000000000000000000000000000000000000000..4d198d4261b9db686f69e81daba0347928553674 GIT binary patch literal 6973 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 P00000NkvXXu0mjfjuL!d literal 0 HcmV?d00001 diff --git a/superset/assets/branding/Superset_Logo_Gradient_Text.svg b/superset/assets/branding/Superset_Logo_Gradient_Text.svg new file mode 100644 index 0000000000000..b7cef042f49c8 --- /dev/null +++ b/superset/assets/branding/Superset_Logo_Gradient_Text.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/superset/assets/branding/Superset_Logo_Gradient_Text@2x.png b/superset/assets/branding/Superset_Logo_Gradient_Text@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..32ed8c0e77659436be843e0c55b9f6b72d26e0c3 GIT binary patch literal 16664 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 QM+QKWqH-d2LIy$q3%;-``Tzg` literal 0 HcmV?d00001 diff --git a/superset/assets/branding/Superset_Logo_Horizontal_Lockup.png b/superset/assets/branding/Superset_Logo_Horizontal_Lockup.png new file mode 100644 index 0000000000000000000000000000000000000000..6ebe4115e710d23d670cc9a1945421daa5d33ff3 GIT binary patch literal 8350 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 + + + + + + + + + + + + + + + + + + + diff --git a/superset/assets/branding/Superset_Logo_Horizontal_Lockup@2x.png b/superset/assets/branding/Superset_Logo_Horizontal_Lockup@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..95443ab4b89337b9b43eca98f7195a7116f27d55 GIT binary patch literal 20448 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 literal 0 HcmV?d00001 diff --git a/superset/assets/branding/Superset_Logo_No_Text.svg b/superset/assets/branding/Superset_Logo_No_Text.svg new file mode 100644 index 0000000000000..2dcc76b71dd03 --- /dev/null +++ b/superset/assets/branding/Superset_Logo_No_Text.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/superset/assets/branding/Superset_Logo_No_Text@2x.png b/superset/assets/branding/Superset_Logo_No_Text@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..56abe266023edf5b757fba3e54485f0d69e67891 GIT binary patch literal 17786 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 RTJPBCGYkwm-v1I7 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 + + + + + + + + + + + + + + + + + + + diff --git a/superset/assets/branding/Superset_Logo_Vertical_Lockup@2x.png b/superset/assets/branding/Superset_Logo_Vertical_Lockup@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6cd44b5c81f07d8919a1e50469c75546c4d9af6d GIT binary patch literal 20639 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 literal 0 HcmV?d00001 diff --git a/superset/assets/cypress.json b/superset/assets/cypress.json new file mode 100644 index 0000000000000..7ede593bdcc3c --- /dev/null +++ b/superset/assets/cypress.json @@ -0,0 +1,5 @@ +{ + "baseUrl": "http://localhost:8081", + "videoUploadOnPasses": false, + "ignoreTestFiles": "*.helper.js" +} diff --git a/superset/assets/cypress/.eslintrc b/superset/assets/cypress/.eslintrc new file mode 100644 index 0000000000000..5b988562725d3 --- /dev/null +++ b/superset/assets/cypress/.eslintrc @@ -0,0 +1,8 @@ +{ + "plugins": [ + "cypress" + ], + "env": { + "cypress/globals": true + } +} diff --git a/superset/assets/cypress/integration/dashboard/dashboard_tests.js b/superset/assets/cypress/integration/dashboard/dashboard_tests.js new file mode 100644 index 0000000000000..31ce7044558da --- /dev/null +++ b/superset/assets/cypress/integration/dashboard/dashboard_tests.js @@ -0,0 +1,26 @@ +describe('Load dashboard', () => { + it('Load birth names dashboard', () => { + cy.server(); + cy.login(); + + cy.visit('/superset/dashboard/births'); + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.wait(10000, ['@getJson']); + + let sliceData; + + cy.get('@getJson.all').then((xhrs) => { + sliceData = xhrs; + xhrs.forEach((data) => { + expect(data.status).to.eq(200); + expect(data.response.body).to.have.property('error', null); + cy.get(`#slice-container-${data.response.body.form_data.slice_id}`); + }); + cy.get('#app').then((data) => { + const bootstrapData = JSON.parse(data[0].dataset.bootstrap); + expect(bootstrapData.dashboard_data.slices.length).to.eq(sliceData.length); + }); + }); + }); +}); diff --git a/superset/assets/cypress/integration/explore/control_tests.js b/superset/assets/cypress/integration/explore/control_tests.js new file mode 100644 index 0000000000000..a742d6d0a9005 --- /dev/null +++ b/superset/assets/cypress/integration/explore/control_tests.js @@ -0,0 +1,62 @@ +// *********************************************** +// Tests for setting controls in the UI +// *********************************************** + +describe('Groupby', () => { + it('Set groupby', () => { + cy.server(); + cy.login(); + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@getJson' }); + + 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('button.query').click(); + cy.verifySliceSuccess({ waitAlias: '@getJson' }); + }); +}); + +describe('SimpleAdhocMetric', () => { + it('Clear metric and set simple adhoc metric', () => { + cy.server(); + cy.login(); + + const metricName = 'Girl Births'; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByName('Num Births Trend'); + cy.verifySliceSuccess({ waitAlias: '@getJson' }); + + 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('#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('button.query').click(); + cy.wait(['@getJson']).then((data) => { + expect(data.status).to.eq(200); + expect(data.response.body).to.have.property('error', null); + expect(data.response.body.data[0].key).to.equal(metricName); + cy.get('.slice_container'); + }); + }); +}); diff --git a/superset/assets/cypress/integration/explore/visualizations/big_number.js b/superset/assets/cypress/integration/explore/visualizations/big_number.js new file mode 100644 index 0000000000000..c6bca9e0e51e6 --- /dev/null +++ b/superset/assets/cypress/integration/explore/visualizations/big_number.js @@ -0,0 +1,60 @@ +import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper'; + +// Big Number Total + +describe('Big Number Total', () => { + const BIG_NUMBER_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'big_number_total' }; + + it('Test big number chart with adhoc metric', () => { + cy.server(); + cy.login(); + + const formData = { ...BIG_NUMBER_DEFAULTS, metric: NUM_METRIC }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', querySubstring: NUM_METRIC.label }); + }); + + it('Test big number chart with simple filter', () => { + cy.server(); + cy.login(); + + const filters = [ + { + expressionType: 'SIMPLE', + subject: 'name', + operator: 'in', + comparator: ['Aaron', 'Amy', 'Andrea'], + clause: 'WHERE', + sqlExpression: null, + fromFormData: true, + filterOptionName: 'filter_4y6teao56zs_ebjsvwy48c', + }, + ]; + + const formData = { ...BIG_NUMBER_DEFAULTS, metric: 'count', adhoc_filters: filters }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson' }); + }); + + it('Test big number chart ignores groupby', () => { + cy.server(); + cy.login(); + + const formData = { ...BIG_NUMBER_DEFAULTS, metric: NUM_METRIC, groupby: ['state'] }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.wait(['@getJson']).then((data) => { + expect(data.status).to.eq(200); + if (data.response.body.error) { + expect(data.response.body.error).to.eq(null); + } + expect(data.response.body.query).not.contains(formData.groupby[0]); + cy.get('.slice_container'); + }); + }); +}); diff --git a/superset/assets/cypress/integration/explore/visualizations/line.js b/superset/assets/cypress/integration/explore/visualizations/line.js new file mode 100644 index 0000000000000..dc4e7d4bc871a --- /dev/null +++ b/superset/assets/cypress/integration/explore/visualizations/line.js @@ -0,0 +1,157 @@ +import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper'; + +describe('Line', () => { + const LINE_CHART_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'line' }; + + it('Test line chart with adhoc metric', () => { + cy.server(); + cy.login(); + + const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with groupby', () => { + cy.server(); + cy.login(); + + const metrics = ['count']; + const groupby = ['gender']; + + const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with simple filter', () => { + cy.server(); + cy.login(); + + const metrics = ['count']; + const filters = [ + { + expressionType: 'SIMPLE', + subject: 'name', + operator: 'in', + comparator: ['Aaron', 'Amy', 'Andrea'], + clause: 'WHERE', + sqlExpression: null, + fromFormData: true, + filterOptionName: 'filter_4y6teao56zs_ebjsvwy48c', + }, + ]; + + const formData = { ...LINE_CHART_DEFAULTS, metrics, adhoc_filters: filters }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with series limit sort asc', () => { + cy.server(); + cy.login(); + + const formData = { + ...LINE_CHART_DEFAULTS, + metrics: [NUM_METRIC], + limit: 10, + groupby: ['name'], + timeseries_limit_metric: NUM_METRIC, + }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with series limit sort desc', () => { + cy.server(); + cy.login(); + + const formData = { + ...LINE_CHART_DEFAULTS, + metrics: [NUM_METRIC], + limit: 10, + groupby: ['name'], + timeseries_limit_metric: NUM_METRIC, + order_desc: true, + }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with rolling avg', () => { + cy.server(); + cy.login(); + + const metrics = [NUM_METRIC]; + + const formData = { ...LINE_CHART_DEFAULTS, metrics, rolling_type: 'mean', rolling_periods: 10 }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with time shift 1 year', () => { + cy.server(); + cy.login(); + + const metrics = [NUM_METRIC]; + + const formData = { + ...LINE_CHART_DEFAULTS, + metrics, + time_compare: ['1+year'], + comparison_type: 'values', + }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with time shift yoy', () => { + cy.server(); + cy.login(); + + const metrics = [NUM_METRIC]; + + const formData = { + ...LINE_CHART_DEFAULTS, + metrics, + time_compare: ['1+year'], + comparison_type: 'ratio', + }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); + + it('Test line chart with time shift percentage change', () => { + cy.server(); + cy.login(); + + const metrics = [NUM_METRIC]; + + const formData = { + ...LINE_CHART_DEFAULTS, + metrics, + time_compare: ['1+year'], + comparison_type: 'percentage', + }; + + cy.route('POST', '/superset/explore_json/**').as('getJson'); + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' }); + }); +}); diff --git a/superset/assets/cypress/integration/explore/visualizations/shared.helper.js b/superset/assets/cypress/integration/explore/visualizations/shared.helper.js new file mode 100644 index 0000000000000..7a1e5192dbd5e --- /dev/null +++ b/superset/assets/cypress/integration/explore/visualizations/shared.helper.js @@ -0,0 +1,40 @@ +// *********************************************** +// Constants for visualization tests +// *********************************************** + +export const FORM_DATA_DEFAULTS = { + datasource: '3__table', + granularity_sqla: 'ds', + time_grain_sqla: null, + time_range: '100+years+ago+:+now', + adhoc_filters: [], + groupby: [], + limit: null, + timeseries_limit_metric: null, + order_desc: false, + contribution: false, +}; + +export const NUM_METRIC = { + expressionType: 'SIMPLE', + column: { + id: 336, + column_name: 'num', + verbose_name: null, + description: null, + expression: '', + filterable: false, + groupby: false, + is_dttm: false, + type: 'BIGINT', + database_expression: null, + python_date_format: null, + optionName: '_col_num', + }, + aggregate: 'SUM', + sqlExpression: null, + hasCustomLabel: false, + fromFormData: false, + label: 'Sum(num)', + optionName: 'metric_1de0s4viy5d_ly7y8k6ghvk', + }; diff --git a/superset/assets/cypress/plugins/index.js b/superset/assets/cypress/plugins/index.js new file mode 100644 index 0000000000000..df3a5aeeaf1ec --- /dev/null +++ b/superset/assets/cypress/plugins/index.js @@ -0,0 +1,17 @@ +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +module.exports = (/* on, config */) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +}; diff --git a/superset/assets/cypress/support/commands.js b/superset/assets/cypress/support/commands.js new file mode 100644 index 0000000000000..28e02a5ac7474 --- /dev/null +++ b/superset/assets/cypress/support/commands.js @@ -0,0 +1,73 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This is will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) + +const BASE_EXPLORE_URL = '/superset/explore/?form_data='; + +Cypress.Commands.add('login', () => { + cy.request({ + method: 'POST', + url: 'http://localhost:8081/login/', + body: { username: 'admin', password: 'general' }, + }).then((response) => { + expect(response.status).to.eq(200); + }); +}); + +Cypress.Commands.add('visitChartByName', (name) => { + cy.request(`http://localhost:8081/chart/api/read?_flt_3_slice_name=${name}`).then((response) => { + cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${response.body.pks[0]}}`); + }); +}); + +Cypress.Commands.add('visitChartById', (chartId) => { + cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${chartId}}`); +}); + +Cypress.Commands.add('visitChartByParams', (params) => { + cy.visit(`${BASE_EXPLORE_URL}${params}`); +}); + +Cypress.Commands.add('verifySliceSuccess', ({ waitAlias, querySubstring, chartSelector }) => { + cy.wait(waitAlias).then((data) => { + expect(data.status).to.eq(200); + if (data.response.body.error) { + expect(data.response.body.error).to.eq(null); + } + if (querySubstring) { + expect(data.response.body.query).contains(querySubstring); + } + + cy.get('.slice_container').within(() => { + if (chartSelector) { + cy.get(chartSelector).then((charts) => { + const firstChart = charts[0]; + expect(firstChart.clientWidth).greaterThan(0); + expect(firstChart.clientHeight).greaterThan(0); + }); + } + }); + }); +}); diff --git a/superset/assets/cypress/support/index.js b/superset/assets/cypress/support/index.js new file mode 100644 index 0000000000000..37a498fb5bf39 --- /dev/null +++ b/superset/assets/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/superset/assets/cypress_build.sh b/superset/assets/cypress_build.sh new file mode 100755 index 0000000000000..dd80e873880a2 --- /dev/null +++ b/superset/assets/cypress_build.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +superset/bin/superset db upgrade +superset/bin/superset load_test_users +superset/bin/superset load_examples +superset/bin/superset init +superset/bin/superset runserver & + +cd "$(dirname "$0")" + +npm install -g yarn +yarn +npm run build +npm run cypress run +kill %1 diff --git a/superset/assets/images/viz_thumbnails/big_number.png b/superset/assets/images/viz_thumbnails/big_number.png index 01d6da45ba992678834bb427502074549d7d50b3..90ac5a5b120aba6d3fbd5dd7e6bba8fac239dba1 100644 GIT binary patch literal 103045 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 XP4UQ$wW1&YUqVw=SLLe`BIN%7X6AQ> diff --git a/superset/assets/package.json b/superset/assets/package.json index d66316a219b46..f326ec6d6b08e 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -8,15 +8,18 @@ "test": "spec" }, "scripts": { - "test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js 'spec/**/*_spec.*'", - "test:one": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js", - "cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --compilers babel-core/register --require spec/helpers/browser.js --require ignore-styles 'spec/**/*_spec.*'", + "tdd": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js 'spec/**/*_spec.*' --watch --recursive", + "test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js 'spec/**/*_spec.*'", + "test:one": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js", + "cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --compilers babel-core/register --require spec/helpers/shim.js --require ignore-styles 'spec/**/*_spec.*'", "dev": "webpack --mode=development --colors --progress --debug --watch", + "dev-server": "webpack-dev-server --mode=development --progress", "prod": "node --max_old_space_size=4096 webpack --mode=production --colors --progress", "build": "webpack --mode=production --colors --progress", "lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx .", "lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx .", - "sync-backend": "babel-node --presets env src/syncBackend.js" + "sync-backend": "babel-node --presets env src/syncBackend.js", + "cypress": "cypress" }, "repository": { "type": "git", @@ -44,9 +47,13 @@ "homepage": "http://superset.apache.org/", "dependencies": { "@data-ui/event-flow": "^0.0.54", + "@data-ui/histogram": "^0.0.64", "@data-ui/sparkline": "^0.0.54", + "@data-ui/theme": "^0.0.62", "@data-ui/xy-chart": "^0.0.61", - "@vx/responsive": "0.0.153", + "@vx/legend": "^0.0.170", + "@vx/responsive": "0.0.172", + "@vx/scale": "^0.0.165", "babel-register": "^6.24.1", "bootstrap": "^3.3.6", "bootstrap-slider": "^10.0.0", @@ -54,13 +61,14 @@ "classnames": "^2.2.5", "d3": "^3.5.17", "d3-cloud": "^1.2.1", + "d3-color": "^1.2.0", "d3-hierarchy": "^1.1.5", "d3-sankey": "^0.4.2", "d3-svg-legend": "^1.x", "d3-tip": "^0.9.1", "datamaps": "^0.5.8", "datatables.net-bs": "^1.10.15", - "deck.gl": "^5.1.4", + "deck.gl": "^5.3.4", "distributions": "^1.0.0", "dnd-core": "^2.6.0", "dompurify": "^1.0.3", @@ -70,32 +78,31 @@ "immutable": "^3.8.2", "jed": "^1.1.1", "jquery": "3.1.1", + "json-bigint": "^0.3.0", "lodash.throttle": "^4.1.1", - "luma.gl": "^5.1.4", "mapbox-gl": "^0.45.0", "mathjs": "^3.20.2", "moment": "^2.20.1", "mousetrap": "^1.6.1", "mustache": "^2.2.1", "nvd3": "1.8.6", - "parse-iso-duration": "^1.0.0", "prop-types": "^15.6.0", "re-resizable": "^4.3.1", - "react": "^15.6.2", + "react": "^16.4.1", "react-ace": "^5.10.0", "react-addons-css-transition-group": "^15.6.0", "react-addons-shallow-compare": "^15.4.2", "react-bootstrap": "^0.31.5", - "react-bootstrap-datetimepicker": "0.0.22", "react-bootstrap-dialog": "^0.10.0", "react-bootstrap-slider": "2.1.5", "react-bootstrap-table": "^4.3.1", "react-color": "^2.13.8", - "react-datetime": "2.14.0", + "react-datetime": "^2.14.0", "react-dnd": "^2.5.4", "react-dnd-html5-backend": "^2.5.4", - "react-dom": "^15.6.2", + "react-dom": "^16.4.1", "react-gravatar": "^2.6.1", + "react-hot-loader": "^4.3.6", "react-map-gl": "^3.0.4", "react-markdown": "^3.3.0", "react-redux": "^5.0.2", @@ -108,8 +115,8 @@ "react-sticky": "^6.0.2", "react-syntax-highlighter": "^7.0.4", "react-virtualized": "9.19.1", - "react-virtualized-select": "^2.4.0", - "reactable": "1.0.2", + "react-virtualized-select": "^3.1.3", + "reactable": "^1.1.0", "redux": "^3.5.2", "redux-localstorage": "^0.4.1", "redux-thunk": "^2.1.0", @@ -117,7 +124,7 @@ "shortid": "^2.2.6", "sprintf-js": "^1.1.1", "srcdoc-polyfill": "^1.0.0", - "supercluster": "https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40", + "supercluster": "^4.1.1", "underscore": "^1.8.3", "urijs": "^1.18.10", "viewport-mercator-project": "^5.0.0" @@ -133,20 +140,24 @@ "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-polyfill": "^6.23.0", "babel-preset-airbnb": "^2.1.1", + "babel-preset-env": "^1.7.0", "chai": "^4.0.2", "clean-webpack-plugin": "^0.1.19", "css-loader": "^0.28.0", - "enzyme": "^2.0.0", + "cypress": "^3.0.3", + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.1.1", "eslint": "^4.19.0", "eslint-config-airbnb": "^15.0.1", "eslint-config-prettier": "^2.9.0", + "eslint-plugin-cypress": "^2.0.1", "eslint-plugin-import": "^2.2.0", "eslint-plugin-jsx-a11y": "^5.1.1", "eslint-plugin-prettier": "^2.6.0", "eslint-plugin-react": "^7.0.1", "exports-loader": "^0.7.0", "file-loader": "^1.1.11", - "github-changes": "^1.0.4", + "gl": "^4.0.4", "ignore-styles": "^5.0.1", "imports-loader": "^0.7.1", "istanbul": "^1.0.0-alpha", @@ -155,8 +166,10 @@ "less": "^2.6.1", "less-loader": "^4.1.0", "mini-css-extract-plugin": "^0.4.0", - "mocha": "^3.2.0", - "npm-check-updates": "^2.14.0", + "minimist": "^1.2.0", + "mocha": "^3.5.3", + "npm-check-updates": "^2.14.2", + "optimize-css-assets-webpack-plugin": "^5.0.1", "po2json": "^0.4.5", "prettier": "^1.12.1", "react-addons-test-utils": "^15.6.2", @@ -167,9 +180,10 @@ "transform-loader": "^0.2.3", "uglifyjs-webpack-plugin": "^1.1.0", "url-loader": "^1.0.1", - "webpack": "^4.6.0", + "webpack": "^4.18.0", "webpack-assets-manifest": "^3.0.1", "webpack-cli": "^2.0.10", + "webpack-dev-server": "^3.1.7", "webpack-sources": "^1.1.0" } } diff --git a/superset/assets/spec/.eslintrc b/superset/assets/spec/.eslintrc new file mode 100644 index 0000000000000..5b4214b0d60f3 --- /dev/null +++ b/superset/assets/spec/.eslintrc @@ -0,0 +1,8 @@ +{ + "env": { + "mocha": true + }, + "rules": { + "import/no-extraneous-dependencies": ["error", {"devDependencies": true}] + } +} diff --git a/superset/assets/spec/helpers/browser.js b/superset/assets/spec/helpers/shim.js similarity index 81% rename from superset/assets/spec/helpers/browser.js rename to superset/assets/spec/helpers/shim.js index 80ce31539feae..6decdc96b21b3 100644 --- a/superset/assets/spec/helpers/browser.js +++ b/superset/assets/spec/helpers/shim.js @@ -1,7 +1,11 @@ -/* eslint no-undef: 0, no-native-reassign: 0 */ +/* eslint no-native-reassign: 0 */ import 'babel-polyfill'; import chai from 'chai'; import jsdom from 'jsdom'; +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); require('babel-register')({ // NOTE: If `dynamic-import-node` is in .babelrc alongside @@ -30,6 +34,10 @@ global.navigator = { appName: 'Netscape', }; +// Fix `Option is not defined` +// https://stackoverflow.com/questions/39501589/jsdom-option-is-not-defined-when-running-my-mocha-test +global.Option = window.Option; + // Configuration copied from https://github.com/sinonjs/sinon/issues/657 // allowing for sinon.fakeServer to work diff --git a/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx b/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx index 6955688851675..20bc359dc6135 100644 --- a/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx +++ b/superset/assets/spec/javascripts/CRUD/CollectionTable_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import CollectionTable from '../../../src/CRUD/CollectionTable'; diff --git a/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx b/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx index 96ed089599736..de242ba999d45 100644 --- a/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx +++ b/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import { Button } from 'react-bootstrap'; import Select from 'react-virtualized-select'; diff --git a/superset/assets/spec/javascripts/chart/Chart_spec.jsx b/superset/assets/spec/javascripts/chart/Chart_spec.jsx index 30f24feb6574e..e5c0bb9843ccb 100644 --- a/superset/assets/spec/javascripts/chart/Chart_spec.jsx +++ b/superset/assets/spec/javascripts/chart/Chart_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; @@ -66,12 +65,10 @@ describe('Chart', () => { }); it('should call after resize', () => { - const prevProp = wrapper.props(); wrapper.setProps({ chartStatus: 'rendered', height: 100, }); - wrapper.instance().componentDidUpdate(prevProp); expect(stub.callCount).to.equals(1); }); }); diff --git a/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx b/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx index 867006993e8ba..316ac34774f81 100644 --- a/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx +++ b/superset/assets/spec/javascripts/components/AlteredSliceTag_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { Table, Thead, Td, Th, Tr } from 'reactable'; diff --git a/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx b/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx index bde4610258b41..7401eae54cdd0 100644 --- a/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx +++ b/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import Select from 'react-select'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; @@ -63,7 +62,6 @@ describe('AsyncSelect', () => { , ); const spy = sinon.spy(wrapper.instance(), 'onChange'); - wrapper.instance().fetchOptions(); server.respond(); expect(spy.callCount).to.equal(1); diff --git a/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx b/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx index 8a7f74abcdbb3..8358b49e97dc4 100644 --- a/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx +++ b/superset/assets/spec/javascripts/components/CachedLabel_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { Label } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/components/Checkbox_spec.jsx b/superset/assets/spec/javascripts/components/Checkbox_spec.jsx index b53fbfab53745..8b74d12589bd7 100644 --- a/superset/assets/spec/javascripts/components/Checkbox_spec.jsx +++ b/superset/assets/spec/javascripts/components/Checkbox_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import sinon from 'sinon'; import { shallow } from 'enzyme'; diff --git a/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx b/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx index 1c531a1ff19a1..04529e1240d30 100644 --- a/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx +++ b/superset/assets/spec/javascripts/components/ColumnOption_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import ColumnOption from '../../../src/components/ColumnOption'; diff --git a/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx b/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx index 9e3cfe82a3c5f..587469f5c68ec 100644 --- a/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx +++ b/superset/assets/spec/javascripts/components/ColumnTypeLabel_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import ColumnTypeLabel from '../../../src/components/ColumnTypeLabel'; diff --git a/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx b/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx index 84357d799e1d9..f8a042610b629 100644 --- a/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx +++ b/superset/assets/spec/javascripts/components/CopyToClipboard_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import CopyToClipboard from '../../../src/components/CopyToClipboard'; diff --git a/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx b/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx index 81c8f15016e01..5232d8ab8c051 100644 --- a/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx +++ b/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { mount } from 'enzyme'; import FilterableTable from '../../../../src/components/FilterableTable/FilterableTable'; diff --git a/superset/assets/spec/javascripts/components/MetricOption_spec.jsx b/superset/assets/spec/javascripts/components/MetricOption_spec.jsx index 7af2cebd60993..3fd923000d7cf 100644 --- a/superset/assets/spec/javascripts/components/MetricOption_spec.jsx +++ b/superset/assets/spec/javascripts/components/MetricOption_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import MetricOption from '../../../src/components/MetricOption'; diff --git a/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx b/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx index 6f132a9bcc3ee..41adf12af7b66 100644 --- a/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx +++ b/superset/assets/spec/javascripts/components/ModalTrigger_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import ModalTrigger from '../../../src/components/ModalTrigger'; diff --git a/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx b/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx index 278a1ac24a1b6..43b5a268312cc 100644 --- a/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx +++ b/superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx @@ -3,7 +3,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import VirtualizedSelect from 'react-virtualized-select'; import Select, { Creatable } from 'react-select'; diff --git a/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx b/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx index cf45332ebb9af..4b818e108bd69 100644 --- a/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx +++ b/superset/assets/spec/javascripts/components/OptionDescription_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import InfoTooltipWithTrigger from '../../../src/components/InfoTooltipWithTrigger'; diff --git a/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx b/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx index e5a17b75c1c89..33826fecbbb93 100644 --- a/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx +++ b/superset/assets/spec/javascripts/components/PopoverSection_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import PopoverSection from '../../../src/components/PopoverSection'; diff --git a/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx b/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx index 1aa0074ccf951..67edd08ca6315 100644 --- a/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx +++ b/superset/assets/spec/javascripts/components/URLShortLinkButton_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { OverlayTrigger } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx b/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx index 494d0d390a0a1..6311262da638c 100644 --- a/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx +++ b/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import URLShortLinkModal from '../../../src/components/URLShortLinkModal'; diff --git a/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx b/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx index e793fef2c48df..a854f7eb27f1c 100644 --- a/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx +++ b/superset/assets/spec/javascripts/components/VirtualizedRendererWrap_spec.jsx @@ -3,7 +3,6 @@ import React from 'react'; import sinon from 'sinon'; import PropTypes from 'prop-types'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import VirtualizedRendererWrap from '../../../src/components/VirtualizedRendererWrap'; diff --git a/superset/assets/spec/javascripts/dashboard/.eslintrc b/superset/assets/spec/javascripts/dashboard/.eslintrc index a3f86e3a17a0c..36759a20b1e5a 100644 --- a/superset/assets/spec/javascripts/dashboard/.eslintrc +++ b/superset/assets/spec/javascripts/dashboard/.eslintrc @@ -17,7 +17,6 @@ "no-mixed-operators": 0, "no-continue": 2, "no-bitwise": 2, - "no-undef": 2, "no-multi-assign": 2, "no-restricted-properties": 2, "no-prototype-builtins": 2, diff --git a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js index e58bb11a48bff..5be11913d2cae 100644 --- a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js +++ b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx index d316dc3d385b7..094e1ee65b32f 100644 --- a/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/CodeModal_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import CodeModal from '../../../../src/dashboard/components/CodeModal'; diff --git a/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx index 8c991fa489cb1..d630fe91698d5 100644 --- a/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/CssEditor_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import CssEditor from '../../../../src/dashboard/components/CssEditor'; diff --git a/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx index 4c3185fecdebf..7215e0859b530 100644 --- a/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx @@ -1,7 +1,6 @@ import { Provider } from 'react-redux'; import React from 'react'; import { shallow, mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import ParentSize from '@vx/responsive/build/components/ParentSize'; diff --git a/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx index d11c37f3315fa..83f97608457c9 100644 --- a/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/DashboardGrid_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx index aa82f01705e91..76ff38872c6ef 100644 --- a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; @@ -152,7 +151,6 @@ describe('Dashboard', () => { it('should call refresh if a filter is added', () => { const wrapper = setup({ dashboardState: overrideDashboardState }); const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept'); - const prevProps = wrapper.instance().props; wrapper.setProps({ dashboardState: { ...overrideDashboardState, @@ -162,7 +160,6 @@ describe('Dashboard', () => { }, }, }); - wrapper.instance().componentDidUpdate(prevProps); refreshExceptSpy.restore(); expect(refreshExceptSpy.callCount).to.equal(1); }); @@ -170,14 +167,12 @@ describe('Dashboard', () => { it('should call refresh if a filter is removed', () => { const wrapper = setup({ dashboardState: overrideDashboardState }); const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept'); - const prevProps = wrapper.instance().props; wrapper.setProps({ dashboardState: { ...overrideDashboardState, filters: {}, }, }); - wrapper.instance().componentDidUpdate(prevProps); refreshExceptSpy.restore(); expect(refreshExceptSpy.callCount).to.equal(1); }); @@ -185,7 +180,6 @@ describe('Dashboard', () => { it('should call refresh if a filter is changed', () => { const wrapper = setup({ dashboardState: overrideDashboardState }); const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept'); - const prevProps = wrapper.instance().props; wrapper.setProps({ dashboardState: { ...overrideDashboardState, @@ -195,7 +189,6 @@ describe('Dashboard', () => { }, }, }); - wrapper.instance().componentDidUpdate(prevProps); refreshExceptSpy.restore(); expect(refreshExceptSpy.callCount).to.equal(1); }); @@ -203,7 +196,6 @@ describe('Dashboard', () => { it('should not call refresh if filters change and refresh is false', () => { const wrapper = setup({ dashboardState: overrideDashboardState }); const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept'); - const prevProps = wrapper.instance().props; wrapper.setProps({ dashboardState: { ...overrideDashboardState, @@ -214,7 +206,6 @@ describe('Dashboard', () => { refresh: false, }, }); - wrapper.instance().componentDidUpdate(prevProps); refreshExceptSpy.restore(); expect(refreshExceptSpy.callCount).to.equal(0); }); diff --git a/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx index 673118bd3b7b2..199b3d21262aa 100644 --- a/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { shallow } from 'enzyme'; import { DropdownButton, MenuItem } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx index e7ecfc142c9fe..28153bb3ecb03 100644 --- a/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { shallow } from 'enzyme'; import Header from '../../../../src/dashboard/components/Header'; diff --git a/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx index 92a18c10c9ee3..e43f1147a171c 100644 --- a/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/MissingChart_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import Loading from '../../../../src/components/Loading'; diff --git a/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx index 564857c0887e9..7df757589beda 100644 --- a/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import RefreshIntervalModal from '../../../../src/dashboard/components/RefreshIntervalModal'; diff --git a/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx index da0f7df754410..704eb52732d7b 100644 --- a/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/SliceAdder_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it, beforeEach, afterEach } from 'mocha'; import sinon from 'sinon'; import { expect } from 'chai'; diff --git a/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx index c7e2c2a8d3672..b45e9d8ac3624 100644 --- a/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/dnd/DragDroppable_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx index 821b6371f70c1..a2e50e8d2cfbd 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx @@ -1,7 +1,6 @@ import { Provider } from 'react-redux'; import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx index dbd70540c4c5b..db8b45af57364 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx index e97414b65e205..a0fbffde0d5b2 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx @@ -1,7 +1,6 @@ import { Provider } from 'react-redux'; import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx index c8317f8459faf..05425324f338c 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx index 1d547756a896a..f21f106749754 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Header_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx index ca71045de7f42..f3aceee5b42bc 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx @@ -1,7 +1,6 @@ import { Provider } from 'react-redux'; import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; import AceEditor from 'react-ace'; @@ -113,9 +112,10 @@ describe('Markdown', () => { // the mode dropdown onchange instead const dropdown = wrapper.find(MarkdownModeDropdown); dropdown.prop('onChange')('preview'); + wrapper.update(); - expect(wrapper.find(AceEditor)).to.have.length(0); expect(wrapper.find(ReactMarkdown)).to.have.length(1); + expect(wrapper.find(AceEditor)).to.have.length(0); }); it('should call updateComponents when editMode changes from edit => preview, and there are markdownSource changes', () => { diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx index a718ff406a1af..54037c770ffc1 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx @@ -1,7 +1,6 @@ import { Provider } from 'react-redux'; import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx index a984565b4c4fb..1162e84261f3c 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx @@ -1,12 +1,11 @@ import { Provider } from 'react-redux'; import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; import DashboardComponent from '../../../../../src/dashboard/containers/DashboardComponent'; -import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton'; +import DeleteComponentModal from '../../../../../src/dashboard/components/DeleteComponentModal'; import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable'; import EditableTitle from '../../../../../src/components/EditableTitle'; import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu'; @@ -86,14 +85,14 @@ describe('Tabs', () => { expect(wrapper.find(WithPopoverMenu)).to.have.length(1); }); - it('should render a DeleteComponentButton when focused if its not the only tab', () => { + it('should render a DeleteComponentModal when focused if its not the only tab', () => { let wrapper = setup(); wrapper.find(WithPopoverMenu).simulate('click'); // focus - expect(wrapper.find(DeleteComponentButton)).to.have.length(0); + expect(wrapper.find(DeleteComponentModal)).to.have.length(0); wrapper = setup({ editMode: true }); wrapper.find(WithPopoverMenu).simulate('click'); - expect(wrapper.find(DeleteComponentButton)).to.have.length(1); + expect(wrapper.find(DeleteComponentModal)).to.have.length(1); wrapper = setup({ editMode: true, @@ -103,16 +102,18 @@ describe('Tabs', () => { }, }); wrapper.find(WithPopoverMenu).simulate('click'); - expect(wrapper.find(DeleteComponentButton)).to.have.length(0); + expect(wrapper.find(DeleteComponentModal)).to.have.length(0); }); - it('should call deleteComponent when deleted', () => { + it('should show modal when clicked delete icon', () => { const deleteComponent = sinon.spy(); const wrapper = setup({ editMode: true, deleteComponent }); wrapper.find(WithPopoverMenu).simulate('click'); // focus - wrapper.find(DeleteComponentButton).simulate('click'); + wrapper.find('.icon-button').simulate('click'); - expect(deleteComponent.callCount).to.equal(1); + const modal = document.getElementsByClassName('modal'); + expect(modal).to.have.length(1); + expect(deleteComponent.callCount).to.equal(0); }); }); diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx index d521fe50457ba..16f4360c3d3f5 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx @@ -1,7 +1,6 @@ import { Provider } from 'react-redux'; import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; import { Tabs as BootstrapTabs, Tab as BootstrapTab } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx index 4334b37ca455a..9e2993ad677be 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/DraggableNewComponent_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import DragDroppable from '../../../../../../src/dashboard/components/dnd/DragDroppable'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx index cb07cb988a4e3..240cf5ea8a37a 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewColumn_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx index 71703b3900fc6..af96f6045eae8 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewDivider_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx index a499fe8f6e836..5f2194c62ded1 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewHeader_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx index e91893d489dd1..b86d1674bb9f9 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewRow_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent'; diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx index 4e71c8ca42612..edd13b76d0e3f 100644 --- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/new/NewTabs_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent'; diff --git a/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx index 1f8508574371d..3d0fca7c8edae 100644 --- a/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu'; diff --git a/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx index 5add770a8cf2b..d382d252e1dff 100644 --- a/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/menu/WithPopoverMenu_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu'; diff --git a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx index 69fca76f692cf..be4ae7c283117 100644 --- a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import Resizable from 're-resizable'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import ResizableContainer from '../../../../../src/dashboard/components/resizable/ResizableContainer'; diff --git a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx index 0c37855d25d27..66e42860e7360 100644 --- a/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import ResizableHandle from '../../../../../src/dashboard/components/resizable/ResizableHandle'; diff --git a/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js b/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js index dd933ac572697..1b805e0d6b8b1 100644 --- a/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js +++ b/superset/assets/spec/javascripts/dashboard/reducers/dashboardLayout_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import layoutReducer from '../../../../src/dashboard/reducers/dashboardLayout'; diff --git a/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js b/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js index 7772f71015515..a8e3dbd86ea66 100644 --- a/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js +++ b/superset/assets/spec/javascripts/dashboard/reducers/dashboardState_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { diff --git a/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js b/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js index 7e3bb76bc128e..df43ae591d5ae 100644 --- a/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js +++ b/superset/assets/spec/javascripts/dashboard/reducers/sliceEntities_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { diff --git a/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js b/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js index b49a91f7bb2f4..e8986be49d5ec 100644 --- a/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/componentIsResizable_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import componentIsResizable from '../../../../src/dashboard/util/componentIsResizable'; diff --git a/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js b/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js index 4ff6a52bcc730..7169229be7616 100644 --- a/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/dnd-reorder_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import reorderItem from '../../../../src/dashboard/util/dnd-reorder'; diff --git a/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js b/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js index 8e6f88964c61e..3fc7d0250e2d7 100644 --- a/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/dropOverflowsParent_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import dropOverflowsParent from '../../../../src/dashboard/util/dropOverflowsParent'; diff --git a/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js b/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js index ecaca676640ba..372f4cb93a730 100644 --- a/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/findFirstParentContainer_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import findFirstParentContainerId from '../../../../src/dashboard/util/findFirstParentContainer'; diff --git a/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js b/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js index 71c8aece17d12..2ff15b23e3205 100644 --- a/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/findParentId_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import findParentId from '../../../../src/dashboard/util/findParentId'; diff --git a/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js b/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js index 71bbccd59a261..3674ac5faf8cb 100644 --- a/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/getChartIdsFromLayout_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import getChartIdsFromLayout from '../../../../src/dashboard/util/getChartIdsFromLayout'; diff --git a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js index c45e65a02a35d..cf58c78364d3d 100644 --- a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import getDashboardUrl from '../../../../src/dashboard/util/getDashboardUrl'; diff --git a/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js b/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js index 99e2282f7a2e5..e977e289f3773 100644 --- a/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/getDetailedComponentWidth_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import getDetailedComponentWidth from '../../../../src/dashboard/util/getDetailedComponentWidth'; diff --git a/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js b/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js index 287b7a6f33e6c..938a86adeba3e 100644 --- a/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/getDropPosition_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import getDropPosition, { diff --git a/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js b/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js index 388630be52286..66ef5e7207f73 100644 --- a/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import getFormDataWithExtraFilters from '../../../../src/dashboard/util/charts/getFormDataWithExtraFilters'; diff --git a/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js b/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js index 3563059d7b1a7..2a0efcbb358de 100644 --- a/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/isValidChild_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import isValidChild from '../../../../src/dashboard/util/isValidChild'; diff --git a/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js b/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js index f52eba978f7c3..b7cdb70e320c5 100644 --- a/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/newComponentFactory_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import newComponentFactory from '../../../../src/dashboard/util/newComponentFactory'; diff --git a/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js b/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js index 8d00c186e290b..c86182773733e 100644 --- a/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js +++ b/superset/assets/spec/javascripts/dashboard/util/newEntitiesFromDrop_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import newEntitiesFromDrop from '../../../../src/dashboard/util/newEntitiesFromDrop'; diff --git a/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx b/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx index 137ac3956ffb6..0b7c6ab614abf 100644 --- a/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx +++ b/superset/assets/spec/javascripts/datasource/DatasourceEditor_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { Tabs } from 'react-bootstrap'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import configureStore from 'redux-mock-store'; import $ from 'jquery'; diff --git a/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx b/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx index 3cbc92f9ec9af..ef0a4f4d5a098 100644 --- a/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx +++ b/superset/assets/spec/javascripts/datasource/DatasourceModal_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { Modal } from 'react-bootstrap'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import configureStore from 'redux-mock-store'; import { shallow } from 'enzyme'; import $ from 'jquery'; diff --git a/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js b/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js index 36d3bdd09552d..eb3161e193b57 100644 --- a/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js +++ b/superset/assets/spec/javascripts/explore/AdhocFilter_spec.js @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import { describe, it } from 'mocha'; import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../../../src/explore/AdhocFilter'; diff --git a/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js b/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js index 432be7620a2da..fe6797c013fc0 100644 --- a/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js +++ b/superset/assets/spec/javascripts/explore/AdhocMetric_spec.js @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import { describe, it } from 'mocha'; import AdhocMetric, { EXPRESSION_TYPES } from '../../../src/explore/AdhocMetric'; import { AGGREGATES } from '../../../src/explore/constants'; diff --git a/superset/assets/spec/javascripts/explore/chartActions_spec.js b/superset/assets/spec/javascripts/explore/chartActions_spec.js index f1e49efee2ef6..6d8b009e701a9 100644 --- a/superset/assets/spec/javascripts/explore/chartActions_spec.js +++ b/superset/assets/spec/javascripts/explore/chartActions_spec.js @@ -1,4 +1,3 @@ -import { it, describe } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; import $ from 'jquery'; diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx index 2123ed7c2e09f..f38562534aed9 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterControl_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../../../../src/explore/AdhocFilter'; diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx index 2014bbc7c9f3e..7c99c6c3aed49 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSimpleTabContent_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { FormGroup } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx index a1cdb232472ad..c3766bf388f62 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopoverSqlTabContent_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { FormGroup } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx index 3b062ed272855..23d3b9ab56c08 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { Button, Popover, Tab, Tabs } from 'react-bootstrap'; @@ -106,7 +105,7 @@ describe('AdhocFilterEditPopover', () => { expect(wrapper.find('i.glyphicon-resize-full')).to.have.lengthOf(1); expect(wrapper.instance().onDragDown.calledOnce).to.be.false; - wrapper.find('i.glyphicon-resize-full').simulate('mouseDown'); + wrapper.find('i.glyphicon-resize-full').simulate('mouseDown', {}); expect(wrapper.instance().onDragDown.calledOnce).to.be.true; }); }); diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx index 673b854e5c8f4..9d2e2d3ef5fb6 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterOption_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { Label, OverlayTrigger } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx index 4acb24e279dd3..d845cc5852126 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { OverlayTrigger } from 'react-bootstrap'; @@ -36,7 +35,7 @@ describe('AdhocMetricEditPopoverTitle', () => { it('renders an OverlayTrigger wrapper with the title', () => { const { wrapper } = setup(); expect(wrapper.find(OverlayTrigger)).to.have.lengthOf(1); - expect(wrapper.find(OverlayTrigger).dive().text()).to.equal('My Metric\xa0'); + expect(wrapper.find(OverlayTrigger).find('span').text()).to.equal('My Metric\xa0'); }); it('transfers to edit mode when clicked', () => { diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx index eebc70386903e..5f03c2a7b0336 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopover_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { Button, FormGroup, Popover } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx index 4ed5d68d7dd84..47c09f965b710 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricOption_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { Label, OverlayTrigger } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx index 54ff78e66f135..b0e426c000449 100644 --- a/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricStaticOption_spec.jsx @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import AdhocMetricStaticOption from '../../../../src/explore/components/AdhocMetricStaticOption'; diff --git a/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx index 233eb8d75f2af..e70858b40c82d 100644 --- a/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/AggregateOption_spec.jsx @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import AggregateOption from '../../../../src/explore/components/AggregateOption'; diff --git a/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx index 522329a512b28..9e98cb6e2e7ea 100644 --- a/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/BoundsControl_spec.jsx @@ -3,7 +3,6 @@ import React from 'react'; import { FormControl } from 'react-bootstrap'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { mount } from 'enzyme'; import BoundsControl from '../../../../src/explore/components/controls/BoundsControl'; diff --git a/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx index a3312450b0777..27aba7d371a17 100644 --- a/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/CheckboxControl_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import CheckboxControl from '../../../../src/explore/components/controls/CheckboxControl'; diff --git a/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx index ec586385695d5..2285df4230436 100644 --- a/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ColorPickerControl_spec.jsx @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import { OverlayTrigger } from 'react-bootstrap'; import { SketchPicker } from 'react-color'; diff --git a/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx b/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx index a7d4d66ab6762..1551286af2375 100644 --- a/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ColorScheme_spec.jsx @@ -1,16 +1,15 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { mount } from 'enzyme'; import { Creatable } from 'react-select'; import ColorSchemeControl from '../../../../src/explore/components/controls/ColorSchemeControl'; -import { ALL_COLOR_SCHEMES } from '../../../../src/modules/colors'; +import { getAllSchemes } from '../../../../src/modules/ColorSchemeManager'; const defaultProps = { - options: Object.keys(ALL_COLOR_SCHEMES).map(s => ([s, s])), + options: Object.keys(getAllSchemes()).map(s => ([s, s])), }; describe('ColorSchemeControl', () => { diff --git a/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx b/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx index c63392e41ec1b..7a9a59da0c805 100644 --- a/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ControlPanelSection_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { Panel } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx b/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx index 64a657e1f7684..7f408f9d6d863 100644 --- a/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ControlPanelsContainer_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import { getFormDataFromControls, defaultControls } from '../../../../src/explore/store'; diff --git a/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx b/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx index 118799eb8eba3..80fc0d0f463fd 100644 --- a/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ControlRow_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import ControlSetRow from '../../../../src/explore/components/ControlRow'; diff --git a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx index d03e30b0abfda..7f28bf31355ca 100644 --- a/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/DatasourceControl_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import configureStore from 'redux-mock-store'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import DatasourceModal from '../../../../src/datasource/DatasourceModal'; import DatasourceControl from '../../../../src/explore/components/controls/DatasourceControl'; diff --git a/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx index 0892d05abbb23..2230d973d54d8 100644 --- a/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx @@ -2,9 +2,8 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; -import { Button } from 'react-bootstrap'; +import { Button, Label } from 'react-bootstrap'; import DateFilterControl from '../../../../src/explore/components/controls/DateFilterControl'; import ControlHeader from '../../../../src/explore/components/ControlHeader'; @@ -29,36 +28,28 @@ describe('DateFilterControl', () => { expect(controlHeader).to.have.lengthOf(1); }); it('renders 3 Buttons', () => { - const label = wrapper.find('.label').first(); + const label = wrapper.find(Label).first(); label.simulate('click'); setTimeout(() => { expect(wrapper.find(Button)).to.have.length(3); }, 10); }); it('loads the right state', () => { - const label = wrapper.find('.label').first(); + const label = wrapper.find(Label).first(); label.simulate('click'); setTimeout(() => { expect(wrapper.state().num).to.equal('90'); }, 10); }); - it('sets now and closes', () => { - const label = wrapper.find('.now').first(); - label.simulate('click'); - setTimeout(() => { - expect(wrapper.state().free).to.equal('now'); - expect(wrapper.find('.popover')).to.have.length(0); - }, 10); - }); it('renders 2 dimmed sections', () => { - const label = wrapper.find('.label').first(); + const label = wrapper.find(Label).first(); label.simulate('click'); setTimeout(() => { expect(wrapper.find(Button)).to.have.length(3); }, 10); }); it('opens and closes', () => { - const label = wrapper.find('.label').first(); + const label = wrapper.find(Label).first(); label.simulate('click'); setTimeout(() => { expect(wrapper.find('.popover')).to.have.length(1); diff --git a/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx index 49c41d37037aa..8bca8714f5aeb 100644 --- a/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx @@ -1,8 +1,6 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { mount } from 'enzyme'; -import { Modal } from 'react-bootstrap'; import ModalTrigger from './../../../../src/components/ModalTrigger'; import DisplayQueryButton from '../../../../src/explore/components/DisplayQueryButton'; @@ -27,6 +25,5 @@ describe('DisplayQueryButton', () => { it('renders a dropdown', () => { const wrapper = mount(); expect(wrapper.find(ModalTrigger)).to.have.lengthOf(2); - expect(wrapper.find(Modal)).to.have.lengthOf(2); }); }); diff --git a/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx index 3789fcc8afedb..896b3103a2038 100644 --- a/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow, mount } from 'enzyme'; import { OverlayTrigger } from 'react-bootstrap'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx b/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx index 5e701f2fb23d4..933e0dfb7e909 100644 --- a/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import ExploreActionButtons from '../../../../src/explore/components/ExploreActionButtons'; diff --git a/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx b/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx index 03c6ad17f983e..b354b8d46aed3 100644 --- a/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import ExploreChartHeader from '../../../../src/explore/components/ExploreChartHeader'; diff --git a/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.js b/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.js index b159f17cb62e1..0d82de58b9041 100644 --- a/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.js +++ b/superset/assets/spec/javascripts/explore/components/ExploreChartPanel_spec.js @@ -3,8 +3,7 @@ // import React from 'react'; // import { expect } from 'chai'; -// import { describe, it } from 'mocha'; - +// // import ChartContainer from '../../../../src/explore/components/ChartContainer'; // describe('ChartContainer', () => { diff --git a/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.js b/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.js index 9fe35e8bcbac2..e6340f2aea03b 100644 --- a/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.js +++ b/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.js @@ -3,8 +3,7 @@ // import React from 'react'; // import { expect } from 'chai'; -// import { describe, it } from 'mocha'; -// import { shallow } from 'enzyme'; +// // import { shallow } from 'enzyme'; // import ExploreViewContainer // from '../../../../src/explore/components/ExploreViewContainer'; diff --git a/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx index 05e02b92a4b45..62e4fe3760099 100644 --- a/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/FilterDefinitionOption_spec.jsx @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import FilterDefinitionOption from '../../../../src/explore/components/FilterDefinitionOption'; diff --git a/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx index 97a685822c4e3..4774d9ca59038 100644 --- a/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/FixedOrMetricControl_spec.jsx @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import { OverlayTrigger } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx b/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx index 418e9c5812a2e..1a8f766c4d50f 100644 --- a/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/MetricDefinitionOption_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import MetricDefinitionOption from '../../../../src/explore/components/MetricDefinitionOption'; diff --git a/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx b/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx index 896a5276e297c..5e0f3aea95176 100644 --- a/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/MetricDefinitionValue_spec.jsx @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import MetricDefinitionValue from '../../../../src/explore/components/MetricDefinitionValue'; diff --git a/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx index 8afc9900bf6d0..6685bffa83785 100644 --- a/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/MetricsControl_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import MetricsControl from '../../../../src/explore/components/controls/MetricsControl'; diff --git a/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx b/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx index c22a7bb7ab3ad..70712af9172f4 100644 --- a/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/QueryAndSaveBtns_spec.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { beforeEach, describe, it } from 'mocha'; import { expect } from 'chai'; import { shallow } from 'enzyme'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx b/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx index 9cdd4851b5790..53dd86095b273 100644 --- a/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/RowCountLabel_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import { Label } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx index 33ab84487a9ff..f29f491c9ac60 100644 --- a/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/RunQueryActionButton_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import RunQueryActionButton diff --git a/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx b/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx index 5e1e4b8cd5d8c..4b4f1a43bcea0 100644 --- a/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx @@ -3,7 +3,6 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow, mount } from 'enzyme'; import { Modal, Button, Radio } from 'react-bootstrap'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx index 38194e5cba407..b0cf1db5f55ef 100644 --- a/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/SelectControl_spec.jsx @@ -4,7 +4,6 @@ import Select, { Creatable } from 'react-select'; import VirtualizedSelect from 'react-virtualized-select'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import OnPasteSelect from '../../../../src/components/OnPasteSelect'; import VirtualizedRendererWrap from '../../../../src/components/VirtualizedRendererWrap'; diff --git a/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx b/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx index c1253f093361b..d6fc1222dc0b7 100644 --- a/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx @@ -3,7 +3,6 @@ import React from 'react'; import { FormControl } from 'react-bootstrap'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import AceEditor from 'react-ace'; diff --git a/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx index 7e28ab0d8ae5f..3a03d76c4272b 100644 --- a/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/TimeSeriesColumnControl_spec.jsx @@ -3,7 +3,6 @@ import React from 'react'; import { FormControl, OverlayTrigger } from 'react-bootstrap'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import TimeSeriesColumnControl from '../../../../src/explore/components/controls/TimeSeriesColumnControl'; diff --git a/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx index a840e7cbb3798..eef3f2772cf9f 100644 --- a/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/ViewportControl_spec.jsx @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import { OverlayTrigger, Label } from 'react-bootstrap'; diff --git a/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx index 03b73c9eae28f..bd41e1d6de6bc 100644 --- a/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/VizTypeControl_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import { Modal } from 'react-bootstrap'; import VizTypeControl from '../../../../src/explore/components/controls/VizTypeControl'; diff --git a/superset/assets/spec/javascripts/explore/exploreActions_spec.js b/superset/assets/spec/javascripts/explore/exploreActions_spec.js index 72aebbd551aa3..7b4749a579d39 100644 --- a/superset/assets/spec/javascripts/explore/exploreActions_spec.js +++ b/superset/assets/spec/javascripts/explore/exploreActions_spec.js @@ -1,5 +1,4 @@ /* eslint-disable no-unused-expressions */ -import { it, describe } from 'mocha'; import { expect } from 'chai'; import { defaultState } from '../../../src/explore/store'; import exploreReducer from '../../../src/explore/reducers/exploreReducer'; diff --git a/superset/assets/spec/javascripts/explore/utils_spec.jsx b/superset/assets/spec/javascripts/explore/utils_spec.jsx index 8bfea68464042..9d2eaf0154236 100644 --- a/superset/assets/spec/javascripts/explore/utils_spec.jsx +++ b/superset/assets/spec/javascripts/explore/utils_spec.jsx @@ -1,4 +1,3 @@ -import { it, describe } from 'mocha'; import { expect } from 'chai'; import URI from 'urijs'; import { getExploreUrlAndPayload, getExploreLongUrl } from '../../../src/explore/exploreUtils'; diff --git a/superset/assets/spec/javascripts/logger_spec.js b/superset/assets/spec/javascripts/logger_spec.js index 64580b4bdbcd3..e5b46b4f9c4cf 100644 --- a/superset/assets/spec/javascripts/logger_spec.js +++ b/superset/assets/spec/javascripts/logger_spec.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/messageToasts/.eslintrc b/superset/assets/spec/javascripts/messageToasts/.eslintrc index a3f86e3a17a0c..36759a20b1e5a 100644 --- a/superset/assets/spec/javascripts/messageToasts/.eslintrc +++ b/superset/assets/spec/javascripts/messageToasts/.eslintrc @@ -17,7 +17,6 @@ "no-mixed-operators": 0, "no-continue": 2, "no-bitwise": 2, - "no-undef": 2, "no-multi-assign": 2, "no-restricted-properties": 2, "no-prototype-builtins": 2, diff --git a/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx b/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx index aa04adcb3cda7..ed39d7c745170 100644 --- a/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx +++ b/superset/assets/spec/javascripts/messageToasts/components/ToastPresenter_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import mockMessageToasts from '../mockMessageToasts'; diff --git a/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx index ce3396cd62391..2ecf88959fbeb 100644 --- a/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx +++ b/superset/assets/spec/javascripts/messageToasts/components/Toast_spec.jsx @@ -1,7 +1,6 @@ import { Alert } from 'react-bootstrap'; import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import mockMessageToasts from '../mockMessageToasts'; diff --git a/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js b/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js index 8d7127087e7a0..6471ccfa58bdb 100644 --- a/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js +++ b/superset/assets/spec/javascripts/messageToasts/reducers/messageToasts_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { ADD_TOAST, REMOVE_TOAST } from '../../../../src/messageToasts/actions'; diff --git a/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js b/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js index a3c7ce906809d..edcddefac65af 100644 --- a/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js +++ b/superset/assets/spec/javascripts/messageToasts/utils/getToastsFromPyFlashMessages_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { diff --git a/superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js b/superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js new file mode 100644 index 0000000000000..1696dd28aa33b --- /dev/null +++ b/superset/assets/spec/javascripts/modules/CategoricalColorNameSpace_spec.js @@ -0,0 +1,130 @@ +import { it, describe, before } from 'mocha'; +import { expect } from 'chai'; +import CategoricalColorNamespace, { + getNamespace, + getScale, + getColor, + DEFAULT_NAMESPACE, +} from '../../../src/modules/CategoricalColorNamespace'; +import { registerScheme } from '../../../src/modules/ColorSchemeManager'; + +describe('CategoricalColorNamespace', () => { + before(() => { + registerScheme('testColors', ['red', 'green', 'blue']); + registerScheme('testColors2', ['red', 'green', 'blue']); + }); + it('The class constructor cannot be accessed directly', () => { + expect(CategoricalColorNamespace).to.not.be.a('Function'); + }); + describe('static getNamespace()', () => { + it('returns default namespace if name is not specified', () => { + const namespace = getNamespace(); + expect(namespace !== undefined).to.equal(true); + expect(namespace.name).to.equal(DEFAULT_NAMESPACE); + }); + it('returns namespace with specified name', () => { + const namespace = getNamespace('myNamespace'); + expect(namespace !== undefined).to.equal(true); + expect(namespace.name).to.equal('myNamespace'); + }); + it('returns existing instance if the name already exists', () => { + const ns1 = getNamespace('myNamespace'); + const ns2 = getNamespace('myNamespace'); + expect(ns1).to.equal(ns2); + const ns3 = getNamespace(); + const ns4 = getNamespace(); + expect(ns3).to.equal(ns4); + }); + }); + describe('.getScale()', () => { + it('returns a CategoricalColorScale from given scheme name', () => { + const namespace = getNamespace('test-get-scale1'); + const scale = namespace.getScale('testColors'); + expect(scale).to.not.equal(undefined); + expect(scale.getColor('dog')).to.not.equal(undefined); + }); + it('returns same scale if the scale with that name already exists in this namespace', () => { + const namespace = getNamespace('test-get-scale2'); + const scale1 = namespace.getScale('testColors'); + const scale2 = namespace.getScale('testColors2'); + const scale3 = namespace.getScale('testColors2'); + const scale4 = namespace.getScale('testColors'); + expect(scale1).to.equal(scale4); + expect(scale2).to.equal(scale3); + }); + }); + describe('.setColor()', () => { + it('overwrites color for all CategoricalColorScales in this namespace', () => { + const namespace = getNamespace('test-set-scale1'); + namespace.setColor('dog', 'black'); + const scale = namespace.getScale('testColors'); + expect(scale.getColor('dog')).to.equal('black'); + expect(scale.getColor('boy')).to.not.equal('black'); + }); + it('can override forcedColors in each scale', () => { + const namespace = getNamespace('test-set-scale2'); + namespace.setColor('dog', 'black'); + const scale = namespace.getScale('testColors'); + scale.setColor('dog', 'pink'); + expect(scale.getColor('dog')).to.equal('black'); + expect(scale.getColor('boy')).to.not.equal('black'); + }); + it('does not affect scales in other namespaces', () => { + const ns1 = getNamespace('test-set-scale3.1'); + ns1.setColor('dog', 'black'); + const scale1 = ns1.getScale('testColors'); + const ns2 = getNamespace('test-set-scale3.2'); + const scale2 = ns2.getScale('testColors'); + expect(scale1.getColor('dog')).to.equal('black'); + expect(scale2.getColor('dog')).to.not.equal('black'); + }); + it('returns the namespace instance', () => { + const ns1 = getNamespace('test-set-scale3.1'); + const ns2 = ns1.setColor('dog', 'black'); + expect(ns1).to.equal(ns2); + }); + }); + describe('static getScale()', () => { + it('getScale() returns a CategoricalColorScale with default scheme in default namespace', () => { + const scale = getScale(); + expect(scale).to.not.equal(undefined); + const scale2 = getNamespace().getScale(); + expect(scale).to.equal(scale2); + }); + it('getScale(scheme) returns a CategoricalColorScale with specified scheme in default namespace', () => { + const scale = getScale('testColors'); + expect(scale).to.not.equal(undefined); + const scale2 = getNamespace().getScale('testColors'); + expect(scale).to.equal(scale2); + }); + it('getScale(scheme, namespace) returns a CategoricalColorScale with specified scheme in specified namespace', () => { + const scale = getScale('testColors', 'test-getScale'); + expect(scale).to.not.equal(undefined); + const scale2 = getNamespace('test-getScale').getScale('testColors'); + expect(scale).to.equal(scale2); + }); + }); + describe('static getColor()', () => { + it('getColor(value) returns a color from default scheme in default namespace', () => { + const value = 'dog'; + const color = getColor(value); + const color2 = getNamespace().getScale().getColor(value); + expect(color).to.equal(color2); + }); + it('getColor(value, scheme) returns a color from specified scheme in default namespace', () => { + const value = 'dog'; + const scheme = 'testColors'; + const color = getColor(value, scheme); + const color2 = getNamespace().getScale(scheme).getColor(value); + expect(color).to.equal(color2); + }); + it('getColor(value, scheme, namespace) returns a color from specified scheme in specified namespace', () => { + const value = 'dog'; + const scheme = 'testColors'; + const namespace = 'test-getColor'; + const color = getColor(value, scheme, namespace); + const color2 = getNamespace(namespace).getScale(scheme).getColor(value); + expect(color).to.equal(color2); + }); + }); +}); diff --git a/superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js b/superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js new file mode 100644 index 0000000000000..fc2e2ea99030e --- /dev/null +++ b/superset/assets/spec/javascripts/modules/CategoricalColorScale_spec.js @@ -0,0 +1,96 @@ +import { it, describe } from 'mocha'; +import { expect } from 'chai'; +import CategoricalColorScale from '../../../src/modules/CategoricalColorScale'; + +describe('CategoricalColorScale', () => { + it('exists', () => { + expect(CategoricalColorScale !== undefined).to.equal(true); + }); + + describe('new CategoricalColorScale(colors, parentForcedColors)', () => { + it('can create new scale when parentForcedColors is not given', () => { + const scale = new CategoricalColorScale(['blue', 'red', 'green']); + expect(scale).to.be.instanceOf(CategoricalColorScale); + }); + it('can create new scale when parentForcedColors is given', () => { + const parentForcedColors = {}; + const scale = new CategoricalColorScale(['blue', 'red', 'green'], parentForcedColors); + expect(scale).to.be.instanceOf(CategoricalColorScale); + expect(scale.parentForcedColors).to.equal(parentForcedColors); + }); + }); + describe('.getColor(value)', () => { + it('returns same color for same value', () => { + const scale = new CategoricalColorScale(['blue', 'red', 'green']); + const c1 = scale.getColor('pig'); + const c2 = scale.getColor('horse'); + const c3 = scale.getColor('pig'); + scale.getColor('cow'); + const c5 = scale.getColor('horse'); + + expect(c1).to.equal(c3); + expect(c2).to.equal(c5); + }); + it('returns different color for consecutive items', () => { + const scale = new CategoricalColorScale(['blue', 'red', 'green']); + const c1 = scale.getColor('pig'); + const c2 = scale.getColor('horse'); + const c3 = scale.getColor('cat'); + + expect(c1).to.not.equal(c2); + expect(c2).to.not.equal(c3); + expect(c3).to.not.equal(c1); + }); + it('recycles colors when number of items exceed available colors', () => { + const colorSet = {}; + const scale = new CategoricalColorScale(['blue', 'red', 'green']); + const colors = [ + scale.getColor('pig'), + scale.getColor('horse'), + scale.getColor('cat'), + scale.getColor('cow'), + scale.getColor('donkey'), + scale.getColor('goat'), + ]; + colors.forEach((color) => { + if (colorSet[color]) { + colorSet[color]++; + } else { + colorSet[color] = 1; + } + }); + expect(Object.keys(colorSet).length).to.equal(3); + ['blue', 'red', 'green'].forEach((color) => { + expect(colorSet[color]).to.equal(2); + }); + }); + }); + describe('.setColor(value, forcedColor)', () => { + it('overrides default color', () => { + const scale = new CategoricalColorScale(['blue', 'red', 'green']); + scale.setColor('pig', 'pink'); + expect(scale.getColor('pig')).to.equal('pink'); + }); + it('does not override parentForcedColors', () => { + const scale1 = new CategoricalColorScale(['blue', 'red', 'green']); + scale1.setColor('pig', 'black'); + const scale2 = new CategoricalColorScale(['blue', 'red', 'green'], scale1.forcedColors); + scale2.setColor('pig', 'pink'); + expect(scale1.getColor('pig')).to.equal('black'); + expect(scale2.getColor('pig')).to.equal('black'); + }); + it('returns the scale', () => { + const scale = new CategoricalColorScale(['blue', 'red', 'green']); + const output = scale.setColor('pig', 'pink'); + expect(scale).to.equal(output); + }); + }); + describe('.toFunction()', () => { + it('returns a function that wraps getColor', () => { + const scale = new CategoricalColorScale(['blue', 'red', 'green']); + const colorFn = scale.toFunction(); + expect(scale.getColor('pig')).to.equal(colorFn('pig')); + expect(scale.getColor('cat')).to.equal(colorFn('cat')); + }); + }); +}); diff --git a/superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js b/superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js new file mode 100644 index 0000000000000..236b1e43a0ac3 --- /dev/null +++ b/superset/assets/spec/javascripts/modules/ColorSchemeManager_spec.js @@ -0,0 +1,141 @@ +import { it, describe, beforeEach } from 'mocha'; +import { expect } from 'chai'; +import ColorSchemeManager, { + getInstance, + getScheme, + getAllSchemes, + getDefaultSchemeName, + setDefaultSchemeName, + registerScheme, + registerMultipleSchemes, +} from '../../../src/modules/ColorSchemeManager'; + +describe('ColorSchemeManager', () => { + beforeEach(() => { + const m = getInstance(); + m.clearScheme(); + m.registerScheme('test', ['red', 'green', 'blue']); + m.registerScheme('test2', ['orange', 'yellow', 'pink']); + m.setDefaultSchemeName('test'); + }); + it('The class constructor cannot be accessed directly', () => { + expect(ColorSchemeManager).to.not.be.a('Function'); + }); + describe('static getInstance()', () => { + it('returns a singleton instance', () => { + const m1 = getInstance(); + const m2 = getInstance(); + expect(m1).to.not.equal(undefined); + expect(m1).to.equal(m2); + }); + }); + describe('.getScheme()', () => { + it('.getScheme() returns default color scheme', () => { + const scheme = getInstance().getScheme(); + expect(scheme).to.deep.equal(['red', 'green', 'blue']); + }); + it('.getScheme(name) returns color scheme with specified name', () => { + const scheme = getInstance().getScheme('test2'); + expect(scheme).to.deep.equal(['orange', 'yellow', 'pink']); + }); + }); + describe('.getAllSchemes()', () => { + it('returns all registered schemes', () => { + const schemes = getInstance().getAllSchemes(); + expect(schemes).to.deep.equal({ + test: ['red', 'green', 'blue'], + test2: ['orange', 'yellow', 'pink'], + }); + }); + }); + describe('.getDefaultSchemeName()', () => { + it('returns default scheme name', () => { + const name = getInstance().getDefaultSchemeName(); + expect(name).to.equal('test'); + }); + }); + describe('.setDefaultSchemeName()', () => { + it('set default scheme name', () => { + getInstance().setDefaultSchemeName('test2'); + const name = getInstance().getDefaultSchemeName(); + expect(name).to.equal('test2'); + getInstance().setDefaultSchemeName('test'); + }); + it('returns the ColorSchemeManager instance', () => { + const instance = getInstance().setDefaultSchemeName('test'); + expect(instance).to.equal(getInstance()); + }); + }); + describe('.registerScheme(name, colors)', () => { + it('sets schemename and color', () => { + getInstance().registerScheme('test3', ['cyan', 'magenta']); + const scheme = getInstance().getScheme('test3'); + expect(scheme).to.deep.equal(['cyan', 'magenta']); + }); + it('returns the ColorSchemeManager instance', () => { + const instance = getInstance().registerScheme('test3', ['cyan', 'magenta']); + expect(instance).to.equal(getInstance()); + }); + }); + describe('.registerMultipleSchemes(object)', () => { + it('sets multiple schemes at once', () => { + getInstance().registerMultipleSchemes({ + test4: ['cyan', 'magenta'], + test5: ['brown', 'purple'], + }); + const scheme1 = getInstance().getScheme('test4'); + expect(scheme1).to.deep.equal(['cyan', 'magenta']); + const scheme2 = getInstance().getScheme('test5'); + expect(scheme2).to.deep.equal(['brown', 'purple']); + }); + it('returns the ColorSchemeManager instance', () => { + const instance = getInstance().registerMultipleSchemes({ + test4: ['cyan', 'magenta'], + test5: ['brown', 'purple'], + }); + expect(instance).to.equal(getInstance()); + }); + }); + describe('static getScheme()', () => { + it('is equivalent to getInstance().getScheme()', () => { + expect(getInstance().getScheme()).to.equal(getScheme()); + }); + }); + describe('static getAllSchemes()', () => { + it('is equivalent to getInstance().getAllSchemes()', () => { + expect(getInstance().getAllSchemes()).to.equal(getAllSchemes()); + }); + }); + describe('static getDefaultSchemeName()', () => { + it('is equivalent to getInstance().getDefaultSchemeName()', () => { + expect(getInstance().getDefaultSchemeName()).to.equal(getDefaultSchemeName()); + }); + }); + describe('static setDefaultSchemeName()', () => { + it('is equivalent to getInstance().setDefaultSchemeName()', () => { + setDefaultSchemeName('test2'); + const name = getInstance().getDefaultSchemeName(); + expect(name).to.equal('test2'); + setDefaultSchemeName('test'); + }); + }); + describe('static registerScheme()', () => { + it('is equivalent to getInstance().registerScheme()', () => { + registerScheme('test3', ['cyan', 'magenta']); + const scheme = getInstance().getScheme('test3'); + expect(scheme).to.deep.equal(['cyan', 'magenta']); + }); + }); + describe('static registerMultipleSchemes()', () => { + it('is equivalent to getInstance().registerMultipleSchemes()', () => { + registerMultipleSchemes({ + test4: ['cyan', 'magenta'], + test5: ['brown', 'purple'], + }); + const scheme1 = getInstance().getScheme('test4'); + expect(scheme1).to.deep.equal(['cyan', 'magenta']); + const scheme2 = getInstance().getScheme('test5'); + expect(scheme2).to.deep.equal(['brown', 'purple']); + }); + }); +}); diff --git a/superset/assets/spec/javascripts/modules/colors_spec.jsx b/superset/assets/spec/javascripts/modules/colors_spec.jsx index e83b4732b115c..a91c74f16326a 100644 --- a/superset/assets/spec/javascripts/modules/colors_spec.jsx +++ b/superset/assets/spec/javascripts/modules/colors_spec.jsx @@ -1,12 +1,20 @@ -import { it, describe } from 'mocha'; import { expect } from 'chai'; - -import { ALL_COLOR_SCHEMES, getColorFromScheme, hexToRGB } from '../../../src/modules/colors'; +import { getColorFromScheme, hexToRGB } from '../../../src/modules/colors'; +import { getInstance } from '../../../src/modules/ColorSchemeManager'; +import airbnb from '../../../src/modules/colorSchemes/airbnb'; +import categoricalSchemes from '../../../src/modules/colorSchemes/categorical'; describe('colors', () => { + before(() => { + // Register color schemes + getInstance() + .registerScheme('bnbColors', airbnb.bnbColors) + .registerMultipleSchemes(categoricalSchemes) + .setDefaultSchemeName('bnbColors'); + }); it('default to bnbColors', () => { const color1 = getColorFromScheme('CA'); - expect(color1).to.equal(ALL_COLOR_SCHEMES.bnbColors[0]); + expect(airbnb.bnbColors).to.include(color1); }); it('getColorFromScheme series with same scheme should have the same color', () => { const color1 = getColorFromScheme('CA', 'bnbColors'); @@ -14,19 +22,18 @@ describe('colors', () => { const color3 = getColorFromScheme('CA', 'bnbColors'); const color4 = getColorFromScheme('NY', 'bnbColors'); - expect(color1).to.equal(ALL_COLOR_SCHEMES.bnbColors[0]); - expect(color2).to.equal(ALL_COLOR_SCHEMES.googleCategory20c[0]); expect(color1).to.equal(color3); - expect(color4).to.equal(ALL_COLOR_SCHEMES.bnbColors[1]); + expect(color1).to.not.equal(color2); + expect(color1).to.not.equal(color4); }); it('getColorFromScheme forcing colors persists through calls', () => { expect(getColorFromScheme('boys', 'bnbColors', 'blue')).to.equal('blue'); expect(getColorFromScheme('boys', 'bnbColors')).to.equal('blue'); - expect(getColorFromScheme('boys', 'googleCategory20c')).to.equal('blue'); + expect(getColorFromScheme('boys', 'googleCategory20c')).to.not.equal('blue'); expect(getColorFromScheme('girls', 'bnbColors', 'pink')).to.equal('pink'); expect(getColorFromScheme('girls', 'bnbColors')).to.equal('pink'); - expect(getColorFromScheme('girls', 'googleCategory20c')).to.equal('pink'); + expect(getColorFromScheme('girls', 'googleCategory20c')).to.not.equal('pink'); }); it('getColorFromScheme is not case sensitive', () => { const c1 = getColorFromScheme('girls', 'bnbColors'); diff --git a/superset/assets/spec/javascripts/modules/dates_spec.js b/superset/assets/spec/javascripts/modules/dates_spec.js index 7101b1dad2ee6..957d8205437ce 100644 --- a/superset/assets/spec/javascripts/modules/dates_spec.js +++ b/superset/assets/spec/javascripts/modules/dates_spec.js @@ -1,5 +1,4 @@ -import { it, describe } from 'mocha'; -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import { tickMultiFormat, formatDate, diff --git a/superset/assets/spec/javascripts/modules/geo_spec.jsx b/superset/assets/spec/javascripts/modules/geo_spec.jsx index db51bb4f0618e..18b45db079633 100644 --- a/superset/assets/spec/javascripts/modules/geo_spec.jsx +++ b/superset/assets/spec/javascripts/modules/geo_spec.jsx @@ -1,4 +1,3 @@ -import { it, describe } from 'mocha'; import { expect } from 'chai'; import { unitToRadius } from '../../../src/modules/geo'; diff --git a/superset/assets/spec/javascripts/modules/sandbox_spec.jsx b/superset/assets/spec/javascripts/modules/sandbox_spec.jsx index 05283b046156e..f86e69cca7be6 100644 --- a/superset/assets/spec/javascripts/modules/sandbox_spec.jsx +++ b/superset/assets/spec/javascripts/modules/sandbox_spec.jsx @@ -1,4 +1,3 @@ -import { it, describe } from 'mocha'; import { expect } from 'chai'; import sandboxedEval from '../../../src/modules/sandbox'; diff --git a/superset/assets/spec/javascripts/modules/time_spec.js b/superset/assets/spec/javascripts/modules/time_spec.js index 59dab8effe031..09a0d1491cffd 100644 --- a/superset/assets/spec/javascripts/modules/time_spec.js +++ b/superset/assets/spec/javascripts/modules/time_spec.js @@ -1,6 +1,31 @@ -import { it, describe } from 'mocha'; -import { expect } from 'chai'; -import { getPlaySliderParams } from '../../../src/modules/time'; +import { expect, assert } from 'chai'; + +import moment from 'moment'; +import { getPlaySliderParams, truncate } from '../../../src/modules/time'; + +describe('truncate', () => { + it('truncates timestamps', () => { + const timestamp = moment('2018-03-03T03:03:03.333'); + const isoDurations = [ + // basic units + [moment.duration('PT1S'), moment('2018-03-03T03:03:03')], + [moment.duration('PT1M'), moment('2018-03-03T03:03:00')], + [moment.duration('PT1H'), moment('2018-03-03T03:00:00')], + [moment.duration('P1D'), moment('2018-03-03T00:00:00')], + [moment.duration('P1M'), moment('2018-03-01T00:00:00')], + [moment.duration('P1Y'), moment('2018-01-01T00:00:00')], + + // durations that are multiples + [moment.duration('PT2H'), moment('2018-03-03T02:00:00')], + [moment.duration('P2D'), moment('2018-03-03T00:00:00')], + ]; + let result; + isoDurations.forEach(([step, expected]) => { + result = truncate(timestamp, step); + expect(result.format()).to.equal(expected.format()); + }); + }); +}); describe('getPlaySliderParams', () => { it('is a function', () => { @@ -9,49 +34,49 @@ describe('getPlaySliderParams', () => { it('handles durations', () => { const timestamps = [ - new Date('2018-01-01'), - new Date('2018-01-02'), - new Date('2018-01-03'), - new Date('2018-01-04'), - new Date('2018-01-05'), - new Date('2018-01-06'), - new Date('2018-01-07'), - new Date('2018-01-08'), - new Date('2018-01-09'), - new Date('2018-01-10'), - ].map(d => d.getTime()); - const { start, end, step, values, disabled } = getPlaySliderParams(timestamps, 'P2D'); - expect(new Date(start)).to.eql(new Date('2018-01-01')); - expect(new Date(end)).to.eql(new Date('2018-01-11')); - expect(step).to.equal(2 * 24 * 60 * 60 * 1000); - expect(values.map(v => new Date(v))).to.eql([ - new Date('2018-01-01'), - new Date('2018-01-03'), + moment('2018-01-01T00:00:00'), + moment('2018-01-02T00:00:00'), + moment('2018-01-03T00:00:00'), + moment('2018-01-04T00:00:00'), + moment('2018-01-05T00:00:00'), + moment('2018-01-06T00:00:00'), + moment('2018-01-07T00:00:00'), + moment('2018-01-08T00:00:00'), + moment('2018-01-09T00:00:00'), + moment('2018-01-10T00:00:00'), + ].map(d => parseInt(d.format('x'), 10)); + const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, 'P2D'); + expect(moment(start).format()).to.equal(moment('2018-01-01T00:00:00').format()); + expect(moment(end).format()).to.equal(moment('2018-01-11T00:00:00').format()); + expect(getStep(start)).to.equal(2 * 24 * 60 * 60 * 1000); + expect(values.map(v => moment(v).format())).to.eql([ + moment('2018-01-01T00:00:00').format(), + moment('2018-01-03T00:00:00').format(), ]); expect(disabled).to.equal(false); }); it('handles intervals', () => { const timestamps = [ - new Date('2018-01-01'), - new Date('2018-01-02'), - new Date('2018-01-03'), - new Date('2018-01-04'), - new Date('2018-01-05'), - new Date('2018-01-06'), - new Date('2018-01-07'), - new Date('2018-01-08'), - new Date('2018-01-09'), - new Date('2018-01-10'), - ].map(d => d.getTime()); + moment('2018-01-01T00:00:00'), + moment('2018-01-02T00:00:00'), + moment('2018-01-03T00:00:00'), + moment('2018-01-04T00:00:00'), + moment('2018-01-05T00:00:00'), + moment('2018-01-06T00:00:00'), + moment('2018-01-07T00:00:00'), + moment('2018-01-08T00:00:00'), + moment('2018-01-09T00:00:00'), + moment('2018-01-10T00:00:00'), + ].map(d => parseInt(d.format('x'), 10)); // 1970-01-03 was a Saturday - const { start, end, step, values, disabled } = getPlaySliderParams(timestamps, 'P1W/1970-01-03T00:00:00Z'); - expect(new Date(start)).to.eql(new Date('2017-12-30')); // Saturday - expect(new Date(end)).to.eql(new Date('2018-01-13')); // Saturday - expect(step).to.equal(7 * 24 * 60 * 60 * 1000); - expect(values.map(v => new Date(v))).to.eql([ - new Date('2017-12-30'), - new Date('2018-01-06'), + const { start, end, getStep, values, disabled } = getPlaySliderParams(timestamps, 'P1W/1970-01-03T00:00:00Z'); + expect(moment(start).format()).to.equal(moment('2017-12-30T00:00:00Z').format()); // Saturday + expect(moment(end).format()).to.equal(moment('2018-01-13T00:00:00Z').format()); // Saturday + expect(getStep(start)).to.equal(7 * 24 * 60 * 60 * 1000); + expect(values.map(v => moment(v).format())).to.eql([ + moment('2017-12-30T00:00:00Z').format(), + moment('2018-01-06T00:00:00Z').format(), ]); expect(disabled).to.equal(false); }); diff --git a/superset/assets/spec/javascripts/modules/utils_spec.jsx b/superset/assets/spec/javascripts/modules/utils_spec.jsx index 4a319afbe1c6b..10a4fbc0c11ef 100644 --- a/superset/assets/spec/javascripts/modules/utils_spec.jsx +++ b/superset/assets/spec/javascripts/modules/utils_spec.jsx @@ -1,7 +1,5 @@ -import { it, describe } from 'mocha'; -import { expect } from 'chai'; +import { expect, assert } from 'chai'; import { - tryNumify, slugify, formatSelectOptionsForRange, d3format, @@ -12,12 +10,6 @@ import { } from '../../../src/modules/utils'; describe('utils', () => { - it('tryNumify works as expected', () => { - expect(tryNumify(5)).to.equal(5); - expect(tryNumify('5')).to.equal(5); - expect(tryNumify('5.1')).to.equal(5.1); - expect(tryNumify('a string')).to.equal('a string'); - }); it('slugify slugifies', () => { expect(slugify('My Neat Label! ')).to.equal('my-neat-label'); expect(slugify('Some Letters AnD a 5')).to.equal('some-letters-and-a-5'); diff --git a/superset/assets/spec/javascripts/profile/App_spec.jsx b/superset/assets/spec/javascripts/profile/App_spec.jsx index dcef27be7a42e..80e578999fd4d 100644 --- a/superset/assets/spec/javascripts/profile/App_spec.jsx +++ b/superset/assets/spec/javascripts/profile/App_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { Col, Row, Tab } from 'react-bootstrap'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { user } from './fixtures'; diff --git a/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx b/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx index 08457d9858f86..04b0079c27d65 100644 --- a/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx +++ b/superset/assets/spec/javascripts/profile/CreatedContent_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { user } from './fixtures'; import CreatedContent from '../../../src/profile/components/CreatedContent'; diff --git a/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx b/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx index de0ca80d535de..5387322b4c3d9 100644 --- a/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx +++ b/superset/assets/spec/javascripts/profile/EditableTitle_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import sinon from 'sinon'; import { expect } from 'chai'; diff --git a/superset/assets/spec/javascripts/profile/Favorites_spec.jsx b/superset/assets/spec/javascripts/profile/Favorites_spec.jsx index 252d99ef20404..b35817cfc5263 100644 --- a/superset/assets/spec/javascripts/profile/Favorites_spec.jsx +++ b/superset/assets/spec/javascripts/profile/Favorites_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { user } from './fixtures'; diff --git a/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx b/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx index 60240fc31d83f..0122e672788ea 100644 --- a/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx +++ b/superset/assets/spec/javascripts/profile/RecentActivity_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { user } from './fixtures'; diff --git a/superset/assets/spec/javascripts/profile/Security_spec.jsx b/superset/assets/spec/javascripts/profile/Security_spec.jsx index 20376d24160be..e544c456da293 100644 --- a/superset/assets/spec/javascripts/profile/Security_spec.jsx +++ b/superset/assets/spec/javascripts/profile/Security_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { user, userNoPerms } from './fixtures'; diff --git a/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx b/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx index 315dd6fd1fd56..2c5b3e52543ad 100644 --- a/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx +++ b/superset/assets/spec/javascripts/profile/UserInfo_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import Gravatar from 'react-gravatar'; import { Panel } from 'react-bootstrap'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { user } from './fixtures'; diff --git a/superset/assets/spec/javascripts/sqllab/App_spec.jsx b/superset/assets/spec/javascripts/sqllab/App_spec.jsx index ce76e309f40f0..4e64d179ca77f 100644 --- a/superset/assets/spec/javascripts/sqllab/App_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/App_spec.jsx @@ -3,7 +3,6 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx b/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx index fe4e9c413a845..7ea6862e2b950 100644 --- a/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/ColumnElement_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { mockedActions, table } from './fixtures'; diff --git a/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx b/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx index 662cb352f39e3..e8f8438864d9b 100644 --- a/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/CopyQueryTabUrl_spec.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { initialState } from './fixtures'; diff --git a/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx b/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx index 1d0448e6c738b..98972ffb2bf62 100644 --- a/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx @@ -3,7 +3,6 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx b/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx index 634e8a9d4f398..b8bc395ab6313 100644 --- a/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/HighlightedSql_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import SyntaxHighlighter from 'react-syntax-highlighter'; import { mount, shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import HighlightedSql from '../../../src/SqlLab/components/HighlightedSql'; diff --git a/superset/assets/spec/javascripts/sqllab/Link_spec.jsx b/superset/assets/spec/javascripts/sqllab/Link_spec.jsx index e884d2384173c..f37dd6f2f15cd 100644 --- a/superset/assets/spec/javascripts/sqllab/Link_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/Link_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import Link from '../../../src/SqlLab/components/Link'; diff --git a/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx b/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx index cff450137d831..dc256044af53b 100644 --- a/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx @@ -2,7 +2,6 @@ import React from 'react'; import Select from 'react-select'; import { Button } from 'react-bootstrap'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx b/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx index b8f6dd51850f5..bca5c19ca9e9d 100644 --- a/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/QueryStateLabel_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { Label } from 'react-bootstrap'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import QueryStateLabel from '../../../src/SqlLab/components/QueryStateLabel'; diff --git a/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx b/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx index 5e2560a15ab1f..c985750faf82a 100644 --- a/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/QueryTable_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { Table } from 'reactable'; diff --git a/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx b/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx index 8ca9acdf58d93..d3f3690cc0ae0 100644 --- a/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx b/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx index 865b67aa4498e..c47f593250a63 100644 --- a/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/SaveQuery_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { FormControl } from 'react-bootstrap'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import SaveQuery from '../../../src/SqlLab/components/SaveQuery'; import ModalTrigger from '../../../src/components/ModalTrigger'; diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx index bc4957e13fcb1..62cb9ae197a4f 100644 --- a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import sinon from 'sinon'; import { expect } from 'chai'; diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx index b0689650da306..4e6a2c8701a0f 100644 --- a/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/SqlEditor_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import { initialState, queries, table } from './fixtures'; diff --git a/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx b/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx index d200d4037932e..f95941963f831 100644 --- a/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/TabStatusIcon_spec.jsx @@ -1,7 +1,6 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { describe, it } from 'mocha'; import { shallow } from 'enzyme'; import TabStatusIcon from '../../../src/SqlLab/components/TabStatusIcon'; diff --git a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx index 0846af81667e7..6d4e007eff33a 100644 --- a/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/TabbedSqlEditors_spec.jsx @@ -5,7 +5,6 @@ import URI from 'urijs'; import { Tab } from 'react-bootstrap'; import { shallow, mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx b/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx index ff3087158599f..6d683d3388ec1 100644 --- a/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; import Link from '../../../src/SqlLab/components/Link'; @@ -36,7 +35,7 @@ describe('TableElement', () => { mount(); }); it('sorts columns', () => { - const wrapper = mount(); + const wrapper = shallow(); expect(wrapper.state().sortColumns).to.equal(false); expect(wrapper.find(ColumnElement).first().props().column.name).to.equal('id'); wrapper.find('.sort-cols').simulate('click'); @@ -50,12 +49,10 @@ describe('TableElement', () => { expect(mockedActions.collapseTable.called).to.equal(true); }); it('removes the table', () => { - const wrapper = mount(); + const wrapper = shallow(); expect(wrapper.state().expanded).to.equal(true); wrapper.find('.table-remove').simulate('click'); expect(wrapper.state().expanded).to.equal(false); - setTimeout(() => { - expect(mockedActions.removeTable.called).to.equal(true); - }, 10); + expect(mockedActions.removeDataPreview.called).to.equal(true); }); }); diff --git a/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx b/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx index 681d73142666a..c7a9534073866 100644 --- a/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx +++ b/superset/assets/spec/javascripts/sqllab/Timer_spec.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it, beforeEach } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/superset/assets/spec/javascripts/sqllab/actions_spec.js b/superset/assets/spec/javascripts/sqllab/actions_spec.js index 5909ca8241c25..c2f1f4521ec05 100644 --- a/superset/assets/spec/javascripts/sqllab/actions_spec.js +++ b/superset/assets/spec/javascripts/sqllab/actions_spec.js @@ -1,5 +1,4 @@ /* eslint-disable no-unused-expressions */ -import { it, describe } from 'mocha'; import { expect } from 'chai'; import sinon from 'sinon'; import $ from 'jquery'; @@ -56,7 +55,7 @@ describe('async actions', () => { }); it('calls querySuccess on ajax success', () => { - ajaxStub.yieldsTo('success', { data: '' }); + ajaxStub.yieldsTo('success', '{ "data": "" }'); makeRequest(); expect(dispatch.callCount).to.equal(2); expect(dispatch.getCall(1).args[0].type).to.equal(actions.QUERY_SUCCESS); @@ -86,13 +85,6 @@ describe('async actions', () => { expect(dispatch.args[0][0].type).to.equal(actions.START_QUERY); }); - it('calls querySuccess on ajax success', () => { - ajaxStub.yieldsTo('success', { data: '' }); - makeRequest(); - expect(dispatch.callCount).to.equal(2); - expect(dispatch.getCall(1).args[0].type).to.equal(actions.QUERY_SUCCESS); - }); - it('calls queryFailed on ajax error', () => { ajaxStub.yieldsTo('error', { responseJSON: { error: 'error text' } }); makeRequest(); diff --git a/superset/assets/spec/javascripts/sqllab/reducers_spec.js b/superset/assets/spec/javascripts/sqllab/reducers_spec.js index 2931d13b46a92..1ade0382ec1f3 100644 --- a/superset/assets/spec/javascripts/sqllab/reducers_spec.js +++ b/superset/assets/spec/javascripts/sqllab/reducers_spec.js @@ -1,4 +1,3 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; import * as r from '../../../src/SqlLab/reducers'; diff --git a/superset/assets/spec/javascripts/utils/common_spec.jsx b/superset/assets/spec/javascripts/utils/common_spec.jsx index 861c38ae530c8..1b5651366c796 100644 --- a/superset/assets/spec/javascripts/utils/common_spec.jsx +++ b/superset/assets/spec/javascripts/utils/common_spec.jsx @@ -1,4 +1,3 @@ -import { it, describe } from 'mocha'; import { expect } from 'chai'; import { isTruthy, optionFromValue } from '../../../src/utils/common'; diff --git a/superset/assets/spec/javascripts/visualizations/nvd3_viz_spec.jsx b/superset/assets/spec/javascripts/visualizations/nvd3/utils_spec.js similarity index 68% rename from superset/assets/spec/javascripts/visualizations/nvd3_viz_spec.jsx rename to superset/assets/spec/javascripts/visualizations/nvd3/utils_spec.js index f1b58f1763ed5..d88214e319f6c 100644 --- a/superset/assets/spec/javascripts/visualizations/nvd3_viz_spec.jsx +++ b/superset/assets/spec/javascripts/visualizations/nvd3/utils_spec.js @@ -1,14 +1,13 @@ -import { describe, it } from 'mocha'; import { expect } from 'chai'; -import { formatLabel } from '../../../src/visualizations/nvd3_vis'; +import { formatLabel, tryNumify } from '../../../../src/visualizations/nvd3/utils'; -describe('nvd3 viz', () => { +describe('nvd3/utils', () => { const verboseMap = { foo: 'Foo', bar: 'Bar', }; - describe('formatLabel', () => { + describe('formatLabel()', () => { it('formats simple labels', () => { expect(formatLabel('foo')).to.equal('foo'); expect(formatLabel(['foo'])).to.equal('foo'); @@ -25,4 +24,12 @@ describe('nvd3 viz', () => { expect(formatLabel(['foo', 'bar', 'baz', '2 hours offset'], verboseMap)).to.equal('Foo, Bar, baz, 2 hours offset'); }); }); + describe('tryNumify()', () => { + it('tryNumify works as expected', () => { + expect(tryNumify(5)).to.equal(5); + expect(tryNumify('5')).to.equal(5); + expect(tryNumify('5.1')).to.equal(5.1); + expect(tryNumify('a string')).to.equal('a string'); + }); + }); }); diff --git a/superset/assets/spec/javascripts/visualizations/table_spec.jsx b/superset/assets/spec/javascripts/visualizations/table_spec.jsx new file mode 100644 index 0000000000000..5252e9dd4fe49 --- /dev/null +++ b/superset/assets/spec/javascripts/visualizations/table_spec.jsx @@ -0,0 +1,100 @@ +import { expect } from 'chai'; +import $ from 'jquery'; +import '../../helpers/shim'; +import tableVis from '../../../src/visualizations/table'; + +describe('table viz', () => { + const div = '
'; + const baseSlice = { + selector: '#slice-container', + formData: { + metrics: ['count'], + timeseries_limit_metric: null, + }, + datasource: { + verbose_map: {}, + }, + getFilters: () => ({}), + removeFilter() {}, + addFilter() {}, + height: () => 0, + }; + const basePayload = { + data: { + records: [ + { gender: 'boy', count: 39245 }, + { gender: 'girl', count: 36446 }, + ], + columns: ['gender', 'count'], + }, + }; + + it('renders into a container', () => { + $('body').html(div); + const container = $(baseSlice.selector); + expect(container.length).to.equal(1); + }); + + it('renders header and body datatables in container', () => { + $('body').html(div); + const container = $(baseSlice.selector); + + expect(container.find('.dataTable').length).to.equal(0); + tableVis(baseSlice, basePayload); + expect(container.find('.dataTable').length).to.equal(2); + + const tableHeader = container.find('.dataTable')[0]; + expect($(tableHeader).find('thead tr').length).to.equal(1); + expect($(tableHeader).find('th').length).to.equal(2); + + const tableBody = container.find('.dataTable')[1]; + expect($(tableBody).find('tbody tr').length).to.equal(2); + expect($(tableBody).find('th').length).to.equal(2); + }); + + it('hides the sort by column', () => { + $('body').html(div); + const slice = { ...baseSlice }; + slice.formData = { ...baseSlice.formData, + timeseries_limit_metric: { + label: 'SUM(sum_boys)', + }, + }; + const payload = { + data: { + records: [ + { gender: 'boy', count: 39245, 'SUM(sum_boys)': 48133355 }, + { gender: 'girl', count: 36446, 'SUM(sum_boys)': 0 }, + ], + columns: ['gender', 'count', 'SUM(sum_boys)'], + }, + }; + tableVis(slice, payload); + + const container = $(slice.selector); + const tableHeader = container.find('.dataTable')[0]; + expect($(tableHeader).find('th').length).to.equal(2); + }); + + it('works with empty list for sort by', () => { + $('body').html(div); + const slice = { ...baseSlice }; + slice.formData = { ...baseSlice.formData, + timeseries_limit_metric: [], + }; + const payload = { + data: { + records: [ + { gender: 'boy', count: 39245, 'SUM(sum_boys)': 48133355 }, + { gender: 'girl', count: 36446, 'SUM(sum_boys)': 0 }, + ], + columns: ['gender', 'count', 'SUM(sum_boys)'], + }, + }; + tableVis(slice, payload); + + const container = $(slice.selector); + const tableBody = container.find('.dataTable')[1]; + expect($(tableBody).find('th').length).to.equal(3); + }); +}); diff --git a/superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx b/superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx index 113f00cd255ce..bfb9825948b5a 100644 --- a/superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx +++ b/superset/assets/spec/javascripts/welcome/DashboardTable_spec.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; +import sinon from 'sinon'; import DashboardTable from '../../../src/welcome/DashboardTable'; diff --git a/superset/assets/spec/javascripts/welcome/App_spec.jsx b/superset/assets/spec/javascripts/welcome/Welcome_spec.jsx similarity index 67% rename from superset/assets/spec/javascripts/welcome/App_spec.jsx rename to superset/assets/spec/javascripts/welcome/Welcome_spec.jsx index 46c6fdb90600f..4ffd8ca7fb5ea 100644 --- a/superset/assets/spec/javascripts/welcome/App_spec.jsx +++ b/superset/assets/spec/javascripts/welcome/Welcome_spec.jsx @@ -1,20 +1,19 @@ import React from 'react'; import { Panel, Row, Tab } from 'react-bootstrap'; import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; import { expect } from 'chai'; -import App from '../../../src/welcome/App'; +import Welcome from '../../../src/welcome/Welcome'; -describe('App', () => { +describe('Welcome', () => { const mockedProps = {}; it('is valid', () => { expect( - React.isValidElement(), + React.isValidElement(), ).to.equal(true); }); it('renders 4 Tab, Panel, and Row components', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(Tab)).to.have.length(3); expect(wrapper.find(Panel)).to.have.length(3); expect(wrapper.find(Row)).to.have.length(3); diff --git a/superset/assets/src/CRUD/CollectionTable.jsx b/superset/assets/src/CRUD/CollectionTable.jsx index 2cbc132623231..514f86660e957 100644 --- a/superset/assets/src/CRUD/CollectionTable.jsx +++ b/superset/assets/src/CRUD/CollectionTable.jsx @@ -7,6 +7,8 @@ import Fieldset from './Fieldset'; import { recurseReactClone } from './utils'; import './styles.css'; +import { t } from '../locales'; + const propTypes = { collection: PropTypes.arrayOf(PropTypes.object).isRequired, itemGenerator: PropTypes.func, @@ -210,7 +212,7 @@ export default class CRUDCollection extends React.PureComponent {
{this.props.allowAddItem && } {this.props.extraButtons}
diff --git a/superset/assets/src/CRUD/utils.js b/superset/assets/src/CRUD/utils.js index 6de8c4b1653b7..3bf24e539427a 100644 --- a/superset/assets/src/CRUD/utils.js +++ b/superset/assets/src/CRUD/utils.js @@ -7,7 +7,7 @@ export function recurseReactClone(children, type, propExtender) { */ return React.Children.map(children, (child) => { let newChild = child; - if (child && child.type === type) { + if (child && child.type.name === type.name) { newChild = React.cloneElement(child, propExtender(child)); } if (newChild && newChild.props.children) { diff --git a/superset/assets/src/SqlLab/App.jsx b/superset/assets/src/SqlLab/App.jsx new file mode 100644 index 0000000000000..36d8ddd1f9d50 --- /dev/null +++ b/superset/assets/src/SqlLab/App.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { createStore, compose, applyMiddleware } from 'redux'; +import { Provider } from 'react-redux'; +import thunkMiddleware from 'redux-thunk'; +import { hot } from 'react-hot-loader'; + +import getInitialState from './getInitialState'; +import rootReducer from './reducers'; +import { initEnhancer } from '../reduxUtils'; +import { initJQueryAjax } from '../modules/utils'; +import App from './components/App'; +import { appSetup } from '../common'; + +import './main.less'; +import '../../stylesheets/reactable-pagination.css'; +import '../components/FilterableTable/FilterableTableStyles.css'; + +appSetup(); +initJQueryAjax(); + +const appContainer = document.getElementById('app'); +const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap')); +const state = getInitialState(bootstrapData); + +const store = createStore( + rootReducer, + state, + compose( + applyMiddleware(thunkMiddleware), + initEnhancer(), + ), +); + +// Highlight the navbar menu +const menus = document.querySelectorAll('.nav.navbar-nav li.dropdown'); +const sqlLabMenu = Array.prototype.slice.apply(menus) + .find(element => element.innerText.trim() === 'SQL Lab'); +if (sqlLabMenu) { + const classes = sqlLabMenu.getAttribute('class'); + if (classes.indexOf('active') === -1) { + sqlLabMenu.setAttribute('class', `${classes} active`); + } +} + +const Application = () => ( + + + +); + +export default hot(module)(Application); diff --git a/superset/assets/src/SqlLab/actions.js b/superset/assets/src/SqlLab/actions.js index dae8ebf7a5954..a808949c2f3ad 100644 --- a/superset/assets/src/SqlLab/actions.js +++ b/superset/assets/src/SqlLab/actions.js @@ -1,7 +1,7 @@ -/* global window */ -/* eslint no-undef: 2 */ import $ from 'jquery'; import shortid from 'shortid'; +import JSONbig from 'json-bigint'; + import { now } from '../modules/dates'; import { t } from '../locales'; import { @@ -9,7 +9,7 @@ import { addDangerToast as addDangerToastAction, addInfoToast as addInfoToastAction, } from '../messageToasts/actions'; -import { COMMON_ERR_MESSAGES } from '../common'; +import { COMMON_ERR_MESSAGES } from '../utils/common'; export const RESET_STATE = 'RESET_STATE'; export const ADD_QUERY_EDITOR = 'ADD_QUERY_EDITOR'; @@ -125,10 +125,11 @@ export function fetchQueryResults(query) { const sqlJsonUrl = `/superset/results/${query.resultsKey}/`; $.ajax({ type: 'GET', - dataType: 'json', + dataType: 'text', url: sqlJsonUrl, success(results) { - dispatch(querySuccess(query, results)); + const parsedResults = JSONbig.parse(results); + dispatch(querySuccess(query, parsedResults)); }, error(err) { let msg = t('Failed at retrieving results from the results backend'); @@ -441,7 +442,6 @@ export function popDatasourceQuery(datasourceKey, sql) { }); }; } - export function createDatasourceStarted() { return { type: CREATE_DATASOURCE_STARTED }; } diff --git a/superset/assets/src/SqlLab/components/CopyQueryTabUrl.jsx b/superset/assets/src/SqlLab/components/CopyQueryTabUrl.jsx index fed7c28bbe9a7..e48fe1be8feb7 100644 --- a/superset/assets/src/SqlLab/components/CopyQueryTabUrl.jsx +++ b/superset/assets/src/SqlLab/components/CopyQueryTabUrl.jsx @@ -27,10 +27,13 @@ export default class CopyQueryTabUrl extends React.PureComponent { inMenu copyNode={(
- {t('share query')} +
+ +
+ {t('Share query')}
)} - tooltipText={t('copy URL to clipboard')} + tooltipText={t('Copy URL to clipboard')} shouldShowText={false} getText={this.getUrl.bind(this)} /> diff --git a/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx b/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx index b6412389755db..dfaab5a087a29 100644 --- a/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx +++ b/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx @@ -1,4 +1,3 @@ -/* eslint no-undef: 2 */ import moment from 'moment'; import React from 'react'; import PropTypes from 'prop-types'; diff --git a/superset/assets/src/SqlLab/components/QuerySearch.jsx b/superset/assets/src/SqlLab/components/QuerySearch.jsx index d13d99376bcbb..17dd9b1b6bf86 100644 --- a/superset/assets/src/SqlLab/components/QuerySearch.jsx +++ b/superset/assets/src/SqlLab/components/QuerySearch.jsx @@ -1,4 +1,3 @@ -/* eslint no-undef: 2 */ import React from 'react'; import PropTypes from 'prop-types'; import { Button } from 'react-bootstrap'; diff --git a/superset/assets/src/SqlLab/components/SouthPane.jsx b/superset/assets/src/SqlLab/components/SouthPane.jsx index b55fdda422a7f..65d17f7768c52 100644 --- a/superset/assets/src/SqlLab/components/SouthPane.jsx +++ b/superset/assets/src/SqlLab/components/SouthPane.jsx @@ -55,7 +55,7 @@ class SouthPane extends React.PureComponent { } const dataPreviewTabs = props.dataPreviewQueries.map(query => ( diff --git a/superset/assets/src/SqlLab/components/SqlEditor.jsx b/superset/assets/src/SqlLab/components/SqlEditor.jsx index f396e76ce4ff2..c595ea6c2a99e 100644 --- a/superset/assets/src/SqlLab/components/SqlEditor.jsx +++ b/superset/assets/src/SqlLab/components/SqlEditor.jsx @@ -1,5 +1,3 @@ -/* global window */ -/* eslint no-undef: 2 */ import React from 'react'; import PropTypes from 'prop-types'; import throttle from 'lodash.throttle'; @@ -89,7 +87,7 @@ class SqlEditor extends React.PureComponent { height, }); - if (this.refs.ace.clientHeight) { + if (this.refs.ace && this.refs.ace.clientHeight) { this.props.actions.persistEditorHeight(this.props.queryEditor, this.refs.ace.clientHeight); } } @@ -242,7 +240,7 @@ class SqlEditor extends React.PureComponent { {ctasControls} diff --git a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx index a255ca631b396..7bc71223d637e 100644 --- a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx +++ b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx @@ -1,8 +1,6 @@ -/* global window */ -/* eslint no-undef: 2 */ import React from 'react'; import PropTypes from 'prop-types'; -import { Button } from 'react-bootstrap'; +import { ControlLabel, Button } from 'react-bootstrap'; import Select from 'react-virtualized-select'; import createFilterOptions from 'react-select-fast-filter-options'; @@ -189,13 +187,25 @@ class SqlEditorLeftBar extends React.PureComponent { onChange={this.changeSchema.bind(this)} /> +
+ + {t('See table schema')} +   + + ({this.state.tableOptions.length} +  {t('in')}  + + {this.props.queryEditor.schema} + ) + + {this.props.queryEditor.schema && - + + +
+
+

diff --git a/superset/templates/superset/models/database/macros.html b/superset/templates/superset/models/database/macros.html index 32972bc6508b9..da895b353772a 100644 --- a/superset/templates/superset/models/database/macros.html +++ b/superset/templates/superset/models/database/macros.html @@ -1,7 +1,7 @@ {% macro testconn() %}