/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { ensureIsArray, getColumnLabel } from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/api/core'; import { ColumnMeta } from '@superset-ui/chart-controls'; import { DatasourcesState, Slice, WhatIfColumn } from '../types'; /** * Check if a column is numeric based on its type_generic field */ export function isNumericColumn(column: ColumnMeta): boolean { return column.type_generic === GenericDataType.Numeric; } /** * Extract column names from a slice's form_data * This includes columns from groupby, metrics, x_axis, series, filters, etc. */ export function extractColumnsFromSlice(slice: Slice): Set { const columns = new Set(); const formData = slice.form_data; if (!formData) return columns; // Helper to add column - handles both physical columns (strings) and adhoc columns const addColumn = (col: any) => { if (col) { const label = getColumnLabel(col); if (label) columns.add(label); } }; // Extract groupby columns (can be physical or adhoc) ensureIsArray(formData.groupby).forEach(addColumn); // Extract x_axis column (can be physical or adhoc) if (formData.x_axis) { addColumn(formData.x_axis); } // Extract metrics - get column names from metric definitions ensureIsArray(formData.metrics).forEach((metric: any) => { if (typeof metric === 'string') { // Saved metric name - we can't extract columns from it return; } if (metric && typeof metric === 'object' && 'column' in metric) { const metricColumn = metric.column; if (typeof metricColumn === 'string') { columns.add(metricColumn); } else if (metricColumn?.column_name) { columns.add(metricColumn.column_name); } } }); // Extract metric (singular) - used by pie charts and other single-metric charts if (formData.metric && typeof formData.metric === 'object') { const metric = formData.metric as any; if ('column' in metric) { const metricColumn = metric.column; if (typeof metricColumn === 'string') { columns.add(metricColumn); } else if (metricColumn?.column_name) { columns.add(metricColumn.column_name); } } } // Extract series column (can be physical or adhoc) if (formData.series) { addColumn(formData.series); } // Extract entity column if (formData.entity) { addColumn(formData.entity); } // Extract columns from filters ensureIsArray(formData.adhoc_filters).forEach((filter: any) => { if (filter?.subject && typeof filter.subject === 'string') { columns.add(filter.subject); } }); // Extract columns array (used by some chart types like box_plot) ensureIsArray(formData.columns).forEach(addColumn); return columns; } /** * Get the datasource key from a slice's form_data * Format: "datasourceId__datasourceType" e.g., "2__table" */ export function getDatasourceKey(slice: Slice): string | null { const datasource = slice.form_data?.datasource; if (!datasource || typeof datasource !== 'string') return null; return datasource; } /** * Get numeric columns used by slices on a dashboard * Returns columns grouped by their usage across slices * * Uses sliceEntities.slices instead of charts state because it changes less * frequently (only on slice metadata updates, not on every query result change) */ export function getNumericColumnsForDashboard( slices: { [id: number]: Slice }, datasources: DatasourcesState, ): WhatIfColumn[] { const columnMap = new Map(); Object.values(slices).forEach(slice => { const chartId = slice.slice_id; const datasourceKey = getDatasourceKey(slice); if (!datasourceKey) return; const datasource = datasources[datasourceKey]; if (!datasource?.columns) return; // Extract columns referenced by this slice const referencedColumns = extractColumnsFromSlice(slice); // For each referenced column, check if it's numeric referencedColumns.forEach(colName => { const colMetadata = datasource.columns.find( (c: ColumnMeta) => c.column_name === colName, ); if (colMetadata && isNumericColumn(colMetadata)) { // Create a unique key for this column (datasource + column name) const key = `${datasource.id}:${colName}`; if (!columnMap.has(key)) { columnMap.set(key, { columnName: colName, datasourceId: datasource.id, usedByChartIds: [chartId], description: colMetadata.description, verboseName: colMetadata.verbose_name, }); } else { const existing = columnMap.get(key)!; if (!existing.usedByChartIds.includes(chartId)) { existing.usedByChartIds.push(chartId); } } } }); }); return Array.from(columnMap.values()); } /** * Check if a slice uses a specific column */ export function sliceUsesColumn(slice: Slice, columnName: string): boolean { const columns = extractColumnsFromSlice(slice); return columns.has(columnName); }