Skip to content

Commit

Permalink
Merge pull request #3540 from relative-ci/enhance-metrics-table
Browse files Browse the repository at this point in the history
UI: Enhance metrics table
  • Loading branch information
vio authored Jul 11, 2023
2 parents cb0e13b + 210ecec commit e6d3c0d
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const MetricsTableTitle = (props) => {
const rootClassName = cx(css.root, className);

return (
<Stack className={rootClassName}>
<Stack space="xxsmall" className={rootClassName}>
<FlexStack space="xxxsmall" alignItems="center" as="h3" className={css.title}>
<span>{title}</span>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
}

.info {
color: var(--color-text-light);
font-family: var(--font-family-fixed);
font-size: var(--size-xsmall);
font-weight: normal;
font-weight: bold;
}

.readMoreLink {
Expand Down
188 changes: 71 additions & 117 deletions packages/ui/src/components/metrics-table/metrics-table.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import isEmpty from 'lodash/isEmpty';
Expand All @@ -15,98 +15,68 @@ import styles from './metrics-table.module.css';

const METRIC_TYPE_DATA = getGlobalMetricType(null, METRIC_TYPE_FILE_SIZE);
const VISIBLE_COUNT = 500;
const BASELINE_COLUMN_SPAN = 1;
const CURRENT_COLUMN_SPAN = 3;
const BASELINE_TITLE = 'Baseline';
const CURRENT_TITLE = 'Current';

const getRowsRunTotal = (rows, runIndex) => sum(rows.map((row) => row?.runs?.[runIndex]?.value || 0));

const getHeaderLabelCells = (rows) => (run, runIndex, runs) => {
const isBaseline = runIndex === runs.length - 1;
const className = cx(styles.value, isBaseline ? styles.baseline : styles.current);
const ColumnJob = ({ run, isBaseline }) => {
const colSpan = isBaseline ? BASELINE_COLUMN_SPAN : CURRENT_COLUMN_SPAN;

if (!run) {
return [
{ children: '-', className },
...(!isBaseline ? [{ children: ' ', className: styles.delta }] : []),
];
return (
<Table.Th colSpan={colSpan}>-</Table.Th>
);
}

const { label, internalBuildNumber } = run;

const jobName = (
<JobName
title={runIndex === 0 ? 'Current' : 'Baseline'}
internalBuildNumber={internalBuildNumber}
className={styles.jobName}
>
{label}
</JobName>
return (
<Table.Th className={styles.job} colSpan={colSpan}>
<JobName
title={isBaseline ? BASELINE_TITLE : CURRENT_TITLE }
internalBuildNumber={internalBuildNumber}
className={styles.jobName}
>
{label}
</JobName>
</Table.Th>
);

return [
// Value column
{ children: jobName, className },
// Delta column
...(!isBaseline ? [{ children: ' ', className: cx(styles.delta) }] : []),
];
};

const getHeaderTotalCells = (rows) => (run, runIndex, runs) => {
const isBaseline = runIndex === runs.length - 1;
const className = cx(styles.value, isBaseline ? styles.baseline : styles.current);

const ColumnSum = ({ rows, isBaseline, runIndex }) => {
const currentRunTotal = getRowsRunTotal(rows, runIndex);
const baselineRunTotal = !isBaseline && getRowsRunTotal(rows, runIndex + 1);
const infoTotal = getMetricRunInfo(METRIC_TYPE_DATA, currentRunTotal, baselineRunTotal);

return [
// Value column
{
children: <Metric className={styles.tableHeaderRunMetric} value={infoTotal.displayValue} />,
className,
},

// Delta column
...(!isBaseline
? [
{
children: (
<Delta
displayValue={infoTotal.displayDeltaPercentage}
deltaType={infoTotal.deltaType}
/>
),
className: styles.delta,
},
]
: []),
];
return (
<>
<Table.Th className={cx(styles.value, styles.sum)}>
<Metric className={styles.tableHeaderRunMetric} value={infoTotal.displayValue} />
</Table.Th>
{!isBaseline && (
<>
<Table.Th className={cx(styles.delta, styles.sum)}>
<Delta
displayValue={infoTotal.displayDelta}
deltaType={infoTotal.deltaType}
/>
</Table.Th>
<Table.Th className={cx(styles.delta, styles.sum)}>
<Delta
displayValue={infoTotal.displayDeltaPercentage}
deltaType={infoTotal.deltaType}
/>
</Table.Th>
</>
)}
</>
);
};

const getHeaderRows = (runs, items, showHeaderSum, title) => [
{
className: styles.headerRowColumns,
cells: [
// Metric name column - one empty strying to render the column
{
children: title || ' ',
className: styles.metricName,
rowSpan: showHeaderSum ? 2 : 1,
},

// Runs
...runs.map(getHeaderLabelCells(items)).flat(),
],
},
...(showHeaderSum
? [
{
className: styles.headerRowTotals,
cells: runs.map(getHeaderTotalCells(items)).flat(),
},
]
: []),
];

const MetricsTableRow = ({ item, renderRowHeader }) => (
const Row = ({ item, renderRowHeader }) => (
<Table.Tr className={cx(!item.changed && styles.unchanged)}>
<Table.Th className={styles.metricName}>{renderRowHeader(item)}</Table.Th>

Expand All @@ -120,29 +90,35 @@ const MetricsTableRow = ({ item, renderRowHeader }) => (
<>
<Table.Td className={valueClassName}>-</Table.Td>
{!isBaseline && <Table.Td className={styles.delta} />}
{!isBaseline && <Table.Td className={cx(styles.delta, styles.deltaPercentage)} />}
</>
);
}

const { displayValue, deltaPercentage, displayDeltaPercentage, deltaType } = run;
const { displayValue, deltaPercentage, displayDelta, displayDeltaPercentage, deltaType } = run;

return (
<>
<Table.Td className={valueClassName}>
<Metric value={displayValue} />
</Table.Td>
{!isBaseline && typeof deltaPercentage === 'number' && (
<Table.Td className={styles.delta}>
<Delta displayValue={displayDeltaPercentage} deltaType={deltaType} />
</Table.Td>
<>
<Table.Td className={styles.delta}>
<Delta displayValue={displayDelta} deltaType={deltaType} />
</Table.Td>
<Table.Td className={cx(styles.delta, styles.deltaPercentage)}>
<Delta displayValue={displayDeltaPercentage} deltaType={deltaType} />
</Table.Td>
</>
)}
</>
);
})}
</Table.Tr>
);

MetricsTableRow.propTypes = {
Row.propTypes = {
item: PropTypes.shape({
changed: PropTypes.bool,
runs: PropTypes.arrayOf(
Expand All @@ -165,21 +141,12 @@ export const MetricsTable = ({
items,
emptyMessage,
showHeaderSum,
headerRows,
title,
showAllItems,
setShowAllItems,
...restProps
}) => {
const { headers, columnClassNames } = useMemo(() => {
const headerColumns = getHeaderRows(runs, items, showHeaderSum, title);

return {
headers: [...headerRows, ...headerColumns],
// First header row has the column class names
columnClassNames: headerColumns[0].cells.map((headerColumn) => headerColumn.className),
};
}, [headerRows, runs, items, showHeaderSum, title]);
const columnCount = (runs.length - 1) * CURRENT_COLUMN_SPAN + BASELINE_COLUMN_SPAN + 1;

const rootClassName = cx(
styles.root,
Expand All @@ -196,24 +163,22 @@ export const MetricsTable = ({
return (
<Table className={rootClassName} compact {...restProps}>
<Table.THead>
{headers.map((headerRow) => {
const { cells, className: rowClassName } = headerRow.cells
? headerRow
: { cells: headerRow };

return (
<Table.Tr className={rowClassName}>
{cells.map((header) => (
<Table.Th {...header} />
))}
</Table.Tr>
);
})}
<Table.Tr className={styles.headerRow}>
<Table.Th className={styles.metricName} rowSpan={showHeaderSum ? 2 : 1}>
{title || ' '}
</Table.Th>
{runs.map((run, runIndex) => <ColumnJob run={run} isBaseline={runIndex === runs.length - 1} />)}
</Table.Tr>
{showHeaderSum && (
<Table.Tr className={styles.headerRow}>
{runs.map((run, runIndex) => <ColumnSum rows={items} isBaseline={runIndex === runs.length - 1} runIndex={runIndex} />)}
</Table.Tr>
)}
</Table.THead>
<Table.TBody>
{showEmpty && (
<Table.Tr>
<Table.Td className={styles.empty} colSpan={columnClassNames?.length || 1}>
<Table.Td className={styles.empty} colSpan={columnCount}>
<Stack space="xxsmall">
<div>
<Icon glyph={Icon.ICONS.INFO} className={styles.emptyIcon} size="large" />
Expand All @@ -227,12 +192,12 @@ export const MetricsTable = ({
{showItems && (
<>
{visibleItems.map((item) => (
<MetricsTableRow key={item.key} item={item} renderRowHeader={renderRowHeader} />
<Row key={item.key} item={item} renderRowHeader={renderRowHeader} />
))}

{hasHiddenItems && (
<Table.Tr>
<Table.Td className={styles.showAllItems} colSpan={columnClassNames?.length || 1}>
<Table.Td className={styles.showAllItems} colSpan={columnCount}>
{showAllItems ? (
<button
onClick={() => setShowAllItems(false)}
Expand Down Expand Up @@ -266,7 +231,6 @@ MetricsTable.defaultProps = {
renderRowHeader: (item) => item.label,
emptyMessage: 'No entries found.',
showHeaderSum: false,
headerRows: [],
title: '',
};

Expand All @@ -292,15 +256,5 @@ MetricsTable.propTypes = {
).isRequired,
emptyMessage: PropTypes.element,
showHeaderSum: PropTypes.bool,
headerRows: PropTypes.arrayOf(
PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.element,
PropTypes.shape({
children: PropTypes.node,
}),
]),
),
),
title: PropTypes.element,
};
20 changes: 9 additions & 11 deletions packages/ui/src/components/metrics-table/metrics-table.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
text-transform: none;
}

.root .job {
text-align: right;
}

.root .value {
padding-left: var(--space-large);
text-align: right;
Expand All @@ -27,33 +31,27 @@

.root .delta {
padding-left: 0;
text-align: left;
text-align: right;
font-size: var(--size-xsmall);
vertical-align: baseline;
}

.root tbody .delta {
line-height: 1.6rem; /* baseline alignment with the value */
}

.root .value.current {
padding-right: var(--space-xxsmall);
line-height: 1.6rem; /* baseline alignment with the column value */
}

.jobName {
white-space: nowrap;
}

/* columns overrides for showHeaderSum */
.showHeaderSum .headerRowColumns .value,
.showHeaderSum .headerRowColumns .delta {
.showHeaderSum .headerRow .job {
border-bottom: 0;
padding-bottom: 0;
}

.headerRowTotals th {
padding-top: 0 !important;
font-weight: normal;
.showHeaderSum .headerRow .sum {
padding-top: var(--space-xxxsmall);
font-size: var(--size-xsmall);
vertical-align: middle;
}
Expand Down
19 changes: 2 additions & 17 deletions packages/ui/src/components/metrics-table/metrics-table.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,23 +217,8 @@ export const MultipleRuns = () => (
<MetricsTable runs={RUNS} items={ITEMS_MULTIPLE_RUNS} />
);

export const WithHeaderRows = () => (
<MetricsTable
runs={RUNS}
items={ITEMS_MULTIPLE_RUNS}
headerRows={[
[
'Metric',
{
children: 'Colspan',
colSpan: 1 + (RUNS.length - 1) * 2 + 1,
style: {
textAlign: 'center'
}
}
]
]}
/>
export const WithHeaderSum = () => (
<MetricsTable runs={RUNS} items={[ITEMS_MULTIPLE_RUNS[0], ITEMS_MULTIPLE_RUNS[4]]} showHeaderSum />
);

export const Empty = () =><MetricsTable runs={RUNS} items={[]} />;

0 comments on commit e6d3c0d

Please sign in to comment.