Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TablePanel: Add support for Count calculation per column or per entire dataset #58134

Merged
merged 10 commits into from Nov 28, 2022
Expand Up @@ -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;

Expand Down
20 changes: 15 additions & 5 deletions packages/grafana-ui/src/components/Table/FooterRow.tsx
Expand Up @@ -14,11 +14,12 @@ export interface FooterRowProps {
footerGroups: HeaderGroup[];
footerValues: FooterItem[];
isPaginationVisible: boolean;
isCountAllSet: boolean;
height: number;
}

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);

Expand All @@ -40,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) =>
renderFooterCell(column, tableStyles, height)
{footerGroup.headers.map((column: ColumnInstance) =>
renderFooterCell(column, tableStyles, height, isCountAllSet)
)}
</div>
);
Expand All @@ -50,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) {
Expand All @@ -71,10 +72,19 @@ 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) {
const count = footerValues[index];
if (typeof count !== 'string') {
return EmptyCell;
}

return FooterCell({ value: [{ Count: count }] });
}

return FooterCell({ value: footerValues[index] });
}
33 changes: 24 additions & 9 deletions packages/grafana-ui/src/components/Table/Table.tsx
Expand Up @@ -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
Expand Down Expand Up @@ -232,14 +232,28 @@ 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 (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);
}
} else {
setFooterItems(undefined);
}
Expand Down Expand Up @@ -381,6 +395,7 @@ export const Table = memo((props: Props) => {
<FooterRow
height={footerHeight}
isPaginationVisible={Boolean(enablePagination)}
isCountAllSet={Boolean(props.footerOptions?.countAll)}
footerValues={footerItems}
footerGroups={footerGroups}
totalColumnsWidth={totalColumnsWidth}
Expand Down
1 change: 1 addition & 0 deletions packages/grafana-ui/src/components/Table/types.ts
Expand Up @@ -50,4 +50,5 @@ export interface TableFooterCalc {
reducer: string[]; // actually 1 value
fields?: string[];
enablePagination?: boolean;
countAll?: boolean;
}
5 changes: 3 additions & 2 deletions packages/grafana-ui/src/components/Table/utils.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
});
}

Expand Down
1 change: 1 addition & 0 deletions public/app/plugins/panel/table/models.gen.ts
Expand Up @@ -27,6 +27,7 @@ export const defaultPanelOptions: PanelOptions = {
footer: {
zoltanbedi marked this conversation as resolved.
Show resolved Hide resolved
show: false,
reducer: [],
countAll: false,
},
};

Expand Down
7 changes: 7 additions & 0 deletions public/app/plugins/panel/table/module.tsx
Expand Up @@ -131,6 +131,13 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
defaultValue: [ReducerID.sum],
showIf: (cfg) => cfg.footer?.show,
})
.addBooleanSwitch({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I'm thinking we should add a description here and maybe change the name of this option as well to make it more clear.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I'd say we might to reverse the language, like "Count Per Field" with a description along the lines of "Count the non-empty results of each field separately"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this I understand we also reverse the functionality. E.g: We show a count for all fields in one column and when the "Count Per Field" switch is active we show a count for each field. This would change all existing table panels for the users. Is this something we'd want?

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],
Expand Down