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 779883cf3265de1cb6c96f387484db1fa33db4e6 Author: Beto Dealmeida <[email protected]> AuthorDate: Fri Feb 13 18:00:40 2026 -0500 Working --- .../features/semanticLayers/SemanticLayerModal.tsx | 72 +++++++++++++++++++++- superset/static/service-worker.js | 2 +- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx b/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx index 045b102aa38..fbeb984ffa9 100644 --- a/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx +++ b/superset-frontend/src/features/semanticLayers/SemanticLayerModal.tsx @@ -20,6 +20,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { t } from '@apache-superset/core'; import { styled } from '@apache-superset/core/ui'; import { SupersetClient } from '@superset-ui/core'; +import { Input, Spin } from 'antd'; import { Select } from '@superset-ui/core/components'; import { Icons } from '@superset-ui/core/components/Icons'; import { JsonForms, withJsonFormsControlProps } from '@jsonforms/react'; @@ -85,7 +86,53 @@ const constEntry = { renderer: ConstRenderer, }; -const renderers = [...rendererRegistryEntries, passwordEntry, constEntry]; +/** + * Renderer for fields marked `x-dynamic` in the JSON Schema. + * Shows a loading spinner inside the input while the schema is being + * refreshed with dynamic values from the backend. + */ +function DynamicFieldControl(props: ControlProps) { + const { refreshingSchema, formData: cfgData } = props.config ?? {}; + const deps = (props.schema as Record<string, unknown>)?.['x-dependsOn']; + const refreshing = + refreshingSchema && + Array.isArray(deps) && + areDependenciesSatisfied(deps as string[], (cfgData as Record<string, unknown>) ?? {}); + + if (!refreshing) { + return TextControl(props); + } + + const uischema = { + ...props.uischema, + options: { + ...props.uischema.options, + placeholderText: t('Loading...'), + inputProps: { suffix: <Spin size="small" /> }, + }, + }; + return TextControl({ ...props, uischema, enabled: false }); +} +const DynamicFieldRenderer = withJsonFormsControlProps(DynamicFieldControl); +const dynamicFieldEntry = { + tester: rankWith( + 3, + and( + isStringControl, + schemaMatches( + s => (s as Record<string, unknown>)?.['x-dynamic'] === true, + ), + ), + ), + renderer: DynamicFieldRenderer, +}; + +const renderers = [ + ...rendererRegistryEntries, + passwordEntry, + constEntry, + dynamicFieldEntry, +]; type Step = 'type' | 'config'; type ValidationMode = 'ValidateAndHide' | 'ValidateAndShow'; @@ -265,6 +312,7 @@ export default function SemanticLayerModal({ addSuccessToast, }: SemanticLayerModalProps) { const [step, setStep] = useState<Step>('type'); + const [name, setName] = useState(''); const [selectedType, setSelectedType] = useState<string | null>(null); const [types, setTypes] = useState<SemanticLayerType[]>([]); const [loading, setLoading] = useState(false); @@ -274,6 +322,8 @@ export default function SemanticLayerModal({ ); const [formData, setFormData] = useState<Record<string, unknown>>({}); const [saving, setSaving] = useState(false); + const [hasErrors, setHasErrors] = useState(true); + const [refreshingSchema, setRefreshingSchema] = useState(false); const [validationMode, setValidationMode] = useState<ValidationMode>('ValidateAndHide'); const errorsRef = useRef<ErrorObject[]>([]); @@ -308,6 +358,7 @@ export default function SemanticLayerModal({ async (type: string, configuration?: Record<string, unknown>) => { const isInitialFetch = !configuration; if (isInitialFetch) setLoading(true); + else setRefreshingSchema(true); try { const { json } = await SupersetClient.post({ endpoint: '/api/v1/semantic_layer/schema/configuration', @@ -323,6 +374,7 @@ export default function SemanticLayerModal({ } } finally { if (isInitialFetch) setLoading(false); + else setRefreshingSchema(false); } }, [addDangerToast, applySchema], @@ -333,11 +385,14 @@ export default function SemanticLayerModal({ fetchTypes(); } else { setStep('type'); + setName(''); setSelectedType(null); setTypes([]); setConfigSchema(null); setUiSchema(undefined); setFormData({}); + setHasErrors(true); + setRefreshingSchema(false); setValidationMode('ValidateAndHide'); errorsRef.current = []; lastDepSnapshotRef.current = ''; @@ -369,7 +424,7 @@ export default function SemanticLayerModal({ try { await SupersetClient.post({ endpoint: '/api/v1/semantic_layer/', - jsonPayload: { type: selectedType, configuration: formData }, + jsonPayload: { name, type: selectedType, configuration: formData }, }); addSuccessToast(t('Semantic layer created')); onHide(); @@ -421,6 +476,7 @@ export default function SemanticLayerModal({ ({ data, errors }: { data: Record<string, unknown>; errors?: ErrorObject[] }) => { setFormData(data); errorsRef.current = errors ?? []; + setHasErrors(errorsRef.current.length > 0); if ( validationMode === 'ValidateAndShow' && errorsRef.current.length === 0 @@ -448,7 +504,9 @@ export default function SemanticLayerModal({ title={title} icon={<Icons.PlusOutlined />} width={step === 'type' ? MODAL_STANDARD_WIDTH : MODAL_MEDIUM_WIDTH} - saveDisabled={step === 'type' ? !selectedType : saving} + saveDisabled={ + step === 'type' ? !selectedType : saving || !name.trim() || hasErrors + } saveText={step === 'type' ? undefined : t('Create')} saveLoading={saving} contentLoading={loading} @@ -480,6 +538,13 @@ export default function SemanticLayerModal({ <Icons.CaretLeftOutlined iconSize="s" /> {t('Back')} </BackLink> + <ModalFormField label={t('Name')} required> + <Input + value={name} + onChange={e => setName(e.target.value)} + placeholder={t('Name of the semantic layer')} + /> + </ModalFormField> {configSchema && ( <JsonForms schema={configSchema} @@ -487,6 +552,7 @@ export default function SemanticLayerModal({ data={formData} renderers={renderers} cells={cellRegistryEntries} + config={{ refreshingSchema, formData }} validationMode={validationMode} onChange={handleFormChange} /> diff --git a/superset/static/service-worker.js b/superset/static/service-worker.js index d0398cca888..0a942440ae1 100644 --- a/superset/static/service-worker.js +++ b/superset/static/service-worker.js @@ -170,7 +170,7 @@ eval("{/**\n * Licensed to the Apache Software Foundation (ASF) under one\n * or /******/ /******/ /* webpack/runtime/getFullHash */ /******/ (() => { -/******/ __webpack_require__.h = () => ("f2a1d44b322dddcb4e87") +/******/ __webpack_require__.h = () => ("669bfdb2217bd9a71a0c") /******/ })(); /******/ /******/ /* webpack/runtime/harmony module decorator */
