You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2021/01/13 17:10:14 UTC

[superset] 03/09: feat: Resizable dataset and controls panels on Explore view (#12411)

This is an automated email from the ASF dual-hosted git repository.

villebro pushed a commit to branch 1.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 288f6bb88ce3c78640982aa7e7889d4e4981059d
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Tue Jan 12 19:39:56 2021 +0100

    feat: Resizable dataset and controls panels on Explore view (#12411)
    
    * Implement resizable panels on explore view
    
    * Optimize chart rendering while resizing
    
    * Make dataset column narrower
    
    Co-authored-by: Evan Rusackas <ev...@preset.io>
---
 superset-frontend/package-lock.json                |  21 +++++
 superset-frontend/package.json                     |   1 +
 .../src/explore/components/DatasourcePanel.tsx     |  26 +++++-
 .../src/explore/components/ExploreChartPanel.jsx   | 104 +++++++++++----------
 .../explore/components/ExploreViewContainer.jsx    |  21 +++--
 5 files changed, 116 insertions(+), 57 deletions(-)

diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index e5e3916..8461aba 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -39186,6 +39186,11 @@
       "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
       "dev": true
     },
+    "lodash.throttle": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+      "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+    },
     "lodash.topath": {
       "version": "4.5.2",
       "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
@@ -44123,6 +44128,11 @@
         "performance-now": "^2.1.0"
       }
     },
+    "raf-schd": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz",
+      "integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ=="
+    },
     "railroad-diagrams": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
@@ -46500,6 +46510,17 @@
         }
       }
     },
+    "react-resize-detector": {
+      "version": "6.0.1-rc.1",
+      "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.0.1-rc.1.tgz",
+      "integrity": "sha512-r+UZtJottPZaW/2CKAyb4Vgpi6KROsXBH890UChK7mB8DSFf8nEvwqpvE9akfd8wQOGi0cXekcGLHzYp9FiscA==",
+      "requires": {
+        "lodash.debounce": "^4.0.8",
+        "lodash.throttle": "^4.1.1",
+        "raf-schd": "^4.0.2",
+        "resize-observer-polyfill": "^1.5.1"
+      }
+    },
     "react-router": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz",
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index 574aaa3..fbc0224 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -149,6 +149,7 @@
     "react-loadable": "^5.5.0",
     "react-markdown": "^4.3.1",
     "react-redux": "^7.2.0",
+    "react-resize-detector": "^6.0.1-rc.1",
     "react-router-dom": "^5.1.2",
     "react-search-input": "^0.11.3",
     "react-select": "^3.1.0",
diff --git a/superset-frontend/src/explore/components/DatasourcePanel.tsx b/superset-frontend/src/explore/components/DatasourcePanel.tsx
index b185ea1..95b9d3f 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel.tsx
@@ -141,6 +141,24 @@ const DatasourceContainer = styled.div`
   }
 `;
 
+const LabelContainer = styled.div`
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  & > span {
+    white-space: nowrap;
+  }
+
+  .option-label {
+    display: inline;
+  }
+
+  .metric-option > .option-label {
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+`;
+
 const DataSourcePanel = ({
   datasource,
   controls: { datasource: datasourceControl },
@@ -200,9 +218,9 @@ const DataSourcePanel = ({
               {t(`Showing %s of %s`, metricSlice.length, metrics.length)}
             </div>
             {metricSlice.map(m => (
-              <div key={m.metric_name} className="column">
+              <LabelContainer key={m.metric_name} className="column">
                 <MetricOption metric={m} showType />
-              </div>
+              </LabelContainer>
             ))}
           </Collapse.Panel>
           <Collapse.Panel
@@ -213,9 +231,9 @@ const DataSourcePanel = ({
               {t(`Showing %s of %s`, columnSlice.length, columns.length)}
             </div>
             {columnSlice.map(col => (
-              <div key={col.column_name} className="column">
+              <LabelContainer key={col.column_name} className="column">
                 <ColumnOption column={col} showType />
-              </div>
+              </LabelContainer>
             ))}
           </Collapse.Panel>
         </Collapse>
diff --git a/superset-frontend/src/explore/components/ExploreChartPanel.jsx b/superset-frontend/src/explore/components/ExploreChartPanel.jsx
index a1e57a8..bb9bd60 100644
--- a/superset-frontend/src/explore/components/ExploreChartPanel.jsx
+++ b/superset-frontend/src/explore/components/ExploreChartPanel.jsx
@@ -16,12 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useState, useEffect, useRef, useCallback } from 'react';
+import React, { useState, useEffect, useCallback, useMemo } from 'react';
 import PropTypes from 'prop-types';
 import Split from 'react-split';
-import { ParentSize } from '@vx/responsive';
 import { styled, useTheme } from '@superset-ui/core';
-import debounce from 'lodash/debounce';
+import { useResizeDetector } from 'react-resize-detector';
 import { chartPropShape } from 'src/dashboard/util/propShapes';
 import ChartContainer from 'src/chart/ChartContainer';
 import ConnectedExploreChartHeader from './ExploreChartHeader';
@@ -55,6 +54,7 @@ const propTypes = {
 const GUTTER_SIZE_FACTOR = 1.25;
 
 const CHART_PANEL_PADDING = 30;
+const HEADER_PADDING = 15;
 
 const INITIAL_SIZES = [90, 10];
 const MIN_SIZES = [300, 50];
@@ -104,20 +104,32 @@ const ExploreChartPanel = props => {
   const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR;
   const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR;
 
-  const panelHeadingRef = useRef(null);
+  const { height: hHeight, ref: headerRef } = useResizeDetector({
+    refreshMode: 'debounce',
+    refreshRate: 300,
+  });
+  const { width: chartWidth, ref: chartRef } = useResizeDetector({
+    refreshMode: 'debounce',
+    refreshRate: 300,
+  });
   const [splitSizes, setSplitSizes] = useState(INITIAL_SIZES);
 
   const calcSectionHeight = useCallback(
     percent => {
-      const headerHeight = props.standalone
-        ? 0
-        : panelHeadingRef?.current?.offsetHeight ?? 50;
+      let headerHeight;
+      if (props.standalone) {
+        headerHeight = 0;
+      } else if (hHeight) {
+        headerHeight = hHeight + HEADER_PADDING;
+      } else {
+        headerHeight = 50;
+      }
       const containerHeight = parseInt(props.height, 10) - headerHeight;
       return (
         (containerHeight * percent) / 100 - (gutterHeight / 2 + gutterMargin)
       );
     },
-    [gutterHeight, gutterMargin, props.height, props.standalone],
+    [gutterHeight, gutterMargin, props.height, props.standalone, hHeight],
   );
 
   const [tableSectionHeight, setTableSectionHeight] = useState(
@@ -132,15 +144,11 @@ const ExploreChartPanel = props => {
   );
 
   useEffect(() => {
-    const recalcSizes = debounce(() => recalcPanelSizes(splitSizes), 200);
-
-    window.addEventListener('resize', recalcSizes);
-    return () => window.removeEventListener('resize', recalcSizes);
-  }, [props.standalone, recalcPanelSizes, splitSizes]);
+    recalcPanelSizes(splitSizes);
+  }, [recalcPanelSizes, splitSizes]);
 
   const onDragEnd = sizes => {
     setSplitSizes(sizes);
-    recalcPanelSizes(sizes);
   };
 
   const onCollapseChange = openPanelName => {
@@ -154,42 +162,46 @@ const ExploreChartPanel = props => {
       ];
     }
     setSplitSizes(splitSizes);
-    recalcPanelSizes(splitSizes);
   };
 
-  const renderChart = () => {
+  const renderChart = useCallback(() => {
     const { chart } = props;
     const newHeight = calcSectionHeight(splitSizes[0]) - CHART_PANEL_PADDING;
     return (
-      <ParentSize>
-        {({ width }) =>
-          width > 0 && (
-            <ChartContainer
-              width={Math.floor(width)}
-              height={newHeight}
-              annotationData={chart.annotationData}
-              chartAlert={chart.chartAlert}
-              chartStackTrace={chart.chartStackTrace}
-              chartId={chart.id}
-              chartStatus={chart.chartStatus}
-              triggerRender={props.triggerRender}
-              datasource={props.datasource}
-              errorMessage={props.errorMessage}
-              formData={props.form_data}
-              onQuery={props.onQuery}
-              owners={props?.slice?.owners}
-              queriesResponse={chart.queriesResponse}
-              refreshOverlayVisible={props.refreshOverlayVisible}
-              setControlValue={props.actions.setControlValue}
-              timeout={props.timeout}
-              triggerQuery={chart.triggerQuery}
-              vizType={props.vizType}
-            />
-          )
-        }
-      </ParentSize>
+      chartWidth > 0 && (
+        <ChartContainer
+          width={Math.floor(chartWidth)}
+          height={newHeight}
+          annotationData={chart.annotationData}
+          chartAlert={chart.chartAlert}
+          chartStackTrace={chart.chartStackTrace}
+          chartId={chart.id}
+          chartStatus={chart.chartStatus}
+          triggerRender={props.triggerRender}
+          datasource={props.datasource}
+          errorMessage={props.errorMessage}
+          formData={props.form_data}
+          onQuery={props.onQuery}
+          owners={props?.slice?.owners}
+          queriesResponse={chart.queriesResponse}
+          refreshOverlayVisible={props.refreshOverlayVisible}
+          setControlValue={props.actions.setControlValue}
+          timeout={props.timeout}
+          triggerQuery={chart.triggerQuery}
+          vizType={props.vizType}
+        />
+      )
     );
-  };
+  }, [calcSectionHeight, chartWidth, props, splitSizes]);
+
+  const panelBody = useMemo(
+    () => (
+      <div className="panel-body" ref={chartRef}>
+        {renderChart()}
+      </div>
+    ),
+    [chartRef, renderChart],
+  );
 
   if (props.standalone) {
     // dom manipulation hack to get rid of the boostrap theme's body background
@@ -222,14 +234,12 @@ const ExploreChartPanel = props => {
     [dimension]: `calc(${elementSize}% - ${gutterSize + gutterMargin}px)`,
   });
 
-  const panelBody = <div className="panel-body">{renderChart()}</div>;
-
   return (
     <Styles
       className="panel panel-default chart-container"
       style={{ height: props.height }}
     >
-      <div className="panel-heading" ref={panelHeadingRef}>
+      <div className="panel-heading" ref={headerRef}>
         {header}
       </div>
       {props.vizType === 'filter_box' ? (
diff --git a/superset-frontend/src/explore/components/ExploreViewContainer.jsx b/superset-frontend/src/explore/components/ExploreViewContainer.jsx
index 6599890..1a701df 100644
--- a/superset-frontend/src/explore/components/ExploreViewContainer.jsx
+++ b/superset-frontend/src/explore/components/ExploreViewContainer.jsx
@@ -23,6 +23,7 @@ import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 import { styled, t, supersetTheme, css } from '@superset-ui/core';
 import { debounce } from 'lodash';
+import { Resizable } from 're-resizable';
 
 import { useDynamicPluginContext } from 'src/components/DynamicPlugins';
 import { Global } from '@emotion/core';
@@ -81,10 +82,8 @@ const Styles = styled.div`
   border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
   .explore-column {
     display: flex;
-    flex: 0 0 ${({ theme }) => theme.gridUnit * 95}px;
     flex-direction: column;
     padding: ${({ theme }) => 2 * theme.gridUnit}px 0;
-    max-width: ${({ theme }) => theme.gridUnit * 95}px;
     max-height: 100%;
   }
   .data-source-selection {
@@ -404,7 +403,11 @@ function ExploreViewContainer(props) {
           dashboardId={props.dashboardId}
         />
       )}
-      <div
+      <Resizable
+        defaultSize={{ width: 300 }}
+        minWidth={300}
+        maxWidth="33%"
+        enable={{ right: true }}
         className={
           isCollapsed ? 'no-show' : 'explore-column data-source-selection'
         }
@@ -430,7 +433,7 @@ function ExploreViewContainer(props) {
           controls={props.controls}
           actions={props.actions}
         />
-      </div>
+      </Resizable>
       {isCollapsed ? (
         <div
           className="sidebar"
@@ -452,7 +455,13 @@ function ExploreViewContainer(props) {
           <Icon name="dataset-physical" width={16} />
         </div>
       ) : null}
-      <div className="col-sm-3 explore-column controls-column">
+      <Resizable
+        defaultSize={{ width: 320 }}
+        minWidth={320}
+        maxWidth="33%"
+        enable={{ right: true }}
+        className="col-sm-3 explore-column controls-column"
+      >
         <QueryAndSaveBtns
           canAdd={!!(props.can_add || props.can_overwrite)}
           onQuery={onQuery}
@@ -470,7 +479,7 @@ function ExploreViewContainer(props) {
           datasource_type={props.datasource_type}
           isDatasourceMetaLoading={props.isDatasourceMetaLoading}
         />
-      </div>
+      </Resizable>
       <div
         className={`main-explore-content ${
           isCollapsed ? 'col-sm-9' : 'col-sm-7'