@bundle-stats/ui 3.2.1 → 3.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/lib/app/app.js +31 -6
  3. package/lib/app/app.stories.js +31 -3
  4. package/lib/assets/icons/alert-circle.svg +1 -0
  5. package/lib/assets/icons/arrow-right-circle.svg +1 -0
  6. package/lib/assets/icons/check-circle.svg +1 -0
  7. package/lib/assets/icons/close.svg +1 -1
  8. package/lib/assets/icons.svg +50 -1
  9. package/lib/components/browsertime-table/browsertime-table.js +1 -1
  10. package/lib/components/browsertime-table/browsertime-table.stories.js +1 -1
  11. package/lib/components/budget-info/budget-info.js +80 -0
  12. package/lib/components/budget-info/budget-info.module.css +40 -0
  13. package/lib/components/budget-info/budget-info.stories.js +44 -0
  14. package/lib/components/budget-info/index.js +18 -0
  15. package/lib/components/budget-insights/budget-insights.js +200 -0
  16. package/lib/components/budget-insights/budget-insights.module.css +53 -0
  17. package/lib/components/budget-insights/budget-insights.stories.js +104 -0
  18. package/lib/components/budget-insights/index.js +18 -0
  19. package/lib/components/bundle-assets/bundle-assets.js +3 -3
  20. package/lib/components/bundle-assets/bundle-assets.utils.js +2 -2
  21. package/lib/components/bundle-assets/index.js +3 -3
  22. package/lib/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js +1 -1
  23. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js +44 -8
  24. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  25. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +58 -43
  26. package/lib/components/bundle-modules/bundle-modules.js +3 -3
  27. package/lib/components/bundle-modules/bundle-modules.utils.js +2 -2
  28. package/lib/components/bundle-modules/index.js +3 -3
  29. package/lib/components/bundle-packages/bundle-packages.js +1 -1
  30. package/lib/components/bundle-packages/bundle-packages.utils.js +2 -2
  31. package/lib/components/bundle-packages/index.js +1 -1
  32. package/lib/components/index.js +8 -0
  33. package/lib/components/lighthouse-table/lighthouse-table.js +1 -1
  34. package/lib/components/metrics-table/metrics-table.js +1 -1
  35. package/lib/components/metrics-table-search/metrics-table-search.js +1 -1
  36. package/lib/components/metrics-table-search/metrics-table-search.stories.js +1 -1
  37. package/lib/components/summary/summary.js +40 -62
  38. package/lib/components/summary/summary.module.css +31 -14
  39. package/lib/components/summary/summary.stories.js +29 -0
  40. package/lib/components/summary-item/summary-item.js +30 -13
  41. package/lib/components/summary-item/summary-item.module.css +18 -9
  42. package/lib/components/summary-item/summary-item.stories.js +67 -52
  43. package/lib/constants.js +6 -6
  44. package/lib/css/variables.css +15 -11
  45. package/lib/hooks/search-params.js +2 -2
  46. package/lib/query-state.js +1 -1
  47. package/lib/stories/wrapper.js +2 -2
  48. package/lib/ui/alert/alert.js +13 -3
  49. package/lib/ui/filters/filters.container.js +5 -5
  50. package/lib/ui/filters/filters.js +2 -2
  51. package/lib/ui/filters/filters.stories.js +3 -3
  52. package/lib/ui/horizontal-bar-chart/horizontal-bar-chart.js +3 -3
  53. package/lib/ui/icon/icon.js +3 -0
  54. package/lib/ui/icon/icon.stories.js +40 -22
  55. package/lib/ui/panels/panels.js +2 -2
  56. package/lib/ui/popover/popover.js +3 -3
  57. package/lib/ui/popover/popover.module.css +2 -1
  58. package/lib/ui/tabs/tabs.js +2 -2
  59. package/lib/ui/tooltip/tooltip.module.css +1 -0
  60. package/lib/utils.js +2 -2
  61. package/lib-esm/app/app.js +57 -31
  62. package/lib-esm/app/app.stories.js +29 -1
  63. package/lib-esm/assets/icons/alert-circle.svg +1 -0
  64. package/lib-esm/assets/icons/arrow-right-circle.svg +1 -0
  65. package/lib-esm/assets/icons/check-circle.svg +1 -0
  66. package/lib-esm/assets/icons/close.svg +1 -1
  67. package/lib-esm/assets/icons.svg +50 -1
  68. package/lib-esm/components/budget-info/budget-info.js +58 -0
  69. package/lib-esm/components/budget-info/budget-info.module.css +40 -0
  70. package/lib-esm/components/budget-info/budget-info.stories.js +30 -0
  71. package/lib-esm/components/budget-info/index.js +1 -0
  72. package/lib-esm/components/budget-insights/budget-insights.js +148 -0
  73. package/lib-esm/components/budget-insights/budget-insights.module.css +53 -0
  74. package/lib-esm/components/budget-insights/budget-insights.stories.js +84 -0
  75. package/lib-esm/components/budget-insights/index.js +1 -0
  76. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +23 -7
  77. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  78. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +46 -21
  79. package/lib-esm/components/index.js +1 -0
  80. package/lib-esm/components/summary/summary.js +26 -45
  81. package/lib-esm/components/summary/summary.module.css +31 -14
  82. package/lib-esm/components/summary/summary.stories.js +27 -0
  83. package/lib-esm/components/summary-item/summary-item.js +28 -12
  84. package/lib-esm/components/summary-item/summary-item.module.css +18 -9
  85. package/lib-esm/components/summary-item/summary-item.stories.js +52 -42
  86. package/lib-esm/constants.js +4 -4
  87. package/lib-esm/css/variables.css +15 -11
  88. package/lib-esm/ui/alert/alert.js +6 -3
  89. package/lib-esm/ui/filters/filters.container.js +39 -41
  90. package/lib-esm/ui/icon/icon.js +3 -0
  91. package/lib-esm/ui/icon/icon.stories.js +22 -11
  92. package/lib-esm/ui/popover/popover.js +2 -2
  93. package/lib-esm/ui/popover/popover.module.css +2 -1
  94. package/lib-esm/ui/tooltip/tooltip.module.css +1 -0
  95. package/package.json +15 -15
  96. package/src/app/app.jsx +24 -8
  97. package/src/assets/icons/alert-circle.svg +1 -0
  98. package/src/assets/icons/arrow-right-circle.svg +1 -0
  99. package/src/assets/icons/check-circle.svg +1 -0
  100. package/src/assets/icons/close.svg +1 -1
  101. package/src/assets/icons.svg +50 -1
  102. package/src/components/budget-info/budget-info.jsx +65 -0
  103. package/src/components/budget-info/budget-info.module.css +40 -0
  104. package/src/components/budget-info/index.js +1 -0
  105. package/src/components/budget-insights/budget-insights.jsx +157 -0
  106. package/src/components/budget-insights/budget-insights.module.css +53 -0
  107. package/src/components/budget-insights/index.js +1 -0
  108. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.jsx +27 -13
  109. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  110. package/src/components/index.js +1 -0
  111. package/src/components/summary/summary.jsx +26 -44
  112. package/src/components/summary/summary.module.css +31 -14
  113. package/src/components/summary-item/summary-item.jsx +26 -24
  114. package/src/components/summary-item/summary-item.module.css +18 -9
  115. package/src/constants.js +7 -7
  116. package/src/css/variables.css +15 -11
  117. package/src/ui/alert/alert.jsx +7 -2
  118. package/src/ui/icon/icon.jsx +3 -0
  119. package/src/ui/popover/popover.jsx +3 -3
  120. package/src/ui/popover/popover.module.css +2 -1
  121. package/src/ui/tooltip/tooltip.module.css +1 -0
@@ -0,0 +1,148 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
3
+ import React, { useMemo, useState } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import cx from 'classnames';
6
+ import { METRIC_COMPONENT_LINKS } from '@bundle-stats/utils';
7
+ import * as budgetsInsightsTransformer from '@bundle-stats/utils/lib-esm/transformers/budgets-insights';
8
+ import { Box } from '../../layout/box';
9
+ import { Stack } from '../../layout/stack';
10
+ import { FlexStack } from '../../layout/flex-stack';
11
+ import { Alert } from '../../ui/alert';
12
+ import { Icon } from '../../ui/icon';
13
+ import { ComponentLink } from '../component-link';
14
+ import css from './budget-insights.module.css';
15
+
16
+ const Budget = props => {
17
+ const {
18
+ className,
19
+ metricId,
20
+ budgetInsight,
21
+ CustomLink
22
+ } = props;
23
+ const componentLinkOptions = METRIC_COMPONENT_LINKS.get(metricId);
24
+ const budgetInsightInfo = budgetsInsightsTransformer.getInfo(metricId, budgetInsight);
25
+ const {
26
+ data: messageData
27
+ } = budgetInsightInfo.message;
28
+ return /*#__PURE__*/React.createElement(CustomLink, _extends({
29
+ className: cx(css.budget, className)
30
+ }, componentLinkOptions == null ? void 0 : componentLinkOptions.link), /*#__PURE__*/React.createElement("strong", null, messageData.metricLabel), ` value (`, /*#__PURE__*/React.createElement("strong", null, messageData.currentValue), `) is ${messageData.diffLabel} `, /*#__PURE__*/React.createElement("strong", null, messageData.budgetValue), ` budget `);
31
+ };
32
+
33
+ Budget.propTypes = {
34
+ className: PropTypes.string,
35
+ metricId: PropTypes.string.isRequired,
36
+ budgetInsight: PropTypes.shape({
37
+ currentValue: PropTypes.number.isRequired,
38
+ budgetValue: PropTypes.number.isRequired,
39
+ failed: PropTypes.bool.isRequired
40
+ }).isRequired,
41
+ CustomLink: PropTypes.elementType.isRequired
42
+ };
43
+ Budget.defaultProps = {
44
+ className: ''
45
+ };
46
+
47
+ const BudgetsGroup = props => {
48
+ const {
49
+ className,
50
+ kind,
51
+ source,
52
+ budgets,
53
+ CustomLink
54
+ } = props;
55
+ const [showBudgets, setShowBudgets] = useState(false);
56
+ const isFailed = kind === 'danger';
57
+ return /*#__PURE__*/React.createElement(Alert, {
58
+ kind: kind,
59
+ padding: "none",
60
+ className: cx(css.group, css[kind], className)
61
+ }, /*#__PURE__*/React.createElement(Box, {
62
+ padding: ['xsmall', 'small'],
63
+ className: css.groupHeader,
64
+ as: "button",
65
+ type: "button",
66
+ onClick: () => setShowBudgets(!showBudgets)
67
+ }, /*#__PURE__*/React.createElement(FlexStack, {
68
+ space: "xxsmall",
69
+ className: css.groupTitle
70
+ }, /*#__PURE__*/React.createElement(Icon, {
71
+ className: css.groupIcon,
72
+ glyph: isFailed ? Icon.ICONS.ALERT_CIRCLE : Icon.ICONS.CHECK_CIRCLE
73
+ }), /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("strong", null, budgets.length), ` budget ${budgets.length > 1 ? 'checks' : 'check'} ${isFailed ? 'failed' : 'passed'}`), /*#__PURE__*/React.createElement("span", {
74
+ className: css.groupTitleToggle
75
+ }, showBudgets ? 'hide' : 'show', " all"))), showBudgets && /*#__PURE__*/React.createElement(Box, {
76
+ padding: ['xsmall', 'small'],
77
+ className: css.groupContent
78
+ }, /*#__PURE__*/React.createElement(Stack, {
79
+ space: "none"
80
+ }, budgets.map(_ref => {
81
+ let [key, budget] = _ref;
82
+ return /*#__PURE__*/React.createElement(Budget, {
83
+ key: key,
84
+ metricId: `${source}.${key}`,
85
+ budgetInsight: budget,
86
+ CustomLink: CustomLink
87
+ });
88
+ }))));
89
+ };
90
+
91
+ BudgetsGroup.propTypes = {
92
+ className: PropTypes.string,
93
+ kind: PropTypes.oneOf(['success', 'danger']).isRequired,
94
+ source: PropTypes.string.isRequired,
95
+ budgets: PropTypes.object.isRequired,
96
+ // eslint-disable-line react/forbid-prop-types
97
+ CustomLink: PropTypes.elementType.isRequired
98
+ };
99
+ BudgetsGroup.defaultProps = {
100
+ className: ''
101
+ };
102
+ export const BudgetInsights = props => {
103
+ const {
104
+ className,
105
+ source,
106
+ budgets,
107
+ CustomLink
108
+ } = props;
109
+ const [failedBudgets, passedBudgets] = useMemo(() => {
110
+ const passed = [];
111
+ const failed = [];
112
+ Object.entries(budgets).forEach(_ref2 => {
113
+ let [key, budget] = _ref2;
114
+
115
+ if (budget.failed) {
116
+ failed.push([key, budget]);
117
+ } else {
118
+ passed.push([key, budget]);
119
+ }
120
+ });
121
+ return [failed, passed];
122
+ }, [budgets]);
123
+ return /*#__PURE__*/React.createElement(Stack, {
124
+ className: className,
125
+ space: "xsmall"
126
+ }, failedBudgets.length > 0 && /*#__PURE__*/React.createElement(BudgetsGroup, {
127
+ kind: "danger",
128
+ source: source,
129
+ budgets: failedBudgets,
130
+ CustomLink: CustomLink
131
+ }), passedBudgets.length > 0 && /*#__PURE__*/React.createElement(BudgetsGroup, {
132
+ kind: "success",
133
+ source: source,
134
+ budgets: passedBudgets,
135
+ CustomLink: CustomLink
136
+ }));
137
+ };
138
+ BudgetInsights.propTypes = {
139
+ className: PropTypes.string,
140
+ source: PropTypes.string.isRequired,
141
+ budgets: PropTypes.object.isRequired,
142
+ // eslint-disable-line react/forbid-prop-types
143
+ CustomLink: PropTypes.elementType
144
+ };
145
+ BudgetInsights.defaultProps = {
146
+ className: '',
147
+ CustomLink: ComponentLink
148
+ };
@@ -0,0 +1,53 @@
1
+ .groupHeader {
2
+ width: 100%;
3
+ border: none;
4
+ background: none;
5
+ cursor: pointer;
6
+ color: inherit;
7
+ }
8
+
9
+ .groupTitle {
10
+ width: 100%;
11
+ align-items: center;
12
+ }
13
+
14
+ .groupTitle:hover {
15
+ color: var(--color-text-dark);
16
+ text-decoration: underline;
17
+ }
18
+
19
+ .groupTitleToggle {
20
+ margin: 0 0 0 auto !important;
21
+ }
22
+
23
+ .danger .groupIcon {
24
+ color: var(--color-danger);
25
+ }
26
+
27
+ .success .groupIcon {
28
+ color: var(--color-success);
29
+ }
30
+
31
+ .groupContent {
32
+ padding-top: 0px;
33
+ background: transparent;
34
+ }
35
+
36
+ .budget {
37
+ display: block;
38
+ color: var(--color-text);
39
+ text-decoration: none;
40
+ }
41
+
42
+ .budgetLabel,
43
+ .budgetValue {
44
+ color: var(--color-text-dark);
45
+ font-weight: 500;
46
+ }
47
+
48
+ .budget:hover,
49
+ .budget:focus,
50
+ .budget:active {
51
+ color: var(--color-text);
52
+ text-decoration: underline;
53
+ }
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+ import { MemoryRouter } from 'react-router-dom';
3
+ import { BudgetInsights } from '.';
4
+ export default {
5
+ title: 'Components/BudgetInsights',
6
+ component: BudgetInsights,
7
+ decorators: [Story => /*#__PURE__*/React.createElement(MemoryRouter, null, /*#__PURE__*/React.createElement(Story, null))]
8
+ };
9
+
10
+ const Template = args => /*#__PURE__*/React.createElement(BudgetInsights, args);
11
+
12
+ export const Default = Template.bind();
13
+ Default.args = {
14
+ source: 'webpack',
15
+ budgets: {
16
+ totalSizeByTypeALL: {
17
+ currentValue: 2097917,
18
+ budgetValue: 2097152,
19
+ failed: true
20
+ },
21
+ totalInitialSizeJS: {
22
+ currentValue: 1265645,
23
+ budgetValue: 524288,
24
+ failed: true
25
+ },
26
+ totalInitialSizeCSS: {
27
+ currentValue: 48866,
28
+ budgetValue: 51200,
29
+ failed: false
30
+ },
31
+ chunkCount: {
32
+ currentValue: 12,
33
+ budgetValue: 50,
34
+ failed: false
35
+ },
36
+ moduleCount: {
37
+ currentValue: 1059,
38
+ budgetValue: 2000,
39
+ failed: false
40
+ },
41
+ duplicatePackagesCount: {
42
+ currentValue: 13,
43
+ budgetValue: 5,
44
+ failed: true
45
+ },
46
+ 'sizes.totalSizeByTypeJS': {
47
+ currentValue: 1981470,
48
+ budgetValue: 2097152,
49
+ failed: false
50
+ },
51
+ 'sizes.totalSizeByTypeCSS': {
52
+ currentValue: 56091,
53
+ budgetValue: 20480,
54
+ failed: true
55
+ }
56
+ }
57
+ };
58
+ export const FailedBudgets = Template.bind();
59
+ FailedBudgets.args = {
60
+ source: 'webpack',
61
+ budgets: {
62
+ totalSizeByTypeALL: {
63
+ currentValue: 2097917,
64
+ budgetValue: 2097152,
65
+ failed: true
66
+ },
67
+ totalInitialSizeJS: {
68
+ currentValue: 1265645,
69
+ budgetValue: 524288,
70
+ failed: true
71
+ }
72
+ }
73
+ };
74
+ export const SingleBudget = Template.bind();
75
+ SingleBudget.args = {
76
+ source: 'webpack',
77
+ budgets: {
78
+ totalInitialSizeJS: {
79
+ currentValue: 524288,
80
+ budgetValue: 1265645,
81
+ failed: false
82
+ }
83
+ }
84
+ };
@@ -0,0 +1 @@
1
+ export * from './budget-insights';
@@ -1,32 +1,48 @@
1
- import React from 'react';
1
+ import _get from "lodash/get";
2
+ import React, { useCallback, useMemo } from 'react';
2
3
  import PropTypes from 'prop-types';
3
4
  import { getBundleAssetsFileTypeComponentLink } from '@bundle-stats/utils';
4
5
  import * as webpack from '@bundle-stats/utils/lib-esm/webpack';
5
6
  import { ASSETS_SIZES_FILE_TYPE_MAP } from '../../constants';
7
+ import { FlexStack } from '../../layout/flex-stack';
6
8
  import { MetricsTable } from '../metrics-table';
7
9
  import { ComponentLink } from '../component-link';
10
+ import { BudgetInfo } from '../budget-info';
11
+ import css from './bundle-assets-totals-table.module.css';
8
12
  export const BundleAssetsTotalsTable = _ref => {
13
+ var _jobs$0$insights, _jobs$0$insights$webp;
14
+
9
15
  let {
10
16
  className,
11
17
  jobs,
12
18
  customComponentLink: CustomComponentLink
13
19
  } = _ref;
14
- const items = webpack.compareBySection.sizes(jobs);
15
-
16
- const renderRowHeader = item => {
20
+ const items = useMemo(() => webpack.compareBySection.sizes(jobs), [jobs]);
21
+ const budgets = (_jobs$0$insights = jobs[0].insights) == null ? void 0 : (_jobs$0$insights$webp = _jobs$0$insights.webpack) == null ? void 0 : _jobs$0$insights$webp.budgets;
22
+ const renderRowHeader = useCallback(item => {
17
23
  const fileType = ASSETS_SIZES_FILE_TYPE_MAP[item.key];
18
24
  const {
19
25
  section,
20
26
  title,
21
27
  params
22
28
  } = getBundleAssetsFileTypeComponentLink(fileType, item.label);
29
+ const [_, ...metricSlugs] = item.key.split('.');
30
+ const metricKey = metricSlugs.join('.');
31
+
32
+ const budget = _get(budgets, metricKey);
33
+
23
34
  return /*#__PURE__*/React.createElement(CustomComponentLink, {
24
35
  section: section,
25
36
  title: title,
26
37
  params: params
27
- }, item.label);
28
- };
29
-
38
+ }, /*#__PURE__*/React.createElement(FlexStack, {
39
+ space: "xxsmall",
40
+ className: css.itemTitleText
41
+ }, /*#__PURE__*/React.createElement("span", null, item.label), budget && /*#__PURE__*/React.createElement(BudgetInfo, {
42
+ metricId: item.key,
43
+ budgetInsight: budget
44
+ })));
45
+ }, [CustomComponentLink]);
30
46
  return /*#__PURE__*/React.createElement(MetricsTable, {
31
47
  className: className,
32
48
  runs: jobs,
@@ -1,3 +1,3 @@
1
- .title {
2
- color: var(--color-text-light);
1
+ .itemTitleText {
2
+ align-items: center;
3
3
  }
@@ -1,27 +1,52 @@
1
1
  import React from 'react';
2
- import { storiesOf } from '@storybook/react';
3
2
  import { createJobs } from '@bundle-stats/utils';
4
3
  import baselineStats from '../../../__mocks__/webpack-stats.baseline.json';
5
4
  import currentStats from '../../../__mocks__/webpack-stats.current.json';
6
5
  import { getWrapperDecorator } from '../../stories';
7
6
  import { BundleAssetsTotalsTable } from '.';
8
- const JOBS = createJobs([{
9
- webpack: currentStats
10
- }, {
11
- webpack: baselineStats
12
- }]);
13
- const [currentJob] = JOBS;
14
- const stories = storiesOf('Components/BundleAssetsTotalsTable', module);
15
- stories.addDecorator(getWrapperDecorator());
16
- stories.add('default', () => /*#__PURE__*/React.createElement(BundleAssetsTotalsTable, {
17
- jobs: [currentJob]
18
- }));
19
- stories.add('multiple jobs', () => /*#__PURE__*/React.createElement(BundleAssetsTotalsTable, {
20
- jobs: JOBS
21
- }));
22
- const JOBS_EMPTY_BASELINE = createJobs([{
23
- webpack: currentStats
24
- }, null]);
25
- stories.add('empty baseline', () => /*#__PURE__*/React.createElement(BundleAssetsTotalsTable, {
26
- jobs: JOBS_EMPTY_BASELINE
27
- }));
7
+ export default {
8
+ title: 'Components/BundleAssetsTotalsTable',
9
+ component: BundleAssetsTotalsTable,
10
+ decorators: [getWrapperDecorator()]
11
+ };
12
+
13
+ const Template = args => /*#__PURE__*/React.createElement(BundleAssetsTotalsTable, args);
14
+
15
+ export const SingleJob = Template.bind();
16
+ SingleJob.args = {
17
+ jobs: createJobs([{
18
+ webpack: currentStats
19
+ }])
20
+ };
21
+ export const MultipleJobs = Template.bind();
22
+ MultipleJobs.args = {
23
+ jobs: createJobs([{
24
+ webpack: currentStats
25
+ }, {
26
+ webpack: baselineStats
27
+ }])
28
+ };
29
+ export const EmptyBaseline = Template.bind();
30
+ EmptyBaseline.args = {
31
+ jobs: createJobs([{
32
+ webpack: currentStats
33
+ }, null])
34
+ };
35
+ export const WithBudgets = Template.bind();
36
+ WithBudgets.args = {
37
+ jobs: createJobs([{
38
+ webpack: currentStats
39
+ }, {
40
+ webpack: baselineStats
41
+ }], {
42
+ webpack: {
43
+ budgets: [{
44
+ metric: 'sizes.totalSizeByTypeJS',
45
+ value: 2 * 1024 * 1024
46
+ }, {
47
+ metric: 'sizes.totalSizeByTypeCSS',
48
+ value: 20 * 1024
49
+ }]
50
+ }
51
+ })
52
+ };
@@ -4,6 +4,7 @@ export { BundleAssetsTotalsTable } from './bundle-assets-totals-table';
4
4
  export { BundleAssetsTotalsChartBars } from './bundle-assets-totals-chart-bars';
5
5
  export { BundlePackages } from './bundle-packages';
6
6
  export { BundleModules } from './bundle-modules';
7
+ export { BudgetInsights } from './budget-insights';
7
8
  export { ComponentLink } from './component-link';
8
9
  export { DuplicatePackagesWarning } from './duplicate-packages-warning';
9
10
  export { Delta } from './delta';
@@ -7,76 +7,59 @@ import PropTypes from 'prop-types';
7
7
  import cx from 'classnames';
8
8
  import { METRIC_COMPONENT_LINKS } from '@bundle-stats/utils';
9
9
  import { METRICS_WEBPACK_GENERAL } from '../../constants';
10
+ import { Icon } from '../../ui/icon';
10
11
  import { Box } from '../../layout/box';
11
12
  import { FlexStack } from '../../layout/flex-stack';
12
13
  import { ComponentLink } from '../component-link';
13
14
  import { SummaryItem } from '../summary-item';
14
15
  import css from './summary.module.css';
15
16
 
16
- const CustomSummaryItem = _ref => {
17
- let {
18
- className,
19
- metricOptions,
20
- SummaryItemCustomLink,
21
- ...props
22
- } = _ref;
23
- return /*#__PURE__*/React.createElement(Box, _extends({
24
- padding: ['xsmall', 'small'],
25
- className: cx(className, css.summaryItemLink)
26
- }, props, {
27
- as: SummaryItemCustomLink
28
- }, metricOptions.link));
29
- };
30
-
31
- CustomSummaryItem.propTypes = {
32
- className: PropTypes.string,
33
- metricOptions: PropTypes.shape({
34
- link: PropTypes.any // eslint-disable-line react/forbid-prop-types
17
+ const getSourceMetricId = fullMetricId => fullMetricId.split('.').slice(1).join('.');
35
18
 
36
- }).isRequired,
37
- SummaryItemCustomLink: PropTypes.elementType.isRequired
38
- };
39
- CustomSummaryItem.defaultProps = {
40
- className: ''
41
- };
42
- export const Summary = _ref2 => {
19
+ export const Summary = _ref => {
43
20
  let {
44
21
  className,
45
22
  size,
46
23
  keys,
47
24
  data,
25
+ budgets,
48
26
  loading,
49
27
  showSummaryItemDelta,
50
28
  summaryItemLink: SummaryItemCustomLink
51
- } = _ref2;
29
+ } = _ref;
52
30
  return /*#__PURE__*/React.createElement(Box, {
53
31
  outline: true,
54
32
  className: cx(css.root, className)
55
33
  }, /*#__PURE__*/React.createElement(FlexStack, {
56
34
  className: css.items
57
- }, Array.from(METRIC_COMPONENT_LINKS).filter(_ref3 => {
58
- let [metricId] = _ref3;
59
- return keys.includes(metricId);
60
- }).map(_ref4 => {
61
- let [metricId, metricOptions] = _ref4;
62
- return /*#__PURE__*/React.createElement(SummaryItem, {
63
- as: CustomSummaryItem,
64
- className: css.item,
65
- metricOptions: metricOptions,
66
- SummaryItemCustomLink: SummaryItemCustomLink,
67
- size: size,
35
+ }, Array.from(METRIC_COMPONENT_LINKS).filter(_ref2 => {
36
+ let [metricId] = _ref2;
37
+ return keys.includes(getSourceMetricId(metricId));
38
+ }).map(_ref3 => {
39
+ let [metricId, metricOptions] = _ref3;
40
+ return /*#__PURE__*/React.createElement("div", {
68
41
  key: metricId,
42
+ className: css.item
43
+ }, /*#__PURE__*/React.createElement(SummaryItem, {
44
+ size: size,
69
45
  id: metricId,
70
46
  data: _get(data, metricId),
47
+ budget: _get(budgets, metricId),
71
48
  loading: loading,
72
49
  showMetricDescription: true,
73
50
  showDelta: showSummaryItemDelta && metricOptions.showDelta !== false
74
- });
51
+ }), !loading && /*#__PURE__*/React.createElement(SummaryItemCustomLink, _extends({}, metricOptions.link, {
52
+ className: css.readMore
53
+ }), /*#__PURE__*/React.createElement(Icon, {
54
+ className: css.readMoreIcon,
55
+ glyph: Icon.ICONS.ARROW_RIGHT_CIRLCE
56
+ })));
75
57
  })));
76
58
  };
77
59
  Summary.defaultProps = {
78
60
  className: '',
79
61
  data: null,
62
+ budgets: null,
80
63
  loading: false,
81
64
  size: '',
82
65
  keys: METRICS_WEBPACK_GENERAL,
@@ -87,12 +70,10 @@ Summary.propTypes = {
87
70
  className: PropTypes.string,
88
71
  size: PropTypes.string,
89
72
  keys: PropTypes.arrayOf(PropTypes.string),
90
- data: PropTypes.shape({
91
- [PropTypes.string]: PropTypes.shape({
92
- baseline: PropTypes.number,
93
- current: PropTypes.number
94
- })
95
- }),
73
+ data: PropTypes.object,
74
+ // eslint-disable-line react/forbid-prop-types
75
+ budgets: PropTypes.object,
76
+ // eslint-disable-line react/forbid-prop-types
96
77
  loading: PropTypes.bool,
97
78
  showSummaryItemDelta: PropTypes.bool,
98
79
  summaryItemLink: PropTypes.elementType
@@ -3,35 +3,52 @@
3
3
  overflow: auto;
4
4
  }
5
5
 
6
- .items {}
7
-
8
6
  .item {
7
+ padding: var(--space-small);
9
8
  flex: 1 0 25%; /* force equal sizes */
10
9
  position: relative;
11
10
  z-index: 1;
12
11
  }
13
12
 
14
- .item:focus-visible {
15
- background: var(--color-highlight);
16
- outline: none;
17
- }
18
-
19
13
  .item::before {
20
14
  content: '';
21
15
  display: block;
22
16
  width: 1px;
23
17
  position: absolute;
24
18
  left: -1px;
25
- top: var(--space-xsmall);
26
- bottom: var(--space-xsmall);
19
+ top: var(--space-small);
20
+ bottom: var(--space-small);
27
21
  background: var(--color-outline);
28
22
  }
29
23
 
30
- .item:hover {
31
- background: var(--color-highlight);
24
+ .readMore {
25
+ display: block;
26
+ position: absolute;
27
+ right: 0;
28
+ bottom: 0;
29
+ padding: var(--space-small);
30
+ color: var(--color-text-ultra-light);
31
+ transition: var(--transition-out);
32
+ }
33
+
34
+ .readMore:hover,
35
+ .readMore:focus {
36
+ color: var(--color-primary);
37
+ transition: var(--transition-in);
38
+ }
39
+
40
+ .readMoreIcon {
41
+ display: block;
42
+ }
43
+
44
+ /** hover state */
45
+ .readMore {
46
+ opacity: 0;
32
47
  }
33
48
 
34
- .summaryItemLink {
35
- color: inherit;
36
- text-decoration: none;
49
+ .item:hover .readMore,
50
+ .item:active .readMore,
51
+ .item:focus .readMore {
52
+ opacity: 1;
53
+ transition: var(--transition-in);
37
54
  }
@@ -21,6 +21,33 @@ stories.addDecorator(getWrapperDecorator());
21
21
  stories.add('default', () => /*#__PURE__*/React.createElement(Summary, {
22
22
  data: MULTIPLE_JOBS[0].summary
23
23
  }));
24
+ stories.add('budgets', () => /*#__PURE__*/React.createElement(Summary, {
25
+ data: MULTIPLE_JOBS[0].summary,
26
+ budgets: {
27
+ webpack: {
28
+ totalSizeByTypeALL: {
29
+ currentValue: MULTIPLE_JOBS[0].summary.webpack.totalSizeByTypeALL.current,
30
+ budgetValue: 1024 * 1024,
31
+ failed: false
32
+ },
33
+ totalInitialSizeJS: {
34
+ currentValue: MULTIPLE_JOBS[0].summary.webpack.totalInitialSizeJS.current,
35
+ budgetValue: 512 * 1024,
36
+ failed: true
37
+ },
38
+ totalInitialSizeCSS: {
39
+ currentValue: MULTIPLE_JOBS[0].summary.webpack.totalInitialSizeCSS.current,
40
+ budgetValue: 50 * 1024,
41
+ failed: false
42
+ },
43
+ chunkCount: {
44
+ currentValue: MULTIPLE_JOBS[0].summary.webpack.chunkCount.current,
45
+ budgetValue: 50,
46
+ failed: false
47
+ }
48
+ }
49
+ }
50
+ }));
24
51
  stories.add('custom keys', () => /*#__PURE__*/React.createElement(Summary, {
25
52
  keys: METRICS_WEBPACK_ASSETS,
26
53
  data: MULTIPLE_JOBS[0].summary