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()) {
|
||||
const fieldTableOptions = (field.config.custom || {}) as TableFieldOptions;
|
||||
|
||||
if (fieldTableOptions.hidden) {
|
||||
continue;
|
||||
}
|
||||
@ -319,43 +318,73 @@ function toNumber(value: any): number {
|
||||
}
|
||||
|
||||
export function getFooterItems(
|
||||
filterFields: Array<{ id: string; field: Field }>,
|
||||
filterFields: Array<{ id: string; field?: Field } | undefined>,
|
||||
values: any[number],
|
||||
options: TableFooterCalc,
|
||||
theme2: GrafanaTheme2
|
||||
): FooterItem[] {
|
||||
/*
|
||||
Here, `filterFields` is passed to as the `headerGroups[0].headers` array that was destrcutured from the `useTable` hook.
|
||||
Unfortunately, since the `headerGroups` object is data based ONLY on the rendered "non-hidden" column headers,
|
||||
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,
|
||||
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.
|
||||
Here, `filterFields` is passed as the `headerGroups[0].headers` array
|
||||
that was destructured from the `useTable` hook. Unfortunately, since
|
||||
the `headerGroups` object is data based ONLY on the rendered "non-hidden"
|
||||
column headers, 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,
|
||||
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;
|
||||
// Build the additional field that will correct the off-by-one footer issue.
|
||||
const fieldToAdd = { id: '0', field: buildFieldsForOptionalRowNums(length) };
|
||||
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) => {
|
||||
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.
|
||||
if (i === 1 && options.reducer && options.reducer.length > 0) {
|
||||
const reducer = fieldReducers.get(options.reducer[0]);
|
||||
return reducer.name;
|
||||
}
|
||||
// Otherwise return `undefined`, which will render an <EmptyCell />.
|
||||
// Render an <EmptyCell />.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let newField = clone(data.field);
|
||||
newField.values = new ArrayVector(values[i]);
|
||||
newField.values = new ArrayVector(values[data.id]);
|
||||
newField.state = undefined;
|
||||
|
||||
data.field = newField;
|
||||
|
||||
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) {
|
||||
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