diff --git a/superset-frontend/src/dashboard/components/gridComponents/Column.jsx b/superset-frontend/src/dashboard/components/gridComponents/Column.jsx index d11937ab176ab..71c1892f3b887 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Column.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Column.jsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { PureComponent, Fragment } from 'react'; +import { Fragment, useCallback, useState, useMemo, memo } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { css, styled, t } from '@superset-ui/core'; @@ -119,203 +119,219 @@ const emptyColumnContentStyles = theme => css` color: ${theme.colors.text.label}; `; -class Column extends PureComponent { - constructor(props) { - super(props); - this.state = { - isFocused: false, - }; - this.handleChangeBackground = this.handleUpdateMeta.bind( - this, - 'background', - ); - this.handleChangeFocus = this.handleChangeFocus.bind(this); - this.handleDeleteComponent = this.handleDeleteComponent.bind(this); - } +const Column = props => { + const { + component: columnComponent, + parentComponent, + index, + availableColumnCount, + columnWidth, + minColumnWidth, + depth, + onResizeStart, + onResize, + onResizeStop, + handleComponentDrop, + editMode, + onChangeTab, + isComponentVisible, + deleteComponent, + id, + parentId, + updateComponents, + } = props; - handleDeleteComponent() { - const { deleteComponent, id, parentId } = this.props; + const [isFocused, setIsFocused] = useState(false); + + const handleDeleteComponent = useCallback(() => { deleteComponent(id, parentId); - } + }, [deleteComponent, id, parentId]); - handleChangeFocus(nextFocus) { - this.setState(() => ({ isFocused: Boolean(nextFocus) })); - } + const handleChangeFocus = useCallback(nextFocus => { + setIsFocused(Boolean(nextFocus)); + }, []); - handleUpdateMeta(metaKey, nextValue) { - const { updateComponents, component } = this.props; - if (nextValue && component.meta[metaKey] !== nextValue) { - updateComponents({ - [component.id]: { - ...component, - meta: { - ...component.meta, - [metaKey]: nextValue, + const handleChangeBackground = useCallback( + nextValue => { + const metaKey = 'background'; + if (nextValue && columnComponent.meta[metaKey] !== nextValue) { + updateComponents({ + [columnComponent.id]: { + ...columnComponent, + meta: { + ...columnComponent.meta, + [metaKey]: nextValue, + }, }, - }, - }); - } - } + }); + } + }, + [columnComponent, updateComponents], + ); - render() { - const { - component: columnComponent, - parentComponent, - index, - availableColumnCount, - columnWidth, - minColumnWidth, - depth, - onResizeStart, - onResize, - onResizeStop, - handleComponentDrop, - editMode, - onChangeTab, - isComponentVisible, - } = this.props; + const columnItems = useMemo( + () => columnComponent.children || [], + [columnComponent.children], + ); - const columnItems = columnComponent.children || []; - const backgroundStyle = backgroundStyleOptions.find( - opt => - opt.value === - (columnComponent.meta.background || BACKGROUND_TRANSPARENT), - ); + const backgroundStyle = backgroundStyleOptions.find( + opt => + opt.value === (columnComponent.meta.background || BACKGROUND_TRANSPARENT), + ); - return ( - ( + - {({ dragSourceRef }) => ( - , + ]} + editMode={editMode} + > + {editMode && ( + + + + } + /> + + )} + - , - ]} - editMode={editMode} - > - {editMode && ( - - - - } - /> - - )} - - {editMode && ( - 0 && 'droptarget-edge', - )} - editMode - > - {({ dropIndicatorProps }) => - dropIndicatorProps &&
+ {editMode && ( + + : { + component: columnItems[0], + })} + depth={depth} + index={0} + orientation="column" + onDrop={handleComponentDrop} + className={cx( + 'empty-droptarget', + columnItems.length > 0 && 'droptarget-edge', )} - {columnItems.length === 0 ? ( -
{t('Empty column')}
- ) : ( - columnItems.map((componentId, itemIndex) => ( - - - {editMode && ( - - {({ dropIndicatorProps }) => - dropIndicatorProps && ( -
- ) - } - + editMode + > + {({ dropIndicatorProps }) => + dropIndicatorProps &&
+ } + + )} + {columnItems.length === 0 ? ( +
{t('Empty column')}
+ ) : ( + columnItems.map((componentId, itemIndex) => ( + + + {editMode && ( + - )) - )} - - - - )} - - ); - } -} + editMode + > + {({ dropIndicatorProps }) => + dropIndicatorProps &&
+ } + + )} + + )) + )} + + + + ), + [ + availableColumnCount, + backgroundStyle.className, + columnComponent, + columnItems, + columnWidth, + depth, + editMode, + handleChangeBackground, + handleChangeFocus, + handleComponentDrop, + handleDeleteComponent, + isComponentVisible, + isFocused, + minColumnWidth, + onChangeTab, + onResize, + onResizeStart, + onResizeStop, + ], + ); + + return ( + + {renderChild} + + ); +}; Column.propTypes = propTypes; Column.defaultProps = defaultProps; -export default Column; +export default memo(Column); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx index 60a499dbe4183..1523e6fe3e3c4 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx @@ -23,6 +23,7 @@ import { useRef, useEffect, useMemo, + memo, } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; @@ -425,4 +426,4 @@ const Row = props => { Row.propTypes = propTypes; -export default Row; +export default memo(Row); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx index bebcb3916b002..a477420249455 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx @@ -20,7 +20,7 @@ import { fireEvent, render } from 'spec/helpers/testing-library'; import { AntdModal } from 'src/components'; import fetchMock from 'fetch-mock'; -import { Tabs } from 'src/dashboard/components/gridComponents/Tabs'; +import Tabs from 'src/dashboard/components/gridComponents/Tabs'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout'; import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout'; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts index 095a9edaf3de8..db21db547d597 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts @@ -19,6 +19,7 @@ import { Behavior, FeatureFlag } from '@superset-ui/core'; import * as uiCore from '@superset-ui/core'; import { DashboardLayout } from 'src/dashboard/types'; +import { CHART_TYPE } from 'src/dashboard/util/componentTypes'; import { nativeFilterGate, findTabsWithChartsInScope } from './utils'; let isFeatureEnabledMock: jest.MockInstance; @@ -119,7 +120,10 @@ test('findTabsWithChartsInScope should handle a recursive layout structure', () }, } as any as DashboardLayout; - expect(Array.from(findTabsWithChartsInScope(dashboardLayout, []))).toEqual( + const chartLayoutItems = Object.values(dashboardLayout).filter( + item => item.type === CHART_TYPE, + ); + expect(Array.from(findTabsWithChartsInScope(chartLayoutItems, []))).toEqual( [], ); });