diff --git a/superset-frontend/src/assets/images/dashboard.svg b/superset-frontend/src/assets/images/dashboard.svg new file mode 100644 index 0000000000000..161f93a98b419 --- /dev/null +++ b/superset-frontend/src/assets/images/dashboard.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + diff --git a/superset-frontend/src/components/EmptyState/index.tsx b/superset-frontend/src/components/EmptyState/index.tsx index 99a6ce7bc7160..e27c72894c15e 100644 --- a/superset-frontend/src/components/EmptyState/index.tsx +++ b/superset-frontend/src/components/EmptyState/index.tsx @@ -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; } export interface ImageContainerProps { - image: string | ReactNode; + image: ReactNode; size: EmptyStateSize; } @@ -103,6 +103,7 @@ const SmallDescription = styled(Description)` const ActionButton = styled(Button)` ${({ theme }) => css` margin-top: ${theme.gridUnit * 4}px; + z-index: 1; `} `; diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index bd6e663db68fd..577351a389dbc 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -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'; @@ -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'; @@ -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'; @@ -133,7 +134,7 @@ const StyledHeader = styled.div` grid-column: 2; grid-row: 1; position: sticky; - top: 0px; + top: 0; z-index: 100; `; @@ -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; @@ -187,6 +188,7 @@ const StyledDashboardContent = styled.div<{ .dashboard-builder-sidepane { width: ${BUILDER_SIDEPANEL_WIDTH}px; + z-index: 1; } .dashboard-component-chart-holder { @@ -208,6 +210,9 @@ const DashboardBuilder: FC = () => { const editMode = useSelector( state => state.dashboardState.editMode, ); + const canEdit = useSelector( + ({ dashboardInfo }) => dashboardInfo.dash_edit_perm, + ); const directPathToChild = useSelector( state => state.dashboardState.directPathToChild, ); @@ -374,6 +379,20 @@ const DashboardBuilder: FC = () => { `div > .filterStatusPopover.ant-popover{z-index: 101}`} `} /> + {!editMode && + !topLevelTabs && + dashboardLayout[DASHBOARD_GRID_ID]?.children?.length === 0 && ( + + )}
); +const DashboardEmptyStateContainer = styled.div` + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +`; + class DashboardGrid extends React.PureComponent { constructor(props) { super(props); @@ -140,82 +148,100 @@ class DashboardGrid extends React.PureComponent { const { isResizing, rowGuideTop } = this.state; return width < 100 ? null : ( -
-
- {/* make the area above components droppable */} - {editMode && ( - - {renderDraggableContentBottom} - - )} - - {gridComponent?.children?.map((id, index) => ( - - ))} - - {/* make the area below components droppable */} - {editMode && gridComponent?.children?.length > 0 && ( - - {renderDraggableContentTop} - - )} - - {isResizing && - Array(GRID_COLUMN_COUNT) - .fill(null) - .map((_, i) => ( -
- ))} - - {isResizing && rowGuideTop && ( -
+ {editMode && gridComponent?.children?.length === 0 && ( + + + + {t('Create a new chart')} + + } + buttonAction={() => { + window.location.assign('/chart/add'); }} + image="chart.svg" /> - )} + + )} +
+
+ {/* make the area above components droppable */} + {editMode && ( + + {renderDraggableContentBottom} + + )} + {gridComponent?.children?.map((id, index) => ( + + ))} + {/* make the area below components droppable */} + {editMode && gridComponent?.children?.length > 0 && ( + + {renderDraggableContentTop} + + )} + {isResizing && + Array(GRID_COLUMN_COUNT) + .fill(null) + .map((_, i) => ( +
+ ))} + {isResizing && rowGuideTop && ( +
+ )} +
-
+ ); } } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx index 6b07c2c7234ab..b8d5a4b6dc43c 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx @@ -44,6 +44,7 @@ import Loading from 'src/components/Loading'; import { getInitialDataMask } from 'src/dataMask/reducer'; import { URL_PARAMS } from 'src/constants'; import { getUrlParam } from 'src/utils/urlUtils'; +import { EmptyStateSmall } from 'src/components/EmptyState'; import { checkIsApplyDisabled, TabIds } from './utils'; import FilterSets from './FilterSets'; import { @@ -57,6 +58,7 @@ import { createFilterKey, updateFilterKey } from './keyValue'; import EditSection from './FilterSets/EditSection'; import Header from './Header'; import FilterControls from './FilterControls/FilterControls'; +import { RootState } from '../../../types'; export const FILTER_BAR_TEST_ID = 'filter-bar'; export const getFilterBarTestId = testWithId(FILTER_BAR_TEST_ID); @@ -135,6 +137,10 @@ const StyledTabs = styled(Tabs)` } `; +const FilterBarEmptyStateContainer = styled.div` + margin-top: ${({ theme }) => theme.gridUnit * 8}px; +`; + export interface FiltersBarProps { filtersOpen: boolean; toggleFiltersBar: any; @@ -168,6 +174,9 @@ const FilterBar: React.FC = ({ const dashboardId = useSelector( ({ dashboardInfo }) => dashboardInfo?.id, ); + const canEdit = useSelector( + ({ dashboardInfo }) => dashboardInfo.dash_edit_perm, + ); const handleFilterSelectionChange = useCallback( ( @@ -382,11 +391,26 @@ const FilterBar: React.FC = ({ ) : (
- + {filterValues.length === 0 ? ( + + + + ) : ( + + )}
)}