mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TablePanel: fix footer bug; no footer calculated values after "hidden" column override (#64269)
* baldm0mma/bug/tableFooter/ first commit * baldm0mma/bug/tableFooter/ investigation annotations * baldm0mma/bug/tableFooter/ solution * baldm0mma/bug/tableFooter/ rem conlogs in footerrow.txs * baldm0mma/bug/tableFooter/ rem conlogs in tablepanel.tsx * baldm0mma/bug/tableFooter/ rem conlgs in table.tsx * baldm0mma/bug/tableFooter/ rem conlogs in utils.ts * baldm0mma/bug/tableFooter/ reset return in footerRow.tsx * baldm0mma/bug/tableFooter/ rem unused anno in table.tsx * baldm0mma/bug/tableFooter/ rem unsed annos in utils.ts * baldm0mma/bug/tableFooter/ add addMissingColumnIndex * baldm0mma/bug/tableFooter/ add annos * baldm0mma/bug/tableFooter/ add annos * baldm0mma/bug/tableFooter/ / add annos * baldm0mma/bug/tableFooterFix/ update annos * baldm0mma/bug/tableFooterFix/ update spelling in utils * baldm0mma/bug/tableFooterFix/ rem unused condition in utils.ts * baldm0mma/bug/tableFooterFix/ update anno in utils.ts * Wrap comments and fix misspelling. * baldm0mma/bug/tableFooterFix/ add TSDoc * baldm0mma/bug/tableFooterFix/ update annotations in utils.ts --------- Co-authored-by: Kyle Cunningham <kyle@codeincarnate.com>
This commit is contained in:
parent
af9a0dbe39
commit
09341a0cd6
@ -99,7 +99,6 @@ export function getColumns(
|
|||||||
|
|
||||||
for (const [fieldIndex, field] of data.fields.entries()) {
|
for (const [fieldIndex, field] of data.fields.entries()) {
|
||||||
const fieldTableOptions = (field.config.custom || {}) as TableFieldOptions;
|
const fieldTableOptions = (field.config.custom || {}) as TableFieldOptions;
|
||||||
|
|
||||||
if (fieldTableOptions.hidden) {
|
if (fieldTableOptions.hidden) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -319,43 +318,73 @@ function toNumber(value: any): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getFooterItems(
|
export function getFooterItems(
|
||||||
filterFields: Array<{ id: string; field: Field }>,
|
filterFields: Array<{ id: string; field?: Field } | undefined>,
|
||||||
values: any[number],
|
values: any[number],
|
||||||
options: TableFooterCalc,
|
options: TableFooterCalc,
|
||||||
theme2: GrafanaTheme2
|
theme2: GrafanaTheme2
|
||||||
): FooterItem[] {
|
): FooterItem[] {
|
||||||
/*
|
/*
|
||||||
Here, `filterFields` is passed to as the `headerGroups[0].headers` array that was destrcutured from the `useTable` hook.
|
Here, `filterFields` is passed as the `headerGroups[0].headers` array
|
||||||
Unfortunately, since the `headerGroups` object is data based ONLY on the rendered "non-hidden" column headers,
|
that was destructured from the `useTable` hook. Unfortunately, since
|
||||||
it will NOT include the Row Number column if it has been toggled off. This will shift the rendering of the footer left 1 column,
|
the `headerGroups` object is data based ONLY on the rendered "non-hidden"
|
||||||
creating an off-by-one issue. This is why we test for a `field.id` of "0". If the condition is truthy, the togglable Row Number column is being rendered,
|
column headers, it will NOT include the Row Number column if it has been
|
||||||
and we can proceed normally. If not, we must add the field data in its place so that the footer data renders in the expected column.
|
toggled off. This will shift the rendering of the footer left 1 column,
|
||||||
|
creating an off-by-one issue. This is why we test for a `field.id` of "0".
|
||||||
|
If the condition is truthy, the togglable Row Number column is being rendered,
|
||||||
|
and we can proceed normally. If not, we must add the field data in its place
|
||||||
|
so that the footer data renders in the expected column.
|
||||||
*/
|
*/
|
||||||
if (!filterFields.some((field) => field.id === '0')) {
|
if (!filterFields.some((field) => field?.id === '0')) {
|
||||||
const length = values.length;
|
const length = values.length;
|
||||||
// Build the additional field that will correct the off-by-one footer issue.
|
// Build the additional field that will correct the off-by-one footer issue.
|
||||||
const fieldToAdd = { id: '0', field: buildFieldsForOptionalRowNums(length) };
|
const fieldToAdd = { id: '0', field: buildFieldsForOptionalRowNums(length) };
|
||||||
filterFields = [fieldToAdd, ...filterFields];
|
filterFields = [fieldToAdd, ...filterFields];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The FooterItems[] are calculated using both the `headerGroups[0].headers`
|
||||||
|
(filterFields) and `rows` (values) destructured from the useTable() hook.
|
||||||
|
This cacluation is based on the data from each index in `filterFields`
|
||||||
|
array as well as the corresponding index in the `values` array.
|
||||||
|
When the user hides a column through an override, the getColumns()
|
||||||
|
hook is invoked, removes said hidden column, sends the updated column
|
||||||
|
data to the useTable() hook, which then builds `headerGroups[0].headers`
|
||||||
|
without the hidden column. However, it doesn't remove the hidden column
|
||||||
|
from the `row` data, instead it substututes the hidden column row data
|
||||||
|
with an `undefined` value. Therefore, the `row` array length never changes,
|
||||||
|
despite the `headerGroups[0].headers` length changing at every column removal.
|
||||||
|
This makes all footer reduce calculations AFTER the first hidden column
|
||||||
|
in the `headerGroups[0].headers` break, since the indexing of both
|
||||||
|
arrays is no longer in parity.
|
||||||
|
|
||||||
|
So, here we simply recursively test for the "hidden" columns
|
||||||
|
from `headerGroups[0].headers`. Each column has an ID property that corresponds
|
||||||
|
to its own index, therefore if (`filterField.id` !== `String(index)`),
|
||||||
|
we know there is one or more hidden columns; at which point we update
|
||||||
|
the index with an ersatz placeholder with just an `id` property.
|
||||||
|
*/
|
||||||
|
addMissingColumnIndex(filterFields);
|
||||||
|
|
||||||
return filterFields.map((data, i) => {
|
return filterFields.map((data, i) => {
|
||||||
if (data.field.type !== FieldType.number) {
|
// Then test for numerical data - this will filter out placeholder `filterFields` as well.
|
||||||
|
if (data?.field?.type !== FieldType.number) {
|
||||||
// Show the reducer type ("Total", "Range", "Count", "Delta", etc) in the first non "Row Number" column, only if it cannot be numerically reduced.
|
// Show the reducer type ("Total", "Range", "Count", "Delta", etc) in the first non "Row Number" column, only if it cannot be numerically reduced.
|
||||||
if (i === 1 && options.reducer && options.reducer.length > 0) {
|
if (i === 1 && options.reducer && options.reducer.length > 0) {
|
||||||
const reducer = fieldReducers.get(options.reducer[0]);
|
const reducer = fieldReducers.get(options.reducer[0]);
|
||||||
return reducer.name;
|
return reducer.name;
|
||||||
}
|
}
|
||||||
// Otherwise return `undefined`, which will render an <EmptyCell />.
|
// Render an <EmptyCell />.
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newField = clone(data.field);
|
let newField = clone(data.field);
|
||||||
newField.values = new ArrayVector(values[i]);
|
newField.values = new ArrayVector(values[data.id]);
|
||||||
newField.state = undefined;
|
newField.state = undefined;
|
||||||
|
|
||||||
data.field = newField;
|
data.field = newField;
|
||||||
|
|
||||||
if (options.fields && options.fields.length > 0) {
|
if (options.fields && options.fields.length > 0) {
|
||||||
const f = options.fields.find((f) => f === data.field.name);
|
const f = options.fields.find((f) => f === data?.field?.name);
|
||||||
if (f) {
|
if (f) {
|
||||||
return getFormattedValue(data.field, options.reducer, theme2);
|
return getFormattedValue(data.field, options.reducer, theme2);
|
||||||
}
|
}
|
||||||
@ -477,3 +506,30 @@ export const defaultRowNumberColumnFieldData: Omit<Field, 'values'> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This recurses through an array of `filterFields` (Array<{ id: string; field?: Field } | undefined>)
|
||||||
|
* and adds back the missing indecies that are removed due to hiding a column through an panel override.
|
||||||
|
* This is necessary to create Array.length parity between the `filterFields` array and the `values` array (any[number]),
|
||||||
|
* since the footer value calculations are based on the corresponding index values of both arrays.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* This function uses the splice() method, and therefore mutates the array.
|
||||||
|
*
|
||||||
|
* @param columns - An array of `filterFields` (Array<{ id: string; field?: Field } | undefined>).
|
||||||
|
* @returns void; this function returns nothing; it only mutates values as a side effect.
|
||||||
|
*/
|
||||||
|
function addMissingColumnIndex(columns: Array<{ id: string; field?: Field } | undefined>): void {
|
||||||
|
const missingIndex = columns.findIndex((field, index) => field?.id !== String(index));
|
||||||
|
|
||||||
|
// Base case
|
||||||
|
if (missingIndex === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splice in missing column
|
||||||
|
columns.splice(missingIndex, 0, { id: String(missingIndex) });
|
||||||
|
|
||||||
|
// Recurse
|
||||||
|
addMissingColumnIndex(columns);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user