mirror of
https://github.com/apache/superset.git
synced 2026-04-24 10:35:01 +00:00
Working on filters
This commit is contained in:
@@ -190,13 +190,75 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
useState<ListViewFetchDataConfig | null>(null);
|
||||
const [currentSourceFilter, setCurrentSourceFilter] = useState<string>('');
|
||||
|
||||
/**
|
||||
* Fetches "Data connection" filter options — a combined list of databases
|
||||
* and semantic layers.
|
||||
*
|
||||
* Semantic layer values are prefixed with "sl:" so that fetchData can tell
|
||||
* them apart from integer database IDs and route to the correct API filter.
|
||||
*/
|
||||
const fetchConnectionOptions = useCallback(
|
||||
async (filterValue = '', page: number, pageSize: number) => {
|
||||
const showDatabases = currentSourceFilter !== 'semantic_layer';
|
||||
const showSemanticLayers =
|
||||
isFeatureEnabled(FeatureFlag.SemanticLayers) &&
|
||||
currentSourceFilter !== 'database';
|
||||
|
||||
const [dbResult, slResult] = await Promise.all([
|
||||
showDatabases
|
||||
? createFetchRelated(
|
||||
'dataset',
|
||||
'database',
|
||||
createErrorHandler(errMsg =>
|
||||
t(
|
||||
'An error occurred while fetching %s: %s',
|
||||
datasetsLabelLower(),
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
)(filterValue, page, pageSize)
|
||||
: Promise.resolve({ data: [], totalCount: 0 }),
|
||||
showSemanticLayers
|
||||
? SupersetClient.get({
|
||||
endpoint: `/api/v1/semantic_layer/?q=${rison.encode_uri({
|
||||
...(filterValue
|
||||
? { filters: [{ col: 'name', opr: 'ct', value: filterValue }] }
|
||||
: {}),
|
||||
page: 0,
|
||||
page_size: 100,
|
||||
})}`,
|
||||
})
|
||||
.then(({ json = {} }) => ({
|
||||
data: (json?.result ?? []).map(
|
||||
(layer: { uuid: string; name: string }) => ({
|
||||
label: layer.name,
|
||||
// "sl:" prefix distinguishes semantic layers from DB integer IDs
|
||||
value: `sl:${layer.uuid}`,
|
||||
}),
|
||||
),
|
||||
totalCount: json?.count ?? 0,
|
||||
}))
|
||||
.catch(() => ({ data: [], totalCount: 0 }))
|
||||
: Promise.resolve({ data: [], totalCount: 0 }),
|
||||
]);
|
||||
|
||||
return {
|
||||
// Semantic layers first, then databases
|
||||
data: [...slResult.data, ...dbResult.data],
|
||||
totalCount: slResult.totalCount + dbResult.totalCount,
|
||||
};
|
||||
},
|
||||
[currentSourceFilter],
|
||||
);
|
||||
|
||||
const fetchData = useCallback((config: ListViewFetchDataConfig) => {
|
||||
setLastFetchConfig(config);
|
||||
setLoading(true);
|
||||
const { pageIndex, pageSize, sortBy, filters: filterValues } = config;
|
||||
|
||||
// Separate source_type filter from other filters
|
||||
// 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 =
|
||||
@@ -204,8 +266,9 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
? (sourceTypeFilter.value as { value: string }).value
|
||||
: ((sourceTypeFilter?.value as string) ?? '');
|
||||
setCurrentSourceFilter(sourceVal);
|
||||
|
||||
const otherFilters = filterValues
|
||||
.filter(f => f.id !== 'source_type')
|
||||
.filter(f => f.id !== 'source_type' && f.id !== 'database')
|
||||
.filter(
|
||||
({ value }) => value !== '' && value !== null && value !== undefined,
|
||||
)
|
||||
@@ -231,6 +294,30 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const queryParams = rison.encode_uri({
|
||||
order_column: sortBy[0].id,
|
||||
order_direction: sortBy[0].desc ? 'desc' : 'asc',
|
||||
@@ -744,7 +831,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
operator: FilterOperator.Equals,
|
||||
unfilteredLabel: t('All'),
|
||||
selects: [
|
||||
{ label: databaseLabel(), value: 'database' },
|
||||
{ label: t('Database'), value: 'database' },
|
||||
{ label: t('Semantic Layer'), value: 'semantic_layer' },
|
||||
],
|
||||
},
|
||||
@@ -797,20 +884,10 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
Header: databaseLabel(),
|
||||
key: 'database',
|
||||
id: 'database',
|
||||
input: 'select',
|
||||
input: 'select' as const,
|
||||
operator: FilterOperator.RelationOneMany,
|
||||
unfilteredLabel: 'All',
|
||||
fetchSelects: createFetchRelated(
|
||||
'dataset',
|
||||
'database',
|
||||
createErrorHandler(errMsg =>
|
||||
t(
|
||||
'An error occurred while fetching %s: %s',
|
||||
datasetsLabelLower(),
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
fetchSelects: fetchConnectionOptions,
|
||||
paginate: true,
|
||||
dropdownStyle: { minWidth: WIDER_DROPDOWN_WIDTH },
|
||||
},
|
||||
|
||||
@@ -62,14 +62,32 @@ class GetCombinedDatasourceListCommand(BaseCommand):
|
||||
order_direction = self._args.get("order_direction", "desc")
|
||||
filters = self._args.get("filters", [])
|
||||
|
||||
source_type, name_filter, sql_filter, type_filter = self._parse_filters(filters)
|
||||
(
|
||||
source_type,
|
||||
name_filter,
|
||||
sql_filter,
|
||||
type_filter,
|
||||
database_id,
|
||||
semantic_layer_uuid,
|
||||
) = self._parse_filters(filters)
|
||||
|
||||
# A connection filter implicitly narrows the source type: selecting a
|
||||
# database ID means "show only datasets", and selecting a semantic layer
|
||||
# UUID means "show only semantic views". Only apply the implicit
|
||||
# narrowing when the user hasn't already set an explicit source_type.
|
||||
if source_type == "all":
|
||||
if database_id is not None:
|
||||
source_type = "database"
|
||||
elif semantic_layer_uuid is not None:
|
||||
source_type = "semantic_layer"
|
||||
|
||||
source_type = self._resolve_source_type(source_type, sql_filter, type_filter)
|
||||
|
||||
if source_type == "empty":
|
||||
return {"count": 0, "result": []}
|
||||
|
||||
ds_q = DatasourceDAO.build_dataset_query(name_filter, sql_filter)
|
||||
sv_q = DatasourceDAO.build_semantic_view_query(name_filter)
|
||||
ds_q = DatasourceDAO.build_dataset_query(name_filter, sql_filter, database_id)
|
||||
sv_q = DatasourceDAO.build_semantic_view_query(name_filter, semantic_layer_uuid)
|
||||
|
||||
if source_type == "database":
|
||||
combined = ds_q.subquery()
|
||||
@@ -126,10 +144,15 @@ class GetCombinedDatasourceListCommand(BaseCommand):
|
||||
return "database"
|
||||
if not self._can_read_datasets:
|
||||
return "semantic_layer"
|
||||
# An explicit source_type selection ("database" or "semantic_layer") always
|
||||
# wins. This prevents e.g. Type="Semantic View" from overriding an explicit
|
||||
# Source="Database" filter and showing inconsistent results.
|
||||
if source_type in ("database", "semantic_layer"):
|
||||
return source_type
|
||||
# sql_filter (physical/virtual toggle) only applies to datasets
|
||||
if sql_filter is not None:
|
||||
return "database"
|
||||
# Explicit semantic-view type filter
|
||||
# Explicit semantic-view type filter (only reached when source_type="all")
|
||||
if type_filter == "semantic_view":
|
||||
return "semantic_layer"
|
||||
return source_type
|
||||
@@ -137,20 +160,24 @@ class GetCombinedDatasourceListCommand(BaseCommand):
|
||||
@staticmethod
|
||||
def _parse_filters(
|
||||
filters: list[dict[str, Any]],
|
||||
) -> tuple[str, str | None, bool | None, str | None]:
|
||||
) -> tuple[str, str | None, bool | None, str | None, int | None, str | None]:
|
||||
"""
|
||||
Translate raw rison filter dicts into typed query parameters.
|
||||
|
||||
Returns:
|
||||
source_type: "all" | "database" | "semantic_layer"
|
||||
name_filter: substring to match against name/table_name
|
||||
sql_filter: True → physical only, False → virtual only, None → both
|
||||
type_filter: "semantic_view" when the caller wants only semantic views
|
||||
source_type: "all" | "database" | "semantic_layer"
|
||||
name_filter: substring to match against name/table_name
|
||||
sql_filter: True → physical only, False → virtual only, None → both
|
||||
type_filter: "semantic_view" when the caller wants only semantic views
|
||||
database_id: filter datasets to a specific database ID
|
||||
semantic_layer_uuid: filter semantic views to a specific semantic layer UUID
|
||||
"""
|
||||
source_type = "all"
|
||||
name_filter: str | None = None
|
||||
sql_filter: bool | None = None
|
||||
type_filter: str | None = None
|
||||
database_id: int | None = None
|
||||
semantic_layer_uuid: str | None = None
|
||||
|
||||
for f in filters:
|
||||
col = f.get("col")
|
||||
@@ -165,5 +192,19 @@ class GetCombinedDatasourceListCommand(BaseCommand):
|
||||
type_filter = "semantic_view"
|
||||
else:
|
||||
sql_filter = value
|
||||
elif col == "database" and value is not None:
|
||||
try:
|
||||
database_id = int(value)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
elif col == "semantic_layer_uuid" and value is not None:
|
||||
semantic_layer_uuid = str(value)
|
||||
|
||||
return source_type, name_filter, sql_filter, type_filter
|
||||
return (
|
||||
source_type,
|
||||
name_filter,
|
||||
sql_filter,
|
||||
type_filter,
|
||||
database_id,
|
||||
semantic_layer_uuid,
|
||||
)
|
||||
|
||||
@@ -91,6 +91,7 @@ class DatasourceDAO(BaseDAO[Datasource]):
|
||||
def build_dataset_query(
|
||||
name_filter: str | None,
|
||||
sql_filter: bool | None,
|
||||
database_id: int | None = None,
|
||||
) -> Select:
|
||||
"""Build a SELECT for datasets, applying access and content filters."""
|
||||
ds_q = select(
|
||||
@@ -116,11 +117,17 @@ class DatasourceDAO(BaseDAO[Datasource]):
|
||||
else:
|
||||
ds_q = ds_q.where(and_(SqlaTable.sql.isnot(None), SqlaTable.sql != ""))
|
||||
|
||||
if database_id is not None:
|
||||
ds_q = ds_q.where(SqlaTable.database_id == database_id)
|
||||
|
||||
return ds_q
|
||||
|
||||
@staticmethod
|
||||
def build_semantic_view_query(name_filter: str | None) -> Select:
|
||||
"""Build a SELECT for semantic views, applying name filter."""
|
||||
def build_semantic_view_query(
|
||||
name_filter: str | None,
|
||||
semantic_layer_uuid: str | None = None,
|
||||
) -> Select:
|
||||
"""Build a SELECT for semantic views, applying name and layer filters."""
|
||||
sv_q = select(
|
||||
SemanticView.id.label("item_id"),
|
||||
literal("semantic_layer").label("source_type"),
|
||||
@@ -131,6 +138,9 @@ class DatasourceDAO(BaseDAO[Datasource]):
|
||||
if name_filter:
|
||||
sv_q = sv_q.where(SemanticView.name.ilike(f"%{name_filter}%"))
|
||||
|
||||
if semantic_layer_uuid is not None:
|
||||
sv_q = sv_q.where(SemanticView.semantic_layer_uuid == semantic_layer_uuid)
|
||||
|
||||
return sv_q
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user