mirror of
https://github.com/apache/superset.git
synced 2026-04-20 00:24:38 +00:00
feat(viz-type): Ag grid table plugin Integration (#33517)
Signed-off-by: hainenber <dotronghai96@gmail.com> Co-authored-by: Amaan Nawab <nelsondrew07@gmail.com> Co-authored-by: Levis Mbote <111055098+LevisNgigi@users.noreply.github.com> Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com> Co-authored-by: Paul Rhodes <withnale@users.noreply.github.com> Co-authored-by: Vitor Avila <96086495+Vitor-Avila@users.noreply.github.com> Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com> Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com> Co-authored-by: Sam Firke <sfirke@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
/* eslint-disable camelcase */
|
||||
/**
|
||||
* 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 { ColDef } from 'ag-grid-community';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { DataRecord, GenericDataType } from '@superset-ui/core';
|
||||
import { ColorFormatters } from '@superset-ui/chart-controls';
|
||||
import { extent as d3Extent, max as d3Max } from 'd3-array';
|
||||
import {
|
||||
BasicColorFormatterType,
|
||||
CellRendererProps,
|
||||
InputColumn,
|
||||
} from '../types';
|
||||
import getCellClass from './getCellClass';
|
||||
import filterValueGetter from './filterValueGetter';
|
||||
import dateFilterComparator from './dateFilterComparator';
|
||||
import { getAggFunc } from './getAggFunc';
|
||||
import { TextCellRenderer } from '../renderers/TextCellRenderer';
|
||||
import { NumericCellRenderer } from '../renderers/NumericCellRenderer';
|
||||
import CustomHeader from '../AgGridTable/components/CustomHeader';
|
||||
import { valueFormatter, valueGetter } from './formatValue';
|
||||
import getCellStyle from './getCellStyle';
|
||||
|
||||
interface InputData {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type UseColDefsProps = {
|
||||
columns: InputColumn[];
|
||||
data: InputData[];
|
||||
serverPagination: boolean;
|
||||
isRawRecords: boolean;
|
||||
defaultAlignPN: boolean;
|
||||
showCellBars: boolean;
|
||||
colorPositiveNegative: boolean;
|
||||
totals: DataRecord | undefined;
|
||||
columnColorFormatters: ColorFormatters;
|
||||
allowRearrangeColumns?: boolean;
|
||||
basicColorFormatters?: { [Key: string]: BasicColorFormatterType }[];
|
||||
isUsingTimeComparison?: boolean;
|
||||
emitCrossFilters?: boolean;
|
||||
alignPositiveNegative: boolean;
|
||||
slice_id: number;
|
||||
};
|
||||
|
||||
type ValueRange = [number, number];
|
||||
|
||||
function getValueRange(
|
||||
key: string,
|
||||
alignPositiveNegative: boolean,
|
||||
data: InputData[],
|
||||
) {
|
||||
if (typeof data?.[0]?.[key] === 'number') {
|
||||
const nums = data.map(row => row[key]) as number[];
|
||||
return (
|
||||
alignPositiveNegative ? [0, d3Max(nums.map(Math.abs))] : d3Extent(nums)
|
||||
) as ValueRange;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const getCellDataType = (col: InputColumn) => {
|
||||
switch (col.dataType) {
|
||||
case GenericDataType.Numeric:
|
||||
return 'number';
|
||||
case GenericDataType.Temporal:
|
||||
return 'date';
|
||||
case GenericDataType.Boolean:
|
||||
return 'boolean';
|
||||
default:
|
||||
return 'text';
|
||||
}
|
||||
};
|
||||
|
||||
const getFilterType = (col: InputColumn) => {
|
||||
switch (col.dataType) {
|
||||
case GenericDataType.Numeric:
|
||||
return 'agNumberColumnFilter';
|
||||
case GenericDataType.String:
|
||||
return 'agMultiColumnFilter';
|
||||
case GenericDataType.Temporal:
|
||||
return 'agDateColumnFilter';
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
function getHeaderLabel(col: InputColumn) {
|
||||
let headerLabel: string | undefined;
|
||||
|
||||
const hasOriginalLabel = !!col?.originalLabel;
|
||||
const isMain = col?.key?.includes('Main');
|
||||
const hasDisplayTypeIcon = col?.config?.displayTypeIcon !== false;
|
||||
const hasCustomColumnName = !!col?.config?.customColumnName;
|
||||
|
||||
if (hasOriginalLabel && hasCustomColumnName) {
|
||||
if ('displayTypeIcon' in col.config) {
|
||||
headerLabel =
|
||||
hasDisplayTypeIcon && !isMain
|
||||
? `${col.label} ${col.config.customColumnName}`
|
||||
: col.config.customColumnName;
|
||||
} else {
|
||||
headerLabel = col.config.customColumnName;
|
||||
}
|
||||
} else if (hasOriginalLabel && isMain) {
|
||||
headerLabel = col.originalLabel;
|
||||
} else if (hasOriginalLabel && !hasDisplayTypeIcon) {
|
||||
headerLabel = '';
|
||||
} else {
|
||||
headerLabel = col?.label;
|
||||
}
|
||||
return headerLabel || '';
|
||||
}
|
||||
|
||||
export const useColDefs = ({
|
||||
columns,
|
||||
data,
|
||||
serverPagination,
|
||||
isRawRecords,
|
||||
defaultAlignPN,
|
||||
showCellBars,
|
||||
colorPositiveNegative,
|
||||
totals,
|
||||
columnColorFormatters,
|
||||
allowRearrangeColumns,
|
||||
basicColorFormatters,
|
||||
isUsingTimeComparison,
|
||||
emitCrossFilters,
|
||||
alignPositiveNegative,
|
||||
slice_id,
|
||||
}: UseColDefsProps) => {
|
||||
const getCommonColProps = useCallback(
|
||||
(
|
||||
col: InputColumn,
|
||||
): ColDef & {
|
||||
isMain: boolean;
|
||||
} => {
|
||||
const {
|
||||
config,
|
||||
isMetric,
|
||||
isPercentMetric,
|
||||
isNumeric,
|
||||
key: originalKey,
|
||||
dataType,
|
||||
originalLabel,
|
||||
} = col;
|
||||
|
||||
const alignPN =
|
||||
config.alignPositiveNegative === undefined
|
||||
? defaultAlignPN
|
||||
: config.alignPositiveNegative;
|
||||
|
||||
const hasColumnColorFormatters =
|
||||
isNumeric &&
|
||||
Array.isArray(columnColorFormatters) &&
|
||||
columnColorFormatters.length > 0;
|
||||
|
||||
const hasBasicColorFormatters =
|
||||
isUsingTimeComparison &&
|
||||
Array.isArray(basicColorFormatters) &&
|
||||
basicColorFormatters.length > 0;
|
||||
|
||||
const isMain = originalKey?.includes('Main');
|
||||
const colId = isMain
|
||||
? originalKey.replace('Main', '').trim()
|
||||
: originalKey;
|
||||
const isTextColumn =
|
||||
dataType === GenericDataType.String ||
|
||||
dataType === GenericDataType.Temporal;
|
||||
|
||||
const valueRange =
|
||||
!hasBasicColorFormatters &&
|
||||
!hasColumnColorFormatters &&
|
||||
showCellBars &&
|
||||
(config.showCellBars ?? true) &&
|
||||
(isMetric || isRawRecords || isPercentMetric) &&
|
||||
getValueRange(originalKey, alignPN || alignPositiveNegative, data);
|
||||
|
||||
const filter = getFilterType(col);
|
||||
|
||||
return {
|
||||
field: colId,
|
||||
headerName: getHeaderLabel(col),
|
||||
valueFormatter: p => valueFormatter(p, col),
|
||||
valueGetter: p => valueGetter(p, col),
|
||||
cellStyle: p =>
|
||||
getCellStyle({
|
||||
...p,
|
||||
hasColumnColorFormatters,
|
||||
columnColorFormatters,
|
||||
hasBasicColorFormatters,
|
||||
basicColorFormatters,
|
||||
col,
|
||||
}),
|
||||
cellClass: p =>
|
||||
getCellClass({
|
||||
...p,
|
||||
col,
|
||||
emitCrossFilters,
|
||||
}),
|
||||
minWidth: config?.columnWidth ?? 100,
|
||||
filter,
|
||||
...(isPercentMetric && {
|
||||
filterValueGetter,
|
||||
}),
|
||||
...(dataType === GenericDataType.Temporal && {
|
||||
filterParams: {
|
||||
comparator: dateFilterComparator,
|
||||
},
|
||||
}),
|
||||
cellDataType: getCellDataType(col),
|
||||
defaultAggFunc: getAggFunc(col),
|
||||
initialAggFunc: getAggFunc(col),
|
||||
...(!(isMetric || isPercentMetric) && {
|
||||
allowedAggFuncs: [
|
||||
'sum',
|
||||
'min',
|
||||
'max',
|
||||
'count',
|
||||
'avg',
|
||||
'first',
|
||||
'last',
|
||||
],
|
||||
}),
|
||||
cellRenderer: (p: CellRendererProps) =>
|
||||
isTextColumn ? TextCellRenderer(p) : NumericCellRenderer(p),
|
||||
cellRendererParams: {
|
||||
allowRenderHtml: true,
|
||||
columns,
|
||||
hasBasicColorFormatters,
|
||||
col,
|
||||
basicColorFormatters,
|
||||
valueRange,
|
||||
alignPositiveNegative: alignPN || alignPositiveNegative,
|
||||
colorPositiveNegative,
|
||||
},
|
||||
context: {
|
||||
isMetric,
|
||||
isPercentMetric,
|
||||
isNumeric,
|
||||
},
|
||||
lockPinned: !allowRearrangeColumns,
|
||||
sortable: !serverPagination || !isPercentMetric,
|
||||
...(serverPagination && {
|
||||
headerComponent: CustomHeader,
|
||||
comparator: () => 0,
|
||||
headerComponentParams: {
|
||||
slice_id,
|
||||
},
|
||||
}),
|
||||
isMain,
|
||||
...(!isMain &&
|
||||
originalLabel && {
|
||||
columnGroupShow: 'open',
|
||||
}),
|
||||
...(originalLabel && {
|
||||
timeComparisonKey: originalLabel,
|
||||
}),
|
||||
wrapText: !config?.truncateLongCells,
|
||||
autoHeight: !config?.truncateLongCells,
|
||||
};
|
||||
},
|
||||
[
|
||||
columns,
|
||||
data,
|
||||
defaultAlignPN,
|
||||
columnColorFormatters,
|
||||
basicColorFormatters,
|
||||
showCellBars,
|
||||
colorPositiveNegative,
|
||||
isUsingTimeComparison,
|
||||
isRawRecords,
|
||||
emitCrossFilters,
|
||||
allowRearrangeColumns,
|
||||
serverPagination,
|
||||
alignPositiveNegative,
|
||||
],
|
||||
);
|
||||
|
||||
const stringifiedCols = JSON.stringify(columns);
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
const groupIndexMap = new Map<string, number>();
|
||||
|
||||
return columns.reduce<ColDef[]>((acc, col) => {
|
||||
const colDef = getCommonColProps(col);
|
||||
|
||||
if (col?.originalLabel) {
|
||||
if (groupIndexMap.has(col.originalLabel)) {
|
||||
const groupIdx = groupIndexMap.get(col.originalLabel)!;
|
||||
(acc[groupIdx] as { children: ColDef[] }).children.push(colDef);
|
||||
} else {
|
||||
const group = {
|
||||
headerName: col.originalLabel,
|
||||
marryChildren: true,
|
||||
openByDefault: true,
|
||||
children: [colDef],
|
||||
};
|
||||
groupIndexMap.set(col.originalLabel, acc.length);
|
||||
acc.push(group);
|
||||
}
|
||||
} else {
|
||||
acc.push(colDef);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}, [stringifiedCols, getCommonColProps]);
|
||||
|
||||
return colDefs;
|
||||
};
|
||||
Reference in New Issue
Block a user