mirror of https://github.com/apache/superset.git
fix: Dropdown menu not closing on Dashboard view (#11671)
This commit is contained in:
parent
5cb6c25ca0
commit
3ad65bc163
|
@ -70,27 +70,24 @@ describe('Dashboard top-level controls', () => {
|
|||
.find('.world_map')
|
||||
.should('be.exist');
|
||||
cy.get(`#slice_${mapId}-controls`).click();
|
||||
cy.get(`#slice_${mapId}-controls`)
|
||||
.next()
|
||||
.find('[data-test="dashboard-slice-refresh-tooltip"]')
|
||||
.trigger('click', { force: true });
|
||||
cy.get(`[data-test="slice_${mapId}-menu"]`)
|
||||
.find('[data-test="refresh-dashboard-menu-item"]')
|
||||
.click({ force: true });
|
||||
|
||||
// not allow dashboard level force refresh when any chart is loading
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
'have.class',
|
||||
'ant-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
// not allow chart level force refresh when it is loading
|
||||
cy.get(`#slice_${mapId}-controls`)
|
||||
.next()
|
||||
.find('[data-test="dashboard-slice-refresh-tooltip"]')
|
||||
.parent()
|
||||
.should('have.class', 'ant-menu-item-disabled');
|
||||
cy.get(`[data-test="slice_${mapId}-menu"]`)
|
||||
.find('[data-test="refresh-dashboard-menu-item"]')
|
||||
.should('have.class', 'ant-dropdown-menu-item-disabled');
|
||||
|
||||
cy.wait(`@postJson_${mapId}_force`);
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
'not.have.class',
|
||||
'ant-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -100,15 +97,15 @@ describe('Dashboard top-level controls', () => {
|
|||
cy.get('[data-test="more-horiz"]').click();
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
'not.have.class',
|
||||
'ant-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
|
||||
// wait the all dash finish loading.
|
||||
cy.wait(sliceRequests);
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').click();
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').click({ force: true });
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
'have.class',
|
||||
'ant-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
|
||||
// wait all charts force refreshed
|
||||
|
@ -124,7 +121,7 @@ describe('Dashboard top-level controls', () => {
|
|||
cy.get('[data-test="more-horiz"]').click();
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
'not.have.class',
|
||||
'ant-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -64,7 +64,9 @@ function openAdvancedProperties() {
|
|||
function openDashboardEditProperties() {
|
||||
// open dashboard properties edit modal
|
||||
cy.get('#save-dash-split-button').trigger('click', { force: true });
|
||||
cy.get('.dropdown-menu').contains('Edit dashboard properties').click();
|
||||
cy.get('[data-test=header-actions-menu]')
|
||||
.contains('Edit dashboard properties')
|
||||
.click({ force: true });
|
||||
}
|
||||
|
||||
describe('Dashboard edit action', () => {
|
||||
|
|
|
@ -33,6 +33,11 @@ describe('Dashboard edit markdown', () => {
|
|||
cy.get('[data-test="dashboard-header"]')
|
||||
.find('[data-test="edit-alt"]')
|
||||
.click();
|
||||
|
||||
// lazy load - need to open dropdown for the scripts to load
|
||||
cy.get('[data-test="dashboard-header"]')
|
||||
.find('[data-test="more-horiz"]')
|
||||
.click();
|
||||
cy.get('script').then(nodes => {
|
||||
// load 5 new script chunks for css editor
|
||||
expect(nodes.length).to.greaterThan(numScripts);
|
||||
|
|
|
@ -18,8 +18,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { DropdownButton } from 'react-bootstrap';
|
||||
import { Menu } from 'src/common/components';
|
||||
import { Menu, NoAnimationDropdown } from 'src/common/components';
|
||||
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
|
||||
import URLShortLinkModal from 'src/components/URLShortLinkModal';
|
||||
import HeaderActionsDropdown from 'src/dashboard/components/HeaderActionsDropdown';
|
||||
|
@ -57,40 +56,43 @@ describe('HeaderActionsDropdown', () => {
|
|||
const wrapper = shallow(
|
||||
<HeaderActionsDropdown {...props} {...overrideProps} />,
|
||||
);
|
||||
return wrapper;
|
||||
const menu = shallow(
|
||||
<div>{wrapper.find(NoAnimationDropdown).props().overlay}</div>,
|
||||
);
|
||||
return { wrapper, menu };
|
||||
}
|
||||
|
||||
describe('readonly-user', () => {
|
||||
const overrideProps = { userCanSave: false };
|
||||
|
||||
it('should render the DropdownButton', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(DropdownButton)).toExist();
|
||||
const { wrapper } = setup(overrideProps);
|
||||
expect(wrapper.find(NoAnimationDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should not render the SaveModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(SaveModal)).not.toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(SaveModal)).not.toExist();
|
||||
});
|
||||
|
||||
it('should render five Menu items', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(Menu.Item)).toHaveLength(5);
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(Menu.Item)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(RefreshIntervalModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(RefreshIntervalModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render the URLShortLinkModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(URLShortLinkModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(URLShortLinkModal)).toExist();
|
||||
});
|
||||
|
||||
it('should not render the CssEditor', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(CssEditor)).not.toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(CssEditor)).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -98,33 +100,33 @@ describe('HeaderActionsDropdown', () => {
|
|||
const overrideProps = { userCanSave: true };
|
||||
|
||||
it('should render the DropdownButton', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(DropdownButton)).toExist();
|
||||
const { wrapper } = setup(overrideProps);
|
||||
expect(wrapper.find(NoAnimationDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should render the SaveModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(SaveModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(SaveModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render six Menu items', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(Menu.Item)).toHaveLength(6);
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(Menu.Item)).toHaveLength(6);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(RefreshIntervalModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(RefreshIntervalModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render the URLShortLinkModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(URLShortLinkModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(URLShortLinkModal)).toExist();
|
||||
});
|
||||
|
||||
it('should not render the CssEditor', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(CssEditor)).not.toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(CssEditor)).not.toExist();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -132,33 +134,33 @@ describe('HeaderActionsDropdown', () => {
|
|||
const overrideProps = { userCanSave: true, editMode: true };
|
||||
|
||||
it('should render the DropdownButton', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(DropdownButton)).toExist();
|
||||
const { wrapper } = setup(overrideProps);
|
||||
expect(wrapper.find(NoAnimationDropdown)).toExist();
|
||||
});
|
||||
|
||||
it('should render the SaveModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(SaveModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(SaveModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render seven MenuItems', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(Menu.Item)).toHaveLength(7);
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(Menu.Item)).toHaveLength(7);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(RefreshIntervalModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(RefreshIntervalModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render the URLShortLinkModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(URLShortLinkModal)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(URLShortLinkModal)).toExist();
|
||||
});
|
||||
|
||||
it('should render the CssEditor', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(CssEditor)).toExist();
|
||||
const { menu } = setup(overrideProps);
|
||||
expect(menu.find(CssEditor)).toExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { styled } from '@superset-ui/core';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Skeleton, Menu as AntdMenu } from 'antd';
|
||||
import { Dropdown, Skeleton, Menu as AntdMenu } from 'antd';
|
||||
import { DropDownProps } from 'antd/lib/dropdown';
|
||||
|
||||
/*
|
||||
Antd is re-exported from here so we can override components with Emotion as needed.
|
||||
|
@ -57,6 +59,13 @@ export const Menu = Object.assign(AntdMenu, {
|
|||
Item: MenuItem,
|
||||
});
|
||||
|
||||
export const NoAnimationDropdown = (props: DropDownProps) => (
|
||||
<Dropdown
|
||||
overlayStyle={{ zIndex: 4000, animationDuration: '0s' }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export const ThinSkeleton = styled(Skeleton)`
|
||||
h3 {
|
||||
margin: ${({ theme }) => theme.gridUnit}px 0;
|
||||
|
|
|
@ -102,9 +102,9 @@ export default class ModalTrigger extends React.Component {
|
|||
/* eslint-disable jsx-a11y/interactive-supports-focus */
|
||||
return (
|
||||
<>
|
||||
<span onClick={this.open} role="button">
|
||||
<div onClick={this.open} role="button">
|
||||
{this.props.triggerNode}
|
||||
</span>
|
||||
</div>
|
||||
{this.renderModal()}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -19,10 +19,9 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { SupersetClient, t } from '@superset-ui/core';
|
||||
import { DropdownButton } from 'react-bootstrap';
|
||||
import { styled, SupersetClient, t } from '@superset-ui/core';
|
||||
|
||||
import { Menu } from 'src/common/components';
|
||||
import { Menu, NoAnimationDropdown } from 'src/common/components';
|
||||
import Icon from 'src/components/Icon';
|
||||
|
||||
import CssEditor from './CssEditor';
|
||||
|
@ -84,6 +83,10 @@ const MENU_KEYS = {
|
|||
TOGGLE_FULLSCREEN: 'toggle-fullscreen',
|
||||
};
|
||||
|
||||
const DropdownButton = styled.div`
|
||||
margin-left: ${({ theme }) => theme.gridUnit * 2.5}px;
|
||||
`;
|
||||
|
||||
class HeaderActionsDropdown extends React.PureComponent {
|
||||
static discardChanges() {
|
||||
window.location.reload();
|
||||
|
@ -188,112 +191,114 @@ class HeaderActionsDropdown extends React.PureComponent {
|
|||
const emailSubject = `${emailTitle} ${dashboardTitle}`;
|
||||
const emailBody = t('Check out this dashboard: ');
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
title={<Icon name="more-horiz" />}
|
||||
noCaret
|
||||
id="save-dash-split-button"
|
||||
bsSize="large"
|
||||
style={{ border: 'none', padding: 0, marginLeft: '4px' }}
|
||||
pullRight
|
||||
const menu = (
|
||||
<Menu
|
||||
onClick={this.handleMenuClick}
|
||||
selectable={false}
|
||||
data-test="header-actions-menu"
|
||||
>
|
||||
<Menu onClick={this.handleMenuClick} selectable={false}>
|
||||
{userCanSave && (
|
||||
<Menu.Item key={MENU_KEYS.SAVE_MODAL}>
|
||||
<SaveModal
|
||||
addSuccessToast={this.props.addSuccessToast}
|
||||
addDangerToast={this.props.addDangerToast}
|
||||
dashboardId={dashboardId}
|
||||
dashboardTitle={dashboardTitle}
|
||||
dashboardInfo={dashboardInfo}
|
||||
saveType={SAVE_TYPE_NEWDASHBOARD}
|
||||
layout={layout}
|
||||
expandedSlices={expandedSlices}
|
||||
refreshFrequency={refreshFrequency}
|
||||
shouldPersistRefreshFrequency={shouldPersistRefreshFrequency}
|
||||
lastModifiedTime={lastModifiedTime}
|
||||
customCss={customCss}
|
||||
colorNamespace={colorNamespace}
|
||||
colorScheme={colorScheme}
|
||||
onSave={onSave}
|
||||
triggerNode={
|
||||
<span data-test="save-as-menu-item">{t('Save as')}</span>
|
||||
}
|
||||
canOverwrite={userCanEdit}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item key={MENU_KEYS.SHARE_DASHBOARD}>
|
||||
<URLShortLinkModal
|
||||
url={getDashboardUrl(
|
||||
window.location.pathname,
|
||||
getActiveFilters(),
|
||||
window.location.hash,
|
||||
)}
|
||||
emailSubject={emailSubject}
|
||||
emailContent={emailBody}
|
||||
{userCanSave && (
|
||||
<Menu.Item key={MENU_KEYS.SAVE_MODAL}>
|
||||
<SaveModal
|
||||
addSuccessToast={this.props.addSuccessToast}
|
||||
addDangerToast={this.props.addDangerToast}
|
||||
triggerNode={<span>{t('Share dashboard')}</span>}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key={MENU_KEYS.REFRESH_DASHBOARD}
|
||||
data-test="refresh-dashboard-menu-item"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{t('Refresh dashboard')}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={MENU_KEYS.AUTOREFRESH_MODAL}>
|
||||
<RefreshIntervalModal
|
||||
dashboardId={dashboardId}
|
||||
dashboardTitle={dashboardTitle}
|
||||
dashboardInfo={dashboardInfo}
|
||||
saveType={SAVE_TYPE_NEWDASHBOARD}
|
||||
layout={layout}
|
||||
expandedSlices={expandedSlices}
|
||||
refreshFrequency={refreshFrequency}
|
||||
refreshLimit={refreshLimit}
|
||||
refreshWarning={refreshWarning}
|
||||
onChange={this.changeRefreshInterval}
|
||||
editMode={editMode}
|
||||
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
|
||||
shouldPersistRefreshFrequency={shouldPersistRefreshFrequency}
|
||||
lastModifiedTime={lastModifiedTime}
|
||||
customCss={customCss}
|
||||
colorNamespace={colorNamespace}
|
||||
colorScheme={colorScheme}
|
||||
onSave={onSave}
|
||||
triggerNode={
|
||||
<span data-test="save-as-menu-item">{t('Save as')}</span>
|
||||
}
|
||||
canOverwrite={userCanEdit}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item key={MENU_KEYS.SHARE_DASHBOARD}>
|
||||
<URLShortLinkModal
|
||||
url={getDashboardUrl(
|
||||
window.location.pathname,
|
||||
getActiveFilters(),
|
||||
window.location.hash,
|
||||
)}
|
||||
emailSubject={emailSubject}
|
||||
emailContent={emailBody}
|
||||
addDangerToast={this.props.addDangerToast}
|
||||
triggerNode={<span>{t('Share dashboard')}</span>}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key={MENU_KEYS.REFRESH_DASHBOARD}
|
||||
data-test="refresh-dashboard-menu-item"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{t('Refresh dashboard')}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={MENU_KEYS.AUTOREFRESH_MODAL}>
|
||||
<RefreshIntervalModal
|
||||
refreshFrequency={refreshFrequency}
|
||||
refreshLimit={refreshLimit}
|
||||
refreshWarning={refreshWarning}
|
||||
onChange={this.changeRefreshInterval}
|
||||
editMode={editMode}
|
||||
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
|
||||
/>
|
||||
</Menu.Item>
|
||||
|
||||
{editMode && (
|
||||
<Menu.Item key={MENU_KEYS.SET_FILTER_MAPPING}>
|
||||
<FilterScopeModal
|
||||
className="m-r-5"
|
||||
triggerNode={t('Set filter mapping')}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{editMode && (
|
||||
<Menu.Item key={MENU_KEYS.SET_FILTER_MAPPING}>
|
||||
<FilterScopeModal
|
||||
className="m-r-5"
|
||||
triggerNode={t('Set filter mapping')}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{editMode && (
|
||||
<Menu.Item key={MENU_KEYS.EDIT_PROPERTIES}>
|
||||
{t('Edit dashboard properties')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{editMode && (
|
||||
<Menu.Item key={MENU_KEYS.EDIT_PROPERTIES}>
|
||||
{t('Edit dashboard properties')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{editMode && (
|
||||
<Menu.Item key={MENU_KEYS.EDIT_CSS}>
|
||||
<CssEditor
|
||||
triggerNode={<span>{t('Edit CSS')}</span>}
|
||||
initialCss={this.state.css}
|
||||
templates={this.state.cssTemplates}
|
||||
onChange={this.changeCss}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{editMode && (
|
||||
<Menu.Item key={MENU_KEYS.EDIT_CSS}>
|
||||
<CssEditor
|
||||
triggerNode={<span>{t('Edit CSS')}</span>}
|
||||
initialCss={this.state.css}
|
||||
templates={this.state.cssTemplates}
|
||||
onChange={this.changeCss}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{!editMode && (
|
||||
<Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
|
||||
{t('Download as image')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!editMode && (
|
||||
<Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
|
||||
{t('Download as image')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{!editMode && (
|
||||
<Menu.Item key={MENU_KEYS.TOGGLE_FULLSCREEN}>
|
||||
{t('Toggle FullScreen')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu>
|
||||
</DropdownButton>
|
||||
{!editMode && (
|
||||
<Menu.Item key={MENU_KEYS.TOGGLE_FULLSCREEN}>
|
||||
{t('Toggle FullScreen')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<NoAnimationDropdown overlay={menu} trigger={['click']}>
|
||||
<DropdownButton id="save-dash-split-button" role="button">
|
||||
<Icon name="more-horiz" />
|
||||
</DropdownButton>
|
||||
</NoAnimationDropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { DropdownButton } from 'react-bootstrap';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { Menu } from 'src/common/components';
|
||||
import { Menu, NoAnimationDropdown } from 'src/common/components';
|
||||
import URLShortLinkModal from '../../components/URLShortLinkModal';
|
||||
import downloadAsImage from '../../utils/downloadAsImage';
|
||||
import getDashboardUrl from '../util/getDashboardUrl';
|
||||
|
@ -82,6 +81,12 @@ const VerticalDotsContainer = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const RefreshTooltip = styled.div`
|
||||
height: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
margin: ${({ theme }) => theme.gridUnit}px 0;
|
||||
color: ${({ theme }) => theme.colors.grayscale.base};
|
||||
`;
|
||||
|
||||
const VerticalDotsTrigger = () => (
|
||||
<VerticalDotsContainer>
|
||||
<span className="dot" />
|
||||
|
@ -161,70 +166,77 @@ class SliceHeaderControls extends React.PureComponent {
|
|||
? t('Cached %s', cachedWhen)
|
||||
: (updatedWhen && t('Fetched %s', updatedWhen)) || '';
|
||||
const resizeLabel = isFullSize ? t('Minimize') : t('Maximize');
|
||||
return (
|
||||
<DropdownButton
|
||||
id={`slice_${slice.slice_id}-controls`}
|
||||
pullRight
|
||||
noCaret
|
||||
title={<VerticalDotsTrigger />}
|
||||
style={{ padding: 0 }}
|
||||
// react-bootstrap handles visibility, but call toggle to force a re-render
|
||||
// and update the fetched/cached timestamps
|
||||
onToggle={this.toggleControls}
|
||||
|
||||
const menu = (
|
||||
<Menu
|
||||
onClick={this.handleMenuClick}
|
||||
selectable={false}
|
||||
data-test={`slice_${slice.slice_id}-menu`}
|
||||
>
|
||||
<Menu onClick={this.handleMenuClick} selectable={false}>
|
||||
<Menu.Item
|
||||
key={MENU_KEYS.FORCE_REFRESH}
|
||||
disabled={this.props.chartStatus === 'loading'}
|
||||
style={{ height: 'auto', lineHeight: 'initial' }}
|
||||
>
|
||||
{t('Force refresh')}
|
||||
<div
|
||||
className="refresh-tooltip"
|
||||
data-test="dashboard-slice-refresh-tooltip"
|
||||
>
|
||||
{refreshTooltip}
|
||||
</div>
|
||||
<Menu.Item
|
||||
key={MENU_KEYS.FORCE_REFRESH}
|
||||
disabled={this.props.chartStatus === 'loading'}
|
||||
style={{ height: 'auto', lineHeight: 'initial' }}
|
||||
data-test="refresh-dashboard-menu-item"
|
||||
>
|
||||
{t('Force refresh')}
|
||||
<RefreshTooltip data-test="dashboard-slice-refresh-tooltip">
|
||||
{refreshTooltip}
|
||||
</RefreshTooltip>
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
{slice.description && (
|
||||
<Menu.Item key={MENU_KEYS.TOGGLE_CHART_DESCRIPTION}>
|
||||
{t('Toggle chart description')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
{slice.description && (
|
||||
<Menu.Item key={MENU_KEYS.TOGGLE_CHART_DESCRIPTION}>
|
||||
{t('Toggle chart description')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{this.props.supersetCanExplore && (
|
||||
<Menu.Item key={MENU_KEYS.EXPLORE_CHART}>
|
||||
{t('Explore chart')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{this.props.supersetCanCSV && (
|
||||
<Menu.Item key={MENU_KEYS.EXPORT_CSV}>{t('Export CSV')}</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Item key={MENU_KEYS.RESIZE_LABEL}>{resizeLabel}</Menu.Item>
|
||||
|
||||
<Menu.Item key={MENU_KEYS.SHARE_CHART}>
|
||||
<URLShortLinkModal
|
||||
url={getDashboardUrl(
|
||||
window.location.pathname,
|
||||
getActiveFilters(),
|
||||
componentId,
|
||||
)}
|
||||
addDangerToast={addDangerToast}
|
||||
title={t('Share chart')}
|
||||
triggerNode={<span>{t('Share chart')}</span>}
|
||||
/>
|
||||
{this.props.supersetCanExplore && (
|
||||
<Menu.Item key={MENU_KEYS.EXPLORE_CHART}>
|
||||
{t('Explore chart')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
|
||||
{t('Download as image')}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</DropdownButton>
|
||||
{this.props.supersetCanCSV && (
|
||||
<Menu.Item key={MENU_KEYS.EXPORT_CSV}>{t('Export CSV')}</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Item key={MENU_KEYS.RESIZE_LABEL}>{resizeLabel}</Menu.Item>
|
||||
|
||||
<Menu.Item key={MENU_KEYS.SHARE_CHART}>
|
||||
<URLShortLinkModal
|
||||
url={getDashboardUrl(
|
||||
window.location.pathname,
|
||||
getActiveFilters(),
|
||||
componentId,
|
||||
)}
|
||||
addDangerToast={addDangerToast}
|
||||
title={t('Share chart')}
|
||||
triggerNode={<span>{t('Share chart')}</span>}
|
||||
/>
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
|
||||
{t('Download as image')}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<NoAnimationDropdown
|
||||
overlay={menu}
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
dropdownAlign={{
|
||||
offset: [-40, 4],
|
||||
}}
|
||||
>
|
||||
<a id={`slice_${slice.slice_id}-controls`} role="button">
|
||||
<VerticalDotsTrigger />
|
||||
</a>
|
||||
</NoAnimationDropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,29 +109,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.dashboard .dashboard-header {
|
||||
#save-dash-split-button {
|
||||
border-radius: 0;
|
||||
margin-left: -9px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
|
||||
&.btn.btn-primary {
|
||||
border-left-color: @lightest;
|
||||
}
|
||||
|
||||
& + .dropdown-menu.dropdown-menu-right {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.caret {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard .chart-header,
|
||||
.dashboard .dashboard-header {
|
||||
.dropdown-menu {
|
||||
|
|
Loading…
Reference in New Issue