This is an automated email from the ASF dual-hosted git repository. hugh pushed a commit to branch hm/ar-filters in repository https://gitbox.apache.org/repos/asf/superset.git
commit 16751c09c3328eab4970d7c6e8f5e8d1870dc6d8 Author: Hugh Miles <[email protected]> AuthorDate: Fri Feb 7 00:15:42 2025 +0000 save --- .../src/features/alerts/AlertReportModal.tsx | 25 ++++++----- superset-frontend/src/features/alerts/types.ts | 7 +++ superset/commands/dashboard/permalink/create.py | 19 +++++++- superset/commands/explore/get.py | 1 + superset/commands/report/execute.py | 31 +++++++++++-- superset/reports/models.py | 52 +++++++++++----------- superset/views/core.py | 12 ++++- 7 files changed, 101 insertions(+), 46 deletions(-) diff --git a/superset-frontend/src/features/alerts/AlertReportModal.tsx b/superset-frontend/src/features/alerts/AlertReportModal.tsx index 5152160b4f..d971847637 100644 --- a/superset-frontend/src/features/alerts/AlertReportModal.tsx +++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx @@ -463,6 +463,8 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({ const [nativeFilter, setSelectedNativeFilter] = useState<object>({}); const [nativeFilters, setGlobalNativeFilters] = useState<object>({}); + console.log('okkkkk', nativeFilter) + // Validation const [validationStatus, setValidationStatus] = useState<ValidationObject>({ [Sections.General]: { @@ -676,7 +678,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({ // todo(hughhh): refactor to handle multiple native filters - currentAlert.extra.nativeFilter = nativeFilter + currentAlert.extra.dashboard.nativeFilters = [nativeFilter] const data: any = { ...currentAlert, @@ -1186,14 +1188,14 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({ setForceScreenshot(event.target.checked); }; - const onChangeDashboardFilter = (value: any) => { - console.log('dashboardFilter', value); + const onChangeDashboardFilter = (nativeFilterId: string) => { + console.log('dashboardFilter', nativeFilterId); // set dashboardFilter const anchor = currentAlert?.extra?.dashboard?.anchor const inScopeFilters = nativeFilters[anchor]; - const filter = inScopeFilters.filter((f: any) => f.id === value)[0] + const filter = inScopeFilters.filter((f: any) => f.id === nativeFilterId)[0] console.log(filter) // use filter to grab values from API - console.log('about to get filter values', value); + console.log('about to get filter values', nativeFilterId); const datasetId = filter.targets[0].datasetId; const columnName = filter.targets[0].column.name; @@ -1227,19 +1229,18 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({ force: false, ownState: {} } - + setSelectedNativeFilter({...nativeFilter, columnName, nativeFilterId}) getChartDataRequest(filterValues).then(response => { setNativeFilterValues(response.json.result[0].data.map((item: any) => ({value: item[columnName], label: item[columnName]}))) }); - } - const onChangeDashboardFilterValue = (value: any) => { - console.log('dashboardValue', value); - // set dashboardValue - setSelectedNativeFilter({...nativeFilter, nativeFilterValue: value}); + const onChangeDashboardFilterValue = (filterValue: any) => { + console.log('dashboardValue', filterValue); - + // todo(hughhh): refactor to handle multiple native filters + // once you have multiselect + setSelectedNativeFilter({...nativeFilter, filterValues: [filterValue]}); } // Make sure notification settings has the required info diff --git a/superset-frontend/src/features/alerts/types.ts b/superset-frontend/src/features/alerts/types.ts index 06449697c3..3b00067a48 100644 --- a/superset-frontend/src/features/alerts/types.ts +++ b/superset-frontend/src/features/alerts/types.ts @@ -94,8 +94,15 @@ export type DashboardState = { anchor?: string; }; +export type ExtraNativeFilter = { + columnName?: string; + filterValues?: Array<any>; + // filterOp?: string; // assuming all operators are 'IN' for now +}; + export type Extra = { dashboard?: DashboardState; + nativeFilters?: Array<ExtraNativeFilter>; }; export type Operator = '<' | '>' | '<=' | '>=' | '==' | '!=' | 'not null'; diff --git a/superset/commands/dashboard/permalink/create.py b/superset/commands/dashboard/permalink/create.py index 20bc5118f5..7bd91f8640 100644 --- a/superset/commands/dashboard/permalink/create.py +++ b/superset/commands/dashboard/permalink/create.py @@ -66,11 +66,21 @@ class CreateDashboardPermalinkCommand(BaseDashboardPermalinkCommand): def run(self) -> str: self.validate() dashboard = DashboardDAO.get_by_id_or_slug(self.dashboard_id) + print("creating permalink...") + value = { "dashboardId": str(dashboard.uuid), - "state": self.state, + "state": {**self.state, "urlParams": [['native_filter', '(NATIVE_FILTER-8jS1fx4hl:(extraFormData:(filters:!((col:country_name,op:IN,val:!(Brazil)))),filterState:(label:country_name,validateStatus:!f,value:!(Brazil)),id:NATIVE_FILTER-8jS1fx4hl,ownState:()))']]}, } user_id = get_user_id() + + print('.' * 10) + print(self.resource) + print(user_id) + print(value) + print(self.codec) + print('.' * 10) + entry = KeyValueDAO.upsert_entry( resource=self.resource, key=get_deterministic_uuid(self.salt, (user_id, value)), @@ -79,7 +89,12 @@ class CreateDashboardPermalinkCommand(BaseDashboardPermalinkCommand): ) db.session.flush() assert entry.id # for type checks - return encode_permalink_key(key=entry.id, salt=self.salt) + + hash = encode_permalink_key(key=entry.id, salt=self.salt) + print("permalink created") + print(hash) + + return hash def validate(self) -> None: pass diff --git a/superset/commands/explore/get.py b/superset/commands/explore/get.py index dc600bbf97..d32b910137 100644 --- a/superset/commands/explore/get.py +++ b/superset/commands/explore/get.py @@ -69,6 +69,7 @@ class GetExploreCommand(BaseCommand, ABC): state = permalink_value["state"] initial_form_data = state["formData"] url_params = state.get("urlParams") + print('perm_url_params', url_params) if url_params: initial_form_data["url_params"] = dict(url_params) elif self._form_data_key: diff --git a/superset/commands/report/execute.py b/superset/commands/report/execute.py index 4345a7160b..493d0a64f1 100644 --- a/superset/commands/report/execute.py +++ b/superset/commands/report/execute.py @@ -228,16 +228,27 @@ class BaseReportState: Retrieve the URL for the dashboard tabs, or return the dashboard URL if no tabs are available. """ # noqa: E501 force = "true" if self._report_schedule.force_screenshot else "false" + + if ( dashboard_state := self._report_schedule.extra.get("dashboard") ) and feature_flag_manager.is_feature_enabled("ALERT_REPORT_TABS"): if anchor := dashboard_state.get("anchor"): try: anchor_list: list[str] = json.loads(anchor) - return self._get_tabs_urls(anchor_list, user_friendly=user_friendly) + urls = self._get_tabs_urls(anchor_list, user_friendly=user_friendly) + return urls except json.JSONDecodeError: logger.debug("Anchor value is not a list, Fall back to single tab") - return [self._get_tab_url(dashboard_state)] + + print('returning single tab url....') + native_filter_params = self._report_schedule.get_native_filters_params() + return [self._get_tab_url({ + 'anchor': anchor, + 'urlParams': {'native_filters': native_filter_params}, + 'dataMask': None, + 'activeTabs': None}, + user_friendly=user_friendly)] dashboard = self._report_schedule.dashboard dashboard_id_or_slug = ( @@ -260,10 +271,13 @@ class BaseReportState: """ Get one tab url """ + print('in single tab....') + print(dashboard_state) permalink_key = CreateDashboardPermalinkCommand( dashboard_id=str(self._report_schedule.dashboard.uuid), state=dashboard_state, ).run() + return get_url_path( "Superset.dashboard_permalink", key=permalink_key, @@ -271,18 +285,21 @@ class BaseReportState: ) def _get_tabs_urls( - self, tab_anchors: list[str], user_friendly: bool = False + self, + tab_anchors: list[str], + user_friendly: bool = False ) -> list[str]: """ Get multple tabs urls """ + print('hello5') return [ self._get_tab_url( { "anchor": tab_anchor, "dataMask": None, "activeTabs": None, - "urlParams": None, # could potentially back the native_filters here? + "urlParams": url_params, }, user_friendly=user_friendly, ) @@ -320,6 +337,7 @@ class BaseReportState: ] else: urls = self.get_dashboard_urls() + print('urls', urls) window_width, window_height = app.config["WEBDRIVER_WINDOW"]["dashboard"] width = min(max_width, self._report_schedule.custom_width or window_width) @@ -481,6 +499,11 @@ class BaseReportState: error_text = None header_data = self._get_log_data() url = self._get_url(user_friendly=True) + + print("*"*100) + print(url) + print("*"*100) + if ( feature_flag_manager.is_feature_enabled("ALERTS_ATTACH_REPORTS") or self._report_schedule.type == ReportScheduleType.REPORT diff --git a/superset/reports/models.py b/superset/reports/models.py index 932c4d6077..063e8d6b6c 100644 --- a/superset/reports/models.py +++ b/superset/reports/models.py @@ -186,36 +186,36 @@ class ReportSchedule(AuditMixinNullable, ExtraJSONMixin, Model): def crontab_humanized(self) -> str: return get_description(self.crontab) - def get_native_filters_params(self) -> Optional[str]: + def get_native_filters_params(self) -> Optional[dict[str, any]]: params = None - if self.extra and self.extra.get("native_filters"): - filter = self.extra.get("native_filters", {}) - params = self._generate_native_filter(filter.get("id"), filter.get("column"), filter.get("value")) - return prison.dumps(params) + dashboard = self.extra.get('dashboard') + if dashboard and dashboard.get("nativeFilters"): + for filter in dashboard.get("nativeFilters", []): + # todo(hugh): handle multiple nativeFilters + multi values + params = self._generate_native_filter(filter.get("nativeFilterId"), filter.get("columnName"), filter.get("filterValues")[0]) + return params - def _generate_native_filter(native_filter_id: str, column: str, value: str) -> dict: + def _generate_native_filter(self, native_filter_id: str, column_name: str, value: str) -> dict: return { - "native_filters": { - native_filter_id: { - "id": native_filter_id, - "extraFormData": { - "filters": [ - { - "col": column, - "op": "IN", - "val": [value] - } - ] - }, - "filterState": { - "label": column, # pretty name but still works without this value - "validateStatus": False, - "value": [value] - }, - "ownState": {} - } + native_filter_id: { + "id": native_filter_id, + "extraFormData": { + "filters": [ + { + "col": column_name, + "op": "IN", + "val": [value] + } + ] + }, + "filterState": { + "label": column_name, # pretty name but still works without this value + "validateStatus": False, + "value": [value] + }, + "ownState": {} } - } + } class ReportRecipients(Model, AuditMixinNullable): diff --git a/superset/views/core.py b/superset/views/core.py index 8a56befe0b..f31bba4a2b 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -19,6 +19,7 @@ from __future__ import annotations import contextlib import logging +import prison from datetime import datetime from typing import Any, Callable, cast from urllib import parse @@ -846,15 +847,22 @@ class Superset(BaseSupersetView): return redirect("/dashboard/list/") if not value: return json_error_response(_("permalink state not found"), status=404) + + print('value___', value) dashboard_id, state = value["dashboardId"], value.get("state", {}) url = f"/superset/dashboard/{dashboard_id}?permalink_key={key}" if url_params := state.get("urlParams"): - params = parse.urlencode(url_params) - url = f"{url}&{params}" + for param_key, param_val in url_params: + if param_key == "native_filter": + url = f"{url}&native_filters={param_val}" + else: + params = parse.urlencode([param_key, param_val]) + url = f"{url}&{params}" if original_params := request.query_string.decode(): url = f"{url}&{original_params}" if hash_ := state.get("anchor", state.get("hash")): url = f"{url}#{hash_}" + return redirect(url) @api
