You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2019/06/08 01:00:00 UTC
[incubator-superset] 01/08: [dashboard] allow user re-order
top-level tabs (#7390)
This is an automated email from the ASF dual-hosted git repository.
michellet pushed a commit to branch release--0.33
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit 4a970cfb0cd310d3beb3fadeebfd9c8cb915600e
Author: Grace Guo <gr...@airbnb.com>
AuthorDate: Tue Apr 30 00:40:35 2019 -0700
[dashboard] allow user re-order top-level tabs (#7390)
(cherry picked from commit 9e703f399b78af1a198ba5d69353385b77dd701e)
---
.../dashboard/actions/dashboardLayout_spec.js | 67 +++++++++++++++++++++-
.../src/dashboard/actions/dashboardLayout.js | 29 +++++++++-
.../src/dashboard/components/DashboardBuilder.jsx | 7 ++-
.../dashboard/components/gridComponents/Tab.jsx | 5 --
4 files changed, 95 insertions(+), 13 deletions(-)
diff --git a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
index d57207d..0dfca9c 100644
--- a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
@@ -39,7 +39,10 @@ import {
} from '../../../../src/dashboard/actions/dashboardLayout';
import { setUnsavedChanges } from '../../../../src/dashboard/actions/dashboardState';
-import { addInfoToast } from '../../../../src/messageToasts/actions';
+import {
+ addWarningToast,
+ ADD_TOAST,
+} from '../../../../src/messageToasts/actions';
import {
DASHBOARD_GRID_TYPE,
@@ -334,7 +337,9 @@ describe('dashboardLayout actions', () => {
const thunk = handleComponentDrop(dropResult);
thunk(dispatch, getState);
- expect(dispatch.getCall(0).args[0].type).toEqual(addInfoToast('').type);
+ expect(dispatch.getCall(0).args[0].type).toEqual(
+ addWarningToast('').type,
+ );
expect(dispatch.callCount).toBe(1);
});
@@ -402,6 +407,64 @@ describe('dashboardLayout actions', () => {
expect(dispatch.callCount).toBe(2);
});
+
+ it('should dispatch a toast if drop top-level tab into nested tab', () => {
+ const { getState, dispatch } = setup({
+ dashboardLayout: {
+ present: {
+ [DASHBOARD_ROOT_ID]: {
+ children: ['TABS-ROOT_TABS'],
+ id: DASHBOARD_ROOT_ID,
+ type: 'ROOT',
+ },
+ 'TABS-ROOT_TABS': {
+ children: ['TAB-iMppmTOQy', 'TAB-rt1y8cQ6K9', 'TAB-X_pnCIwPN'],
+ id: 'TABS-ROOT_TABS',
+ meta: {},
+ parents: ['ROOT_ID'],
+ type: TABS_TYPE,
+ },
+ 'TABS-ROW_TABS': {
+ children: [
+ 'TAB-dKIDBT03bQ',
+ 'TAB-PtxY5bbTe',
+ 'TAB-Wc2P-yGMz',
+ 'TAB-U-xe_si7i',
+ ],
+ id: 'TABS-ROW_TABS',
+ meta: {},
+ parents: ['ROOT_ID', 'TABS-ROOT_TABS', 'TAB-X_pnCIwPN'],
+ type: TABS_TYPE,
+ },
+ },
+ },
+ });
+ const dropResult = {
+ source: {
+ id: 'TABS-ROOT_TABS',
+ index: 1,
+ type: TABS_TYPE,
+ },
+ destination: {
+ id: 'TABS-ROW_TABS',
+ index: 1,
+ type: TABS_TYPE,
+ },
+ dragging: {
+ id: 'TAB-rt1y8cQ6K9',
+ meta: { text: 'New Tab' },
+ type: 'TAB',
+ },
+ };
+
+ const thunk1 = handleComponentDrop(dropResult);
+ thunk1(dispatch, getState);
+
+ const thunk2 = dispatch.getCall(0).args[0];
+ thunk2(dispatch, getState);
+
+ expect(dispatch.getCall(1).args[0].type).toEqual(ADD_TOAST);
+ });
});
describe('undoLayoutAction', () => {
diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js
index 23cb90e..1b5b0c9 100644
--- a/superset/assets/src/dashboard/actions/dashboardLayout.js
+++ b/superset/assets/src/dashboard/actions/dashboardLayout.js
@@ -17,8 +17,9 @@
* under the License.
*/
import { ActionCreators as UndoActionCreators } from 'redux-undo';
+import { t } from '@superset-ui/translation';
-import { addInfoToast } from '../../messageToasts/actions';
+import { addWarningToast } from '../../messageToasts/actions';
import { setUnsavedChanges } from './dashboardState';
import { TABS_TYPE, ROW_TYPE } from '../util/componentTypes';
import {
@@ -153,8 +154,10 @@ export function handleComponentDrop(dropResult) {
if (overflowsParent) {
return dispatch(
- addInfoToast(
- `There is not enough space for this component. Try decreasing its width, or increasing the destination width.`,
+ addWarningToast(
+ t(
+ `There is not enough space for this component. Try decreasing its width, or increasing the destination width.`,
+ ),
),
);
}
@@ -162,12 +165,30 @@ export function handleComponentDrop(dropResult) {
const { source, destination } = dropResult;
const droppedOnRoot = destination && destination.id === DASHBOARD_ROOT_ID;
const isNewComponent = source.id === NEW_COMPONENTS_SOURCE_ID;
+ const dashboardRoot = getState().dashboardLayout.present[DASHBOARD_ROOT_ID];
+ const rootChildId =
+ dashboardRoot && dashboardRoot.children ? dashboardRoot.children[0] : '';
if (droppedOnRoot) {
dispatch(createTopLevelTabs(dropResult));
} else if (destination && isNewComponent) {
dispatch(createComponent(dropResult));
} else if (
+ // Add additional allow-to-drop logic for tag/tags source.
+ // We only allow
+ // - top-level tab => top-level tab: rearrange top-level tab order
+ // - nested tab => top-level tab: allow row tab become top-level tab
+ // Dashboard does not allow top-level tab become nested tab, to avoid
+ // nested tab inside nested tab.
+ source.type === TABS_TYPE &&
+ destination.type === TABS_TYPE &&
+ source.id === rootChildId &&
+ destination.id !== rootChildId
+ ) {
+ return dispatch(
+ addWarningToast(t(`Can not move top level tab into nested tabs`)),
+ );
+ } else if (
destination &&
source &&
!// ensure it has moved
@@ -176,6 +197,8 @@ export function handleComponentDrop(dropResult) {
dispatch(moveComponent(dropResult));
}
+ // call getState() again down here in case redux state is stale after
+ // previous dispatch(es)
const { dashboardLayout: undoableLayout } = getState();
// if we moved a child from a Tab or Row parent and it was the only child, delete the parent.
diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
index 345807d..f50410a 100644
--- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx
+++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
@@ -79,10 +79,11 @@ class DashboardBuilder extends React.Component {
const { dashboardLayout, directPathToChild } = props;
const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
const rootChildId = dashboardRoot.children[0];
- const topLevelTabs =
- rootChildId !== DASHBOARD_GRID_ID && dashboardLayout[rootChildId];
const tabIndex = findTabIndexByComponentId({
- currentComponent: topLevelTabs || dashboardLayout[DASHBOARD_ROOT_ID],
+ currentComponent:
+ rootChildId === DASHBOARD_GRID_ID
+ ? dashboardLayout[DASHBOARD_ROOT_ID]
+ : dashboardLayout[rootChildId],
directPathToChild,
});
diff --git a/superset/assets/src/dashboard/components/gridComponents/Tab.jsx b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
index 49a0f18..d441c8e 100644
--- a/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
@@ -26,7 +26,6 @@ import AnchorLink from '../../../components/AnchorLink';
import DeleteComponentModal from '../DeleteComponentModal';
import WithPopoverMenu from '../menu/WithPopoverMenu';
import { componentShape } from '../../util/propShapes';
-import { DASHBOARD_ROOT_DEPTH } from '../../util/constants';
export const RENDER_TAB = 'RENDER_TAB';
export const RENDER_TAB_CONTENT = 'RENDER_TAB_CONTENT';
@@ -219,10 +218,6 @@ export default class Tab extends React.PureComponent {
index={index}
depth={depth}
onDrop={this.handleDrop}
- // disable drag drop of top-level Tab's to prevent invalid nesting of a child in
- // itself, e.g. if a top-level Tab has a Tabs child, dragging the Tab into the Tabs would
- // reusult in circular children
- disableDragDrop={depth <= DASHBOARD_ROOT_DEPTH + 1}
editMode={editMode}
>
{({ dropIndicatorProps, dragSourceRef }) => (