This is an automated email from the ASF dual-hosted git repository.

betodealmeida pushed a commit to branch oauth-during-db-creation
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 7a8d2c585f6bfb92072bd074a9bee238eefdd7bc
Author: Beto Dealmeida <[email protected]>
AuthorDate: Wed May 13 15:14:51 2026 -0400

    feat(semantic layers): form for semantic layer with single semantic view
---
 .../features/semanticLayers/jsonFormsHelpers.tsx   | 91 +++++++++++++++++++++-
 .../semanticViews/AddSemanticViewModal.tsx         | 38 ++++++++-
 2 files changed, 123 insertions(+), 6 deletions(-)

diff --git a/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx 
b/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx
index 85c7b891dbc..7368f61ceec 100644
--- a/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx
+++ b/superset-frontend/src/features/semanticLayers/jsonFormsHelpers.tsx
@@ -255,14 +255,98 @@ const EnumNamesRenderer = 
withJsonFormsControlProps(EnumNamesControl);
 const enumNamesEntry = {
   // Rank 5: higher than the default string renderer (2–3) so this fires
   // whenever x-enumNames is present, regardless of the underlying type.
+  // Array-of-enum schemas are handled by ``multiEnumEntry`` below — this
+  // renderer only targets scalar string/number controls.
   tester: rankWith(
     5,
+    and(
+      schemaMatches(s => {
+        const names = (s as Record<string, unknown>)['x-enumNames'];
+        return Array.isArray(names) && (names as unknown[]).length > 0;
+      }),
+      schemaMatches(
+        s => (s as Record<string, unknown>)?.type !== 'array',
+      ),
+    ),
+  ),
+  renderer: EnumNamesRenderer,
+};
+
+/**
+ * Renderer for ``{type: 'array', items: {enum: [...]}}`` schemas.  Renders
+ * a single Antd Select with ``mode="multiple"`` (tag-style multi-select),
+ * matching the natural expectation of a "pick several from a list" control.
+ *
+ * Without this, the default ``PrimitiveArrayControl`` from the upstream
+ * library renders an "Add …" button that creates one single-select per
+ * element — visually wrong for an enum multi-select and unable to display
+ * ``items.x-enumNames`` labels.
+ *
+ * The renderer is dynamic-aware: when the host form is refreshing the
+ * schema (e.g. compatible options narrowing as the user picks), the Select
+ * shows a loading indicator without becoming disabled, so the user can
+ * continue editing while options refresh.
+ */
+function MultiEnumControl(props: ControlProps) {
+  const { refreshingSchema } = props.config ?? {};
+  const arraySchema = props.schema as Record<string, unknown>;
+  const itemsSchema =
+    (arraySchema.items as Record<string, unknown>) ??
+    ({} as Record<string, unknown>);
+
+  const enumValues = (itemsSchema.enum as unknown[]) ?? [];
+  const enumNames =
+    (itemsSchema['x-enumNames'] as string[]) ?? enumValues.map(String);
+
+  const options = enumValues.map((value, index) => ({
+    value: value as string | number,
+    label: enumNames[index] ?? String(value),
+  }));
+
+  const value = Array.isArray(props.data) ? (props.data as unknown[]) : [];
+
+  const tooltip = (props.uischema?.options as Record<string, unknown>)
+    ?.tooltip as string | undefined;
+
+  return (
+    <Form.Item label={props.label} tooltip={tooltip}>
+      <Select
+        mode="multiple"
+        value={value as (string | number)[]}
+        onChange={next => props.handleChange(props.path, next)}
+        options={options}
+        style={{ width: '100%' }}
+        disabled={!props.enabled}
+        loading={!!refreshingSchema}
+        allowClear
+        optionFilterProp="label"
+        placeholder={
+          (props.uischema?.options as Record<string, unknown>)
+            ?.placeholderText as string | undefined
+        }
+      />
+    </Form.Item>
+  );
+}
+const MultiEnumRenderer = withJsonFormsControlProps(MultiEnumControl);
+const multiEnumEntry = {
+  // Rank 35: must beat upstream ``PrimitiveArrayRenderer`` (rank 30) so an
+  // ``array``/``items.enum`` schema renders as one Antd multi-select tag
+  // box instead of the "Add" repeater pattern that PrimitiveArray uses.
+  tester: rankWith(
+    35,
     schemaMatches(s => {
-      const names = (s as Record<string, unknown>)['x-enumNames'];
-      return Array.isArray(names) && (names as unknown[]).length > 0;
+      const schema = s as Record<string, unknown>;
+      if (schema?.type !== 'array') return false;
+      const items = schema.items as Record<string, unknown> | undefined;
+      return (
+        !!items &&
+        Array.isArray(items.enum) &&
+        (items.enum as unknown[]).length > 0
+      );
     }),
   ),
-  renderer: EnumNamesRenderer,
+  renderer: MultiEnumRenderer,
 };
 
 export const renderers = [
@@ -271,6 +355,7 @@ export const renderers = [
   constEntry,
   readOnlyEntry,
   enumNamesEntry,
+  multiEnumEntry,
   dynamicFieldEntry,
 ];
 
diff --git 
a/superset-frontend/src/features/semanticViews/AddSemanticViewModal.tsx 
b/superset-frontend/src/features/semanticViews/AddSemanticViewModal.tsx
index e85900c0b92..14bb9e42191 100644
--- a/superset-frontend/src/features/semanticViews/AddSemanticViewModal.tsx
+++ b/superset-frontend/src/features/semanticViews/AddSemanticViewModal.tsx
@@ -254,7 +254,9 @@ export default function AddSemanticViewModal({
           !schema?.properties ||
           Object.keys(schema.properties).length === 0
         ) {
-          // No runtime config needed — fetch views right away
+          // Preserve top-level runtime metadata (e.g. x-singleView) even when
+          // there are no form fields, then fetch views right away.
+          applyRuntimeSchema(schema);
           fetchViews(uuid, {}, gen);
         } else {
           applyRuntimeSchema(schema);
@@ -456,6 +458,32 @@ export default function AddSemanticViewModal({
   const viewsDisabled =
     loadingViews || (!loadingViews && availableViews.length === 0);
 
+  // When ``x-singleView: true`` the runtime form fully describes a single
+  // semantic view (e.g. a MetricFlow cube).  Hide the picker and auto-select
+  // whatever ``get_semantic_views`` returned so the Add button can fire
+  // without an extra user click.
+  const singleViewMode =
+    (runtimeSchema as Record<string, unknown> | null)?.['x-singleView'] ===
+    true;
+
+  useEffect(() => {
+    if (!singleViewMode) return;
+    const namesToAdd = availableViews
+      .filter(v => !v.already_added)
+      .map(v => v.name)
+      .sort((a, b) => a.localeCompare(b))
+      .slice(0, 1);
+    setSelectedViewNames(prev => {
+      if (
+        prev.length === namesToAdd.length &&
+        prev.every((n, i) => n === namesToAdd[i])
+      ) {
+        return prev;
+      }
+      return namesToAdd;
+    });
+  }, [singleViewMode, availableViews]);
+
   return (
     <StandardModal
       show={show}
@@ -511,8 +539,12 @@ export default function AddSemanticViewModal({
           </>
         )}
 
-        {/* Semantic Views — always visible once a layer is selected */}
-        {selectedLayerUuid && !loadingRuntime && (
+        {/* Semantic Views — always visible once a layer is selected, unless
+            the runtime schema declares ``x-singleView: true``: extensions
+            (e.g. MetricFlow cubes) whose runtime form fully describes a
+            single view set that flag so the picker disappears and the
+            view is auto-selected when ``get_semantic_views`` returns it. */}
+        {selectedLayerUuid && !loadingRuntime && !singleViewMode && (
           <ModalFormField label={t('Semantic Views')}>
             <Select
               ariaLabel={t('Semantic views')}

Reply via email to