Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): Chart title click redirects to Explore in new tab #20111

Merged
merged 1 commit into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions superset-frontend/src/components/EditableTitle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
import React, { useEffect, useState, useRef } from 'react';
import cx from 'classnames';
import { styled, t } from '@superset-ui/core';
import { css, styled, t } from '@superset-ui/core';
import { Tooltip } from 'src/components/Tooltip';
import CertifiedBadge from '../CertifiedBadge';

Expand All @@ -37,6 +37,7 @@ export interface EditableTitleProps {
placeholder?: string;
certifiedBy?: string;
certificationDetails?: string;
onClickTitle?: () => void;
}

const StyledCertifiedBadge = styled(CertifiedBadge)`
Expand All @@ -57,6 +58,7 @@ export default function EditableTitle({
placeholder = '',
certifiedBy,
certificationDetails,
onClickTitle,
// rest is related to title tooltip
...rest
}: EditableTitleProps) {
Expand Down Expand Up @@ -216,7 +218,23 @@ export default function EditableTitle({
}
if (!canEdit) {
// don't actually want an input in this case
titleComponent = <span data-test="editable-title-input">{value}</span>;
titleComponent = onClickTitle ? (
<span
role="button"
onClick={onClickTitle}
tabIndex={0}
data-test="editable-title-input"
css={css`
:hover {
text-decoration: underline;
}
`}
>
{value}
</span>
) : (
<span data-test="editable-title-input">{value}</span>
);
}
return (
<span
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jest.mock('src/dashboard/components/FiltersBadge', () => ({
),
}));

const createProps = () => ({
const createProps = (overrides: any = {}) => ({
filters: {}, // is in typing but not being used
editMode: false,
annotationQuery: { param01: 'annotationQuery' } as any,
Expand Down Expand Up @@ -159,6 +159,7 @@ const createProps = () => ({
formData: { slice_id: 1, datasource: '58__table' },
width: 100,
height: 100,
...overrides,
});

test('Should render', () => {
Expand Down Expand Up @@ -264,6 +265,48 @@ test('Should render title', () => {
expect(screen.getByText('Vaccine Candidates per Phase')).toBeInTheDocument();
});

test('Should render click to edit prompt and run onExploreChart on click', async () => {
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();

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

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 });
userEvent.hover(screen.getByText('Vaccine Candidates per Phase'));
expect(
screen.queryByText(
'Click to edit Vaccine Candidates per Phase in a new tab',
),
).not.toBeInTheDocument();

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

test('Should not render click to edit prompt and run onExploreChart on click if in edit mode', () => {
const props = createProps({ editMode: true });
render(<SliceHeader {...props} />, { useRedux: true });
userEvent.hover(screen.getByText('Vaccine Candidates per Phase'));
expect(
screen.queryByText(
'Click to edit Vaccine Candidates per Phase in a new tab',
),
).not.toBeInTheDocument();

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

test('Should render "annotationsLoading"', () => {
const props = createProps();
render(<SliceHeader {...props} />, { useRedux: true });
Expand Down
14 changes: 12 additions & 2 deletions superset-frontend/src/dashboard/components/SliceHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,18 @@ const SliceHeader: FC<SliceHeaderProps> = ({
[crossFilterValue],
);

const handleClickTitle =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use useCallback/useMemo to keep the same function reference?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually is the same reference! We don't create a new function here, only either assign the one from props or undefined. So unless onExploreChart changes on every render (and if it does, we should fix it there), the reference will stay the same

!editMode && supersetCanExplore ? onExploreChart : undefined;

useEffect(() => {
const headerElement = headerRef.current;
if (
if (handleClickTitle) {
setHeaderTooltip(
sliceName
? t('Click to edit %s in a new tab', sliceName)
: t('Click to edit chart in a new tab'),
Comment on lines +114 to +116
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kind of surprised by sliceName being optional:
image
Not a blocker, but would be interesting to see if it really should be optional.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think those checks are for the edge case where sliceName is an empty string (not sure if it's possible to achieve such state)

);
} else if (
headerElement &&
(headerElement.scrollWidth > headerElement.offsetWidth ||
headerElement.scrollHeight > headerElement.offsetHeight)
Expand All @@ -115,7 +124,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
} else {
setHeaderTooltip(null);
}
}, [sliceName, width, height]);
}, [sliceName, width, height, handleClickTitle]);

return (
<div className="chart-header" data-test="slice-header" ref={innerRef}>
Expand All @@ -132,6 +141,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
emptyText=""
onSaveTitle={updateSliceName}
showTooltip={false}
onClickTitle={handleClickTitle}
/>
</Tooltip>
{!!Object.values(annotationQuery).length && (
Expand Down