You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2026/08/07 20:40:47 UTC
(superset) branch sc-116919-filter-time-native-filter-keys-stripped created (now d0a537cc984)
This is an automated email from the ASF dual-hosted git repository.
eschutho pushed a change to branch sc-116919-filter-time-native-filter-keys-stripped
in repository https://gitbox.apache.org/repos/asf/superset.git
at d0a537cc984 fix(dashboard): preserve native filter keys for dataset-less filters on save
This branch includes the following new commits:
new d0a537cc984 fix(dashboard): preserve native filter keys for dataset-less filters on save
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: fix(dashboard): preserve native filter keys for dataset-less filters on save
Posted by el...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
eschutho pushed a commit to branch sc-116919-filter-time-native-filter-keys-stripped
in repository https://gitbox.apache.org/repos/asf/superset.git
commit d0a537cc984e8a141dcceef00080207dd394948b
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Fri Aug 7 20:40:32 2026 +0000
fix(dashboard): preserve native filter keys for dataset-less filters on save
The filter-config modal's save transform discriminated between an
in-progress form item and an already-saved filter by checking for a
`dataset` key. Filter types without a dataset control (e.g. `filter_time`)
have no such key, so their form state was misclassified as an
already-saved filter and persisted verbatim. This dropped serialized keys
(`targets`, `defaultDataMask`, `cascadeParentIds`) and leaked form-only
keys (`defaultValueQueriesData`, raw `requiredFirst`) into the stored
`native_filter_configuration`.
Discriminate on the `targets` array instead, which every saved filter
carries and no form item does, so dataset-less filters are serialized
through the same path as dataset-backed ones.
Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
.../transformers/filterTransformer.test.ts | 118 +++++++++++++++++++++
.../transformers/filterTransformer.ts | 11 +-
2 files changed, 128 insertions(+), 1 deletion(-)
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.test.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.test.ts
new file mode 100644
index 00000000000..6e8edfcaa57
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.test.ts
@@ -0,0 +1,118 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Filter, NativeFilterType } from '@superset-ui/core';
+import { NativeFiltersFormItem } from '../types';
+import { transformFilterForSave } from './filterTransformer';
+
+const baseFormItem = {
+ type: NativeFilterType.NativeFilter,
+ scope: { rootPath: ['ROOT_ID'], excluded: [] },
+ controlValues: {},
+ requiredFirst: {},
+ defaultValue: null,
+ defaultDataMask: { filterState: {}, extraFormData: {} },
+ sortMetric: null,
+ description: '',
+ // form-only fields that must never leak into the saved filter
+ defaultValueQueriesData: null,
+} as unknown as NativeFiltersFormItem;
+
+test('serializes a dataset-less filter (filter_time) into a full Filter', () => {
+ // A ``filter_time`` filter has no dataset/column controls, so its form item
+ // carries neither a ``dataset`` nor a ``targets`` key. It must still be
+ // transformed like any other native filter rather than persisted verbatim.
+ const formItem: NativeFiltersFormItem = {
+ ...baseFormItem,
+ name: 'Time Range',
+ filterType: 'filter_time',
+ dependencies: ['NATIVE_FILTER-parent'],
+ };
+
+ const result = transformFilterForSave(
+ 'NATIVE_FILTER-abc',
+ formItem,
+ ) as Filter;
+
+ // Keys the bug used to strip are present and well-formed.
+ expect(result.targets).toEqual([{}]);
+ expect(result.defaultDataMask).toBeDefined();
+ expect(result.cascadeParentIds).toEqual(['NATIVE_FILTER-parent']);
+
+ // Form-only keys must not leak into the persisted config.
+ expect(result).not.toHaveProperty('defaultValueQueriesData');
+ expect(result).not.toHaveProperty('dependencies');
+ // Empty requiredFirst collapses to undefined instead of the raw form object.
+ expect(result.requiredFirst).toBeUndefined();
+
+ expect(result.name).toBe('Time Range');
+ expect(result.filterType).toBe('filter_time');
+});
+
+test('serializes a dataset-backed filter (filter_select) into a full Filter', () => {
+ const formItem: NativeFiltersFormItem = {
+ ...baseFormItem,
+ name: 'Region',
+ filterType: 'filter_select',
+ dataset: { value: 42, label: 'sales' },
+ column: 'region',
+ dependencies: [],
+ };
+
+ const result = transformFilterForSave(
+ 'NATIVE_FILTER-def',
+ formItem,
+ ) as Filter;
+
+ expect(result.targets).toEqual([
+ { datasetId: 42, column: { name: 'region' } },
+ ]);
+ expect(result.defaultDataMask).toBeDefined();
+ expect(result.cascadeParentIds).toEqual([]);
+ expect(result).not.toHaveProperty('defaultValueQueriesData');
+});
+
+test('passes an already-saved Filter through untouched (aside from trimming)', () => {
+ // Values coming from the stored filter config map (e.g. cascade-parent
+ // cleanup) already carry a ``targets`` array and must be preserved as-is.
+ const savedFilter: Filter = {
+ id: 'NATIVE_FILTER-ghi',
+ name: 'Time Range',
+ filterType: 'filter_time',
+ type: NativeFilterType.NativeFilter,
+ targets: [{}],
+ defaultDataMask: { filterState: {}, extraFormData: {} },
+ cascadeParentIds: ['NATIVE_FILTER-parent'],
+ controlValues: {},
+ scope: { rootPath: ['ROOT_ID'], excluded: [] },
+ description: ' needs trim ',
+ chartsInScope: [1, 2],
+ tabsInScope: ['TAB-1'],
+ };
+
+ const result = transformFilterForSave(
+ 'NATIVE_FILTER-ghi',
+ savedFilter,
+ ) as Filter;
+
+ expect(result.targets).toEqual([{}]);
+ expect(result.cascadeParentIds).toEqual(['NATIVE_FILTER-parent']);
+ expect(result.chartsInScope).toEqual([1, 2]);
+ expect(result.tabsInScope).toEqual(['TAB-1']);
+ expect(result.description).toBe('needs trim');
+});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.ts
index 3ceabffc152..4c572e545c8 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.ts
@@ -67,7 +67,16 @@ function isDividerType(
function isFormInput(
formInputs: NativeFilterFormOrSaved,
): formInputs is NativeFiltersFormItem {
- return 'dataset' in formInputs;
+ // Values reaching this transform come either from the modal form
+ // (`NativeFiltersFormItem`) or from the already-saved filter config map
+ // (`Filter`). A saved filter always carries a serialized `targets` array,
+ // while a form item never does. Keying the discriminator off `dataset`
+ // misclassified filter types with no dataset control (e.g. `filter_time`)
+ // as saved filters, so their raw form state was persisted verbatim —
+ // dropping serialized keys such as `targets`, `defaultDataMask`, and
+ // `cascadeParentIds`, and leaking form-only keys such as
+ // `defaultValueQueriesData` and `requiredFirst`.
+ return !('targets' in formInputs);
}
function transformDivider(