Skip to content

Commit 7098fc8

Browse files
committed
feat(ui): Budgets component
1 parent 1d9b771 commit 7098fc8

File tree

4 files changed

+251
-0
lines changed

4 files changed

+251
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.checkMetric {
2+
display: inline;
3+
}
4+
5+
.checkMetric::before {
6+
content: ' (';
7+
}
8+
9+
.checkMetric::after {
10+
content: ') ';
11+
}
12+
13+
.checkMetricDelta {
14+
padding: 2px;
15+
font-size: 8px;
16+
vertical-align: super;
17+
}
18+
19+
.checkThreshold {
20+
display: inline;
21+
font-weight: bold;
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React from 'react';
2+
import { BudgetResult, BudgetStatus, MetricRunInfoDeltaType } from '@bundle-stats/utils';
3+
4+
import { Budgets } from '.';
5+
6+
export default {
7+
title: 'Components/Budgets',
8+
comonent: Budgets,
9+
};
10+
11+
const BUDGETS: Array<BudgetResult> = [
12+
{
13+
config: {
14+
condition: {
15+
fact: 'webpack.duplicatePackagesCount.delta',
16+
operator: 'greaterThan',
17+
value: 0,
18+
},
19+
status: BudgetStatus.FAILURE,
20+
},
21+
value: 2,
22+
data: {
23+
value: 4,
24+
displayValue: '4',
25+
delta: 2,
26+
displayDelta: '+2',
27+
deltaPercentage: 50,
28+
deltaType: MetricRunInfoDeltaType.HIGH_NEGATIVE,
29+
displayDeltaPercentage: '+50%',
30+
},
31+
matched: true,
32+
},
33+
{
34+
config: {
35+
condition: {
36+
fact: 'webpack.duplicatePackagesCount.value',
37+
operator: 'greaterThan',
38+
value: 0,
39+
},
40+
status: BudgetStatus.WARNING,
41+
},
42+
value: 2,
43+
data: {
44+
value: 2,
45+
displayValue: '2',
46+
delta: 0,
47+
displayDelta: '0',
48+
deltaType: MetricRunInfoDeltaType.NO_CHANGE,
49+
deltaPercentage: 0,
50+
displayDeltaPercentage: '0%',
51+
},
52+
matched: true,
53+
},
54+
{
55+
config: {
56+
condition: {
57+
fact: 'webpack.packageCount.delta',
58+
operator: 'greaterThan',
59+
value: 0,
60+
},
61+
status: BudgetStatus.WARNING,
62+
},
63+
value: 10,
64+
data: {
65+
value: 10,
66+
displayValue: '1',
67+
delta: 1,
68+
displayDelta: '+1',
69+
deltaType: MetricRunInfoDeltaType.NEGATIVE,
70+
deltaPercentage: 10,
71+
displayDeltaPercentage: '+10%',
72+
},
73+
matched: true,
74+
},
75+
{
76+
config: {
77+
condition: {
78+
fact: 'webpack.totalSizeByTypeALL.delta',
79+
operator: 'greaterThan',
80+
value: 5 * 1024,
81+
},
82+
status: BudgetStatus.WARNING,
83+
},
84+
value: 0,
85+
data: {
86+
value: 1048576,
87+
displayValue: '1MiB',
88+
delta: 10 * 1024,
89+
displayDelta: '+10KiB',
90+
deltaType: MetricRunInfoDeltaType.NEGATIVE,
91+
deltaPercentage: 1,
92+
displayDeltaPercentage: '+1%',
93+
},
94+
matched: true,
95+
},
96+
];
97+
98+
export const Default = () => <Budgets budgets={BUDGETS} />;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React, { useMemo } from 'react';
2+
import {
3+
BudgetEvaluated,
4+
BudgetResult,
5+
BudgetStatus,
6+
ConditionOperator,
7+
MetricRunInfoDeltaType,
8+
getGlobalMetricType,
9+
} from '@bundle-stats/utils';
10+
11+
import { Stack } from '../../layout/stack';
12+
import { Alert } from '../../ui/alert';
13+
import { Delta } from '../delta';
14+
import { Metric } from '../metric';
15+
// @ts-ignore
16+
import css from './budgets.module.css';
17+
18+
const OPERATOR_MAP: Record<ConditionOperator, string> = {
19+
smallerThanInclusive: 'equal',
20+
smallerThan: 'bellow',
21+
equal: 'equal',
22+
notEqual: 'not equal',
23+
greaterThan: 'above',
24+
greaterThanInclusive: 'equal',
25+
};
26+
27+
const ALERT_MAP: Record<string, string> = {
28+
FAILURE: 'danger',
29+
WARNING: 'warning',
30+
SUCCESS: 'success',
31+
};
32+
33+
interface BudgetProps extends React.HTMLAttributes<HTMLElement> {
34+
budget: BudgetEvaluated;
35+
}
36+
37+
const Budget = (props: BudgetProps) => {
38+
const { budget, ...restProps } = props;
39+
40+
const metricSegments = budget.config.condition.fact.split('.');
41+
const metricKey = metricSegments.slice(0, -1).join('.');
42+
43+
const field = metricSegments[metricSegments.length - 1];
44+
45+
let displayField = '';
46+
let deltaDisplayField: string;
47+
48+
switch (field) {
49+
case 'deltaPercentage':
50+
displayField = 'relative difference';
51+
deltaDisplayField = budget.data.displayDeltaPercentage as string;
52+
break;
53+
case 'delta':
54+
displayField = 'absolute difference';
55+
deltaDisplayField = budget.data.displayDelta as string;
56+
break;
57+
default:
58+
displayField = 'value';
59+
deltaDisplayField = budget.data.displayDeltaPercentage as string;
60+
}
61+
62+
const metric = getGlobalMetricType(metricKey);
63+
64+
return (
65+
<Alert kind={ALERT_MAP[budget.config.status]} {...restProps}>
66+
<p>
67+
<strong>{metric.label}</strong>
68+
{` ${displayField} `}
69+
<Metric inline value={budget.data.value} formatter={metric.formatter} className={css.checkMetric}>
70+
<Delta
71+
displayValue={deltaDisplayField}
72+
deltaType={budget.data.deltaType as MetricRunInfoDeltaType}
73+
inverted
74+
className={css.checkMetricDelta}
75+
/>
76+
</Metric>
77+
{` is ${OPERATOR_MAP[budget.config.condition.operator]} `}
78+
<Metric
79+
value={budget.config.condition.value}
80+
formatter={metric.formatter}
81+
inline
82+
className={css.checkThreshold}
83+
/>
84+
</p>
85+
</Alert>
86+
);
87+
};
88+
89+
export interface BudgetsProps extends React.HTMLAttributes<HTMLElement> {
90+
budgets: Array<BudgetResult>;
91+
}
92+
93+
export const Budgets = (props: BudgetsProps) => {
94+
const { budgets, ...restProps } = props;
95+
96+
const [matchedBudgets] = useMemo(() => {
97+
const matched: Array<BudgetEvaluated> = [];
98+
const unmatched: Array<BudgetEvaluated> = [];
99+
100+
budgets.forEach((budget) => {
101+
// @ts-expect-error
102+
if (typeof budget.data === 'undefined') {
103+
return;
104+
}
105+
106+
// @ts-expect-error
107+
if (typeof budget.matched === 'undefined') {
108+
return;
109+
}
110+
111+
// @ts-expect-error
112+
if (budget.matched === true) {
113+
matched.push(budget as BudgetEvaluated);
114+
// @ts-expect-error
115+
} else if (budget.matched === false) {
116+
unmatched.push(budget as BudgetEvaluated);
117+
}
118+
});
119+
120+
return [matched, unmatched];
121+
}, [budgets]);
122+
123+
return (
124+
<Stack space="xxsmall" {...restProps}>
125+
{matchedBudgets.map((budget) => (
126+
<Budget budget={budget} />
127+
))}
128+
</Stack>
129+
);
130+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './budgets';

0 commit comments

Comments
 (0)