You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2022/08/22 18:00:04 UTC

[superset] branch chore/e2e-tests-drilltodetail-modal created (now e82832937d)

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

diegopucci pushed a change to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git


      at e82832937d Add example ECharts Dashboard

This branch includes the following new commits:

     new e0e703820f Add drill-to-detail modal.
     new 8ab13c96ff Include additional filters from dashboard context in request.
     new 91d9b9c6ee Set page cache size to be approximately equal to memory usage of Samples pane.
     new e20dc8be3c Update getDatasourceSamples signature.
     new 17cad5122a One-line import/export.
     new eab75a7766 Fix incorrect argument order in getDatasourceSamples invocation.
     new 0e931e620c Fix height of modal.
     new 5980017c53 Disable option in chart menu unless feature flag is set.
     new b25c44330e Open modal on right-click.
     new 8a9d448014 Fix double requests on modal open, controls disappearing on filter update.
     new bf463961e9 Show formattedVal in clearable filter tag.
     new 24c079adb6 Set force=false for all requests.
     new e65e4483d8 Rearrange/refactor DrillDetailPane.
     new 1c7fa8f366 Reset page index on reload.
     new bd21c5ea2e Fix endless re-requests on request failure.
     new 621ea7314f Fix modal layout issues.
     new 5d9c4266ab Merge branch 'master' of https://github.com/apache/superset
     new 6987b165ef Merge branch 'codyml/sc-52488/apply-drill-to-detail-modal-filters-gathered' of https://github.com/codyml/superset into chore/e2e-tests-drilltodetail-modal
     new e82832937d Add example ECharts Dashboard

The 19 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[superset] 15/19: Fix endless re-requests on request failure.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit bd21c5ea2e38d0391e1e1952cf7406c1c9c2601d
Author: Cody Leff <co...@preset.io>
AuthorDate: Tue Aug 16 21:39:54 2022 -0700

    Fix endless re-requests on request failure.
---
 .../src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx     | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index 0682ae671b..c697ff7f29 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -116,12 +116,14 @@ export default function DrillDetailPane({
 
   //  Clear cache on reload button click
   const handleReload = useCallback(() => {
+    setResponseError('');
     setResultsPages(new Map());
     setPageIndex(0);
   }, []);
 
   //  Clear cache and reset page index if filters change
   useEffect(() => {
+    setResponseError('');
     setResultsPages(new Map());
     setPageIndex(0);
   }, [filters]);
@@ -145,7 +147,7 @@ export default function DrillDetailPane({
 
   //  Download page of results & trim cache if page not in cache
   useEffect(() => {
-    if (!isLoading && !resultsPages.has(pageIndex)) {
+    if (!responseError && !isLoading && !resultsPages.has(pageIndex)) {
       setIsLoading(true);
       const jsonPayload = getDrillPayload(formData, filters);
       const cachePageLimit = Math.ceil(SAMPLES_ROW_LIMIT / PAGE_SIZE);
@@ -189,6 +191,7 @@ export default function DrillDetailPane({
     formData,
     isLoading,
     pageIndex,
+    responseError,
     resultsPages,
   ]);
 


[superset] 02/19: Include additional filters from dashboard context in request.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 8ab13c96ff7f1d173d665a50ffdf2e930301b6ca
Author: Cody Leff <co...@preset.io>
AuthorDate: Thu Aug 4 16:52:10 2022 -0400

    Include additional filters from dashboard context in request.
---
 .../components/DrillDetailPane/DrillDetailPane.tsx | 33 ++++++++++------
 .../dashboard/components/DrillDetailPane/utils.ts  | 46 ++++++++++++++++++++++
 .../components/SliceHeaderControls/index.tsx       |  5 ++-
 3 files changed, 71 insertions(+), 13 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index f6b50f9d17..4fb13f9c7f 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 import React, {
   useState,
   useEffect,
@@ -31,6 +30,7 @@ import {
   GenericDataType,
   t,
   useTheme,
+  QueryFormData,
 } from '@superset-ui/core';
 import Loading from 'src/components/Loading';
 import { EmptyStateMedium } from 'src/components/EmptyState';
@@ -38,6 +38,7 @@ import TableView, { EmptyWrapperType } from 'src/components/TableView';
 import { useTableColumns } from 'src/explore/components/DataTableControl';
 import { getDatasourceSamples } from 'src/components/Chart/chartAction';
 import TableControls from './TableControls';
+import { getDrillPayload } from './utils';
 
 type ResultsPage = {
   total: number;
@@ -51,15 +52,17 @@ const MAX_CACHED_PAGES = 5;
 
 export default function DrillDetailPane({
   datasource,
-  initialFilters,
+  queryFormData,
+  drillFilters,
 }: {
   datasource: string;
-  initialFilters?: BinaryQueryObjectFilterClause[];
+  queryFormData?: QueryFormData;
+  drillFilters?: BinaryQueryObjectFilterClause[];
 }) {
   const theme = useTheme();
   const [pageIndex, setPageIndex] = useState(0);
   const lastPageIndex = useRef(pageIndex);
-  const [filters, setFilters] = useState(initialFilters || []);
+  const [filters, setFilters] = useState(drillFilters || []);
   const [isLoading, setIsLoading] = useState(false);
   const [responseError, setResponseError] = useState('');
   const [resultsPages, setResultsPages] = useState<Map<number, ResultsPage>>(
@@ -110,13 +113,11 @@ export default function DrillDetailPane({
   useEffect(() => {
     if (!resultsPages.has(pageIndex)) {
       setIsLoading(true);
-      getDatasourceSamples(
-        datasourceType,
-        datasourceId,
-        true,
-        filters.length ? { filters } : null,
-        { page: pageIndex + 1, perPage: PAGE_SIZE },
-      )
+      const jsonPayload = getDrillPayload(queryFormData, drillFilters);
+      getDatasourceSamples(datasourceType, datasourceId, true, jsonPayload, {
+        page: pageIndex + 1,
+        perPage: PAGE_SIZE,
+      })
         .then(response => {
           setResultsPages(
             new Map([
@@ -141,7 +142,15 @@ export default function DrillDetailPane({
           setIsLoading(false);
         });
     }
-  }, [datasourceId, datasourceType, filters, pageIndex, resultsPages]);
+  }, [
+    datasourceId,
+    datasourceType,
+    drillFilters,
+    filters,
+    pageIndex,
+    queryFormData,
+    resultsPages,
+  ]);
 
   // this is to preserve the order of the columns, even if there are integer values,
   // while also only grabbing the first column's keys
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/utils.ts b/superset-frontend/src/dashboard/components/DrillDetailPane/utils.ts
new file mode 100644
index 0000000000..e59af45b0d
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/utils.ts
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ */
+import { omit } from 'lodash';
+import {
+  ensureIsArray,
+  QueryFormData,
+  BinaryQueryObjectFilterClause,
+  buildQueryObject,
+} from '@superset-ui/core';
+
+export function getDrillPayload(
+  queryFormData?: QueryFormData,
+  drillFilters?: BinaryQueryObjectFilterClause[],
+) {
+  if (!queryFormData) {
+    return undefined;
+  }
+  const queryObject = buildQueryObject(queryFormData);
+  const extras = omit(queryObject.extras, 'having');
+  const filters = [
+    ...ensureIsArray(queryObject.filters),
+    ...ensureIsArray(drillFilters),
+  ];
+  return {
+    granularity: queryObject.granularity,
+    time_range: queryObject.time_range,
+    filters,
+    extras,
+  };
+}
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index bb41c82d3e..77d80199c1 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -448,7 +448,10 @@ class SliceHeaderControls extends React.PureComponent<
               }
               modalTitle={t('Drill to detail: %s', slice.slice_name)}
               modalBody={
-                <DrillDetailPane datasource={this.props.slice.datasource} />
+                <DrillDetailPane
+                  datasource={this.props.slice.datasource}
+                  queryFormData={this.props.formData}
+                />
               }
             />
           </Menu.Item>


[superset] 16/19: Fix modal layout issues.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 621ea7314ffe2dcab546d11bc41b9f69268ff14e
Author: Cody Leff <co...@preset.io>
AuthorDate: Wed Aug 17 21:54:14 2022 -0700

    Fix modal layout issues.
---
 .../src/components/Chart/DrillDetailModal.tsx           | 17 +++++++++++++++++
 .../components/DrillDetailPane/DrillDetailPane.tsx      | 16 ++++++----------
 .../dashboard/components/SliceHeaderControls/index.tsx  | 16 ++++++++++++++++
 3 files changed, 39 insertions(+), 10 deletions(-)

diff --git a/superset-frontend/src/components/Chart/DrillDetailModal.tsx b/superset-frontend/src/components/Chart/DrillDetailModal.tsx
index 7180d8922d..128359741b 100644
--- a/superset-frontend/src/components/Chart/DrillDetailModal.tsx
+++ b/superset-frontend/src/components/Chart/DrillDetailModal.tsx
@@ -28,8 +28,10 @@ import { useSelector } from 'react-redux';
 import { useHistory } from 'react-router-dom';
 import {
   BinaryQueryObjectFilterClause,
+  css,
   QueryFormData,
   t,
+  useTheme,
 } from '@superset-ui/core';
 import DrillDetailPane from 'src/dashboard/components/DrillDetailPane';
 import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
@@ -46,6 +48,7 @@ const DrillDetailModal: React.FC<{
   const openModal = useCallback(() => setShowModal(true), []);
   const closeModal = useCallback(() => setShowModal(false), []);
   const history = useHistory();
+  const theme = useTheme();
   const dashboardPageId = useContext(DashboardPageIdContext);
   const { slice_name: chartName } = useSelector(
     (state: { sliceEntities: { slices: Record<number, Slice> } }) =>
@@ -70,6 +73,12 @@ const DrillDetailModal: React.FC<{
 
   return (
     <Modal
+      css={css`
+        .ant-modal-body {
+          display: flex;
+          flex-direction: column;
+        }
+      `}
       show={showModal}
       onHide={closeModal}
       title={t('Drill to detail: %s', chartName)}
@@ -89,6 +98,14 @@ const DrillDetailModal: React.FC<{
       }
       responsive
       resizable
+      resizableConfig={{
+        minHeight: theme.gridUnit * 128,
+        minWidth: theme.gridUnit * 128,
+        defaultSize: {
+          width: 'auto',
+          height: '75vh',
+        },
+      }}
       draggable
       destroyOnClose
     >
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index c697ff7f29..bf3f1985b7 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -233,21 +233,17 @@ export default function DrillDetailPane({
         showRowCount={false}
         small
         css={css`
-          min-height: 0;
-          overflow: scroll;
+          overflow: auto;
+          .table {
+            margin-bottom: 0;
+          }
         `}
       />
     );
   }
 
   return (
-    <div
-      css={css`
-        display: flex;
-        flex-direction: column;
-        height: ${theme.gridUnit * 128}px;
-      `}
-    >
+    <>
       <TableControls
         filters={filters}
         setFilters={setFilters}
@@ -256,6 +252,6 @@ export default function DrillDetailPane({
         onReload={handleReload}
       />
       {tableContent}
-    </div>
+    </>
   );
 }
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index abb59ce359..ee99767b31 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -37,6 +37,7 @@ import {
   QueryFormData,
   styled,
   t,
+  useTheme,
 } from '@superset-ui/core';
 import { Menu } from 'src/components/Menu';
 import { NoAnimationDropdown } from 'src/components/Dropdown';
@@ -171,6 +172,7 @@ const DashboardChartModalTrigger = ({
   const closeModal = useCallback(() => setShowModal(false), []);
   const history = useHistory();
   const exploreChart = () => history.push(exploreUrl);
+  const theme = useTheme();
 
   return (
     <>
@@ -184,6 +186,12 @@ const DashboardChartModalTrigger = ({
       </span>
       {(() => (
         <Modal
+          css={css`
+            .ant-modal-body {
+              display: flex;
+              flex-direction: column;
+            }
+          `}
           show={showModal}
           onHide={closeModal}
           title={modalTitle}
@@ -207,6 +215,14 @@ const DashboardChartModalTrigger = ({
           }
           responsive
           resizable
+          resizableConfig={{
+            minHeight: theme.gridUnit * 128,
+            minWidth: theme.gridUnit * 128,
+            defaultSize: {
+              width: 'auto',
+              height: '75vh',
+            },
+          }}
           draggable
           destroyOnClose
         >


[superset] 03/19: Set page cache size to be approximately equal to memory usage of Samples pane.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 91d9b9c6ee95741bafd519df02862e56bb251d57
Author: Cody Leff <co...@preset.io>
AuthorDate: Fri Aug 5 16:42:10 2022 -0400

    Set page cache size to be approximately equal to memory usage of Samples pane.
---
 .../components/DrillDetailPane/DrillDetailPane.tsx        | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index 4fb13f9c7f..a09494d98a 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -23,6 +23,7 @@ import React, {
   useCallback,
   useRef,
 } from 'react';
+import { useSelector } from 'react-redux';
 import {
   BinaryQueryObjectFilterClause,
   css,
@@ -31,6 +32,7 @@ import {
   t,
   useTheme,
   QueryFormData,
+  JsonObject,
 } from '@superset-ui/core';
 import Loading from 'src/components/Loading';
 import { EmptyStateMedium } from 'src/components/EmptyState';
@@ -48,7 +50,6 @@ type ResultsPage = {
 };
 
 const PAGE_SIZE = 50;
-const MAX_CACHED_PAGES = 5;
 
 export default function DrillDetailPane({
   datasource,
@@ -69,7 +70,12 @@ export default function DrillDetailPane({
     new Map(),
   );
 
-  //  Get string identifier for dataset
+  const SAMPLES_ROW_LIMIT = useSelector(
+    (state: { common: { conf: JsonObject } }) =>
+      state.common.conf.SAMPLES_ROW_LIMIT,
+  );
+
+  //  Extract datasource ID/type from string ID
   const [datasourceId, datasourceType] = useMemo(
     () => datasource.split('__'),
     [datasource],
@@ -110,6 +116,7 @@ export default function DrillDetailPane({
   }, [pageIndex, resultsPages]);
 
   //  Download page of results & trim cache if page not in cache
+  const cachePageLimit = Math.ceil(SAMPLES_ROW_LIMIT / PAGE_SIZE);
   useEffect(() => {
     if (!resultsPages.has(pageIndex)) {
       setIsLoading(true);
@@ -121,7 +128,7 @@ export default function DrillDetailPane({
         .then(response => {
           setResultsPages(
             new Map([
-              ...[...resultsPages.entries()].slice(-MAX_CACHED_PAGES + 1),
+              ...[...resultsPages.entries()].slice(-cachePageLimit + 1),
               [
                 pageIndex,
                 {
@@ -143,10 +150,10 @@ export default function DrillDetailPane({
         });
     }
   }, [
+    cachePageLimit,
     datasourceId,
     datasourceType,
     drillFilters,
-    filters,
     pageIndex,
     queryFormData,
     resultsPages,


[superset] 11/19: Show formattedVal in clearable filter tag.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit bf463961e977851dd651c5f26ed2b9d0f6b26ca8
Author: Cody Leff <co...@preset.io>
AuthorDate: Thu Aug 11 14:39:21 2022 -0600

    Show formattedVal in clearable filter tag.
---
 .../src/dashboard/components/DrillDetailPane/TableControls.tsx       | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
index 96badcc157..9b0e072379 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
@@ -68,7 +68,10 @@ export default function TableControls({
   const filterTags = useMemo(
     () =>
       Object.entries(filterMap)
-        .map(([colName, { val }]) => ({ colName, val }))
+        .map(([colName, { val, formattedVal }]) => ({
+          colName,
+          val: formattedVal ?? val,
+        }))
         .sort((a, b) => a.colName.localeCompare(b.colName)),
     [filterMap],
   );


[superset] 19/19: Add example ECharts Dashboard

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit e82832937d2c290b466577ade128e6ccaba0fb2e
Author: geido <di...@gmail.com>
AuthorDate: Mon Aug 22 20:59:22 2022 +0300

    Add example ECharts Dashboard
---
 superset/cli/examples.py               |   3 +
 superset/examples/data_loading.py      |   1 +
 superset/examples/echarts_dashboard.py | 161 +++++++++++++++++++++++++++++++++
 3 files changed, 165 insertions(+)

diff --git a/superset/cli/examples.py b/superset/cli/examples.py
index cad87da9d3..cdee3747a8 100755
--- a/superset/cli/examples.py
+++ b/superset/cli/examples.py
@@ -54,6 +54,9 @@ def load_examples_run(
     if load_test_data:
         print("Loading [Tabbed dashboard]")
         examples.load_tabbed_dashboard(only_metadata)
+
+        print("Loading [ECharts Dashboard]")
+        examples.load_echarts_dashboard()
     else:
         print("Loading [Random long/lat data]")
         examples.load_long_lat_data(only_metadata, force)
diff --git a/superset/examples/data_loading.py b/superset/examples/data_loading.py
index 6eae82a799..6b1346623e 100644
--- a/superset/examples/data_loading.py
+++ b/superset/examples/data_loading.py
@@ -33,3 +33,4 @@ from .sf_population_polygons import load_sf_population_polygons
 from .tabbed_dashboard import load_tabbed_dashboard
 from .utils import load_examples_from_configs
 from .world_bank import load_world_bank_health_n_pop
+from .echarts_dashboard import load_echarts_dashboard
\ No newline at end of file
diff --git a/superset/examples/echarts_dashboard.py b/superset/examples/echarts_dashboard.py
new file mode 100644
index 0000000000..25d6b0e112
--- /dev/null
+++ b/superset/examples/echarts_dashboard.py
@@ -0,0 +1,161 @@
+# 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.
+import json
+import textwrap
+
+from superset import db
+from superset.models.dashboard import Dashboard
+
+from .helpers import update_slice_ids
+
+DASH_SLUG = "echarts_dash"
+
+
+def load_echarts_dashboard() -> None:
+    """Loading a dashboard featuring EChart charts"""
+
+    print("Creating the dashboard")
+    db.session.expunge_all()
+    dash = db.session.query(Dashboard).filter_by(slug=DASH_SLUG).first()
+
+    if not dash:
+        dash = Dashboard()
+    js = textwrap.dedent(
+        """\
+{
+    "CHART-dxV7Il74hH": {
+      "children": [],
+      "id": "CHART-dxV7Il74hH",
+      "meta": {
+        "chartId": 597,
+        "height": 50,
+        "sliceName": "Box plot",
+        "width": 6
+      },
+      "type": "CHART"
+    },
+    "CHART-YyHWQacdcj": {
+      "children": [],
+      "id": "CHART-YyHWQacdcj",
+      "meta": {
+          "chartId": 15,
+          "height": 50,
+          "sliceName": "Participants",
+          "width": 6
+      },
+      "type": "CHART"
+    },
+    "CHART-oWKBOJ6Ydh": {
+      "children": [],
+      "id": "CHART-oWKBOJ6Ydh",
+      "meta":{
+          "chartId": 16,
+          "height": 50,
+          "sliceName": "Genders",
+          "width": 6
+        },
+      "type": "CHART"
+    },
+    "CHART-06Kg-rUggO": {
+      "children": [],
+      "id": "CHART-06Kg-rUggO",
+      "meta": {
+        "chartId": 617,
+        "height": 50,
+        "sliceName": "Number of Girls",
+        "width": 6
+      },
+      "type": "CHART"
+    },
+    "CHART--wEhS-MDSg": {
+      "children": [],
+      "id": "CHART--wEhS-MDS",
+      "meta": {
+        "chartId": 2,
+        "height": 50,
+        "sliceName": "Energy Force Layout",
+        "width": 6
+      },
+      "type": "CHART"
+    },
+    "GRID_ID": {
+        "children": [
+            "ROW-SytNzNA4X",
+            "ROW-HkFFEzVRVm",
+            "ROW-BytNzNA4Y"
+        ],
+        "id": "GRID_ID",
+        "type": "GRID"
+    },
+    "HEADER_ID": {
+        "id": "HEADER_ID",
+        "meta": {
+            "text": "ECharts Charts"
+        },
+        "type": "HEADER"
+    },
+    "ROOT_ID": {
+        "children": [
+            "GRID_ID"
+        ],
+        "id": "ROOT_ID",
+        "type": "ROOT"
+    },
+    "ROW-HkFFEzVRVm": {
+        "children": [
+            "CHART-dxV7Il74hH",
+            "CHART-oWKBOJ6Ydh"
+        ],
+        "id": "ROW-HkFFEzVRVm",
+        "meta": {
+            "background": "BACKGROUND_TRANSPARENT"
+        },
+        "type": "ROW"
+    },
+    "ROW-SytNzNA4X": {
+        "children": [
+            "CHART-06Kg-rUggO",
+            "CHART-YyHWQacdcj"
+        ],
+        "id": "ROW-SytNzNA4X",
+        "meta": {
+            "background": "BACKGROUND_TRANSPARENT"
+        },
+        "type": "ROW"
+    },
+    "ROW-BytNzNA4Y": {
+        "children": [
+            "CHART--wEhS-MDSg"
+        ],
+        "id": "ROW-BytNzNA4Y",
+        "meta": {
+            "background": "BACKGROUND_TRANSPARENT"
+        },
+        "type": "ROW"
+    },
+    "DASHBOARD_VERSION_KEY": "v2"
+}
+    """
+    )
+    pos = json.loads(js)
+    slices = update_slice_ids(pos)
+    dash.dashboard_title = "ECharts Dashboard"
+    dash.position_json = json.dumps(pos, indent=4)
+    dash.slug = DASH_SLUG
+    dash.slices = slices
+    db.session.merge(dash)
+    db.session.commit()


[superset] 13/19: Rearrange/refactor DrillDetailPane.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit e65e4483d8fd1b74d9e13d353e5ca22d8d72e95c
Author: Cody Leff <co...@preset.io>
AuthorDate: Thu Aug 11 15:00:51 2022 -0600

    Rearrange/refactor DrillDetailPane.
---
 .../components/DrillDetailPane/DrillDetailPane.tsx | 92 +++++++++-------------
 1 file changed, 37 insertions(+), 55 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index 03a3f99f68..f8ee5f0a9d 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -90,6 +90,35 @@ export default function DrillDetailPane({
     return resultsPages.get(lastPageIndex.current);
   }, [pageIndex, resultsPages]);
 
+  // this is to preserve the order of the columns, even if there are integer values,
+  // while also only grabbing the first column's keys
+  const columns = useTableColumns(
+    resultsPage?.colNames,
+    resultsPage?.colTypes,
+    resultsPage?.data,
+    formData.datasource,
+  );
+
+  //  Disable sorting on columns
+  const sortDisabledColumns = useMemo(
+    () =>
+      columns.map(column => ({
+        ...column,
+        disableSortBy: true,
+      })),
+    [columns],
+  );
+
+  //  Update page index on pagination click
+  const onServerPagination = useCallback(({ pageIndex }) => {
+    setPageIndex(pageIndex);
+  }, []);
+
+  //  Clear cache on reload button click
+  const handleReload = useCallback(() => {
+    setResultsPages(new Map());
+  }, []);
+
   //  Clear cache and reset page index if filters change
   useEffect(() => {
     setResultsPages(new Map());
@@ -114,11 +143,11 @@ export default function DrillDetailPane({
   }, [pageIndex, resultsPages]);
 
   //  Download page of results & trim cache if page not in cache
-  const cachePageLimit = Math.ceil(SAMPLES_ROW_LIMIT / PAGE_SIZE);
   useEffect(() => {
     if (!isLoading && !resultsPages.has(pageIndex)) {
       setIsLoading(true);
       const jsonPayload = getDrillPayload(formData, filters);
+      const cachePageLimit = Math.ceil(SAMPLES_ROW_LIMIT / PAGE_SIZE);
       getDatasourceSamples(
         datasourceType,
         datasourceId,
@@ -152,7 +181,7 @@ export default function DrillDetailPane({
         });
     }
   }, [
-    cachePageLimit,
+    SAMPLES_ROW_LIMIT,
     datasourceId,
     datasourceType,
     filters,
@@ -162,72 +191,25 @@ export default function DrillDetailPane({
     resultsPages,
   ]);
 
-  // this is to preserve the order of the columns, even if there are integer values,
-  // while also only grabbing the first column's keys
-  const columns = useTableColumns(
-    resultsPage?.colNames,
-    resultsPage?.colTypes,
-    resultsPage?.data,
-    formData.datasource,
-  );
-
-  const sortDisabledColumns = columns.map(column => ({
-    ...column,
-    disableSortBy: true,
-  }));
-
-  //  Update page index on pagination click
-  const onServerPagination = useCallback(({ pageIndex }) => {
-    setPageIndex(pageIndex);
-  }, []);
-
-  //  Clear cache on reload button click
-  const handleReload = useCallback(() => {
-    setResultsPages(new Map());
-  }, []);
-
   let tableContent = null;
-
   if (responseError) {
     //  Render error if page download failed
     tableContent = (
-      <div
+      <pre
         css={css`
-          height: ${theme.gridUnit * 128}px;
+          margin-top: ${theme.gridUnit * 4}px;
         `}
       >
-        <pre
-          css={css`
-            margin-top: ${theme.gridUnit * 4}px;
-          `}
-        >
-          {responseError}
-        </pre>
-      </div>
+        {responseError}
+      </pre>
     );
   } else if (!resultsPages.size) {
     //  Render loading if first page hasn't loaded
-    tableContent = (
-      <div
-        css={css`
-          height: ${theme.gridUnit * 128}px;
-        `}
-      >
-        <Loading />
-      </div>
-    );
+    tableContent = <Loading />;
   } else if (resultsPage?.total === 0) {
     //  Render empty state if no results are returned for page
     const title = t('No rows were returned for this dataset');
-    tableContent = (
-      <div
-        css={css`
-          height: ${theme.gridUnit * 128}px;
-        `}
-      >
-        <EmptyStateMedium image="document.svg" title={title} />
-      </div>
-    );
+    tableContent = <EmptyStateMedium image="document.svg" title={title} />;
   } else {
     //  Render table if at least one page has successfully loaded
     tableContent = (


[superset] 07/19: Fix height of modal.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 0e931e620c819c4fa4a12314ee4b5053d2623749
Author: Cody Leff <co...@preset.io>
AuthorDate: Fri Aug 5 17:56:48 2022 -0400

    Fix height of modal.
---
 .../components/DrillDetailPane/DrillDetailPane.tsx | 28 ++++++++++++++++------
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index 915b636412..aeb330cb16 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -190,13 +190,19 @@ export default function DrillDetailPane({
   //  Render error if page download failed
   if (responseError) {
     return (
-      <pre
+      <div
         css={css`
-          margin-top: ${theme.gridUnit * 4}px;
+          height: ${theme.gridUnit * 128}px;
         `}
       >
-        {responseError}
-      </pre>
+        <pre
+          css={css`
+            margin-top: ${theme.gridUnit * 4}px;
+          `}
+        >
+          {responseError}
+        </pre>
+      </div>
     );
   }
 
@@ -205,7 +211,7 @@ export default function DrillDetailPane({
     return (
       <div
         css={css`
-          height: ${theme.gridUnit * 25}px;
+          height: ${theme.gridUnit * 128}px;
         `}
       >
         <Loading />
@@ -216,7 +222,15 @@ export default function DrillDetailPane({
   //  Render empty state if no results are returned for page
   if (resultsPage?.total === 0) {
     const title = t('No rows were returned for this dataset');
-    return <EmptyStateMedium image="document.svg" title={title} />;
+    return (
+      <div
+        css={css`
+          height: ${theme.gridUnit * 128}px;
+        `}
+      >
+        <EmptyStateMedium image="document.svg" title={title} />
+      </div>
+    );
   }
 
   //  Render chart if at least one page has successfully loaded
@@ -225,6 +239,7 @@ export default function DrillDetailPane({
       css={css`
         display: flex;
         flex-direction: column;
+        height: ${theme.gridUnit * 128}px;
       `}
     >
       <TableControls
@@ -251,7 +266,6 @@ export default function DrillDetailPane({
         css={css`
           min-height: 0;
           overflow: scroll;
-          height: ${theme.gridUnit * 128}px;
         `}
       />
     </div>


[superset] 09/19: Open modal on right-click.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit b25c44330e365bd653df1585d2230eeb30acbadd
Author: Cody Leff <co...@preset.io>
AuthorDate: Tue Aug 9 21:19:03 2022 -0600

    Open modal on right-click.
---
 .../src/components/Chart/ChartRenderer.jsx         |  26 +++---
 .../src/components/Chart/DrillDetailModal.tsx      | 100 +++++++++++++++++++++
 .../components/DrillDetailPane/DrillDetailPane.tsx |  24 +++--
 .../dashboard/components/DrillDetailPane/utils.ts  |   2 +-
 .../components/SliceHeaderControls/index.tsx       |   7 +-
 5 files changed, 129 insertions(+), 30 deletions(-)

diff --git a/superset-frontend/src/components/Chart/ChartRenderer.jsx b/superset-frontend/src/components/Chart/ChartRenderer.jsx
index 4c11cfc085..d1584441e3 100644
--- a/superset-frontend/src/components/Chart/ChartRenderer.jsx
+++ b/superset-frontend/src/components/Chart/ChartRenderer.jsx
@@ -30,6 +30,7 @@ import {
 import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
 import { EmptyStateBig, EmptyStateSmall } from 'src/components/EmptyState';
 import ChartContextMenu from './ChartContextMenu';
+import DrillDetailModal from './DrillDetailModal';
 
 const propTypes = {
   annotationData: PropTypes.object,
@@ -83,6 +84,7 @@ class ChartRenderer extends React.Component {
     super(props);
     this.state = {
       inContextMenu: false,
+      drillDetailFilters: null,
     };
     this.hasQueryResponseChange = false;
 
@@ -202,10 +204,7 @@ class ChartRenderer extends React.Component {
   }
 
   handleContextMenuSelected(filters) {
-    const extraFilters = this.props.formData.extra_form_data?.filters || [];
-    // eslint-disable-next-line no-alert
-    alert(JSON.stringify(filters.concat(extraFilters)));
-    this.setState({ inContextMenu: false });
+    this.setState({ inContextMenu: false, drillDetailFilters: filters });
   }
 
   handleContextMenuClosed() {
@@ -289,12 +288,19 @@ class ChartRenderer extends React.Component {
     return (
       <div>
         {this.props.source === 'dashboard' && (
-          <ChartContextMenu
-            ref={this.contextMenuRef}
-            id={chartId}
-            onSelection={this.handleContextMenuSelected}
-            onClose={this.handleContextMenuClosed}
-          />
+          <>
+            <ChartContextMenu
+              ref={this.contextMenuRef}
+              id={chartId}
+              onSelection={this.handleContextMenuSelected}
+              onClose={this.handleContextMenuClosed}
+            />
+            <DrillDetailModal
+              chartId={chartId}
+              initialFilters={this.state.drillDetailFilters}
+              formData={currentFormData}
+            />
+          </>
         )}
         <SuperChart
           disableErrorBoundary
diff --git a/superset-frontend/src/components/Chart/DrillDetailModal.tsx b/superset-frontend/src/components/Chart/DrillDetailModal.tsx
new file mode 100644
index 0000000000..7180d8922d
--- /dev/null
+++ b/superset-frontend/src/components/Chart/DrillDetailModal.tsx
@@ -0,0 +1,100 @@
+/**
+ * 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.
+ */
+
+import React, {
+  useCallback,
+  useContext,
+  useEffect,
+  useMemo,
+  useState,
+} from 'react';
+import { useSelector } from 'react-redux';
+import { useHistory } from 'react-router-dom';
+import {
+  BinaryQueryObjectFilterClause,
+  QueryFormData,
+  t,
+} from '@superset-ui/core';
+import DrillDetailPane from 'src/dashboard/components/DrillDetailPane';
+import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
+import { Slice } from 'src/types/Chart';
+import Modal from '../Modal';
+import Button from '../Button';
+
+const DrillDetailModal: React.FC<{
+  chartId: number;
+  initialFilters?: BinaryQueryObjectFilterClause[];
+  formData: QueryFormData;
+}> = ({ chartId, initialFilters, formData }) => {
+  const [showModal, setShowModal] = useState(false);
+  const openModal = useCallback(() => setShowModal(true), []);
+  const closeModal = useCallback(() => setShowModal(false), []);
+  const history = useHistory();
+  const dashboardPageId = useContext(DashboardPageIdContext);
+  const { slice_name: chartName } = useSelector(
+    (state: { sliceEntities: { slices: Record<number, Slice> } }) =>
+      state.sliceEntities.slices[chartId],
+  );
+
+  const exploreUrl = useMemo(
+    () => `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${chartId}`,
+    [chartId, dashboardPageId],
+  );
+
+  const exploreChart = useCallback(() => {
+    history.push(exploreUrl);
+  }, [exploreUrl, history]);
+
+  //  Trigger modal open when initial filters change
+  useEffect(() => {
+    if (initialFilters) {
+      openModal();
+    }
+  }, [initialFilters, openModal]);
+
+  return (
+    <Modal
+      show={showModal}
+      onHide={closeModal}
+      title={t('Drill to detail: %s', chartName)}
+      footer={
+        <>
+          <Button
+            buttonStyle="secondary"
+            buttonSize="small"
+            onClick={exploreChart}
+          >
+            {t('Edit chart')}
+          </Button>
+          <Button buttonStyle="primary" buttonSize="small" onClick={closeModal}>
+            {t('Close')}
+          </Button>
+        </>
+      }
+      responsive
+      resizable
+      draggable
+      destroyOnClose
+    >
+      <DrillDetailPane formData={formData} initialFilters={initialFilters} />
+    </Modal>
+  );
+};
+
+export default DrillDetailModal;
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index aeb330cb16..070c997efa 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -52,18 +52,16 @@ type ResultsPage = {
 const PAGE_SIZE = 50;
 
 export default function DrillDetailPane({
-  datasource,
-  queryFormData,
-  drillFilters,
+  formData,
+  initialFilters,
 }: {
-  datasource: string;
-  queryFormData?: QueryFormData;
-  drillFilters?: BinaryQueryObjectFilterClause[];
+  formData: QueryFormData;
+  initialFilters?: BinaryQueryObjectFilterClause[];
 }) {
   const theme = useTheme();
   const [pageIndex, setPageIndex] = useState(0);
   const lastPageIndex = useRef(pageIndex);
-  const [filters, setFilters] = useState(drillFilters || []);
+  const [filters, setFilters] = useState(initialFilters || []);
   const [isLoading, setIsLoading] = useState(false);
   const [responseError, setResponseError] = useState('');
   const [resultsPages, setResultsPages] = useState<Map<number, ResultsPage>>(
@@ -77,8 +75,8 @@ export default function DrillDetailPane({
 
   //  Extract datasource ID/type from string ID
   const [datasourceId, datasourceType] = useMemo(
-    () => datasource.split('__'),
-    [datasource],
+    () => formData.datasource.split('__'),
+    [formData.datasource],
   );
 
   //  Get page of results
@@ -120,7 +118,7 @@ export default function DrillDetailPane({
   useEffect(() => {
     if (!resultsPages.has(pageIndex)) {
       setIsLoading(true);
-      const jsonPayload = getDrillPayload(queryFormData, drillFilters);
+      const jsonPayload = getDrillPayload(formData, filters);
       getDatasourceSamples(
         datasourceType,
         datasourceId,
@@ -157,9 +155,9 @@ export default function DrillDetailPane({
     cachePageLimit,
     datasourceId,
     datasourceType,
-    drillFilters,
+    filters,
+    formData,
     pageIndex,
-    queryFormData,
     resultsPages,
   ]);
 
@@ -169,7 +167,7 @@ export default function DrillDetailPane({
     resultsPage?.colNames,
     resultsPage?.colTypes,
     resultsPage?.data,
-    datasource,
+    formData.datasource,
   );
 
   const sortDisabledColumns = columns.map(column => ({
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/utils.ts b/superset-frontend/src/dashboard/components/DrillDetailPane/utils.ts
index e59af45b0d..03494024a9 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/utils.ts
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/utils.ts
@@ -35,7 +35,7 @@ export function getDrillPayload(
   const extras = omit(queryObject.extras, 'having');
   const filters = [
     ...ensureIsArray(queryObject.filters),
-    ...ensureIsArray(drillFilters),
+    ...ensureIsArray(drillFilters).map(f => omit(f, 'formattedVal')),
   ];
   return {
     granularity: queryObject.granularity,
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index fa246a8429..abb59ce359 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -448,12 +448,7 @@ class SliceHeaderControls extends React.PureComponent<
                   </span>
                 }
                 modalTitle={t('Drill to detail: %s', slice.slice_name)}
-                modalBody={
-                  <DrillDetailPane
-                    datasource={this.props.slice.datasource}
-                    queryFormData={this.props.formData}
-                  />
-                }
+                modalBody={<DrillDetailPane formData={this.props.formData} />}
               />
             </Menu.Item>
           )}


[superset] 04/19: Update getDatasourceSamples signature.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit e20dc8be3c952e9fd50e5387f729bbf9612090d7
Author: Cody Leff <co...@preset.io>
AuthorDate: Fri Aug 5 17:13:38 2022 -0400

    Update getDatasourceSamples signature.
---
 .../src/components/Chart/chartAction.js            | 28 +++++++++++++++-------
 .../components/DrillDetailPane/DrillDetailPane.tsx | 12 ++++++----
 2 files changed, 28 insertions(+), 12 deletions(-)

diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js
index f45c5f42cc..dea41497b8 100644
--- a/superset-frontend/src/components/Chart/chartAction.js
+++ b/superset-frontend/src/components/Chart/chartAction.js
@@ -19,7 +19,7 @@
 /* eslint no-undef: 'error' */
 /* eslint no-param-reassign: ["error", { "props": false }] */
 import moment from 'moment';
-import { t, SupersetClient } from '@superset-ui/core';
+import { t, SupersetClient, isDefined } from '@superset-ui/core';
 import { getControlsState } from 'src/explore/store';
 import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
 import {
@@ -603,15 +603,27 @@ export const getDatasourceSamples = async (
   datasourceId,
   force,
   jsonPayload,
-  pagination,
+  perPage,
+  page,
 ) => {
-  let endpoint = `/datasource/samples?force=${force}&datasource_type=${datasourceType}&datasource_id=${datasourceId}`;
-  if (pagination) {
-    endpoint += `&page=${pagination.page}&per_page=${pagination.perPage}`;
-  }
-
   try {
-    const response = await SupersetClient.post({ endpoint, jsonPayload });
+    const searchParams = {
+      force,
+      datasource_type: datasourceType,
+      datasource_id: datasourceId,
+    };
+
+    if (isDefined(perPage) && isDefined(page)) {
+      searchParams.per_page = perPage;
+      searchParams.page = page;
+    }
+
+    const response = await SupersetClient.post({
+      endpoint: '/datasource/samples',
+      jsonPayload,
+      searchParams,
+    });
+
     return response.json.result;
   } catch (err) {
     const clientError = await getClientErrorObject(err);
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index a09494d98a..94f0ee0749 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -121,10 +121,14 @@ export default function DrillDetailPane({
     if (!resultsPages.has(pageIndex)) {
       setIsLoading(true);
       const jsonPayload = getDrillPayload(queryFormData, drillFilters);
-      getDatasourceSamples(datasourceType, datasourceId, true, jsonPayload, {
-        page: pageIndex + 1,
-        perPage: PAGE_SIZE,
-      })
+      getDatasourceSamples(
+        datasourceType,
+        datasourceId,
+        true,
+        jsonPayload,
+        pageIndex + 1,
+        PAGE_SIZE,
+      )
         .then(response => {
           setResultsPages(
             new Map([


[superset] 14/19: Reset page index on reload.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 1c7fa8f36644b2122a0b5d2f0cdddca9e24986e3
Author: Cody Leff <co...@preset.io>
AuthorDate: Thu Aug 11 15:02:13 2022 -0600

    Reset page index on reload.
---
 .../src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx         | 1 +
 1 file changed, 1 insertion(+)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index f8ee5f0a9d..0682ae671b 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -117,6 +117,7 @@ export default function DrillDetailPane({
   //  Clear cache on reload button click
   const handleReload = useCallback(() => {
     setResultsPages(new Map());
+    setPageIndex(0);
   }, []);
 
   //  Clear cache and reset page index if filters change


[superset] 10/19: Fix double requests on modal open, controls disappearing on filter update.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 8a9d4480148aa4bd34e1ab4cfc090006f0951c1f
Author: Cody Leff <co...@preset.io>
AuthorDate: Wed Aug 10 14:01:07 2022 -0600

    Fix double requests on modal open, controls disappearing on filter update.
---
 .../components/DrillDetailPane/DrillDetailPane.tsx | 64 ++++++++++++----------
 .../components/DrillDetailPane/TableControls.tsx   |  5 +-
 2 files changed, 37 insertions(+), 32 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index 070c997efa..b92b87a170 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -116,7 +116,7 @@ export default function DrillDetailPane({
   //  Download page of results & trim cache if page not in cache
   const cachePageLimit = Math.ceil(SAMPLES_ROW_LIMIT / PAGE_SIZE);
   useEffect(() => {
-    if (!resultsPages.has(pageIndex)) {
+    if (!isLoading && !resultsPages.has(pageIndex)) {
       setIsLoading(true);
       const jsonPayload = getDrillPayload(formData, filters);
       getDatasourceSamples(
@@ -157,6 +157,7 @@ export default function DrillDetailPane({
     datasourceType,
     filters,
     formData,
+    isLoading,
     pageIndex,
     resultsPages,
   ]);
@@ -185,9 +186,11 @@ export default function DrillDetailPane({
     setResultsPages(new Map());
   }, []);
 
-  //  Render error if page download failed
+  let tableContent = null;
+
   if (responseError) {
-    return (
+    //  Render error if page download failed
+    tableContent = (
       <div
         css={css`
           height: ${theme.gridUnit * 128}px;
@@ -202,11 +205,9 @@ export default function DrillDetailPane({
         </pre>
       </div>
     );
-  }
-
-  //  Render loading if first page hasn't loaded
-  if (!resultsPages.size) {
-    return (
+  } else if (!resultsPages.size) {
+    //  Render loading if first page hasn't loaded
+    tableContent = (
       <div
         css={css`
           height: ${theme.gridUnit * 128}px;
@@ -215,12 +216,10 @@ export default function DrillDetailPane({
         <Loading />
       </div>
     );
-  }
-
-  //  Render empty state if no results are returned for page
-  if (resultsPage?.total === 0) {
+  } else if (resultsPage?.total === 0) {
+    //  Render empty state if no results are returned for page
     const title = t('No rows were returned for this dataset');
-    return (
+    tableContent = (
       <div
         css={css`
           height: ${theme.gridUnit * 128}px;
@@ -229,23 +228,9 @@ export default function DrillDetailPane({
         <EmptyStateMedium image="document.svg" title={title} />
       </div>
     );
-  }
-
-  //  Render chart if at least one page has successfully loaded
-  return (
-    <div
-      css={css`
-        display: flex;
-        flex-direction: column;
-        height: ${theme.gridUnit * 128}px;
-      `}
-    >
-      <TableControls
-        filters={filters}
-        setFilters={setFilters}
-        totalCount={resultsPage?.total}
-        onReload={handleReload}
-      />
+  } else {
+    //  Render table if at least one page has successfully loaded
+    tableContent = (
       <TableView
         columns={sortDisabledColumns}
         data={resultsPage?.data || []}
@@ -266,6 +251,25 @@ export default function DrillDetailPane({
           overflow: scroll;
         `}
       />
+    );
+  }
+
+  return (
+    <div
+      css={css`
+        display: flex;
+        flex-direction: column;
+        height: ${theme.gridUnit * 128}px;
+      `}
+    >
+      <TableControls
+        filters={filters}
+        setFilters={setFilters}
+        totalCount={resultsPage?.total}
+        loading={isLoading}
+        onReload={handleReload}
+      />
+      {tableContent}
     </div>
   );
 }
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
index 0be301de46..96badcc157 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
@@ -33,12 +33,13 @@ export default function TableControls({
   filters,
   setFilters,
   totalCount,
+  loading,
   onReload,
 }: {
   filters: BinaryQueryObjectFilterClause[];
   setFilters: (filters: BinaryQueryObjectFilterClause[]) => void;
   totalCount?: number;
-  loading?: boolean;
+  loading: boolean;
   onReload: () => void;
 }) {
   const theme = useTheme();
@@ -120,7 +121,7 @@ export default function TableControls({
           height: min-content;
         `}
       >
-        <RowCountLabel rowcount={totalCount} />
+        <RowCountLabel loading={loading && !totalCount} rowcount={totalCount} />
         <Icons.ReloadOutlined
           iconColor={theme.colors.grayscale.light1}
           iconSize="l"


[superset] 17/19: Merge branch 'master' of https://github.com/apache/superset

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 5d9c4266ab90b73bceef662cc38d1a6d6584fccd
Merge: 394d62ee51 11bf7b9125
Author: geido <di...@gmail.com>
AuthorDate: Mon Aug 22 16:54:04 2022 +0300

    Merge branch 'master' of https://github.com/apache/superset

 CONTRIBUTING.md                                    |     2 +-
 Makefile                                           |    12 +-
 UPDATING.md                                        |     1 +
 docs/static/resources/openapi.json                 |     4 +-
 requirements/base.txt                              |     8 +-
 setup.py                                           |     7 +-
 superset-frontend/.eslintrc.js                     |     1 +
 superset-frontend/.storybook/main.js               |     4 +-
 superset-frontend/.storybook/preview.jsx           |     1 +
 .../integration/dashboard/edit_properties.test.ts  |     2 +-
 .../cypress/integration/explore/control.test.ts    |    31 +
 .../explore/visualizations/area.test.js            |     2 +-
 .../explore/visualizations/box_plot.test.js        |     2 +-
 .../explore/visualizations/bubble.test.js          |     2 +-
 .../explore/visualizations/compare.test.js         |     2 +-
 .../explore/visualizations/dist_bar.test.js        |     2 +-
 .../explore/visualizations/dual_line.test.js       |     2 +-
 .../explore/visualizations/gauge.test.js           |     2 +-
 .../explore/visualizations/graph.test.ts           |     2 +-
 .../explore/visualizations/histogram.test.ts       |     2 +-
 .../explore/visualizations/line.test.ts            |     2 +-
 .../integration/explore/visualizations/pie.test.js |     2 +-
 .../explore/visualizations/sankey.test.js          |     2 +-
 .../explore/visualizations/sunburst.test.js        |     2 +-
 .../explore/visualizations/treemap.test.js         |     2 +-
 .../explore/visualizations/world_map.test.js       |     2 +-
 superset-frontend/package-lock.json                | 12804 +++++++++++--------
 superset-frontend/package.json                     |    23 +-
 .../src/sections/advancedAnalytics.tsx             |     1 -
 .../superset-ui-core/src/query/types/Filter.ts     |     6 +
 .../src/ui-overrides/ExtensionsRegistry.ts         |     2 +
 .../test/query/types/Filter.test.ts                |    23 +
 .../src/Gauge/controlPanel.tsx                     |     6 +-
 .../src/Gauge/transformProps.ts                    |    79 +-
 .../plugin-chart-echarts/src/Gauge/types.ts        |     8 +-
 .../src/plugin/buildQuery.ts                       |    10 +-
 .../src/plugin/controlPanel.tsx                    |     3 +-
 .../src/plugin/controls/orderBy.tsx                |     8 +-
 superset-frontend/src/SqlLab/actions/sqlLab.js     |     2 +-
 .../SaveDatasetModal/SaveDatasetModal.test.tsx     |    74 +-
 .../src/components/Datasource/DatasourceEditor.jsx |     4 +-
 .../Datasource/DatasourceEditor.test.jsx           |    12 +-
 .../src/components/FilterableTable/index.tsx       |   462 +-
 .../src/components/ListViewCard/index.tsx          |    49 +-
 .../src/components/MetadataBar/ContentConfig.tsx   |   143 +
 .../src/components/MetadataBar/ContentType.ts      |    91 +
 .../components/MetadataBar/MetadataBar.stories.tsx |   109 +
 .../components/MetadataBar/MetadataBar.test.tsx    |   257 +
 .../components/MetadataBar/Overview.stories.mdx    |   137 +
 .../src/components/MetadataBar/index.tsx           |   190 +
 superset-frontend/src/components/Radio/index.tsx   |     1 +
 .../src/components/TableSelector/index.tsx         |     1 -
 .../components/AddSliceCard/AddSliceCard.tsx       |     2 +-
 .../components/BuilderComponentPane/index.tsx      |    15 +-
 .../DashboardBuilder/DashboardBuilder.tsx          |     1 +
 .../src/dashboard/components/Header/index.jsx      |     1 +
 .../ColorSchemeControl/ColorSchemeLabel.test.tsx   |    59 +
 .../ColorSchemeControl/ColorSchemeLabel.tsx        |   126 +
 .../controls/ColorSchemeControl/index.jsx          |    36 +-
 .../getFormDataFromDashboardContext.test.ts        |    76 +
 .../getFormDataWithDashboardContext.ts             |    57 +-
 .../CRUD/data/database/DatabaseModal/styles.ts     |     2 +-
 superset-frontend/src/views/CRUD/hooks.ts          |     4 +-
 .../src/views/CRUD/welcome/Welcome.tsx             |   143 +-
 .../src/views/components/Menu.test.tsx             |   119 +-
 superset-frontend/src/views/components/Menu.tsx    |    13 +-
 superset-frontend/webpack.config.js                |    19 +
 superset/charts/api.py                             |     4 +
 superset/common/query_context_processor.py         |    14 +
 superset/config.py                                 |    36 +-
 superset/connectors/sqla/views.py                  |     3 +-
 superset/databases/api.py                          |     6 +-
 superset/db_engine_specs/sqlite.py                 |     2 +-
 superset/examples/bart_lines.py                    |     6 +-
 superset/examples/birth_names.py                   |     5 +-
 superset/examples/country_map.py                   |     8 +-
 superset/examples/energy.py                        |     6 +-
 superset/examples/flights.py                       |    10 +-
 superset/examples/helpers.py                       |    16 +-
 superset/examples/long_lat.py                      |     6 +-
 superset/examples/multiformat_time_series.py       |     6 +-
 superset/examples/paris.py                         |     6 +-
 superset/examples/random_time_series.py            |     6 +-
 superset/examples/sf_population_polygons.py        |     6 +-
 superset/examples/world_bank.py                    |     6 +-
 ...665d_fix_table_chart_conditional_formatting_.py |    82 +
 superset/models/helpers.py                         |    15 +-
 superset/queries/saved_queries/api.py              |     1 +
 superset/reports/commands/execute.py               |    38 +-
 superset/reports/models.py                         |     5 +
 superset/reports/notifications/base.py             |     2 +
 superset/reports/notifications/email.py            |    11 +-
 superset/result_set.py                             |     3 +
 superset/templates/superset/basic.html             |     1 +
 .../templates/superset/models/database/macros.html |     2 +-
 superset/utils/core.py                             |    18 +-
 superset/utils/pandas_postprocessing/__init__.py   |     6 +
 .../utils/pandas_postprocessing/contribution.py    |     3 +
 superset/utils/pandas_postprocessing/flatten.py    |     5 +-
 superset/utils/pandas_postprocessing/utils.py      |    10 +
 superset/views/base.py                             |    10 +-
 superset/viz.py                                    |     4 +-
 tests/integration_tests/charts/api_tests.py        |    15 +-
 tests/integration_tests/conftest.py                |    27 +
 tests/integration_tests/core_tests.py              |     4 +-
 tests/integration_tests/databases/api_tests.py     |    34 +-
 tests/integration_tests/email_tests.py             |    31 +
 .../queries/saved_queries/api_tests.py             |     4 +-
 tests/integration_tests/query_context_tests.py     |    45 +
 .../commands/execute_dashboard_report_tests.py     |    45 +
 tests/unit_tests/db_engine_specs/test_sqlite.py    |     4 +-
 tests/unit_tests/notifications/email_tests.py      |     9 +
 .../pandas_postprocessing/test_flatten.py          |    19 +
 .../unit_tests/pandas_postprocessing/test_utils.py |    30 +
 114 files changed, 9962 insertions(+), 5954 deletions(-)


[superset] 08/19: Disable option in chart menu unless feature flag is set.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 5980017c534557a585ce53b7e4de8a59dd20e959
Author: Cody Leff <co...@preset.io>
AuthorDate: Fri Aug 5 19:35:05 2022 -0400

    Disable option in chart menu unless feature flag is set.
---
 .../components/SliceHeaderControls/index.tsx       | 39 +++++++++++-----------
 1 file changed, 20 insertions(+), 19 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index 77d80199c1..fa246a8429 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -437,25 +437,26 @@ class SliceHeaderControls extends React.PureComponent<
           </Menu.Item>
         )}
 
-        {this.props.supersetCanExplore && (
-          <Menu.Item key={MENU_KEYS.DRILL_TO_DETAIL}>
-            <DashboardChartModalTrigger
-              exploreUrl={this.props.exploreUrl}
-              triggerNode={
-                <span data-test="view-query-menu-item">
-                  {t('Drill to detail')}
-                </span>
-              }
-              modalTitle={t('Drill to detail: %s', slice.slice_name)}
-              modalBody={
-                <DrillDetailPane
-                  datasource={this.props.slice.datasource}
-                  queryFormData={this.props.formData}
-                />
-              }
-            />
-          </Menu.Item>
-        )}
+        {isFeatureEnabled(FeatureFlag.DRILL_TO_DETAIL) &&
+          this.props.supersetCanExplore && (
+            <Menu.Item key={MENU_KEYS.DRILL_TO_DETAIL}>
+              <DashboardChartModalTrigger
+                exploreUrl={this.props.exploreUrl}
+                triggerNode={
+                  <span data-test="view-query-menu-item">
+                    {t('Drill to detail')}
+                  </span>
+                }
+                modalTitle={t('Drill to detail: %s', slice.slice_name)}
+                modalBody={
+                  <DrillDetailPane
+                    datasource={this.props.slice.datasource}
+                    queryFormData={this.props.formData}
+                  />
+                }
+              />
+            </Menu.Item>
+          )}
 
         {(slice.description || this.props.supersetCanExplore) && (
           <Menu.Divider />


[superset] 18/19: Merge branch 'codyml/sc-52488/apply-drill-to-detail-modal-filters-gathered' of https://github.com/codyml/superset into chore/e2e-tests-drilltodetail-modal

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 6987b165ef3c69c348e3201cbb4b0b90a94b9e22
Merge: 5d9c4266ab 621ea7314f
Author: geido <di...@gmail.com>
AuthorDate: Mon Aug 22 16:54:56 2022 +0300

    Merge branch 'codyml/sc-52488/apply-drill-to-detail-modal-filters-gathered' of https://github.com/codyml/superset into chore/e2e-tests-drilltodetail-modal

 .../superset-ui-core/src/query/types/Query.ts      |  45 ++--
 .../src/components/Chart/ChartRenderer.jsx         |  26 ++-
 .../src/components/Chart/DrillDetailModal.tsx      | 117 ++++++++++
 .../src/components/Chart/chartAction.js            |  23 +-
 .../components/DrillDetailPane/DrillDetailPane.tsx | 257 +++++++++++++++++++++
 .../components/DrillDetailPane/TableControls.tsx   | 138 +++++++++++
 .../dashboard/components/DrillDetailPane/index.ts  |  20 ++
 .../dashboard/components/DrillDetailPane/utils.ts  |  46 ++++
 .../components/SliceHeaderControls/index.tsx       | 128 ++++++++--
 9 files changed, 755 insertions(+), 45 deletions(-)


[superset] 01/19: Add drill-to-detail modal.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit e0e703820ff2f0d6e642425fd48e92d50811c3db
Author: Cody Leff <co...@preset.io>
AuthorDate: Wed Aug 10 14:40:30 2022 -0600

    Add drill-to-detail modal.
---
 .../superset-ui-core/src/query/types/Query.ts      |  45 ++--
 .../src/components/Chart/chartAction.js            |   7 +-
 .../components/DrillDetailPane/DrillDetailPane.tsx | 239 +++++++++++++++++++++
 .../components/DrillDetailPane/TableControls.tsx   | 134 ++++++++++++
 .../dashboard/components/DrillDetailPane/index.ts  |  22 ++
 .../components/SliceHeaderControls/index.tsx       | 111 ++++++++--
 6 files changed, 526 insertions(+), 32 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
index ec600da862..6c86b397fd 100644
--- a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
+++ b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
@@ -32,26 +32,33 @@ import { PostProcessingRule } from './PostProcessing';
 import { JsonObject } from '../../connection';
 import { TimeGranularity } from '../../time-format';
 
-export type QueryObjectFilterClause = {
+export type BaseQueryObjectFilterClause = {
   col: QueryFormColumn;
   grain?: TimeGranularity;
   isExtra?: boolean;
-} & (
-  | {
-      op: BinaryOperator;
-      val: string | number | boolean;
-      formattedVal?: string;
-    }
-  | {
-      op: SetOperator;
-      val: (string | number | boolean)[];
-      formattedVal?: string[];
-    }
-  | {
-      op: UnaryOperator;
-      formattedVal?: string;
-    }
-);
+};
+
+export type BinaryQueryObjectFilterClause = BaseQueryObjectFilterClause & {
+  op: BinaryOperator;
+  val: string | number | boolean;
+  formattedVal?: string;
+};
+
+export type SetQueryObjectFilterClause = BaseQueryObjectFilterClause & {
+  op: SetOperator;
+  val: (string | number | boolean)[];
+  formattedVal?: string[];
+};
+
+export type UnaryQueryObjectFilterClause = BaseQueryObjectFilterClause & {
+  op: UnaryOperator;
+  formattedVal?: string;
+};
+
+export type QueryObjectFilterClause =
+  | BinaryQueryObjectFilterClause
+  | SetQueryObjectFilterClause
+  | UnaryQueryObjectFilterClause;
 
 export type QueryObjectExtras = Partial<{
   /** HAVING condition for Druid */
@@ -402,4 +409,8 @@ export enum ContributionType {
   Column = 'column',
 }
 
+export type DatasourceSamplesQuery = {
+  filters?: QueryObjectFilterClause[];
+};
+
 export default {};
diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js
index 044593eb37..f45c5f42cc 100644
--- a/superset-frontend/src/components/Chart/chartAction.js
+++ b/superset-frontend/src/components/Chart/chartAction.js
@@ -603,8 +603,13 @@ export const getDatasourceSamples = async (
   datasourceId,
   force,
   jsonPayload,
+  pagination,
 ) => {
-  const endpoint = `/datasource/samples?force=${force}&datasource_type=${datasourceType}&datasource_id=${datasourceId}`;
+  let endpoint = `/datasource/samples?force=${force}&datasource_type=${datasourceType}&datasource_id=${datasourceId}`;
+  if (pagination) {
+    endpoint += `&page=${pagination.page}&per_page=${pagination.perPage}`;
+  }
+
   try {
     const response = await SupersetClient.post({ endpoint, jsonPayload });
     return response.json.result;
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
new file mode 100644
index 0000000000..f6b50f9d17
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -0,0 +1,239 @@
+/**
+ * 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.
+ */
+
+import React, {
+  useState,
+  useEffect,
+  useMemo,
+  useCallback,
+  useRef,
+} from 'react';
+import {
+  BinaryQueryObjectFilterClause,
+  css,
+  ensureIsArray,
+  GenericDataType,
+  t,
+  useTheme,
+} from '@superset-ui/core';
+import Loading from 'src/components/Loading';
+import { EmptyStateMedium } from 'src/components/EmptyState';
+import TableView, { EmptyWrapperType } from 'src/components/TableView';
+import { useTableColumns } from 'src/explore/components/DataTableControl';
+import { getDatasourceSamples } from 'src/components/Chart/chartAction';
+import TableControls from './TableControls';
+
+type ResultsPage = {
+  total: number;
+  data: Record<string, any>[];
+  colNames: string[];
+  colTypes: GenericDataType[];
+};
+
+const PAGE_SIZE = 50;
+const MAX_CACHED_PAGES = 5;
+
+export default function DrillDetailPane({
+  datasource,
+  initialFilters,
+}: {
+  datasource: string;
+  initialFilters?: BinaryQueryObjectFilterClause[];
+}) {
+  const theme = useTheme();
+  const [pageIndex, setPageIndex] = useState(0);
+  const lastPageIndex = useRef(pageIndex);
+  const [filters, setFilters] = useState(initialFilters || []);
+  const [isLoading, setIsLoading] = useState(false);
+  const [responseError, setResponseError] = useState('');
+  const [resultsPages, setResultsPages] = useState<Map<number, ResultsPage>>(
+    new Map(),
+  );
+
+  //  Get string identifier for dataset
+  const [datasourceId, datasourceType] = useMemo(
+    () => datasource.split('__'),
+    [datasource],
+  );
+
+  //  Get page of results
+  const resultsPage = useMemo(() => {
+    const nextResultsPage = resultsPages.get(pageIndex);
+    if (nextResultsPage) {
+      lastPageIndex.current = pageIndex;
+      return nextResultsPage;
+    }
+
+    return resultsPages.get(lastPageIndex.current);
+  }, [pageIndex, resultsPages]);
+
+  //  Clear cache and reset page index if filters change
+  useEffect(() => {
+    setResultsPages(new Map());
+    setPageIndex(0);
+  }, [filters]);
+
+  //  Update cache order if page in cache
+  useEffect(() => {
+    if (
+      resultsPages.has(pageIndex) &&
+      [...resultsPages.keys()].at(-1) !== pageIndex
+    ) {
+      const nextResultsPages = new Map(resultsPages);
+      nextResultsPages.delete(pageIndex);
+      setResultsPages(
+        nextResultsPages.set(
+          pageIndex,
+          resultsPages.get(pageIndex) as ResultsPage,
+        ),
+      );
+    }
+  }, [pageIndex, resultsPages]);
+
+  //  Download page of results & trim cache if page not in cache
+  useEffect(() => {
+    if (!resultsPages.has(pageIndex)) {
+      setIsLoading(true);
+      getDatasourceSamples(
+        datasourceType,
+        datasourceId,
+        true,
+        filters.length ? { filters } : null,
+        { page: pageIndex + 1, perPage: PAGE_SIZE },
+      )
+        .then(response => {
+          setResultsPages(
+            new Map([
+              ...[...resultsPages.entries()].slice(-MAX_CACHED_PAGES + 1),
+              [
+                pageIndex,
+                {
+                  total: response.total_count,
+                  data: response.data,
+                  colNames: ensureIsArray(response.colnames),
+                  colTypes: ensureIsArray(response.coltypes),
+                },
+              ],
+            ]),
+          );
+          setResponseError('');
+        })
+        .catch(error => {
+          setResponseError(`${error.name}: ${error.message}`);
+        })
+        .finally(() => {
+          setIsLoading(false);
+        });
+    }
+  }, [datasourceId, datasourceType, filters, pageIndex, resultsPages]);
+
+  // this is to preserve the order of the columns, even if there are integer values,
+  // while also only grabbing the first column's keys
+  const columns = useTableColumns(
+    resultsPage?.colNames,
+    resultsPage?.colTypes,
+    resultsPage?.data,
+    datasource,
+  );
+
+  const sortDisabledColumns = columns.map(column => ({
+    ...column,
+    disableSortBy: true,
+  }));
+
+  //  Update page index on pagination click
+  const onServerPagination = useCallback(({ pageIndex }) => {
+    setPageIndex(pageIndex);
+  }, []);
+
+  //  Clear cache on reload button click
+  const handleReload = useCallback(() => {
+    setResultsPages(new Map());
+  }, []);
+
+  //  Render error if page download failed
+  if (responseError) {
+    return (
+      <pre
+        css={css`
+          margin-top: ${theme.gridUnit * 4}px;
+        `}
+      >
+        {responseError}
+      </pre>
+    );
+  }
+
+  //  Render loading if first page hasn't loaded
+  if (!resultsPages.size) {
+    return (
+      <div
+        css={css`
+          height: ${theme.gridUnit * 25}px;
+        `}
+      >
+        <Loading />
+      </div>
+    );
+  }
+
+  //  Render empty state if no results are returned for page
+  if (resultsPage?.total === 0) {
+    const title = t('No rows were returned for this dataset');
+    return <EmptyStateMedium image="document.svg" title={title} />;
+  }
+
+  //  Render chart if at least one page has successfully loaded
+  return (
+    <div
+      css={css`
+        display: flex;
+        flex-direction: column;
+      `}
+    >
+      <TableControls
+        filters={filters}
+        setFilters={setFilters}
+        totalCount={resultsPage?.total}
+        onReload={handleReload}
+      />
+      <TableView
+        columns={sortDisabledColumns}
+        data={resultsPage?.data || []}
+        pageSize={PAGE_SIZE}
+        totalCount={resultsPage?.total}
+        serverPagination
+        initialPageIndex={pageIndex}
+        onServerPagination={onServerPagination}
+        loading={isLoading}
+        noDataText={t('No results')}
+        emptyWrapperType={EmptyWrapperType.Small}
+        className="table-condensed"
+        isPaginationSticky
+        showRowCount={false}
+        small
+        css={css`
+          min-height: 0;
+          overflow: scroll;
+          height: ${theme.gridUnit * 128}px;
+        `}
+      />
+    </div>
+  );
+}
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
new file mode 100644
index 0000000000..0be301de46
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.tsx
@@ -0,0 +1,134 @@
+/**
+ * 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.
+ */
+
+import React, { useCallback, useMemo } from 'react';
+import { Tag } from 'antd';
+import {
+  BinaryQueryObjectFilterClause,
+  css,
+  isAdhocColumn,
+  t,
+  useTheme,
+} from '@superset-ui/core';
+import RowCountLabel from 'src/explore/components/RowCountLabel';
+import Icons from 'src/components/Icons';
+
+export default function TableControls({
+  filters,
+  setFilters,
+  totalCount,
+  onReload,
+}: {
+  filters: BinaryQueryObjectFilterClause[];
+  setFilters: (filters: BinaryQueryObjectFilterClause[]) => void;
+  totalCount?: number;
+  loading?: boolean;
+  onReload: () => void;
+}) {
+  const theme = useTheme();
+  const filterMap: Record<string, BinaryQueryObjectFilterClause> = useMemo(
+    () =>
+      Object.assign(
+        {},
+        ...filters.map(filter => ({
+          [isAdhocColumn(filter.col)
+            ? (filter.col.label as string)
+            : filter.col]: filter,
+        })),
+      ),
+    [filters],
+  );
+
+  const removeFilter = useCallback(
+    colName => {
+      const updatedFilterMap = { ...filterMap };
+      delete updatedFilterMap[colName];
+      setFilters([...Object.values(updatedFilterMap)]);
+    },
+    [filterMap, setFilters],
+  );
+
+  const filterTags = useMemo(
+    () =>
+      Object.entries(filterMap)
+        .map(([colName, { val }]) => ({ colName, val }))
+        .sort((a, b) => a.colName.localeCompare(b.colName)),
+    [filterMap],
+  );
+
+  return (
+    <div
+      css={css`
+        display: flex;
+        justify-content: space-between;
+        padding: ${theme.gridUnit / 2}px 0;
+      `}
+    >
+      <div
+        css={css`
+          display: flex;
+          flex-wrap: wrap;
+          margin-bottom: -${theme.gridUnit * 4}px;
+        `}
+      >
+        {filterTags.map(({ colName, val }) => (
+          <Tag
+            closable
+            onClose={removeFilter.bind(null, colName)}
+            key={colName}
+            css={css`
+              height: ${theme.gridUnit * 6}px;
+              display: flex;
+              align-items: center;
+              padding: ${theme.gridUnit / 2}px ${theme.gridUnit * 2}px;
+              margin-right: ${theme.gridUnit * 4}px;
+              margin-bottom: ${theme.gridUnit * 4}px;
+              line-height: 1.2;
+            `}
+          >
+            <span
+              css={css`
+                margin-right: ${theme.gridUnit}px;
+              `}
+            >
+              {colName}
+            </span>
+            <strong>{val}</strong>
+          </Tag>
+        ))}
+      </div>
+      <div
+        css={css`
+          display: flex;
+          align-items: center;
+          height: min-content;
+        `}
+      >
+        <RowCountLabel rowcount={totalCount} />
+        <Icons.ReloadOutlined
+          iconColor={theme.colors.grayscale.light1}
+          iconSize="l"
+          aria-label={t('Reload')}
+          role="button"
+          onClick={onReload}
+        />
+      </div>
+    </div>
+  );
+}
diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/index.ts b/superset-frontend/src/dashboard/components/DrillDetailPane/index.ts
new file mode 100644
index 0000000000..aeaf795770
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/index.ts
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+
+import DrillDetailPane from './DrillDetailPane';
+
+export default DrillDetailPane;
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index 8673a03848..bb41c82d3e 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -16,8 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { MouseEvent, Key } from 'react';
-import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
+import React, {
+  MouseEvent,
+  Key,
+  ReactChild,
+  useState,
+  useCallback,
+} from 'react';
+import {
+  Link,
+  RouteComponentProps,
+  useHistory,
+  withRouter,
+} from 'react-router-dom';
 import moment from 'moment';
 import {
   Behavior,
@@ -40,6 +51,8 @@ import ModalTrigger from 'src/components/ModalTrigger';
 import Button from 'src/components/Button';
 import ViewQueryModal from 'src/explore/components/controls/ViewQueryModal';
 import { ResultsPaneOnDashboard } from 'src/explore/components/DataTablesPane';
+import Modal from 'src/components/Modal';
+import DrillDetailPane from 'src/dashboard/components/DrillDetailPane';
 
 const MENU_KEYS = {
   CROSS_FILTER_SCOPING: 'cross_filter_scoping',
@@ -52,6 +65,7 @@ const MENU_KEYS = {
   TOGGLE_CHART_DESCRIPTION: 'toggle_chart_description',
   VIEW_QUERY: 'view_query',
   VIEW_RESULTS: 'view_results',
+  DRILL_TO_DETAIL: 'drill_to_detail',
 };
 
 const VerticalDotsContainer = styled.div`
@@ -97,6 +111,7 @@ export interface SliceHeaderControlsProps {
     slice_id: number;
     slice_description: string;
     form_data?: { emit_filter?: boolean };
+    datasource: string;
   };
 
   componentId: string;
@@ -140,6 +155,68 @@ const dropdownIconsStyles = css`
   }
 `;
 
+const DashboardChartModalTrigger = ({
+  exploreUrl,
+  triggerNode,
+  modalTitle,
+  modalBody,
+}: {
+  exploreUrl: string;
+  triggerNode: ReactChild;
+  modalTitle: ReactChild;
+  modalBody: ReactChild;
+}) => {
+  const [showModal, setShowModal] = useState(false);
+  const openModal = useCallback(() => setShowModal(true), []);
+  const closeModal = useCallback(() => setShowModal(false), []);
+  const history = useHistory();
+  const exploreChart = () => history.push(exploreUrl);
+
+  return (
+    <>
+      <span
+        data-test="span-modal-trigger"
+        onClick={openModal}
+        role="button"
+        tabIndex={0}
+      >
+        {triggerNode}
+      </span>
+      {(() => (
+        <Modal
+          show={showModal}
+          onHide={closeModal}
+          title={modalTitle}
+          footer={
+            <>
+              <Button
+                buttonStyle="secondary"
+                buttonSize="small"
+                onClick={exploreChart}
+              >
+                {t('Edit chart')}
+              </Button>
+              <Button
+                buttonStyle="primary"
+                buttonSize="small"
+                onClick={closeModal}
+              >
+                {t('Close')}
+              </Button>
+            </>
+          }
+          responsive
+          resizable
+          draggable
+          destroyOnClose
+        >
+          {modalBody}
+        </Modal>
+      ))()}
+    </>
+  );
+};
+
 class SliceHeaderControls extends React.PureComponent<
   SliceHeaderControlsPropsWithRouter,
   State
@@ -339,7 +416,8 @@ class SliceHeaderControls extends React.PureComponent<
 
         {this.props.supersetCanExplore && (
           <Menu.Item key={MENU_KEYS.VIEW_RESULTS}>
-            <ModalTrigger
+            <DashboardChartModalTrigger
+              exploreUrl={this.props.exploreUrl}
               triggerNode={
                 <span data-test="view-query-menu-item">
                   {t('View as table')}
@@ -355,18 +433,23 @@ class SliceHeaderControls extends React.PureComponent<
                   isVisible
                 />
               }
-              modalFooter={
-                <Button
-                  buttonStyle="secondary"
-                  buttonSize="small"
-                  onClick={() => this.props.history.push(this.props.exploreUrl)}
-                >
-                  {t('Edit chart')}
-                </Button>
+            />
+          </Menu.Item>
+        )}
+
+        {this.props.supersetCanExplore && (
+          <Menu.Item key={MENU_KEYS.DRILL_TO_DETAIL}>
+            <DashboardChartModalTrigger
+              exploreUrl={this.props.exploreUrl}
+              triggerNode={
+                <span data-test="view-query-menu-item">
+                  {t('Drill to detail')}
+                </span>
+              }
+              modalTitle={t('Drill to detail: %s', slice.slice_name)}
+              modalBody={
+                <DrillDetailPane datasource={this.props.slice.datasource} />
               }
-              draggable
-              resizable
-              responsive
             />
           </Menu.Item>
         )}


[superset] 06/19: Fix incorrect argument order in getDatasourceSamples invocation.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit eab75a7766a44124af2e71312196ba30918d681b
Author: Cody Leff <co...@preset.io>
AuthorDate: Fri Aug 5 17:42:19 2022 -0400

    Fix incorrect argument order in getDatasourceSamples invocation.
---
 .../src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index 94f0ee0749..915b636412 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -126,8 +126,8 @@ export default function DrillDetailPane({
         datasourceId,
         true,
         jsonPayload,
-        pageIndex + 1,
         PAGE_SIZE,
+        pageIndex + 1,
       )
         .then(response => {
           setResultsPages(


[superset] 12/19: Set force=false for all requests.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 24c079adb6d0015935164708a43caa110ccf1df5
Author: Cody Leff <co...@preset.io>
AuthorDate: Thu Aug 11 14:41:49 2022 -0600

    Set force=false for all requests.
---
 .../src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
index b92b87a170..03a3f99f68 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.tsx
@@ -122,7 +122,7 @@ export default function DrillDetailPane({
       getDatasourceSamples(
         datasourceType,
         datasourceId,
-        true,
+        false,
         jsonPayload,
         PAGE_SIZE,
         pageIndex + 1,


[superset] 05/19: One-line import/export.

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch chore/e2e-tests-drilltodetail-modal
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 17cad5122aed7427249c05378735d3eaede3e4af
Author: Cody Leff <co...@preset.io>
AuthorDate: Fri Aug 5 17:30:48 2022 -0400

    One-line import/export.
---
 superset-frontend/src/dashboard/components/DrillDetailPane/index.ts | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/index.ts b/superset-frontend/src/dashboard/components/DrillDetailPane/index.ts
index aeaf795770..7e23e0a55c 100644
--- a/superset-frontend/src/dashboard/components/DrillDetailPane/index.ts
+++ b/superset-frontend/src/dashboard/components/DrillDetailPane/index.ts
@@ -17,6 +17,4 @@
  * under the License.
  */
 
-import DrillDetailPane from './DrillDetailPane';
-
-export default DrillDetailPane;
+export { default } from './DrillDetailPane';