mirror of https://github.com/grafana/grafana.git
Table: Fix logic to calculate footer height (#110954)
* Table: Fix logic to calculate footer height
* add non-numeric footer case to gdev
* Update packages/grafana-ui/src/components/Table/TableNG/utils.ts
* Update packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
---------
(cherry picked from commit cb37539ed7
)
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
parent
987573a17c
commit
8ce2c2d3eb
|
@ -1442,6 +1442,67 @@
|
|||
}
|
||||
],
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"footer": {
|
||||
"reducers": ["lastNotNull", "countAll"]
|
||||
},
|
||||
"inspect": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 24
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"showHeader": true
|
||||
},
|
||||
"pluginVersion": "12.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "a,b\nfoo,bar\nbaz,bim\nbop,boop",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "No numeric fields",
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
|
|
|
@ -108,7 +108,6 @@ export function TableNG(props: TableNGProps) {
|
|||
enablePagination = false,
|
||||
enableSharedCrosshair = false,
|
||||
enableVirtualization,
|
||||
fieldConfig,
|
||||
frozenColumns = 0,
|
||||
getActions = () => [],
|
||||
height,
|
||||
|
@ -125,12 +124,6 @@ export function TableNG(props: TableNGProps) {
|
|||
width,
|
||||
} = props;
|
||||
|
||||
const hasFooter = useMemo(
|
||||
() => data.fields.some((field) => field.config?.custom?.footer?.reducers?.length ?? false),
|
||||
[data.fields]
|
||||
);
|
||||
const footerHeight = hasFooter ? calculateFooterHeight(data, fieldConfig) : 0;
|
||||
|
||||
const theme = useTheme2();
|
||||
const styles = useStyles2(getGridStyles, enablePagination, transparent);
|
||||
const panelContext = usePanelContext();
|
||||
|
@ -146,7 +139,16 @@ export function TableNG(props: TableNGProps) {
|
|||
[getActions, data, userCanExecuteActions]
|
||||
);
|
||||
|
||||
const visibleFields = useMemo(() => getVisibleFields(data.fields), [data.fields]);
|
||||
const hasHeader = !noHeader;
|
||||
const hasFooter = useMemo(
|
||||
() => visibleFields.some((field) => Boolean(field.config.custom?.footer?.reducers?.length)),
|
||||
[visibleFields]
|
||||
);
|
||||
const footerHeight = useMemo(
|
||||
() => (hasFooter ? calculateFooterHeight(visibleFields) : 0),
|
||||
[hasFooter, visibleFields]
|
||||
);
|
||||
|
||||
const resizeHandler = useColumnResize(onColumnResize);
|
||||
|
||||
|
@ -173,7 +175,7 @@ export function TableNG(props: TableNGProps) {
|
|||
const [expandedRows, setExpandedRows] = useState(() => new Set<number>());
|
||||
|
||||
// vt scrollbar accounting for column auto-sizing
|
||||
const visibleFields = useMemo(() => getVisibleFields(data.fields), [data.fields]);
|
||||
|
||||
const defaultRowHeight = useMemo(
|
||||
() => getDefaultRowHeight(theme, visibleFields, cellHeight),
|
||||
[theme, visibleFields, cellHeight]
|
||||
|
|
|
@ -46,6 +46,7 @@ import {
|
|||
getDefaultRowHeight,
|
||||
getDisplayName,
|
||||
predicateByName,
|
||||
calculateFooterHeight,
|
||||
} from './utils';
|
||||
|
||||
describe('TableNG utils', () => {
|
||||
|
@ -1380,6 +1381,35 @@ describe('TableNG utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('calculateFooterHeight', () => {
|
||||
it('should return 0 if no footer is present', () => {
|
||||
const frame = createDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', values: [1, 1, 2], nanos: [100, 99, 0] },
|
||||
{ name: 'value', values: [10, 20, 30] },
|
||||
],
|
||||
});
|
||||
|
||||
expect(calculateFooterHeight(frame.fields)).toBe(0);
|
||||
});
|
||||
|
||||
it('should return the height in pixels for the max reducers on a given field', () => {
|
||||
const frame = createDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
values: [1, 1, 2],
|
||||
nanos: [100, 99, 0],
|
||||
config: { custom: { footer: { reducers: ['min', 'max', 'count'] } } },
|
||||
},
|
||||
{ name: 'value', values: [10, 20, 30], config: { custom: { footer: { reducers: ['min'] } } } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(calculateFooterHeight(frame.fields)).toBe(78); // 3 reducers * 22px line height + 12px padding
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDisplayName', () => {
|
||||
it('should return the display name if set', () => {
|
||||
const field: Field = {
|
||||
|
|
|
@ -8,7 +8,6 @@ import { Count, varPreLine } from 'uwrap';
|
|||
import {
|
||||
FieldType,
|
||||
Field,
|
||||
FieldConfigSource,
|
||||
formattedValueToString,
|
||||
GrafanaTheme2,
|
||||
DisplayValue,
|
||||
|
@ -842,55 +841,18 @@ export const processNestedTableRows = (
|
|||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Get the maximum number of reducers across all fields
|
||||
*/
|
||||
const getMaxReducerCount = (dataFrame: DataFrame, fieldConfig?: FieldConfigSource): number => {
|
||||
// Filter to only numeric fields that can have reducers
|
||||
const numericFields = dataFrame.fields.filter(({ type }) => type === FieldType.number);
|
||||
|
||||
// If there are no numeric fields, return 0
|
||||
if (numericFields.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Map each field to its reducer count (direct config or override)
|
||||
const reducerCounts = numericFields.map((field) => {
|
||||
// Get the direct reducer count from the field config
|
||||
const directReducers = field.config?.custom?.footer?.reducers ?? [];
|
||||
let reducerCount = directReducers.length;
|
||||
|
||||
// Check for overrides if field config is available
|
||||
if (fieldConfig?.overrides) {
|
||||
// Find override that matches this field
|
||||
const override = fieldConfig.overrides.find(
|
||||
({ matcher: { id, options } }) => id === 'byName' && options === getDisplayName(field)
|
||||
);
|
||||
|
||||
// Check if there's a footer reducer property in the override
|
||||
const footerProperty = override?.properties?.find(({ id }) => id === 'custom.footer.reducers');
|
||||
if (footerProperty?.value && Array.isArray(footerProperty.value)) {
|
||||
// If override exists, it takes precedence over direct config
|
||||
reducerCount = footerProperty.value.length;
|
||||
}
|
||||
}
|
||||
|
||||
return reducerCount;
|
||||
});
|
||||
|
||||
// Return the maximum count or 0 if no reducers found
|
||||
return reducerCounts.length > 0 ? Math.max(...reducerCounts) : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Calculate the footer height based on the maximum reducer count
|
||||
*/
|
||||
export const calculateFooterHeight = (dataFrame: DataFrame, fieldConfig?: FieldConfigSource) => {
|
||||
const maxReducerCount = getMaxReducerCount(dataFrame, fieldConfig);
|
||||
export const calculateFooterHeight = (fields: Field[]): number => {
|
||||
let maxReducerCount = 0;
|
||||
for (const field of fields) {
|
||||
maxReducerCount = Math.max(maxReducerCount, field.config.custom?.footer?.reducers?.length ?? 0);
|
||||
}
|
||||
|
||||
// Base height (+ padding) + height per reducer
|
||||
return maxReducerCount * TABLE.LINE_HEIGHT + TABLE.CELL_PADDING * 2;
|
||||
return maxReducerCount > 0 ? maxReducerCount * TABLE.LINE_HEIGHT + TABLE.CELL_PADDING * 2 : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue