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