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 2025/07/17 22:31:12 UTC
(superset) branch dbt-metricflow updated (83c8c4d7e5 -> 1d82e85a55)
This is an automated email from the ASF dual-hosted git repository.
beto pushed a change to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
from 83c8c4d7e5 Works
new de13b1cf44 Disable instead of hide
new b27d6dc9b6 Checkpoint
new 25b06dbedb Checkpoint
new b17ea1c875 Checkpoint
new 81cf3ca024 Checkpoint
new 3430d69972 Checkpoint
new 1d82e85a55 More charts
The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../src/shared-controls/dndControls.tsx | 141 +++++++--
.../DatasourcePanelDragOption/index.tsx | 24 +-
.../DatasourcePanel/DatasourcePanelItem.tsx | 1 +
.../explore/components/DatasourcePanel/types.ts | 2 +
.../controls/SemanticLayerVerification.tsx | 323 +++++++++++++++++----
.../components/controls/withAsyncVerification.tsx | 40 ++-
6 files changed, 427 insertions(+), 104 deletions(-)
(superset) 02/07: Checkpoint
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit b27d6dc9b6d5fe7f9ab99c05eb8cf8663b117800
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jul 17 16:13:01 2025 -0400
Checkpoint
---
.../src/shared-controls/dndControls.tsx | 4 +--
.../controls/SemanticLayerVerification.tsx | 32 +++++++++++++-----
.../components/controls/withAsyncVerification.tsx | 39 ++++++++++++++--------
3 files changed, 52 insertions(+), 23 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
index 3d9077b427..120a8cbbd8 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
@@ -100,8 +100,8 @@ function enhanceControlWithSemanticLayer(
if (withAsyncVerification) {
const verificationFn =
verificationType === 'metrics'
- ? createMetricsVerification()
- : createColumnsVerification();
+ ? createMetricsVerification(controlName)
+ : createColumnsVerification(controlName);
return withAsyncVerification({
baseControl: baseControl.type,
diff --git a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
index e9d0192503..850be4e11a 100644
--- a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
@@ -132,17 +132,25 @@ async function callValidationAPI(
/**
* Create verification function for metrics controls
*/
-export function createMetricsVerification(): AsyncVerify {
+export function createMetricsVerification(controlName?: string): AsyncVerify {
return async (props: ControlPropsWithExtras) => {
- const { datasource, form_data, savedMetrics = [], actions } = props;
+ const { datasource, form_data, savedMetrics = [], actions, value } = props;
// Only verify for semantic layer datasources
if (!supportsSemanticLayerVerification(datasource as Dataset)) {
return null;
}
- // Extract current form fields
- const queryFields = collectQueryFields(form_data || {});
+ // Create updated form data with the current value
+ const updatedFormData = { ...form_data };
+
+ // Update the appropriate field based on the control name
+ if (value !== undefined && controlName) {
+ updatedFormData[controlName] = value;
+ }
+
+ // Extract current form fields from updated form data
+ const queryFields = collectQueryFields(updatedFormData || {});
// Call validation API
const validationResult = await callValidationAPI(
@@ -204,17 +212,25 @@ export function createMetricsVerification(): AsyncVerify {
/**
* Create verification function for dimensions controls
*/
-export function createColumnsVerification(): AsyncVerify {
+export function createColumnsVerification(controlName?: string): AsyncVerify {
return async (props: ControlPropsWithExtras) => {
- const { datasource, form_data, options = [], actions } = props;
+ const { datasource, form_data, options = [], actions, value } = props;
// Only verify for semantic layer datasources
if (!supportsSemanticLayerVerification(datasource as Dataset)) {
return null;
}
- // Extract current form fields
- const queryFields = collectQueryFields(form_data || {});
+ // Create updated form data with the current value
+ const updatedFormData = { ...form_data };
+
+ // Update the appropriate field based on the control name
+ if (value !== undefined && controlName) {
+ updatedFormData[controlName] = value;
+ }
+
+ // Extract current form fields from updated form data
+ const queryFields = collectQueryFields(updatedFormData || {});
// Call validation API
const validationResult = await callValidationAPI(
diff --git a/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx b/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx
index 5ed60aea4d..852ac70a30 100644
--- a/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx
@@ -145,6 +145,7 @@ export default function withAsyncVerification({
const [verifiedProps, setVerifiedProps] = useState({});
const [isLoading, setIsLoading] = useState<boolean>(initialIsLoading);
const { addWarningToast } = restProps.actions;
+ const verificationTriggeredByChange = useRef(false);
// memoize `restProps`, so that verification only triggers when material
// props are actually updated.
@@ -153,19 +154,6 @@ export default function withAsyncVerification({
otherProps = otherPropsRef.current = restProps;
}
- const handleChange = useCallback(
- (value: JsonValue) => {
- // the default onChange handler, triggers the `setControlValue` action
- if (basicOnChange) {
- basicOnChange(value);
- }
- if (onChange) {
- onChange(value, { ...otherProps, ...verifiedProps });
- }
- },
- [basicOnChange, otherProps, verifiedProps],
- );
-
const verifyProps = useEffectEvent(
(verifyFunc: AsyncVerify, props: typeof otherProps) => {
if (showLoadingState) {
@@ -202,8 +190,33 @@ export default function withAsyncVerification({
},
);
+ const handleChange = useCallback(
+ (value: JsonValue) => {
+ // the default onChange handler, triggers the `setControlValue` action
+ if (basicOnChange) {
+ basicOnChange(value);
+ }
+ if (onChange) {
+ onChange(value, { ...otherProps, ...verifiedProps });
+ }
+
+ // Trigger verification with the new value if verification is enabled
+ if (needAsyncVerification && verify) {
+ verificationTriggeredByChange.current = true;
+ const propsWithNewValue = { ...otherProps, ...verifiedProps, value };
+ verifyProps(verify, propsWithNewValue);
+ }
+ },
+ [basicOnChange, otherProps, verifiedProps, needAsyncVerification, verify, verifyProps],
+ );
+
useEffect(() => {
if (needAsyncVerification && verify) {
+ // Skip verification if it was just triggered by onChange
+ if (verificationTriggeredByChange.current) {
+ verificationTriggeredByChange.current = false;
+ return;
+ }
verifyProps(verify, otherProps);
}
}, [needAsyncVerification, verify, otherProps, verifyProps]);
(superset) 06/07: Checkpoint
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 3430d69972173cfc74f86569851b4b50879b6b35
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jul 17 18:25:17 2025 -0400
Checkpoint
---
.../components/controls/SemanticLayerVerification.tsx | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
index c9b0eba3f3..303d22af3f 100644
--- a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
@@ -28,6 +28,7 @@ export function collectQueryFields(formData: any): {
dimensions: string[];
metrics: string[];
} {
+ console.log('collectQueryFields input:', formData);
const dimensions: string[] = [];
const metrics: string[] = [];
@@ -81,10 +82,13 @@ export function collectQueryFields(formData: any): {
: (metric as any)?.metric_name || String(metric),
);
- return {
+ const result = {
dimensions: [...new Set(cleanDimensions)], // Remove duplicates
metrics: [...new Set(cleanMetrics)], // Remove duplicates
};
+
+ console.log('collectQueryFields output:', result);
+ return result;
}
/**
@@ -170,7 +174,8 @@ export function createMetricsVerification(controlName?: string): AsyncVerify {
const updatedFormData = { ...form_data };
// Update the appropriate field based on the control name
- if (value !== undefined && controlName) {
+ // Handle both addition and removal of values (including empty arrays)
+ if (controlName) {
updatedFormData[controlName] = value;
}
@@ -183,6 +188,9 @@ export function createMetricsVerification(controlName?: string): AsyncVerify {
originalFormData: form_data,
updatedFormData,
value,
+ valueType: typeof value,
+ isArray: Array.isArray(value),
+ valueLength: Array.isArray(value) ? value.length : 'N/A',
dimensions: queryFields.dimensions,
metrics: queryFields.metrics,
});
@@ -279,7 +287,8 @@ export function createColumnsVerification(controlName?: string): AsyncVerify {
const updatedFormData = { ...form_data };
// Update the appropriate field based on the control name
- if (value !== undefined && controlName) {
+ // Handle both addition and removal of values (including empty arrays)
+ if (controlName) {
updatedFormData[controlName] = value;
}
@@ -292,6 +301,9 @@ export function createColumnsVerification(controlName?: string): AsyncVerify {
originalFormData: form_data,
updatedFormData,
value,
+ valueType: typeof value,
+ isArray: Array.isArray(value),
+ valueLength: Array.isArray(value) ? value.length : 'N/A',
dimensions: queryFields.dimensions,
metrics: queryFields.metrics,
});
(superset) 01/07: Disable instead of hide
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit de13b1cf44a156da691a7a834e05925b85604828
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jul 17 15:52:51 2025 -0400
Disable instead of hide
---
.../src/shared-controls/dndControls.tsx | 4 +-
.../DatasourcePanelDragOption/index.tsx | 24 ++++--
.../DatasourcePanel/DatasourcePanelItem.tsx | 1 +
.../explore/components/DatasourcePanel/types.ts | 2 +
.../controls/SemanticLayerVerification.tsx | 89 ++++++++++++----------
5 files changed, 70 insertions(+), 50 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
index 03cb0fefbd..3d9077b427 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
@@ -123,7 +123,9 @@ function enhanceControlWithSemanticLayer(
// Only add semantic layer props if utilities are available
if (withAsyncVerification) {
- const needsVerification = needsSemanticLayerVerification(state.datasource);
+ const needsVerification = needsSemanticLayerVerification(
+ state.datasource,
+ );
return {
...originalProps,
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelDragOption/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelDragOption/index.tsx
index 533b6c4bb1..d4054dc5aa 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelDragOption/index.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelDragOption/index.tsx
@@ -29,8 +29,8 @@ import { Icons } from '@superset-ui/core/components/Icons';
import { DatasourcePanelDndItem } from '../types';
-const DatasourceItemContainer = styled.div`
- ${({ theme }) => css`
+const DatasourceItemContainer = styled.div<{ isDisabled?: boolean }>`
+ ${({ theme, isDisabled }) => css`
display: flex;
align-items: center;
justify-content: space-between;
@@ -40,12 +40,16 @@ const DatasourceItemContainer = styled.div`
// hack to make the drag preview image corners rounded
transform: translate(0, 0);
- color: ${theme.colorText};
+ color: ${isDisabled ? theme.colorTextSecondary : theme.colorText};
background-color: ${theme.colorBgLayout};
border-radius: 4px;
+ opacity: ${isDisabled ? 0.5 : 1};
+ cursor: ${isDisabled ? 'not-allowed' : 'grab'};
&:hover {
- background-color: ${theme.colorPrimaryBgHover};
+ background-color: ${isDisabled
+ ? theme.colorBgLayout
+ : theme.colorPrimaryBgHover};
}
> div {
@@ -58,6 +62,7 @@ const DatasourceItemContainer = styled.div`
interface DatasourcePanelDragOptionProps extends DatasourcePanelDndItem {
labelRef?: RefObject<any>;
showTooltip?: boolean;
+ isDisabled?: boolean;
}
type MetricOption = Omit<Metric, 'id'> & {
@@ -67,7 +72,7 @@ type MetricOption = Omit<Metric, 'id'> & {
export default function DatasourcePanelDragOption(
props: DatasourcePanelDragOptionProps,
) {
- const { labelRef, showTooltip, type, value } = props;
+ const { labelRef, showTooltip, type, value, isDisabled } = props;
const [{ isDragging }, drag] = useDrag({
item: {
value: props.value,
@@ -76,6 +81,7 @@ export default function DatasourcePanelDragOption(
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
+ canDrag: !isDisabled,
});
const optionProps = {
@@ -85,13 +91,17 @@ export default function DatasourcePanelDragOption(
};
return (
- <DatasourceItemContainer data-test="DatasourcePanelDragOption" ref={drag}>
+ <DatasourceItemContainer
+ data-test="DatasourcePanelDragOption"
+ ref={isDisabled ? undefined : drag}
+ isDisabled={isDisabled}
+ >
{type === DndItemType.Column ? (
<StyledColumnOption column={value as ColumnMeta} {...optionProps} />
) : (
<StyledMetricOption metric={value as MetricOption} {...optionProps} />
)}
- <Icons.Drag />
+ {isDisabled ? <Icons.LockOutlined /> : <Icons.Drag />}
</DatasourceItemContainer>
);
}
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx
index 115f5b729c..93a215910c 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx
@@ -244,6 +244,7 @@ const DatasourcePanelItem = ({
? DndItemType.Column
: DndItemType.Metric
}
+ isDisabled={item.item.isDisabled}
/>
</LabelWrapper>
)}
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/types.ts b/superset-frontend/src/explore/components/DatasourcePanel/types.ts
index 0639f3c827..73e88cf7fc 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/types.ts
+++ b/superset-frontend/src/explore/components/DatasourcePanel/types.ts
@@ -62,10 +62,12 @@ export type DatasourceFolder = {
export type MetricItem = Metric & {
type: 'metric';
+ isDisabled?: boolean;
};
export type ColumnItem = DatasourcePanelColumn & {
type: 'column';
+ isDisabled?: boolean;
};
export type FolderItem = MetricItem | ColumnItem;
diff --git a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
index e618ee8f92..e9d0192503 100644
--- a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
@@ -94,7 +94,7 @@ function supportsSemanticLayerVerification(datasource: Dataset): boolean {
if (!datasource || !('database' in datasource) || !datasource.database) {
return false;
}
-
+
const database = datasource.database as any;
return Boolean(database.engine_information?.supports_dynamic_columns);
}
@@ -161,40 +161,42 @@ export function createMetricsVerification(): AsyncVerify {
validMetricNames.has(metric.metric_name || metric),
);
- // Also filter the datasource metrics (for left panel) if this is a Dataset
+ // Mark datasource metrics and columns as disabled if invalid (for left panel)
const dataset = datasource as Dataset;
- let filteredDatasourceMetrics = dataset.metrics;
- let filteredDatasourceColumns = dataset.columns;
-
+ let updatedDatasourceMetrics = dataset.metrics;
+ let updatedDatasourceColumns = dataset.columns;
+
if (dataset.metrics) {
- filteredDatasourceMetrics = dataset.metrics.filter((metric: any) =>
- validMetricNames.has(metric.metric_name || metric),
- );
+ updatedDatasourceMetrics = dataset.metrics.map((metric: any) => ({
+ ...metric,
+ isDisabled: !validMetricNames.has(metric.metric_name || metric),
+ }));
}
-
- // Also filter columns using the same validation result
+
+ // Also update columns using the same validation result
const validDimensionNames = new Set(validationResult.dimensions);
if (dataset.columns) {
- filteredDatasourceColumns = dataset.columns.filter((column: any) =>
- validDimensionNames.has(column.column_name || column),
- );
+ updatedDatasourceColumns = dataset.columns.map((column: any) => ({
+ ...column,
+ isDisabled: !validDimensionNames.has(column.column_name || column),
+ }));
}
- // Create filtered datasource for left panel
- const filteredDatasource = {
+ // Create updated datasource for left panel
+ const updatedDatasource = {
...dataset,
- metrics: filteredDatasourceMetrics,
- columns: filteredDatasourceColumns,
+ metrics: updatedDatasourceMetrics,
+ columns: updatedDatasourceColumns,
};
// Update the Redux store's datasource to affect the left panel
if (actions && typeof actions.syncDatasourceMetadata === 'function') {
- actions.syncDatasourceMetadata(filteredDatasource);
+ actions.syncDatasourceMetadata(updatedDatasource);
}
return {
savedMetrics: filteredSavedMetrics,
- datasource: filteredDatasource,
+ datasource: updatedDatasource,
};
};
}
@@ -225,46 +227,49 @@ export function createColumnsVerification(): AsyncVerify {
return null;
}
- // Filter dimension options to only include valid ones
+ // Mark dimension options as disabled if invalid
const validDimensionNames = new Set(validationResult.dimensions);
- const filteredOptions = options.filter((option: any) =>
- validDimensionNames.has(option.column_name || option),
- );
+ const updatedOptions = options.map((option: any) => ({
+ ...option,
+ isDisabled: !validDimensionNames.has(option.column_name || option),
+ }));
- // Also filter the datasource columns (for left panel) if this is a Dataset
+ // Mark datasource columns and metrics as disabled if invalid (for left panel)
const dataset = datasource as Dataset;
- let filteredDatasourceColumns = dataset.columns;
- let filteredDatasourceMetrics = dataset.metrics;
-
+ let updatedDatasourceColumns = dataset.columns;
+ let updatedDatasourceMetrics = dataset.metrics;
+
if (dataset.columns) {
- filteredDatasourceColumns = dataset.columns.filter((column: any) =>
- validDimensionNames.has(column.column_name || column),
- );
+ updatedDatasourceColumns = dataset.columns.map((column: any) => ({
+ ...column,
+ isDisabled: !validDimensionNames.has(column.column_name || column),
+ }));
}
-
- // Also filter metrics using the same validation result
+
+ // Also update metrics using the same validation result
const validMetricNames = new Set(validationResult.metrics);
if (dataset.metrics) {
- filteredDatasourceMetrics = dataset.metrics.filter((metric: any) =>
- validMetricNames.has(metric.metric_name || metric),
- );
+ updatedDatasourceMetrics = dataset.metrics.map((metric: any) => ({
+ ...metric,
+ isDisabled: !validMetricNames.has(metric.metric_name || metric),
+ }));
}
- // Create filtered datasource for left panel
- const filteredDatasource = {
+ // Create updated datasource for left panel
+ const updatedDatasource = {
...dataset,
- columns: filteredDatasourceColumns,
- metrics: filteredDatasourceMetrics,
+ columns: updatedDatasourceColumns,
+ metrics: updatedDatasourceMetrics,
};
// Update the Redux store's datasource to affect the left panel
if (actions && typeof actions.syncDatasourceMetadata === 'function') {
- actions.syncDatasourceMetadata(filteredDatasource);
+ actions.syncDatasourceMetadata(updatedDatasource);
}
return {
- options: filteredOptions,
- datasource: filteredDatasource,
+ options: updatedOptions,
+ datasource: updatedDatasource,
};
};
}
(superset) 07/07: More charts
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 1d82e85a557d4cc19fa2906bc021b64285315acf
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jul 17 18:31:03 2025 -0400
More charts
---
.../src/shared-controls/dndControls.tsx | 70 +++++++++++++++++-----
.../controls/SemanticLayerVerification.tsx | 56 +++++++++++++++++
2 files changed, 112 insertions(+), 14 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
index 10001a954c..4604d49d4c 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
@@ -270,8 +270,8 @@ export const dndColumnsControl = enhanceControlWithSemanticLayer(
'columns',
);
-export const dndSeriesControl: typeof dndGroupByControl = {
- ...dndGroupByControl,
+const baseDndSeriesControl: typeof baseDndGroupByControl = {
+ ...baseDndGroupByControl,
label: t('Dimension'),
multi: false,
default: null,
@@ -281,8 +281,14 @@ export const dndSeriesControl: typeof dndGroupByControl = {
),
};
-export const dndEntityControl: typeof dndGroupByControl = {
- ...dndGroupByControl,
+export const dndSeriesControl = enhanceControlWithSemanticLayer(
+ baseDndSeriesControl,
+ 'series',
+ 'columns',
+);
+
+const baseDndEntityControl: typeof baseDndGroupByControl = {
+ ...baseDndGroupByControl,
label: t('Entity'),
default: null,
multi: false,
@@ -290,6 +296,12 @@ export const dndEntityControl: typeof dndGroupByControl = {
description: t('This defines the element to be plotted on the chart'),
};
+export const dndEntityControl = enhanceControlWithSemanticLayer(
+ baseDndEntityControl,
+ 'entity',
+ 'columns',
+);
+
export const dndAdhocFilterControl: SharedControlConfig<
'DndFilterSelect' | 'AdhocFilterControl'
> = {
@@ -399,15 +411,21 @@ export const dndSortByControl: SharedControlConfig<
}),
};
-export const dndSizeControl: typeof dndAdhocMetricControl = {
- ...dndAdhocMetricControl,
+const baseDndSizeControl: typeof baseDndAdhocMetricControl = {
+ ...baseDndAdhocMetricControl,
label: t('Bubble Size'),
description: t('Metric used to calculate bubble size'),
default: null,
};
-export const dndXControl: typeof dndAdhocMetricControl = {
- ...dndAdhocMetricControl,
+export const dndSizeControl = enhanceControlWithSemanticLayer(
+ baseDndSizeControl,
+ 'size',
+ 'metrics',
+);
+
+const baseDndXControl: typeof baseDndAdhocMetricControl = {
+ ...baseDndAdhocMetricControl,
label: t('X Axis'),
description: t(
"The dataset column/metric that returns the values on your chart's x-axis.",
@@ -415,8 +433,14 @@ export const dndXControl: typeof dndAdhocMetricControl = {
default: null,
};
-export const dndYControl: typeof dndAdhocMetricControl = {
- ...dndAdhocMetricControl,
+export const dndXControl = enhanceControlWithSemanticLayer(
+ baseDndXControl,
+ 'x',
+ 'metrics',
+);
+
+const baseDndYControl: typeof baseDndAdhocMetricControl = {
+ ...baseDndAdhocMetricControl,
label: t('Y Axis'),
description: t(
"The dataset column/metric that returns the values on your chart's y-axis.",
@@ -424,14 +448,26 @@ export const dndYControl: typeof dndAdhocMetricControl = {
default: null,
};
-export const dndSecondaryMetricControl: typeof dndAdhocMetricControl = {
- ...dndAdhocMetricControl,
+export const dndYControl = enhanceControlWithSemanticLayer(
+ baseDndYControl,
+ 'y',
+ 'metrics',
+);
+
+const baseDndSecondaryMetricControl: typeof baseDndAdhocMetricControl = {
+ ...baseDndAdhocMetricControl,
label: t('Color Metric'),
default: null,
validators: [],
description: t('A metric to use for color'),
};
+export const dndSecondaryMetricControl = enhanceControlWithSemanticLayer(
+ baseDndSecondaryMetricControl,
+ 'secondary_metric',
+ 'metrics',
+);
+
export const dndGranularitySqlaControl: typeof dndSeriesControl = {
...dndSeriesControl,
...temporalColumnMixin,
@@ -452,7 +488,13 @@ export const dndGranularitySqlaControl: typeof dndSeriesControl = {
valueKey: 'column_name',
};
-export const dndXAxisControl: typeof dndGroupByControl = {
- ...dndGroupByControl,
+const baseDndXAxisControl: typeof baseDndGroupByControl = {
+ ...baseDndGroupByControl,
...xAxisMixin,
};
+
+export const dndXAxisControl = enhanceControlWithSemanticLayer(
+ baseDndXAxisControl,
+ 'x_axis',
+ 'columns',
+);
diff --git a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
index 303d22af3f..3ef71373dd 100644
--- a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
@@ -47,6 +47,34 @@ export function collectQueryFields(formData: any): {
: [formData.columns]),
);
}
+ if (formData.series_columns) {
+ dimensions.push(
+ ...(Array.isArray(formData.series_columns)
+ ? formData.series_columns
+ : [formData.series_columns]),
+ );
+ }
+ if (formData.series) {
+ dimensions.push(
+ ...(Array.isArray(formData.series)
+ ? formData.series
+ : [formData.series]),
+ );
+ }
+ if (formData.entity) {
+ dimensions.push(
+ ...(Array.isArray(formData.entity)
+ ? formData.entity
+ : [formData.entity]),
+ );
+ }
+ if (formData.x_axis) {
+ dimensions.push(
+ ...(Array.isArray(formData.x_axis)
+ ? formData.x_axis
+ : [formData.x_axis]),
+ );
+ }
// Extract metrics from various field types
if (formData.metrics) {
@@ -59,6 +87,9 @@ export function collectQueryFields(formData: any): {
if (formData.metric) {
metrics.push(formData.metric);
}
+ if (formData.metric_2) {
+ metrics.push(formData.metric_2);
+ }
if (formData.percent_metrics) {
metrics.push(
...(Array.isArray(formData.percent_metrics)
@@ -66,6 +97,21 @@ export function collectQueryFields(formData: any): {
: [formData.percent_metrics]),
);
}
+ if (formData.timeseries_limit_metric) {
+ metrics.push(formData.timeseries_limit_metric);
+ }
+ if (formData.x) {
+ metrics.push(formData.x);
+ }
+ if (formData.y) {
+ metrics.push(formData.y);
+ }
+ if (formData.size) {
+ metrics.push(formData.size);
+ }
+ if (formData.secondary_metric) {
+ metrics.push(formData.secondary_metric);
+ }
// Filter out null/undefined values and convert objects to strings if needed
const cleanDimensions = dimensions
@@ -456,12 +502,22 @@ export function createSemanticLayerOnChange(
* Get list of control fields that should trigger re-rendering
*/
export const SEMANTIC_LAYER_CONTROL_FIELDS = [
+ // Metric controls
'metrics',
'metric',
'metric_2',
'percent_metrics',
'timeseries_limit_metric',
+ 'x',
+ 'y',
+ 'size',
+ 'secondary_metric',
+
+ // Dimension controls
'groupby',
'columns',
'series_columns',
+ 'series',
+ 'entity',
+ 'x_axis',
];
(superset) 05/07: Checkpoint
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 81cf3ca0242393447e2e34d6c96926930941a139
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jul 17 18:21:34 2025 -0400
Checkpoint
---
.../components/controls/SemanticLayerVerification.tsx | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
index 7b0610f27d..c9b0eba3f3 100644
--- a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
@@ -141,9 +141,10 @@ async function callValidationAPI(
apiCallCache.set(cacheKey, apiPromise);
// Remove from cache after a short delay to allow for immediate duplicates
+ // Increased timeout to handle rapid successive calls from multiple controls
setTimeout(() => {
apiCallCache.delete(cacheKey);
- }, 100);
+ }, 500);
return await apiPromise;
} catch (error) {
@@ -177,6 +178,15 @@ export function createMetricsVerification(controlName?: string): AsyncVerify {
const queryFields = collectQueryFields(updatedFormData || {});
// Call validation API
+ console.log('Metrics verification API call:', {
+ controlName,
+ originalFormData: form_data,
+ updatedFormData,
+ value,
+ dimensions: queryFields.dimensions,
+ metrics: queryFields.metrics,
+ });
+
const validationResult = await callValidationAPI(
datasource as Dataset,
queryFields.dimensions,
@@ -279,6 +289,9 @@ export function createColumnsVerification(controlName?: string): AsyncVerify {
// Call validation API
console.log('Columns verification API call:', {
controlName,
+ originalFormData: form_data,
+ updatedFormData,
+ value,
dimensions: queryFields.dimensions,
metrics: queryFields.metrics,
});
(superset) 03/07: Checkpoint
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 25b06dbedbde56df324d7e145fcdbbb3cff21cbb
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jul 17 16:40:45 2025 -0400
Checkpoint
---
.../src/shared-controls/dndControls.tsx | 67 +++++++++++++++++-----
.../controls/SemanticLayerVerification.tsx | 31 +++++++++-
.../components/controls/withAsyncVerification.tsx | 1 +
3 files changed, 81 insertions(+), 18 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
index 120a8cbbd8..10001a954c 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx
@@ -54,6 +54,12 @@ let createColumnsVerification: any = null;
let createSemanticLayerOnChange: any = null;
let SEMANTIC_LAYER_CONTROL_FIELDS: any = null;
+// Notification system for when utilities are set
+const enhancedControls: Array<{
+ controlName: string;
+ invalidateCache: () => void;
+}> = [];
+
// Export function to set semantic layer utilities from main app
export function setSemanticLayerUtilities(utilities: {
withAsyncVerification: any;
@@ -69,6 +75,11 @@ export function setSemanticLayerUtilities(utilities: {
createSemanticLayerOnChange,
SEMANTIC_LAYER_CONTROL_FIELDS,
} = utilities);
+
+ // Notify all enhanced controls that utilities are now available
+ enhancedControls.forEach(control => {
+ control.invalidateCache();
+ });
}
/**
@@ -92,27 +103,52 @@ function enhanceControlWithSemanticLayer(
controlName: string,
verificationType: 'metrics' | 'columns',
) {
+ // Cache for the enhanced control type
+ let cachedEnhancedType: any = null;
+ let utilitiesWereAvailable = false;
+
+ // Register with notification system
+ enhancedControls.push({
+ controlName,
+ invalidateCache: () => {
+ cachedEnhancedType = null;
+ utilitiesWereAvailable = false;
+ }
+ });
+
// Return a control that will be enhanced at runtime if utilities are available
return {
...baseControl,
// Override the type to use a function that checks for enhancement at runtime
get type() {
- if (withAsyncVerification) {
- const verificationFn =
- verificationType === 'metrics'
- ? createMetricsVerification(controlName)
- : createColumnsVerification(controlName);
-
- return withAsyncVerification({
- baseControl: baseControl.type,
- verify: verificationFn,
- onChange: createSemanticLayerOnChange(
- controlName,
- SEMANTIC_LAYER_CONTROL_FIELDS,
- ),
- showLoadingState: true,
- });
+ // Check if utilities became available since last call
+ const utilitiesAvailableNow = !!withAsyncVerification;
+
+ if (utilitiesAvailableNow) {
+ // If utilities just became available or we haven't cached yet, create enhanced control
+ if (!utilitiesWereAvailable || !cachedEnhancedType) {
+ const verificationFn =
+ verificationType === 'metrics'
+ ? createMetricsVerification(controlName)
+ : createColumnsVerification(controlName);
+
+ cachedEnhancedType = withAsyncVerification({
+ baseControl: baseControl.type,
+ verify: verificationFn,
+ onChange: createSemanticLayerOnChange(
+ controlName,
+ SEMANTIC_LAYER_CONTROL_FIELDS,
+ ),
+ showLoadingState: true,
+ });
+
+ utilitiesWereAvailable = true;
+ }
+
+ return cachedEnhancedType;
}
+
+ utilitiesWereAvailable = false;
return baseControl.type;
},
mapStateToProps: (state: any, controlState: any) => {
@@ -131,6 +167,7 @@ function enhanceControlWithSemanticLayer(
...originalProps,
needAsyncVerification: needsVerification,
form_data: state.form_data,
+ datasource: state.datasource, // Pass datasource to verification function
};
}
diff --git a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
index 850be4e11a..ed5e24ef18 100644
--- a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
@@ -99,6 +99,9 @@ function supportsSemanticLayerVerification(datasource: Dataset): boolean {
return Boolean(database.engine_information?.supports_dynamic_columns);
}
+// Cache for API calls to prevent duplicates
+const apiCallCache = new Map<string, Promise<{ dimensions: string[]; metrics: string[] } | null>>();
+
/**
* Call the validation API
*/
@@ -112,19 +115,40 @@ async function callValidationAPI(
return null;
}
+ // Create cache key based on the request parameters
+ const cacheKey = JSON.stringify({
+ datasource_id: datasource.id,
+ dimensions: selectedDimensions.sort(),
+ metrics: selectedMetrics.sort(),
+ });
+
+ // Check if we already have a pending request for the same parameters
+ if (apiCallCache.has(cacheKey)) {
+ return apiCallCache.get(cacheKey)!;
+ }
+
try {
- const response = await SupersetClient.post({
+ const apiPromise = SupersetClient.post({
endpoint: `/api/v1/database/${databaseId}/valid_metrics_and_dimensions/`,
jsonPayload: {
datasource_id: datasource.id,
dimensions: selectedDimensions,
metrics: selectedMetrics,
},
- });
+ }).then(response => response.json as { dimensions: string[]; metrics: string[] });
+
+ // Cache the promise
+ apiCallCache.set(cacheKey, apiPromise);
- return response.json as { dimensions: string[]; metrics: string[] };
+ // Remove from cache after a short delay to allow for immediate duplicates
+ setTimeout(() => {
+ apiCallCache.delete(cacheKey);
+ }, 100);
+
+ return await apiPromise;
} catch (error) {
console.warn('Failed to fetch valid metrics and dimensions:', error);
+ apiCallCache.delete(cacheKey);
return null;
}
}
@@ -233,6 +257,7 @@ export function createColumnsVerification(controlName?: string): AsyncVerify {
const queryFields = collectQueryFields(updatedFormData || {});
// Call validation API
+
const validationResult = await callValidationAPI(
datasource as Dataset,
queryFields.dimensions,
diff --git a/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx b/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx
index 852ac70a30..5c45427247 100644
--- a/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/withAsyncVerification.tsx
@@ -146,6 +146,7 @@ export default function withAsyncVerification({
const [isLoading, setIsLoading] = useState<boolean>(initialIsLoading);
const { addWarningToast } = restProps.actions;
const verificationTriggeredByChange = useRef(false);
+
// memoize `restProps`, so that verification only triggers when material
// props are actually updated.
(superset) 04/07: Checkpoint
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch dbt-metricflow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit b17ea1c875ec71bfe38df3d8de062d37cc824797
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jul 17 16:58:16 2025 -0400
Checkpoint
---
.../controls/SemanticLayerVerification.tsx | 94 +++++++++++++++++++++-
1 file changed, 91 insertions(+), 3 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
index ed5e24ef18..7b0610f27d 100644
--- a/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
+++ b/superset-frontend/src/explore/components/controls/SemanticLayerVerification.tsx
@@ -198,19 +198,39 @@ export function createMetricsVerification(controlName?: string): AsyncVerify {
let updatedDatasourceMetrics = dataset.metrics;
let updatedDatasourceColumns = dataset.columns;
+ // Filter valid names to only include those that exist in the original datasource
+ const originalDimensionNames = new Set(dataset.columns?.map((col: any) => col.column_name) || []);
+ const originalMetricNames = new Set(dataset.metrics?.map((metric: any) => metric.metric_name) || []);
+
+ const filteredValidMetricNames = new Set(
+ validationResult.metrics.filter(metric => originalMetricNames.has(metric))
+ );
+ const filteredValidDimensionNames = new Set(
+ validationResult.dimensions.filter(dim => originalDimensionNames.has(dim))
+ );
+
+ console.log('Metrics verification filtering:', {
+ controlName,
+ originalMetricCount: originalMetricNames.size,
+ apiValidMetricCount: validationResult.metrics.length,
+ filteredValidMetricCount: filteredValidMetricNames.size,
+ originalDimensionCount: originalDimensionNames.size,
+ apiValidDimensionCount: validationResult.dimensions.length,
+ filteredValidDimensionCount: filteredValidDimensionNames.size,
+ });
+
if (dataset.metrics) {
updatedDatasourceMetrics = dataset.metrics.map((metric: any) => ({
...metric,
- isDisabled: !validMetricNames.has(metric.metric_name || metric),
+ isDisabled: !filteredValidMetricNames.has(metric.metric_name || metric),
}));
}
// Also update columns using the same validation result
- const validDimensionNames = new Set(validationResult.dimensions);
if (dataset.columns) {
updatedDatasourceColumns = dataset.columns.map((column: any) => ({
...column,
- isDisabled: !validDimensionNames.has(column.column_name || column),
+ isDisabled: !filteredValidDimensionNames.has(column.column_name || column),
}));
}
@@ -257,6 +277,11 @@ export function createColumnsVerification(controlName?: string): AsyncVerify {
const queryFields = collectQueryFields(updatedFormData || {});
// Call validation API
+ console.log('Columns verification API call:', {
+ controlName,
+ dimensions: queryFields.dimensions,
+ metrics: queryFields.metrics,
+ });
const validationResult = await callValidationAPI(
datasource as Dataset,
@@ -268,6 +293,12 @@ export function createColumnsVerification(controlName?: string): AsyncVerify {
return null;
}
+ console.log('Columns verification API response:', {
+ controlName,
+ validDimensions: validationResult.dimensions,
+ validMetrics: validationResult.metrics,
+ });
+
// Mark dimension options as disabled if invalid
const validDimensionNames = new Set(validationResult.dimensions);
const updatedOptions = options.map((option: any) => ({
@@ -296,6 +327,63 @@ export function createColumnsVerification(controlName?: string): AsyncVerify {
}));
}
+ // Debug: Check which dimensions are valid but not in original datasource
+ const originalDimensionNames = new Set(dataset.columns?.map((col: any) => col.column_name) || []);
+ const originalMetricNames = new Set(dataset.metrics?.map((metric: any) => metric.metric_name) || []);
+
+ const validDimensionsNotInOriginal = validationResult.dimensions.filter(
+ (dim: string) => !originalDimensionNames.has(dim)
+ );
+ const validMetricsNotInOriginal = validationResult.metrics.filter(
+ (metric: string) => !originalMetricNames.has(metric)
+ );
+
+ console.log('Columns verification datasource update:', {
+ controlName,
+ originalColumns: dataset.columns?.length,
+ updatedColumns: updatedDatasourceColumns?.length,
+ originalMetrics: dataset.metrics?.length,
+ updatedMetrics: updatedDatasourceMetrics?.length,
+ validDimensionCount: validDimensionNames.size,
+ validMetricCount: validMetricNames.size,
+ validDimensionsNotInOriginal,
+ validMetricsNotInOriginal,
+ });
+
+ // Fix: Only mark columns as disabled if they exist in the original datasource
+ // This prevents the UI from trying to process valid dimensions that don't exist
+ const filteredValidDimensionNames = new Set(
+ validationResult.dimensions.filter(dim => originalDimensionNames.has(dim))
+ );
+ const filteredValidMetricNames = new Set(
+ validationResult.metrics.filter(metric => originalMetricNames.has(metric))
+ );
+
+ console.log('Columns verification filtering:', {
+ controlName,
+ originalDimensionCount: originalDimensionNames.size,
+ apiValidDimensionCount: validationResult.dimensions.length,
+ filteredValidDimensionCount: filteredValidDimensionNames.size,
+ originalMetricCount: originalMetricNames.size,
+ apiValidMetricCount: validationResult.metrics.length,
+ filteredValidMetricCount: filteredValidMetricNames.size,
+ });
+
+ // Update the disabled state logic to use filtered valid names
+ if (dataset.columns) {
+ updatedDatasourceColumns = dataset.columns.map((column: any) => ({
+ ...column,
+ isDisabled: !filteredValidDimensionNames.has(column.column_name || column),
+ }));
+ }
+
+ if (dataset.metrics) {
+ updatedDatasourceMetrics = dataset.metrics.map((metric: any) => ({
+ ...metric,
+ isDisabled: !filteredValidMetricNames.has(metric.metric_name || metric),
+ }));
+ }
+
// Create updated datasource for left panel
const updatedDatasource = {
...dataset,