mirror of https://github.com/apache/superset.git
perf(dashboard): Virtualization POC (#21438)
This commit is contained in:
parent
070b865e32
commit
406e44bba1
|
@ -31,6 +31,7 @@ export enum FeatureFlag {
|
|||
DASHBOARD_FILTERS_EXPERIMENTAL = 'DASHBOARD_FILTERS_EXPERIMENTAL',
|
||||
DASHBOARD_NATIVE_FILTERS = 'DASHBOARD_NATIVE_FILTERS',
|
||||
DASHBOARD_NATIVE_FILTERS_SET = 'DASHBOARD_NATIVE_FILTERS_SET',
|
||||
DASHBOARD_VIRTUALIZATION = 'DASHBOARD_VIRTUALIZATION',
|
||||
DASHBOARD_RBAC = 'DASHBOARD_RBAC',
|
||||
DATAPANEL_CLOSED_BY_DEFAULT = 'DATAPANEL_CLOSED_BY_DEFAULT',
|
||||
DISABLE_DATASET_SOURCE_EDIT = 'DISABLE_DATASET_SOURCE_EDIT',
|
||||
|
|
|
@ -28,6 +28,7 @@ import ErrorBoundary from 'src/components/ErrorBoundary';
|
|||
import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import { isCurrentUserBot } from 'src/utils/isBot';
|
||||
import { ResourceStatus } from 'src/hooks/apiResources/apiResources';
|
||||
import ChartRenderer from './ChartRenderer';
|
||||
import { ChartErrorMessage } from './ChartErrorMessage';
|
||||
|
@ -74,6 +75,7 @@ const propTypes = {
|
|||
ownState: PropTypes.object,
|
||||
postTransformProps: PropTypes.func,
|
||||
datasetsStatus: PropTypes.oneOf(['loading', 'error', 'complete']),
|
||||
isInView: PropTypes.bool,
|
||||
};
|
||||
|
||||
const BLANK = {};
|
||||
|
@ -92,6 +94,7 @@ const defaultProps = {
|
|||
chartStackTrace: null,
|
||||
isDeactivatedViz: false,
|
||||
force: false,
|
||||
isInView: true,
|
||||
};
|
||||
|
||||
const Styles = styled.div`
|
||||
|
@ -307,11 +310,17 @@ class Chart extends React.PureComponent {
|
|||
width={width}
|
||||
>
|
||||
<div className="slice_container" data-test="slice-container">
|
||||
<ChartRenderer
|
||||
{...this.props}
|
||||
source={this.props.dashboardId ? 'dashboard' : 'explore'}
|
||||
data-test={this.props.vizType}
|
||||
/>
|
||||
{this.props.isInView ||
|
||||
!isFeatureEnabled(FeatureFlag.DASHBOARD_VIRTUALIZATION) ||
|
||||
isCurrentUserBot() ? (
|
||||
<ChartRenderer
|
||||
{...this.props}
|
||||
source={this.props.dashboardId ? 'dashboard' : 'explore'}
|
||||
data-test={this.props.vizType}
|
||||
/>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
{isLoading && !isDeactivatedViz && <Loading />}
|
||||
</Styles>
|
||||
|
|
|
@ -92,6 +92,7 @@ const propTypes = {
|
|||
filterState: PropTypes.object,
|
||||
postTransformProps: PropTypes.func,
|
||||
datasetsStatus: PropTypes.oneOf(['loading', 'error', 'complete']),
|
||||
isInView: PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
|
@ -382,6 +383,7 @@ class Chart extends React.Component {
|
|||
filterboxMigrationState,
|
||||
postTransformProps,
|
||||
datasetsStatus,
|
||||
isInView,
|
||||
} = this.props;
|
||||
|
||||
const { width } = this.state;
|
||||
|
@ -511,6 +513,7 @@ class Chart extends React.Component {
|
|||
filterboxMigrationState={filterboxMigrationState}
|
||||
postTransformProps={postTransformProps}
|
||||
datasetsStatus={datasetsStatus}
|
||||
isInView={isInView}
|
||||
/>
|
||||
</div>
|
||||
</SliceContainer>
|
||||
|
|
|
@ -137,7 +137,7 @@ describe('ChartHolder', () => {
|
|||
|
||||
rerender(
|
||||
<Provider store={store}>
|
||||
<ChartHolder {...defaultProps} editMode />
|
||||
<ChartHolder {...defaultProps} editMode isInView />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
|
@ -414,6 +414,7 @@ describe('ChartHolder', () => {
|
|||
deleteComponent={deleteComponent}
|
||||
fullSizeChartId={null}
|
||||
editMode
|
||||
isInView
|
||||
/>
|
||||
</Provider>,
|
||||
);
|
||||
|
|
|
@ -67,6 +67,8 @@ interface ChartHolderProps {
|
|||
handleComponentDrop: (...args: unknown[]) => unknown;
|
||||
setFullSizeChartId: (chartId: number | null) => void;
|
||||
postAddSliceFromDashboard?: () => void;
|
||||
|
||||
isInView: boolean;
|
||||
}
|
||||
|
||||
const ChartHolder: React.FC<ChartHolderProps> = ({
|
||||
|
@ -91,6 +93,7 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
|
|||
handleComponentDrop,
|
||||
setFullSizeChartId,
|
||||
postAddSliceFromDashboard,
|
||||
isInView,
|
||||
}) => {
|
||||
const { chartId } = component.meta;
|
||||
const isFullSize = fullSizeChartId === chartId;
|
||||
|
@ -314,6 +317,7 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
|
|||
setControlValue={handleExtraControl}
|
||||
extraControls={extraControls}
|
||||
postTransformProps={handlePostTransformProps}
|
||||
isInView={isInView}
|
||||
/>
|
||||
{editMode && (
|
||||
<HoverMenu position="top">
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
|
||||
|
||||
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
|
||||
import DragHandle from 'src/dashboard/components/dnd/DragHandle';
|
||||
|
@ -32,6 +33,7 @@ import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
|
|||
import { componentShape } from 'src/dashboard/util/propShapes';
|
||||
import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions';
|
||||
import { BACKGROUND_TRANSPARENT } from 'src/dashboard/util/constants';
|
||||
import { isCurrentUserBot } from 'src/utils/isBot';
|
||||
|
||||
const propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
|
@ -61,6 +63,7 @@ class Row extends React.PureComponent {
|
|||
super(props);
|
||||
this.state = {
|
||||
isFocused: false,
|
||||
isInView: false,
|
||||
};
|
||||
this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
|
||||
this.handleUpdateMeta = this.handleUpdateMeta.bind(this);
|
||||
|
@ -69,6 +72,50 @@ class Row extends React.PureComponent {
|
|||
'background',
|
||||
);
|
||||
this.handleChangeFocus = this.handleChangeFocus.bind(this);
|
||||
|
||||
this.containerRef = React.createRef();
|
||||
this.observerEnabler = null;
|
||||
this.observerDisabler = null;
|
||||
}
|
||||
|
||||
// if chart not rendered - render it if it's less than 1 view height away from current viewport
|
||||
// if chart rendered - remove it if it's more than 4 view heights away from current viewport
|
||||
componentDidMount() {
|
||||
if (
|
||||
isFeatureEnabled(FeatureFlag.DASHBOARD_VIRTUALIZATION) &&
|
||||
!isCurrentUserBot()
|
||||
) {
|
||||
this.observerEnabler = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && !this.state.isInView) {
|
||||
this.setState({ isInView: true });
|
||||
}
|
||||
},
|
||||
{
|
||||
rootMargin: '100% 0px',
|
||||
},
|
||||
);
|
||||
this.observerDisabler = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (!entry.isIntersecting && this.state.isInView) {
|
||||
this.setState({ isInView: false });
|
||||
}
|
||||
},
|
||||
{
|
||||
rootMargin: '400% 0px',
|
||||
},
|
||||
);
|
||||
const element = this.containerRef.current;
|
||||
if (element) {
|
||||
this.observerEnabler.observe(element);
|
||||
this.observerDisabler.observe(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.observerEnabler?.disconnect();
|
||||
this.observerDisabler?.disconnect();
|
||||
}
|
||||
|
||||
handleChangeFocus(nextFocus) {
|
||||
|
@ -161,6 +208,7 @@ class Row extends React.PureComponent {
|
|||
backgroundStyle.className,
|
||||
)}
|
||||
data-test={`grid-row-${backgroundStyle.className}`}
|
||||
ref={this.containerRef}
|
||||
>
|
||||
{rowItems.map((componentId, itemIndex) => (
|
||||
<DashboardComponent
|
||||
|
@ -178,6 +226,7 @@ class Row extends React.PureComponent {
|
|||
onResizeStop={onResizeStop}
|
||||
isComponentVisible={isComponentVisible}
|
||||
onChangeTab={onChangeTab}
|
||||
isInView={this.state.isInView}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// navigator.webdriver is true when browser is controlled by a bot
|
||||
export const isCurrentUserBot = () => window?.navigator?.webdriver;
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export enum DASHBOARD_VIRTUALIZATION_MODE {
|
||||
NONE = 'NONE',
|
||||
VIEWPORT = 'VIEWPORT',
|
||||
PAGINATED = 'PAGINATED',
|
||||
}
|
||||
|
||||
export const isDashboardVirtualizationEnabled = (
|
||||
virtualizationMode: DASHBOARD_VIRTUALIZATION_MODE,
|
||||
) =>
|
||||
virtualizationMode === DASHBOARD_VIRTUALIZATION_MODE.VIEWPORT ||
|
||||
virtualizationMode === DASHBOARD_VIRTUALIZATION_MODE.PAGINATED;
|
|
@ -425,6 +425,7 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = {
|
|||
# Feature is under active development and breaking changes are expected
|
||||
"DASHBOARD_NATIVE_FILTERS_SET": False,
|
||||
"DASHBOARD_FILTERS_EXPERIMENTAL": False,
|
||||
"DASHBOARD_VIRTUALIZATION": False,
|
||||
"GLOBAL_ASYNC_QUERIES": False,
|
||||
"VERSIONED_EXPORT": True,
|
||||
"EMBEDDED_SUPERSET": False,
|
||||
|
@ -761,7 +762,6 @@ SQLLAB_SCHEDULE_WARNING_MESSAGE = None
|
|||
# Force refresh while auto-refresh in dashboard
|
||||
DASHBOARD_AUTO_REFRESH_MODE: Literal["fetch", "force"] = "force"
|
||||
|
||||
|
||||
# Default celery config is to use SQLA as a broker, in a production setting
|
||||
# you'll want to use a proper broker as specified here:
|
||||
# http://docs.celeryproject.org/en/latest/getting-started/brokers/index.html
|
||||
|
|
|
@ -103,6 +103,7 @@ FRONTEND_CONF_KEYS = (
|
|||
"SQLALCHEMY_DISPLAY_TEXT",
|
||||
"GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL",
|
||||
"DASHBOARD_AUTO_REFRESH_MODE",
|
||||
"DASHBOARD_VIRTUALIZATION",
|
||||
"SCHEDULED_QUERIES",
|
||||
"EXCEL_EXTENSIONS",
|
||||
"CSV_EXTENSIONS",
|
||||
|
|
Loading…
Reference in New Issue