You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2026/04/23 17:37:45 UTC
(superset) 22/33: Fix filters
This is an automated email from the ASF dual-hosted git repository.
betodealmeida pushed a commit to branch backup/semantic-layer-dynamic-columns-before-rebase-20260423-133321
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 392be533153266916ac2dd878822722dcac3ce0c
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Fri Apr 17 17:02:41 2026 -0400
Fix filters
---
.../src/superset_core/semantic_layers/view.py | 7 +
.../FilterBar/FilterControls/GroupByFilterCard.tsx | 5 +-
.../features/semanticLayers/jsonFormsHelpers.tsx | 27 +--
superset-frontend/src/pages/DatasetList/index.tsx | 212 +++++++++++----------
4 files changed, 143 insertions(+), 108 deletions(-)
diff --git a/superset-core/src/superset_core/semantic_layers/view.py b/superset-core/src/superset_core/semantic_layers/view.py
index 7b4ef7faa2e..02bcdb820c8 100644
--- a/superset-core/src/superset_core/semantic_layers/view.py
+++ b/superset-core/src/superset_core/semantic_layers/view.py
@@ -47,6 +47,13 @@ class SemanticView(ABC):
features: frozenset[SemanticViewFeature]
+ @property
+ @abstractmethod
+ def name(self) -> str:
+ """
+ Returns the display name of the semantic view.
+ """
+
@abstractmethod
def uid(self) -> str:
"""
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/GroupByFilterCard.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/GroupByFilterCard.tsx
index c6ce6629f82..ff6cf057cbc 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/GroupByFilterCard.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/GroupByFilterCard.tsx
@@ -51,7 +51,10 @@ import { addDangerToast } from 'src/components/MessageToasts/actions';
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
import { dispatchChartCustomizationHoverAction } from './utils';
import { mergeExtraFormData } from '../../utils';
-import { datasetLabel as getDatasetLabel, datasetLabelLower } from 'src/utils/semanticLayerLabels';
+import {
+ datasetLabel as getDatasetLabel,
+ datasetLabelLower,
+} from 'src/utils/semanticLayerLabels';
interface ColumnApiResponse {
column_name?: string;
diff --git a/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx b/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx
index 6fbdb8ea283..a4c65a1859c 100644
--- a/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx
+++ b/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx
@@ -105,15 +105,21 @@ function ReadOnlyControl({
}
}, [defaultValue, data, handleChange, path]);
- return TextControl({ ...rest, data, handleChange, path, schema, enabled: false });
+ return TextControl({
+ ...rest,
+ data,
+ handleChange,
+ path,
+ schema,
+ enabled: false,
+ });
}
const ReadOnlyRenderer = withJsonFormsControlProps(ReadOnlyControl);
const readOnlyEntry = {
tester: rankWith(
11,
schemaMatches(
- s =>
- s !== undefined && (s as Record<string, unknown>).readOnly === true,
+ s => s !== undefined && (s as Record<string, unknown>).readOnly === true,
),
),
renderer: ReadOnlyRenderer,
@@ -147,7 +153,10 @@ function DynamicFieldControl(props: ControlProps) {
const refreshing =
refreshingSchema &&
Array.isArray(deps) &&
- areDependenciesSatisfied(deps as string[], (cfgData as Record<string, unknown>) ?? {});
+ areDependenciesSatisfied(
+ deps as string[],
+ (cfgData as Record<string, unknown>) ?? {},
+ );
if (!refreshing) {
return TextControl(props);
@@ -214,15 +223,13 @@ export function sanitizeSchema(schema: JsonSchema): JsonSchema {
* Builds a JSON Forms UI schema from a JSON Schema, using the first
* `examples` entry as placeholder text for each string property.
*/
-export function buildUiSchema(
- schema: JsonSchema,
-): UISchemaElement | undefined {
+export function buildUiSchema(schema: JsonSchema): UISchemaElement | undefined {
if (!schema.properties) return undefined;
// Use explicit property order from backend if available,
// otherwise fall back to the JSON object key order
const propertyOrder: string[] =
- (schema as Record<string, unknown>)['x-propertyOrder'] as string[] ??
+ ((schema as Record<string, unknown>)['x-propertyOrder'] as string[]) ??
Object.keys(schema.properties);
const elements = propertyOrder
@@ -271,9 +278,7 @@ export function getDynamicDependencies(
'x-dependsOn' in prop &&
Array.isArray((prop as Record<string, unknown>)['x-dependsOn'])
) {
- deps[key] = (prop as Record<string, unknown>)[
- 'x-dependsOn'
- ] as string[];
+ deps[key] = (prop as Record<string, unknown>)['x-dependsOn'] as string[];
}
}
return deps;
diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx
index ec2709983a3..92305e5ff2d 100644
--- a/superset-frontend/src/pages/DatasetList/index.tsx
+++ b/superset-frontend/src/pages/DatasetList/index.tsx
@@ -24,7 +24,14 @@ import {
FeatureFlag,
} from '@superset-ui/core';
import { styled, useTheme, css } from '@apache-superset/core/theme';
-import { FunctionComponent, useState, useMemo, useCallback, useRef, Key } from 'react';
+import {
+ FunctionComponent,
+ useState,
+ useMemo,
+ useCallback,
+ useRef,
+ Key,
+} from 'react';
import type { CellProps } from 'react-table';
import { Link, useHistory } from 'react-router-dom';
import rison from 'rison';
@@ -218,10 +225,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
* - Selecting Source=Semantic Layer → clear DB connection + Physical/Virtual type
*/
const cascadeClear = useCallback(
- (
- changed: 'source' | 'type' | 'connection',
- newValue: unknown,
- ) => {
+ (changed: 'source' | 'type' | 'connection', newValue: unknown) => {
if (!isFeatureEnabled(FeatureFlag.SemanticLayers)) return;
const isSlConnection = (v: unknown) =>
@@ -229,23 +233,34 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
const isDbConnection = (v: unknown) =>
v !== undefined && v !== null && v !== '' && !isSlConnection(v);
const isSemanticViewType = (v: unknown) => v === 'semantic_view';
- const isPhysicalVirtualType = (v: unknown) =>
- v === true || v === false;
+ const isPhysicalVirtualType = (v: unknown) => v === true || v === false;
if (changed === 'connection') {
- if (isSlConnection(newValue) && isPhysicalVirtualType(currentTypeFilter.current)) {
+ if (
+ isSlConnection(newValue) &&
+ isPhysicalVirtualType(currentTypeFilter.current)
+ ) {
filtersRef.current?.clearFilterById('sql');
}
- if (isDbConnection(newValue) && isSemanticViewType(currentTypeFilter.current)) {
+ if (
+ isDbConnection(newValue) &&
+ isSemanticViewType(currentTypeFilter.current)
+ ) {
filtersRef.current?.clearFilterById('sql');
}
}
if (changed === 'type') {
- if (isSemanticViewType(newValue) && isDbConnection(currentConnectionFilter.current)) {
+ if (
+ isSemanticViewType(newValue) &&
+ isDbConnection(currentConnectionFilter.current)
+ ) {
filtersRef.current?.clearFilterById('database');
}
- if (isPhysicalVirtualType(newValue) && isSlConnection(currentConnectionFilter.current)) {
+ if (
+ isPhysicalVirtualType(newValue) &&
+ isSlConnection(currentConnectionFilter.current)
+ ) {
filtersRef.current?.clearFilterById('database');
}
}
@@ -305,7 +320,9 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
? SupersetClient.get({
endpoint: `/api/v1/semantic_layer/?q=${rison.encode_uri({
...(filterValue
- ? { filters: [{ col: 'name', opr: 'ct', value: filterValue }] }
+ ? {
+ filters: [{ col: 'name', opr: 'ct', value: filterValue }],
+ }
: {}),
page: 0,
page_size: 100,
@@ -334,97 +351,100 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
[currentSourceFilter],
);
- const fetchData = useCallback((config: ListViewFetchDataConfig) => {
- setLastFetchConfig(config);
- setLoading(true);
- const { pageIndex, pageSize, sortBy, filters: filterValues } = config;
-
- // Separate source_type and database/connection filters for special handling
- const sourceTypeFilter = filterValues.find(f => f.id === 'source_type');
- const databaseFilter = filterValues.find(f => f.id === 'database');
-
- // Track source filter for conditional Type filter visibility
- const sourceVal =
- sourceTypeFilter?.value && typeof sourceTypeFilter.value === 'object'
- ? (sourceTypeFilter.value as { value: string }).value
- : ((sourceTypeFilter?.value as string) ?? '');
- setCurrentSourceFilter(sourceVal);
-
- const otherFilters = filterValues
- .filter(f => f.id !== 'source_type' && f.id !== 'database')
- .filter(
- ({ value }) => value !== '' && value !== null && value !== undefined,
- )
- .map(({ id, operator: opr, value }) => ({
- col: id,
- opr,
- value:
- value && typeof value === 'object' && 'value' in value
- ? value.value
- : value,
- }));
-
- // Add source_type filter for the combined endpoint
- const sourceTypeValue =
+ const fetchData = useCallback(
+ (config: ListViewFetchDataConfig) => {
+ setLastFetchConfig(config);
+ setLoading(true);
+ const { pageIndex, pageSize, sortBy, filters: filterValues } = config;
+
+ // Separate source_type and database/connection filters for special handling
+ const sourceTypeFilter = filterValues.find(f => f.id === 'source_type');
+ const databaseFilter = filterValues.find(f => f.id === 'database');
+
+ // Track source filter for conditional Type filter visibility
+ const sourceVal =
sourceTypeFilter?.value && typeof sourceTypeFilter.value === 'object'
? (sourceTypeFilter.value as { value: string }).value
- : (sourceTypeFilter?.value as string | undefined);
- if (sourceTypeValue) {
- otherFilters.push({
- col: 'source_type',
- opr: 'eq',
- value: sourceTypeValue,
- });
- }
-
- const queryParams = rison.encode_uri({
- order_column: sortBy[0].id,
- order_direction: sortBy[0].desc ? 'desc' : 'asc',
- page: pageIndex,
- page_size: pageSize,
- ...(otherFilters.length ? { filters: otherFilters } : {}),
- });
+ : ((sourceTypeFilter?.value as string) ?? '');
+ setCurrentSourceFilter(sourceVal);
+
+ const otherFilters = filterValues
+ .filter(f => f.id !== 'source_type' && f.id !== 'database')
+ .filter(
+ ({ value }) => value !== '' && value !== null && value !== undefined,
+ )
+ .map(({ id, operator: opr, value }) => ({
+ col: id,
+ opr,
+ value:
+ value && typeof value === 'object' && 'value' in value
+ ? value.value
+ : value,
+ }));
+
+ // Add source_type filter for the combined endpoint
+ const sourceTypeValue =
+ sourceTypeFilter?.value && typeof sourceTypeFilter.value === 'object'
+ ? (sourceTypeFilter.value as { value: string }).value
+ : (sourceTypeFilter?.value as string | undefined);
+ if (sourceTypeValue) {
+ otherFilters.push({
+ col: 'source_type',
+ opr: 'eq',
+ value: sourceTypeValue,
+ });
+ }
- // Translate the "Data connection" filter: values prefixed with "sl:" are
- // semantic layer UUIDs; plain values are database IDs.
- if (databaseFilter?.value !== undefined && databaseFilter.value !== '') {
- const raw =
- databaseFilter.value &&
- typeof databaseFilter.value === 'object' &&
- 'value' in databaseFilter.value
- ? (databaseFilter.value as { value: unknown }).value
- : databaseFilter.value;
- if (typeof raw === 'string' && raw.startsWith('sl:')) {
- otherFilters.push({
- col: 'semantic_layer_uuid',
- opr: 'eq',
- value: raw.slice(3),
- });
- } else if (raw !== null && raw !== undefined && raw !== '') {
- otherFilters.push({
- col: 'database',
- opr: databaseFilter.operator,
- value: raw as string | number,
+ const queryParams = rison.encode_uri({
+ order_column: sortBy[0].id,
+ order_direction: sortBy[0].desc ? 'desc' : 'asc',
+ page: pageIndex,
+ page_size: pageSize,
+ ...(otherFilters.length ? { filters: otherFilters } : {}),
});
+
+ // Translate the "Data connection" filter: values prefixed with "sl:" are
+ // semantic layer UUIDs; plain values are database IDs.
+ if (databaseFilter?.value !== undefined && databaseFilter.value !== '') {
+ const raw =
+ databaseFilter.value &&
+ typeof databaseFilter.value === 'object' &&
+ 'value' in databaseFilter.value
+ ? (databaseFilter.value as { value: unknown }).value
+ : databaseFilter.value;
+ if (typeof raw === 'string' && raw.startsWith('sl:')) {
+ otherFilters.push({
+ col: 'semantic_layer_uuid',
+ opr: 'eq',
+ value: raw.slice(3),
+ });
+ } else if (raw !== null && raw !== undefined && raw !== '') {
+ otherFilters.push({
+ col: 'database',
+ opr: databaseFilter.operator,
+ value: raw as string | number,
+ });
+ }
}
- }
- return SupersetClient.get({
- endpoint: `/api/v1/datasource/?q=${queryParams}`,
- })
- .then(({ json = {} }) => {
- setDatasets(json.result);
- setDatasetCount(json.count);
- })
- .catch(() => {
- addDangerToast(
- t('An error occurred while fetching %s', datasetsLabelLower()),
- );
+ return SupersetClient.get({
+ endpoint: `/api/v1/datasource/?q=${queryParams}`,
})
- .finally(() => {
- setLoading(false);
- });
- }, [addDangerToast]);
+ .then(({ json = {} }) => {
+ setDatasets(json.result);
+ setDatasetCount(json.count);
+ })
+ .catch(() => {
+ addDangerToast(
+ t('An error occurred while fetching %s', datasetsLabelLower()),
+ );
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ },
+ [addDangerToast],
+ );
const refreshData = useCallback(() => {
if (lastFetchConfig) {