sadpandajoe commented on code in PR #43108:
URL: https://github.com/apache/superset/pull/43108#discussion_r3996244421


##########
superset-frontend/src/dashboard/containers/DashboardPage.tsx:
##########
@@ -226,6 +289,60 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: 
PageProps) => {
         }
       } else if (nativeFilterKeyValue) {
         dataMask = await getFilterValue(id, nativeFilterKeyValue);
+      } else {
+        const savedFilters = getSavedDashboardFilters(id, userId);
+        // Guard against corrupted or unexpected localStorage data shapes
+        // (e.g. a JSON array or primitive) before assigning to dataMask.
+        if (
+          savedFilters &&
+          typeof savedFilters === 'object' &&
+          !Array.isArray(savedFilters)
+        ) {
+          const isVersioned =
+            'dataMask' in savedFilters && 'filterDefinitions' in savedFilters;
+          const maskToRestore = isVersioned
+            ? savedFilters.dataMask
+            : savedFilters;
+          const savedDefinitions = isVersioned
+            ? savedFilters.filterDefinitions
+            : {};
+
+          const currentFilters = (dashboard?.metadata
+            ?.native_filter_configuration ?? []) as NativeFilterConfigEntry[];
+
+          // Only restore entries whose filter ID still exists in the current
+          // native filter configuration. If we have a snapshotted definition
+          // (from the newer versioned schema), we also verify that the 
filter's
+          // target columns/datasets and type have not changed. This prevents
+          // stale extraFormData from a retargeted filter from being hydrated.
+          const validatedFilters = Object.fromEntries(
+            Object.entries(maskToRestore).filter(([filterId]) => {
+              const currentConfig = currentFilters.find(f => f.id === 
filterId);

Review Comment:
   Dashboards with legacy null entries in `native_filter_configuration` now 
throw here before `hydrateDashboard`, because `.find` dereferences each entry 
while the existing hydration path filters falsey values. Could this filter the 
configuration and cover `[null, validFilter]` before comparing IDs?



##########
superset-frontend/src/dashboard/containers/DashboardPage.tsx:
##########
@@ -81,6 +81,30 @@ import {
 
 type NativeFilterConfigEntry = Partial<Filter> & { id: string };
 
+const DASHBOARD_FILTERS_STORAGE_PREFIX = 'superset_dashboard_filters_';

Review Comment:
   Agreed—embedded users have no `userId`, so this dashboard-only key persists 
selections across guest-token sessions even though `FilterBar` disables 
persistence for them. Can save and restore skip users without an authenticated 
ID?



##########
superset-frontend/src/dashboard/containers/DashboardPage.tsx:
##########
@@ -226,6 +267,11 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: 
PageProps) => {
         }
       } else if (nativeFilterKeyValue) {
         dataMask = await getFilterValue(id, nativeFilterKeyValue);
+      } else {
+        const savedFilters = getSavedDashboardFilters(id, userId);
+        if (savedFilters) {
+          dataMask = savedFilters;
+        }
       }

Review Comment:
   Agreed—the outer-object check still accepts a versioned value whose 
`dataMask` is null, then `Object.entries(maskToRestore)` throws before the 
dashboard hydrates. Could the nested schema be validated before iterating it?



##########
superset-frontend/src/dashboard/containers/DashboardPage.tsx:
##########
@@ -381,8 +434,24 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: 
PageProps) => {
   }, [addDangerToast, datasets, datasetsApiError, dispatch, isNotFoundError]);
 
   const relevantDataMask = useSelector(selectRelevantDatamask);
+  const fullDataMask = useSelector(selectDataMask);
+  const nativeFilters = useSelector(selectNativeFilters);
   const activeFilters = useSelector(selectActiveFilters);
 
+  useEffect(() => {
+    if (!id || hydratedDashboardId !== id) return;
+    // Persist only entries that correspond to configured native filters.
+    // This avoids saving chart customization or other transient dataMask
+    // entries that are not part of the user's filter selections.
+    const nativeFilterIds = Object.keys(nativeFilters);
+    const nativeFilterMask = Object.fromEntries(
+      nativeFilterIds
+        .filter(filterId => filterId in fullDataMask)
+        .map(filterId => [filterId, fullDataMask[filterId]]),
+    );
+    saveDashboardFilters(id, userId, nativeFilterMask);

Review Comment:
   The ref stays true while this page switches dashboards, so if the old 
Dashboard cleanup clears `dataMask` before the new charts arrive, this effect 
can still persist an empty mask. Could the guard track the hydrated dashboard 
ID or skip an empty `nativeFilterMask`?



##########
superset-frontend/src/dashboard/containers/DashboardPage.tsx:
##########
@@ -226,6 +268,17 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: 
PageProps) => {
         }
       } else if (nativeFilterKeyValue) {
         dataMask = await getFilterValue(id, nativeFilterKeyValue);
+      } else {
+        const savedFilters = getSavedDashboardFilters(id, userId);
+        // Guard against corrupted or unexpected localStorage data shapes
+        // (e.g. a JSON array or primitive) before assigning to dataMask.
+        if (
+          savedFilters &&
+          typeof savedFilters === 'object' &&
+          !Array.isArray(savedFilters)
+        ) {
+          dataMask = savedFilters;

Review Comment:
   The unversioned fallback still restores state by ID alone, so a retargeted 
filter can apply the old `extraFormData` and then be resaved with the new 
definition. Since this storage key has not shipped, can unversioned entries be 
rejected instead?



-- 
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]

Reply via email to