{showTitlePanelItems && (
diff --git a/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.tsx b/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.tsx
index cd741e5c338ba..7beec04ffd7a3 100644
--- a/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.tsx
+++ b/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.tsx
@@ -243,7 +243,11 @@ export default function HeaderReportDropDown({
triggerNode.closest('.action-button')
}
>
-
+
@@ -253,7 +257,7 @@ export default function HeaderReportDropDown({
role="button"
title={t('Schedule email report')}
tabIndex={0}
- className="action-button"
+ className="action-button action-schedule-report"
onClick={() => setShowModal(true)}
>
diff --git a/superset-frontend/src/dashboard/components/Header/Header.test.tsx b/superset-frontend/src/dashboard/components/Header/Header.test.tsx
index e5851fb2d500b..730596a9f4b2b 100644
--- a/superset-frontend/src/dashboard/components/Header/Header.test.tsx
+++ b/superset-frontend/src/dashboard/components/Header/Header.test.tsx
@@ -122,7 +122,7 @@ function setup(props: HeaderProps, initialState = {}) {
async function openActionsDropdown() {
const btn = screen.getByRole('img', { name: 'more-horiz' });
userEvent.click(btn);
- expect(await screen.findByRole('menu')).toBeInTheDocument();
+ expect(await screen.findByTestId('header-actions-menu')).toBeInTheDocument();
}
test('should render', () => {
@@ -134,7 +134,9 @@ test('should render', () => {
test('should render the title', () => {
const mockedProps = createProps();
setup(mockedProps);
- expect(screen.getByText('Dashboard Title')).toBeInTheDocument();
+ expect(screen.getByTestId('editable-title')).toHaveTextContent(
+ 'Dashboard Title',
+ );
});
test('should render the editable title', () => {
@@ -161,21 +163,30 @@ test('should render the "Draft" status', () => {
});
test('should publish', () => {
- setup(editableProps);
+ const mockedProps = createProps();
+ const canEditProps = {
+ ...mockedProps,
+ dashboardInfo: {
+ ...mockedProps.dashboardInfo,
+ dash_edit_perm: true,
+ dash_save_perm: true,
+ },
+ };
+ setup(canEditProps);
const draft = screen.getByText('Draft');
- expect(editableProps.savePublished).not.toHaveBeenCalled();
+ expect(mockedProps.savePublished).toHaveBeenCalledTimes(0);
userEvent.click(draft);
- expect(editableProps.savePublished).toHaveBeenCalledTimes(1);
+ expect(mockedProps.savePublished).toHaveBeenCalledTimes(1);
});
test('should render the "Undo" action as disabled', () => {
setup(editableProps);
- expect(screen.getByTitle('Undo').parentElement).toBeDisabled();
+ expect(screen.getByTestId('undo-action').parentElement).toBeDisabled();
});
test('should undo', () => {
setup(undoProps);
- const undo = screen.getByTitle('Undo');
+ const undo = screen.getByTestId('undo-action');
expect(undoProps.onUndo).not.toHaveBeenCalled();
userEvent.click(undo);
expect(undoProps.onUndo).toHaveBeenCalledTimes(1);
@@ -191,12 +202,12 @@ test('should undo with key listener', () => {
test('should render the "Redo" action as disabled', () => {
setup(editableProps);
- expect(screen.getByTitle('Redo').parentElement).toBeDisabled();
+ expect(screen.getByTestId('redo-action').parentElement).toBeDisabled();
});
test('should redo', () => {
setup(redoProps);
- const redo = screen.getByTitle('Redo');
+ const redo = screen.getByTestId('redo-action');
expect(redoProps.onRedo).not.toHaveBeenCalled();
userEvent.click(redo);
expect(redoProps.onRedo).toHaveBeenCalledTimes(1);
@@ -212,7 +223,7 @@ test('should redo with key listener', () => {
test('should render the "Discard changes" button', () => {
setup(editableProps);
- expect(screen.getByText('Discard changes')).toBeInTheDocument();
+ expect(screen.getByText('Discard')).toBeInTheDocument();
});
test('should render the "Save" button as disabled', () => {
@@ -297,8 +308,8 @@ test('should toggle the edit mode', () => {
},
};
setup(canEditProps);
- const editDashboard = screen.getByTitle('Edit dashboard');
- expect(screen.queryByTitle('Edit dashboard')).toBeInTheDocument();
+ const editDashboard = screen.getByText('Edit dashboard');
+ expect(screen.queryByText('Edit dashboard')).toBeInTheDocument();
userEvent.click(editDashboard);
expect(mockedProps.logEvent).toHaveBeenCalled();
});
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
index 57fe7a1333973..eb3c6aeb4e973 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
@@ -29,7 +29,7 @@ import HeaderActionsDropdown from '.';
const createProps = () => ({
addSuccessToast: jest.fn(),
addDangerToast: jest.fn(),
- customCss: '#save-dash-split-button{margin-left: 100px;}',
+ customCss: '.ant-menu {margin-left: 100px;}',
dashboardId: 1,
dashboardInfo: {
id: 1,
@@ -59,7 +59,10 @@ const createProps = () => ({
userCanEdit: false,
userCanSave: false,
userCanShare: false,
+ userCanCurate: false,
lastModifiedTime: 0,
+ isDropdownVisible: true,
+ dataMask: {},
});
const editModeOnProps = {
...createProps(),
@@ -67,50 +70,31 @@ const editModeOnProps = {
};
function setup(props: HeaderDropdownProps) {
- return (
+ return render(
-
+ ,
+ { useRedux: true },
);
}
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
-async function openDropdown() {
- const btn = screen.getByRole('img', { name: 'more-horiz' });
- userEvent.click(btn);
- expect(await screen.findByRole('menu')).toBeInTheDocument();
-}
-
test('should render', () => {
const mockedProps = createProps();
- const { container } = render(setup(mockedProps));
+ const { container } = setup(mockedProps);
expect(container).toBeInTheDocument();
});
test('should render the dropdown button', () => {
const mockedProps = createProps();
- render(setup(mockedProps));
+ setup(mockedProps);
expect(screen.getByRole('button')).toBeInTheDocument();
});
-test('should render the dropdown icon', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- expect(screen.getByRole('img', { name: 'more-horiz' })).toBeInTheDocument();
-});
-
-test('should open the dropdown', async () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- await openDropdown();
- expect(await screen.findByRole('menu')).toBeInTheDocument();
-});
-
test('should render the menu items', async () => {
const mockedProps = createProps();
- render(setup(mockedProps));
- await openDropdown();
+ setup(mockedProps);
expect(screen.getAllByRole('menuitem')).toHaveLength(4);
expect(screen.getByText('Refresh dashboard')).toBeInTheDocument();
expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument();
@@ -119,13 +103,11 @@ test('should render the menu items', async () => {
});
test('should render the menu items in edit mode', async () => {
- render(setup(editModeOnProps));
- await openDropdown();
- expect(screen.getAllByRole('menuitem')).toHaveLength(5);
- expect(screen.getByText('Refresh dashboard')).toBeInTheDocument();
+ setup(editModeOnProps);
+ expect(screen.getAllByRole('menuitem')).toHaveLength(4);
expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument();
expect(screen.getByText('Set filter mapping')).toBeInTheDocument();
- expect(screen.getByText('Edit dashboard properties')).toBeInTheDocument();
+ expect(screen.getByText('Edit properties')).toBeInTheDocument();
expect(screen.getByText('Edit CSS')).toBeInTheDocument();
});
@@ -135,10 +117,9 @@ test('should show the share actions', async () => {
...mockedProps,
userCanShare: true,
};
- render(setup(canShareProps));
- await openDropdown();
- expect(screen.getByText('Copy permalink to clipboard')).toBeInTheDocument();
- expect(screen.getByText('Share permalink by email')).toBeInTheDocument();
+ setup(canShareProps);
+
+ expect(screen.getByText('Share')).toBeInTheDocument();
});
test('should render the "Save Modal" when user can save', async () => {
@@ -147,15 +128,13 @@ test('should render the "Save Modal" when user can save', async () => {
...mockedProps,
userCanSave: true,
};
- render(setup(canSaveProps));
- await openDropdown();
+ setup(canSaveProps);
expect(screen.getByText('Save as')).toBeInTheDocument();
});
test('should NOT render the "Save Modal" menu item when user cannot save', async () => {
const mockedProps = createProps();
- render(setup(mockedProps));
- await openDropdown();
+ setup(mockedProps);
expect(screen.queryByText('Save as')).not.toBeInTheDocument();
});
@@ -165,43 +144,41 @@ test('should render the "Refresh dashboard" menu item as disabled when loading',
...mockedProps,
isLoading: true,
};
- render(setup(loadingProps));
- await openDropdown();
+ setup(loadingProps);
expect(screen.getByText('Refresh dashboard')).toHaveClass(
- 'ant-dropdown-menu-item-disabled',
+ 'ant-menu-item-disabled',
);
});
test('should NOT render the "Refresh dashboard" menu item as disabled', async () => {
const mockedProps = createProps();
- render(setup(mockedProps));
- await openDropdown();
+ setup(mockedProps);
expect(screen.getByText('Refresh dashboard')).not.toHaveClass(
- 'ant-dropdown-menu-item-disabled',
+ 'ant-menu-item-disabled',
);
});
test('should render with custom css', () => {
const mockedProps = createProps();
const { customCss } = mockedProps;
- render(setup(mockedProps));
+ setup(mockedProps);
injectCustomCss(customCss);
- expect(screen.getByRole('button')).toHaveStyle('margin-left: 100px');
+ expect(screen.getByTestId('header-actions-menu')).toHaveStyle(
+ 'margin-left: 100px',
+ );
});
test('should refresh the charts', async () => {
const mockedProps = createProps();
- render(setup(mockedProps));
- await openDropdown();
+ setup(mockedProps);
userEvent.click(screen.getByText('Refresh dashboard'));
expect(mockedProps.forceRefreshAllCharts).toHaveBeenCalledTimes(1);
expect(mockedProps.addSuccessToast).toHaveBeenCalledTimes(1);
});
test('should show the properties modal', async () => {
- render(setup(editModeOnProps));
- await openDropdown();
- userEvent.click(screen.getByText('Edit dashboard properties'));
+ setup(editModeOnProps);
+ userEvent.click(screen.getByText('Edit properties'));
expect(editModeOnProps.showPropertiesModal).toHaveBeenCalledTimes(1);
});
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
index ad3dd91ec7ee5..a7860af30f378 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
@@ -19,16 +19,15 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { styled, SupersetClient, t } from '@superset-ui/core';
+import { SupersetClient, t } from '@superset-ui/core';
import { Menu } from 'src/components/Menu';
-import { NoAnimationDropdown } from 'src/components/Dropdown';
-import Icons from 'src/components/Icons';
import { URL_PARAMS } from 'src/constants';
import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
import CssEditor from 'src/dashboard/components/CssEditor';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
import SaveModal from 'src/dashboard/components/SaveModal';
+import HeaderReportDropdown from 'src/components/ReportModal/HeaderReportDropdown';
import injectCustomCss from 'src/dashboard/util/injectCustomCss';
import { SAVE_TYPE_NEWDASHBOARD } from 'src/dashboard/util/constants';
import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
@@ -91,15 +90,9 @@ const MENU_KEYS = {
DOWNLOAD_AS_IMAGE: 'download-as-image',
TOGGLE_FULLSCREEN: 'toggle-fullscreen',
MANAGE_EMBEDDED: 'manage-embedded',
+ MANAGE_EMAIL_REPORT: 'manage-email-report',
};
-const DropdownButton = styled.div`
- margin-left: ${({ theme }) => theme.gridUnit * 2.5}px;
- span {
- color: ${({ theme }) => theme.colors.grayscale.base};
- }
-`;
-
const SCREENSHOT_NODE_SELECTOR = '.dashboard';
class HeaderActionsDropdown extends React.PureComponent {
@@ -112,11 +105,13 @@ class HeaderActionsDropdown extends React.PureComponent {
this.state = {
css: props.customCss,
cssTemplates: [],
+ showReportSubMenu: null,
};
this.changeCss = this.changeCss.bind(this);
this.changeRefreshInterval = this.changeRefreshInterval.bind(this);
this.handleMenuClick = this.handleMenuClick.bind(this);
+ this.setShowReportSubMenu = this.setShowReportSubMenu.bind(this);
}
UNSAFE_componentWillMount() {
@@ -144,6 +139,12 @@ class HeaderActionsDropdown extends React.PureComponent {
}
}
+ setShowReportSubMenu(show) {
+ this.setState({
+ showReportSubMenu: show,
+ });
+ }
+
changeCss(css) {
this.props.onChange();
this.props.updateCss(css);
@@ -224,6 +225,9 @@ class HeaderActionsDropdown extends React.PureComponent {
addSuccessToast,
addDangerToast,
filterboxMigrationState,
+ setIsDropdownVisible,
+ isDropdownVisible,
+ ...rest
} = this.props;
const emailTitle = t('Superset dashboard');
@@ -236,12 +240,47 @@ class HeaderActionsDropdown extends React.PureComponent {
hash: window.location.hash,
});
- const menu = (
-