From fc371aed113a3bbf5808f71d36d96bb7bebb97f0 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Thu, 3 Nov 2022 07:06:52 +0200 Subject: [PATCH 1/7] WIP --- .../src/transformations/fieldReducer.ts | 2 +- .../src/components/Table/FooterRow.tsx | 1 + .../grafana-ui/src/components/Table/Table.tsx | 34 ++++++++++++++----- .../grafana-ui/src/components/Table/types.ts | 1 + public/app/plugins/panel/table/models.gen.ts | 1 + public/app/plugins/panel/table/module.tsx | 7 ++++ 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/grafana-data/src/transformations/fieldReducer.ts b/packages/grafana-data/src/transformations/fieldReducer.ts index 59691aaf81cc..4ce873ab34c6 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.ts @@ -291,7 +291,7 @@ export function doStandardCalcs(field: Field, ignoreNulls: boolean, nullAsZero: } as FieldCalcs; const data = field.values; - calcs.count = data.length; + calcs.count = ignoreNulls ? data.length : data.toArray().filter((val) => val != null).length; const isNumberField = field.type === FieldType.number || FieldType.time; diff --git a/packages/grafana-ui/src/components/Table/FooterRow.tsx b/packages/grafana-ui/src/components/Table/FooterRow.tsx index 59a4659f3d8b..475aaec5680d 100644 --- a/packages/grafana-ui/src/components/Table/FooterRow.tsx +++ b/packages/grafana-ui/src/components/Table/FooterRow.tsx @@ -14,6 +14,7 @@ export interface FooterRowProps { footerGroups: HeaderGroup[]; footerValues: FooterItem[]; isPaginationVisible: boolean; + isCountAllSet: boolean; height: number; } diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index fe69cf86ba31..6ee4da12f7ef 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -11,7 +11,7 @@ import { } from 'react-table'; import { FixedSizeList } from 'react-window'; -import { DataFrame, getFieldDisplayName, Field } from '@grafana/data'; +import { DataFrame, getFieldDisplayName, Field, ReducerID } from '@grafana/data'; import { useStyles2, useTheme2 } from '../../themes'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; @@ -232,14 +232,31 @@ export const Table = memo((props: Props) => { } if (footerOptions.show) { - setFooterItems( - getFooterItems( - headerGroups[0].headers as unknown as Array<{ field: Field }>, - createFooterCalculationValues(rows), - footerOptions, - theme - ) + const footerItems = getFooterItems( + headerGroups[0].headers as unknown as Array<{ field: Field }>, + createFooterCalculationValues(rows), + footerOptions, + theme ); + + if ( + props.footerOptions?.countAll && + footerItems && + typeof footerItems[0] === 'string' && + footerItems[0].toLowerCase() === ReducerID.count + ) { + const maxCount = footerItems.reduce((max, item) => { + if (typeof item === 'string' && !isNaN(+item)) { + return Math.max(max, +item); + } + return max; + }, 0); + + const footerItemsCountAll: FooterItem[] = [footerItems[0], maxCount.toString()]; + setFooterItems(footerItemsCountAll); + } else { + setFooterItems(footerItems); + } } else { setFooterItems(undefined); } @@ -381,6 +398,7 @@ export const Table = memo((props: Props) => { (TablePane defaultValue: [ReducerID.sum], showIf: (cfg) => cfg.footer?.show, }) + .addBooleanSwitch({ + path: 'footer.countAll', + category: [footerCategory], + name: 'Count all data', + defaultValue: defaultPanelOptions.footer?.countAll, + showIf: (cfg) => cfg.footer?.reducer?.length === 1 && cfg.footer?.reducer[0] === ReducerID.count, + }) .addMultiSelect({ path: 'footer.fields', category: [footerCategory], From af6d441059ca38917f91ebc38eb61afdcb48b182 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Thu, 3 Nov 2022 14:11:55 +0200 Subject: [PATCH 2/7] TablePanel: Add support for Count calculation per column or per entire dataset --- .betterer.results | 3 ++- .../grafana-ui/src/components/Table/FooterRow.tsx | 12 ++++++++---- packages/grafana-ui/src/components/Table/Table.tsx | 13 ++++--------- packages/grafana-ui/src/components/Table/utils.ts | 5 +++-- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.betterer.results b/.betterer.results index 5b3c6e7e05ba..98e64e368414 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1502,7 +1502,8 @@ exports[`better eslint`] = { ], "packages/grafana-ui/src/components/Table/FooterRow.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] ], "packages/grafana-ui/src/components/Table/HeaderRow.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] diff --git a/packages/grafana-ui/src/components/Table/FooterRow.tsx b/packages/grafana-ui/src/components/Table/FooterRow.tsx index 475aaec5680d..5879bfea4be9 100644 --- a/packages/grafana-ui/src/components/Table/FooterRow.tsx +++ b/packages/grafana-ui/src/components/Table/FooterRow.tsx @@ -19,7 +19,7 @@ export interface FooterRowProps { } export const FooterRow = (props: FooterRowProps) => { - const { totalColumnsWidth, footerGroups, height, isPaginationVisible } = props; + const { totalColumnsWidth, footerGroups, height, isPaginationVisible, isCountAllSet } = props; const e2eSelectorsTable = selectors.components.Panels.Visualization.Table; const tableStyles = useStyles2(getTableStyles); @@ -42,7 +42,7 @@ export const FooterRow = (props: FooterRowProps) => { style={height ? { height: `${height}px` } : undefined} > {footerGroup.headers.map((column: ColumnInstance, index: number) => - renderFooterCell(column, tableStyles, height) + isCountAllSet && index > 0 ? EmptyCell : renderFooterCell(column, tableStyles, height, isCountAllSet) )} ); @@ -51,7 +51,7 @@ export const FooterRow = (props: FooterRowProps) => { ); }; -function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, height?: number) { +function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, height?: number, isCountAllSet?: boolean) { const footerProps = column.getHeaderProps(); if (!footerProps) { @@ -72,10 +72,14 @@ function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, heig ); } -export function getFooterValue(index: number, footerValues?: FooterItem[]) { +export function getFooterValue(index: number, footerValues?: FooterItem[], isCountAllSet?: boolean) { if (footerValues === undefined) { return EmptyCell; } + if (isCountAllSet) { + return FooterCell({ value: [{ Count: footerValues[index] as string }] }); + } + return FooterCell({ value: footerValues[index] }); } diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 6ee4da12f7ef..eb8a18726bda 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -177,8 +177,8 @@ export const Table = memo((props: Props) => { // React-table column definitions const memoizedColumns = useMemo( - () => getColumns(data, width, columnMinWidth, footerItems), - [data, width, columnMinWidth, footerItems] + () => getColumns(data, width, columnMinWidth, footerItems, footerOptions?.countAll), + [data, width, columnMinWidth, footerItems, footerOptions] ); // Internal react table state reducer @@ -239,12 +239,7 @@ export const Table = memo((props: Props) => { theme ); - if ( - props.footerOptions?.countAll && - footerItems && - typeof footerItems[0] === 'string' && - footerItems[0].toLowerCase() === ReducerID.count - ) { + if (footerOptions.countAll && footerItems) { const maxCount = footerItems.reduce((max, item) => { if (typeof item === 'string' && !isNaN(+item)) { return Math.max(max, +item); @@ -252,7 +247,7 @@ export const Table = memo((props: Props) => { return max; }, 0); - const footerItemsCountAll: FooterItem[] = [footerItems[0], maxCount.toString()]; + const footerItemsCountAll: FooterItem[] = [maxCount.toString()]; setFooterItems(footerItemsCountAll); } else { setFooterItems(footerItems); diff --git a/packages/grafana-ui/src/components/Table/utils.ts b/packages/grafana-ui/src/components/Table/utils.ts index ce988462f056..147a6e2ca4be 100644 --- a/packages/grafana-ui/src/components/Table/utils.ts +++ b/packages/grafana-ui/src/components/Table/utils.ts @@ -61,7 +61,8 @@ export function getColumns( data: DataFrame, availableWidth: number, columnMinWidth: number, - footerValues?: FooterItem[] + footerValues?: FooterItem[], + isCountAllSet?: boolean ): GrafanaTableColumn[] { const columns: GrafanaTableColumn[] = []; let fieldCountWithoutWidth = 0; @@ -104,7 +105,7 @@ export function getColumns( minWidth: fieldTableOptions.minWidth ?? columnMinWidth, filter: memoizeOne(filterByValue(field)), justifyContent: getTextAlign(field), - Footer: getFooterValue(fieldIndex, footerValues), + Footer: getFooterValue(fieldIndex, footerValues, isCountAllSet), }); } From aca5c2bc2c5ee63d603c839125a37c7ddca245b9 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Thu, 3 Nov 2022 14:48:47 +0200 Subject: [PATCH 3/7] refactor --- packages/grafana-ui/src/components/Table/Table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index eb8a18726bda..fa62e207df38 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -11,7 +11,7 @@ import { } from 'react-table'; import { FixedSizeList } from 'react-window'; -import { DataFrame, getFieldDisplayName, Field, ReducerID } from '@grafana/data'; +import { DataFrame, getFieldDisplayName, Field } from '@grafana/data'; import { useStyles2, useTheme2 } from '../../themes'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; From a3951f8266d8d7c6bfa8f0258e293a4d5ce9ce80 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Fri, 4 Nov 2022 12:52:36 +0200 Subject: [PATCH 4/7] refactor --- .betterer.results | 3 +-- .../grafana-ui/src/components/Table/FooterRow.tsx | 11 ++++++++--- packages/grafana-ui/src/components/Table/Table.tsx | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.betterer.results b/.betterer.results index 98e64e368414..5b3c6e7e05ba 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1502,8 +1502,7 @@ exports[`better eslint`] = { ], "packages/grafana-ui/src/components/Table/FooterRow.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "packages/grafana-ui/src/components/Table/HeaderRow.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] diff --git a/packages/grafana-ui/src/components/Table/FooterRow.tsx b/packages/grafana-ui/src/components/Table/FooterRow.tsx index 5879bfea4be9..ca4c5a879c7a 100644 --- a/packages/grafana-ui/src/components/Table/FooterRow.tsx +++ b/packages/grafana-ui/src/components/Table/FooterRow.tsx @@ -41,8 +41,8 @@ export const FooterRow = (props: FooterRowProps) => { data-testid={e2eSelectorsTable.footer} style={height ? { height: `${height}px` } : undefined} > - {footerGroup.headers.map((column: ColumnInstance, index: number) => - isCountAllSet && index > 0 ? EmptyCell : renderFooterCell(column, tableStyles, height, isCountAllSet) + {footerGroup.headers.map((column: ColumnInstance) => + renderFooterCell(column, tableStyles, height, isCountAllSet) )} ); @@ -78,7 +78,12 @@ export function getFooterValue(index: number, footerValues?: FooterItem[], isCou } if (isCountAllSet) { - return FooterCell({ value: [{ Count: footerValues[index] as string }] }); + const count = footerValues[index]; + if (typeof count !== 'string') { + return EmptyCell; + } + + return FooterCell({ value: [{ Count: count }] }); } return FooterCell({ value: footerValues[index] }); diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index fa62e207df38..ff41359c00cf 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -247,7 +247,9 @@ export const Table = memo((props: Props) => { return max; }, 0); - const footerItemsCountAll: FooterItem[] = [maxCount.toString()]; + const footerItemsCountAll: FooterItem[] = new Array(footerItems.length).fill(undefined); + footerItemsCountAll[0] = maxCount.toString(); + console.log(footerItemsCountAll); setFooterItems(footerItemsCountAll); } else { setFooterItems(footerItems); From 480930f8b35eb681a0b6c4f555638bcb328ab4a1 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Wed, 9 Nov 2022 17:08:58 +0200 Subject: [PATCH 5/7] refactor + fixes --- .../src/components/Table/FooterRow.tsx | 9 ++-- .../grafana-ui/src/components/Table/Table.tsx | 53 +++++++++---------- public/app/plugins/panel/table/module.tsx | 2 +- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/packages/grafana-ui/src/components/Table/FooterRow.tsx b/packages/grafana-ui/src/components/Table/FooterRow.tsx index ca4c5a879c7a..8b6813009f42 100644 --- a/packages/grafana-ui/src/components/Table/FooterRow.tsx +++ b/packages/grafana-ui/src/components/Table/FooterRow.tsx @@ -14,12 +14,11 @@ export interface FooterRowProps { footerGroups: HeaderGroup[]; footerValues: FooterItem[]; isPaginationVisible: boolean; - isCountAllSet: boolean; height: number; } export const FooterRow = (props: FooterRowProps) => { - const { totalColumnsWidth, footerGroups, height, isPaginationVisible, isCountAllSet } = props; + const { totalColumnsWidth, footerGroups, height, isPaginationVisible } = props; const e2eSelectorsTable = selectors.components.Panels.Visualization.Table; const tableStyles = useStyles2(getTableStyles); @@ -41,9 +40,7 @@ export const FooterRow = (props: FooterRowProps) => { data-testid={e2eSelectorsTable.footer} style={height ? { height: `${height}px` } : undefined} > - {footerGroup.headers.map((column: ColumnInstance) => - renderFooterCell(column, tableStyles, height, isCountAllSet) - )} + {footerGroup.headers.map((column: ColumnInstance) => renderFooterCell(column, tableStyles, height))} ); })} @@ -51,7 +48,7 @@ export const FooterRow = (props: FooterRowProps) => { ); }; -function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, height?: number, isCountAllSet?: boolean) { +function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, height?: number) { const footerProps = column.getHeaderProps(); if (!footerProps) { diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index ff41359c00cf..a1f517647525 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -11,7 +11,7 @@ import { } from 'react-table'; import { FixedSizeList } from 'react-window'; -import { DataFrame, getFieldDisplayName, Field } from '@grafana/data'; +import { DataFrame, getFieldDisplayName, Field, ReducerID } from '@grafana/data'; import { useStyles2, useTheme2 } from '../../themes'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; @@ -175,10 +175,17 @@ export const Table = memo((props: Props) => { return Array(data.length).fill(0); }, [data]); + const isCountAllSet = Boolean( + footerOptions?.countAll && + footerOptions.reducer && + footerOptions.reducer.length && + footerOptions.reducer[0] === ReducerID.count + ); + // React-table column definitions const memoizedColumns = useMemo( - () => getColumns(data, width, columnMinWidth, footerItems, footerOptions?.countAll), - [data, width, columnMinWidth, footerItems, footerOptions] + () => getColumns(data, width, columnMinWidth, footerItems, isCountAllSet), + [data, width, columnMinWidth, footerItems, isCountAllSet] ); // Internal react table state reducer @@ -231,31 +238,24 @@ export const Table = memo((props: Props) => { return; } - if (footerOptions.show) { - const footerItems = getFooterItems( - headerGroups[0].headers as unknown as Array<{ field: Field }>, - createFooterCalculationValues(rows), - footerOptions, - theme - ); + if (!footerOptions.show) { + setFooterItems(undefined); + return; + } - if (footerOptions.countAll && footerItems) { - const maxCount = footerItems.reduce((max, item) => { - if (typeof item === 'string' && !isNaN(+item)) { - return Math.max(max, +item); - } - return max; - }, 0); - - const footerItemsCountAll: FooterItem[] = new Array(footerItems.length).fill(undefined); - footerItemsCountAll[0] = maxCount.toString(); - console.log(footerItemsCountAll); - setFooterItems(footerItemsCountAll); - } else { - setFooterItems(footerItems); - } + const footerItems = getFooterItems( + headerGroups[0].headers as unknown as Array<{ field: Field }>, + createFooterCalculationValues(rows), + footerOptions, + theme + ); + + if (isCountAllSet) { + const footerItemsCountAll: FooterItem[] = new Array(footerItems.length).fill(undefined); + footerItemsCountAll[0] = data.length.toString(); + setFooterItems(footerItemsCountAll); } else { - setFooterItems(undefined); + setFooterItems(footerItems); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [footerOptions, theme, state.filters, data]); @@ -395,7 +395,6 @@ export const Table = memo((props: Props) => { (TablePane }, }, defaultValue: '', - showIf: (cfg) => cfg.footer?.show, + showIf: (cfg) => cfg.footer?.show && !cfg.footer?.countAll, }) .addCustomEditor({ id: 'footer.enablePagination', From d5cce99af4610838458e33d07203da9530140c9a Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Thu, 10 Nov 2022 10:09:40 +0200 Subject: [PATCH 6/7] refactor + tests --- .../src/components/Table/FooterRow.tsx | 4 +- .../src/components/Table/Table.test.tsx | 114 ++++++++++++++++++ .../grafana-ui/src/components/Table/Table.tsx | 16 +-- .../grafana-ui/src/components/Table/types.ts | 2 +- .../grafana-ui/src/components/Table/utils.ts | 4 +- public/app/plugins/panel/table/models.cue | 1 + public/app/plugins/panel/table/models.gen.ts | 2 +- public/app/plugins/panel/table/module.tsx | 11 +- 8 files changed, 136 insertions(+), 18 deletions(-) diff --git a/packages/grafana-ui/src/components/Table/FooterRow.tsx b/packages/grafana-ui/src/components/Table/FooterRow.tsx index 8b6813009f42..8e9cc1e423c1 100644 --- a/packages/grafana-ui/src/components/Table/FooterRow.tsx +++ b/packages/grafana-ui/src/components/Table/FooterRow.tsx @@ -69,12 +69,12 @@ function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, heig ); } -export function getFooterValue(index: number, footerValues?: FooterItem[], isCountAllSet?: boolean) { +export function getFooterValue(index: number, footerValues?: FooterItem[], isCountRowsSet?: boolean) { if (footerValues === undefined) { return EmptyCell; } - if (isCountAllSet) { + if (isCountRowsSet) { const count = footerValues[index]; if (typeof count !== 'string') { return EmptyCell; diff --git a/packages/grafana-ui/src/components/Table/Table.test.tsx b/packages/grafana-ui/src/components/Table/Table.test.tsx index bd11b19bde17..79bee355ef29 100644 --- a/packages/grafana-ui/src/components/Table/Table.test.tsx +++ b/packages/grafana-ui/src/components/Table/Table.test.tsx @@ -398,4 +398,118 @@ describe('Table', () => { expect(() => screen.getByTestId('table-footer')).toThrow('Unable to find an element'); }); }); + describe('on table footer enabled and count calculation selected', () => { + it('should show count of non-null values', async () => { + getTestContext({ + footerOptions: { show: true, reducer: ['count'] }, + data: toDataFrame({ + name: 'A', + fields: [ + { + name: 'number', + type: FieldType.number, + values: [1, 1, 1, 2, null], + config: { + custom: { + filterable: true, + }, + }, + }, + ], + }), + }); + + expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('4'); + }); + + it('should show count of rows when `count rows` is selected', async () => { + getTestContext({ + footerOptions: { show: true, reducer: ['count'], countRows: true }, + data: toDataFrame({ + name: 'A', + fields: [ + { + name: 'number1', + type: FieldType.number, + values: [1, 1, 1, 2, null], + config: { + custom: { + filterable: true, + }, + }, + }, + ], + }), + }); + + expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual( + 'Count:' + ); + expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1].textContent).toEqual('5'); + }); + + it('should show correct counts when turning `count rows` on and off', async () => { + const { rerender } = getTestContext({ + footerOptions: { show: true, reducer: ['count'], countRows: true }, + data: toDataFrame({ + name: 'A', + fields: [ + { + name: 'number1', + type: FieldType.number, + values: [1, 1, 1, 2, null], + config: { + custom: { + filterable: true, + }, + }, + }, + ], + }), + }); + + expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual( + 'Count:' + ); + expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1].textContent).toEqual('5'); + + const onSortByChange = jest.fn(); + const onCellFilterAdded = jest.fn(); + const onColumnResize = jest.fn(); + const props: Props = { + ariaLabel: 'aria-label', + data: getDefaultDataFrame(), + height: 600, + width: 800, + onSortByChange, + onCellFilterAdded, + onColumnResize, + }; + + const propOverrides = { + footerOptions: { show: true, reducer: ['count'], countRows: false }, + data: toDataFrame({ + name: 'A', + fields: [ + { + name: 'number', + type: FieldType.number, + values: [1, 1, 1, 2, null], + config: { + custom: { + filterable: true, + }, + }, + }, + ], + }), + }; + + Object.assign(props, propOverrides); + + rerender(); + + expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('4'); + }); + }); }); diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index a1f517647525..0cca982c26ef 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -175,8 +175,8 @@ export const Table = memo((props: Props) => { return Array(data.length).fill(0); }, [data]); - const isCountAllSet = Boolean( - footerOptions?.countAll && + const isCountRowsSet = Boolean( + footerOptions?.countRows && footerOptions.reducer && footerOptions.reducer.length && footerOptions.reducer[0] === ReducerID.count @@ -184,8 +184,8 @@ export const Table = memo((props: Props) => { // React-table column definitions const memoizedColumns = useMemo( - () => getColumns(data, width, columnMinWidth, footerItems, isCountAllSet), - [data, width, columnMinWidth, footerItems, isCountAllSet] + () => getColumns(data, width, columnMinWidth, footerItems, isCountRowsSet), + [data, width, columnMinWidth, footerItems, isCountRowsSet] ); // Internal react table state reducer @@ -250,10 +250,10 @@ export const Table = memo((props: Props) => { theme ); - if (isCountAllSet) { - const footerItemsCountAll: FooterItem[] = new Array(footerItems.length).fill(undefined); - footerItemsCountAll[0] = data.length.toString(); - setFooterItems(footerItemsCountAll); + if (isCountRowsSet) { + const footerItemsCountRows: FooterItem[] = new Array(footerItems.length).fill(undefined); + footerItemsCountRows[0] = data.length.toString(); + setFooterItems(footerItemsCountRows); } else { setFooterItems(footerItems); } diff --git a/packages/grafana-ui/src/components/Table/types.ts b/packages/grafana-ui/src/components/Table/types.ts index f11c2ee9aae1..6942ec7594bb 100644 --- a/packages/grafana-ui/src/components/Table/types.ts +++ b/packages/grafana-ui/src/components/Table/types.ts @@ -50,5 +50,5 @@ export interface TableFooterCalc { reducer: string[]; // actually 1 value fields?: string[]; enablePagination?: boolean; - countAll?: boolean; + countRows?: boolean; } diff --git a/packages/grafana-ui/src/components/Table/utils.ts b/packages/grafana-ui/src/components/Table/utils.ts index 147a6e2ca4be..c829567f0bdc 100644 --- a/packages/grafana-ui/src/components/Table/utils.ts +++ b/packages/grafana-ui/src/components/Table/utils.ts @@ -62,7 +62,7 @@ export function getColumns( availableWidth: number, columnMinWidth: number, footerValues?: FooterItem[], - isCountAllSet?: boolean + isCountRowsSet?: boolean ): GrafanaTableColumn[] { const columns: GrafanaTableColumn[] = []; let fieldCountWithoutWidth = 0; @@ -105,7 +105,7 @@ export function getColumns( minWidth: fieldTableOptions.minWidth ?? columnMinWidth, filter: memoizeOne(filterByValue(field)), justifyContent: getTextAlign(field), - Footer: getFooterValue(fieldIndex, footerValues, isCountAllSet), + Footer: getFooterValue(fieldIndex, footerValues, isCountRowsSet), }); } diff --git a/public/app/plugins/panel/table/models.cue b/public/app/plugins/panel/table/models.cue index 798db42b6c63..42bd7463dcaf 100644 --- a/public/app/plugins/panel/table/models.cue +++ b/public/app/plugins/panel/table/models.cue @@ -30,6 +30,7 @@ Panel: thema.#Lineage & { showHeader: bool | *true showTypeIcons: bool | *false sortBy?: [...ui.TableSortByFieldState] + footerOptions?: [...ui.TableFooterCalc] } @cuetsy(kind="interface") PanelFieldConfig: ui.TableFieldOptions & {} @cuetsy(kind="interface") }, diff --git a/public/app/plugins/panel/table/models.gen.ts b/public/app/plugins/panel/table/models.gen.ts index a102cd7f6b02..c4d60a02e10a 100644 --- a/public/app/plugins/panel/table/models.gen.ts +++ b/public/app/plugins/panel/table/models.gen.ts @@ -27,7 +27,7 @@ export const defaultPanelOptions: PanelOptions = { footer: { show: false, reducer: [], - countAll: false, + countRows: false, }, }; diff --git a/public/app/plugins/panel/table/module.tsx b/public/app/plugins/panel/table/module.tsx index 0c2ad8fa05ed..5872e486f2ba 100644 --- a/public/app/plugins/panel/table/module.tsx +++ b/public/app/plugins/panel/table/module.tsx @@ -132,10 +132,11 @@ export const plugin = new PanelPlugin(TablePane showIf: (cfg) => cfg.footer?.show, }) .addBooleanSwitch({ - path: 'footer.countAll', + path: 'footer.countRows', category: [footerCategory], - name: 'Count all data', - defaultValue: defaultPanelOptions.footer?.countAll, + name: 'Count rows', + description: 'Display a single count for all data rows', + defaultValue: defaultPanelOptions.footer?.countRows, showIf: (cfg) => cfg.footer?.reducer?.length === 1 && cfg.footer?.reducer[0] === ReducerID.count, }) .addMultiSelect({ @@ -163,7 +164,9 @@ export const plugin = new PanelPlugin(TablePane }, }, defaultValue: '', - showIf: (cfg) => cfg.footer?.show && !cfg.footer?.countAll, + showIf: (cfg) => + (cfg.footer?.show && !cfg.footer?.countRows) || + (cfg.footer?.reducer?.length === 1 && cfg.footer?.reducer[0] !== ReducerID.count), }) .addCustomEditor({ id: 'footer.enablePagination', From 1c6a30ba18d0f4cc70d1b301eac8b0edfaf9d909 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Mon, 28 Nov 2022 09:47:04 +0200 Subject: [PATCH 7/7] Docs and cue model fix --- .../visualizations/table/index.md | 12 ++++++++++++ public/app/plugins/panel/table/models.cue | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/sources/panels-visualizations/visualizations/table/index.md b/docs/sources/panels-visualizations/visualizations/table/index.md index f795848e51b1..914e09ba23c5 100644 --- a/docs/sources/panels-visualizations/visualizations/table/index.md +++ b/docs/sources/panels-visualizations/visualizations/table/index.md @@ -160,3 +160,15 @@ Columns with filters applied have a blue funnel displayed next to the title. {{< figure src="/static/img/docs/tables/filtered-column.png" max-width="500px" caption="Filtered column" class="docs-image--no-shadow" >}} To remove the filter, click the blue funnel icon and then click **Clear filter**. + +## Table footer + +You can use the table footer to show [calculations]({{< relref "../../calculation-types/" >}}) on fields. + +After enabling the table footer, you can select your **Calculation** and select the **Fields** that should be calculated. Not selecting any field apply the calculation to all numeric fields. + +### Count rows + +On selecting the **Count** calculation, you will see the **Count rows** switch. + +By enabling this option the footer will show the number of rows in the dataset instead of the number of values in the selected fields. diff --git a/public/app/plugins/panel/table/models.cue b/public/app/plugins/panel/table/models.cue index 42bd7463dcaf..798db42b6c63 100644 --- a/public/app/plugins/panel/table/models.cue +++ b/public/app/plugins/panel/table/models.cue @@ -30,7 +30,6 @@ Panel: thema.#Lineage & { showHeader: bool | *true showTypeIcons: bool | *false sortBy?: [...ui.TableSortByFieldState] - footerOptions?: [...ui.TableFooterCalc] } @cuetsy(kind="interface") PanelFieldConfig: ui.TableFieldOptions & {} @cuetsy(kind="interface") },