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
This commit is contained in:
Maxime Beauchemin 2020-08-05 18:53:53 -07:00 committed by GitHub
parent 51a88cb19b
commit ece91928a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 417 additions and 614 deletions

View File

@ -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();
});
});

View File

@ -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');
});
});

View File

@ -0,0 +1,21 @@
<!--
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.
-->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14C13.1046 14 14 13.1046 14 12C14 10.8954 13.1046 10 12 10ZM5 10C3.89543 10 3 10.8954 3 12C3 13.1046 3.89543 14 5 14C6.10457 14 7 13.1046 7 12C7 10.8954 6.10457 10 5 10ZM19 10C17.8954 10 17 10.8954 17 12C17 13.1046 17.8954 14 19 14C20.1046 14 21 13.1046 21 12C21 10.8954 20.1046 10 19 10Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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();
});

View File

@ -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', () => {

View File

@ -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();

View File

@ -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,

View File

@ -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,
});
});

View File

@ -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,

View File

@ -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) {

View File

@ -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({
</div>
<div className="item">
<span>{t('Data source')} </span>
<span // eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: datasourceLink }}
/>
<a href={datasourceUrl}>{datasourceName}</a>
</div>
</div>
</div>

View File

@ -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 (
<Tabs className="m-t-10 tabs-components">
<Tab eventKey={1} title={t('Components')}>
<NewTabs />
<NewRow />
<NewColumn />
<NewHeader />
<NewMarkdown />
<NewDivider />
</Tab>
<Tab eventKey={2} title={t('Charts')} className="tab-charts">
<SliceAdder
height={height + (isSticky ? SUPERSET_HEADER_HEIGHT : 0)}
/>
</Tab>
</Tabs>
);
}
render() {
const {
topOffset,
builderPaneType,
showBuilderPane,
setColorSchemeAndUnsavedChanges,
colorScheme,
} = this.props;
const { topOffset } = this.props;
return (
<div
className="dashboard-builder-sidepane"
@ -66,22 +82,7 @@ class BuilderComponentPane extends React.PureComponent {
className="viewport"
style={isSticky ? { ...style, top: topOffset } : null}
>
{builderPaneType === BUILDER_PANE_TYPE.ADD_COMPONENTS && (
<InsertComponentPane
height={height}
isSticky={isSticky}
showBuilderPane={showBuilderPane}
/>
)}
{builderPaneType === BUILDER_PANE_TYPE.COLORS && (
<ColorComponentPane
showBuilderPane={showBuilderPane}
setColorSchemeAndUnsavedChanges={
setColorSchemeAndUnsavedChanges
}
colorScheme={colorScheme}
/>
)}
{this.renderTabs(height)}
</div>
)}
</Sticky>

View File

@ -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 (
<div className="slider-container">
<div className="component-layer slide-content">
<div className="dashboard-builder-sidepane-header">
<span>{'Color Settings'}</span>
<i
className="fa fa-times trigger"
onClick={this.onCloseButtonClick}
role="none"
/>
</div>
<div
className="panel-body"
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<ColorSchemeControl
description={t(
"Any color palette selected here will override the colors applied to this dashboard's individual charts",
)}
label={t('Color Scheme')}
name="color_scheme"
onChange={setColorSchemeAndUnsavedChanges}
value={colorScheme}
choices={this.getChoices}
schemes={this.getSchemes}
hovered={this.state.hovered}
/>
</div>
</div>
</div>
);
}
}
ColorComponentPane.propTypes = propTypes;
ColorComponentPane.defaultProps = defaultProps;
export default ColorComponentPane;

View File

@ -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 (
<ColorSchemeControl
description={t(
"Any color palette selected here will override the colors applied to this dashboard's individual charts",
)}
label={t('Color Scheme')}
name="color_scheme"
onChange={this.props.onChange}
value={colorScheme}
choices={this.choices}
clearable
schemes={this.schemes}
hovered={this.state.hovered}
/>
);
}
}
ColorSchemeControlWrapper.propTypes = propTypes;
ColorSchemeControlWrapper.defaultProps = defaultProps;
export default ColorSchemeControlWrapper;

View File

@ -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 {
)}
</ParentSize>
</div>
{editMode && builderPaneType !== BUILDER_PANE_TYPE.NONE && (
{editMode && (
<BuilderComponentPane
topOffset={HEADER_HEIGHT + (topLevelTabs ? TABS_HEIGHT : 0)}
showBuilderPane={showBuilderPane}
builderPaneType={builderPaneType}
setColorSchemeAndUnsavedChanges={setColorSchemeAndUnsavedChanges}
colorScheme={colorScheme}
/>

View File

@ -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 (
<StyledDashboardHeader className="dashboard-header">
@ -399,92 +387,63 @@ class Header extends React.PureComponent {
{userCanSaveAs && (
<div className="button-container">
{editMode && (
<Button
bsSize="small"
onClick={onUndo}
disabled={undoLength < 1}
bsStyle={this.state.emphasizeUndo ? 'primary' : undefined}
>
<div title="Undo" className="undo-action fa fa-reply" />
</Button>
)}
{editMode && (
<Button
bsSize="small"
onClick={onRedo}
disabled={redoLength < 1}
bsStyle={this.state.emphasizeRedo ? 'primary' : undefined}
>
<div title="Redo" className="redo-action fa fa-share" />
</Button>
)}
{editMode && (
<Button
active={builderPaneType === BUILDER_PANE_TYPE.ADD_COMPONENTS}
bsSize="small"
onClick={this.onInsertComponentsButtonClick}
>
{t('Components')}
</Button>
)}
{editMode && (
<Button
active={builderPaneType === BUILDER_PANE_TYPE.COLORS}
bsSize="small"
onClick={this.onColorsButtonClick}
>
{t('Colors')}
</Button>
)}
{editMode && (
<FilterScopeModal
triggerNode={<Button bsSize="small">{t('Filters')}</Button>}
/>
)}
{editMode && hasUnsavedChanges && (
<Button
bsSize="small"
bsStyle={popButton ? 'primary' : undefined}
onClick={this.overwriteDashboard}
>
{t('Save changes')}
</Button>
)}
{editMode && !hasUnsavedChanges && (
<Button
bsSize="small"
onClick={this.toggleEditMode}
bsStyle={undefined}
disabled={!userCanEdit}
>
{t('Switch to view mode')}
</Button>
)}
{editMode && (
<UndoRedoKeylisteners
onUndo={this.handleCtrlZ}
onRedo={this.handleCtrlY}
/>
<>
<ButtonGroup className="m-r-5">
<Button
bsSize="small"
onClick={onUndo}
disabled={undoLength < 1}
bsStyle={this.state.emphasizeUndo ? 'primary' : undefined}
>
<i title="Undo" className="undo-action fa fa-reply" />
&nbsp;
</Button>
<Button
bsSize="small"
onClick={onRedo}
disabled={redoLength < 1}
bsStyle={this.state.emphasizeRedo ? 'primary' : undefined}
>
&nbsp;
<i title="Redo" className="redo-action fa fa-share" />
</Button>
</ButtonGroup>
<Button
bsSize="small"
className="m-r-5"
onClick={this.constructor.discardChanges}
bsStyle="default"
>
{t('Discard Changes')}
</Button>
<Button
bsSize="small"
disabled={!hasUnsavedChanges}
bsStyle="primary"
onClick={this.overwriteDashboard}
>
{t('Save')}
</Button>
</>
)}
</div>
)}
{editMode && (
<UndoRedoKeylisteners
onUndo={this.handleCtrlZ}
onRedo={this.handleCtrlY}
/>
)}
{!editMode && !hasUnsavedChanges && (
<Button
bsSize="small"
{!editMode && (
<span
role="button"
tabIndex={0}
className="action-button"
onClick={this.toggleEditMode}
bsStyle={popButton ? 'primary' : undefined}
disabled={!userCanEdit}
>
{t('Edit dashboard')}
</Button>
<Icon name="pencil" />
</span>
)}
{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' },

View File

@ -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 (
<DropdownButton
title=""
title={<Icon name="more" />}
noCaret
id="save-dash-split-button"
bsStyle={hasUnsavedChanges ? 'primary' : undefined}
bsSize="small"
style={{ border: 'none', padding: 0, marginLeft: '4px' }}
pullRight
>
{userCanSave && (
<SaveModal
addSuccessToast={this.props.addSuccessToast}
addDangerToast={this.props.addDangerToast}
dashboardId={dashboardId}
dashboardTitle={dashboardTitle}
dashboardInfo={dashboardInfo}
saveType={SAVE_TYPE_NEWDASHBOARD}
layout={layout}
expandedSlices={expandedSlices}
refreshFrequency={refreshFrequency}
shouldPersistRefreshFrequency={shouldPersistRefreshFrequency}
customCss={customCss}
colorNamespace={colorNamespace}
colorScheme={colorScheme}
onSave={onSave}
isMenuItem
triggerNode={<span>{t('Save as')}</span>}
canOverwrite={userCanEdit}
/>
<>
<SaveModal
addSuccessToast={this.props.addSuccessToast}
addDangerToast={this.props.addDangerToast}
dashboardId={dashboardId}
dashboardTitle={dashboardTitle}
dashboardInfo={dashboardInfo}
saveType={SAVE_TYPE_NEWDASHBOARD}
layout={layout}
expandedSlices={expandedSlices}
refreshFrequency={refreshFrequency}
shouldPersistRefreshFrequency={shouldPersistRefreshFrequency}
customCss={customCss}
colorNamespace={colorNamespace}
colorScheme={colorScheme}
onSave={onSave}
isMenuItem
triggerNode={<span>{t('Save as')}</span>}
canOverwrite={userCanEdit}
/>
</>
)}
{hasUnsavedChanges && userCanSave && (
<div>
<MenuItem
eventKey="discard"
onSelect={HeaderActionsDropdown.discardChanges}
>
{t('Discard changes')}
</MenuItem>
</div>
)}
{userCanSave && <MenuItem divider />}
<MenuItem onClick={forceRefreshAllCharts} disabled={isLoading}>
{t('Force refresh dashboard')}
</MenuItem>
<RefreshIntervalModal
refreshFrequency={refreshFrequency}
refreshLimit={refreshLimit}
refreshWarning={refreshWarning}
onChange={this.changeRefreshInterval}
editMode={editMode}
triggerNode={
<span>
{editMode
? t('Set auto-refresh interval')
: t('Auto-refresh dashboard')}
</span>
}
/>
{editMode && (
<MenuItem onClick={this.props.showPropertiesModal}>
{t('Edit dashboard properties')}
</MenuItem>
)}
<URLShortLinkModal
url={getDashboardUrl(
window.location.pathname,
@ -223,14 +189,37 @@ class HeaderActionsDropdown extends React.PureComponent {
isMenuItem
triggerNode={<span>{t('Share dashboard')}</span>}
/>
<MenuItem onClick={forceRefreshAllCharts} disabled={isLoading}>
{t('Refresh dashboard')}
</MenuItem>
<MenuItem divider />
<RefreshIntervalModal
refreshFrequency={refreshFrequency}
refreshLimit={refreshLimit}
refreshWarning={refreshWarning}
onChange={this.changeRefreshInterval}
editMode={editMode}
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
/>
{editMode && (
<CssEditor
triggerNode={<span>{t('Edit CSS')}</span>}
initialCss={this.state.css}
templates={this.state.cssTemplates}
onChange={this.changeCss}
/>
<>
<FilterScopeModal
className="m-r-5"
triggerNode={
<MenuItem bsSize="small">{t('Set filter mapping')}</MenuItem>
}
/>
<MenuItem onClick={this.props.showPropertiesModal}>
{t('Edit dashboard properties')}
</MenuItem>
<CssEditor
triggerNode={<span>{t('Edit CSS')}</span>}
initialCss={this.state.css}
templates={this.state.cssTemplates}
onChange={this.changeCss}
/>
</>
)}
{!editMode && (

View File

@ -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 (
<div className={cx('slider-container', this.state.slideDirection)}>
<div className="component-layer slide-content">
<div className="dashboard-builder-sidepane-header">
<span>{t('Insert components')}</span>
<i
className="fa fa-times trigger"
onClick={this.onCloseButtonClick}
role="none"
/>
</div>
<div
className="new-component static"
role="none"
onClick={this.openSlicesPane}
>
<div className="new-component-placeholder fa fa-area-chart" />
<div className="new-component-label">
{t('Your charts & filters')}
</div>
<i className="fa fa-arrow-right trigger" />
</div>
<NewTabs />
<NewRow />
<NewColumn />
<NewHeader />
<NewMarkdown />
<NewDivider />
</div>
<div className="slices-layer slide-content">
<div
className="dashboard-builder-sidepane-header"
onClick={this.closeSlicesPane}
role="none"
>
<i className="fa fa-arrow-left trigger" />
<span>{t('Your charts and filters')}</span>
</div>
<SliceAdder
height={
this.props.height +
(this.props.isSticky ? SUPERSET_HEADER_HEIGHT : 0)
}
/>
</div>
</div>
);
}
}
InsertComponentPane.propTypes = propTypes;
export default InsertComponentPane;

View File

@ -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 (
<Modal show={this.props.show} onHide={this.props.onHide} bsSize="lg">
<form onSubmit={this.save}>
<form onSubmit={this.submit}>
<Modal.Header closeButton>
<Modal.Title>
<div>
@ -249,6 +277,13 @@ class PropertiesModal extends React.PureComponent {
)}
</p>
</Col>
<Col md={6}>
<h3 style={{ marginTop: '1em' }}>{t('Colors')}</h3>
<ColorSchemeControlWrapper
onChange={this.onColorSchemeChange}
colorScheme={values.colorScheme}
/>
</Col>
</Row>
<Row>
<Col md={12}>
@ -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}
</Button>
<Button type="button" bsSize="sm" onClick={this.props.onHide}>
<Button type="button" bsSize="sm" onClick={onHide}>
{t('Cancel')}
</Button>
<Dialog

View File

@ -22,7 +22,8 @@ import Select from 'src/components/Select';
import { t } from '@superset-ui/translation';
import { Alert, Button } from 'react-bootstrap';
import ModalTrigger from '../../components/ModalTrigger';
import ModalTrigger from 'src/components/ModalTrigger';
import FormLabel from 'src/components/FormLabel';
const propTypes = {
triggerNode: PropTypes.node.isRequired,
@ -96,7 +97,7 @@ class RefreshIntervalModal extends React.PureComponent {
modalTitle={t('Refresh Interval')}
modalBody={
<div>
{t('Choose the refresh frequency for this dashboard')}
<FormLabel>{t('Refresh frequency')}</FormLabel>
<Select
options={options}
value={this.state.refreshFrequency}
@ -115,10 +116,12 @@ class RefreshIntervalModal extends React.PureComponent {
}
modalFooter={
<>
<Button bsStyle="primary" onClick={this.onSave}>
<Button bsStyle="primary" bsSize="sm" onClick={this.onSave}>
{editMode ? t('Save') : t('Save for this session')}
</Button>
<Button onClick={this.onCancel}>{t('Cancel')}</Button>
<Button onClick={this.onCancel} bsSize="sm">
{t('Cancel')}
</Button>
</>
}
/>

View File

@ -20,7 +20,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DropdownButton, MenuItem } from 'react-bootstrap';
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
import { List } from 'react-virtualized';
import SearchInput, { createFilter } from 'react-search-input';
import { t } from '@superset-ui/translation';
@ -60,14 +60,9 @@ const KEYS_TO_SORT = [
];
const MARGIN_BOTTOM = 16;
const SIDEPANE_HEADER_HEIGHT = 55;
const SIDEPANE_HEADER_HEIGHT = 30;
const SLICE_ADDER_CONTROL_HEIGHT = 64;
const DEFAULT_CELL_HEIGHT = 136;
const cache = new CellMeasurerCache({
defaultHeight: DEFAULT_CELL_HEIGHT,
fixedWidth: true,
});
const DEFAULT_CELL_HEIGHT = 112;
class SliceAdder extends React.Component {
static sortByComparator(attr) {
@ -91,7 +86,6 @@ class SliceAdder extends React.Component {
sortBy: KEYS_TO_SORT.findIndex(item => item.key === 'changed_on'),
selectedSliceIdsSet: new Set(props.selectedSliceIds),
};
this.rowRenderer = this.rowRenderer.bind(this);
this.searchUpdated = this.searchUpdated.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
@ -159,7 +153,7 @@ class SliceAdder extends React.Component {
});
}
rowRenderer({ key, index, style, parent }) {
rowRenderer({ key, index, style }) {
const { filteredSlices, selectedSliceIdsSet } = this.state;
const cellData = filteredSlices[index];
const isSelected = selectedSliceIdsSet.has(cellData.slice_id);
@ -190,23 +184,16 @@ class SliceAdder extends React.Component {
style={{}}
>
{({ dragSourceRef }) => (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
<AddSliceCard
innerRef={dragSourceRef}
style={style}
sliceName={cellData.slice_name}
lastModified={cellData.changed_on_humanized}
visType={cellData.viz_type}
datasourceLink={cellData.datasource_link}
isSelected={isSelected}
/>
</CellMeasurer>
<AddSliceCard
innerRef={dragSourceRef}
style={style}
sliceName={cellData.slice_name}
lastModified={cellData.changed_on_humanized}
visType={cellData.viz_type}
datasourceUrl={cellData.datasource_url}
datasourceName={cellData.datasource_name}
isSelected={isSelected}
/>
)}
</DragDroppable>
);
@ -227,7 +214,6 @@ class SliceAdder extends React.Component {
onChange={this.searchUpdated}
onKeyPress={this.handleKeyPress}
/>
<DropdownButton
title={`Sort by ${KEYS_TO_SORT[this.state.sortBy].label}`}
onSelect={this.handleSelect}
@ -246,8 +232,7 @@ class SliceAdder extends React.Component {
width={376}
height={slicesListHeight}
rowCount={this.state.filteredSlices.length}
deferredMeasurementCache={cache}
rowHeight={cache.rowHeight}
rowHeight={DEFAULT_CELL_HEIGHT}
rowRenderer={this.rowRenderer}
searchTerm={this.state.searchTerm}
sortBy={this.state.sortBy}

View File

@ -72,9 +72,10 @@ function AddSliceDragPreview({ dragItem, slices, isDragging, currentOffset }) {
transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
}}
sliceName={slice.slice_name}
lastModified={slice.modified}
lastModified={slice.changed_on_humanized}
visType={slice.viz_type}
datasourceLink={slice.datasource_link}
datasourceUrl={slice.datasource_url}
datasourceName={slice.datasource_name}
/>
);
}

View File

@ -513,9 +513,11 @@ export default class FilterScopeSelector extends React.PureComponent {
</div>
<div className="dashboard-modal-actions-container">
<Button onClick={this.onClose}>{t('Close')}</Button>
<Button bsSize="sm" onClick={this.onClose}>
{t('Close')}
</Button>
{showSelector && (
<Button bsStyle="primary" onClick={this.onSave}>
<Button bsSize="sm" bsStyle="primary" onClick={this.onSave}>
{t('Save')}
</Button>
)}

View File

@ -36,7 +36,6 @@ function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState }) {
editMode: dashboardState.editMode,
showBuilderPane: dashboardState.showBuilderPane,
directPathToChild: dashboardState.directPathToChild,
builderPaneType: dashboardState.builderPaneType,
colorScheme: dashboardState.colorScheme,
};
}

View File

@ -30,6 +30,7 @@ import {
fetchFaveStar,
saveFaveStar,
savePublished,
setColorSchemeAndUnsavedChanges,
fetchCharts,
updateCss,
onChange,
@ -83,7 +84,8 @@ function mapStateToProps({
hasUnsavedChanges: !!dashboardState.hasUnsavedChanges,
maxUndoHistoryExceeded: !!dashboardState.maxUndoHistoryExceeded,
editMode: !!dashboardState.editMode,
builderPaneType: dashboardState.builderPaneType,
slug: dashboardInfo.slug,
metadata: dashboardInfo.metadata,
};
}
@ -97,6 +99,7 @@ function mapDispatchToProps(dispatch) {
onRedo: redoLayoutAction,
setEditMode,
showBuilderPane,
setColorSchemeAndUnsavedChanges,
fetchFaveStar,
saveFaveStar,
savePublished,

View File

@ -35,7 +35,6 @@ import {
SET_DIRECT_PATH,
SET_FOCUSED_FILTER_FIELD,
} from '../actions/dashboardState';
import { BUILDER_PANE_TYPE } from '../util/constants';
export default function dashboardStateReducer(state = {}, action) {
const actionHandlers = {
@ -70,9 +69,6 @@ export default function dashboardStateReducer(state = {}, action) {
return {
...state,
editMode: action.editMode,
builderPaneType: action.editMode
? BUILDER_PANE_TYPE.ADD_COMPONENTS
: BUILDER_PANE_TYPE.NONE,
};
},
[SET_MAX_UNDO_HISTORY_EXCEEDED]() {
@ -80,7 +76,7 @@ export default function dashboardStateReducer(state = {}, action) {
return { ...state, maxUndoHistoryExceeded };
},
[SHOW_BUILDER_PANE]() {
return { ...state, builderPaneType: action.builderPaneType };
return { ...state };
},
[SET_COLOR_SCHEME]() {
return {
@ -108,7 +104,6 @@ export default function dashboardStateReducer(state = {}, action) {
hasUnsavedChanges: false,
maxUndoHistoryExceeded: false,
editMode: false,
builderPaneType: BUILDER_PANE_TYPE.NONE,
updatedColorScheme: false,
};
},

View File

@ -31,7 +31,6 @@ import { getParam } from '../../modules/utils';
import { applyDefaultFormData } from '../../explore/store';
import { buildActiveFilters } from '../util/activeDashboardFilters';
import {
BUILDER_PANE_TYPE,
DASHBOARD_HEADER_ID,
GRID_DEFAULT_CHART_WIDTH,
GRID_COLUMN_COUNT,
@ -301,10 +300,6 @@ export default function (bootstrapData) {
colorScheme: dashboard.metadata.color_scheme,
editMode: dashboard.dash_edit_perm && editMode,
isPublished: dashboard.published,
builderPaneType:
dashboard.dash_edit_perm && editMode
? BUILDER_PANE_TYPE.ADD_COMPONENTS
: BUILDER_PANE_TYPE.NONE,
hasUnsavedChanges: false,
maxUndoHistoryExceeded: false,
},

View File

@ -94,11 +94,13 @@
.chart-card {
border: 1px solid @gray-light;
font-weight: @font-weight-light;
padding: 16px;
margin: 0 16px 16px 16px;
padding: 8px;
margin: 0 8px 8px 8px;
position: relative;
cursor: move;
background: fade(@lightest, @opacity-medium-light);
white-space: nowrap;
overflow: hidden;
&:hover {
background: @gray-bg;
@ -147,6 +149,7 @@
.slice-adder-container {
position: relative;
background-color: white;
min-height: 200px; /* for loader positioning */
.error-message {
@ -185,18 +188,4 @@
outline: none;
}
}
.color-scheme-container {
list-style: none;
margin: 0;
padding: 0;
display: flex;
align-items: center;
}
.color-scheme-container li {
flex-basis: 9px;
height: 10px;
margin: 9px 1px;
}
}

View File

@ -115,19 +115,6 @@ body {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
& > :nth-child(3) {
border-radius: @border-radius-normal 0px 0px @border-radius-normal;
border-right: none;
}
& > :nth-child(4) {
border-radius: 0px @border-radius-normal @border-radius-normal 0px;
}
& > :not(:nth-child(3)):not(:last-child) {
margin-right: 8px;
}
}
}

View File

@ -63,13 +63,6 @@ export const SAVE_TYPE_NEWDASHBOARD = 'newDashboard';
// could be overwritten by server-side config
export const DASHBOARD_POSITION_DATA_LIMIT = 65535;
// Dashboard pane types
export const BUILDER_PANE_TYPE = {
NONE: 'NONE',
ADD_COMPONENTS: 'ADD_COMPONENTS',
COLORS: 'COLORS',
};
// filter indicators display length
export const FILTER_INDICATORS_DISPLAY_LENGTH = 3;

View File

@ -102,7 +102,6 @@ export const dashboardStatePropShape = PropTypes.shape({
expandedSlices: PropTypes.object,
editMode: PropTypes.bool,
isPublished: PropTypes.bool.isRequired,
builderPaneType: PropTypes.string.isRequired,
colorNamespace: PropTypes.string,
colorScheme: PropTypes.string,
updatedColorScheme: PropTypes.bool,

View File

@ -22,6 +22,7 @@ import { isFunction } from 'lodash';
import { CreatableSelect } from 'src/components/Select';
import ControlHeader from '../ControlHeader';
import TooltipWrapper from '../../../components/TooltipWrapper';
import './ColorSchemeControl.less';
const propTypes = {
description: PropTypes.string,
@ -29,6 +30,7 @@ const propTypes = {
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.string,
clearable: PropTypes.bool,
default: PropTypes.string,
choices: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.array),
@ -41,6 +43,7 @@ const propTypes = {
const defaultProps = {
choices: [],
schemes: {},
clearable: false,
onChange: () => {},
};
@ -111,7 +114,7 @@ export default class ColorSchemeControl extends React.PureComponent {
options,
value: this.props.value,
autosize: false,
clearable: false,
clearable: this.props.clearable,
onChange: this.onChange,
optionRenderer: this.renderOption,
valueRenderer: this.renderOption,

View File

@ -0,0 +1,31 @@
/**
* 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.
*/
.color-scheme-container {
list-style: none;
margin: 0;
padding: 0;
display: flex;
align-items: center;
li {
flex-basis: 9px;
height: 10px;
margin: 9px 1px;
}
}

View File

@ -61,20 +61,6 @@
background-color: transparent !important;
}
.color-scheme-container {
list-style: none;
margin: 0;
padding: 0;
display: flex;
align-items: center;
li {
flex-basis: 9px;
height: 10px;
margin: 9px 1px;
}
}
.control-panel-section {
.panel-body {
margin-left: 15px;

View File

@ -495,9 +495,8 @@ class DashboardList extends React.PureComponent<Props, State> {
{dashboardToEdit && (
<PropertiesModal
dashboardId={dashboardToEdit.id}
onDashboardSave={this.handleDashboardEdit}
onHide={() => this.setState({ dashboardToEdit: null })}
show
onSubmit={this.handleDashboardEdit}
/>
)}
<ListView

View File

@ -234,6 +234,7 @@ table,
& > li > a:hover,
& > li > a:focus {
background-image: none;
text-decoration: none;
}
}

View File

@ -152,10 +152,9 @@ class DashboardDAO(BaseDAO):
key: v for key, v in default_filters_data.items() if int(key) in slice_ids
}
md["default_filters"] = json.dumps(applicable_filters)
md["color_scheme"] = data.get("color_scheme")
if data.get("color_namespace"):
md["color_namespace"] = data.get("color_namespace")
if data.get("color_scheme"):
md["color_scheme"] = data.get("color_scheme")
if data.get("label_colors"):
md["label_colors"] = data.get("label_colors")
dashboard.json_metadata = json.dumps(md)

View File

@ -104,7 +104,7 @@ class DashboardJSONMetadataSchema(Schema):
default_filters = fields.Str()
stagger_refresh = fields.Boolean()
stagger_time = fields.Integer()
color_scheme = fields.Str()
color_scheme = fields.Str(allow_none=True)
label_colors = fields.Dict()

View File

@ -84,6 +84,7 @@ class SliceAsync(SliceModelView): # pylint: disable=too-many-ancestors
"creator",
"datasource_id",
"datasource_link",
"datasource_url",
"datasource_name_text",
"datasource_type",
"description",