From ece91928a9339190163c0bc72b96e51217a90d1e Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Wed, 5 Aug 2020 18:53:53 -0700 Subject: [PATCH] style: use tabs in dashboard edit pane (#10394) * style: use tabs in dashboard edit pane * fix tests * more hackin' * getting ready to rip cell measurer * working * pogress * Fix cards * done * fix jest * fix cy --- .../integration/dashboard/edit_mode.test.js | 13 +- .../integration/dashboard/save.test.js | 8 +- superset-frontend/images/icons/more.svg | 21 +++ .../components/DashboardBuilder_spec.jsx | 4 - .../components/HeaderActionsDropdown_spec.jsx | 2 +- .../dashboard/components/Header_spec.jsx | 19 +- .../dashboard/fixtures/mockDashboardState.js | 2 - .../dashboard/reducers/dashboardState_spec.js | 3 - .../src/components/Icon/index.tsx | 3 + .../src/dashboard/actions/dashboardState.js | 4 +- .../src/dashboard/components/AddSliceCard.jsx | 16 +- .../components/BuilderComponentPane.jsx | 67 +++---- .../components/ColorComponentPane.jsx | 107 ------------ .../components/ColorSchemeControlWrapper.jsx | 72 ++++++++ .../dashboard/components/DashboardBuilder.jsx | 6 +- .../src/dashboard/components/Header.jsx | 165 +++++++----------- .../components/HeaderActionsDropdown.jsx | 125 ++++++------- .../components/InsertComponentPane.jsx | 118 ------------- .../dashboard/components/PropertiesModal.jsx | 89 +++++++--- .../components/RefreshIntervalModal.jsx | 11 +- .../src/dashboard/components/SliceAdder.jsx | 45 ++--- .../components/dnd/AddSliceDragPreview.jsx | 5 +- .../filterscope/FilterScopeSelector.jsx | 6 +- .../dashboard/containers/DashboardBuilder.jsx | 1 - .../dashboard/containers/DashboardHeader.jsx | 5 +- .../src/dashboard/reducers/dashboardState.js | 7 +- .../src/dashboard/reducers/getInitialState.js | 5 - .../stylesheets/builder-sidepane.less | 21 +-- .../src/dashboard/stylesheets/dashboard.less | 13 -- .../src/dashboard/util/constants.ts | 7 - .../src/dashboard/util/propShapes.jsx | 1 - .../controls/ColorSchemeControl.jsx | 5 +- .../controls/ColorSchemeControl.less | 31 ++++ superset-frontend/src/explore/main.less | 14 -- .../views/CRUD/dashboard/DashboardList.tsx | 3 +- .../stylesheets/less/cosmo/bootswatch.less | 1 + superset/dashboards/dao.py | 3 +- superset/dashboards/schemas.py | 2 +- superset/views/chart/views.py | 1 + 39 files changed, 417 insertions(+), 614 deletions(-) create mode 100644 superset-frontend/images/icons/more.svg delete mode 100644 superset-frontend/src/dashboard/components/ColorComponentPane.jsx create mode 100644 superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx delete mode 100644 superset-frontend/src/dashboard/components/InsertComponentPane.jsx create mode 100644 superset-frontend/src/explore/components/controls/ColorSchemeControl.less diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js index b72535496d..1db86b5cda 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js @@ -23,7 +23,7 @@ describe('Dashboard edit mode', () => { cy.server(); cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD); - cy.get('.dashboard-header').contains('Edit dashboard').click(); + cy.get('.dashboard-header [data-test=pencil]').click(); }); it('remove, and add chart flow', () => { @@ -38,11 +38,12 @@ describe('Dashboard edit mode', () => { cy.get('.grid-container .box_plot').should('not.exist'); }); - // open charts list - cy.get('.component-layer').contains('Your charts & filters').click(); + cy.get('.tabs-components .nav-tabs li a').contains('Charts').click(); // find box plot is available from list - cy.get('.slices-layer').find('.chart-card-container').contains('Box plot'); + cy.get('.tabs-components') + .find('.chart-card-container') + .contains('Box plot'); // drag-n-drop const dataTransfer = { data: {} }; @@ -62,14 +63,14 @@ describe('Dashboard edit mode', () => { cy.get('.grid-container .box_plot').should('be.exist'); // should show Save changes button - cy.get('.dashboard-header .button-container').contains('Save changes'); + cy.get('.dashboard-header .button-container').contains('Save'); // undo 2 steps cy.get('.dashboard-header .undo-action').click().click(); // no changes, can switch to view mode cy.get('.dashboard-header .button-container') - .contains('Switch to view mode') + .contains('Discard Changes') .click(); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js index 650d372232..028a2366d2 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js @@ -53,20 +53,18 @@ describe('Dashboard save action', () => { cy.get('.grid-container .box_plot', { timeout: 5000 }); // wait for 5 secs // remove box_plot chart from dashboard - cy.get('.dashboard-header') - .contains('Edit dashboard') - .trigger('click', { force: true }); + cy.get('.dashboard-header [data-test=pencil]').click(); cy.get('.fa.fa-trash').last().trigger('click', { force: true }); cy.get('.grid-container .box_plot').should('not.exist'); cy.route('POST', '/superset/save_dash/**/').as('saveRequest'); cy.get('.dashboard-header') - .contains('Save changes') + .contains('Save') .trigger('click', { force: true }); // go back to view mode cy.wait('@saveRequest'); - cy.get('.dashboard-header').contains('Edit dashboard'); + cy.get('.dashboard-header [data-test=pencil]').click(); cy.get('.grid-container .box_plot').should('not.exist'); }); }); diff --git a/superset-frontend/images/icons/more.svg b/superset-frontend/images/icons/more.svg new file mode 100644 index 0000000000..5a66fe35c7 --- /dev/null +++ b/superset-frontend/images/icons/more.svg @@ -0,0 +1,21 @@ + + + + diff --git a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx index fd71eb6ac9..3d0f3637e2 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx @@ -32,7 +32,6 @@ import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; import DashboardHeader from 'src/dashboard/containers/DashboardHeader'; import DashboardGrid from 'src/dashboard/containers/DashboardGrid'; import * as dashboardStateActions from 'src/dashboard/actions/dashboardState'; -import { BUILDER_PANE_TYPE } from 'src/dashboard/util/constants'; import WithDragDropContext from '../helpers/WithDragDropContext'; import { @@ -64,7 +63,6 @@ describe('DashboardBuilder', () => { deleteTopLevelTabs() {}, editMode: false, showBuilderPane() {}, - builderPaneType: BUILDER_PANE_TYPE.NONE, setColorSchemeAndUnsavedChanges() {}, colorScheme: undefined, handleComponentDrop() {}, @@ -155,7 +153,6 @@ describe('DashboardBuilder', () => { wrapper.setProps({ ...props, editMode: true, - builderPaneType: BUILDER_PANE_TYPE.ADD_COMPONENTS, }); expect(wrapper.find(BuilderComponentPane)).toExist(); }); @@ -167,7 +164,6 @@ describe('DashboardBuilder', () => { wrapper.setProps({ ...props, editMode: true, - builderPaneType: BUILDER_PANE_TYPE.COLORS, }); expect(wrapper.find(BuilderComponentPane)).toExist(); }); diff --git a/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx index 815dd26a83..495e629092 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx @@ -68,7 +68,7 @@ describe('HeaderActionsDropdown', () => { it('should render two MenuItems', () => { const wrapper = setup(overrideProps); - expect(wrapper.find(MenuItem)).toHaveLength(2); + expect(wrapper.find(MenuItem)).toHaveLength(3); }); it('should render the RefreshIntervalModal', () => { diff --git a/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx index a5425dc399..bc05ddd4c9 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/Header_spec.jsx @@ -25,7 +25,6 @@ import PublishedStatus from 'src/dashboard/components/PublishedStatus'; import HeaderActionsDropdown from 'src/dashboard/components/HeaderActionsDropdown'; import Button from 'src/components/Button'; import UndoRedoKeylisteners from 'src/dashboard/components/UndoRedoKeylisteners'; -import { BUILDER_PANE_TYPE } from 'src/dashboard/util/constants'; describe('Header', () => { const props = { @@ -59,7 +58,6 @@ describe('Header', () => { editMode: false, setEditMode: () => {}, showBuilderPane: () => {}, - builderPaneType: BUILDER_PANE_TYPE.NONE, updateCss: () => {}, hasUnsavedChanges: false, maxUndoHistoryExceeded: false, @@ -111,11 +109,6 @@ describe('Header', () => { expect(wrapper.find(HeaderActionsDropdown)).toExist(); }); - it('should render one Button', () => { - const wrapper = setup(overrideProps); - expect(wrapper.find(Button)).toExist(); - }); - it('should not set up undo/redo', () => { const wrapper = setup(overrideProps); expect(wrapper.find(UndoRedoKeylisteners)).not.toExist(); @@ -154,11 +147,6 @@ describe('Header', () => { expect(wrapper.find(HeaderActionsDropdown)).toExist(); }); - it('should render one Button', () => { - const wrapper = setup(overrideProps); - expect(wrapper.find(Button)).toExist(); - }); - it('should not set up undo/redo', () => { const wrapper = setup(overrideProps); expect(wrapper.find(UndoRedoKeylisteners)).not.toExist(); @@ -199,7 +187,7 @@ describe('Header', () => { it('should render five Buttons', () => { const wrapper = setup(overrideProps); - expect(wrapper.find(Button)).toHaveLength(5); + expect(wrapper.find(Button)).toHaveLength(4); }); it('should set up undo/redo', () => { @@ -239,11 +227,6 @@ describe('Header', () => { expect(wrapper.find(HeaderActionsDropdown)).toExist(); }); - it('should render one Button', () => { - const wrapper = setup(overrideProps); - expect(wrapper.find(Button)).toExist(); - }); - it('should not set up undo/redo', () => { const wrapper = setup(overrideProps); expect(wrapper.find(UndoRedoKeylisteners)).not.toExist(); diff --git a/superset-frontend/spec/javascripts/dashboard/fixtures/mockDashboardState.js b/superset-frontend/spec/javascripts/dashboard/fixtures/mockDashboardState.js index 172742268c..f6809a28b7 100644 --- a/superset-frontend/spec/javascripts/dashboard/fixtures/mockDashboardState.js +++ b/superset-frontend/spec/javascripts/dashboard/fixtures/mockDashboardState.js @@ -16,14 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { BUILDER_PANE_TYPE } from 'src/dashboard/util/constants'; import { sliceId } from './mockChartQueries'; export default { sliceIds: [sliceId], expandedSlices: {}, editMode: false, - builderPaneType: BUILDER_PANE_TYPE.NONE, hasUnsavedChanges: false, maxUndoHistoryExceeded: false, isStarred: true, diff --git a/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js b/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js index fe5d098191..8f6de34e1f 100644 --- a/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js +++ b/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js @@ -30,7 +30,6 @@ import { } from 'src/dashboard/actions/dashboardState'; import dashboardStateReducer from 'src/dashboard/reducers/dashboardState'; -import { BUILDER_PANE_TYPE } from 'src/dashboard/util/constants'; describe('dashboardState reducer', () => { it('should return initial state', () => { @@ -72,7 +71,6 @@ describe('dashboardState reducer', () => { ), ).toEqual({ editMode: true, - builderPaneType: BUILDER_PANE_TYPE.ADD_COMPONENTS, }); }); @@ -128,7 +126,6 @@ describe('dashboardState reducer', () => { hasUnsavedChanges: false, maxUndoHistoryExceeded: false, editMode: false, - builderPaneType: BUILDER_PANE_TYPE.NONE, updatedColorScheme: false, }); }); diff --git a/superset-frontend/src/components/Icon/index.tsx b/superset-frontend/src/components/Icon/index.tsx index 7eb3916354..a0d516db1c 100644 --- a/superset-frontend/src/components/Icon/index.tsx +++ b/superset-frontend/src/components/Icon/index.tsx @@ -32,6 +32,7 @@ import { ReactComponent as ErrorIcon } from 'images/icons/error.svg'; import { ReactComponent as FavoriteSelectedIcon } from 'images/icons/favorite-selected.svg'; import { ReactComponent as FavoriteUnselectedIcon } from 'images/icons/favorite-unselected.svg'; import { ReactComponent as PencilIcon } from 'images/icons/pencil.svg'; +import { ReactComponent as MoreIcon } from 'images/icons/more.svg'; import { ReactComponent as SearchIcon } from 'images/icons/search.svg'; import { ReactComponent as SortAscIcon } from 'images/icons/sort-asc.svg'; import { ReactComponent as SortDescIcon } from 'images/icons/sort-desc.svg'; @@ -55,6 +56,7 @@ type IconName = | 'error' | 'favorite-selected' | 'favorite-unselected' + | 'more' | 'pencil' | 'search' | 'sort' @@ -84,6 +86,7 @@ export const iconsRegistry: Record< close: CloseIcon, compass: CompassIcon, error: ErrorIcon, + more: MoreIcon, pencil: PencilIcon, search: SearchIcon, sort: SortIcon, diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js index 33737f83bd..024c884619 100644 --- a/superset-frontend/src/dashboard/actions/dashboardState.js +++ b/superset-frontend/src/dashboard/actions/dashboardState.js @@ -253,8 +253,8 @@ export function fetchCharts( } export const SHOW_BUILDER_PANE = 'SHOW_BUILDER_PANE'; -export function showBuilderPane(builderPaneType) { - return { type: SHOW_BUILDER_PANE, builderPaneType }; +export function showBuilderPane() { + return { type: SHOW_BUILDER_PANE }; } export function addSliceToDashboard(id, component) { diff --git a/superset-frontend/src/dashboard/components/AddSliceCard.jsx b/superset-frontend/src/dashboard/components/AddSliceCard.jsx index 497ceced71..525aac5db8 100644 --- a/superset-frontend/src/dashboard/components/AddSliceCard.jsx +++ b/superset-frontend/src/dashboard/components/AddSliceCard.jsx @@ -22,24 +22,28 @@ import PropTypes from 'prop-types'; import { t } from '@superset-ui/translation'; const propTypes = { - datasourceLink: PropTypes.string, + datasourceUrl: PropTypes.string, + datasourceName: PropTypes.string, innerRef: PropTypes.func, isSelected: PropTypes.bool, - lastModified: PropTypes.string.isRequired, + lastModified: PropTypes.string, sliceName: PropTypes.string.isRequired, style: PropTypes.object, visType: PropTypes.string.isRequired, }; const defaultProps = { - datasourceLink: '—', + datasourceUrl: null, + datasourceName: '-', innerRef: null, isSelected: false, style: null, + lastModified: null, }; function AddSliceCard({ - datasourceLink, + datasourceUrl, + datasourceName, innerRef, isSelected, lastModified, @@ -62,9 +66,7 @@ function AddSliceCard({
{t('Data source')} - + {datasourceName}
diff --git a/superset-frontend/src/dashboard/components/BuilderComponentPane.jsx b/superset-frontend/src/dashboard/components/BuilderComponentPane.jsx index 2d2ab08993..336f399692 100644 --- a/superset-frontend/src/dashboard/components/BuilderComponentPane.jsx +++ b/superset-frontend/src/dashboard/components/BuilderComponentPane.jsx @@ -19,37 +19,53 @@ /* eslint-env browser */ import PropTypes from 'prop-types'; import React from 'react'; +import { Tabs, Tab } from 'react-bootstrap'; import { StickyContainer, Sticky } from 'react-sticky'; import { ParentSize } from '@vx/responsive'; -import InsertComponentPane, { - SUPERSET_HEADER_HEIGHT, -} from './InsertComponentPane'; -import ColorComponentPane from './ColorComponentPane'; -import { BUILDER_PANE_TYPE } from '../util/constants'; +import { t } from '@superset-ui/translation'; + +import NewColumn from './gridComponents/new/NewColumn'; +import NewDivider from './gridComponents/new/NewDivider'; +import NewHeader from './gridComponents/new/NewHeader'; +import NewRow from './gridComponents/new/NewRow'; +import NewTabs from './gridComponents/new/NewTabs'; +import NewMarkdown from './gridComponents/new/NewMarkdown'; +import SliceAdder from '../containers/SliceAdder'; const propTypes = { topOffset: PropTypes.number, - showBuilderPane: PropTypes.func.isRequired, - builderPaneType: PropTypes.string.isRequired, - setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired, - colorScheme: PropTypes.string, }; const defaultProps = { topOffset: 0, - colorScheme: undefined, }; +const SUPERSET_HEADER_HEIGHT = 59; + class BuilderComponentPane extends React.PureComponent { + renderTabs(height) { + const { isSticky } = this.props; + return ( + + + + + + + + + + + + + + ); + } render() { - const { - topOffset, - builderPaneType, - showBuilderPane, - setColorSchemeAndUnsavedChanges, - colorScheme, - } = this.props; + const { topOffset } = this.props; return (
- {builderPaneType === BUILDER_PANE_TYPE.ADD_COMPONENTS && ( - - )} - {builderPaneType === BUILDER_PANE_TYPE.COLORS && ( - - )} + {this.renderTabs(height)}
)} diff --git a/superset-frontend/src/dashboard/components/ColorComponentPane.jsx b/superset-frontend/src/dashboard/components/ColorComponentPane.jsx deleted file mode 100644 index ee6aec5852..0000000000 --- a/superset-frontend/src/dashboard/components/ColorComponentPane.jsx +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -/* eslint-env browser */ -import PropTypes from 'prop-types'; -import React from 'react'; -import { getCategoricalSchemeRegistry } from '@superset-ui/color'; -import { t } from '@superset-ui/translation'; - -import ColorSchemeControl from '../../explore/components/controls/ColorSchemeControl'; -import { BUILDER_PANE_TYPE } from '../util/constants'; - -const propTypes = { - showBuilderPane: PropTypes.func.isRequired, - setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired, - colorScheme: PropTypes.string, -}; - -const defaultProps = { - colorScheme: undefined, -}; - -class ColorComponentPane extends React.PureComponent { - constructor(props) { - super(props); - this.state = { hovered: false }; - this.categoricalSchemeRegistry = getCategoricalSchemeRegistry(); - this.getChoices = this.getChoices.bind(this); - this.getSchemes = this.getSchemes.bind(this); - this.onCloseButtonClick = this.onCloseButtonClick.bind(this); - this.onMouseEnter = this.setHover.bind(this, true); - this.onMouseLeave = this.setHover.bind(this, false); - } - - onCloseButtonClick() { - this.props.showBuilderPane(BUILDER_PANE_TYPE.NONE); - } - - getChoices() { - return this.categoricalSchemeRegistry.keys().map(s => [s, s]); - } - - getSchemes() { - return this.categoricalSchemeRegistry.getMap(); - } - - setHover(hovered) { - this.setState({ hovered }); - } - - render() { - const { setColorSchemeAndUnsavedChanges, colorScheme } = this.props; - - return ( -
-
-
- {'Color Settings'} - -
-
- -
-
-
- ); - } -} - -ColorComponentPane.propTypes = propTypes; -ColorComponentPane.defaultProps = defaultProps; - -export default ColorComponentPane; diff --git a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx new file mode 100644 index 0000000000..edbfa2adcd --- /dev/null +++ b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* eslint-env browser */ +import PropTypes from 'prop-types'; +import React from 'react'; +import { getCategoricalSchemeRegistry } from '@superset-ui/color'; +import { t } from '@superset-ui/translation'; + +import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl'; + +const propTypes = { + onChange: PropTypes.func.isRequired, + colorScheme: PropTypes.string, +}; + +const defaultProps = { + colorScheme: undefined, + onChange: () => {}, +}; + +class ColorSchemeControlWrapper extends React.PureComponent { + constructor(props) { + super(props); + this.state = { hovered: false }; + this.categoricalSchemeRegistry = getCategoricalSchemeRegistry(); + this.choices = this.categoricalSchemeRegistry.keys().map(s => [s, s]); + this.schemes = this.categoricalSchemeRegistry.getMap(); + } + setHover(hovered) { + this.setState({ hovered }); + } + + render() { + const { colorScheme } = this.props; + return ( + + ); + } +} + +ColorSchemeControlWrapper.propTypes = propTypes; +ColorSchemeControlWrapper.defaultProps = defaultProps; + +export default ColorSchemeControlWrapper; diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx index de9a94eddd..5ca6f2db11 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx @@ -39,7 +39,6 @@ import getDragDropManager from '../util/getDragDropManager'; import findTabIndexByComponentId from '../util/findTabIndexByComponentId'; import { - BUILDER_PANE_TYPE, DASHBOARD_GRID_ID, DASHBOARD_ROOT_ID, DASHBOARD_ROOT_DEPTH, @@ -56,7 +55,6 @@ const propTypes = { deleteTopLevelTabs: PropTypes.func.isRequired, editMode: PropTypes.bool.isRequired, showBuilderPane: PropTypes.func.isRequired, - builderPaneType: PropTypes.string.isRequired, colorScheme: PropTypes.string, setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired, handleComponentDrop: PropTypes.func.isRequired, @@ -161,7 +159,6 @@ class DashboardBuilder extends React.Component { dashboardLayout, editMode, showBuilderPane, - builderPaneType, setColorSchemeAndUnsavedChanges, colorScheme, } = this.props; @@ -265,11 +262,10 @@ class DashboardBuilder extends React.Component { )} - {editMode && builderPaneType !== BUILDER_PANE_TYPE.NONE && ( + {editMode && ( diff --git a/superset-frontend/src/dashboard/components/Header.jsx b/superset-frontend/src/dashboard/components/Header.jsx index 3d04284829..89f2084ed8 100644 --- a/superset-frontend/src/dashboard/components/Header.jsx +++ b/superset-frontend/src/dashboard/components/Header.jsx @@ -21,20 +21,21 @@ import moment from 'moment'; import React from 'react'; import PropTypes from 'prop-types'; import styled from '@superset-ui/style'; +import { ButtonGroup } from 'react-bootstrap'; import { CategoricalColorNamespace } from '@superset-ui/color'; import { t } from '@superset-ui/translation'; +import Icon from 'src/components/Icon'; + import HeaderActionsDropdown from './HeaderActionsDropdown'; import EditableTitle from '../../components/EditableTitle'; import Button from '../../components/Button'; import FaveStar from '../../components/FaveStar'; -import FilterScopeModal from './filterscope/FilterScopeModal'; import PublishedStatus from './PublishedStatus'; import UndoRedoKeylisteners from './UndoRedoKeylisteners'; import { chartPropShape } from '../util/propShapes'; import { - BUILDER_PANE_TYPE, UNDO_LIMIT, SAVE_TYPE_OVERWRITE, DASHBOARD_POSITION_DATA_LIMIT, @@ -62,6 +63,7 @@ const propTypes = { customCss: PropTypes.string.isRequired, colorNamespace: PropTypes.string, colorScheme: PropTypes.string, + setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired, isStarred: PropTypes.bool.isRequired, isPublished: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired, @@ -75,7 +77,6 @@ const propTypes = { editMode: PropTypes.bool.isRequired, setEditMode: PropTypes.func.isRequired, showBuilderPane: PropTypes.func.isRequired, - builderPaneType: PropTypes.string.isRequired, updateCss: PropTypes.func.isRequired, logEvent: PropTypes.func.isRequired, hasUnsavedChanges: PropTypes.bool.isRequired, @@ -124,10 +125,6 @@ class Header extends React.PureComponent { this.handleChangeText = this.handleChangeText.bind(this); this.handleCtrlZ = this.handleCtrlZ.bind(this); this.handleCtrlY = this.handleCtrlY.bind(this); - this.onInsertComponentsButtonClick = this.onInsertComponentsButtonClick.bind( - this, - ); - this.onColorsButtonClick = this.onColorsButtonClick.bind(this); this.toggleEditMode = this.toggleEditMode.bind(this); this.forceRefresh = this.forceRefresh.bind(this); this.startPeriodicRender = this.startPeriodicRender.bind(this); @@ -162,14 +159,6 @@ class Header extends React.PureComponent { clearTimeout(this.ctrlZTimeout); } - onInsertComponentsButtonClick() { - this.props.showBuilderPane(BUILDER_PANE_TYPE.ADD_COMPONENTS); - } - - onColorsButtonClick() { - this.props.showBuilderPane(BUILDER_PANE_TYPE.COLORS); - } - handleChangeText(nextText) { const { updateDashboardTitle, onChange } = this.props; if (nextText && this.props.dashboardTitle !== nextText) { @@ -340,6 +329,7 @@ class Header extends React.PureComponent { expandedSlices, customCss, colorNamespace, + setColorSchemeAndUnsavedChanges, colorScheme, onUndo, onRedo, @@ -350,7 +340,6 @@ class Header extends React.PureComponent { updateCss, editMode, isPublished, - builderPaneType, dashboardInfo, hasUnsavedChanges, isLoading, @@ -366,7 +355,6 @@ class Header extends React.PureComponent { const refreshWarning = dashboardInfo.common.conf .SUPERSET_DASHBOARD_PERIODICAL_REFRESH_WARNING_MESSAGE; - const popButton = hasUnsavedChanges; return ( @@ -399,92 +387,63 @@ class Header extends React.PureComponent { {userCanSaveAs && (
{editMode && ( - - )} - - {editMode && ( - - )} - - {editMode && ( - - )} - - {editMode && ( - - )} - - {editMode && ( - {t('Filters')}} - /> - )} - - {editMode && hasUnsavedChanges && ( - - )} - - {editMode && !hasUnsavedChanges && ( - - )} - - {editMode && ( - + <> + + + + + + + )}
)} + {editMode && ( + + )} - {!editMode && !hasUnsavedChanges && ( - + + )} {this.state.showingPropertiesModal && ( @@ -492,12 +451,18 @@ class Header extends React.PureComponent { dashboardId={dashboardInfo.id} show={this.state.showingPropertiesModal} onHide={this.hidePropertiesModal} - onDashboardSave={updates => { - this.props.dashboardInfoChanged({ + colorScheme={this.props.colorScheme} + onSubmit={updates => { + const { + dashboardInfoChanged, + dashboardTitleChanged, + } = this.props; + dashboardInfoChanged({ slug: updates.slug, metadata: JSON.parse(updates.jsonMetadata), }); - this.props.dashboardTitleChanged(updates.title); + setColorSchemeAndUnsavedChanges(updates.colorScheme); + dashboardTitleChanged(updates.title); if (updates.slug) { history.pushState( { event: 'dashboard_properties_changed' }, diff --git a/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx b/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx index da574e0d10..8a1c2e9022 100644 --- a/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx +++ b/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx @@ -18,16 +18,20 @@ */ import React from 'react'; import PropTypes from 'prop-types'; + import { SupersetClient } from '@superset-ui/connection'; import { DropdownButton, MenuItem } from 'react-bootstrap'; import { t } from '@superset-ui/translation'; +import Icon from 'src/components/Icon'; + import CssEditor from './CssEditor'; import RefreshIntervalModal from './RefreshIntervalModal'; import SaveModal from './SaveModal'; import injectCustomCss from '../util/injectCustomCss'; import { SAVE_TYPE_NEWDASHBOARD } from '../util/constants'; import URLShortLinkModal from '../../components/URLShortLinkModal'; +import FilterScopeModal from './filterscope/FilterScopeModal'; import downloadAsImage from '../../utils/downloadAsImage'; import getDashboardUrl from '../util/getDashboardUrl'; import { getActiveFilters } from '../util/activeDashboardFilters'; @@ -38,7 +42,6 @@ const propTypes = { dashboardInfo: PropTypes.object.isRequired, dashboardId: PropTypes.number.isRequired, dashboardTitle: PropTypes.string.isRequired, - hasUnsavedChanges: PropTypes.bool.isRequired, customCss: PropTypes.string.isRequired, colorNamespace: PropTypes.string, colorScheme: PropTypes.string, @@ -128,7 +131,6 @@ class HeaderActionsDropdown extends React.PureComponent { customCss, colorNamespace, colorScheme, - hasUnsavedChanges, layout, expandedSlices, onSave, @@ -145,72 +147,36 @@ class HeaderActionsDropdown extends React.PureComponent { return ( } + noCaret id="save-dash-split-button" - bsStyle={hasUnsavedChanges ? 'primary' : undefined} bsSize="small" + style={{ border: 'none', padding: 0, marginLeft: '4px' }} pullRight > {userCanSave && ( - {t('Save as')}} - canOverwrite={userCanEdit} - /> + <> + {t('Save as')}} + canOverwrite={userCanEdit} + /> + )} - - {hasUnsavedChanges && userCanSave && ( -
- - {t('Discard changes')} - -
- )} - - {userCanSave && } - - - {t('Force refresh dashboard')} - - - - {editMode - ? t('Set auto-refresh interval') - : t('Auto-refresh dashboard')} - - } - /> - - {editMode && ( - - {t('Edit dashboard properties')} - - )} - {t('Share dashboard')}} /> + + {t('Refresh dashboard')} + + + {t('Set auto-refresh interval')}} + /> {editMode && ( - {t('Edit CSS')}} - initialCss={this.state.css} - templates={this.state.cssTemplates} - onChange={this.changeCss} - /> + <> + {t('Set filter mapping')} + } + /> + + {t('Edit dashboard properties')} + + {t('Edit CSS')}} + initialCss={this.state.css} + templates={this.state.cssTemplates} + onChange={this.changeCss} + /> + )} {!editMode && ( diff --git a/superset-frontend/src/dashboard/components/InsertComponentPane.jsx b/superset-frontend/src/dashboard/components/InsertComponentPane.jsx deleted file mode 100644 index 31413471f7..0000000000 --- a/superset-frontend/src/dashboard/components/InsertComponentPane.jsx +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -/* eslint-env browser */ -import PropTypes from 'prop-types'; -import React from 'react'; -import cx from 'classnames'; -import { t } from '@superset-ui/translation'; - -import NewColumn from './gridComponents/new/NewColumn'; -import NewDivider from './gridComponents/new/NewDivider'; -import NewHeader from './gridComponents/new/NewHeader'; -import NewRow from './gridComponents/new/NewRow'; -import NewTabs from './gridComponents/new/NewTabs'; -import NewMarkdown from './gridComponents/new/NewMarkdown'; -import SliceAdder from '../containers/SliceAdder'; -import { BUILDER_PANE_TYPE } from '../util/constants'; - -export const SUPERSET_HEADER_HEIGHT = 59; - -const propTypes = { - height: PropTypes.number.isRequired, - isSticky: PropTypes.bool.isRequired, - showBuilderPane: PropTypes.func.isRequired, -}; - -class InsertComponentPane extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - slideDirection: 'slide-out', - }; - - this.onCloseButtonClick = this.onCloseButtonClick.bind(this); - this.openSlicesPane = this.slide.bind(this, 'slide-in'); - this.closeSlicesPane = this.slide.bind(this, 'slide-out'); - } - - onCloseButtonClick() { - this.props.showBuilderPane(BUILDER_PANE_TYPE.NONE); - } - - slide(direction) { - this.setState({ - slideDirection: direction, - }); - } - - render() { - return ( -
-
-
- {t('Insert components')} - -
-
-
-
- {t('Your charts & filters')} -
- - -
- - - - - - -
-
-
- - {t('Your charts and filters')} -
- -
-
- ); - } -} - -InsertComponentPane.propTypes = propTypes; - -export default InsertComponentPane; diff --git a/superset-frontend/src/dashboard/components/PropertiesModal.jsx b/superset-frontend/src/dashboard/components/PropertiesModal.jsx index 77c29952e4..aac3be277a 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal.jsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal.jsx @@ -27,6 +27,7 @@ import { t } from '@superset-ui/translation'; import { SupersetClient } from '@superset-ui/connection'; import FormLabel from 'src/components/FormLabel'; +import ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeControlWrapper'; import getClientErrorObject from '../../utils/getClientErrorObject'; import withToasts from '../../messageToasts/enhancers/withToasts'; import '../stylesheets/buttons.less'; @@ -35,14 +36,20 @@ const propTypes = { dashboardId: PropTypes.number.isRequired, show: PropTypes.bool.isRequired, onHide: PropTypes.func, - onDashboardSave: PropTypes.func, + colorScheme: PropTypes.object, + setColorSchemeAndUnsavedChanges: PropTypes.func, + onSubmit: PropTypes.func, addSuccessToast: PropTypes.func.isRequired, + onlyApply: PropTypes.bool, }; const defaultProps = { onHide: () => {}, - onDashboardSave: () => {}, + setColorSchemeAndUnsavedChanges: () => {}, + onSubmit: () => {}, show: false, + colorScheme: undefined, + onlyApply: false, }; class PropertiesModal extends React.PureComponent { @@ -55,6 +62,7 @@ class PropertiesModal extends React.PureComponent { slug: '', owners: [], json_metadata: '', + colorScheme: props.colorScheme, }, isDashboardLoaded: false, isAdvancedOpen: false, @@ -62,15 +70,19 @@ class PropertiesModal extends React.PureComponent { this.onChange = this.onChange.bind(this); this.onMetadataChange = this.onMetadataChange.bind(this); this.onOwnersChange = this.onOwnersChange.bind(this); - this.save = this.save.bind(this); + this.submit = this.submit.bind(this); this.toggleAdvanced = this.toggleAdvanced.bind(this); this.loadOwnerOptions = this.loadOwnerOptions.bind(this); this.handleErrorResponse = this.handleErrorResponse.bind(this); + this.onColorSchemeChange = this.onColorSchemeChange.bind(this); } componentDidMount() { this.fetchDashboardDetails(); } + onColorSchemeChange(value) { + this.updateFormState('colorScheme', value); + } onOwnersChange(value) { this.updateFormState('owners', value); @@ -155,39 +167,55 @@ class PropertiesModal extends React.PureComponent { }); } - save(e) { + submit(e) { e.preventDefault(); e.stopPropagation(); const { values } = this.state; + const { onlyApply } = this.props; const owners = values.owners.map(o => o.value); - - SupersetClient.put({ - endpoint: `/api/v1/dashboard/${this.props.dashboardId}`, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - dashboard_title: values.dashboard_title, - slug: values.slug || null, - json_metadata: values.json_metadata || null, - owners, - }), - }).then(({ json }) => { - this.props.addSuccessToast(t('The dashboard has been saved')); - this.props.onDashboardSave({ + if (onlyApply) { + this.props.onSubmit({ id: this.props.dashboardId, - title: json.result.dashboard_title, - slug: json.result.slug, - jsonMetadata: json.result.json_metadata, - ownerIds: json.result.owners, + title: values.dashboard_title, + slug: values.slug, + jsonMetadata: values.json_metadata, + ownerIds: owners, + colorScheme: values.colorScheme, }); this.props.onHide(); - }, this.handleErrorResponse); + } else { + SupersetClient.put({ + endpoint: `/api/v1/dashboard/${this.props.dashboardId}`, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + dashboard_title: values.dashboard_title, + slug: values.slug || null, + json_metadata: values.json_metadata || null, + owners, + }), + }).then(({ json }) => { + this.props.addSuccessToast(t('The dashboard has been saved')); + this.props.onSubmit({ + id: this.props.dashboardId, + title: json.result.dashboard_title, + slug: json.result.slug, + jsonMetadata: json.result.json_metadata, + ownerIds: json.result.owners, + colorScheme: values.colorScheme, + }); + this.props.onHide(); + }, this.handleErrorResponse); + } } render() { - const { values, isDashboardLoaded, isAdvancedOpen } = this.state; + const { values, isDashboardLoaded, isAdvancedOpen, errors } = this.state; + const { onHide, onlyApply } = this.props; + + const saveLabel = onlyApply ? t('Apply') : t('Save'); return ( -
+
@@ -249,6 +277,13 @@ class PropertiesModal extends React.PureComponent { )}

+ +

{t('Colors')}

+ + @@ -300,11 +335,11 @@ class PropertiesModal extends React.PureComponent { bsSize="sm" bsStyle="primary" className="m-r-5" - disabled={this.state.errors.length > 0} + disabled={errors.length > 0} > - {t('Save')} + {saveLabel} - - {t('Choose the refresh frequency for this dashboard')} + {t('Refresh frequency')}