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 <[email protected]> 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.
