TableNG: Footer fields declaration should work for displayName or name (#108482)
back to using our own getDisplayNam
This commit is contained in:
@@ -208,7 +208,7 @@ export function TableNG(props: TableNGProps) {
|
||||
});
|
||||
|
||||
// Create a map of column key to text wrap
|
||||
const footerCalcs = useFooterCalcs(sortedRows, data.fields, { enabled: hasFooter, footerOptions, isCountRowsSet });
|
||||
const footerCalcs = useFooterCalcs(sortedRows, data, { enabled: hasFooter, footerOptions, isCountRowsSet });
|
||||
const applyToRowBgFn = useMemo(() => getApplyToRowBgFn(data.fields, theme) ?? undefined, [data.fields, theme]);
|
||||
|
||||
const renderRow = useMemo(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { varPreLine } from 'uwrap';
|
||||
|
||||
import { Field, FieldType } from '@grafana/data';
|
||||
import { cacheFieldDisplayNames, createDataFrame, Field, FieldType } from '@grafana/data';
|
||||
|
||||
import {
|
||||
useFilteredRows,
|
||||
@@ -226,11 +226,11 @@ describe('TableNG hooks', () => {
|
||||
|
||||
describe('useFooterCalcs', () => {
|
||||
const rows = [
|
||||
{ Field1: 1, Text: 'a', __depth: 0, __index: 0 },
|
||||
{ Field1: 2, Text: 'b', __depth: 0, __index: 1 },
|
||||
{ Field1: 3, Text: 'c', __depth: 0, __index: 2 },
|
||||
{ Field2: 3, Text: 'd', __depth: 0, __index: 3 },
|
||||
{ Field2: 10, Text: 'e', __depth: 0, __index: 4 },
|
||||
{ 'Field 1': 1, Text: 'a', __depth: 0, __index: 0 },
|
||||
{ 'Field 1': 2, Text: 'b', __depth: 0, __index: 1 },
|
||||
{ 'Field 1': 3, Text: 'c', __depth: 0, __index: 2 },
|
||||
{ 'Field 2': 3, Text: 'd', __depth: 0, __index: 3 },
|
||||
{ 'Field 2': 10, Text: 'e', __depth: 0, __index: 4 },
|
||||
];
|
||||
|
||||
const numericField: Field = {
|
||||
@@ -239,6 +239,7 @@ describe('TableNG hooks', () => {
|
||||
values: [1, 2, 3],
|
||||
config: {
|
||||
custom: {},
|
||||
displayName: 'Field 1',
|
||||
},
|
||||
display: (value: unknown) => ({
|
||||
text: String(value),
|
||||
@@ -247,7 +248,6 @@ describe('TableNG hooks', () => {
|
||||
prefix: undefined,
|
||||
suffix: undefined,
|
||||
}),
|
||||
state: {},
|
||||
getLinks: undefined,
|
||||
};
|
||||
|
||||
@@ -255,7 +255,10 @@ describe('TableNG hooks', () => {
|
||||
name: 'Field2',
|
||||
type: FieldType.number,
|
||||
values: [3, 10],
|
||||
config: { custom: {} },
|
||||
config: {
|
||||
custom: {},
|
||||
displayName: 'Field 2',
|
||||
},
|
||||
display: (value: unknown) => ({
|
||||
text: String(value),
|
||||
numeric: Number(value),
|
||||
@@ -263,7 +266,6 @@ describe('TableNG hooks', () => {
|
||||
prefix: undefined,
|
||||
suffix: undefined,
|
||||
}),
|
||||
state: {},
|
||||
getLinks: undefined,
|
||||
};
|
||||
|
||||
@@ -279,105 +281,135 @@ describe('TableNG hooks', () => {
|
||||
prefix: undefined,
|
||||
suffix: undefined,
|
||||
}),
|
||||
state: {},
|
||||
getLinks: undefined,
|
||||
};
|
||||
|
||||
it('should calculate sum for numeric fields', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, numericField], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: ['sum'] },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual(['Total', '6']); // 1 + 2 + 3
|
||||
});
|
||||
|
||||
it('should calculate mean for numeric fields', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, numericField], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: ['mean'] },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual(['Mean', '2']); // (1 + 2 + 3) / 3
|
||||
});
|
||||
|
||||
it('should return an empty string for non-numeric fields', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, textField], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, textField] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: ['sum'] },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual(['Total', '']);
|
||||
});
|
||||
|
||||
it('should return empty array if no footerOptions are provided', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, textField], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField, numericField2] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when footer is disabled', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, textField], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField, numericField2] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: false,
|
||||
footerOptions: { show: true, reducer: ['sum'] },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when reducer is undefined', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, textField], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, textField] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: undefined },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when reducer is empty', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, textField], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField, numericField2] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: [] },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty string if fields array doesnt include this field', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, numericField, numericField2], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField, numericField2] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: ['sum'], fields: ['Field2', 'Field3'] },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual(['Total', '', '13']);
|
||||
});
|
||||
|
||||
it('should return the calculation if fields array includes this field', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFooterCalcs(rows, [textField, numericField, numericField2], {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField, numericField2] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: ['sum'], fields: ['Field1', 'Field2', 'Field3'] },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual(['Total', '6', '13']);
|
||||
});
|
||||
|
||||
it('should return the calculation if fields array includes this field by either name or display name', () => {
|
||||
const { result } = renderHook(() => {
|
||||
const data = createDataFrame({ fields: [textField, numericField, numericField2] });
|
||||
cacheFieldDisplayNames([data]);
|
||||
return useFooterCalcs(rows, data, {
|
||||
enabled: true,
|
||||
footerOptions: { show: true, reducer: ['sum'], fields: ['Field1', 'Field 2'] },
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current).toEqual(['Total', '6', '13']);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,16 @@ import { useState, useMemo, useEffect, useCallback, useRef, useLayoutEffect, Ref
|
||||
import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-grid';
|
||||
import { varPreLine } from 'uwrap';
|
||||
|
||||
import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data';
|
||||
import {
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldMatcherID,
|
||||
fieldReducers,
|
||||
FieldType,
|
||||
formattedValueToString,
|
||||
getFieldMatcher,
|
||||
reduceField,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { useTheme2 } from '../../../themes/ThemeContext';
|
||||
import { TableCellDisplayMode, TableColumnResizeActionCallback } from '../types';
|
||||
@@ -255,7 +264,7 @@ export interface FooterCalcsOptions {
|
||||
|
||||
export function useFooterCalcs(
|
||||
rows: TableRow[],
|
||||
fields: Field[],
|
||||
data: DataFrame,
|
||||
{ enabled, footerOptions, isCountRowsSet }: FooterCalcsOptions
|
||||
): string[] {
|
||||
return useMemo(() => {
|
||||
@@ -265,7 +274,11 @@ export function useFooterCalcs(
|
||||
return [];
|
||||
}
|
||||
|
||||
return fields.map((field, index) => {
|
||||
const fieldNameMatcher = footerOptions.fields
|
||||
? getFieldMatcher({ id: FieldMatcherID.byNames, options: { names: footerOptions.fields } })
|
||||
: undefined;
|
||||
|
||||
return data.fields.map((field, index) => {
|
||||
if (field.state?.calcs) {
|
||||
delete field.state?.calcs;
|
||||
}
|
||||
@@ -289,8 +302,9 @@ export function useFooterCalcs(
|
||||
return '';
|
||||
}
|
||||
|
||||
// If fields array is specified, only show footer for fields included in that array
|
||||
if (footerOptions.fields?.length && !footerOptions.fields?.includes(getDisplayName(field))) {
|
||||
// If fields array is specified, only show footer for fields included in that array.
|
||||
// the array can include either the display name or the field name.
|
||||
if (fieldNameMatcher && !fieldNameMatcher(field, data, [data])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -305,7 +319,7 @@ export function useFooterCalcs(
|
||||
|
||||
return formattedValueToString(displayFn(value));
|
||||
});
|
||||
}, [fields, enabled, footerOptions, isCountRowsSet, rows]);
|
||||
}, [data, enabled, footerOptions, isCountRowsSet, rows]);
|
||||
}
|
||||
|
||||
interface TypographyCtx {
|
||||
|
||||
@@ -537,7 +537,10 @@ export const processNestedTableRows = (
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* returns the display name of a field
|
||||
* returns the display name of a field.
|
||||
* We intentionally do not want to use @grafana/data's getFieldDisplayName here,
|
||||
* instead we have a call to cacheFieldDisplayNames up in TablePanel to handle this
|
||||
* before we begin.
|
||||
*/
|
||||
export const getDisplayName = (field: Field): string => {
|
||||
return field.state?.displayName ?? field.name;
|
||||
|
||||
Reference in New Issue
Block a user