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


##########
superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx:
##########
@@ -311,6 +315,65 @@ const FilterBar: FC<FiltersBarProps> = ({
 
         const hasRequiredValue = isRequired && isEmptyValue;
 
+        // Cascade clearing: when a parent filter's value changes, every
+        // transitive descendant (child) dependent filter must have its
+        // selection reset. Otherwise the child keeps a stale value that no
+        // longer belongs to the parent's option set (e.g. Country=UK with a
+        // City value only valid under USA), producing impossible filter
+        // combinations that blank charts.
+        const prevMask = draft[filter.id];
+        const prevValue = prevMask?.filterState?.value;
+        const prevExtra = prevMask?.extraFormData;
+        const nextExtra = baseDataMask.extraFormData;
+        // Filters configured with defaultToFirstItem auto-select their first
+        // option on load. That seed is initialization, not a dependency
+        // change, and must not clear descendants. Persisted values reach the
+        // applied state through the sync effect rather than this callback, so
+        // any other first emission is a genuine user selection.
+        const isAutoSeedInit =
+          prevValue === undefined && 
!!filter.controlValues?.defaultToFirstItem;
+        // The effective dependency state is the parent's extraFormData (the
+        // clauses and time_range merged into descendants), not the raw
+        // selected value: inverse-selection toggles change the clause while
+        // the selected value stays identical.
+        const parentValueChanged =
+          !!prevMask && !isAutoSeedInit && !isEqual(prevExtra, nextExtra);
+        if (parentValueChanged) {
+          const childIds = resolveTransitiveChildIds(filter.id, filters);
+          childIds.forEach(childId => {
+            // Only cascade-clear descendants that are in scope for the active
+            // tab, mirroring the handleClearAll scope guard. An out-of-scope
+            // child must keep its staged value (Apply would otherwise stage a
+            // null it never dispatches, leaving stale applied state) and its
+            // required-validateStatus (which would wrongly block Apply).
+            if (!inScopeFilterIds.has(childId)) return;

Review Comment:
   Skipping an inactive-tab descendant leaves its old selection applied after 
this parent change. When the user later opens that tab, no parent transition 
reruns this block, so a globally scoped `Country=UK` can still be combined with 
the preserved `City=New York` and blank the charts this change is meant to 
protect; could skipped descendants be invalidated when they enter scope (or 
cleared through an Apply-safe path)?



##########
superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx:
##########
@@ -513,6 +515,27 @@ export default function PluginFilterSelect(props: 
PluginFilterSelectProps) {
     }
   }, [clearAllTrigger, onClearAllComplete, updateDataMask]);
 
+  useEffect(() => {
+    // When a parent filter's value changes, a cascading clear signals this
+    // dependent filter to reset its visual selection. Same behavior as a
+    // global clear-all but scoped to one descendant.
+    if (cascadeClearTrigger) {
+      dispatchDataMask({
+        type: 'filterState',
+        extraFormData: {},
+        filterState: {
+          value: undefined,
+          label: undefined,
+        },
+      });
+
+      updateDataMask(null);
+      setSearch('');

Review Comment:
   `setSearch('')` does not clear the Search-all query stored in 
`dataMask.ownState`, and a pending `onSearch` debounce can write the old term 
back after this effect. A child cleared after `Country` changes can therefore 
refetch only the old `New York` matches while showing an empty search box; 
should cascade clear cancel `onSearch` and reset `ownState.search` too?



##########
superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx:
##########
@@ -311,6 +315,65 @@ const FilterBar: FC<FiltersBarProps> = ({
 
         const hasRequiredValue = isRequired && isEmptyValue;
 
+        // Cascade clearing: when a parent filter's value changes, every
+        // transitive descendant (child) dependent filter must have its
+        // selection reset. Otherwise the child keeps a stale value that no
+        // longer belongs to the parent's option set (e.g. Country=UK with a
+        // City value only valid under USA), producing impossible filter
+        // combinations that blank charts.
+        const prevMask = draft[filter.id];
+        const prevValue = prevMask?.filterState?.value;
+        const prevExtra = prevMask?.extraFormData;
+        const nextExtra = baseDataMask.extraFormData;
+        // Filters configured with defaultToFirstItem auto-select their first
+        // option on load. That seed is initialization, not a dependency
+        // change, and must not clear descendants. Persisted values reach the
+        // applied state through the sync effect rather than this callback, so
+        // any other first emission is a genuine user selection.
+        const isAutoSeedInit =
+          prevValue === undefined && 
!!filter.controlValues?.defaultToFirstItem;
+        // The effective dependency state is the parent's extraFormData (the
+        // clauses and time_range merged into descendants), not the raw
+        // selected value: inverse-selection toggles change the clause while
+        // the selected value stays identical.
+        const parentValueChanged =
+          !!prevMask && !isAutoSeedInit && !isEqual(prevExtra, nextExtra);

Review Comment:
   This also treats the Select plugin's mount-time synchronization as a parent 
change. A restored parent first emits its reducer's initial `extraFormData: {}` 
before re-emitting its saved clauses, so opening a dashboard with `Country=USA` 
and `City=New York` clears the child without any user action (and the current 
integration assertion still passes because it only checks after the click). 
Could this transition ignore plugin initialization emissions and only 
invalidate descendants after a genuine user change?



##########
superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx:
##########
@@ -311,6 +315,65 @@ const FilterBar: FC<FiltersBarProps> = ({
 
         const hasRequiredValue = isRequired && isEmptyValue;
 
+        // Cascade clearing: when a parent filter's value changes, every
+        // transitive descendant (child) dependent filter must have its
+        // selection reset. Otherwise the child keeps a stale value that no
+        // longer belongs to the parent's option set (e.g. Country=UK with a
+        // City value only valid under USA), producing impossible filter
+        // combinations that blank charts.
+        const prevMask = draft[filter.id];
+        const prevValue = prevMask?.filterState?.value;
+        const prevExtra = prevMask?.extraFormData;
+        const nextExtra = baseDataMask.extraFormData;
+        // Filters configured with defaultToFirstItem auto-select their first
+        // option on load. That seed is initialization, not a dependency
+        // change, and must not clear descendants. Persisted values reach the
+        // applied state through the sync effect rather than this callback, so
+        // any other first emission is a genuine user selection.
+        const isAutoSeedInit =
+          prevValue === undefined && 
!!filter.controlValues?.defaultToFirstItem;
+        // The effective dependency state is the parent's extraFormData (the
+        // clauses and time_range merged into descendants), not the raw
+        // selected value: inverse-selection toggles change the clause while
+        // the selected value stays identical.
+        const parentValueChanged =
+          !!prevMask && !isAutoSeedInit && !isEqual(prevExtra, nextExtra);
+        if (parentValueChanged) {
+          const childIds = resolveTransitiveChildIds(filter.id, filters);
+          childIds.forEach(childId => {
+            // Only cascade-clear descendants that are in scope for the active
+            // tab, mirroring the handleClearAll scope guard. An out-of-scope
+            // child must keep its staged value (Apply would otherwise stage a
+            // null it never dispatches, leaving stale applied state) and its
+            // required-validateStatus (which would wrongly block Apply).
+            if (!inScopeFilterIds.has(childId)) return;
+            const childMask = draft[childId];
+            if (!childMask) return;
+            childMask.extraFormData = {};
+            const { filterState } = childMask;
+            if (filterState) {
+              const childIsRequired =
+                !!filters[childId]?.controlValues?.enableEmptyFilter;
+              // Mirror handleClearAll: range filters use [null, null] as the
+              // canonical cleared value.  Bare null would be ignored by
+              // RangeFilterPlugin's sync effect, leaving stale UI.
+              filterState.value =
+                filters[childId]?.filterType === 'filter_range'
+                  ? [null, null]
+                  : null;
+              filterState.validateStatus = childIsRequired
+                ? 'error'
+                : undefined;
+            }
+            // Signal the child's filter plugin to clear its visual selection
+            // and avoid re-applying defaults.
+            setCascadeClearTriggers(prev => ({

Review Comment:
   Range and Time descendants never call `onCascadeClearComplete`, so this 
trigger remains true after their value clears. If that filter is edited to a 
Select without remounting the bar, the new Select consumes the stale trigger 
and wipes its fresh default (or leaves a required filter invalid); should every 
supported dependent type acknowledge the trigger, or should triggers only be 
created for Select children?



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