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
The following commit(s) were added to refs/heads/dashboard-builder by this push: new 82a0e3e Dashboard save button (#4979) 82a0e3e is described below commit 82a0e3ee64e825e30bb71ae758f4e2dd3571b989 Author: Grace Guo <grace....@airbnb.com> 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={<span>{t('Set auto-refresh interval')}</span>} /> - <SaveModal - addSuccessToast={this.props.addSuccessToast} - addDangerToast={this.props.addDangerToast} - dashboardId={this.props.dashboardInfo.id} - dashboardTitle={dashboardTitle} - layout={layout} - filters={filters} - expandedSlices={expandedSlices} - onSave={onSave} - css={this.state.css} - triggerNode={<span>{editMode ? t('Save') : t('Save as')}</span>} - isMenuItem - /> {editMode && ( <MenuItem target="_blank" @@ -134,6 +118,14 @@ class Controls extends React.PureComponent { {editMode && ( <MenuItem href={emailLink}>{t('Email dashboard link')}</MenuItem> )} + {editMode && ( + <CssEditor + triggerNode={<span>{t('Edit CSS')}</span>} + initialCss={this.state.css} + templates={this.state.cssTemplates} + onChange={this.changeCss} + /> + )} </DropdownButton> </span> ); 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 ( <div className="dashboard-header"> <div className="dashboard-component-header header-large"> <EditableTitle title={dashboardTitle} - canEdit={this.props.dashboardInfo.dash_save_perm && editMode} + canEdit={userCanEdit && editMode} onSaveTitle={this.handleChangeText} showTooltip={false} /> <span className="favstar m-l-5"> <FaveStar - itemId={this.props.dashboardInfo.id} + itemId={dashboardInfo.id} fetchFaveStar={this.props.fetchFaveStar} saveFaveStar={this.props.saveFaveStar} isStarred={this.props.isStarred} @@ -127,7 +168,7 @@ class Header extends React.PureComponent { </span> </div> <ButtonToolbar> - {userCanEdit && ( + {userCanSaveAs && ( <ButtonGroup> {editMode && ( <Button @@ -161,44 +202,65 @@ class Header extends React.PureComponent { <Button bsSize="small" onClick={this.toggleEditMode} - bsStyle={editMode ? undefined : 'primary'} + bsStyle={hasUnsavedChanges ? 'primary' : undefined} + disabled={!userCanEdit} > - {editMode ? t('Switch to View Mode') : t('Edit Dashboard')} + {editMode ? t('Switch to view mode') : t('Edit dashboard')} </Button> ) : ( + <Button + bsSize="small" + bsStyle={hasUnsavedChanges ? 'primary' : undefined} + onClick={this.overwriteDashboard} + > + {t('Save changes')} + </Button> + )} + <DropdownButton + title="" + id="save-dash-split-button" + bsStyle={hasUnsavedChanges ? 'primary' : undefined} + bsSize="small" + pullRight + > <SaveModal addSuccessToast={this.props.addSuccessToast} addDangerToast={this.props.addDangerToast} - dashboardId={this.props.dashboardInfo.id} + dashboardId={dashboardInfo.id} dashboardTitle={dashboardTitle} + saveType={SAVE_TYPE_NEWDASHBOARD} layout={layout} filters={filters} expandedSlices={expandedSlices} + css={css} onSave={onSave} - // @TODO need to figure out css - css="" - triggerNode={ - <Button bsStyle="primary" bsSize="small"> - {t('Save changes')} - </Button> - } + isMenuItem + triggerNode={<span>{t('Save as')}</span>} + canOverwrite={userCanEdit} /> - )} + {hasUnsavedChanges && ( + <MenuItem eventKey="discard" onSelect={Header.discardChanges}> + {t('Discard changes')} + </MenuItem> + )} + </DropdownButton> </ButtonGroup> )} <Controls addSuccessToast={this.props.addSuccessToast} addDangerToast={this.props.addDangerToast} - dashboardInfo={this.props.dashboardInfo} + dashboardInfo={dashboardInfo} dashboardTitle={dashboardTitle} layout={layout} filters={filters} expandedSlices={expandedSlices} + css={css} onSave={onSave} onChange={onChange} forceRefreshAllCharts={this.forceRefresh} startPeriodicRender={this.props.startPeriodicRender} + updateCss={updateCss} editMode={editMode} /> </ButtonToolbar> diff --git a/superset/assets/src/dashboard/components/SaveModal.jsx b/superset/assets/src/dashboard/components/SaveModal.jsx index 4f05d2c..804674c 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={ <FormGroup> <Radio - value="overwrite" + value={SAVE_TYPE_OVERWRITE} onChange={this.handleSaveTypeChange} - checked={this.state.saveType === 'overwrite'} + checked={this.state.saveType === SAVE_TYPE_OVERWRITE} + disabled={!this.props.canOverwrite} > {t('Overwrite Dashboard [%s]', this.props.dashboardTitle)} </Radio> <hr /> <Radio - value="newDashboard" + value={SAVE_TYPE_NEWDASHBOARD} onChange={this.handleSaveTypeChange} - checked={this.state.saveType === 'newDashboard'} + checked={this.state.saveType === SAVE_TYPE_NEWDASHBOARD} > {t('Save as:')} </Radio> 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 ( <div className="slice-adder-container"> <div className="controls"> @@ -202,7 +211,7 @@ class SliceAdder extends React.Component { this.state.filteredSlices.length > 0 && ( <List width={376} - height={this.props.height} + height={slicesListHeight} rowCount={this.state.filteredSlices.length} rowHeight={136} rowRenderer={this.rowRenderer} diff --git a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx index 5326e0f..de8e653 100644 --- a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx +++ b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx @@ -11,6 +11,8 @@ const propTypes = { isCached: PropTypes.bool, isExpanded: PropTypes.bool, cachedDttm: PropTypes.string, + supersetCanExplore: PropTypes.bool, + sliceCanEdit: PropTypes.bool, toggleExpandSlice: PropTypes.func, forceRefresh: PropTypes.func, exploreChart: PropTypes.func, @@ -25,6 +27,8 @@ const defaultProps = { cachedDttm: null, isCached: false, isExpanded: false, + supersetCanExplore: false, + sliceCanEdit: false, }; const VerticalDotsTrigger = () => ( @@ -93,13 +97,19 @@ class SliceHeaderControls extends React.PureComponent { </MenuItem> )} - <MenuItem href={slice.edit_url} target="_blank"> - {t('Edit chart metadata')} - </MenuItem> + {this.props.sliceCanEdit && ( + <MenuItem href={slice.edit_url} target="_blank"> + {t('Edit chart metadata')} + </MenuItem> + )} <MenuItem onClick={this.exportCSV}>{t('Export CSV')}</MenuItem> - <MenuItem onClick={this.exploreChart}>{t('Explore chart')}</MenuItem> + {this.props.supersetCanExplore && ( + <MenuItem onClick={this.exploreChart}> + {t('Explore chart')} + </MenuItem> + )} </Dropdown.Menu> </Dropdown> ); 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; } -- To stop receiving notification emails like this one, please contact ccwilli...@apache.org.