fix: add smart cache invalidation + fix tests

pull/102284/head
Alex Spencer 3 months ago
parent d33584b473
commit 8d40e72b61
  1. 4
      .betterer.results
  2. 6
      packages/grafana-ui/src/components/Table/TableNG/Cells/summaryCell.tsx
  3. 104
      packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx
  4. 29
      packages/grafana-ui/src/components/Table/TableNG/utils.ts

@ -713,7 +713,9 @@ exports[`better eslint`] = {
],
"packages/grafana-ui/src/components/Table/TableNG/utils.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
"packages/grafana-ui/src/components/Table/TableRT/Filter.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]

@ -23,11 +23,11 @@ export const SummaryCell = ({ sortedRows, field }: SummaryCellProps) => {
return (
<div className={styles.footerCell}>
{Object.entries(footerItem).map(([reducerId, { reducerName, formattedValue }]) => {
const allSumReducers = Object.keys(footerItem).every((item) => item === 'sum');
const isSingleSumReducer = Object.keys(footerItem).every((item) => item === 'sum');
return (
<div key={reducerId} className={cx(styles.footerItem, allSumReducers && styles.sumReducer)}>
{!allSumReducers && <div className={styles.footerItemLabel}>{reducerName}</div>}
<div key={reducerId} className={cx(styles.footerItem, isSingleSumReducer && styles.sumReducer)}>
{!isSingleSumReducer && <div className={styles.footerItemLabel}>{reducerName}</div>}
<div className={styles.footerItemValue}>{formattedValue}</div>
</div>
);

@ -378,18 +378,23 @@ describe('TableNG', () => {
});
it('renders footer with aggregations when footerOptions are provided', () => {
const baseFrame = createBasicDataFrame();
// Create a filter function that only shows rows with A1
const frameWithReducers = {
...baseFrame,
fields: baseFrame.fields.map((field) => ({
...field,
config: {
...field.config,
custom: {
footer: { reducer: ['sum'] },
},
},
})),
};
const { container } = render(
<TableNG
enableVirtualization={false}
data={createBasicDataFrame()}
width={800}
height={600}
footerOptions={{
show: true,
reducer: ['sum'],
countRows: false,
}}
/>
<TableNG enableVirtualization={false} data={frameWithReducers} width={800} height={600} />
);
// Check for footer row
@ -399,36 +404,6 @@ describe('TableNG', () => {
// Sum of Column B values (1+2+3=6)
expect(screen.getByText('6')).toBeInTheDocument();
});
it('renders row count in footer when countRows is true', () => {
const { container } = render(
<TableNG
enableVirtualization={false}
data={createBasicDataFrame()}
width={800}
height={600}
footerOptions={{
show: true,
reducer: ['count'],
countRows: true, // Enable row counting
}}
/>
);
// Check for footer row
const footerRow = container.querySelector('.rdg-summary-row');
expect(footerRow).toBeInTheDocument();
// Get the text content of the footer cells
const footerCells = footerRow?.querySelectorAll('[role="gridcell"]');
const footerTexts = Array.from(footerCells || []).map((cell) => cell.textContent);
// The first cell should contain the row count (3 rows)
expect(footerTexts[0]).toBe('Count3');
// There should be no other footer cells
expect(footerTexts[1]).toBe('');
});
});
describe('Pagination', () => {
@ -985,30 +960,37 @@ describe('TableNG', () => {
});
it('updates footer calculations when rows are filtered', () => {
// Create a filtered frame with only the first row
const baseFrame = createBasicDataFrame();
const baseFrameWithReducers = {
...baseFrame,
fields: baseFrame.fields.map((field) => ({
...field,
state: {
calcs: {
sum: {
value: 6,
formattedValue: '6',
reducerName: 'sum',
},
},
},
config: { ...field.config, custom: { footer: { reducer: ['sum'] } } },
})),
};
const filteredFrame = {
...baseFrame,
length: 1,
fields: baseFrame.fields.map((field) => ({
...field,
values: field.name === 'Column A' ? ['A1'] : field.name === 'Column B' ? [1] : field.values.slice(0, 1),
config: { ...field.config, custom: { footer: { reducer: ['sum'] } } },
})),
};
// Render with unfiltered data and footer options
const { container, rerender } = render(
<TableNG
enableVirtualization={false}
data={baseFrame}
width={800}
height={600}
footerOptions={{
show: true,
reducer: ['sum'],
countRows: false,
}}
/>
<TableNG enableVirtualization={false} data={baseFrameWithReducers} width={800} height={600} />
);
// Check initial footer sum (1+2+3=6)
@ -1023,19 +1005,7 @@ describe('TableNG', () => {
expect(initialFooterTexts[1]).toBe('6');
// Rerender with filtered data
rerender(
<TableNG
enableVirtualization={false}
data={filteredFrame}
width={800}
height={600}
footerOptions={{
show: true,
reducer: ['sum'],
countRows: false,
}}
/>
);
rerender(<TableNG enableVirtualization={false} data={filteredFrame} width={800} height={600} />);
// Check filtered footer sum (should be 1)
const filteredFooter = container.querySelector('.rdg-summary-row');

@ -14,6 +14,7 @@ import {
LinkModel,
DisplayValueAlignmentFactors,
DataFrame,
FieldState,
} from '@grafana/data';
import {
BarGaugeDisplayMode,
@ -39,6 +40,11 @@ import {
Comparator,
} from './types';
// Extend FieldState to include our custom tracking property
interface FooterFieldState extends FieldState {
lastProcessedRowCount?: number;
}
/* ---------------------------- Cell calculations --------------------------- */
export function getCellHeight(
text: string,
@ -277,7 +283,7 @@ export function getAlignmentFactor(
export interface FooterItem {
[reducerId: string]: {
value: string;
value: number | null;
formattedValue: string;
reducerName: string;
};
@ -294,6 +300,25 @@ export function getFooterItemNG(rows: TableRow[], field: Field): FooterItem | nu
return null;
}
// Cache invalidation tracking
// Only invalidate the cache when the row count changes, which indicates filtering/sorting changes
if (!field.state) {
field.state = {};
}
const currentRowCount = rows.length;
const lastRowCount = (field.state as FooterFieldState).lastProcessedRowCount;
// Check if we need to invalidate the cache
if (lastRowCount !== currentRowCount) {
// Cache should be invalidated as row count has changed
if (field.state.calcs) {
delete field.state.calcs;
}
// Update the row count tracker
(field.state as FooterFieldState).lastProcessedRowCount = currentRowCount;
}
// Calculate all specified reducers
const results = reduceField({
field: {
@ -308,7 +333,7 @@ export function getFooterItemNG(rows: TableRow[], field: Field): FooterItem | nu
reducers.forEach((reducerId: string) => {
if (results[reducerId] !== undefined) {
const value = results[reducerId];
const value: number | null = results[reducerId];
const reducerName = fieldReducers.get(reducerId)?.name || reducerId;
const formattedValue = field.display ? formattedValueToString(field.display(value)) : String(value);

Loading…
Cancel
Save