Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): Implement empty states for dashboard #18712

Merged
merged 4 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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