Skip to content

Commit

Permalink
feat(dashboard): Implement empty states for empty tabs (#19408)
Browse files Browse the repository at this point in the history
* feat(dashboard): Implement empty states in empty tabs

* Change button to in text link

* Add edit dashboard button to dashboard empty state

* Add tests

* Fix test
  • Loading branch information
kgabryje authored Apr 1, 2022
1 parent 11bf0d0 commit fc8cb22
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 30 deletions.
9 changes: 9 additions & 0 deletions superset-frontend/src/components/EmptyState/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ const EmptyStateContainer = styled.div`
& .ant-empty-image svg {
width: auto;
}
& a,
& span[role='button'] {
color: inherit;
text-decoration: underline;
&:hover {
color: ${theme.colors.grayscale.base};
}
}
`}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ import getDirectPathToTabIndex from 'src/dashboard/util/getDirectPathToTabIndex'
import { URL_PARAMS } from 'src/constants';
import { getUrlParam } from 'src/utils/urlUtils';
import { DashboardLayout, RootState } from 'src/dashboard/types';
import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState';
import {
setDirectPathToChild,
setEditMode,
} from 'src/dashboard/actions/dashboardState';
import { useElementOnScreen } from 'src/hooks/useElementOnScreen';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import {
Expand Down Expand Up @@ -398,6 +401,8 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
'Go to the edit mode to configure the dashboard and add charts',
)
}
buttonText={canEdit && t('Edit the dashboard')}
buttonAction={() => dispatch(setEditMode(true))}
image="dashboard.svg"
/>
)}
Expand Down
83 changes: 65 additions & 18 deletions superset-frontend/src/dashboard/components/DashboardGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { componentShape } from '../util/propShapes';
import DashboardComponent from '../containers/DashboardComponent';
import DragDroppable from './dnd/DragDroppable';
import { GRID_GUTTER_SIZE, GRID_COLUMN_COUNT } from '../util/constants';
import { TAB_TYPE } from '../util/componentTypes';

const propTypes = {
depth: PropTypes.number.isRequired,
Expand Down Expand Up @@ -137,36 +138,82 @@ class DashboardGrid extends React.PureComponent {
gridComponent,
handleComponentDrop,
depth,
editMode,
width,
isComponentVisible,
editMode,
canEdit,
setEditMode,
} = this.props;
const columnPlusGutterWidth =
(width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT;

const columnWidth = columnPlusGutterWidth - GRID_GUTTER_SIZE;
const { isResizing, rowGuideTop } = this.state;

const shouldDisplayEmptyState = gridComponent?.children?.length === 0;
const shouldDisplayTopLevelTabEmptyState =
shouldDisplayEmptyState && gridComponent.type === TAB_TYPE;

const dashboardEmptyState = editMode && (
<EmptyStateBig
title={t('Drag and drop components and charts to the dashboard')}
description={t(
'You can create a new chart or use existing ones from the panel on the right',
)}
buttonText={
<>
<i className="fa fa-plus" />
{t('Create a new chart')}
</>
}
buttonAction={() => {
window.open('/chart/add', '_blank', 'noopener noreferrer');
}}
image="chart.svg"
/>
);

const topLevelTabEmptyState = editMode ? (
<EmptyStateBig
title={t('Drag and drop components to this tab')}
description={t(
`You can create a new chart or use existing ones from the panel on the right`,
)}
buttonText={
<>
<i className="fa fa-plus" />
{t('Create a new chart')}
</>
}
buttonAction={() => {
window.open('/chart/add', '_blank', 'noopener noreferrer');
}}
image="chart.svg"
/>
) : (
<EmptyStateBig
title={t('There are no components added to this tab')}
description={
canEdit && t('You can add the components in the edit mode.')
}
buttonText={canEdit && t('Edit the dashboard')}
buttonAction={
canEdit &&
(() => {
setEditMode(true);
})
}
image="chart.svg"
/>
);

return width < 100 ? null : (
<>
{editMode && gridComponent?.children?.length === 0 && (
{shouldDisplayEmptyState && (
<DashboardEmptyStateContainer>
<EmptyStateBig
title={t('Drag and drop components and charts to the dashboard')}
description={t(
'You can create new charts or use existing ones from the panel on the right',
)}
buttonText={
<>
<i className="fa fa-plus" />
{t('Create a new chart')}
</>
}
buttonAction={() => {
window.location.assign('/chart/add');
}}
image="chart.svg"
/>
{shouldDisplayTopLevelTabEmptyState
? topLevelTabEmptyState
: dashboardEmptyState}
</DashboardEmptyStateContainer>
)}
<div className="dashboard-grid" ref={this.setGridRef}>
Expand Down
67 changes: 65 additions & 2 deletions superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { styled } from '@superset-ui/core';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { styled, t } from '@superset-ui/core';

import { EmptyStateMedium } from 'src/components/EmptyState';
import { setEditMode } from 'src/dashboard/actions/dashboardState';
import DashboardComponent from '../../containers/DashboardComponent';
import DragDroppable from '../dnd/DragDroppable';
import EditableTitle from '../../../components/EditableTitle';
Expand All @@ -40,6 +44,7 @@ const propTypes = {
renderType: PropTypes.oneOf([RENDER_TAB, RENDER_TAB_CONTENT]).isRequired,
onDropOnTab: PropTypes.func,
editMode: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
filters: PropTypes.object.isRequired,

// grid related
Expand All @@ -53,6 +58,7 @@ const propTypes = {
handleComponentDrop: PropTypes.func.isRequired,
updateComponents: PropTypes.func.isRequired,
setDirectPathToChild: PropTypes.func.isRequired,
setEditMode: PropTypes.func.isRequired,
};

const defaultProps = {
Expand Down Expand Up @@ -85,7 +91,7 @@ const renderDraggableContentTop = dropProps =>
<div className="drop-indicator drop-indicator--top" />
);

export default class Tab extends React.PureComponent {
class Tab extends React.PureComponent {
constructor(props) {
super(props);
this.handleChangeText = this.handleChangeText.bind(this);
Expand Down Expand Up @@ -143,8 +149,11 @@ export default class Tab extends React.PureComponent {
onResizeStop,
editMode,
isComponentVisible,
canEdit,
setEditMode,
} = this.props;

const shouldDisplayEmptyState = tabComponent.children.length === 0;
return (
<div className="dashboard-component-tabs-content">
{/* Make top of tab droppable */}
Expand All @@ -162,6 +171,43 @@ export default class Tab extends React.PureComponent {
{renderDraggableContentTop}
</DragDroppable>
)}
{shouldDisplayEmptyState && (
<EmptyStateMedium
title={
editMode
? t('Drag and drop components to this tab')
: t('There are no components added to this tab')
}
description={
canEdit &&
(editMode ? (
<span>
{t('You can')}{' '}
<a
href="/chart/add"
rel="noopener noreferrer"
target="_blank"
>
{t('create a new chart')}
</a>{' '}
{t('or use existing ones from the panel on the right')}
</span>
) : (
<span>
{t('You can add the components in the')}{' '}
<span
role="button"
tabIndex={0}
onClick={() => setEditMode(true)}
>
{t('edit mode')}
</span>
</span>
))
}
image="chart.svg"
/>
)}
{tabComponent.children.map((componentId, componentIndex) => (
<DashboardComponent
key={componentId}
Expand Down Expand Up @@ -262,3 +308,20 @@ export default class Tab extends React.PureComponent {

Tab.propTypes = propTypes;
Tab.defaultProps = defaultProps;

function mapStateToProps(state) {
return {
canEdit: state.dashboardInfo.dash_edit_perm,
};
}

function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
setEditMode,
},
dispatch,
);
}

export default connect(mapStateToProps, mapDispatchToProps)(Tab);
Loading

0 comments on commit fc8cb22

Please sign in to comment.