From commits-return-1125-archive-asf-public=cust-asf.ponee.io@superset.incubator.apache.org Fri Jun 22 02:54:20 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id 8AAF61807A1 for ; Fri, 22 Jun 2018 02:54:18 +0200 (CEST) Received: (qmail 41378 invoked by uid 500); 22 Jun 2018 00:54:17 -0000 Mailing-List: contact commits-help@superset.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@superset.incubator.apache.org Delivered-To: mailing list commits@superset.incubator.apache.org Received: (qmail 41350 invoked by uid 99); 22 Jun 2018 00:54:17 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 22 Jun 2018 00:54:17 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 6BBE1850CB; Fri, 22 Jun 2018 00:54:16 +0000 (UTC) Date: Fri, 22 Jun 2018 00:54:26 +0000 To: "commits@superset.apache.org" Subject: [incubator-superset] 11/26: Dashboard save button (#4979) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: ccwilliams@apache.org In-Reply-To: <152962885522.16472.1136442268545544298@gitbox.apache.org> References: <152962885522.16472.1136442268545544298@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: incubator-superset X-Git-Refname: refs/heads/dashboard-builder X-Git-Reftype: branch X-Git-Rev: ae37277ddf56ee4b1fc6d6309a9c0499d0bf94e7 X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20180622005416.6BBE1850CB@gitbox.apache.org> This is an automated email from the ASF dual-hosted git repository. ccwilliams pushed a commit to branch dashboard-builder in repository https://gitbox.apache.org/repos/asf/incubator-superset.git commit ae37277ddf56ee4b1fc6d6309a9c0499d0bf94e7 Author: Grace Guo AuthorDate: Tue May 22 22:36:33 2018 -0700 Dashboard save button (#4979) * save button * fix slices list height * save custom css * merge save-dash changes from dashboard v1 https://github.com/apache/incubator-superset/pull/4900 https://github.com/apache/incubator-superset/pull/5051 --- .../assets/src/dashboard/actions/dashboardState.js | 43 +++++++++- .../assets/src/dashboard/components/Controls.jsx | 38 ++++----- .../assets/src/dashboard/components/Header.jsx | 98 ++++++++++++++++++---- .../assets/src/dashboard/components/SaveModal.jsx | 78 ++++++----------- .../assets/src/dashboard/components/SliceAdder.jsx | 11 ++- .../dashboard/components/SliceHeaderControls.jsx | 18 +++- .../dashboard/components/gridComponents/Chart.jsx | 6 ++ superset/assets/src/dashboard/containers/Chart.jsx | 2 + .../src/dashboard/containers/DashboardHeader.jsx | 7 +- .../src/dashboard/reducers/dashboardState.js | 6 +- .../src/dashboard/reducers/getInitialState.js | 8 +- .../assets/src/dashboard/stylesheets/builder.less | 5 ++ .../src/dashboard/stylesheets/dashboard.less | 32 ++++++- superset/assets/src/dashboard/util/constants.js | 4 + superset/assets/src/modules/utils.js | 2 +- 15 files changed, 246 insertions(+), 112 deletions(-) diff --git a/superset/assets/src/dashboard/actions/dashboardState.js b/superset/assets/src/dashboard/actions/dashboardState.js index 10c0a26..42f68ad 100644 --- a/superset/assets/src/dashboard/actions/dashboardState.js +++ b/superset/assets/src/dashboard/actions/dashboardState.js @@ -6,7 +6,15 @@ import { addChart, removeChart, refreshChart } from '../../chart/chartAction'; import { chart as initChart } from '../../chart/chartReducer'; import { fetchDatasourceMetadata } from '../../dashboard/actions/datasources'; import { applyDefaultFormData } from '../../explore/stores/store'; -import { addWarningToast } from './messageToasts'; +import { getAjaxErrorMsg } from '../../modules/utils'; +import { SAVE_TYPE_OVERWRITE } from '../util/constants'; +import { t } from '../../locales'; + +import { + addSuccessToast, + addWarningToast, + addDangerToast, +} from './messageToasts'; export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES'; export function setUnsavedChanges(hasUnsavedChanges) { @@ -66,6 +74,11 @@ export function toggleExpandSlice(sliceId) { return { type: TOGGLE_EXPAND_SLICE, sliceId }; } +export const UPDATE_CSS = 'UPDATE_CSS'; +export function updateCss(css) { + return { type: UPDATE_CSS, css }; +} + export const SET_EDIT_MODE = 'SET_EDIT_MODE'; export function setEditMode(editMode) { return { type: SET_EDIT_MODE, editMode }; @@ -81,7 +94,7 @@ export function onSave() { return { type: ON_SAVE }; } -export function saveDashboard() { +export function saveDashboardRequestSuccess() { return dispatch => { dispatch(onSave()); // clear layout undo history @@ -89,6 +102,32 @@ export function saveDashboard() { }; } +export function saveDashboardRequest(data, id, saveType) { + const path = saveType === SAVE_TYPE_OVERWRITE ? 'save_dash' : 'copy_dash'; + const url = `/superset/${path}/${id}/`; + return dispatch => + $.ajax({ + type: 'POST', + url, + data: { + data: JSON.stringify(data), + }, + success: () => { + dispatch(saveDashboardRequestSuccess()); + dispatch(addSuccessToast(t('This dashboard was saved successfully.'))); + }, + error: error => { + const errorMsg = getAjaxErrorMsg(error); + dispatch( + addDangerToast( + `${t('Sorry, there was an error saving this dashboard: ')} + ${errorMsg}`, + ), + ); + }, + }); +} + export function fetchCharts(chartList = [], force = false, interval = 0) { return (dispatch, getState) => { const timeout = getState().dashboardInfo.common.conf diff --git a/superset/assets/src/dashboard/components/Controls.jsx b/superset/assets/src/dashboard/components/Controls.jsx index 07b6c33..9d54b09 100644 --- a/superset/assets/src/dashboard/components/Controls.jsx +++ b/superset/assets/src/dashboard/components/Controls.jsx @@ -4,8 +4,8 @@ import PropTypes from 'prop-types'; import $ from 'jquery'; import { DropdownButton, MenuItem } from 'react-bootstrap'; +import CssEditor from './CssEditor'; import RefreshIntervalModal from './RefreshIntervalModal'; -import SaveModal from './SaveModal'; import { t } from '../../locales'; function updateDom(css) { @@ -31,12 +31,10 @@ const propTypes = { addDangerToast: PropTypes.func.isRequired, dashboardInfo: PropTypes.object.isRequired, dashboardTitle: PropTypes.string.isRequired, - layout: PropTypes.object.isRequired, - filters: PropTypes.object.isRequired, - expandedSlices: PropTypes.object.isRequired, + css: PropTypes.string.isRequired, slices: PropTypes.array, - onSave: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, + updateCss: PropTypes.func.isRequired, forceRefreshAllCharts: PropTypes.func.isRequired, startPeriodicRender: PropTypes.func.isRequired, editMode: PropTypes.bool, @@ -51,9 +49,11 @@ class Controls extends React.PureComponent { constructor(props) { super(props); this.state = { - css: '', + css: props.css, cssTemplates: [], }; + + this.changeCss = this.changeCss.bind(this); } componentWillMount() { @@ -74,17 +74,14 @@ class Controls extends React.PureComponent { updateDom(css); }); this.props.onChange(); + this.props.updateCss(css); } render() { const { dashboardTitle, - layout, - filters, - expandedSlices, startPeriodicRender, forceRefreshAllCharts, - onSave, editMode, } = this.props; @@ -110,19 +107,6 @@ class Controls extends React.PureComponent { } triggerNode={{t('Set auto-refresh interval')}} /> - {editMode ? t('Save') : t('Save as')}} - isMenuItem - /> {editMode && ( {t('Email dashboard link')} )} + {editMode && ( + {t('Edit CSS')}} + initialCss={this.state.css} + templates={this.state.cssTemplates} + onChange={this.changeCss} + /> + )} ); diff --git a/superset/assets/src/dashboard/components/Header.jsx b/superset/assets/src/dashboard/components/Header.jsx index 21b01db..31bd08c 100644 --- a/superset/assets/src/dashboard/components/Header.jsx +++ b/superset/assets/src/dashboard/components/Header.jsx @@ -1,6 +1,12 @@ +/* eslint-env browser */ import React from 'react'; import PropTypes from 'prop-types'; -import { ButtonGroup, ButtonToolbar } from 'react-bootstrap'; +import { + DropdownButton, + MenuItem, + ButtonGroup, + ButtonToolbar, +} from 'react-bootstrap'; import Controls from './Controls'; import EditableTitle from '../../components/EditableTitle'; @@ -9,7 +15,11 @@ import FaveStar from '../../components/FaveStar'; import SaveModal from './SaveModal'; import { chartPropShape } from '../util/propShapes'; import { t } from '../../locales'; -import { UNDO_LIMIT } from '../util/constants'; +import { + UNDO_LIMIT, + SAVE_TYPE_NEWDASHBOARD, + SAVE_TYPE_OVERWRITE, +} from '../util/constants'; const propTypes = { addSuccessToast: PropTypes.func.isRequired, @@ -20,6 +30,7 @@ const propTypes = { layout: PropTypes.object.isRequired, filters: PropTypes.object.isRequired, expandedSlices: PropTypes.object.isRequired, + css: PropTypes.string.isRequired, isStarred: PropTypes.bool.isRequired, onSave: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, @@ -32,6 +43,7 @@ const propTypes = { setEditMode: PropTypes.func.isRequired, showBuilderPane: PropTypes.bool.isRequired, toggleBuilderPane: PropTypes.func.isRequired, + updateCss: PropTypes.func.isRequired, hasUnsavedChanges: PropTypes.bool.isRequired, maxUndoHistoryExceeded: PropTypes.bool.isRequired, @@ -45,6 +57,10 @@ const propTypes = { }; class Header extends React.PureComponent { + static discardChanges() { + window.location.reload(); + } + constructor(props) { super(props); this.state = { @@ -54,6 +70,7 @@ class Header extends React.PureComponent { this.handleChangeText = this.handleChangeText.bind(this); this.toggleEditMode = this.toggleEditMode.bind(this); this.forceRefresh = this.forceRefresh.bind(this); + this.overwriteDashboard = this.overwriteDashboard.bind(this); } componentWillReceiveProps(nextProps) { @@ -88,38 +105,62 @@ class Header extends React.PureComponent { this.props.setEditMode(!this.props.editMode); } + overwriteDashboard() { + const { + dashboardTitle, + layout: positions, + expandedSlices, + css, + filters, + dashboardInfo, + } = this.props; + + const data = { + positions, + expanded_slices: expandedSlices, + css, + dashboard_title: dashboardTitle, + default_filters: JSON.stringify(filters), + }; + + this.props.onSave(data, dashboardInfo.id, SAVE_TYPE_OVERWRITE); + } + render() { const { dashboardTitle, layout, filters, expandedSlices, + css, onUndo, onRedo, undoLength, redoLength, onChange, onSave, + updateCss, editMode, showBuilderPane, dashboardInfo, hasUnsavedChanges, } = this.props; - const userCanEdit = dashboardInfo.dash_save_perm; + const userCanEdit = dashboardInfo.dash_edit_perm; + const userCanSaveAs = dashboardInfo.dash_save_perm; return (
- {userCanEdit && ( + {userCanSaveAs && ( {editMode && ( ) : ( + + )} + - {t('Save changes')} - - } + isMenuItem + triggerNode={{t('Save as')}} + canOverwrite={userCanEdit} /> - )} + {hasUnsavedChanges && ( + + {t('Discard changes')} + + )} + )} diff --git a/superset/assets/src/dashboard/components/SaveModal.jsx b/superset/assets/src/dashboard/components/SaveModal.jsx index 1d287d6..9d63331 100644 --- a/superset/assets/src/dashboard/components/SaveModal.jsx +++ b/superset/assets/src/dashboard/components/SaveModal.jsx @@ -1,13 +1,12 @@ /* eslint-env browser */ import React from 'react'; import PropTypes from 'prop-types'; -import $ from 'jquery'; import { Button, FormControl, FormGroup, Radio } from 'react-bootstrap'; -import { getAjaxErrorMsg } from '../../modules/utils'; import ModalTrigger from '../../components/ModalTrigger'; import { t } from '../../locales'; import Checkbox from '../../components/Checkbox'; +import { SAVE_TYPE_OVERWRITE, SAVE_TYPE_NEWDASHBOARD } from '../util/constants'; const propTypes = { addSuccessToast: PropTypes.func.isRequired, @@ -16,21 +15,25 @@ const propTypes = { dashboardTitle: PropTypes.string.isRequired, expandedSlices: PropTypes.object.isRequired, layout: PropTypes.object.isRequired, + saveType: PropTypes.oneOf([SAVE_TYPE_OVERWRITE, SAVE_TYPE_NEWDASHBOARD]), triggerNode: PropTypes.node.isRequired, filters: PropTypes.object.isRequired, + css: PropTypes.string.isRequired, onSave: PropTypes.func.isRequired, isMenuItem: PropTypes.bool, + canOverwrite: PropTypes.bool.isRequired, }; const defaultProps = { isMenuItem: false, + saveType: SAVE_TYPE_OVERWRITE, }; class SaveModal extends React.PureComponent { constructor(props) { super(props); this.state = { - saveType: 'overwrite', + saveType: props.saveType, newDashName: `${props.dashboardTitle} [copy]`, duplicateSlices: false, }; @@ -40,6 +43,7 @@ class SaveModal extends React.PureComponent { this.saveDashboard = this.saveDashboard.bind(this); this.setModalRef = this.setModalRef.bind(this); this.toggleDuplicateSlices = this.toggleDuplicateSlices.bind(this); + this.onSave = this.props.onSave.bind(this); } setModalRef(ref) { @@ -59,37 +63,7 @@ class SaveModal extends React.PureComponent { handleNameChange(event) { this.setState({ newDashName: event.target.value, - saveType: 'newDashboard', - }); - } - - // @TODO this should all be moved to actions - saveDashboardRequest(data, url, saveType) { - $.ajax({ - type: 'POST', - url, - data: { - data: JSON.stringify(data), - }, - success: resp => { - this.modal.close(); - this.props.onSave(); - if (saveType === 'newDashboard') { - window.location = `/superset/dashboard/${resp.id}/`; - } else { - this.props.addSuccessToast( - t('This dashboard was saved successfully.'), - ); - } - }, - error: error => { - this.modal.close(); - const errorMsg = getAjaxErrorMsg(error); - this.props.addDangerToast( - `${t('Sorry, there was an error saving this dashboard: ')} - ${errorMsg}`, - ); - }, + saveType: SAVE_TYPE_NEWDASHBOARD, }); } @@ -98,6 +72,7 @@ class SaveModal extends React.PureComponent { const { dashboardTitle, layout: positions, + css, expandedSlices, filters, dashboardId, @@ -105,26 +80,24 @@ class SaveModal extends React.PureComponent { const data = { positions, + css, expanded_slices: expandedSlices, dashboard_title: dashboardTitle, default_filters: JSON.stringify(filters), duplicate_slices: this.state.duplicateSlices, }; - let url = null; - if (saveType === 'overwrite') { - url = `/superset/save_dash/${dashboardId}/`; - this.saveDashboardRequest(data, url, saveType); - } else if (saveType === 'newDashboard') { - if (!newDashName) { - this.props.addDangerToast( - t('You must pick a name for the new dashboard'), - ); - } else { - data.dashboard_title = newDashName; - url = `/superset/copy_dash/${dashboardId}/`; - this.saveDashboardRequest(data, url, saveType); - } + if (saveType === SAVE_TYPE_NEWDASHBOARD && !newDashName) { + this.props.addDangerToast( + t('You must pick a name for the new dashboard'), + ); + } else { + this.onSave(data, dashboardId, saveType).done(resp => { + if (saveType === SAVE_TYPE_NEWDASHBOARD) { + window.location = `/superset/dashboard/${resp.id}/`; + } + }); + this.modal.close(); } } @@ -138,17 +111,18 @@ class SaveModal extends React.PureComponent { modalBody={ {t('Overwrite Dashboard [%s]', this.props.dashboardTitle)}
{t('Save as:')} diff --git a/superset/assets/src/dashboard/components/SliceAdder.jsx b/superset/assets/src/dashboard/components/SliceAdder.jsx index 05c4270..47451c4 100644 --- a/superset/assets/src/dashboard/components/SliceAdder.jsx +++ b/superset/assets/src/dashboard/components/SliceAdder.jsx @@ -39,6 +39,10 @@ const KEYS_TO_SORT = [ { key: 'changed_on', label: 'Recent' }, ]; +const MARGIN_BOTTOM = 16; +const SIDEPANE_HEADER_HEIGHT = 55; +const SLICE_ADDER_CONTROL_HEIGHT = 64; + class SliceAdder extends React.Component { static sortByComparator(attr) { const desc = attr === 'changed_on' ? -1 : 1; @@ -166,6 +170,11 @@ class SliceAdder extends React.Component { } render() { + const slicesListHeight = + this.props.height - + SIDEPANE_HEADER_HEIGHT - + SLICE_ADDER_CONTROL_HEIGHT - + MARGIN_BOTTOM; return (
@@ -202,7 +211,7 @@ class SliceAdder extends React.Component { this.state.filteredSlices.length > 0 && ( ( @@ -96,13 +100,19 @@ class SliceHeaderControls extends React.PureComponent { )} - - {t('Edit chart metadata')} - + {this.props.sliceCanEdit && ( + + {t('Edit chart metadata')} + + )} {t('Export CSV')} - {t('Explore chart')} + {this.props.supersetCanExplore && ( + + {t('Explore chart')} + + )} ); diff --git a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx index 4742d71..9f8d723 100644 --- a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx @@ -29,6 +29,8 @@ const propTypes = { removeFilter: PropTypes.func.isRequired, editMode: PropTypes.bool.isRequired, isExpanded: PropTypes.bool.isRequired, + supersetCanExplore: PropTypes.bool.isRequired, + sliceCanEdit: PropTypes.bool.isRequired, }; // we use state + shouldComponentUpdate() logic to prevent perf-wrecking @@ -155,6 +157,8 @@ class Chart extends React.Component { sliceName, toggleExpandSlice, timeout, + supersetCanExplore, + sliceCanEdit, } = this.props; const { width } = this.state; @@ -179,6 +183,8 @@ class Chart extends React.Component { exportCSV={this.exportCSV} updateSliceName={updateSliceName} sliceName={sliceName} + supersetCanExplore={supersetCanExplore} + sliceCanEdit={sliceCanEdit} /> {/* diff --git a/superset/assets/src/dashboard/containers/Chart.jsx b/superset/assets/src/dashboard/containers/Chart.jsx index 61627d2..107e6c7 100644 --- a/superset/assets/src/dashboard/containers/Chart.jsx +++ b/superset/assets/src/dashboard/containers/Chart.jsx @@ -40,6 +40,8 @@ function mapStateToProps( }), editMode: dashboardState.editMode, isExpanded: !!dashboardState.expandedSlices[id], + supersetCanExplore: !!dashboardInfo.superset_can_explore, + sliceCanEdit: !!dashboardInfo.slice_can_edit, }; } diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx index fe7e7bb..19be06c 100644 --- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx +++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx @@ -10,8 +10,9 @@ import { saveFaveStar, fetchCharts, startPeriodicRender, + updateCss, onChange, - saveDashboard, + saveDashboardRequest, setMaxUndoHistoryExceeded, maxUndoHistoryToast, } from '../actions/dashboardState'; @@ -42,6 +43,7 @@ function mapStateToProps({ (undoableLayout.present[DASHBOARD_HEADER_ID] || {}).meta || {} ).text, expandedSlices: dashboard.expandedSlices, + css: dashboard.css, charts, userId: dashboardInfo.userId, isStarred: !!dashboard.isStarred, @@ -66,8 +68,9 @@ function mapDispatchToProps(dispatch) { fetchCharts, startPeriodicRender, updateDashboardTitle, + updateCss, onChange, - onSave: saveDashboard, + onSave: saveDashboardRequest, setMaxUndoHistoryExceeded, maxUndoHistoryToast, }, diff --git a/superset/assets/src/dashboard/reducers/dashboardState.js b/superset/assets/src/dashboard/reducers/dashboardState.js index 2d44399..2523494 100644 --- a/superset/assets/src/dashboard/reducers/dashboardState.js +++ b/superset/assets/src/dashboard/reducers/dashboardState.js @@ -14,13 +14,13 @@ import { TOGGLE_BUILDER_PANE, TOGGLE_EXPAND_SLICE, TOGGLE_FAVE_STAR, - UPDATE_DASHBOARD_TITLE, + UPDATE_CSS, } from '../actions/dashboardState'; export default function dashboardStateReducer(state = {}, action) { const actionHandlers = { - [UPDATE_DASHBOARD_TITLE]() { - return { ...state, title: action.title }; + [UPDATE_CSS]() { + return { ...state, css: action.css }; }, [ADD_SLICE]() { const updatedSliceIds = new Set(state.sliceIds); diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js index b209043..f129bf7 100644 --- a/superset/assets/src/dashboard/reducers/getInitialState.js +++ b/superset/assets/src/dashboard/reducers/getInitialState.js @@ -58,10 +58,7 @@ export default function(bootstrapData) { future: [], }; - delete dashboard.position_json; - delete dashboard.css; - - // creat a lookup to sync layout names with slice names + // create a lookup to sync layout names with slice names const chartIdToLayoutId = {}; Object.values(layout).forEach(layoutComponent => { if (layoutComponent.type === CHART_TYPE) { @@ -124,6 +121,8 @@ export default function(bootstrapData) { userId: user_id, dash_edit_perm: dashboard.dash_edit_perm, dash_save_perm: dashboard.dash_save_perm, + superset_can_explore: dashboard.superset_can_explore, + slice_can_edit: dashboard.slice_can_edit, common, }, dashboardState: { @@ -131,6 +130,7 @@ export default function(bootstrapData) { refresh: false, filters, expandedSlices: dashboard.metadata.expanded_slices || {}, + css: dashboard.css || '', editMode: false, showBuilderPane: false, hasUnsavedChanges: false, diff --git a/superset/assets/src/dashboard/stylesheets/builder.less b/superset/assets/src/dashboard/stylesheets/builder.less index 7c14056..ecf192e 100644 --- a/superset/assets/src/dashboard/stylesheets/builder.less +++ b/superset/assets/src/dashboard/stylesheets/builder.less @@ -46,9 +46,14 @@ /* @TODO remove upon new theme */ .btn.btn-primary { background: @almost-black !important; + border-color: @almost-black; color: white !important; } +.dropdown-toggle.btn.btn-primary .caret { + color: white; +} + .background--transparent { background-color: transparent; } diff --git a/superset/assets/src/dashboard/stylesheets/dashboard.less b/superset/assets/src/dashboard/stylesheets/dashboard.less index 8d8c8be..5756786 100644 --- a/superset/assets/src/dashboard/stylesheets/dashboard.less +++ b/superset/assets/src/dashboard/stylesheets/dashboard.less @@ -38,6 +38,29 @@ } } +.dashboard .dashboard-header { + #save-dash-split-button { + border-radius: 0; + margin-left: -8px; + height: 30px; + width: 30px; + + &.btn.btn-primary { + border-left-color: white; + } + + .caret { + position: absolute; + top: 24px; + left: 3px; + } + + & + .dropdown-menu.dropdown-menu-right { + min-width: unset; + } + } +} + .dashboard .chart-header, .dashboard .dashboard-header { .dropdown-menu { @@ -63,7 +86,7 @@ padding: 0 16px; position: absolute; top: 0; - right: -22px; + right: -16px; //increase the click-able area for the button &:hover { cursor: pointer; @@ -80,12 +103,17 @@ .is-cached & { background-color: @pink; - margin-right: 6px; } .vertical-dots-container & { display: block; } + + a[role="menuitem"] & { + width: 8px; + height: 8px; + margin-right: 8px; + } } diff --git a/superset/assets/src/dashboard/util/constants.js b/superset/assets/src/dashboard/util/constants.js index d682687..ef2c8bb 100644 --- a/superset/assets/src/dashboard/util/constants.js +++ b/superset/assets/src/dashboard/util/constants.js @@ -41,3 +41,7 @@ export const DANGER_TOAST = 'DANGER_TOAST'; // undo-redo export const UNDO_LIMIT = 50; + +// save dash options +export const SAVE_TYPE_OVERWRITE = 'overwrite'; +export const SAVE_TYPE_NEWDASHBOARD = 'newDashboard'; diff --git a/superset/assets/src/modules/utils.js b/superset/assets/src/modules/utils.js index eb937bb..c5d4e75 100644 --- a/superset/assets/src/modules/utils.js +++ b/superset/assets/src/modules/utils.js @@ -198,7 +198,7 @@ export function slugify(string) { export function getAjaxErrorMsg(error) { const respJSON = error.responseJSON; - return (respJSON && respJSON.message) ? respJSON.message : + return (respJSON && respJSON.error) ? respJSON.error : error.responseText; }