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

[release-4.4] Bug 1805201: Improve monitoring dashboards table layout #4385

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions frontend/public/components/monitoring/_monitoring.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
margin-right: 20px;
}

.monitoring-dashboards__label-column-header {
// Set a min-width to avoid aggressive wrapping, which makes the table much taller.
min-width: 175px;
@media (min-width: $screen-lg-min) {
min-width: 225px;
}
}

.monitoring-dashboards__options {
display: flex;
float: right;
Expand Down
5 changes: 2 additions & 3 deletions frontend/public/components/monitoring/dashboards/format.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from 'react';
import {
humanizeBinaryBytes,
humanizeDecimalBytesPerSec,
humanizeNumber,
humanizePacketsPerSec,
} from '../../utils';

Expand All @@ -29,8 +30,6 @@ export const formatNumber = (s: string, decimals = 2, format = 'short'): React.R
case 'short':
// fall through
default:
return Intl.NumberFormat(undefined, {
maximumFractionDigits: decimals,
}).format(value);
return humanizeNumber(value).string;
}
};
69 changes: 46 additions & 23 deletions frontend/public/components/monitoring/dashboards/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
sortable,
Table as PFTable,
TableBody,
TableGridBreakpoint,
TableHeader,
TableVariant,
} from '@patternfly/react-table';
Expand All @@ -18,6 +19,35 @@ import { getPrometheusURL, PrometheusEndpoint } from '../../graphs/helpers';
import { EmptyBox, usePoll, useSafeFetch } from '../../utils';
import { TablePagination } from '../metrics';

type AugmentedColumnStyle = ColumnStyle & {
className?: string;
};

// Get the columns from the panel styles. Filters out hidden columns and orders
// them so the label columns are displayed first.
const getColumns = (styles: ColumnStyle[]): AugmentedColumnStyle[] => {
const labelColumns = [];
const valueColumns = [];
styles.forEach((col: ColumnStyle) => {
// Remove hidden or regex columns.
if (col.type === 'hidden' || col.pattern.startsWith('/') || !col.alias) {
return;
}

if (col.pattern.startsWith('Value #')) {
valueColumns.push(col);
} else {
labelColumns.push({
...col,
className: 'monitoring-dashboards__label-column-header',
});
}
});

// Show non-value columns first.
return [...labelColumns, ...valueColumns];
};

const paginationOptions = [5, 10, 20, 50, 100].map((n) => ({
title: n.toString(),
value: n,
Expand All @@ -30,7 +60,8 @@ const Table: React.FC<Props> = ({ panel, pollInterval, queries }) => {
const [page, setPage] = React.useState(1);
const [perPage, setPerPage] = React.useState(5);
const [sortBy, setSortBy] = React.useState<ISortBy>({ index: 0, direction: 'asc' });
const onSort = (e, i, direction) => setSortBy({ index: i, direction });
const onSort = (e, index: ISortBy['index'], direction: ISortBy['direction']) =>
setSortBy({ index, direction });
const safeFetch = React.useCallback(useSafeFetch(), []);

const tick = () => {
Expand All @@ -42,11 +73,10 @@ const Table: React.FC<Props> = ({ panel, pollInterval, queries }) => {
.then((responses: PrometheusResponse[]) => {
setError(undefined);
setLoading(false);
// FIXME: This makes the following assumptions about the data:
// Note: This makes the following assumptions about the data:
// 1. The transform is `table`
// 2. The results will only have one label, and it is present in all query responses.
// 3. The value will be an instance vector (single value).
// 4. The time column is hidden.
// 2. The value will be an instance vector (single value).
// 3. The time column is hidden.
// The Grafana implementation is much more involved. See
// https://grafana.com/docs/grafana/latest/features/panels/table_panel/#merge-multiple-queries-per-table
setData(
Expand Down Expand Up @@ -84,16 +114,7 @@ const Table: React.FC<Props> = ({ panel, pollInterval, queries }) => {
return <EmptyBox label="Data" />;
}

// Make a copy of the array and move the first label to the front.
// FIXME: Remove magic number for label index.
const styles = [...panel.styles];
const labelIndex = queries.length + 1;
styles.unshift(styles.splice(labelIndex, 1)[0]);

// Remove hidden and regex columns.
const columns: ColumnStyle[] = styles.filter(
({ type, pattern, alias }) => type !== 'hidden' && !pattern.startsWith('/') && alias,
);
const columns: AugmentedColumnStyle[] = getColumns(panel.styles);

// Sort the data.
const sort = (row) => {
Expand All @@ -110,46 +131,48 @@ const Table: React.FC<Props> = ({ panel, pollInterval, queries }) => {
return _.isFinite(num) ? num : val;
};
const sortedData = _.orderBy(data, [sort], [sortBy.direction]);
const visibleData = sortedData.slice((page - 1) * perPage, page * perPage);

// Format the table rows.
const formattedRows: string[][] = sortedData.map((values: { [key: string]: string }) => {
return columns.reduce((acc, { type, decimals = 2, pattern, unit = '' }) => {
const rows: React.ReactNode[][] = visibleData.map((values: { [key: string]: string }) => {
return columns.reduce((acc: React.ReactNode[], { type, decimals = 2, pattern, unit = '' }) => {
const value = values[pattern];
switch (type) {
case 'number':
acc.push(formatNumber(value, decimals, unit));
break;
default:
acc.push(value || '-');
acc.push(value || <span className="text-muted">-</span>);
}
return acc;
}, []);
});

const headers = columns.map(({ alias: title }) => ({
const headers = columns.map(({ alias: title, className }) => ({
title,
transforms: [sortable],
...(className ? { props: { className } } : {}),
}));
const paginatedRows: string[][] = formattedRows.slice((page - 1) * perPage, page * perPage);

return (
<>
<div className="monitoring-dashboards__table-container">
<PFTable
aria-label="query results table"
cells={headers}
className="monitoring-dashboards__table"
gridBreakPoint={TableGridBreakpoint.none}
onSort={onSort}
rows={paginatedRows}
rows={rows}
sortBy={sortBy}
variant={TableVariant.compact}
className="monitoring-dashboards__table"
>
<TableHeader />
<TableBody />
</PFTable>
</div>
<TablePagination
itemCount={formattedRows.length}
itemCount={sortedData.length}
paginationOptions={paginationOptions}
page={page}
perPage={perPage}
Expand Down