This is an automated email from the ASF dual-hosted git repository. beto pushed a commit to branch semantic-layer-ui-semantic-layer in repository https://gitbox.apache.org/repos/asf/superset.git
commit 33f6902001bb1c7ea718b3042256fb0404261403 Author: Beto Dealmeida <[email protected]> AuthorDate: Tue Feb 17 11:06:37 2026 -0500 CRUD working --- .../features/semanticLayers/SemanticLayerModal.tsx | 88 ++++- superset-frontend/src/pages/DatabaseList/index.tsx | 383 ++++++++++++++++----- superset/commands/semantic_layer/create.py | 5 + superset/commands/semantic_layer/update.py | 5 + superset/semantic_layers/api.py | 169 ++++++++- superset/static/service-worker.js | 206 +++++------ 6 files changed, 654 insertions(+), 202 deletions(-) diff --git a/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx b/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx index fbeb984ffa9..37694a9e9a6 100644 --- a/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx +++ b/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx @@ -303,6 +303,7 @@ interface SemanticLayerModalProps { onHide: () => void; addDangerToast: (msg: string) => void; addSuccessToast: (msg: string) => void; + semanticLayerUuid?: string; } export default function SemanticLayerModal({ @@ -310,7 +311,9 @@ export default function SemanticLayerModal({ onHide, addDangerToast, addSuccessToast, + semanticLayerUuid, }: SemanticLayerModalProps) { + const isEditMode = !!semanticLayerUuid; const [step, setStep] = useState<Step>('type'); const [name, setName] = useState(''); const [selectedType, setSelectedType] = useState<string | null>(null); @@ -380,9 +383,47 @@ export default function SemanticLayerModal({ [addDangerToast, applySchema], ); + const fetchExistingLayer = useCallback( + async (uuid: string) => { + setLoading(true); + try { + const { json } = await SupersetClient.get({ + endpoint: `/api/v1/semantic_layer/${uuid}`, + }); + const layer = json.result; + setName(layer.name ?? ''); + setSelectedType(layer.type); + setFormData(layer.configuration ?? {}); + setHasErrors(false); + // Fetch base schema (no configuration → no Snowflake connection) to + // show the form immediately. The existing maybeRefreshSchema machinery + // will trigger an enriched fetch in the background once deps are + // satisfied, and DynamicFieldControl will show per-field spinners. + const { json: schemaJson } = await SupersetClient.post({ + endpoint: '/api/v1/semantic_layer/schema/configuration', + jsonPayload: { type: layer.type }, + }); + applySchema(schemaJson.result); + setStep('config'); + } catch { + addDangerToast( + t('An error occurred while fetching the semantic layer'), + ); + } finally { + setLoading(false); + } + }, + [addDangerToast, applySchema], + ); + useEffect(() => { if (show) { - fetchTypes(); + if (isEditMode && semanticLayerUuid) { + fetchTypes(); + fetchExistingLayer(semanticLayerUuid); + } else { + fetchTypes(); + } } else { setStep('type'); setName(''); @@ -399,7 +440,7 @@ export default function SemanticLayerModal({ dynamicDepsRef.current = {}; if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); } - }, [show, fetchTypes]); + }, [show, fetchTypes, isEditMode, semanticLayerUuid, fetchExistingLayer]); const handleStepAdvance = () => { if (selectedType) { @@ -422,14 +463,26 @@ export default function SemanticLayerModal({ const handleCreate = async () => { setSaving(true); try { - await SupersetClient.post({ - endpoint: '/api/v1/semantic_layer/', - jsonPayload: { name, type: selectedType, configuration: formData }, - }); - addSuccessToast(t('Semantic layer created')); + if (isEditMode && semanticLayerUuid) { + await SupersetClient.put({ + endpoint: `/api/v1/semantic_layer/${semanticLayerUuid}`, + jsonPayload: { name, configuration: formData }, + }); + addSuccessToast(t('Semantic layer updated')); + } else { + await SupersetClient.post({ + endpoint: '/api/v1/semantic_layer/', + jsonPayload: { name, type: selectedType, configuration: formData }, + }); + addSuccessToast(t('Semantic layer created')); + } onHide(); } catch { - addDangerToast(t('An error occurred while creating the semantic layer')); + addDangerToast( + isEditMode + ? t('An error occurred while updating the semantic layer') + : t('An error occurred while creating the semantic layer'), + ); } finally { setSaving(false); } @@ -491,8 +544,9 @@ export default function SemanticLayerModal({ const selectedTypeName = types.find(type => type.id === selectedType)?.name ?? ''; - const title = - step === 'type' + const title = isEditMode + ? t('Edit %s', selectedTypeName || t('Semantic Layer')) + : step === 'type' ? t('New Semantic Layer') : t('Configure %s', selectedTypeName); @@ -502,12 +556,12 @@ export default function SemanticLayerModal({ onHide={onHide} onSave={handleSave} title={title} - icon={<Icons.PlusOutlined />} + icon={isEditMode ? <Icons.EditOutlined /> : <Icons.PlusOutlined />} width={step === 'type' ? MODAL_STANDARD_WIDTH : MODAL_MEDIUM_WIDTH} saveDisabled={ step === 'type' ? !selectedType : saving || !name.trim() || hasErrors } - saveText={step === 'type' ? undefined : t('Create')} + saveText={step === 'type' ? undefined : isEditMode ? t('Save') : t('Create')} saveLoading={saving} contentLoading={loading} > @@ -534,10 +588,12 @@ export default function SemanticLayerModal({ </ModalContent> ) : ( <ModalContent> - <BackLink type="button" onClick={handleBack}> - <Icons.CaretLeftOutlined iconSize="s" /> - {t('Back')} - </BackLink> + {!isEditMode && ( + <BackLink type="button" onClick={handleBack}> + <Icons.CaretLeftOutlined iconSize="s" /> + {t('Back')} + </BackLink> + )} <ModalFormField label={t('Name')} required> <Input value={name} diff --git a/superset-frontend/src/pages/DatabaseList/index.tsx b/superset-frontend/src/pages/DatabaseList/index.tsx index 1c1c8ccdc2c..1858e6c7572 100644 --- a/superset-frontend/src/pages/DatabaseList/index.tsx +++ b/superset-frontend/src/pages/DatabaseList/index.tsx @@ -24,7 +24,7 @@ import { FeatureFlag, } from '@superset-ui/core'; import { css, styled, useTheme } from '@apache-superset/core/ui'; -import { useState, useMemo, useEffect } from 'react'; +import { useState, useMemo, useEffect, useCallback } from 'react'; import rison from 'rison'; import { useSelector } from 'react-redux'; import { useQueryParams, BooleanParam } from 'use-query-params'; @@ -50,6 +50,7 @@ import { ListView, ListViewFilterOperator as FilterOperator, ListViewFilters, + type ListViewFetchDataConfig, } from 'src/components'; import { Typography } from '@superset-ui/core/components/Typography'; import { getUrlParam } from 'src/utils/urlUtils'; @@ -78,6 +79,11 @@ const dbConfigExtraExtension = extensionsRegistry.get( const PAGE_SIZE = 25; +type ConnectionItem = DatabaseObject & { + source_type?: 'database' | 'semantic_layer'; + sl_type?: string; +}; + interface DatabaseDeleteObject extends DatabaseObject { charts: any; dashboards: any; @@ -117,20 +123,105 @@ function DatabaseList({ user, }: DatabaseListProps) { const theme = useTheme(); + const showSemanticLayers = isFeatureEnabled(FeatureFlag.SemanticLayers); + + // Standard database list view resource (used when SL flag is OFF) const { state: { - loading, - resourceCount: databaseCount, - resourceCollection: databases, + loading: dbLoading, + resourceCount: dbCount, + resourceCollection: dbCollection, }, hasPerm, - fetchData, - refreshData, + fetchData: dbFetchData, + refreshData: dbRefreshData, } = useListViewResource<DatabaseObject>( 'database', t('database'), addDangerToast, ); + + // Combined endpoint state (used when SL flag is ON) + const [combinedItems, setCombinedItems] = useState<ConnectionItem[]>([]); + const [combinedCount, setCombinedCount] = useState(0); + const [combinedLoading, setCombinedLoading] = useState(true); + const [lastFetchConfig, setLastFetchConfig] = + useState<ListViewFetchDataConfig | null>(null); + + const combinedFetchData = useCallback( + (config: ListViewFetchDataConfig) => { + setLastFetchConfig(config); + setCombinedLoading(true); + const { pageIndex, pageSize, sortBy, filters: filterValues } = config; + + const sourceTypeFilter = filterValues.find(f => f.id === 'source_type'); + const otherFilters = filterValues + .filter(f => f.id !== 'source_type') + .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, + })); + + const sourceTypeValue = + sourceTypeFilter?.value && typeof sourceTypeFilter.value === 'object' + ? (sourceTypeFilter.value as { value: string }).value + : sourceTypeFilter?.value; + 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 } : {}), + }); + + return SupersetClient.get({ + endpoint: `/api/v1/semantic_layer/connections/?q=${queryParams}`, + }) + .then(({ json = {} }) => { + setCombinedItems(json.result); + setCombinedCount(json.count); + }) + .catch(() => { + addDangerToast(t('An error occurred while fetching connections')); + }) + .finally(() => { + setCombinedLoading(false); + }); + }, + [addDangerToast], + ); + + const combinedRefreshData = useCallback(() => { + if (lastFetchConfig) { + return combinedFetchData(lastFetchConfig); + } + return undefined; + }, [lastFetchConfig, combinedFetchData]); + + // Select the right data source based on feature flag + const loading = showSemanticLayers ? combinedLoading : dbLoading; + const databaseCount = showSemanticLayers ? combinedCount : dbCount; + const databases: ConnectionItem[] = showSemanticLayers + ? combinedItems + : dbCollection; + const fetchData = showSemanticLayers ? combinedFetchData : dbFetchData; + const refreshData = showSemanticLayers ? combinedRefreshData : dbRefreshData; + const fullUser = useSelector<any, UserWithPermissionsAndRoles>( state => state.user, ); @@ -159,6 +250,11 @@ function DatabaseList({ useState<boolean>(false); const [semanticLayerModalOpen, setSemanticLayerModalOpen] = useState<boolean>(false); + const [slCurrentlyEditing, setSlCurrentlyEditing] = useState<string | null>( + null, + ); + const [slCurrentlyDeleting, setSlCurrentlyDeleting] = + useState<ConnectionItem | null>(null); const [allowUploads, setAllowUploads] = useState<boolean>(false); const isAdmin = isUserAdmin(fullUser); @@ -440,6 +536,23 @@ function DatabaseList({ const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; + function handleSemanticLayerDelete(item: ConnectionItem) { + SupersetClient.delete({ + endpoint: `/api/v1/semantic_layer/${item.uuid}`, + }).then( + () => { + refreshData(); + addSuccessToast(t('Deleted: %s', item.database_name)); + setSlCurrentlyDeleting(null); + }, + createErrorHandler(errMsg => + addDangerToast( + t('There was an issue deleting %s: %s', item.database_name, errMsg), + ), + ), + ); + } + const columns = useMemo( () => [ { @@ -452,7 +565,7 @@ function DatabaseList({ accessor: 'backend', Header: t('Backend'), size: 'xl', - disableSortBy: true, // TODO: api support for sorting by 'backend' + disableSortBy: true, id: 'backend', }, { @@ -466,13 +579,12 @@ function DatabaseList({ <span>{t('AQE')}</span> </Tooltip> ), - Cell: ({ - row: { - original: { allow_run_async: allowRunAsync }, - }, - }: { - row: { original: { allow_run_async: boolean } }; - }) => <BooleanDisplay value={allowRunAsync} />, + Cell: ({ row: { original } }: any) => + original.source_type === 'semantic_layer' ? ( + <span>–</span> + ) : ( + <BooleanDisplay value={original.allow_run_async} /> + ), size: 'sm', id: 'allow_run_async', }, @@ -487,33 +599,36 @@ function DatabaseList({ <span>{t('DML')}</span> </Tooltip> ), - Cell: ({ - row: { - original: { allow_dml: allowDML }, - }, - }: any) => <BooleanDisplay value={allowDML} />, + Cell: ({ row: { original } }: any) => + original.source_type === 'semantic_layer' ? ( + <span>–</span> + ) : ( + <BooleanDisplay value={original.allow_dml} /> + ), size: 'sm', id: 'allow_dml', }, { accessor: 'allow_file_upload', Header: t('File upload'), - Cell: ({ - row: { - original: { allow_file_upload: allowFileUpload }, - }, - }: any) => <BooleanDisplay value={allowFileUpload} />, + Cell: ({ row: { original } }: any) => + original.source_type === 'semantic_layer' ? ( + <span>–</span> + ) : ( + <BooleanDisplay value={original.allow_file_upload} /> + ), size: 'md', id: 'allow_file_upload', }, { accessor: 'expose_in_sqllab', Header: t('Expose in SQL Lab'), - Cell: ({ - row: { - original: { expose_in_sqllab: exposeInSqllab }, - }, - }: any) => <BooleanDisplay value={exposeInSqllab} />, + Cell: ({ row: { original } }: any) => + original.source_type === 'semantic_layer' ? ( + <span>–</span> + ) : ( + <BooleanDisplay value={original.expose_in_sqllab} /> + ), size: 'md', id: 'expose_in_sqllab', }, @@ -533,6 +648,49 @@ function DatabaseList({ }, { Cell: ({ row: { original } }: any) => { + const isSemanticLayer = + original.source_type === 'semantic_layer'; + + if (isSemanticLayer) { + if (!canEdit && !canDelete) return null; + return ( + <Actions className="actions"> + {canDelete && ( + <Tooltip + id="delete-action-tooltip" + title={t('Delete')} + placement="bottom" + > + <span + role="button" + tabIndex={0} + className="action-button" + onClick={() => setSlCurrentlyDeleting(original)} + > + <Icons.DeleteOutlined iconSize="l" /> + </span> + </Tooltip> + )} + {canEdit && ( + <Tooltip + id="edit-action-tooltip" + title={t('Edit')} + placement="bottom" + > + <span + role="button" + tabIndex={0} + className="action-button" + onClick={() => setSlCurrentlyEditing(original.uuid)} + > + <Icons.EditOutlined iconSize="l" /> + </span> + </Tooltip> + )} + </Actions> + ); + } + const handleEdit = () => handleDatabaseEditModal({ database: original, modalOpen: true }); const handleDelete = () => openDatabaseDeleteModal(original); @@ -618,6 +776,12 @@ function DatabaseList({ hidden: !canEdit && !canDelete, disableSortBy: true, }, + { + accessor: 'source_type', + hidden: true, + disableSortBy: true, + id: 'source_type', + }, { accessor: QueryObjectColumns.ChangedBy, hidden: true, @@ -627,8 +791,8 @@ function DatabaseList({ [canDelete, canEdit, canExport], ); - const filters: ListViewFilters = useMemo( - () => [ + const filters: ListViewFilters = useMemo(() => { + const baseFilters: ListViewFilters = [ { Header: t('Name'), key: 'search', @@ -636,62 +800,83 @@ function DatabaseList({ input: 'search', operator: FilterOperator.Contains, }, - { - Header: t('Expose in SQL Lab'), - key: 'expose_in_sql_lab', - id: 'expose_in_sqllab', - input: 'select', - operator: FilterOperator.Equals, - unfilteredLabel: t('All'), - selects: [ - { label: t('Yes'), value: true }, - { label: t('No'), value: false }, - ], - }, - { - Header: ( - <Tooltip - id="allow-run-async-filter-header-tooltip" - title={t('Asynchronous query execution')} - placement="top" - > - <span>{t('AQE')}</span> - </Tooltip> - ), - key: 'allow_run_async', - id: 'allow_run_async', + ]; + + if (showSemanticLayers) { + baseFilters.push({ + Header: t('Source'), + key: 'source_type', + id: 'source_type', input: 'select', operator: FilterOperator.Equals, unfilteredLabel: t('All'), selects: [ - { label: t('Yes'), value: true }, - { label: t('No'), value: false }, + { label: t('Database'), value: 'database' }, + { label: t('Semantic Layer'), value: 'semantic_layer' }, ], - }, - { - Header: t('Modified by'), - key: 'changed_by', - id: 'changed_by', - input: 'select', - operator: FilterOperator.RelationOneMany, - unfilteredLabel: t('All'), - fetchSelects: createFetchRelated( - 'database', - 'changed_by', - createErrorHandler(errMsg => - t( - 'An error occurred while fetching dataset datasource values: %s', - errMsg, + }); + } + + if (!showSemanticLayers) { + baseFilters.push( + { + Header: t('Expose in SQL Lab'), + key: 'expose_in_sql_lab', + id: 'expose_in_sqllab', + input: 'select', + operator: FilterOperator.Equals, + unfilteredLabel: t('All'), + selects: [ + { label: t('Yes'), value: true }, + { label: t('No'), value: false }, + ], + }, + { + Header: ( + <Tooltip + id="allow-run-async-filter-header-tooltip" + title={t('Asynchronous query execution')} + placement="top" + > + <span>{t('AQE')}</span> + </Tooltip> + ), + key: 'allow_run_async', + id: 'allow_run_async', + input: 'select', + operator: FilterOperator.Equals, + unfilteredLabel: t('All'), + selects: [ + { label: t('Yes'), value: true }, + { label: t('No'), value: false }, + ], + }, + { + Header: t('Modified by'), + key: 'changed_by', + id: 'changed_by', + input: 'select', + operator: FilterOperator.RelationOneMany, + unfilteredLabel: t('All'), + fetchSelects: createFetchRelated( + 'database', + 'changed_by', + createErrorHandler(errMsg => + t( + 'An error occurred while fetching dataset datasource values: %s', + errMsg, + ), ), + user, ), - user, - ), - paginate: true, - dropdownStyle: { minWidth: WIDER_DROPDOWN_WIDTH }, - }, - ], - [], - ); + paginate: true, + dropdownStyle: { minWidth: WIDER_DROPDOWN_WIDTH }, + }, + ); + } + + return baseFilters; + }, [showSemanticLayers]); return ( <> @@ -736,10 +921,46 @@ function DatabaseList({ /> <SemanticLayerModal show={semanticLayerModalOpen} - onHide={() => setSemanticLayerModalOpen(false)} + onHide={() => { + setSemanticLayerModalOpen(false); + refreshData(); + }} addDangerToast={addDangerToast} addSuccessToast={addSuccessToast} /> + <SemanticLayerModal + show={!!slCurrentlyEditing} + onHide={() => { + setSlCurrentlyEditing(null); + refreshData(); + }} + addDangerToast={addDangerToast} + addSuccessToast={addSuccessToast} + semanticLayerUuid={slCurrentlyEditing ?? undefined} + /> + {slCurrentlyDeleting && ( + <DeleteModal + description={ + <p> + {t('Are you sure you want to delete')}{' '} + <b>{slCurrentlyDeleting.database_name}</b>? + </p> + } + onConfirm={() => { + if (slCurrentlyDeleting) { + handleSemanticLayerDelete(slCurrentlyDeleting); + } + }} + onHide={() => setSlCurrentlyDeleting(null)} + open + title={ + <ModalTitleWithIcon + icon={<Icons.DeleteOutlined />} + title={t('Delete Semantic Layer?')} + /> + } + /> + )} {databaseCurrentlyDeleting && ( <DeleteModal description={ diff --git a/superset/commands/semantic_layer/create.py b/superset/commands/semantic_layer/create.py index b0778fe8084..250476d210f 100644 --- a/superset/commands/semantic_layer/create.py +++ b/superset/commands/semantic_layer/create.py @@ -16,6 +16,7 @@ # under the License. from __future__ import annotations +import json import logging from functools import partial from typing import Any @@ -48,6 +49,10 @@ class CreateSemanticLayerCommand(BaseCommand): ) def run(self) -> Model: self.validate() + if isinstance(self._properties.get("configuration"), dict): + self._properties["configuration"] = json.dumps( + self._properties["configuration"] + ) return SemanticLayerDAO.create(attributes=self._properties) def validate(self) -> None: diff --git a/superset/commands/semantic_layer/update.py b/superset/commands/semantic_layer/update.py index 5242406af8c..66c02d568ab 100644 --- a/superset/commands/semantic_layer/update.py +++ b/superset/commands/semantic_layer/update.py @@ -16,6 +16,7 @@ # under the License. from __future__ import annotations +import json import logging from functools import partial from typing import Any @@ -87,6 +88,10 @@ class UpdateSemanticLayerCommand(BaseCommand): def run(self) -> Model: self.validate() assert self._model + if isinstance(self._properties.get("configuration"), dict): + self._properties["configuration"] = json.dumps( + self._properties["configuration"] + ) return SemanticLayerDAO.update(self._model, attributes=self._properties) def validate(self) -> None: diff --git a/superset/semantic_layers/api.py b/superset/semantic_layers/api.py index bb94a0e4b91..bed9dc996a5 100644 --- a/superset/semantic_layers/api.py +++ b/superset/semantic_layers/api.py @@ -21,12 +21,13 @@ import logging from typing import Any from flask import make_response, request, Response -from flask_appbuilder.api import expose, protect, safe +from flask_appbuilder.api import expose, protect, rison, safe +from flask_appbuilder.api.schemas import get_list_schema from flask_appbuilder.models.sqla.interface import SQLAInterface from marshmallow import ValidationError from pydantic import ValidationError as PydanticValidationError -from superset import event_logger +from superset import db, event_logger, is_feature_enabled from superset.commands.semantic_layer.create import CreateSemanticLayerCommand from superset.commands.semantic_layer.delete import DeleteSemanticLayerCommand from superset.commands.semantic_layer.exceptions import ( @@ -46,6 +47,7 @@ from superset.commands.semantic_layer.update import ( ) from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP from superset.daos.semantic_layer import SemanticLayerDAO +from superset.models.core import Database from superset.semantic_layers.models import SemanticLayer, SemanticView from superset.semantic_layers.registry import registry from superset.semantic_layers.schemas import ( @@ -65,12 +67,17 @@ logger = logging.getLogger(__name__) def _serialize_layer(layer: SemanticLayer) -> dict[str, Any]: + config = layer.configuration + if isinstance(config, str): + config = json.loads(config) return { "uuid": str(layer.uuid), "name": layer.name, "description": layer.description, "type": layer.type, "cache_timeout": layer.cache_timeout, + "configuration": config or {}, + "changed_on_delta_humanized": layer.changed_on_delta_humanized(), } @@ -515,6 +522,164 @@ class SemanticLayerRestApi(BaseSupersetApi): ) return self.response_422(message=str(ex)) + @expose("/connections/", methods=("GET",)) + @protect() + @safe + @statsd_metrics + @rison(get_list_schema) + @event_logger.log_this_with_context( + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}" + ".connections", + log_to_statsd=False, + ) + def connections(self, **kwargs: Any) -> FlaskResponse: + """List databases and semantic layers combined. + --- + get: + summary: List databases and semantic layers combined + parameters: + - in: query + name: q + content: + application/json: + schema: + $ref: '#/components/schemas/get_list_schema' + responses: + 200: + description: Combined list of databases and semantic layers + 401: + $ref: '#/components/responses/401' + 500: + $ref: '#/components/responses/500' + """ + args = kwargs.get("rison", {}) + page = args.get("page", 0) + page_size = args.get("page_size", 25) + order_column = args.get("order_column", "changed_on") + order_direction = args.get("order_direction", "desc") + filters = args.get("filters", []) + + source_type = "all" + name_filter = None + for f in filters: + if f.get("col") == "source_type": + source_type = f.get("value", "all") + elif f.get("col") == "database_name" and f.get("opr") == "ct": + name_filter = f.get("value") + + if not is_feature_enabled("SEMANTIC_LAYERS"): + source_type = "database" + + reverse = order_direction == "desc" + + def _sort_key_changed_on( + item: tuple[str, Database | SemanticLayer], + ) -> Any: + return item[1].changed_on or "" + + def _sort_key_name( + item: tuple[str, Database | SemanticLayer], + ) -> str: + obj = item[1] + raw = ( + obj.database_name # type: ignore[union-attr] + if item[0] == "database" + else obj.name + ) + return raw.lower() + + sort_key_map = { + "changed_on_delta_humanized": _sort_key_changed_on, + "database_name": _sort_key_name, + } + sort_key = sort_key_map.get(order_column, _sort_key_changed_on) + + # Fetch databases (lightweight: only loads ORM objects, no eager joins) + db_items: list[tuple[str, Database]] = [] + if source_type in ("all", "database"): + db_q = db.session.query(Database) + if name_filter: + db_q = db_q.filter( + Database.database_name.ilike(f"%{name_filter}%") + ) + db_items = [("database", obj) for obj in db_q.all()] + + # Fetch semantic layers + sl_items: list[tuple[str, SemanticLayer]] = [] + if source_type in ("all", "semantic_layer"): + sl_q = db.session.query(SemanticLayer) + if name_filter: + sl_q = sl_q.filter( + SemanticLayer.name.ilike(f"%{name_filter}%") + ) + sl_items = [("semantic_layer", obj) for obj in sl_q.all()] + + # Merge, sort, count, paginate + all_items: list[tuple[str, Any]] = db_items + sl_items # type: ignore + all_items.sort(key=sort_key, reverse=reverse) # type: ignore + total_count = len(all_items) + + start = page * page_size + page_items = all_items[start : start + page_size] + + # Serialize + result = [] + for item_type, obj in page_items: + if item_type == "database": + result.append(self._serialize_database(obj)) + else: + result.append(self._serialize_semantic_layer(obj)) + + return self.response(200, count=total_count, result=result) + + @staticmethod + def _serialize_database(obj: Database) -> dict[str, Any]: + changed_by = obj.changed_by + return { + "source_type": "database", + "id": obj.id, + "uuid": str(obj.uuid), + "database_name": obj.database_name, + "backend": obj.backend, + "allow_run_async": obj.allow_run_async, + "allow_dml": obj.allow_dml, + "allow_file_upload": obj.allow_file_upload, + "expose_in_sqllab": obj.expose_in_sqllab, + "changed_on_delta_humanized": obj.changed_on_delta_humanized(), + "changed_by": { + "first_name": changed_by.first_name, + "last_name": changed_by.last_name, + } + if changed_by + else None, + } + + @staticmethod + def _serialize_semantic_layer(obj: SemanticLayer) -> dict[str, Any]: + changed_by = obj.changed_by + sl_type = obj.type + cls = registry.get(sl_type) + type_name = cls.name if cls else sl_type + return { + "source_type": "semantic_layer", + "uuid": str(obj.uuid), + "database_name": obj.name, + "backend": type_name, + "sl_type": sl_type, + "description": obj.description, + "allow_run_async": None, + "allow_dml": None, + "allow_file_upload": None, + "expose_in_sqllab": None, + "changed_on_delta_humanized": obj.changed_on_delta_humanized(), + "changed_by": { + "first_name": changed_by.first_name, + "last_name": changed_by.last_name, + } + if changed_by + else None, + } + @expose("/", methods=("GET",)) @protect() @safe diff --git a/superset/static/service-worker.js b/superset/static/service-worker.js index 0a942440ae1..88dce3da348 100644 --- a/superset/static/service-worker.js +++ b/superset/static/service-worker.js @@ -24,7 +24,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; -/******/ +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache @@ -44,29 +44,29 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ loaded: false, /******/ exports: {} /******/ }; -/******/ +/******/ /******/ // Execute the module function /******/ var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: __webpack_require__ }; /******/ __webpack_require__.i.forEach(function(handler) { handler(execOptions); }); /******/ module = execOptions.module; /******/ execOptions.factory.call(module.exports, module, module.exports, execOptions.require); -/******/ +/******/ /******/ // Flag the module as loaded /******/ module.loaded = true; -/******/ +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ +/******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = __webpack_modules__; -/******/ +/******/ /******/ // expose the module cache /******/ __webpack_require__.c = __webpack_module_cache__; -/******/ +/******/ /******/ // expose the module execution interceptor /******/ __webpack_require__.i = []; -/******/ +/******/ /************************************************************************/ /******/ /* webpack/runtime/chunk loaded */ /******/ (() => { @@ -99,7 +99,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return result; /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules @@ -111,7 +111,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return getter; /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/create fake namespace object */ /******/ (() => { /******/ var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__); @@ -141,7 +141,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return ns; /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports @@ -153,7 +153,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/get javascript update chunk filename */ /******/ (() => { /******/ // This function allow to reference all chunks @@ -162,17 +162,17 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return "" + chunkId + "." + __webpack_require__.h() + ".hot-update.js"; /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/get update manifest filename */ /******/ (() => { /******/ __webpack_require__.hmrF = () => ("service-worker." + __webpack_require__.h() + ".hot-update.json"); /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/getFullHash */ /******/ (() => { -/******/ __webpack_require__.h = () => ("669bfdb2217bd9a71a0c") +/******/ __webpack_require__.h = () => ("9e4777e49256d5920929") /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/harmony module decorator */ /******/ (() => { /******/ __webpack_require__.hmd = (module) => { @@ -187,12 +187,12 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return module; /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/load script */ /******/ (() => { /******/ var inProgress = {}; @@ -211,13 +211,13 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ if(!script) { /******/ needAttach = true; /******/ script = document.createElement('script'); -/******/ +/******/ /******/ script.charset = 'utf-8'; /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } /******/ script.setAttribute("data-webpack", dataWebpackPrefix + key); -/******/ +/******/ /******/ script.src = url; /******/ } /******/ inProgress[url] = [done]; @@ -237,7 +237,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ needAttach && document.head.appendChild(script); /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports @@ -248,7 +248,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/node module decorator */ /******/ (() => { /******/ __webpack_require__.nmd = (module) => { @@ -257,7 +257,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return module; /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/sharing */ /******/ (() => { /******/ __webpack_require__.S = {}; @@ -309,30 +309,30 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1)); /******/ }; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/hot module replacement */ /******/ (() => { /******/ var currentModuleData = {}; /******/ var installedModules = __webpack_require__.c; -/******/ +/******/ /******/ // module and require creation /******/ var currentChildModule; /******/ var currentParents = []; -/******/ +/******/ /******/ // status /******/ var registeredStatusHandlers = []; /******/ var currentStatus = "idle"; -/******/ +/******/ /******/ // while downloading /******/ var blockingPromises = 0; /******/ var blockingPromisesWaiting = []; -/******/ +/******/ /******/ // The update info /******/ var currentUpdateApplyHandlers; /******/ var queuedInvalidatedModules; -/******/ +/******/ /******/ __webpack_require__.hmrD = currentModuleData; -/******/ +/******/ /******/ __webpack_require__.i.push(function (options) { /******/ var module = options.module; /******/ var require = createRequire(options.require, options.id); @@ -342,10 +342,10 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ currentParents = []; /******/ options.require = require; /******/ }); -/******/ +/******/ /******/ __webpack_require__.hmrC = {}; /******/ __webpack_require__.hmrI = {}; -/******/ +/******/ /******/ function createRequire(require, moduleId) { /******/ var me = installedModules[moduleId]; /******/ if (!me) return require; @@ -396,7 +396,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }; /******/ return fn; /******/ } -/******/ +/******/ /******/ function createModuleHotObject(moduleId, me) { /******/ var _main = currentChildModule !== moduleId; /******/ var hot = { @@ -414,7 +414,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ currentChildModule = _main ? undefined : moduleId; /******/ __webpack_require__(moduleId); /******/ }, -/******/ +/******/ /******/ // Module API /******/ active: true, /******/ accept: function (dep, callback, errorHandler) { @@ -481,7 +481,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ break; /******/ } /******/ }, -/******/ +/******/ /******/ // Management API /******/ check: hotCheck, /******/ apply: hotApply, @@ -496,24 +496,24 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ var idx = registeredStatusHandlers.indexOf(l); /******/ if (idx >= 0) registeredStatusHandlers.splice(idx, 1); /******/ }, -/******/ +/******/ /******/ // inherit from previous dispose call /******/ data: currentModuleData[moduleId] /******/ }; /******/ currentChildModule = undefined; /******/ return hot; /******/ } -/******/ +/******/ /******/ function setStatus(newStatus) { /******/ currentStatus = newStatus; /******/ var results = []; -/******/ +/******/ /******/ for (var i = 0; i < registeredStatusHandlers.length; i++) /******/ results[i] = registeredStatusHandlers[i].call(null, newStatus); -/******/ +/******/ /******/ return Promise.all(results).then(function () {}); /******/ } -/******/ +/******/ /******/ function unblock() { /******/ if (--blockingPromises === 0) { /******/ setStatus("ready").then(function () { @@ -527,7 +527,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }); /******/ } /******/ } -/******/ +/******/ /******/ function trackBlockingPromise(promise) { /******/ switch (currentStatus) { /******/ case "ready": @@ -541,7 +541,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return promise; /******/ } /******/ } -/******/ +/******/ /******/ function waitForBlockingPromises(fn) { /******/ if (blockingPromises === 0) return fn(); /******/ return new Promise(function (resolve) { @@ -550,7 +550,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }); /******/ }); /******/ } -/******/ +/******/ /******/ function hotCheck(applyOnUpdate) { /******/ if (currentStatus !== "idle") { /******/ throw new Error("check() is only allowed in idle status"); @@ -565,11 +565,11 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ ); /******/ } -/******/ +/******/ /******/ return setStatus("prepare").then(function () { /******/ var updatedModules = []; /******/ currentUpdateApplyHandlers = []; -/******/ +/******/ /******/ return Promise.all( /******/ Object.keys(__webpack_require__.hmrC).reduce(function ( /******/ promises, @@ -599,7 +599,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }); /******/ }); /******/ } -/******/ +/******/ /******/ function hotApply(options) { /******/ if (currentStatus !== "ready") { /******/ return Promise.resolve().then(function () { @@ -612,46 +612,46 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ return internalApply(options); /******/ } -/******/ +/******/ /******/ function internalApply(options) { /******/ options = options || {}; -/******/ +/******/ /******/ applyInvalidatedModules(); -/******/ +/******/ /******/ var results = currentUpdateApplyHandlers.map(function (handler) { /******/ return handler(options); /******/ }); /******/ currentUpdateApplyHandlers = undefined; -/******/ +/******/ /******/ var errors = results /******/ .map(function (r) { /******/ return r.error; /******/ }) /******/ .filter(Boolean); -/******/ +/******/ /******/ if (errors.length > 0) { /******/ return setStatus("abort").then(function () { /******/ throw errors[0]; /******/ }); /******/ } -/******/ +/******/ /******/ // Now in "dispose" phase /******/ var disposePromise = setStatus("dispose"); -/******/ +/******/ /******/ results.forEach(function (result) { /******/ if (result.dispose) result.dispose(); /******/ }); -/******/ +/******/ /******/ // Now in "apply" phase /******/ var applyPromise = setStatus("apply"); -/******/ +/******/ /******/ var error; /******/ var reportError = function (err) { /******/ if (!error) error = err; /******/ }; -/******/ +/******/ /******/ var outdatedModules = []; -/******/ +/******/ /******/ var onAccepted = function () { /******/ return Promise.all([disposePromise, applyPromise]).then(function () { /******/ // handle errors in accept handlers and self accepted module load @@ -660,7 +660,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ throw error; /******/ }); /******/ } -/******/ +/******/ /******/ if (queuedInvalidatedModules) { /******/ return internalApply(options).then(function (list) { /******/ outdatedModules.forEach(function (moduleId) { @@ -669,13 +669,13 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return list; /******/ }); /******/ } -/******/ +/******/ /******/ return setStatus("idle").then(function () { /******/ return outdatedModules; /******/ }); /******/ }); /******/ }; -/******/ +/******/ /******/ return Promise.all( /******/ results /******/ .filter(function (result) { @@ -696,7 +696,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }) /******/ .then(onAccepted); /******/ } -/******/ +/******/ /******/ function applyInvalidatedModules() { /******/ if (queuedInvalidatedModules) { /******/ if (!currentUpdateApplyHandlers) currentUpdateApplyHandlers = []; @@ -713,12 +713,12 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ } /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/publicPath */ /******/ (() => { /******/ __webpack_require__.p = "/static/assets/"; /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/react refresh */ /******/ (() => { /******/ const setup = (moduleId) => { @@ -736,7 +736,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }; /******/ return refresh; /******/ }; -/******/ +/******/ /******/ __webpack_require__.i.push((options) => { /******/ const originalFactory = options.factory; /******/ options.factory = function(moduleObject, moduleExports, webpackRequire) { @@ -762,7 +762,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }; /******/ }); /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/consumes */ /******/ (() => { /******/ var parseVersion = (str) => { @@ -843,7 +843,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ return fn(scopeName, __webpack_require__.S[scopeName], key, eager, c, d); /******/ }); -/******/ +/******/ /******/ var useFallback = (scopeName, key, fallback) => { /******/ return fallback ? fallback() : failAsNotExist(scopeName, key); /******/ } @@ -904,11 +904,11 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }); /******/ // no chunk loading of consumes /******/ })(); -/******/ +/******/ /******/ /* webpack/runtime/jsonp chunk loading */ /******/ (() => { /******/ // no baseURI -/******/ +/******/ /******/ // object to store loaded and loading chunks /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded @@ -916,13 +916,13 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ "service-worker": 0, /******/ "webpack_sharing_consume_default_react-dom_react-dom-webpack_sharing_consume_default_react_rea-153fef": 0 /******/ }; -/******/ +/******/ /******/ // no chunk on demand loading -/******/ +/******/ /******/ // no prefetching -/******/ +/******/ /******/ // no preloaded -/******/ +/******/ /******/ var currentUpdatedModulesList; /******/ var waitingUpdateResolves = {}; /******/ function loadUpdateChunk(chunkId, updatedModulesList) { @@ -948,7 +948,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ __webpack_require__.l(url, loadingEnded); /******/ }); /******/ } -/******/ +/******/ /******/ globalThis["webpackHotUpdatesuperset"] = (chunkId, moreModules, runtime) => { /******/ for(var moduleId in moreModules) { /******/ if(__webpack_require__.o(moreModules, moduleId)) { @@ -962,7 +962,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ waitingUpdateResolves[chunkId] = undefined; /******/ } /******/ }; -/******/ +/******/ /******/ var currentUpdateChunks; /******/ var currentUpdate; /******/ var currentUpdateRemovedChunks; @@ -973,7 +973,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ function getAffectedModuleEffects(updateModuleId) { /******/ var outdatedModules = [updateModuleId]; /******/ var outdatedDependencies = {}; -/******/ +/******/ /******/ var queue = outdatedModules.map(function (id) { /******/ return { /******/ chain: [id], @@ -1031,7 +1031,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }); /******/ } /******/ } -/******/ +/******/ /******/ return { /******/ type: "accepted", /******/ moduleId: updateModuleId, @@ -1039,26 +1039,26 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ outdatedDependencies: outdatedDependencies /******/ }; /******/ } -/******/ +/******/ /******/ function addAllToSet(a, b) { /******/ for (var i = 0; i < b.length; i++) { /******/ var item = b[i]; /******/ if (a.indexOf(item) === -1) a.push(item); /******/ } /******/ } -/******/ +/******/ /******/ // at begin all updates modules are outdated /******/ // the "outdated" status can propagate to parents if they don't accept the children /******/ var outdatedDependencies = {}; /******/ var outdatedModules = []; /******/ var appliedUpdate = {}; -/******/ +/******/ /******/ var warnUnexpectedRequire = function warnUnexpectedRequire(module) { /******/ console.warn( /******/ "[HMR] unexpected require(" + module.id + ") to disposed module" /******/ ); /******/ }; -/******/ +/******/ /******/ for (var moduleId in currentUpdate) { /******/ if (__webpack_require__.o(currentUpdate, moduleId)) { /******/ var newModuleFactory = currentUpdate[moduleId]; @@ -1141,7 +1141,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ } /******/ currentUpdate = undefined; -/******/ +/******/ /******/ // Store self accepted outdated modules to require them later by the module system /******/ var outdatedSelfAcceptedModules = []; /******/ for (var j = 0; j < outdatedModules.length; j++) { @@ -1162,41 +1162,41 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }); /******/ } /******/ } -/******/ +/******/ /******/ var moduleOutdatedDependencies; -/******/ +/******/ /******/ return { /******/ dispose: function () { /******/ currentUpdateRemovedChunks.forEach(function (chunkId) { /******/ delete installedChunks[chunkId]; /******/ }); /******/ currentUpdateRemovedChunks = undefined; -/******/ +/******/ /******/ var idx; /******/ var queue = outdatedModules.slice(); /******/ while (queue.length > 0) { /******/ var moduleId = queue.pop(); /******/ var module = __webpack_require__.c[moduleId]; /******/ if (!module) continue; -/******/ +/******/ /******/ var data = {}; -/******/ +/******/ /******/ // Call dispose handlers /******/ var disposeHandlers = module.hot._disposeHandlers; /******/ for (j = 0; j < disposeHandlers.length; j++) { /******/ disposeHandlers[j].call(null, data); /******/ } /******/ __webpack_require__.hmrD[moduleId] = data; -/******/ +/******/ /******/ // disable module (this disables requires from this module) /******/ module.hot.active = false; -/******/ +/******/ /******/ // remove module from cache /******/ delete __webpack_require__.c[moduleId]; -/******/ +/******/ /******/ // when disposing there is no need to call dispose handler /******/ delete outdatedDependencies[moduleId]; -/******/ +/******/ /******/ // remove "parents" references from all children /******/ for (j = 0; j < module.children.length; j++) { /******/ var child = __webpack_require__.c[module.children[j]]; @@ -1207,7 +1207,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ } /******/ } -/******/ +/******/ /******/ // remove outdated dependency from module children /******/ var dependency; /******/ for (var outdatedModuleId in outdatedDependencies) { @@ -1233,12 +1233,12 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ __webpack_require__.m[updateModuleId] = appliedUpdate[updateModuleId]; /******/ } /******/ } -/******/ +/******/ /******/ // run new runtime modules /******/ for (var i = 0; i < currentUpdateRuntime.length; i++) { /******/ currentUpdateRuntime[i](__webpack_require__); /******/ } -/******/ +/******/ /******/ // call accept handlers /******/ for (var outdatedModuleId in outdatedDependencies) { /******/ if (__webpack_require__.o(outdatedDependencies, outdatedModuleId)) { @@ -1309,7 +1309,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ } /******/ } -/******/ +/******/ /******/ var onAccepted = function () { /******/ // Load self accepted modules /******/ for (var o = 0; o < outdatedSelfAcceptedModules.length; o++) { @@ -1353,7 +1353,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ } /******/ }; -/******/ +/******/ /******/ return Promise.all(acceptPromises) /******/ .then(onAccepted) /******/ .then(function () { @@ -1413,7 +1413,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ }; /******/ } /******/ }; -/******/ +/******/ /******/ __webpack_require__.hmrM = () => { /******/ if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API"); /******/ return fetch(__webpack_require__.p + __webpack_require__.hmrF()).then((response) => { @@ -1422,9 +1422,9 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ return response.json(); /******/ }); /******/ }; -/******/ +/******/ /******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); -/******/ +/******/ /******/ // install a JSONP callback for chunk loading /******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { /******/ var [chunkIds, moreModules, runtime] = data; @@ -1449,14 +1449,14 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ } /******/ return __webpack_require__.O(result); /******/ } -/******/ +/******/ /******/ var chunkLoadingGlobal = globalThis["webpackChunksuperset"] = globalThis["webpackChunksuperset"] || []; /******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); /******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); /******/ })(); -/******/ +/******/ /************************************************************************/ -/******/ +/******/ /******/ // module cache are used so entry inlining is disabled /******/ // startup /******/ // Load entry module and return exports @@ -1466,6 +1466,6 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ __webpack_require__.O(undefined, ["vendors","vendors-node_modules_rc-component_color-picker_es_index_js-node_modules_rc-component_mutate-o-484854","webpack_sharing_consume_default_react-dom_react-dom-webpack_sharing_consume_default_react_rea-153fef"], () => (__webpack_require__("./node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry.js"))) /******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["vendors","vendors-node_modules_rc-component_color-picker_es_index_js-node_modules_rc-component_mutate-o-484854","webpack_sharing_consume_default_react-dom_react-dom-webpack_sharing_consume_default_react_rea-153fef"], () => (__webpack_require__("./src/service-worker.ts"))) /******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); -/******/ +/******/ /******/ })() -; +; \ No newline at end of file
