Skip to content

Commit

Permalink
feat(dashboard): Transition to Explore with React Router (#20606)
Browse files Browse the repository at this point in the history
* feat(dashboard): Use react-router for transition to Explore + cmd click to open in new tab

* Update tooltip

* Add a feature flag

* Update edit chart onclick event

* Fix lint

* Fix tests

* Change feature flag name

* Add tooltip to Edit chart
  • Loading branch information
kgabryje committed Jul 7, 2022
1 parent 34e1336 commit de4f7db
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 46 deletions.
1 change: 1 addition & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ assists people when migrating to a new version.
- [18936](https://github.com/apache/superset/pull/18936): Removes legacy SIP-15 interim logic/flags—specifically the `SIP_15_ENABLED`, `SIP_15_GRACE_PERIOD_END`, `SIP_15_DEFAULT_TIME_RANGE_ENDPOINTS`, and `SIP_15_TOAST_MESSAGE` flags. Time range endpoints are no longer configurable and strictly adhere to the `[start, end)` paradigm, i.e., inclusive of the start and exclusive of the end. Additionally this change removes the now obsolete `time_range_endpoints` from the form-data and resulting in the cache being busted.
- [19570](https://github.com/apache/superset/pull/19570): makes [sqloxide](https://pypi.org/project/sqloxide/) optional so the SIP-68 migration can be run on aarch64. If the migration is taking too long installing sqloxide manually should improve the performance.
- [20170](https://github.com/apache/superset/pull/20170): Introduced a new endpoint for getting datasets samples.
- [20606](https://github.com/apache/superset/pull/20606): When user clicks on chart title or "Edit chart" button in Dashboard page, Explore opens in the same tab. Clicking while holding cmd/ctrl opens Explore in a new tab. To bring back the old behaviour (always opening Explore in a new tab), flip feature flag `DASHBOARD_EDIT_CHART_IN_NEW_TAB` to `True`.

### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export enum FeatureFlag {
UX_BETA = 'UX_BETA',
GENERIC_CHART_AXES = 'GENERIC_CHART_AXES',
USE_ANALAGOUS_COLORS = 'USE_ANALAGOUS_COLORS',
DASHBOARD_EDIT_CHART_IN_NEW_TAB = 'DASHBOARD_EDIT_CHART_IN_NEW_TAB',
}
export type ScheduleQueriesProps = {
JSONSCHEMA: {
Expand Down
4 changes: 2 additions & 2 deletions superset-frontend/src/components/EditableTitle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useEffect, useState, useRef } from 'react';
import React, { MouseEvent, useEffect, useState, useRef } from 'react';
import cx from 'classnames';
import { css, styled, t } from '@superset-ui/core';
import { Tooltip } from 'src/components/Tooltip';
Expand All @@ -37,7 +37,7 @@ export interface EditableTitleProps {
placeholder?: string;
certifiedBy?: string;
certificationDetails?: string;
onClickTitle?: () => void;
onClickTitle?: (event: MouseEvent) => void;
}

const StyledCertifiedBadge = styled(CertifiedBadge)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const createProps = (overrides: any = {}) => ({

test('Should render', () => {
const props = createProps();
render(<SliceHeader {...props} />, { useRedux: true });
render(<SliceHeader {...props} />, { useRedux: true, useRouter: true });
expect(screen.getByTestId('slice-header')).toBeInTheDocument();
});

Expand Down Expand Up @@ -270,15 +270,41 @@ test('Should render click to edit prompt and run onExploreChart on click', async
render(<SliceHeader {...props} />, { useRedux: true });
userEvent.hover(screen.getByText('Vaccine Candidates per Phase'));
expect(
await screen.findByText(
'Click to edit Vaccine Candidates per Phase in a new tab',
),
await screen.findByText('Click to edit Vaccine Candidates per Phase.'),
).toBeInTheDocument();
expect(
await screen.findByText('Use ctrl + click to open in a new tab.'),
).toBeInTheDocument();

userEvent.click(screen.getByText('Vaccine Candidates per Phase'));
expect(props.onExploreChart).toHaveBeenCalled();
});

test('Display cmd button in tooltip if running on MacOS', async () => {
jest.spyOn(window.navigator, 'appVersion', 'get').mockReturnValue('Mac');
const props = createProps();
render(<SliceHeader {...props} />, { useRedux: true });
userEvent.hover(screen.getByText('Vaccine Candidates per Phase'));
expect(
await screen.findByText('Click to edit Vaccine Candidates per Phase.'),
).toBeInTheDocument();
expect(
await screen.findByText('Use ⌘ + click to open in a new tab.'),
).toBeInTheDocument();
});

test('Display correct tooltip when DASHBOARD_EDIT_CHART_IN_NEW_TAB is enabled', async () => {
window.featureFlags.DASHBOARD_EDIT_CHART_IN_NEW_TAB = true;
const props = createProps();
render(<SliceHeader {...props} />, { useRedux: true });
userEvent.hover(screen.getByText('Vaccine Candidates per Phase'));
expect(
await screen.findByText(
'Click to edit Vaccine Candidates per Phase in a new tab',
),
).toBeInTheDocument();
});

test('Should not render click to edit prompt and run onExploreChart on click if supersetCanExplore=false', () => {
const props = createProps({ supersetCanExplore: false });
render(<SliceHeader {...props} />, { useRedux: true });
Expand Down
18 changes: 11 additions & 7 deletions superset-frontend/src/dashboard/components/SliceHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC, useEffect, useMemo, useRef, useState } from 'react';
import React, {
FC,
ReactNode,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { styled, t } from '@superset-ui/core';
import { useUiConfig } from 'src/components/UiConfigContext';
import { Tooltip } from 'src/components/Tooltip';
Expand All @@ -29,6 +36,7 @@ import FiltersBadge from 'src/dashboard/components/FiltersBadge';
import Icons from 'src/components/Icons';
import { RootState } from 'src/dashboard/types';
import FilterIndicator from 'src/dashboard/components/FiltersBadge/FilterIndicator';
import { getSliceHeaderTooltip } from 'src/dashboard/util/getSliceHeaderTooltip';
import { clearDataMask } from 'src/dataMask/actions';

type SliceHeaderProps = SliceHeaderControlsProps & {
Expand Down Expand Up @@ -89,7 +97,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
}) => {
const dispatch = useDispatch();
const uiConfig = useUiConfig();
const [headerTooltip, setHeaderTooltip] = useState<string | null>(null);
const [headerTooltip, setHeaderTooltip] = useState<ReactNode | null>(null);
const headerRef = useRef<HTMLDivElement>(null);
// TODO: change to indicator field after it will be implemented
const crossFilterValue = useSelector<RootState, any>(
Expand All @@ -110,11 +118,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
useEffect(() => {
const headerElement = headerRef.current;
if (handleClickTitle) {
setHeaderTooltip(
sliceName
? t('Click to edit %s in a new tab', sliceName)
: t('Click to edit chart in a new tab'),
);
setHeaderTooltip(getSliceHeaderTooltip(sliceName));
} else if (
headerElement &&
(headerElement.scrollWidth > headerElement.offsetWidth ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import React, { MouseEvent, Key } from 'react';
import moment from 'moment';
import {
Behavior,
Expand All @@ -32,6 +32,8 @@ import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
import downloadAsImage from 'src/utils/downloadAsImage';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import CrossFilterScopingModal from 'src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingModal';
import { getSliceHeaderTooltip } from 'src/dashboard/util/getSliceHeaderTooltip';
import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
import ModalTrigger from 'src/components/ModalTrigger';
import Button from 'src/components/Button';
Expand Down Expand Up @@ -106,7 +108,7 @@ export interface SliceHeaderControlsProps {
isFullSize?: boolean;
isDescriptionExpanded?: boolean;
formData: QueryFormData;
onExploreChart: () => void;
onExploreChart: (event: MouseEvent) => void;

forceRefresh: (sliceId: number, dashboardId: number) => void;
logExploreChart?: (sliceId: number) => void;
Expand Down Expand Up @@ -170,8 +172,8 @@ class SliceHeaderControls extends React.PureComponent<
key,
domEvent,
}: {
key: React.Key;
domEvent: React.MouseEvent<HTMLElement>;
key: Key;
domEvent: MouseEvent<HTMLElement>;
}) {
switch (key) {
case MENU_KEYS.FORCE_REFRESH:
Expand Down Expand Up @@ -306,9 +308,11 @@ class SliceHeaderControls extends React.PureComponent<
{this.props.supersetCanExplore && (
<Menu.Item
key={MENU_KEYS.EXPLORE_CHART}
onClick={this.props.onExploreChart}
onClick={({ domEvent }) => this.props.onExploreChart(domEvent)}
>
{t('Edit chart')}
<Tooltip title={getSliceHeaderTooltip(this.props.slice.slice_name)}>
{t('Edit chart')}
</Tooltip>
</Menu.Item>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@
import cx from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import { styled, t, logging } from '@superset-ui/core';
import {
styled,
t,
logging,
isFeatureEnabled,
FeatureFlag,
} from '@superset-ui/core';
import { isEqual } from 'lodash';
import { withRouter } from 'react-router-dom';

import { exportChart, mountExploreUrl } from 'src/explore/exploreUtils';
import ChartContainer from 'src/components/Chart/ChartContainer';
Expand Down Expand Up @@ -120,7 +127,7 @@ const SliceContainer = styled.div`
max-height: 100%;
`;

export default class Chart extends React.Component {
class Chart extends React.Component {
constructor(props) {
super(props);
this.state = {
Expand Down Expand Up @@ -270,7 +277,9 @@ export default class Chart extends React.Component {
});
};

onExploreChart = async () => {
onExploreChart = async clickEvent => {
const isOpenInNewTab =
clickEvent.shiftKey || clickEvent.ctrlKey || clickEvent.metaKey;
try {
const lastTabId = window.localStorage.getItem('last_tab_id');
const nextTabId = lastTabId
Expand All @@ -287,7 +296,14 @@ export default class Chart extends React.Component {
[URL_PARAMS.formDataKey.name]: key,
[URL_PARAMS.sliceId.name]: this.props.slice.slice_id,
});
window.open(url, '_blank', 'noreferrer');
if (
isFeatureEnabled(FeatureFlag.DASHBOARD_EDIT_CHART_IN_NEW_TAB) ||
isOpenInNewTab
) {
window.open(url, '_blank', 'noreferrer');
} else {
this.props.history.push(url);
}
} catch (error) {
logging.error(error);
this.props.addDangerToast(t('An error occurred while opening Explore'));
Expand Down Expand Up @@ -496,3 +512,5 @@ export default class Chart extends React.Component {

Chart.propTypes = propTypes;
Chart.defaultProps = defaultProps;

export default withRouter(Chart);
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ describe('Chart', () => {
};

function setup(overrideProps) {
const wrapper = shallow(<Chart {...props} {...overrideProps} />);
const wrapper = shallow(
<Chart.WrappedComponent {...props} {...overrideProps} />,
);
return wrapper;
}

Expand All @@ -94,7 +96,10 @@ describe('Chart', () => {
});

it('should calculate the description height if it has one and isExpanded=true', () => {
const spy = jest.spyOn(Chart.prototype, 'getDescriptionHeight');
const spy = jest.spyOn(
Chart.WrappedComponent.prototype,
'getDescriptionHeight',
);
const wrapper = setup({ isExpanded: true });

expect(wrapper.find('.slice_description')).toExist();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,12 @@
*/

import React from 'react';
import mockState from 'spec/fixtures/mockState';
import { sliceId as chartId } from 'spec/fixtures/mockChartQueries';
import { screen, render } from 'spec/helpers/testing-library';
import { nativeFiltersInfo } from 'src/dashboard/fixtures/mockNativeFilters';
import newComponentFactory from 'src/dashboard/util/newComponentFactory';
import { getMockStore } from 'spec/fixtures/mockStore';
import { initialState } from 'src/SqlLab/fixtures';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { Provider } from 'react-redux';
import { screen, render } from 'spec/helpers/testing-library';
import { CHART_TYPE, ROW_TYPE } from '../../util/componentTypes';
import { ChartHolder } from './index';

Expand Down Expand Up @@ -67,17 +64,14 @@ describe('ChartHolder', () => {
fullSizeChartId: chartId,
setFullSizeChartId: () => {},
};
const mockStore = getMockStore({
...initialState,
});

const renderWrapper = () =>
render(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<ChartHolder {...defaultProps} />{' '}
</DndProvider>
</Provider>,
);
render(<ChartHolder {...defaultProps} />, {
useRouter: true,
useDnd: true,
useRedux: true,
initialState: { ...mockState, ...initialState },
});

it('should render empty state', async () => {
renderWrapper();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Provider } from 'react-redux';
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { MemoryRouter } from 'react-router-dom';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
Expand Down Expand Up @@ -71,9 +72,11 @@ describe('Column', () => {
});
const wrapper = mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<Column {...props} {...overrideProps} />
</DndProvider>
<MemoryRouter>
<DndProvider backend={HTML5Backend}>
<Column {...props} {...overrideProps} />
</DndProvider>
</MemoryRouter>
</Provider>,
{
wrappingComponent: ThemeProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { mount } from 'enzyme';
import sinon from 'sinon';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { MemoryRouter } from 'react-router-dom';

import BackgroundStyleDropdown from 'src/dashboard/components/menu/BackgroundStyleDropdown';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
Expand Down Expand Up @@ -67,9 +68,11 @@ describe('Row', () => {
});
const wrapper = mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<Row {...props} {...overrideProps} />
</DndProvider>
<MemoryRouter>
<DndProvider backend={HTML5Backend}>
<Row {...props} {...overrideProps} />
</DndProvider>
</MemoryRouter>
</Provider>,
{
wrappingComponent: ThemeProvider,
Expand Down
Loading

0 comments on commit de4f7db

Please sign in to comment.