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/05/13 19:15:15 UTC
(superset) 04/04: feat(semantic layers): form for semantic layer with single semantic view
This is an automated email from the ASF dual-hosted git repository.
betodealmeida pushed a commit to branch single-sv
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 06d91c3236f4669098e55d19ca90edd19d5f92bb
Author: Beto Dealmeida <ro...@dealmeida.net>
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 | 32 +++++++-
2 files changed, 118 insertions(+), 5 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..4bdc7bf37d3 100644
--- a/superset-frontend/src/features/semanticViews/AddSemanticViewModal.tsx
+++ b/superset-frontend/src/features/semanticViews/AddSemanticViewModal.tsx
@@ -456,6 +456,30 @@ 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);
+ 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 +535,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')}