john-bodley commented on code in PR #23269:
URL: https://github.com/apache/superset/pull/23269#discussion_r1171934332
##########
superset/utils/dashboard_filter_scopes_converter.py:
##########
@@ -88,3 +90,249 @@ def copy_filter_scopes(
if int(slice_id) in old_to_new_slc_id_dict
]
return new_filter_scopes
+
+
+def convert_filter_scopes_to_native_filters( # pylint:
disable=invalid-name,too-many-branches,too-many-locals,too-many-nested-blocks,too-many-statements
+ json_metadata: Dict[str, Any],
+ position_json: Dict[str, Any],
+ filter_boxes: List[Slice],
+) -> List[Dict[str, Any]]:
+ """
+ Convert the legacy filter scopes et al. to the native filter configuration.
+
+ Dashboard filter scopes are implicitly defined where an undefined scope
implies
+ no immunity, i.e., they apply to all applicable charts. The
`convert_filter_scopes`
+ method provides an explicit definition by extracting the underlying
filter-box
+ configurations.
+
+ Hierarchical legacy filters are defined via non-exclusion of peer or
children
+ filter-box charts whereas native hierarchical filters are defined via
explicit
+ parental relationships, i.e., the inverse.
+
+ :param json_metata: The dashboard metadata
+ :param position_json: The dashboard layout
+ :param filter_boxes: The filter-box charts associated with the dashboard
+ :returns: The native filter configuration
+ :see: convert_filter_scopes
+ """
+
+ shortid = ShortId()
+ default_filters = json.loads(json_metadata.get("default_filters") or "{}")
+ filter_scopes = json_metadata.get("filter_scopes", {})
+ filter_box_ids = {filter_box.id for filter_box in filter_boxes}
+
+ filter_scope_by_key_and_field: Dict[str, Dict[str, Dict[str, Any]]] =
defaultdict(
+ dict
+ )
+
+ filter_by_key_and_field: Dict[str, Dict[str, Dict[str, Any]]] =
defaultdict(dict)
+
+ # Dense representation of filter scopes, falling back to chart level
filter configs
+ # if the respective filter scope is not defined at the dashboard level.
+ for filter_box in filter_boxes:
+ key = str(filter_box.id)
+
+ filter_scope_by_key_and_field[key] = {
+ **(
+ convert_filter_scopes(
+ json_metadata,
+ filter_boxes=[filter_box],
+ ).get(filter_box.id, {})
+ ),
+ **(filter_scopes.get(key, {})),
+ }
+
+ # Contruct the native filters.
+ for filter_box in filter_boxes:
+ key = str(filter_box.id)
+ params = json.loads(filter_box.params or "{}")
+
+ for field, filter_scope in filter_scope_by_key_and_field[key].items():
+ default = default_filters.get(key, {}).get(field)
+
+ fltr: Dict[str, Any] = {
+ "cascadeParentIds": [],
+ "id": f"NATIVE_FILTER-{shortid.generate()}",
+ "scope": {
+ "rootPath": filter_scope["scope"],
+ "excluded": [
+ id_
+ for id_ in filter_scope["immune"]
+ if id_ not in filter_box_ids
+ ],
+ },
+ "type": "NATIVE_FILTER",
+ }
+
+ if field == "__time_col" and params.get("show_sqla_time_column"):
+ fltr.update(
+ {
+ "filterType": "filter_timecolumn",
+ "name": "Time Column",
+ "targets": [{"datasetId": filter_box.datasource_id}],
+ }
+ )
+
+ if not default:
+ default = params.get("granularity_sqla")
+
+ if default:
+ fltr["defaultDataMask"] = {
+ "extraFormData": {"granularity_sqla": default},
+ "filterState": {"value": [default]},
+ }
+ elif field == "__time_grain" and
params.get("show_sqla_time_granularity"):
+ fltr.update(
+ {
+ "filterType": "filter_timegrain",
+ "name": "Time Grain",
+ "targets": [{"datasetId": filter_box.datasource_id}],
+ }
+ )
+
+ if not default:
+ default = params.get("time_grain_sqla")
+
+ if default:
+ fltr["defaultDataMask"] = {
+ "extraFormData": {"time_grain_sqla": default},
+ "filterState": {"value": [default]},
+ }
+ elif field == "__time_range" and params.get("date_filter"):
+ fltr.update(
+ {
+ "filterType": "filter_time",
+ "name": "Time Range",
+ "targets": [{}],
+ }
+ )
+
+ if not default:
+ default = params.get("time_range")
+
+ if default and default != "No filter":
+ fltr["defaultDataMask"] = {
+ "extraFormData": {"time_range": default},
+ "filterState": {"value": default},
+ }
+ else:
+ for config in params.get("filter_configs") or []:
+ if config["column"] == field:
+ fltr.update(
+ {
+ "controlValues": {
+ "defaultToFirstItem": False,
+ "enableEmptyFilter": not config.get(
+ "clearable",
+ True,
+ ),
+ "inverseSelection": False,
+ "multiSelect": config.get(
+ "multiple",
+ False,
+ ),
+ "searchAllOptions": config.get(
+ "searchAllOptions",
+ False,
+ ),
+ },
+ "filterType": "filter_select",
+ "name": config.get("label") or field,
+ "targets": [
+ {
+ "column": {"name": field},
+ "datasetId": filter_box.datasource_id,
+ },
+ ],
+ }
+ )
+
+ if "metric" in config:
+ fltr["sortMetric"] = config["metric"]
+ fltr["controlValues"]["sortAscending"] =
config["asc"]
+
+ if params.get("adhoc_filters"):
+ fltr["adhoc_filters"] = params["adhoc_filters"]
+
+ # Pre-filter available values based on time
range/column.
+ time_range = params.get("time_range")
+
+ if time_range and time_range != "No filter":
+ fltr.update(
+ {
+ "time_range": time_range,
+ "granularity_sqla":
params.get("granularity_sqla"),
+ }
+ )
+
+ if not default:
+ default = config.get("defaultValue")
+
+ if default:
+ if config["multiple"]:
+ default = default.split(";")
+ else:
+ default = [default]
+
+ if default:
+ fltr["defaultDataMask"] = {
+ "extraFormData": {
+ "filters": [
+ {
+ "col": field,
+ "op": "IN",
+ "val": default,
+ }
+ ],
+ },
+ "filterState": {"value": default},
+ }
+
+ break
+
+ if "filterType" in fltr:
+ filter_by_key_and_field[key][field] = fltr
+
+ # Ancestors of filter-box charts.
Review Comment:
@michael-s-molina I generally agree that long functions aren't great and
though I took a pass at extracting this into sub functions the refactor wasn't
great, i.e., assembling the filters really contains the hierarchical wiring.
I also previous refactored fetching the defaults into a function, but there
needed to be lots of special case handling due to where/when the defaults were
used, i.e., a `filter_select` filter may require the time range/granularity but
these defaults are at the chart rather than dashboard level.
The TL;DR is granted the formulation isn't great I sense it's acceptable,
especially in the context of a migration.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]