mirror of https://github.com/apache/superset.git
refactor: Replace react-bootstrap MenuItems with Antd Menu (#11554)
* Refactor SliceHeaderControls * Refactor DisplayQueryButton * Fix duplicate keys * Refactor SliceAdder * Move css from styles to Emotion * Fix e2e test
This commit is contained in:
parent
091432ea8e
commit
1490f3074d
|
@ -81,8 +81,7 @@ describe('Dashboard top-level controls', () => {
|
|||
.next()
|
||||
.find('[data-test="dashboard-slice-refresh-tooltip"]')
|
||||
.parent()
|
||||
.parent()
|
||||
.should('have.class', 'disabled');
|
||||
.should('have.class', 'ant-menu-item-disabled');
|
||||
|
||||
cy.wait(`@postJson_${mapId}_force`);
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import { Menu } from 'src/common/components';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
import { DisplayQueryButton } from 'src/explore/components/DisplayQueryButton';
|
||||
import { MenuItem } from 'react-bootstrap';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
describe('DisplayQueryButton', () => {
|
||||
const defaultProps = {
|
||||
|
@ -51,6 +51,6 @@ describe('DisplayQueryButton', () => {
|
|||
},
|
||||
});
|
||||
expect(wrapper.find(ModalTrigger)).toHaveLength(3);
|
||||
expect(wrapper.find(MenuItem)).toHaveLength(5);
|
||||
expect(wrapper.find(Menu.Item)).toHaveLength(5);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -191,7 +191,7 @@ export function Menu({
|
|||
</DropdownMenu.ItemGroup>,
|
||||
]}
|
||||
{(navbarRight.version_string || navbarRight.version_sha) && [
|
||||
<DropdownMenu.Divider key="user-divider" />,
|
||||
<DropdownMenu.Divider key="navbar-divider" />,
|
||||
<DropdownMenu.ItemGroup
|
||||
key="about-section"
|
||||
title={t('About')}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MenuItem } from 'react-bootstrap';
|
||||
import Modal from 'src/common/components/Modal';
|
||||
import Button from 'src/components/Button';
|
||||
|
||||
|
@ -31,7 +30,6 @@ const propTypes = {
|
|||
beforeOpen: PropTypes.func,
|
||||
onExit: PropTypes.func,
|
||||
isButton: PropTypes.bool,
|
||||
isMenuItem: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
width: PropTypes.string,
|
||||
|
@ -43,7 +41,6 @@ const defaultProps = {
|
|||
beforeOpen: () => {},
|
||||
onExit: () => {},
|
||||
isButton: false,
|
||||
isMenuItem: false,
|
||||
className: '',
|
||||
modalTitle: '',
|
||||
};
|
||||
|
@ -102,14 +99,6 @@ export default class ModalTrigger extends React.Component {
|
|||
</>
|
||||
);
|
||||
}
|
||||
if (this.props.isMenuItem) {
|
||||
return (
|
||||
<>
|
||||
<MenuItem onClick={this.open}>{this.props.triggerNode}</MenuItem>
|
||||
{this.renderModal()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
/* eslint-disable jsx-a11y/interactive-supports-focus */
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -29,7 +29,6 @@ const propTypes = {
|
|||
emailSubject: PropTypes.string,
|
||||
emailContent: PropTypes.string,
|
||||
addDangerToast: PropTypes.func.isRequired,
|
||||
isMenuItem: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
triggerNode: PropTypes.node.isRequired,
|
||||
};
|
||||
|
@ -65,7 +64,6 @@ class URLShortLinkModal extends React.Component {
|
|||
return (
|
||||
<ModalTrigger
|
||||
ref={this.setModalRef}
|
||||
isMenuItem={this.props.isMenuItem}
|
||||
triggerNode={this.props.triggerNode}
|
||||
beforeOpen={this.getCopyUrl}
|
||||
modalTitle={this.props.title || t('Share Dashboard')}
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
/* eslint-env browser */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DropdownButton, MenuItem } from 'react-bootstrap';
|
||||
import { List } from 'react-virtualized';
|
||||
import SearchInput, { createFilter } from 'react-search-input';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import { Select } from 'src/common/components';
|
||||
import AddSliceCard from './AddSliceCard';
|
||||
import AddSliceDragPreview from './dnd/AddSliceDragPreview';
|
||||
import DragDroppable from './dnd/DragDroppable';
|
||||
|
@ -52,12 +52,14 @@ const defaultProps = {
|
|||
};
|
||||
|
||||
const KEYS_TO_FILTERS = ['slice_name', 'viz_type', 'datasource_name'];
|
||||
const KEYS_TO_SORT = [
|
||||
{ key: 'slice_name', label: 'Name' },
|
||||
{ key: 'viz_type', label: 'Vis type' },
|
||||
{ key: 'datasource_name', label: 'Dataset' },
|
||||
{ key: 'changed_on', label: 'Recent' },
|
||||
];
|
||||
const KEYS_TO_SORT = {
|
||||
slice_name: 'Name',
|
||||
viz_type: 'Vis type',
|
||||
datasource_name: 'Dataset',
|
||||
changed_on: 'Recent',
|
||||
};
|
||||
|
||||
const DEFAULT_SORT_KEY = 'changed_on';
|
||||
|
||||
const MARGIN_BOTTOM = 16;
|
||||
const SIDEPANE_HEADER_HEIGHT = 30;
|
||||
|
@ -84,7 +86,7 @@ class SliceAdder extends React.Component {
|
|||
this.state = {
|
||||
filteredSlices: [],
|
||||
searchTerm: '',
|
||||
sortBy: KEYS_TO_SORT.findIndex(item => item.key === 'changed_on'),
|
||||
sortBy: DEFAULT_SORT_KEY,
|
||||
selectedSliceIdsSet: new Set(props.selectedSliceIds),
|
||||
};
|
||||
this.rowRenderer = this.rowRenderer.bind(this);
|
||||
|
@ -102,7 +104,7 @@ class SliceAdder extends React.Component {
|
|||
if (nextProps.lastUpdated !== this.props.lastUpdated) {
|
||||
nextState.filteredSlices = Object.values(nextProps.slices)
|
||||
.filter(createFilter(this.state.searchTerm, KEYS_TO_FILTERS))
|
||||
.sort(SliceAdder.sortByComparator(KEYS_TO_SORT[this.state.sortBy].key));
|
||||
.sort(SliceAdder.sortByComparator(this.state.sortBy));
|
||||
}
|
||||
|
||||
if (nextProps.selectedSliceIds !== this.props.selectedSliceIds) {
|
||||
|
@ -123,7 +125,7 @@ class SliceAdder extends React.Component {
|
|||
getFilteredSortedSlices(searchTerm, sortBy) {
|
||||
return Object.values(this.props.slices)
|
||||
.filter(createFilter(searchTerm, KEYS_TO_FILTERS))
|
||||
.sort(SliceAdder.sortByComparator(KEYS_TO_SORT[sortBy].key));
|
||||
.sort(SliceAdder.sortByComparator(sortBy));
|
||||
}
|
||||
|
||||
handleKeyPress(ev) {
|
||||
|
@ -216,17 +218,17 @@ class SliceAdder extends React.Component {
|
|||
onKeyPress={this.handleKeyPress}
|
||||
data-test="dashboard-charts-filter-search-input"
|
||||
/>
|
||||
<DropdownButton
|
||||
title={`Sort by ${KEYS_TO_SORT[this.state.sortBy].label}`}
|
||||
onSelect={this.handleSelect}
|
||||
<Select
|
||||
id="slice-adder-sortby"
|
||||
defaultValue={DEFAULT_SORT_KEY}
|
||||
onChange={this.handleSelect}
|
||||
>
|
||||
{KEYS_TO_SORT.map((item, index) => (
|
||||
<MenuItem key={item.key} eventKey={index}>
|
||||
Sort by {item.label}
|
||||
</MenuItem>
|
||||
{Object.entries(KEYS_TO_SORT).map(([key, label]) => (
|
||||
<Select.Option key={key} value={key}>
|
||||
Sort by {label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</DropdownButton>
|
||||
</Select>
|
||||
</div>
|
||||
{this.props.isLoading && <Loading />}
|
||||
{!this.props.isLoading && this.state.filteredSlices.length > 0 && (
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Dropdown, MenuItem } from 'react-bootstrap';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { DropdownButton } from 'react-bootstrap';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { Menu } from 'src/common/components';
|
||||
import URLShortLinkModal from '../../components/URLShortLinkModal';
|
||||
import downloadAsImage from '../../utils/downloadAsImage';
|
||||
import getDashboardUrl from '../util/getDashboardUrl';
|
||||
|
@ -58,41 +59,49 @@ const defaultProps = {
|
|||
sliceCanEdit: false,
|
||||
};
|
||||
|
||||
const MENU_KEYS = {
|
||||
FORCE_REFRESH: 'force_refresh',
|
||||
TOGGLE_CHART_DESCRIPTION: 'toggle_chart_description',
|
||||
EXPLORE_CHART: 'explore_chart',
|
||||
EXPORT_CSV: 'export_csv',
|
||||
RESIZE_LABEL: 'resize_label',
|
||||
SHARE_CHART: 'share_chart',
|
||||
DOWNLOAD_AS_IMAGE: 'download_as_image',
|
||||
};
|
||||
|
||||
const VerticalDotsContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.gridUnit / 4}px
|
||||
${({ theme }) => theme.gridUnit * 1.5}px;
|
||||
|
||||
.dot {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const VerticalDotsTrigger = () => (
|
||||
<div className="vertical-dots-container">
|
||||
<VerticalDotsContainer>
|
||||
<span className="dot" />
|
||||
<span className="dot" />
|
||||
<span className="dot" />
|
||||
</div>
|
||||
</VerticalDotsContainer>
|
||||
);
|
||||
|
||||
class SliceHeaderControls extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.exportCSV = this.exportCSV.bind(this);
|
||||
this.exploreChart = this.exploreChart.bind(this);
|
||||
this.toggleControls = this.toggleControls.bind(this);
|
||||
this.refreshChart = this.refreshChart.bind(this);
|
||||
this.toggleExpandSlice = this.props.toggleExpandSlice.bind(
|
||||
this,
|
||||
this.props.slice.slice_id,
|
||||
);
|
||||
|
||||
this.handleToggleFullSize = this.handleToggleFullSize.bind(this);
|
||||
this.handleMenuClick = this.handleMenuClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
showControls: false,
|
||||
};
|
||||
}
|
||||
|
||||
exportCSV() {
|
||||
this.props.exportCSV(this.props.slice.slice_id);
|
||||
}
|
||||
|
||||
exploreChart() {
|
||||
this.props.exploreChart(this.props.slice.slice_id);
|
||||
}
|
||||
|
||||
refreshChart() {
|
||||
if (this.props.updatedDttm) {
|
||||
this.props.forceRefresh(
|
||||
|
@ -108,8 +117,32 @@ class SliceHeaderControls extends React.PureComponent {
|
|||
}));
|
||||
}
|
||||
|
||||
handleToggleFullSize() {
|
||||
this.props.handleToggleFullSize();
|
||||
handleMenuClick({ key, domEvent }) {
|
||||
switch (key) {
|
||||
case MENU_KEYS.FORCE_REFRESH:
|
||||
this.refreshChart();
|
||||
break;
|
||||
case MENU_KEYS.TOGGLE_CHART_DESCRIPTION:
|
||||
this.props.toggleExpandSlice(this.props.slice.slice_id);
|
||||
break;
|
||||
case MENU_KEYS.EXPLORE_CHART:
|
||||
this.props.exploreChart(this.props.slice.slice_id);
|
||||
break;
|
||||
case MENU_KEYS.EXPORT_CSV:
|
||||
this.props.exportCSV(this.props.slice.slice_id);
|
||||
break;
|
||||
case MENU_KEYS.RESIZE_LABEL:
|
||||
this.props.handleToggleFullSize();
|
||||
break;
|
||||
case MENU_KEYS.DOWNLOAD_AS_IMAGE:
|
||||
downloadAsImage(
|
||||
'.dashboard-component-chart-holder',
|
||||
this.props.slice.slice_name,
|
||||
)(domEvent);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -129,21 +162,21 @@ class SliceHeaderControls extends React.PureComponent {
|
|||
: (updatedWhen && t('Fetched %s', updatedWhen)) || '';
|
||||
const resizeLabel = isFullSize ? t('Minimize') : t('Maximize');
|
||||
return (
|
||||
<Dropdown
|
||||
<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}
|
||||
>
|
||||
<Dropdown.Toggle className="slice-header-controls-trigger" noCaret>
|
||||
<VerticalDotsTrigger />
|
||||
</Dropdown.Toggle>
|
||||
|
||||
<Dropdown.Menu>
|
||||
<MenuItem
|
||||
onClick={this.refreshChart}
|
||||
<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
|
||||
|
@ -152,50 +185,46 @@ class SliceHeaderControls extends React.PureComponent {
|
|||
>
|
||||
{refreshTooltip}
|
||||
</div>
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
|
||||
<MenuItem divider />
|
||||
<Menu.Divider />
|
||||
|
||||
{slice.description && (
|
||||
<MenuItem onClick={this.toggleExpandSlice}>
|
||||
<Menu.Item key={MENU_KEYS.TOGGLE_CHART_DESCRIPTION}>
|
||||
{t('Toggle chart description')}
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{this.props.supersetCanExplore && (
|
||||
<MenuItem onClick={this.exploreChart}>
|
||||
<Menu.Item key={MENU_KEYS.EXPLORE_CHART}>
|
||||
{t('Explore chart')}
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{this.props.supersetCanCSV && (
|
||||
<MenuItem onClick={this.exportCSV}>{t('Export CSV')}</MenuItem>
|
||||
<Menu.Item key={MENU_KEYS.EXPORT_CSV}>{t('Export CSV')}</Menu.Item>
|
||||
)}
|
||||
|
||||
<MenuItem onClick={this.handleToggleFullSize}>{resizeLabel}</MenuItem>
|
||||
<Menu.Item key={MENU_KEYS.RESIZE_LABEL}>{resizeLabel}</Menu.Item>
|
||||
|
||||
<URLShortLinkModal
|
||||
url={getDashboardUrl(
|
||||
window.location.pathname,
|
||||
getActiveFilters(),
|
||||
componentId,
|
||||
)}
|
||||
addDangerToast={addDangerToast}
|
||||
isMenuItem
|
||||
title={t('Share chart')}
|
||||
triggerNode={<span>{t('Share chart')}</span>}
|
||||
/>
|
||||
<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>
|
||||
|
||||
<MenuItem
|
||||
onClick={downloadAsImage(
|
||||
'.dashboard-component-chart-holder',
|
||||
slice.slice_name,
|
||||
)}
|
||||
>
|
||||
<Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
|
||||
{t('Download as image')}
|
||||
</MenuItem>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,14 +112,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.slice-header-controls-trigger {
|
||||
padding: 2px 6px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
@dot-diameter: 4px;
|
||||
|
||||
|
@ -131,10 +123,6 @@
|
|||
background-color: @gray;
|
||||
display: inline-block;
|
||||
|
||||
.vertical-dots-container & {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a[role='menuitem'] & {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
|
|
@ -138,16 +138,8 @@ body {
|
|||
padding: 9px 0;
|
||||
}
|
||||
|
||||
.dropdown-menu li a {
|
||||
padding: 3px 16px;
|
||||
color: @almost-black;
|
||||
font-size: @font-size-m;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: @menu-hover;
|
||||
color: @almost-black;
|
||||
}
|
||||
.dropdown-menu li {
|
||||
font-weight: @font-weight-normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,16 +26,11 @@ import markdownSyntax from 'react-syntax-highlighter/dist/cjs/languages/hljs/mar
|
|||
import sqlSyntax from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
|
||||
import jsonSyntax from 'react-syntax-highlighter/dist/cjs/languages/hljs/json';
|
||||
import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github';
|
||||
import {
|
||||
DropdownButton,
|
||||
MenuItem,
|
||||
Row,
|
||||
Col,
|
||||
FormControl,
|
||||
} from 'react-bootstrap';
|
||||
import { DropdownButton, Row, Col, FormControl } from 'react-bootstrap';
|
||||
import { Table } from 'reactable-arc';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import { Menu } from 'src/common/components';
|
||||
import Button from 'src/components/Button';
|
||||
import getClientErrorObject from '../../utils/getClientErrorObject';
|
||||
import CopyToClipboard from '../../components/CopyToClipboard';
|
||||
|
@ -65,6 +60,12 @@ const propTypes = {
|
|||
slice: PropTypes.object,
|
||||
};
|
||||
|
||||
const MENU_KEYS = {
|
||||
EDIT_PROPERTIES: 'edit_properties',
|
||||
RUN_IN_SQL_LAB: 'run_in_sql_lab',
|
||||
DOWNLOAD_AS_IMAGE: 'download_as_image',
|
||||
};
|
||||
|
||||
export class DisplayQueryButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -81,8 +82,8 @@ export class DisplayQueryButton extends React.PureComponent {
|
|||
};
|
||||
this.beforeOpen = this.beforeOpen.bind(this);
|
||||
this.changeFilterText = this.changeFilterText.bind(this);
|
||||
this.openPropertiesModal = this.openPropertiesModal.bind(this);
|
||||
this.closePropertiesModal = this.closePropertiesModal.bind(this);
|
||||
this.handleMenuClick = this.handleMenuClick.bind(this);
|
||||
}
|
||||
|
||||
beforeOpen(resultType) {
|
||||
|
@ -118,10 +119,6 @@ export class DisplayQueryButton extends React.PureComponent {
|
|||
this.setState({ filterText: event.target.value });
|
||||
}
|
||||
|
||||
redirectSQLLab() {
|
||||
this.props.onOpenInEditor(this.props.latestQueryFormData);
|
||||
}
|
||||
|
||||
openPropertiesModal() {
|
||||
this.setState({ isPropertiesModalOpen: true });
|
||||
}
|
||||
|
@ -130,6 +127,35 @@ export class DisplayQueryButton extends React.PureComponent {
|
|||
this.setState({ isPropertiesModalOpen: false });
|
||||
}
|
||||
|
||||
handleMenuClick({ key, domEvent }) {
|
||||
const {
|
||||
chartHeight,
|
||||
slice,
|
||||
onOpenInEditor,
|
||||
latestQueryFormData,
|
||||
} = this.props;
|
||||
switch (key) {
|
||||
case MENU_KEYS.EDIT_PROPERTIES:
|
||||
this.openPropertiesModal();
|
||||
break;
|
||||
case MENU_KEYS.RUN_IN_SQL_LAB:
|
||||
onOpenInEditor(latestQueryFormData);
|
||||
break;
|
||||
case MENU_KEYS.DOWNLOAD_AS_IMAGE:
|
||||
downloadAsImage(
|
||||
'.chart-container',
|
||||
// eslint-disable-next-line camelcase
|
||||
slice?.slice_name ?? t('New chart'),
|
||||
{
|
||||
height: parseInt(chartHeight, 10),
|
||||
},
|
||||
)(domEvent);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renderQueryModalBody() {
|
||||
if (this.state.isLoading) {
|
||||
return <Loading />;
|
||||
|
@ -230,7 +256,7 @@ export class DisplayQueryButton extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { chartHeight, slice } = this.props;
|
||||
const { slice } = this.props;
|
||||
return (
|
||||
<DropdownButton
|
||||
noCaret
|
||||
|
@ -245,62 +271,56 @@ export class DisplayQueryButton extends React.PureComponent {
|
|||
pullRight
|
||||
id="query"
|
||||
>
|
||||
{slice && (
|
||||
<>
|
||||
<MenuItem onClick={this.openPropertiesModal}>
|
||||
<Menu onClick={this.handleMenuClick} selectable={false}>
|
||||
{slice && [
|
||||
<Menu.Item key={MENU_KEYS.EDIT_PROPERTIES}>
|
||||
{t('Edit properties')}
|
||||
</MenuItem>
|
||||
</Menu.Item>,
|
||||
<PropertiesModal
|
||||
slice={slice}
|
||||
show={this.state.isPropertiesModalOpen}
|
||||
onHide={this.closePropertiesModal}
|
||||
onSave={this.props.sliceUpdated}
|
||||
/>,
|
||||
]}
|
||||
<Menu.Item>
|
||||
<ModalTrigger
|
||||
triggerNode={
|
||||
<span data-test="view-query-menu-item">{t('View query')}</span>
|
||||
}
|
||||
modalTitle={t('View query')}
|
||||
beforeOpen={() => this.beforeOpen('query')}
|
||||
modalBody={this.renderQueryModalBody()}
|
||||
responsive
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<ModalTrigger
|
||||
isMenuItem
|
||||
triggerNode={
|
||||
<span data-test="view-query-menu-item">{t('View query')}</span>
|
||||
}
|
||||
modalTitle={t('View query')}
|
||||
beforeOpen={() => this.beforeOpen('query')}
|
||||
modalBody={this.renderQueryModalBody()}
|
||||
responsive
|
||||
/>
|
||||
<ModalTrigger
|
||||
isMenuItem
|
||||
triggerNode={<span>{t('View results')}</span>}
|
||||
modalTitle={t('View results')}
|
||||
beforeOpen={() => this.beforeOpen('results')}
|
||||
modalBody={this.renderResultsModalBody()}
|
||||
responsive
|
||||
/>
|
||||
<ModalTrigger
|
||||
isMenuItem
|
||||
triggerNode={<span>{t('View samples')}</span>}
|
||||
modalTitle={t('View samples')}
|
||||
beforeOpen={() => this.beforeOpen('samples')}
|
||||
modalBody={this.renderSamplesModalBody()}
|
||||
responsive
|
||||
/>
|
||||
{this.state.sqlSupported && (
|
||||
<MenuItem eventKey="3" onClick={this.redirectSQLLab.bind(this)}>
|
||||
{t('Run in SQL Lab')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={downloadAsImage(
|
||||
'.chart-container',
|
||||
// eslint-disable-next-line camelcase
|
||||
slice?.slice_name ?? t('New chart'),
|
||||
{
|
||||
height: parseInt(chartHeight, 10),
|
||||
},
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ModalTrigger
|
||||
triggerNode={<span>{t('View results')}</span>}
|
||||
modalTitle={t('View results')}
|
||||
beforeOpen={() => this.beforeOpen('results')}
|
||||
modalBody={this.renderResultsModalBody()}
|
||||
responsive
|
||||
/>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<ModalTrigger
|
||||
triggerNode={<span>{t('View samples')}</span>}
|
||||
modalTitle={t('View samples')}
|
||||
beforeOpen={() => this.beforeOpen('samples')}
|
||||
modalBody={this.renderSamplesModalBody()}
|
||||
responsive
|
||||
/>
|
||||
</Menu.Item>
|
||||
{this.state.sqlSupported && (
|
||||
<Menu.Item key={MENU_KEYS.RUN_IN_SQL_LAB}>
|
||||
{t('Run in SQL Lab')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
>
|
||||
{t('Download as image')}
|
||||
</MenuItem>
|
||||
<Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
|
||||
{t('Download as image')}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -88,8 +88,6 @@ const FREEFORM_TOOLTIP = t(
|
|||
'`2 weeks from now` can be used.',
|
||||
);
|
||||
|
||||
const DATE_FILTER_POPOVER_STYLE = { width: '250px' };
|
||||
|
||||
const propTypes = {
|
||||
animation: PropTypes.bool,
|
||||
name: PropTypes.string.isRequired,
|
||||
|
@ -173,12 +171,34 @@ function getStateFromCustomRange(value) {
|
|||
};
|
||||
}
|
||||
|
||||
const Styles = styled.div`
|
||||
const TimeFramesStyles = styled.div`
|
||||
.radio {
|
||||
margin: ${({ theme }) => theme.gridUnit}px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const PopoverContentStyles = styled.div`
|
||||
width: ${({ theme }) => theme.gridUnit * 60}px;
|
||||
|
||||
.timeframes-container {
|
||||
margin-left: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
}
|
||||
|
||||
.relative-timerange-container {
|
||||
display: flex;
|
||||
margin-top: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
}
|
||||
|
||||
.timerange-input {
|
||||
width: ${({ theme }) => theme.gridUnit * 15}px;
|
||||
margin: 0 ${({ theme }) => theme.gridUnit}px;
|
||||
}
|
||||
|
||||
.datetime {
|
||||
margin: ${({ theme }) => theme.gridUnit}px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
class DateFilterControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -399,7 +419,7 @@ class DateFilterControl extends React.Component {
|
|||
const timeRange = buildTimeRangeString(nextState.since, nextState.until);
|
||||
|
||||
return (
|
||||
<Styles key={timeFrame}>
|
||||
<TimeFramesStyles key={timeFrame}>
|
||||
<OverlayTrigger
|
||||
key={timeFrame}
|
||||
placement="right"
|
||||
|
@ -419,14 +439,13 @@ class DateFilterControl extends React.Component {
|
|||
</Radio>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</Styles>
|
||||
</TimeFramesStyles>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
<PopoverContentStyles
|
||||
id="filter-popover"
|
||||
style={DATE_FILTER_POPOVER_STYLE}
|
||||
ref={ref => {
|
||||
this.popoverContainer = ref;
|
||||
}}
|
||||
|
@ -438,7 +457,7 @@ class DateFilterControl extends React.Component {
|
|||
onSelect={this.changeTab}
|
||||
>
|
||||
<Tabs.TabPane key="1" tab="Defaults" forceRender>
|
||||
<div style={{ marginLeft: '8px' }}>
|
||||
<div className="timeframes-container">
|
||||
<FormGroup>{timeFrames}</FormGroup>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
|
@ -449,48 +468,37 @@ class DateFilterControl extends React.Component {
|
|||
isSelected={this.state.type === TYPES.CUSTOM_RANGE}
|
||||
onSelect={this.setTypeCustomRange}
|
||||
>
|
||||
<div
|
||||
className="clearfix centered"
|
||||
style={{ marginTop: '8px', display: 'flex' }}
|
||||
>
|
||||
<div className="input-inline">
|
||||
<Select
|
||||
value={this.state.rel}
|
||||
onSelect={value => this.setCustomRange('rel', value)}
|
||||
onFocus={this.setTypeCustomRange}
|
||||
>
|
||||
<Select.Option value={RELATIVE_TIME_OPTIONS.LAST}>
|
||||
Last
|
||||
</Select.Option>
|
||||
<Select.Option value={RELATIVE_TIME_OPTIONS.NEXT}>
|
||||
Next
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
<div
|
||||
style={{ width: '60px' }}
|
||||
className="input-inline m-l-5 m-r-3"
|
||||
<div className="relative-timerange-container clearfix centered">
|
||||
<Select
|
||||
value={this.state.rel}
|
||||
onSelect={value => this.setCustomRange('rel', value)}
|
||||
onFocus={this.setTypeCustomRange}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
onChange={event =>
|
||||
this.setCustomRange('num', event.target.value)
|
||||
}
|
||||
onFocus={this.setTypeCustomRange}
|
||||
onPressEnter={this.close}
|
||||
value={this.state.num}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-inline">
|
||||
<Select
|
||||
value={this.state.grain}
|
||||
onFocus={this.setTypeCustomRange}
|
||||
onSelect={value => this.setCustomRange('grain', value)}
|
||||
dropdownMatchSelectWidth={false}
|
||||
>
|
||||
{grainOptions}
|
||||
</Select>
|
||||
</div>
|
||||
<Select.Option value={RELATIVE_TIME_OPTIONS.LAST}>
|
||||
Last
|
||||
</Select.Option>
|
||||
<Select.Option value={RELATIVE_TIME_OPTIONS.NEXT}>
|
||||
Next
|
||||
</Select.Option>
|
||||
</Select>
|
||||
<Input
|
||||
className="timerange-input"
|
||||
type="text"
|
||||
onChange={event =>
|
||||
this.setCustomRange('num', event.target.value)
|
||||
}
|
||||
onFocus={this.setTypeCustomRange}
|
||||
onPressEnter={this.close}
|
||||
value={this.state.num}
|
||||
/>
|
||||
<Select
|
||||
value={this.state.grain}
|
||||
onFocus={this.setTypeCustomRange}
|
||||
onSelect={value => this.setCustomRange('grain', value)}
|
||||
dropdownMatchSelectWidth={false}
|
||||
>
|
||||
{grainOptions}
|
||||
</Select>
|
||||
</div>
|
||||
</PopoverSection>
|
||||
<PopoverSection
|
||||
|
@ -505,48 +513,42 @@ class DateFilterControl extends React.Component {
|
|||
}}
|
||||
>
|
||||
<InputGroup data-test="date-input-group">
|
||||
<div style={{ margin: '5px 0' }}>
|
||||
<Datetime
|
||||
inputProps={{ 'data-test': 'date-from-input' }}
|
||||
value={this.state.since}
|
||||
defaultValue={this.state.since}
|
||||
viewDate={this.state.since}
|
||||
onChange={value =>
|
||||
this.setCustomStartEnd('since', value)
|
||||
}
|
||||
isValidDate={this.isValidSince}
|
||||
onClick={this.setTypeCustomStartEnd}
|
||||
renderInput={props =>
|
||||
this.renderInput(props, 'showSinceCalendar')
|
||||
}
|
||||
open={this.state.showSinceCalendar}
|
||||
viewMode={this.state.sinceViewMode}
|
||||
onViewModeChange={sinceViewMode =>
|
||||
this.setState({ sinceViewMode })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ margin: '5px 0' }}>
|
||||
<Datetime
|
||||
inputProps={{ 'data-test': 'date-to-input' }}
|
||||
value={this.state.until}
|
||||
defaultValue={this.state.until}
|
||||
viewDate={this.state.until}
|
||||
onChange={value =>
|
||||
this.setCustomStartEnd('until', value)
|
||||
}
|
||||
isValidDate={this.isValidUntil}
|
||||
onClick={this.setTypeCustomStartEnd}
|
||||
renderInput={props =>
|
||||
this.renderInput(props, 'showUntilCalendar')
|
||||
}
|
||||
open={this.state.showUntilCalendar}
|
||||
viewMode={this.state.untilViewMode}
|
||||
onViewModeChange={untilViewMode =>
|
||||
this.setState({ untilViewMode })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Datetime
|
||||
className="datetime"
|
||||
inputProps={{ 'data-test': 'date-from-input' }}
|
||||
value={this.state.since}
|
||||
defaultValue={this.state.since}
|
||||
viewDate={this.state.since}
|
||||
onChange={value => this.setCustomStartEnd('since', value)}
|
||||
isValidDate={this.isValidSince}
|
||||
onClick={this.setTypeCustomStartEnd}
|
||||
renderInput={props =>
|
||||
this.renderInput(props, 'showSinceCalendar')
|
||||
}
|
||||
open={this.state.showSinceCalendar}
|
||||
viewMode={this.state.sinceViewMode}
|
||||
onViewModeChange={sinceViewMode =>
|
||||
this.setState({ sinceViewMode })
|
||||
}
|
||||
/>
|
||||
<Datetime
|
||||
className="datetime"
|
||||
inputProps={{ 'data-test': 'date-to-input' }}
|
||||
value={this.state.until}
|
||||
defaultValue={this.state.until}
|
||||
viewDate={this.state.until}
|
||||
onChange={value => this.setCustomStartEnd('until', value)}
|
||||
isValidDate={this.isValidUntil}
|
||||
onClick={this.setTypeCustomStartEnd}
|
||||
renderInput={props =>
|
||||
this.renderInput(props, 'showUntilCalendar')
|
||||
}
|
||||
open={this.state.showUntilCalendar}
|
||||
viewMode={this.state.untilViewMode}
|
||||
onViewModeChange={untilViewMode =>
|
||||
this.setState({ untilViewMode })
|
||||
}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</PopoverSection>
|
||||
|
@ -564,7 +566,7 @@ class DateFilterControl extends React.Component {
|
|||
Ok
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContentStyles>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue