From c25ea11ded29175b0d0d8801637a96e3aba82ce2 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Tue, 29 Mar 2022 19:43:49 +0200 Subject: [PATCH 01/12] feat(explore): Move chart actions to a dropdown menu --- .../src/components/ModalTrigger/index.jsx | 2 + .../src/components/ReportModal/index.tsx | 25 +- .../src/dashboard/components/Header/index.jsx | 10 +- .../explore/components/EmbedCodeButton.jsx | 234 +++++---- .../components/ExploreActionButtons.tsx | 208 +------- .../ExploreReport.tsx | 92 ++++ .../ExploreAdditionalActionsMenu/index.jsx | 443 ++++++++++++++---- .../components/ExploreChartHeader/index.jsx | 131 +++--- 8 files changed, 651 insertions(+), 494 deletions(-) create mode 100644 superset-frontend/src/explore/components/ExploreAdditionalActionsMenu/ExploreReport.tsx diff --git a/superset-frontend/src/components/ModalTrigger/index.jsx b/superset-frontend/src/components/ModalTrigger/index.jsx index e01e6591adb07..b15000d851fb3 100644 --- a/superset-frontend/src/components/ModalTrigger/index.jsx +++ b/superset-frontend/src/components/ModalTrigger/index.jsx @@ -39,6 +39,7 @@ const propTypes = { resizableConfig: PropTypes.object, draggable: PropTypes.bool, draggableConfig: PropTypes.object, + destroyOnClose: PropTypes.bool, }; const defaultProps = { @@ -89,6 +90,7 @@ export default class ModalTrigger extends React.Component { resizableConfig={this.props.resizableConfig} draggable={this.props.draggable} draggableConfig={this.props.draggableConfig} + destroyOnClose={this.props.destroyOnClose} > {this.props.modalBody} diff --git a/superset-frontend/src/components/ReportModal/index.tsx b/superset-frontend/src/components/ReportModal/index.tsx index c8273bd985801..5cdc216a0e67f 100644 --- a/superset-frontend/src/components/ReportModal/index.tsx +++ b/superset-frontend/src/components/ReportModal/index.tsx @@ -26,8 +26,7 @@ import React, { } from 'react'; import { t, SupersetTheme } from '@superset-ui/core'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; -import { bindActionCreators } from 'redux'; -import { connect, useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { addReport, editReport } from 'src/reports/actions/reports'; import { AlertObject } from 'src/views/CRUD/alert/types'; @@ -85,6 +84,7 @@ interface ChartObject { chartUpdateEndTime: number; chartUpdateStartTime: number; latestQueryFormData: object; + sliceFormData: Record; queryController: { abort: () => {} }; queriesResponse: object; triggerQuery: boolean; @@ -92,7 +92,6 @@ interface ChartObject { } interface ReportProps { - addReport: (report?: ReportObject) => {}; onHide: () => {}; onReportAdd: (report?: ReportObject) => {}; addDangerToast: (msg: string) => void; @@ -102,7 +101,6 @@ interface ReportProps { dashboardId?: number; chart?: ChartObject; creationMethod: string; - props: any; } interface ReportPayloadType { @@ -189,8 +187,8 @@ const ReportModal: FunctionComponent = ({ show = false, ...props }) => { - const vizType = props.props.chart?.sliceFormData?.viz_type; - const isChart = !!props.props.chart; + const vizType = props.chart?.sliceFormData?.viz_type; + const isChart = !!props.chart; const defaultNotificationFormat = isChart && TEXT_BASED_VISUALIZATION_TYPES.includes(vizType) ? NOTIFICATION_FORMATS.TEXT @@ -226,19 +224,19 @@ const ReportModal: FunctionComponent = ({ // Create new Report const newReportValues: Partial = { crontab: currentReport?.crontab, - dashboard: props.props.dashboardId, - chart: props.props.chart?.id, + dashboard: props.dashboardId, + chart: props.chart?.id, description: currentReport?.description, name: currentReport?.name, - owners: [props.props.userId], + owners: [props.userId], recipients: [ { - recipient_config_json: { target: props.props.userEmail }, + recipient_config_json: { target: props.userEmail }, type: 'Email', }, ], type: 'Report', - creation_method: props.props.creationMethod, + creation_method: props.creationMethod, active: true, report_format: currentReport?.report_format || defaultNotificationFormat, timezone: currentReport?.timezone, @@ -416,7 +414,4 @@ const ReportModal: FunctionComponent = ({ ); }; -const mapDispatchToProps = (dispatch: any) => - bindActionCreators({ addReport, editReport }, dispatch); - -export default connect(null, mapDispatchToProps)(withToasts(ReportModal)); +export default withToasts(ReportModal); diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index ad668daf79a53..af68742b4ab17 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -662,12 +662,10 @@ class Header extends React.PureComponent { )} diff --git a/superset-frontend/src/explore/components/EmbedCodeButton.jsx b/superset-frontend/src/explore/components/EmbedCodeButton.jsx index 71f77a4621fa2..b7f53e7c7a4a3 100644 --- a/superset-frontend/src/explore/components/EmbedCodeButton.jsx +++ b/superset-frontend/src/explore/components/EmbedCodeButton.jsx @@ -16,56 +16,60 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; -import { t } from '@superset-ui/core'; - -import Popover from 'src/components/Popover'; -import { FormLabel } from 'src/components/Form'; -import Icons from 'src/components/Icons'; -import { Tooltip } from 'src/components/Tooltip'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { css, styled, t } from '@superset-ui/core'; +import { Input, TextArea } from 'src/components/Input'; import CopyToClipboard from 'src/components/CopyToClipboard'; import { URL_PARAMS } from 'src/constants'; import { getChartPermalink } from 'src/utils/urlUtils'; +import { CopyButton } from './DataTableControl'; -export default class EmbedCodeButton extends React.Component { - constructor(props) { - super(props); - this.state = { - height: '400', - width: '600', - url: '', - errorMessage: '', - }; - this.handleInputChange = this.handleInputChange.bind(this); - this.updateUrl = this.updateUrl.bind(this); +const CopyButtonEmbedCode = styled(CopyButton)` + && { + margin: 0 0 ${({ theme }) => theme.gridUnit}px; } +`; + +const EmbedCodeButton = ({ formData, addDangerToast }) => { + const [height, setHeight] = useState('400'); + const [width, setWidth] = useState('600'); + const [url, setUrl] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); - handleInputChange(e) { + const handleInputChange = useCallback(e => { const { value, name } = e.currentTarget; - const data = {}; - data[name] = value; - this.setState(data); - } + if (name === 'width') { + setWidth(value); + } + if (name === 'height') { + setHeight(value); + } + }, []); - updateUrl() { - this.setState({ url: '' }); - getChartPermalink(this.props.formData) - .then(url => this.setState({ errorMessage: '', url })) + const updateUrl = useCallback(() => { + setUrl(''); + getChartPermalink(formData) + .then(url => { + setUrl(url); + setErrorMessage(''); + }) .catch(() => { - this.setState({ errorMessage: t('Error') }); - this.props.addDangerToast( - t('Sorry, something went wrong. Try again later.'), - ); + setErrorMessage(t('Error')); + addDangerToast(t('Sorry, something went wrong. Try again later.')); }); - } + }, [addDangerToast, formData]); - generateEmbedHTML() { - if (!this.state.url) return ''; - const srcLink = `${this.state.url}?${URL_PARAMS.standalone.name}=1&height=${this.state.height}`; + useEffect(() => { + updateUrl(); + }, []); + + const html = useMemo(() => { + if (!url) return ''; + const srcLink = `${url}?${URL_PARAMS.standalone.name}=1&height=${height}`; return ( '\n' + '' ); - } + }, [height, url, width]); - renderPopoverContent() { - const html = this.generateEmbedHTML(); - const text = - this.state.errorMessage || html || t('Generating link, please wait..'); - return ( -
-
-
-