refactor: Replace react-bootstrap MenuItems with Antd Menu (#11487)

* Remove MenuItem from CopyToClipboard

* Refactor DateFilterControl

* fixup! Remove MenuItem from CopyToClipboard

* Remove console log

* Refactor LanguagePicker

* Refactor HeaderActionsDropdown

* Remove dir with Menu component

* Add imports to common/components/index

* Fix after rebase
This commit is contained in:
Kamil Gabryjelski 2020-11-04 22:47:25 +01:00 committed by GitHub
parent 69810170f7
commit ad98981d9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 210 additions and 208 deletions

View File

@ -72,9 +72,10 @@ describe('Dashboard top-level controls', () => {
.trigger('click', { force: true });
// not allow dashboard level force refresh when any chart is loading
cy.get('[data-test="refresh-dashboard-menu-item"]')
.parent()
.should('have.class', 'disabled');
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'have.class',
'ant-menu-item-disabled',
);
// not allow chart level force refresh when it is loading
cy.get(`#slice_${mapId}-controls`)
.next()
@ -84,25 +85,28 @@ describe('Dashboard top-level controls', () => {
.should('have.class', 'disabled');
cy.wait(`@postJson_${mapId}_force`);
cy.get('[data-test="refresh-dashboard-menu-item"]')
.parent()
.not('have.class', 'disabled');
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'not.have.class',
'ant-menu-item-disabled',
);
});
it('should allow dashboard level force refresh', () => {
// when charts are not start loading, for example, under a secondary tab,
// should allow force refresh
cy.get('[data-test="more-horiz"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]')
.parent()
.not('have.class', 'disabled');
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'not.have.class',
'ant-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"]')
.parent()
.should('have.class', 'disabled');
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'have.class',
'ant-menu-item-disabled',
);
// wait all charts force refreshed
cy.wait(forceRefreshRequests, { responseTimeout: 15000 }).then(xhrs => {
@ -115,8 +119,9 @@ describe('Dashboard top-level controls', () => {
});
cy.get('[data-test="more-horiz"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]')
.parent()
.not('have.class', 'disabled');
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'not.have.class',
'ant-menu-item-disabled',
);
});
});

View File

@ -18,7 +18,8 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { DropdownButton, MenuItem } from 'react-bootstrap';
import { DropdownButton } from 'react-bootstrap';
import { Menu } from 'src/common/components';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
import URLShortLinkModal from 'src/components/URLShortLinkModal';
import HeaderActionsDropdown from 'src/dashboard/components/HeaderActionsDropdown';
@ -72,9 +73,9 @@ describe('HeaderActionsDropdown', () => {
expect(wrapper.find(SaveModal)).not.toExist();
});
it('should render four MenuItems', () => {
it('should render five Menu items', () => {
const wrapper = setup(overrideProps);
expect(wrapper.find(MenuItem)).toHaveLength(4);
expect(wrapper.find(Menu.Item)).toHaveLength(5);
});
it('should render the RefreshIntervalModal', () => {
@ -106,9 +107,9 @@ describe('HeaderActionsDropdown', () => {
expect(wrapper.find(SaveModal)).toExist();
});
it('should render four MenuItems', () => {
it('should render six Menu items', () => {
const wrapper = setup(overrideProps);
expect(wrapper.find(MenuItem)).toHaveLength(4);
expect(wrapper.find(Menu.Item)).toHaveLength(6);
});
it('should render the RefreshIntervalModal', () => {
@ -140,9 +141,9 @@ describe('HeaderActionsDropdown', () => {
expect(wrapper.find(SaveModal)).toExist();
});
it('should render three MenuItems', () => {
it('should render seven MenuItems', () => {
const wrapper = setup(overrideProps);
expect(wrapper.find(MenuItem)).toHaveLength(3);
expect(wrapper.find(Menu.Item)).toHaveLength(7);
});
it('should render the RefreshIntervalModal', () => {

View File

@ -33,8 +33,10 @@ export {
DatePicker,
Dropdown,
Empty,
Input,
Modal,
Popover,
Select,
Skeleton,
Tabs,
Tooltip,

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip, OverlayTrigger, MenuItem } from 'react-bootstrap';
import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import { t } from '@superset-ui/core';
import withToasts from 'src/messageToasts/enhancers/withToasts';
@ -28,7 +28,6 @@ const propTypes = {
onCopyEnd: PropTypes.func,
shouldShowText: PropTypes.bool,
text: PropTypes.string,
inMenu: PropTypes.bool,
wrapped: PropTypes.bool,
tooltipText: PropTypes.string,
addDangerToast: PropTypes.func.isRequired,
@ -38,7 +37,6 @@ const defaultProps = {
copyNode: <span>Copy</span>,
onCopyEnd: () => {},
shouldShowText: true,
inMenu: false,
wrapped: true,
tooltipText: t('Copy to clipboard'),
};
@ -160,27 +158,6 @@ class CopyToClipboard extends React.Component {
);
}
renderInMenu() {
return (
<OverlayTrigger
placement="top"
overlay={this.renderTooltip()}
trigger={['hover']}
>
<MenuItem>
<span
role="button"
tabIndex={0}
onClick={this.onClick}
onMouseOut={this.onMouseOut}
>
{this.props.copyNode}
</span>
</MenuItem>
</OverlayTrigger>
);
}
renderTooltip() {
return (
<Tooltip id="copy-to-clipboard-tooltip">{this.tooltipText()}</Tooltip>
@ -188,11 +165,11 @@ class CopyToClipboard extends React.Component {
}
render() {
const { wrapped, inMenu } = this.props;
const { wrapped } = this.props;
if (!wrapped) {
return this.renderNotWrapped();
}
return inMenu ? this.renderInMenu() : this.renderLink();
return this.renderLink();
}
}

View File

@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
import { MenuItem } from 'react-bootstrap';
import { Menu } from 'src/common/components';
import NavDropdown from 'src/components/NavDropdown';
export interface Languages {
@ -46,17 +46,23 @@ export default function LanguagePicker({
</span>
}
>
{Object.keys(languages).map(langKey =>
langKey === locale ? null : (
<MenuItem key={langKey} href={languages[langKey].url}>
{' '}
<div className="f16">
<i className={`flag ${languages[langKey].flag}`} /> -{' '}
{languages[langKey].name}
</div>
</MenuItem>
),
)}
<Menu
onSelect={({ key }) => {
window.location.href = languages[key].url;
}}
>
{Object.keys(languages).map(langKey =>
langKey === locale ? null : (
<Menu.Item key={langKey}>
{' '}
<div className="f16">
<i className={`flag ${languages[langKey].flag}`} /> -{' '}
{languages[langKey].name}
</div>
</Menu.Item>
),
)}
</Menu>
</NavDropdown>
);
}

View File

@ -81,7 +81,6 @@ class CssEditor extends React.PureComponent {
<ModalTrigger
triggerNode={this.props.triggerNode}
modalTitle={t('CSS')}
isMenuItem
modalBody={
<div>
{this.renderTemplateSelector()}

View File

@ -20,8 +20,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import { SupersetClient, t } from '@superset-ui/core';
import { DropdownButton, MenuItem } from 'react-bootstrap';
import { DropdownButton } from 'react-bootstrap';
import { Menu } from 'src/common/components';
import Icon from 'src/components/Icon';
import CssEditor from './CssEditor';
@ -71,6 +72,18 @@ const defaultProps = {
refreshWarning: null,
};
const MENU_KEYS = {
SAVE_MODAL: 'save-modal',
SHARE_DASHBOARD: 'share-dashboard',
REFRESH_DASHBOARD: 'refresh-dashboard',
AUTOREFRESH_MODAL: 'autorefresh-modal',
SET_FILTER_MAPPING: 'set-filter-mapping',
EDIT_PROPERTIES: 'edit-properties',
EDIT_CSS: 'edit-css',
DOWNLOAD_AS_IMAGE: 'download-as-image',
TOGGLE_FULLSCREEN: 'toggle-fullscreen',
};
class HeaderActionsDropdown extends React.PureComponent {
static discardChanges() {
window.location.reload();
@ -85,6 +98,7 @@ class HeaderActionsDropdown extends React.PureComponent {
this.changeCss = this.changeCss.bind(this);
this.changeRefreshInterval = this.changeRefreshInterval.bind(this);
this.handleMenuClick = this.handleMenuClick.bind(this);
}
UNSAFE_componentWillMount() {
@ -119,12 +133,40 @@ class HeaderActionsDropdown extends React.PureComponent {
this.props.startPeriodicRender(refreshInterval * 1000);
}
handleMenuClick({ key, domEvent }) {
switch (key) {
case MENU_KEYS.REFRESH_DASHBOARD:
this.props.forceRefreshAllCharts();
break;
case MENU_KEYS.EDIT_PROPERTIES:
this.props.showPropertiesModal();
break;
case MENU_KEYS.DOWNLOAD_AS_IMAGE:
downloadAsImage('.dashboard', this.props.dashboardTitle)(domEvent);
break;
case MENU_KEYS.TOGGLE_FULLSCREEN: {
const hasStandalone = window.location.search.includes(
'standalone=true',
);
const url = getDashboardUrl(
window.location.pathname,
getActiveFilters(),
window.location.hash,
!hasStandalone,
);
window.location.replace(url);
break;
}
default:
break;
}
}
render() {
const {
dashboardTitle,
dashboardId,
dashboardInfo,
forceRefreshAllCharts,
refreshFrequency,
shouldPersistRefreshFrequency,
editMode,
@ -155,105 +197,102 @@ class HeaderActionsDropdown extends React.PureComponent {
style={{ border: 'none', padding: 0, marginLeft: '4px' }}
pullRight
>
{userCanSave && (
<>
<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}
isMenuItem
triggerNode={
<span data-test="save-as-menu-item">{t('Save as')}</span>
}
canOverwrite={userCanEdit}
/>
</>
)}
<URLShortLinkModal
url={getDashboardUrl(
window.location.pathname,
getActiveFilters(),
window.location.hash,
<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>
)}
emailSubject={emailSubject}
emailContent={emailBody}
addDangerToast={this.props.addDangerToast}
isMenuItem
triggerNode={<span>{t('Share dashboard')}</span>}
/>
<MenuItem
data-test="refresh-dashboard-menu-item"
onClick={forceRefreshAllCharts}
disabled={isLoading}
>
{t('Refresh dashboard')}
</MenuItem>
<MenuItem divider />
<RefreshIntervalModal
refreshFrequency={refreshFrequency}
refreshLimit={refreshLimit}
refreshWarning={refreshWarning}
onChange={this.changeRefreshInterval}
editMode={editMode}
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
/>
{editMode && (
<>
<FilterScopeModal
className="m-r-5"
triggerNode={
<MenuItem bsSize="small">{t('Set filter mapping')}</MenuItem>
}
/>
<MenuItem onClick={this.props.showPropertiesModal}>
{t('Edit dashboard properties')}
</MenuItem>
<CssEditor
triggerNode={<span>{t('Edit CSS')}</span>}
initialCss={this.state.css}
templates={this.state.cssTemplates}
onChange={this.changeCss}
/>
</>
)}
{!editMode && (
<MenuItem onClick={downloadAsImage('.dashboard', dashboardTitle)}>
{t('Download as image')}
</MenuItem>
)}
{!editMode && (
<MenuItem
onClick={() => {
const hasStandalone = window.location.search.includes(
'standalone=true',
);
const url = getDashboardUrl(
<Menu.Item key={MENU_KEYS.SHARE_DASHBOARD}>
<URLShortLinkModal
url={getDashboardUrl(
window.location.pathname,
getActiveFilters(),
window.location.hash,
!hasStandalone,
);
window.location.replace(url);
}}
)}
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('Toggle FullScreen')}
</MenuItem>
)}
{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.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.DOWNLOAD_AS_IMAGE}>
{t('Download as image')}
</Menu.Item>
)}
{!editMode && (
<Menu.Item key={MENU_KEYS.TOGGLE_FULLSCREEN}>
{t('Toggle FullScreen')}
</Menu.Item>
)}
</Menu>
</DropdownButton>
);
}

View File

@ -94,7 +94,6 @@ class RefreshIntervalModal extends React.PureComponent {
<ModalTrigger
ref={this.modalRef}
triggerNode={this.props.triggerNode}
isMenuItem
modalTitle={t('Refresh Interval')}
modalBody={
<div>

View File

@ -41,14 +41,12 @@ const propTypes = {
colorNamespace: PropTypes.string,
colorScheme: PropTypes.string,
onSave: PropTypes.func.isRequired,
isMenuItem: PropTypes.bool,
canOverwrite: PropTypes.bool.isRequired,
refreshFrequency: PropTypes.number.isRequired,
lastModifiedTime: PropTypes.number.isRequired,
};
const defaultProps = {
isMenuItem: false,
saveType: SAVE_TYPE_OVERWRITE,
colorNamespace: undefined,
colorScheme: undefined,
@ -157,7 +155,6 @@ class SaveModal extends React.PureComponent {
return (
<ModalTrigger
ref={this.setModalRef}
isMenuItem={this.props.isMenuItem}
triggerNode={this.props.triggerNode}
modalTitle={t('Save Dashboard')}
modalBody={

View File

@ -19,16 +19,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
DropdownButton,
FormControl,
FormGroup,
InputGroup,
MenuItem,
OverlayTrigger,
Radio,
Tooltip,
} from 'react-bootstrap';
import Popover from 'src/common/components/Popover';
import { Select, Input } from 'src/common/components';
import Button from 'src/components/Button';
import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
@ -387,15 +386,13 @@ class DateFilterControl extends React.Component {
renderPopover() {
const grainOptions = TIME_GRAIN_OPTIONS.map(grain => (
<MenuItem
onSelect={value => this.setCustomRange('grain', value)}
<Select.Option
key={grain}
eventKey={grain}
value={grain}
active={grain === this.state.grain}
fullWidth={false}
>
{grain}
</MenuItem>
</Select.Option>
));
const timeFrames = COMMON_TIME_FRAMES.map(timeFrame => {
const nextState = getStateFromCommonTimeFrame(timeFrame);
@ -425,6 +422,7 @@ class DateFilterControl extends React.Component {
</Styles>
);
});
return (
<div
id="filter-popover"
@ -453,66 +451,45 @@ class DateFilterControl extends React.Component {
>
<div
className="clearfix centered"
style={{ marginTop: '12px' }}
style={{ marginTop: '8px', display: 'flex' }}
>
<div
style={{ width: '60px', marginTop: '-4px' }}
className="input-inline"
>
<DropdownButton
bsSize="small"
componentClass={InputGroup.Button}
id="input-dropdown-rel"
title={this.state.rel}
<div className="input-inline">
<Select
value={this.state.rel}
onSelect={value => this.setCustomRange('rel', value)}
onFocus={this.setTypeCustomRange}
>
<MenuItem
onSelect={value => this.setCustomRange('rel', value)}
key={RELATIVE_TIME_OPTIONS.LAST}
eventKey={RELATIVE_TIME_OPTIONS.LAST}
active={this.state.rel === RELATIVE_TIME_OPTIONS.LAST}
>
<Select.Option value={RELATIVE_TIME_OPTIONS.LAST}>
Last
</MenuItem>
<MenuItem
onSelect={value => this.setCustomRange('rel', value)}
key={RELATIVE_TIME_OPTIONS.NEXT}
eventKey={RELATIVE_TIME_OPTIONS.NEXT}
active={this.state.rel === RELATIVE_TIME_OPTIONS.NEXT}
>
</Select.Option>
<Select.Option value={RELATIVE_TIME_OPTIONS.NEXT}>
Next
</MenuItem>
</DropdownButton>
</Select.Option>
</Select>
</div>
<div
style={{ width: '60px', marginTop: '-4px' }}
style={{ width: '60px' }}
className="input-inline m-l-5 m-r-3"
>
<FormControl
bsSize="small"
<Input
type="text"
onChange={event =>
this.setCustomRange('num', event.target.value)
}
onFocus={this.setTypeCustomRange}
onKeyPress={this.onEnter}
onPressEnter={this.close}
value={this.state.num}
style={{ height: '30px' }}
/>
</div>
<div
style={{ width: '90px', marginTop: '-4px' }}
className="input-inline"
>
<DropdownButton
bsSize="small"
componentClass={InputGroup.Button}
id="input-dropdown-grain"
title={this.state.grain}
<div className="input-inline">
<Select
value={this.state.grain}
onFocus={this.setTypeCustomRange}
onSelect={value => this.setCustomRange('grain', value)}
dropdownMatchSelectWidth={false}
>
{grainOptions}
</DropdownButton>
</Select>
</div>
</div>
</PopoverSection>