From eef489c6676928d06e99cd1c3203aa8fc2f3cb1a Mon Sep 17 00:00:00 2001 From: cccs-jc <56140112+cccs-jc@users.noreply.github.com> Date: Tue, 1 Jun 2021 14:23:51 -0400 Subject: [PATCH] feat: add support for filters in sqlLab (#14765) --- docs/installation.rst | 31 +++++++++++++++++++ .../src/SqlLab/components/ResultSet.tsx | 15 ++++++++- superset/views/utils.py | 25 ++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8d28bd0b811f..6c3cc1efedb6 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1256,6 +1256,37 @@ in this dictionary are made available for users to use in their SQL. 'my_crazy_macro': lambda x: x*2, } +Default values for jinja templates can be specified via ``Parameters`` menu in the SQL Lab user interface. +In the UI you can assign a set of parameters as JSON + +.. code-block:: JSON + { + "my_table": "foo" + } + +The parameters become available in your SQL (example:SELECT * FROM {{ my_table }} ) by using Jinja templating syntax. +SQL Lab template parameters are stored with the dataset as TEMPLATE PARAMETERS. + +There is a special ``_filters`` parameter which can be used to test filters used in the jinja template. + +.. code-block:: JSON + { + "_filters": { + "col": "action_type", + "op": "IN", + "val": ["sell", "buy"] + } + +.. code-block:: python + SELECT action, count(*) as times + FROM logs + WHERE + action in ({{ "'" + "','".join(filter_values('action_type')) + "'" }}) + GROUP BY action + +Note ``_filters`` is not stored with the dataset. It's only used within the SQL Lab UI. + + Besides default Jinja templating, SQL lab also supports self-defined template processor by setting the ``CUSTOM_TEMPLATE_PROCESSORS`` in your superset configuration. The values in this dictionary overwrite the default Jinja template processors of the diff --git a/superset-frontend/src/SqlLab/components/ResultSet.tsx b/superset-frontend/src/SqlLab/components/ResultSet.tsx index a8fe019c7e6a..bdd0e2920936 100644 --- a/superset-frontend/src/SqlLab/components/ResultSet.tsx +++ b/superset-frontend/src/SqlLab/components/ResultSet.tsx @@ -271,9 +271,22 @@ export default class ResultSet extends React.PureComponent< return; } - const { schema, sql, dbId, templateParams } = this.props.query; + const { schema, sql, dbId } = this.props.query; + let { templateParams } = this.props.query; const selectedColumns = this.props.query?.results?.selected_columns || []; + // The filters param is only used to test jinja templates. + // Remove the special filters entry from the templateParams + // before saving the dataset. + if (templateParams) { + const p = JSON.parse(templateParams); + if (p.filters) { + /* eslint-disable-next-line no-underscore-dangle */ + delete p._filters; + templateParams = JSON.stringify(p); + } + } + this.props.actions .createDatasource({ schema, diff --git a/superset/views/utils.py b/superset/views/utils.py index 767490ca7317..1d1603e41d78 100644 --- a/superset/views/utils.py +++ b/superset/views/utils.py @@ -127,13 +127,16 @@ def loads_request_json(request_json_data: str) -> Dict[Any, Any]: def get_form_data( # pylint: disable=too-many-locals slice_id: Optional[int] = None, use_slice_data: bool = False ) -> Tuple[Dict[str, Any], Optional[Slice]]: - form_data = {} + form_data: Dict[str, Any] = {} # chart data API requests are JSON request_json_data = ( request.json["queries"][0] if request.is_json and "queries" in request.json else None ) + + add_sqllab_custom_filters(form_data) + request_form_data = request.form.get("form_data") request_args_data = request.args.get("form_data") if request_json_data: @@ -196,6 +199,26 @@ def get_form_data( # pylint: disable=too-many-locals return form_data, slc +def add_sqllab_custom_filters(form_data: Dict[Any, Any]) -> Any: + """ + SQLLab can include a "filters" attribute in the templateParams. + The filters attribute is a list of filters to include in the + request. Useful for testing templates in SQLLab. + """ + try: + data = json.loads(request.data) + if isinstance(data, dict): + params_str = data.get("templateParams") + if isinstance(params_str, str): + params = json.loads(params_str) + if isinstance(params, dict): + filters = params.get("filters") + if filters: + form_data.update({"filters": filters}) + except (TypeError, json.JSONDecodeError): + data = {} + + def get_datasource_info( datasource_id: Optional[int], datasource_type: Optional[str], form_data: FormData ) -> Tuple[int, Optional[str]]: