mirror of
https://github.com/apache/superset.git
synced 2026-05-11 19:05:24 +00:00
chore: Create a generic header component for Explore and Dashboard (#20044)
* chore: Create a generic header component for Explore and Dashboard * Add tests * Fix undefined error * Remove duplicate code * Fix cypress test
This commit is contained in:
committed by
GitHub
parent
b53daa91ec
commit
1cd002e801
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import { ChartEditableTitle } from './index';
|
||||
|
||||
const createProps = (overrides: Record<string, any> = {}) => ({
|
||||
title: 'Chart title',
|
||||
placeholder: 'Add the name of the chart',
|
||||
canEdit: true,
|
||||
onSave: jest.fn(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('Chart editable title', () => {
|
||||
it('renders chart title', () => {
|
||||
const props = createProps();
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
expect(screen.getByText('Chart title')).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders placeholder', () => {
|
||||
const props = createProps({
|
||||
title: '',
|
||||
});
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
expect(screen.getByText('Add the name of the chart')).toBeVisible();
|
||||
});
|
||||
|
||||
it('click, edit and save title', () => {
|
||||
const props = createProps();
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
const textboxElement = screen.getByRole('textbox');
|
||||
userEvent.click(textboxElement);
|
||||
userEvent.type(textboxElement, ' edited');
|
||||
expect(screen.getByText('Chart title edited')).toBeVisible();
|
||||
userEvent.type(textboxElement, '{enter}');
|
||||
expect(props.onSave).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders in non-editable mode', () => {
|
||||
const props = createProps({ canEdit: false });
|
||||
render(<ChartEditableTitle {...props} />);
|
||||
const titleElement = screen.getByLabelText('Chart title');
|
||||
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
|
||||
expect(titleElement).toBeVisible();
|
||||
userEvent.click(titleElement);
|
||||
userEvent.type(titleElement, ' edited{enter}');
|
||||
expect(props.onSave).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,213 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
KeyboardEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { css, styled, t } from '@superset-ui/core';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
|
||||
export type ChartEditableTitleProps = {
|
||||
title: string;
|
||||
placeholder: string;
|
||||
onSave: (title: string) => void;
|
||||
canEdit: boolean;
|
||||
};
|
||||
|
||||
const Styles = styled.div`
|
||||
${({ theme }) => css`
|
||||
display: flex;
|
||||
font-size: ${theme.typography.sizes.xl}px;
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
& .chart-title,
|
||||
& .chart-title-input {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
& .chart-title {
|
||||
cursor: default;
|
||||
}
|
||||
& .chart-title-input {
|
||||
border: none;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: ${theme.colors.grayscale.light1};
|
||||
}
|
||||
}
|
||||
|
||||
& .input-sizer {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
display: inline-block;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const ChartEditableTitle = ({
|
||||
title,
|
||||
placeholder,
|
||||
onSave,
|
||||
canEdit,
|
||||
}: ChartEditableTitleProps) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [currentTitle, setCurrentTitle] = useState(title || '');
|
||||
const contentRef = useRef<HTMLInputElement>(null);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
const { width: inputWidth, ref: sizerRef } = useResizeDetector();
|
||||
const { width: containerWidth, ref: containerRef } = useResizeDetector({
|
||||
refreshMode: 'debounce',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && contentRef?.current) {
|
||||
contentRef.current.focus();
|
||||
// move cursor and scroll to the end
|
||||
if (contentRef.current.setSelectionRange) {
|
||||
const { length } = contentRef.current.value;
|
||||
contentRef.current.setSelectionRange(length, length);
|
||||
contentRef.current.scrollLeft = contentRef.current.scrollWidth;
|
||||
}
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
// a trick to make the input grow when user types text
|
||||
// we make additional span component, place it somewhere out of view and copy input
|
||||
// then we can measure the width of that span to resize the input element
|
||||
useLayoutEffect(() => {
|
||||
if (sizerRef?.current) {
|
||||
sizerRef.current.innerHTML = (currentTitle || placeholder).replace(
|
||||
/\s/g,
|
||||
' ',
|
||||
);
|
||||
}
|
||||
}, [currentTitle, placeholder, sizerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
contentRef.current &&
|
||||
contentRef.current.scrollWidth > contentRef.current.clientWidth
|
||||
) {
|
||||
setShowTooltip(true);
|
||||
} else {
|
||||
setShowTooltip(false);
|
||||
}
|
||||
}, [inputWidth, containerWidth]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (!canEdit || isEditing) {
|
||||
return;
|
||||
}
|
||||
setIsEditing(true);
|
||||
}, [canEdit, isEditing]);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
const formattedTitle = currentTitle.trim();
|
||||
setCurrentTitle(formattedTitle);
|
||||
if (title !== formattedTitle) {
|
||||
onSave(formattedTitle);
|
||||
}
|
||||
setIsEditing(false);
|
||||
}, [canEdit, currentTitle, onSave, title]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(ev: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!canEdit || !isEditing) {
|
||||
return;
|
||||
}
|
||||
setCurrentTitle(ev.target.value);
|
||||
},
|
||||
[canEdit, isEditing],
|
||||
);
|
||||
|
||||
const handleKeyPress = useCallback(
|
||||
(ev: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
contentRef.current?.blur();
|
||||
}
|
||||
},
|
||||
[canEdit],
|
||||
);
|
||||
|
||||
return (
|
||||
<Styles ref={containerRef}>
|
||||
<Tooltip
|
||||
id="title-tooltip"
|
||||
title={showTooltip && currentTitle && !isEditing ? currentTitle : null}
|
||||
>
|
||||
{canEdit ? (
|
||||
<input
|
||||
data-test="editable-title-input"
|
||||
className="chart-title-input"
|
||||
aria-label={t('Chart title')}
|
||||
ref={contentRef}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
onClick={handleClick}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder={placeholder}
|
||||
value={currentTitle}
|
||||
css={css`
|
||||
cursor: ${isEditing ? 'text' : 'pointer'};
|
||||
|
||||
${inputWidth &&
|
||||
inputWidth > 0 &&
|
||||
css`
|
||||
width: ${inputWidth}px;
|
||||
`}
|
||||
`}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="chart-title"
|
||||
aria-label={t('Chart title')}
|
||||
ref={contentRef}
|
||||
>
|
||||
{currentTitle}
|
||||
</span>
|
||||
)}
|
||||
</Tooltip>
|
||||
<span ref={sizerRef} className="input-sizer" aria-hidden tabIndex={-1} />
|
||||
</Styles>
|
||||
);
|
||||
};
|
||||
@@ -18,10 +18,13 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import sinon from 'sinon';
|
||||
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import fetchMock from 'fetch-mock';
|
||||
import * as chartAction from 'src/components/Chart/chartAction';
|
||||
import * as downloadAsImage from 'src/utils/downloadAsImage';
|
||||
import * as exploreUtils from 'src/explore/exploreUtils';
|
||||
import ExploreHeader from '.';
|
||||
|
||||
const chartEndpoint = 'glob:*api/v1/chart/*';
|
||||
@@ -30,6 +33,7 @@ fetchMock.get(chartEndpoint, { json: 'foo' });
|
||||
|
||||
const createProps = () => ({
|
||||
chart: {
|
||||
id: 1,
|
||||
latestQueryFormData: {
|
||||
viz_type: 'histogram',
|
||||
datasource: '49__table',
|
||||
@@ -88,17 +92,29 @@ const createProps = () => ({
|
||||
},
|
||||
slice_name: 'Age distribution of respondents',
|
||||
actions: {
|
||||
postChartFormData: () => null,
|
||||
updateChartTitle: () => null,
|
||||
fetchFaveStar: () => null,
|
||||
saveFaveStar: () => null,
|
||||
postChartFormData: jest.fn(),
|
||||
updateChartTitle: jest.fn(),
|
||||
fetchFaveStar: jest.fn(),
|
||||
saveFaveStar: jest.fn(),
|
||||
redirectSQLLab: jest.fn(),
|
||||
},
|
||||
user: {
|
||||
userId: 1,
|
||||
},
|
||||
onSaveChart: jest.fn(),
|
||||
canOverwrite: false,
|
||||
canDownload: false,
|
||||
isStarred: false,
|
||||
});
|
||||
|
||||
fetchMock.post(
|
||||
'http://api/v1/chart/data?form_data=%7B%22slice_id%22%3A318%7D',
|
||||
{ body: {} },
|
||||
{
|
||||
sendAsJson: false,
|
||||
},
|
||||
);
|
||||
|
||||
test('Cancelling changes to the properties should reset previous properties', () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
@@ -136,3 +152,208 @@ test('Save disabled', () => {
|
||||
userEvent.click(screen.getByText('Save'));
|
||||
expect(props.onSaveChart).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Additional actions tests', () => {
|
||||
test('Should render a button', () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
expect(screen.getByLabelText('Menu actions trigger')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Should open a menu', () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
|
||||
expect(screen.getByText('Edit chart properties')).toBeInTheDocument();
|
||||
expect(screen.getByText('Download')).toBeInTheDocument();
|
||||
expect(screen.getByText('Share')).toBeInTheDocument();
|
||||
expect(screen.getByText('View query')).toBeInTheDocument();
|
||||
expect(screen.getByText('Run in SQL Lab')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.queryByText('Set up an email report'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Manage email report')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Should open download submenu', async () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
|
||||
expect(screen.queryByText('Export to .CSV')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Export to .JSON')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Download as image')).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Download')).toBeInTheDocument();
|
||||
userEvent.hover(screen.getByText('Download'));
|
||||
expect(await screen.findByText('Export to .CSV')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Export to .JSON')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Download as image')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Should open share submenu', async () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
|
||||
expect(
|
||||
screen.queryByText('Copy permalink to clipboard'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Embed code')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Share chart by email')).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Share')).toBeInTheDocument();
|
||||
userEvent.hover(screen.getByText('Share'));
|
||||
expect(
|
||||
await screen.findByText('Copy permalink to clipboard'),
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('Embed code')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Share chart by email')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Should call onOpenPropertiesModal when click on "Edit chart properties"', async () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
expect(props.actions.redirectSQLLab).toBeCalledTimes(0);
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
userEvent.click(
|
||||
screen.getByRole('menuitem', { name: 'Edit chart properties' }),
|
||||
);
|
||||
expect(await screen.findByText('Edit Chart Properties')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Should call getChartDataRequest when click on "View query"', async () => {
|
||||
const props = createProps();
|
||||
const getChartDataRequest = jest.spyOn(chartAction, 'getChartDataRequest');
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
expect(getChartDataRequest).toBeCalledTimes(0);
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
expect(getChartDataRequest).toBeCalledTimes(0);
|
||||
|
||||
const menuItem = screen.getByText('View query').parentElement!;
|
||||
userEvent.click(menuItem);
|
||||
|
||||
await waitFor(() => expect(getChartDataRequest).toBeCalledTimes(1));
|
||||
});
|
||||
|
||||
test('Should call onOpenInEditor when click on "Run in SQL Lab"', () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
expect(props.actions.redirectSQLLab).toBeCalledTimes(0);
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
expect(props.actions.redirectSQLLab).toBeCalledTimes(0);
|
||||
|
||||
userEvent.click(screen.getByRole('menuitem', { name: 'Run in SQL Lab' }));
|
||||
expect(props.actions.redirectSQLLab).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('Download', () => {
|
||||
let spyDownloadAsImage = sinon.spy();
|
||||
let spyExportChart = sinon.spy();
|
||||
|
||||
beforeEach(() => {
|
||||
spyDownloadAsImage = sinon.spy(downloadAsImage, 'default');
|
||||
spyExportChart = sinon.spy(exploreUtils, 'exportChart');
|
||||
});
|
||||
afterEach(() => {
|
||||
spyDownloadAsImage.restore();
|
||||
spyExportChart.restore();
|
||||
});
|
||||
test('Should call downloadAsImage when click on "Download as image"', async () => {
|
||||
const props = createProps();
|
||||
const spy = jest.spyOn(downloadAsImage, 'default');
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
expect(spy).toBeCalledTimes(0);
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
expect(spy).toBeCalledTimes(0);
|
||||
|
||||
userEvent.hover(screen.getByText('Download'));
|
||||
const downloadAsImageElement = await screen.findByText(
|
||||
'Download as image',
|
||||
);
|
||||
userEvent.click(downloadAsImageElement);
|
||||
|
||||
expect(spy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Should not export to CSV if canDownload=false', async () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
userEvent.hover(screen.getByText('Download'));
|
||||
const exportCSVElement = await screen.findByText('Export to .CSV');
|
||||
userEvent.click(exportCSVElement);
|
||||
expect(spyExportChart.callCount).toBe(0);
|
||||
spyExportChart.restore();
|
||||
});
|
||||
|
||||
test('Should export to CSV if canDownload=true', async () => {
|
||||
const props = createProps();
|
||||
props.canDownload = true;
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
userEvent.hover(screen.getByText('Download'));
|
||||
const exportCSVElement = await screen.findByText('Export to .CSV');
|
||||
userEvent.click(exportCSVElement);
|
||||
expect(spyExportChart.callCount).toBe(1);
|
||||
spyExportChart.restore();
|
||||
});
|
||||
|
||||
test('Should export to JSON', async () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
userEvent.hover(screen.getByText('Download'));
|
||||
const exportJsonElement = await screen.findByText('Export to .JSON');
|
||||
userEvent.click(exportJsonElement);
|
||||
expect(spyExportChart.callCount).toBe(1);
|
||||
});
|
||||
|
||||
test('Should export to pivoted CSV if canDownloadCSV=true and viz_type=pivot_table_v2', async () => {
|
||||
const props = createProps();
|
||||
props.canDownload = true;
|
||||
props.chart.latestQueryFormData.viz_type = 'pivot_table_v2';
|
||||
render(<ExploreHeader {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByLabelText('Menu actions trigger'));
|
||||
userEvent.hover(screen.getByText('Download'));
|
||||
const exportCSVElement = await screen.findByText(
|
||||
'Export to pivoted .CSV',
|
||||
);
|
||||
userEvent.click(exportCSVElement);
|
||||
expect(spyExportChart.callCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -30,14 +30,12 @@ import {
|
||||
import { toggleActive, deleteActiveReport } from 'src/reports/actions/reports';
|
||||
import { chartPropShape } from 'src/dashboard/util/propShapes';
|
||||
import AlteredSliceTag from 'src/components/AlteredSliceTag';
|
||||
import FaveStar from 'src/components/FaveStar';
|
||||
import Button from 'src/components/Button';
|
||||
import Icons from 'src/components/Icons';
|
||||
import PropertiesModal from 'src/explore/components/PropertiesModal';
|
||||
import { sliceUpdated } from 'src/explore/actions/exploreActions';
|
||||
import CertifiedBadge from 'src/components/CertifiedBadge';
|
||||
import ExploreAdditionalActionsMenu from '../ExploreAdditionalActionsMenu';
|
||||
import { ChartEditableTitle } from './ChartEditableTitle';
|
||||
import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
|
||||
import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu';
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
@@ -48,7 +46,7 @@ const propTypes = {
|
||||
slice: PropTypes.object,
|
||||
sliceName: PropTypes.string,
|
||||
table_name: PropTypes.string,
|
||||
form_data: PropTypes.object,
|
||||
formData: PropTypes.object,
|
||||
ownState: PropTypes.object,
|
||||
timeout: PropTypes.number,
|
||||
chart: chartPropShape,
|
||||
@@ -62,70 +60,25 @@ const saveButtonStyles = theme => css`
|
||||
}
|
||||
`;
|
||||
|
||||
const headerStyles = theme => css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
export const ExploreChartHeader = ({
|
||||
dashboardId,
|
||||
slice,
|
||||
actions,
|
||||
formData,
|
||||
chart,
|
||||
user,
|
||||
canOverwrite,
|
||||
canDownload,
|
||||
isStarred,
|
||||
sliceUpdated,
|
||||
sliceName,
|
||||
onSaveChart,
|
||||
saveDisabled,
|
||||
}) => {
|
||||
const { latestQueryFormData, sliceFormData } = chart;
|
||||
const [isPropertiesModalOpen, setIsPropertiesModalOpen] = useState(false);
|
||||
|
||||
span[role='button'] {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
margin-right: ${theme.gridUnit * 12}px;
|
||||
}
|
||||
|
||||
.right-button-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const buttonsStyles = theme => css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: ${theme.gridUnit * 2}px;
|
||||
|
||||
& .fave-unfave-icon {
|
||||
padding: 0 ${theme.gridUnit}px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const saveButtonContainerStyles = theme => css`
|
||||
margin-right: ${theme.gridUnit * 2}px;
|
||||
`;
|
||||
|
||||
export class ExploreChartHeader extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isPropertiesModalOpen: false,
|
||||
};
|
||||
this.openPropertiesModal = this.openPropertiesModal.bind(this);
|
||||
this.closePropertiesModal = this.closePropertiesModal.bind(this);
|
||||
this.fetchChartDashboardData = this.fetchChartDashboardData.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { dashboardId } = this.props;
|
||||
if (dashboardId) {
|
||||
this.fetchChartDashboardData();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchChartDashboardData() {
|
||||
const { dashboardId, slice } = this.props;
|
||||
const fetchChartDashboardData = async () => {
|
||||
await SupersetClient.get({
|
||||
endpoint: `/api/v1/chart/${slice.slice_id}`,
|
||||
})
|
||||
@@ -162,96 +115,71 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
postChartFormData() {
|
||||
this.props.actions.postChartFormData(
|
||||
this.props.form_data,
|
||||
true,
|
||||
this.props.timeout,
|
||||
this.props.chart.id,
|
||||
this.props.ownState,
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (dashboardId) {
|
||||
fetchChartDashboardData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
openPropertiesModal() {
|
||||
this.setState({
|
||||
isPropertiesModalOpen: true,
|
||||
});
|
||||
}
|
||||
const openPropertiesModal = () => {
|
||||
setIsPropertiesModalOpen(true);
|
||||
};
|
||||
|
||||
closePropertiesModal() {
|
||||
this.setState({
|
||||
isPropertiesModalOpen: false,
|
||||
});
|
||||
}
|
||||
const closePropertiesModal = () => {
|
||||
setIsPropertiesModalOpen(false);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
actions,
|
||||
chart,
|
||||
user,
|
||||
formData,
|
||||
slice,
|
||||
canOverwrite,
|
||||
const [menu, isDropdownVisible, setIsDropdownVisible] =
|
||||
useExploreAdditionalActionsMenu(
|
||||
latestQueryFormData,
|
||||
canDownload,
|
||||
isStarred,
|
||||
sliceUpdated,
|
||||
sliceName,
|
||||
onSaveChart,
|
||||
saveDisabled,
|
||||
} = this.props;
|
||||
const { latestQueryFormData, sliceFormData } = chart;
|
||||
const oldSliceName = slice?.slice_name;
|
||||
return (
|
||||
<div id="slice-header" css={headerStyles}>
|
||||
<div className="title-panel">
|
||||
<ChartEditableTitle
|
||||
title={sliceName}
|
||||
canEdit={
|
||||
!slice ||
|
||||
canOverwrite ||
|
||||
(slice?.owners || []).includes(user?.userId)
|
||||
}
|
||||
onSave={actions.updateChartTitle}
|
||||
placeholder={t('Add the name of the chart')}
|
||||
/>
|
||||
{slice && (
|
||||
<span css={buttonsStyles}>
|
||||
{slice.certified_by && (
|
||||
<CertifiedBadge
|
||||
certifiedBy={slice.certified_by}
|
||||
details={slice.certification_details}
|
||||
/>
|
||||
)}
|
||||
{user.userId && (
|
||||
<FaveStar
|
||||
itemId={slice.slice_id}
|
||||
fetchFaveStar={actions.fetchFaveStar}
|
||||
saveFaveStar={actions.saveFaveStar}
|
||||
isStarred={isStarred}
|
||||
showTooltip
|
||||
/>
|
||||
)}
|
||||
{this.state.isPropertiesModalOpen && (
|
||||
<PropertiesModal
|
||||
show={this.state.isPropertiesModalOpen}
|
||||
onHide={this.closePropertiesModal}
|
||||
onSave={sliceUpdated}
|
||||
slice={slice}
|
||||
/>
|
||||
)}
|
||||
{sliceFormData && (
|
||||
<AlteredSliceTag
|
||||
className="altered"
|
||||
origFormData={{ ...sliceFormData, chartTitle: oldSliceName }}
|
||||
currentFormData={{ ...formData, chartTitle: sliceName }}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="right-button-panel">
|
||||
slice,
|
||||
actions.redirectSQLLab,
|
||||
openPropertiesModal,
|
||||
);
|
||||
|
||||
const oldSliceName = slice?.slice_name;
|
||||
return (
|
||||
<>
|
||||
<PageHeaderWithActions
|
||||
editableTitleProps={{
|
||||
title: sliceName,
|
||||
canEdit:
|
||||
!slice ||
|
||||
canOverwrite ||
|
||||
(slice?.owners || []).includes(user?.userId),
|
||||
onSave: actions.updateChartTitle,
|
||||
placeholder: t('Add the name of the chart'),
|
||||
label: t('Chart title'),
|
||||
}}
|
||||
showTitlePanelItems={!!slice}
|
||||
certificatiedBadgeProps={{
|
||||
certifiedBy: slice?.certified_by,
|
||||
details: slice?.certification_details,
|
||||
}}
|
||||
showFaveStar={!!user?.userId}
|
||||
faveStarProps={{
|
||||
itemId: slice?.slice_id,
|
||||
fetchFaveStar: actions.fetchFaveStar,
|
||||
saveFaveStar: actions.saveFaveStar,
|
||||
isStarred,
|
||||
showTooltip: true,
|
||||
}}
|
||||
titlePanelAdditionalItems={
|
||||
sliceFormData ? (
|
||||
<AlteredSliceTag
|
||||
className="altered"
|
||||
origFormData={{
|
||||
...sliceFormData,
|
||||
chartTitle: oldSliceName,
|
||||
}}
|
||||
currentFormData={{ ...formData, chartTitle: sliceName }}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
rightPanelAdditionalItems={
|
||||
<Tooltip
|
||||
title={
|
||||
saveDisabled
|
||||
@@ -260,7 +188,7 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
}
|
||||
>
|
||||
{/* needed to wrap button in a div - antd tooltip doesn't work with disabled button */}
|
||||
<div css={saveButtonContainerStyles}>
|
||||
<div>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
onClick={onSaveChart}
|
||||
@@ -273,18 +201,24 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<ExploreAdditionalActionsMenu
|
||||
onOpenInEditor={actions.redirectSQLLab}
|
||||
onOpenPropertiesModal={this.openPropertiesModal}
|
||||
slice={slice}
|
||||
canDownloadCSV={canDownload}
|
||||
latestQueryFormData={latestQueryFormData}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
additionalActionsMenu={menu}
|
||||
menuDropdownProps={{
|
||||
visible: isDropdownVisible,
|
||||
onVisibleChange: setIsDropdownVisible,
|
||||
}}
|
||||
/>
|
||||
{isPropertiesModalOpen && (
|
||||
<PropertiesModal
|
||||
show={isPropertiesModalOpen}
|
||||
onHide={closePropertiesModal}
|
||||
onSave={sliceUpdated}
|
||||
slice={slice}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ExploreChartHeader.propTypes = propTypes;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user