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

hugh pushed a commit to branch hugh-v2022.29.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit cda3ddb135103e4b1d1f26efc33c7a47189470dc
Author: hughhhh <[email protected]>
AuthorDate: Thu Jul 21 15:04:33 2022 -0400

    add fixes
---
 .../src/SqlLab/components/ResultSet/index.tsx      | 42 ++++++++----
 superset/models/helpers.py                         | 80 ++++++++++++++++++++--
 superset/utils/core.py                             |  2 +-
 superset/views/core.py                             | 19 ++---
 4 files changed, 116 insertions(+), 27 deletions(-)

diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx 
b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
index 124d2546a6..7b7780a0b4 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
@@ -29,6 +29,9 @@ import {
   SaveDatasetModal,
 } from 'src/SqlLab/components/SaveDatasetModal';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
+import { EXPLORE_CHART_DEFAULT } from 'src/SqlLab/types';
+import { mountExploreUrl } from 'src/explore/exploreUtils';
+import { postFormData } from 'src/explore/exploreUtils/formData';
 import ProgressBar from 'src/components/ProgressBar';
 import Loading from 'src/components/Loading';
 import FilterableTable, {
@@ -41,6 +44,7 @@ import ExploreCtasResultsButton from 
'../ExploreCtasResultsButton';
 import ExploreResultsButton from '../ExploreResultsButton';
 import HighlightedSql from '../HighlightedSql';
 import QueryStateLabel from '../QueryStateLabel';
+import { URL_PARAMS } from 'src/constants';
 
 enum LIMITING_FACTOR {
   QUERY = 'QUERY',
@@ -134,6 +138,8 @@ export default class ResultSet extends React.PureComponent<
     this.reFetchQueryResults = this.reFetchQueryResults.bind(this);
     this.toggleExploreResultsButton =
       this.toggleExploreResultsButton.bind(this);
+    this.createExploreResultsOnClick =
+      this.createExploreResultsOnClick.bind(this);
   }
 
   async componentDidMount() {
@@ -213,6 +219,26 @@ export default class ResultSet extends React.PureComponent<
     }
   }
 
+  async createExploreResultsOnClick() {
+    const { results } = this.props.query;
+
+    if (results.query_id) {
+      const key = await postFormData(results.query_id, 'query', {
+        ...EXPLORE_CHART_DEFAULT,
+        datasource: `${results.query_id}__query`,
+        ...{
+          all_columns: results.columns.map(column => column.name),
+        },
+      });
+      const url = mountExploreUrl(null, {
+        [URL_PARAMS.formDataKey.name]: key,
+      });
+      window.open(url, '_blank', 'noreferrer');
+    } else {
+      this.setState({ showSaveDatasetModal: true });
+    }
+  }
+
   renderControls() {
     if (this.props.search || this.props.visualize || this.props.csv) {
       let { data } = this.props.query.results;
@@ -250,19 +276,11 @@ export default class ResultSet extends 
React.PureComponent<
               this.props.database?.allows_virtual_table_explore && (
                 <ExploreResultsButton
                   database={this.props.database}
-                  onClick={() => {
-                    // There is currently redux / state issue where sometimes 
a query will have serverId
-                    // and other times it will not.  We need this attribute 
consistently for this to work
-                    // const qid = this.props?.query?.results?.query_id;
-                    // if (qid) {
-                    //   // This will open explore using the query as 
datasource
-                    //   window.location.href = 
`/explore/?dataset_type=query&dataset_id=${qid}`;
-                    // } else {
-                    //   this.setState({ showSaveDatasetModal: true });
-                    // }
-                    this.setState({ showSaveDatasetModal: true });
-                  }}
+                  onClick={() => this.setState({ showSaveDatasetModal: true })}
                 />
+                // In order to use the new workflow for a query powered chart, 
replace the
+                // above function with:
+                // onClick={this.createExploreResultsOnClick}
               )}
             {this.props.csv && (
               <Button buttonSize="small" href={`/superset/csv/${query.id}`}>
diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index 77707201b5..9895e6e1cd 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -66,6 +66,7 @@ from superset import app, is_feature_enabled, security_manager
 from superset.advanced_data_type.types import AdvancedDataTypeResponse
 from superset.common.db_query_status import QueryStatus
 from superset.constants import EMPTY_STRING, NULL_STRING
+from superset.db_engine_specs.base import TimestampExpression
 from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
 from superset.exceptions import (
     AdvancedDataTypeResponseError,
@@ -1189,6 +1190,62 @@ class ExploreMixin:  # pylint: 
disable=too-many-public-methods
 
         return or_(*groups)
 
+    def values_for_column(self, column_name: str, limit: int = 10000) -> 
List[Any]:
+        """Runs query against sqla to retrieve some
+        sample values for the given column.
+        """
+        cols = {}
+        for col in self.columns:
+            if isinstance(col, dict):
+                cols[col.get("column_name")] = col
+            else:
+                cols[col.column_name] = col
+
+        target_col = cols[column_name]
+        tp = None  # todo(hughhhh): add back self.get_template_processor()
+        tbl, cte = self.get_from_clause(tp)
+
+        if isinstance(target_col, dict):
+            sql_column = sa.column(target_col.get("name"))
+        else:
+            sql_column = target_col
+
+        qry = sa.select([sql_column]).select_from(tbl).distinct()
+        if limit:
+            qry = qry.limit(limit)
+
+        engine = self.database.get_sqla_engine()
+        sql = qry.compile(engine, compile_kwargs={"literal_binds": True})
+        sql = self._apply_cte(sql, cte)
+        sql = self.mutate_query_from_config(sql)
+
+        df = pd.read_sql_query(sql=sql, con=engine)
+        return df[column_name].to_list()
+
+    def get_timestamp_expression(
+        self,
+        column: Dict[str, Any],
+        time_grain: Optional[str],
+        label: Optional[str] = None,
+        template_processor: Optional[
+            BaseTemplateProcessor
+        ] = None,  # pylint: disable=unused-argument
+    ) -> Union[TimestampExpression, Label]:
+        """
+        Return a SQLAlchemy Core element representation of self to be used in 
a query.
+
+        :param time_grain: Optional time grain, e.g. P1Y
+        :param label: alias/label that column is expected to have
+        :param template_processor: template processor
+        :return: A TimeExpression object wrapped in a Label if supported by db
+        """
+        label = label or utils.DTTM_ALIAS
+        column_spec = self.db_engine_spec.get_column_spec(column.get("type"))
+        type_ = column_spec.sqla_type if column_spec else sa.DateTime
+        col = sa.column(column.get("column_name"), type_=type_)
+        time_expr = self.db_engine_spec.get_timestamp_expr(col, None, 
time_grain)
+        return self.make_sqla_column_compatible(time_expr, label)
+
     def get_sqla_query(  # pylint: 
disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements
         self,
         apply_fetch_values_predicate: bool = False,
@@ -1410,9 +1467,14 @@ class ExploreMixin:  # pylint: 
disable=too-many-public-methods
             time_filters: List[Any] = []
 
             if is_timeseries:
-                timestamp = dttm_col.get_timestamp_expression(
-                    time_grain=time_grain, 
template_processor=template_processor
-                )
+                if isinstance(dttm_col, dict):
+                    timestamp = self.get_timestamp_expression(
+                        dttm_col, time_grain, template_processor
+                    )
+                else:
+                    timestamp = dttm_col.get_timestamp_expression(
+                        time_grain=time_grain, 
template_processor=template_processor
+                    )
                 # always put timestamp as the first column
                 select_exprs.insert(0, timestamp)
                 groupby_all_columns[timestamp.name] = timestamp
@@ -1467,9 +1529,15 @@ class ExploreMixin:  # pylint: 
disable=too-many-public-methods
                 if sqla_col is not None:
                     pass
                 elif col_obj and filter_grain:
-                    sqla_col = col_obj.get_timestamp_expression(
-                        time_grain=filter_grain, 
template_processor=template_processor
-                    )
+                    if isinstance(col_obj, dict):
+                        sqla_col = self.get_timestamp_expression(
+                            col_obj, time_grain, template_processor
+                        )
+                    else:
+                        sqla_col = col_obj.get_timestamp_expression(
+                            time_grain=filter_grain,
+                            template_processor=template_processor,
+                        )
                 elif col_obj and isinstance(col_obj, dict):
                     sqla_col = sa.column(col_obj.get("column_name"))
                 elif col_obj:
diff --git a/superset/utils/core.py b/superset/utils/core.py
index 44d76ea533..ba7237b77b 100644
--- a/superset/utils/core.py
+++ b/superset/utils/core.py
@@ -1728,7 +1728,7 @@ def is_test() -> bool:
     return strtobool(os.environ.get("SUPERSET_TESTENV", "false"))
 
 
-def get_time_filter_status(  # pylint: disable=too-many-branches
+def get_time_filter_status(
     datasource: "BaseDatasource",
     applied_time_extras: Dict[str, str],
 ) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
diff --git a/superset/views/core.py b/superset/views/core.py
index 0598c72446..2b732aad4f 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -427,13 +427,13 @@ class Superset(BaseSupersetView):  # pylint: 
disable=too-many-public-methods
         _, slc = get_form_data(slice_id, use_slice_data=True)
         if not slc:
             abort(404)
-        endpoint = "/superset/explore/?form_data={}".format(
+        endpoint = "/explore/?form_data={}".format(
             parse.quote(json.dumps({"slice_id": slice_id}))
         )
 
         is_standalone_mode = ReservedUrlParameters.is_standalone_mode()
         if is_standalone_mode:
-            endpoint += 
f"&{ReservedUrlParameters.STANDALONE}={is_standalone_mode}"
+            endpoint += f"&{ReservedUrlParameters.STANDALONE}=true"
         return redirect(endpoint)
 
     def get_query_string_response(self, viz_obj: BaseViz) -> FlaskResponse:
@@ -815,7 +815,7 @@ class Superset(BaseSupersetView):  # pylint: 
disable=too-many-public-methods
             try:
                 datasource = DatasourceDAO.get_datasource(
                     db.session,
-                    DatasourceType(cast(str, datasource_type)),
+                    DatasourceType("table"),
                     datasource_id,
                 )
             except DatasetNotFoundError:
@@ -2477,9 +2477,7 @@ class Superset(BaseSupersetView):  # pylint: 
disable=too-many-public-methods
     @has_access
     @event_logger.log_this
     @expose("/csv/<client_id>")
-    def csv(  # pylint: disable=no-self-use,too-many-locals
-        self, client_id: str
-    ) -> FlaskResponse:
+    def csv(self, client_id: str) -> FlaskResponse:  # pylint: 
disable=no-self-use
         """Download the query results as csv."""
         logger.info("Exporting CSV file [%s]", client_id)
         query = db.session.query(Query).filter_by(client_id=client_id).one()
@@ -2502,8 +2500,13 @@ class Superset(BaseSupersetView):  # pylint: 
disable=too-many-public-methods
             obj = _deserialize_results_payload(
                 payload, query, cast(bool, results_backend_use_msgpack)
             )
-            columns = [c["name"] for c in obj["columns"]]
-            df = pd.DataFrame.from_records(obj["data"], columns=columns)
+
+            df = pd.DataFrame(
+                data=obj["data"],
+                dtype=object,
+                columns=[c["name"] for c in obj["columns"]],
+            )
+
             logger.info("Using pandas to convert to CSV")
         else:
             logger.info("Running a query to turn into CSV")

Reply via email to