change_dashboard_filter log event carries no filter information
- Dominant language
- Python
- Stars
- 74.8k
- Forks
- 18.3k
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 685
Description
### Bug description
`change_dashboard_filter` is dispatched for native dashboard filters with an empty payload, so the log records *that* filters changed but never *what* they were changed to.
https://github.com/apache/superset/blob/master/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx#L432
```js
const handleApply = useCallback(() => {
dispatch(logEvent(LOG_ACTIONS_CHANGE_DASHBOARD_FILTER, {}));
```
`dataMaskSelected` — the filter state being applied — is in scope on the very next lines and is discarded. The cross-filter path does carry context by comparison, though only column names:
https://github.com/apache/superset/blob/master/superset-frontend/src/dashboard/components/gridComponents/Chart/Chart.tsx#L297
```js
boundActionCreators.logEvent(LOG_ACTIONS_CHANGE_DASHBOARD_FILTER, {
id: chart?.id,
columns: vals !== null ? [col] : [],
});
```
A resulting row from a real session looks like this — every field is either envelope or session context, nothing about the filter:
```json
{"impression_id": "6K_whEgrMNwJ2mlbS1LfA", "version": "v2", "ts": 1784877876239,
"event_name": "change_dashboard_filter", "event_type": "user",
"event_id": "qtmRj8G_fYdzeH54kRncH", "visibility": "visible"}
```
Relatedly, `select_dashboard_tab` carries `{target_id, index, target_name}`, so there is also no way to know which filters were active when a user viewed a given tab.
### Why it cannot be reconstructed downstream
Two workarounds that look plausible but don't hold:
**Resolve the filter state from a permalink.** Not available for embedded dashboards — the data mask is only published when a real user id is present:
https://github.com/apache/superset/blob/master/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx#L409
```js
// embedded users can't persist filter combinations
if (user?.userId) {
publishDataMask(history, dashboardId, updateKey, dataMaskApplied, tabId);
}
```
so embedded referrers carry no `native_filters_key` and there is nothing to look up.
**Read the filters back out of `ChartDataRestApi.data`.** The native filter contribution is merged into the same `form_data.filters` array as the chart's own saved adhoc filters, with nothing marking which is which. A real payload:
```json
"filters": [
{"col": "status", "op": "==", "val": "done"},
{"col": "user_id", "op": "IS NOT NULL"},
{"col": "task_created_date", "op": "TEMPORAL_RANGE", "val": "No filter"}
]
```
Only the third entry comes from the dashboard filter bar; the first two are saved on the chart. Separating them requires diffing against each chart's stored `params`, and charts served from cache emit no request at all, so the reconstruction is both approximate and incomplete.
### How to reproduce
1. Open a dashboard with a native filter.
2. Select a filter value and click **Apply**.
3. Inspect the `logs` table (or the `/superset/log/` request payload) for the `change_dashboard_filter` event.
4. The event is present, but contains no reference to the filter or the selected value.
### Expected results
The event carries the applied filter state — at minimum the filter name and the selected value(s) — so downstream consumers can tell what was filtered.
### Actual results
The payload is `{}` plus the standard envelope.
### Possible approaches
**A. Enrich at the dispatch site (smallest change).** Pass a compact projection of `dataMaskSelected` in `handleApply`, e.g. `[{ id, name, value }]` per active filter. Authoritative, since it is exactly the state being applied, and local to one call site.
**B. Enrich in `loggerMiddleware` for an allowlist of events.** The middleware already reads the store but does not pull the filter slices:
https://github.com/apache/superset/blob/master/superset-frontend/src/middleware/loggerMiddleware.ts#L158
```js
const { dashboardInfo, explore, impressionId, dashboardLayout, sqlLab } =
store.getState();
```
`dataMask` and `nativeFilters` are in the same store. Filter names are at `nativeFilters.filters[id].name` and selected values at `dataMask[id].filterState.value`, so a compact `applied_filters` array can be attached generically. This also covers the second half of the problem: it would give `select_dashboard_tab` (and other user events) the filter context in effect at the time.
This should be gated to a small allowlist rather than applied to every event — `load_chart` fires per chart per render and would balloon.
A and B are complementary: A gives an explicit "filters were changed to X" event, B gives "this view happened under filters X".
**C. Reconstruct server-side.** Ruled out for the reasons above; noting it so it isn't re-proposed.
### Considerations
Filter values are user data and can be sensitive depending on the deployment (names, identifiers, free text). It may be worth making the value capture configurable, or defaulting to filter names plus a set/unset flag and recording values only when explicitly enabled.
### Environment
- superset: `master` (line references above are against master; also reproduces on 6.0.1)
- browser: any
### Additional context
Observed while building an audit trail over the log table for embedded dashboards. Related: #42562 (Drill to Detail emitted no log event).
Happy to open a PR for A, B, or both — would appreciate a maintainer's preference on the shape of the payload and on whether value capture should be configurable before I do.
Contributor guide
Research direction
Start with superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx, then compare the event payload in Chart.tsx and the state access in superset-frontend/src/middleware/loggerMiddleware.ts. Confirm how dataMaskSelected and nativeFilters are represented, then define the smallest supported event context and verify that change_dashboard_filter and relevant dashboard-view events expose the applied filter information without affecting unrelated logging.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- analytics, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100