Skip to content

Commit

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

* Add empty state to native filters

* Add missing license

* Remove redundant string types from EmptyState
  • Loading branch information
kgabryje authored Feb 14, 2022
1 parent 42d97fb commit f8b3ece
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 90 deletions.
33 changes: 33 additions & 0 deletions superset-frontend/src/assets/images/dashboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions superset-frontend/src/components/EmptyState/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ export enum EmptyStateSize {
}

export interface EmptyStateSmallProps {
title: string | ReactNode;
description?: string | ReactNode;
image: string | ReactNode;
title: ReactNode;
description?: ReactNode;
image: ReactNode;
}

export interface EmptyStateProps extends EmptyStateSmallProps {
buttonText?: string;
buttonText?: ReactNode;
buttonAction?: React.MouseEventHandler<HTMLElement>;
}

export interface ImageContainerProps {
image: string | ReactNode;
image: ReactNode;
size: EmptyStateSize;
}

Expand Down Expand Up @@ -103,6 +103,7 @@ const SmallDescription = styled(Description)`
const ActionButton = styled(Button)`
${({ theme }) => css`
margin-top: ${theme.gridUnit * 4}px;
z-index: 1;
`}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
/* eslint-env browser */
import cx from 'classnames';
import React, { FC, useCallback, useMemo } from 'react';
import { JsonObject, styled, css } from '@superset-ui/core';
import { JsonObject, styled, css, t } from '@superset-ui/core';
import { Global } from '@emotion/react';
import { useDispatch, useSelector } from 'react-redux';
import ErrorBoundary from 'src/components/ErrorBoundary';
import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane';
import DashboardHeader from 'src/dashboard/containers/DashboardHeader';
Expand All @@ -30,7 +32,6 @@ import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
import getDirectPathToTabIndex from 'src/dashboard/util/getDirectPathToTabIndex';
import { URL_PARAMS } from 'src/constants';
import { useDispatch, useSelector } from 'react-redux';
import { getUrlParam } from 'src/utils/urlUtils';
import { DashboardLayout, RootState } from 'src/dashboard/types';
import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState';
Expand All @@ -48,7 +49,7 @@ import {
} from 'src/dashboard/util/constants';
import FilterBar from 'src/dashboard/components/nativeFilters/FilterBar';
import Loading from 'src/components/Loading';
import { Global } from '@emotion/react';
import { EmptyStateBig } from 'src/components/EmptyState';
import { useUiConfig } from 'src/components/UiConfigContext';
import { shouldFocusTabs, getRootLevelTabsComponent } from './utils';
import DashboardContainer from './DashboardContainer';
Expand Down Expand Up @@ -133,7 +134,7 @@ const StyledHeader = styled.div`
grid-column: 2;
grid-row: 1;
position: sticky;
top: 0px;
top: 0;
z-index: 100;
`;

Expand Down Expand Up @@ -163,7 +164,7 @@ const StyledDashboardContent = styled.div<{
.grid-container {
/* without this, the grid will not get smaller upon toggling the builder panel on */
width: 0px;
width: 0;
flex: 1;
position: relative;
margin-top: ${({ theme }) => theme.gridUnit * 6}px;
Expand All @@ -187,6 +188,7 @@ const StyledDashboardContent = styled.div<{
.dashboard-builder-sidepane {
width: ${BUILDER_SIDEPANEL_WIDTH}px;
z-index: 1;
}
.dashboard-component-chart-holder {
Expand All @@ -208,6 +210,9 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
const editMode = useSelector<RootState, boolean>(
state => state.dashboardState.editMode,
);
const canEdit = useSelector<RootState, boolean>(
({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
);
const directPathToChild = useSelector<RootState, string[]>(
state => state.dashboardState.directPathToChild,
);
Expand Down Expand Up @@ -374,6 +379,20 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
`div > .filterStatusPopover.ant-popover{z-index: 101}`}
`}
/>
{!editMode &&
!topLevelTabs &&
dashboardLayout[DASHBOARD_GRID_ID]?.children?.length === 0 && (
<EmptyStateBig
title={t('There are no charts added to this dashboard')}
description={
canEdit &&
t(
'Go to the edit mode to configure the dashboard and add charts',
)
}
image="dashboard.svg"
/>
)}
<div
data-test="dashboard-content"
className={cx('dashboard', editMode && 'dashboard--editing')}
Expand Down
176 changes: 101 additions & 75 deletions superset-frontend/src/dashboard/components/DashboardGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
*/
import React from 'react';
import PropTypes from 'prop-types';

import { styled, t } from '@superset-ui/core';
import { EmptyStateBig } from 'src/components/EmptyState';
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';

const propTypes = {
Expand All @@ -48,6 +48,14 @@ const renderDraggableContentTop = dropProps =>
<div className="drop-indicator drop-indicator--top" />
);

const DashboardEmptyStateContainer = styled.div`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;

class DashboardGrid extends React.PureComponent {
constructor(props) {
super(props);
Expand Down Expand Up @@ -140,82 +148,100 @@ class DashboardGrid extends React.PureComponent {
const { isResizing, rowGuideTop } = this.state;

return width < 100 ? null : (
<div className="dashboard-grid" ref={this.setGridRef}>
<div className="grid-content" data-test="grid-content">
{/* make the area above components droppable */}
{editMode && (
<DragDroppable
component={gridComponent}
depth={depth}
parentComponent={null}
index={0}
orientation="column"
onDrop={this.handleTopDropTargetDrop}
className="empty-droptarget"
editMode
>
{renderDraggableContentBottom}
</DragDroppable>
)}

{gridComponent?.children?.map((id, index) => (
<DashboardComponent
key={id}
id={id}
parentId={gridComponent.id}
depth={depth + 1}
index={index}
availableColumnCount={GRID_COLUMN_COUNT}
columnWidth={columnWidth}
isComponentVisible={isComponentVisible}
onResizeStart={this.handleResizeStart}
onResize={this.handleResize}
onResizeStop={this.handleResizeStop}
onChangeTab={this.handleChangeTab}
/>
))}

{/* make the area below components droppable */}
{editMode && gridComponent?.children?.length > 0 && (
<DragDroppable
component={gridComponent}
depth={depth}
parentComponent={null}
index={gridComponent.children.length}
orientation="column"
onDrop={handleComponentDrop}
className="empty-droptarget"
editMode
>
{renderDraggableContentTop}
</DragDroppable>
)}

{isResizing &&
Array(GRID_COLUMN_COUNT)
.fill(null)
.map((_, i) => (
<div
key={`grid-column-${i}`}
className="grid-column-guide"
style={{
left: i * GRID_GUTTER_SIZE + i * columnWidth,
width: columnWidth,
}}
/>
))}

{isResizing && rowGuideTop && (
<div
className="grid-row-guide"
style={{
top: rowGuideTop,
width,
<>
{editMode && gridComponent?.children?.length === 0 && (
<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"
/>
)}
</DashboardEmptyStateContainer>
)}
<div className="dashboard-grid" ref={this.setGridRef}>
<div className="grid-content" data-test="grid-content">
{/* make the area above components droppable */}
{editMode && (
<DragDroppable
component={gridComponent}
depth={depth}
parentComponent={null}
index={0}
orientation="column"
onDrop={this.handleTopDropTargetDrop}
className="empty-droptarget"
editMode
>
{renderDraggableContentBottom}
</DragDroppable>
)}
{gridComponent?.children?.map((id, index) => (
<DashboardComponent
key={id}
id={id}
parentId={gridComponent.id}
depth={depth + 1}
index={index}
availableColumnCount={GRID_COLUMN_COUNT}
columnWidth={columnWidth}
isComponentVisible={isComponentVisible}
onResizeStart={this.handleResizeStart}
onResize={this.handleResize}
onResizeStop={this.handleResizeStop}
onChangeTab={this.handleChangeTab}
/>
))}
{/* make the area below components droppable */}
{editMode && gridComponent?.children?.length > 0 && (
<DragDroppable
component={gridComponent}
depth={depth}
parentComponent={null}
index={gridComponent.children.length}
orientation="column"
onDrop={handleComponentDrop}
className="empty-droptarget"
editMode
>
{renderDraggableContentTop}
</DragDroppable>
)}
{isResizing &&
Array(GRID_COLUMN_COUNT)
.fill(null)
.map((_, i) => (
<div
key={`grid-column-${i}`}
className="grid-column-guide"
style={{
left: i * GRID_GUTTER_SIZE + i * columnWidth,
width: columnWidth,
}}
/>
))}
{isResizing && rowGuideTop && (
<div
className="grid-row-guide"
style={{
top: rowGuideTop,
width,
}}
/>
)}
</div>
</div>
</div>
</>
);
}
}
Expand Down
Loading

0 comments on commit f8b3ece

Please sign in to comment.