Skip to content

Commit

Permalink
perf(dashboard): Virtualization POC (#21438)
Browse files Browse the repository at this point in the history
  • Loading branch information
kgabryje authored Oct 11, 2022
1 parent 070b865 commit 406e44b
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
19 changes: 14 additions & 5 deletions superset-frontend/src/components/Chart/Chart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -74,6 +75,7 @@ const propTypes = {
ownState: PropTypes.object,
postTransformProps: PropTypes.func,
datasetsStatus: PropTypes.oneOf(['loading', 'error', 'complete']),
isInView: PropTypes.bool,
};

const BLANK = {};
Expand All @@ -92,6 +94,7 @@ const defaultProps = {
chartStackTrace: null,
isDeactivatedViz: false,
force: false,
isInView: true,
};

const Styles = styled.div`
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const propTypes = {
filterState: PropTypes.object,
postTransformProps: PropTypes.func,
datasetsStatus: PropTypes.oneOf(['loading', 'error', 'complete']),
isInView: PropTypes.bool,
};

const defaultProps = {
Expand Down Expand Up @@ -382,6 +383,7 @@ class Chart extends React.Component {
filterboxMigrationState,
postTransformProps,
datasetsStatus,
isInView,
} = this.props;

const { width } = this.state;
Expand Down Expand Up @@ -511,6 +513,7 @@ class Chart extends React.Component {
filterboxMigrationState={filterboxMigrationState}
postTransformProps={postTransformProps}
datasetsStatus={datasetsStatus}
isInView={isInView}
/>
</div>
</SliceContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('ChartHolder', () => {

rerender(
<Provider store={store}>
<ChartHolder {...defaultProps} editMode />
<ChartHolder {...defaultProps} editMode isInView />
</Provider>,
);

Expand Down Expand Up @@ -414,6 +414,7 @@ describe('ChartHolder', () => {
deleteComponent={deleteComponent}
fullSizeChartId={null}
editMode
isInView
/>
</Provider>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ interface ChartHolderProps {
handleComponentDrop: (...args: unknown[]) => unknown;
setFullSizeChartId: (chartId: number | null) => void;
postAddSliceFromDashboard?: () => void;

isInView: boolean;
}

const ChartHolder: React.FC<ChartHolderProps> = ({
Expand All @@ -91,6 +93,7 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
handleComponentDrop,
setFullSizeChartId,
postAddSliceFromDashboard,
isInView,
}) => {
const { chartId } = component.meta;
const isFullSize = fullSizeChartId === chartId;
Expand Down Expand Up @@ -314,6 +317,7 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
setControlValue={handleExtraControl}
extraControls={extraControls}
postTransformProps={handlePostTransformProps}
isInView={isInView}
/>
{editMode && (
<HoverMenu position="top">
Expand Down
49 changes: 49 additions & 0 deletions superset-frontend/src/dashboard/components/gridComponents/Row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -178,6 +226,7 @@ class Row extends React.PureComponent {
onResizeStop={onResizeStop}
isComponentVisible={isComponentVisible}
onChangeTab={onChangeTab}
isInView={this.state.isInView}
/>
))}

Expand Down
21 changes: 21 additions & 0 deletions superset-frontend/src/utils/isBot.ts
Original file line number Diff line number Diff line change
@@ -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;
30 changes: 30 additions & 0 deletions superset-frontend/src/utils/isDashboardVirtualizationEnabled.ts
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ def _try_json_readsha(filepath: str, length: int) -> Optional[str]:
# 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,
Expand Down Expand Up @@ -761,7 +762,6 @@ def _try_json_readsha(filepath: str, length: int) -> Optional[str]:
# 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
Expand Down
1 change: 1 addition & 0 deletions superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"SQLALCHEMY_DISPLAY_TEXT",
"GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL",
"DASHBOARD_AUTO_REFRESH_MODE",
"DASHBOARD_VIRTUALIZATION",
"SCHEDULED_QUERIES",
"EXCEL_EXTENSIONS",
"CSV_EXTENSIONS",
Expand Down

0 comments on commit 406e44b

Please sign in to comment.