@bundle-stats/ui 3.3.0 → 3.3.1-beta.8

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 (58) hide show
  1. package/build-storybook.log +4691 -0
  2. package/lib/app/app.js +20 -5
  3. package/lib/app/app.stories.js +22 -1
  4. package/lib/assets/icons/alert-circle.svg +1 -0
  5. package/lib/assets/icons/check-circle.svg +1 -0
  6. package/lib/assets/icons.svg +8 -0
  7. package/lib/components/budget-info/budget-info.js +80 -0
  8. package/lib/components/budget-info/budget-info.module.css +40 -0
  9. package/lib/components/budget-info/budget-info.stories.js +44 -0
  10. package/lib/components/budget-info/index.js +18 -0
  11. package/lib/components/budget-insights/budget-insights.js +200 -0
  12. package/lib/components/budget-insights/budget-insights.module.css +53 -0
  13. package/lib/components/budget-insights/budget-insights.stories.js +104 -0
  14. package/lib/components/budget-insights/index.js +18 -0
  15. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js +43 -7
  16. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  17. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +20 -1
  18. package/lib/components/index.js +8 -0
  19. package/lib/components/summary/summary.js +12 -7
  20. package/lib/components/summary/summary.module.css +1 -1
  21. package/lib/components/summary/summary.stories.js +29 -0
  22. package/lib/components/summary-item/summary-item.js +22 -5
  23. package/lib/components/summary-item/summary-item.module.css +11 -0
  24. package/lib/components/summary-item/summary-item.stories.js +51 -53
  25. package/lib/constants.js +4 -4
  26. package/lib/ui/alert/alert.js +13 -3
  27. package/lib/ui/icon/icon.js +2 -0
  28. package/lib-esm/app/app.js +19 -5
  29. package/lib-esm/app/app.stories.js +22 -1
  30. package/lib-esm/assets/icons/alert-circle.svg +1 -0
  31. package/lib-esm/assets/icons/check-circle.svg +1 -0
  32. package/lib-esm/assets/icons.svg +8 -0
  33. package/lib-esm/components/budget-info/budget-info.js +58 -0
  34. package/lib-esm/components/budget-info/budget-info.module.css +40 -0
  35. package/lib-esm/components/budget-info/budget-info.stories.js +30 -0
  36. package/lib-esm/components/budget-info/index.js +1 -0
  37. package/lib-esm/components/budget-insights/budget-insights.js +148 -0
  38. package/lib-esm/components/budget-insights/budget-insights.module.css +53 -0
  39. package/lib-esm/components/budget-insights/budget-insights.stories.js +84 -0
  40. package/lib-esm/components/budget-insights/index.js +1 -0
  41. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +24 -7
  42. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.module.css +2 -2
  43. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +18 -0
  44. package/lib-esm/components/index.js +1 -0
  45. package/lib-esm/components/summary/summary.js +11 -7
  46. package/lib-esm/components/summary/summary.module.css +1 -1
  47. package/lib-esm/components/summary/summary.stories.js +27 -0
  48. package/lib-esm/components/summary-item/summary-item.js +20 -4
  49. package/lib-esm/components/summary-item/summary-item.module.css +11 -0
  50. package/lib-esm/components/summary-item/summary-item.stories.js +50 -38
  51. package/lib-esm/constants.js +4 -4
  52. package/lib-esm/ui/alert/alert.js +6 -3
  53. package/lib-esm/ui/icon/icon.js +2 -0
  54. package/package.json +13 -13
  55. package/src/app/app.jsx +2 -2
  56. package/src/app/header/header.jsx +1 -1
  57. package/src/components/bundle-assets/bundle-assets.jsx +1 -1
  58. package/storybook-static/favicon.ico +0 -0
@@ -31,4 +31,22 @@ EmptyBaseline.args = {
31
31
  jobs: createJobs([{
32
32
  webpack: currentStats
33
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
+ })
34
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';
@@ -13,12 +13,16 @@ import { FlexStack } from '../../layout/flex-stack';
13
13
  import { ComponentLink } from '../component-link';
14
14
  import { SummaryItem } from '../summary-item';
15
15
  import css from './summary.module.css';
16
+
17
+ const getSourceMetricId = fullMetricId => fullMetricId.split('.').slice(1).join('.');
18
+
16
19
  export const Summary = _ref => {
17
20
  let {
18
21
  className,
19
22
  size,
20
23
  keys,
21
24
  data,
25
+ budgets,
22
26
  loading,
23
27
  showSummaryItemDelta,
24
28
  summaryItemLink: SummaryItemCustomLink
@@ -30,7 +34,7 @@ export const Summary = _ref => {
30
34
  className: css.items
31
35
  }, Array.from(METRIC_COMPONENT_LINKS).filter(_ref2 => {
32
36
  let [metricId] = _ref2;
33
- return keys.includes(metricId);
37
+ return keys.includes(getSourceMetricId(metricId));
34
38
  }).map(_ref3 => {
35
39
  let [metricId, metricOptions] = _ref3;
36
40
  return /*#__PURE__*/React.createElement("div", {
@@ -40,6 +44,7 @@ export const Summary = _ref => {
40
44
  size: size,
41
45
  id: metricId,
42
46
  data: _get(data, metricId),
47
+ budget: _get(budgets, getSourceMetricId(metricId)),
43
48
  loading: loading,
44
49
  showMetricDescription: true,
45
50
  showDelta: showSummaryItemDelta && metricOptions.showDelta !== false
@@ -54,6 +59,7 @@ export const Summary = _ref => {
54
59
  Summary.defaultProps = {
55
60
  className: '',
56
61
  data: null,
62
+ budgets: null,
57
63
  loading: false,
58
64
  size: '',
59
65
  keys: METRICS_WEBPACK_GENERAL,
@@ -64,12 +70,10 @@ Summary.propTypes = {
64
70
  className: PropTypes.string,
65
71
  size: PropTypes.string,
66
72
  keys: PropTypes.arrayOf(PropTypes.string),
67
- data: PropTypes.shape({
68
- [PropTypes.string]: PropTypes.shape({
69
- baseline: PropTypes.number,
70
- current: PropTypes.number
71
- })
72
- }),
73
+ data: PropTypes.object,
74
+ // eslint-disable-line react/forbid-prop-types
75
+ budgets: PropTypes.object,
76
+ // eslint-disable-line react/forbid-prop-types
73
77
  loading: PropTypes.bool,
74
78
  showSummaryItemDelta: PropTypes.bool,
75
79
  summaryItemLink: PropTypes.elementType
@@ -4,7 +4,7 @@
4
4
  }
5
5
 
6
6
  .item {
7
- padding: var(--space-small) var(--space-xxlarge) var(--space-small) var(--space-small);
7
+ padding: var(--space-small);
8
8
  flex: 1 0 25%; /* force equal sizes */
9
9
  position: relative;
10
10
  z-index: 1;
@@ -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
@@ -11,6 +11,7 @@ import { Stack } from '../../layout/stack';
11
11
  import { FlexStack } from '../../layout/flex-stack';
12
12
  import { Metric } from '../metric';
13
13
  import { Delta } from '../delta';
14
+ import { BudgetInfo } from '../budget-info';
14
15
  import css from './summary-item.module.css';
15
16
 
16
17
  const MetricInfo = _ref => {
@@ -43,6 +44,7 @@ export const SummaryItem = _ref2 => {
43
44
  loading,
44
45
  showDelta,
45
46
  showMetricDescription,
47
+ budget,
46
48
  ...props
47
49
  } = _ref2;
48
50
  const {
@@ -64,12 +66,18 @@ export const SummaryItem = _ref2 => {
64
66
  as: "h3",
65
67
  space: "xxxsmall",
66
68
  className: css.title
67
- }, /*#__PURE__*/React.createElement("span", null, metric.label), showMetricDescriptionTooltip && /*#__PURE__*/React.createElement(HoverCard, {
69
+ }, /*#__PURE__*/React.createElement("span", {
70
+ className: css.titleLabel
71
+ }, metric.label), showMetricDescriptionTooltip && /*#__PURE__*/React.createElement(HoverCard, {
68
72
  className: css.icon,
69
73
  label: /*#__PURE__*/React.createElement(Icon, {
70
74
  glyph: "help"
71
75
  })
72
- }, /*#__PURE__*/React.createElement(MetricInfo, metric))), !loading ? /*#__PURE__*/React.createElement(Stack, null, /*#__PURE__*/React.createElement(Metric, {
76
+ }, /*#__PURE__*/React.createElement(MetricInfo, metric)), budget && /*#__PURE__*/React.createElement(BudgetInfo, {
77
+ className: css.budgetInfo,
78
+ metricId: id,
79
+ budgetInsight: budget
80
+ })), !loading ? /*#__PURE__*/React.createElement(Stack, null, /*#__PURE__*/React.createElement(Metric, {
73
81
  className: css.currentMetric,
74
82
  value: current,
75
83
  formatter: metric.formatter,
@@ -96,7 +104,8 @@ SummaryItem.defaultProps = {
96
104
  size: 'medium',
97
105
  loading: false,
98
106
  showMetricDescription: false,
99
- showDelta: true
107
+ showDelta: true,
108
+ budget: undefined
100
109
  };
101
110
  SummaryItem.propTypes = {
102
111
  /** Adopted child class name */
@@ -122,5 +131,12 @@ SummaryItem.propTypes = {
122
131
  showMetricDescription: PropTypes.bool,
123
132
 
124
133
  /** Show delta */
125
- showDelta: PropTypes.bool
134
+ showDelta: PropTypes.bool,
135
+
136
+ /** Budget insight data */
137
+ budget: PropTypes.shape({
138
+ currentBalue: PropTypes.number,
139
+ budgetValue: PropTypes.number,
140
+ failed: PropTypes.bool
141
+ })
126
142
  };
@@ -1,3 +1,8 @@
1
+ .root {
2
+ position: relative;
3
+ padding-right: var(--space-xlarge);
4
+ }
5
+
1
6
  .title {
2
7
  align-items: center;
3
8
  color: var(--color-text-ultra-light);
@@ -8,6 +13,12 @@
8
13
  white-space: nowrap;
9
14
  }
10
15
 
16
+ .budgetInfo {
17
+ position: absolute;
18
+ right: 0;
19
+ top: 0;
20
+ }
21
+
11
22
  .currentMetric {
12
23
  display: block;
13
24
  color: var(--color-heading);
@@ -2,44 +2,56 @@ import React from 'react';
2
2
  import { SummaryItem } from './summary-item';
3
3
  export default {
4
4
  title: 'Components/SummaryItem',
5
- component: SummaryItem
6
- };
7
- export const Standard = () => /*#__PURE__*/React.createElement(SummaryItem, {
8
- loading: false,
9
- id: "webpack.totalSizeByTypeALL",
10
- data: {
11
- current: 120 * 1000,
12
- baseline: 100 * 1000
13
- }
14
- });
15
- export const SizeLarge = () => /*#__PURE__*/React.createElement(SummaryItem, {
16
- size: "large",
17
- loading: false,
18
- id: "webpack.totalSizeByTypeALL",
19
- data: {
20
- current: 120 * 1000,
21
- baseline: 100 * 1000
5
+ component: SummaryItem,
6
+ decorators: [Story => /*#__PURE__*/React.createElement("div", {
7
+ style: {
8
+ maxWidth: '320px'
9
+ }
10
+ }, /*#__PURE__*/React.createElement(Story, null))],
11
+ args: {
12
+ loading: false,
13
+ id: 'webpack.totalSizeByTypeALL',
14
+ data: {
15
+ current: 120 * 1000,
16
+ baseline: 100 * 1000
17
+ }
22
18
  }
23
- });
24
- export const ShowMetricDescription = () => /*#__PURE__*/React.createElement(SummaryItem, {
25
- loading: false,
26
- id: "webpack.totalSizeByTypeALL",
27
- data: {
28
- current: 120 * 1000,
29
- baseline: 100 * 1000
30
- },
19
+ };
20
+
21
+ const Template = args => /*#__PURE__*/React.createElement(SummaryItem, args);
22
+
23
+ export const Standard = Template.bind();
24
+ export const SizeLarge = Template.bind();
25
+ SizeLarge.args = {
26
+ size: 'large'
27
+ };
28
+ export const ShowMetricDescription = Template.bind();
29
+ ShowMetricDescription.args = {
31
30
  showMetricDescription: true
32
- });
33
- export const ShowDeltaFalse = () => /*#__PURE__*/React.createElement(SummaryItem, {
34
- loading: false,
35
- id: "webpack.totalSizeByTypeALL",
36
- data: {
37
- current: 120 * 1000,
38
- baseline: 0
39
- },
31
+ };
32
+ export const ShowDeltaFalse = Template.bind();
33
+ ShowDeltaFalse.args = {
40
34
  showDelta: false
41
- });
42
- export const Loading = () => /*#__PURE__*/React.createElement(SummaryItem, {
43
- loading: true,
44
- id: "webpack.totalSizeByTypeALL"
45
- });
35
+ };
36
+ export const BudgetError = Template.bind();
37
+ BudgetError.args = {
38
+ showMetricDescription: true,
39
+ budget: {
40
+ currentValue: 120 * 1000,
41
+ budgetValue: 100 * 1000,
42
+ failed: true
43
+ }
44
+ };
45
+ export const BudgetSuccess = Template.bind();
46
+ BudgetSuccess.args = {
47
+ showMetricDescription: true,
48
+ budget: {
49
+ currentValue: 120 * 1000,
50
+ budgetValue: 120 * 1024,
51
+ failed: false
52
+ }
53
+ };
54
+ export const Loading = Template.bind();
55
+ Loading.args = {
56
+ loading: true
57
+ };
@@ -25,7 +25,7 @@ export const SECTION_URLS = {
25
25
  export const ASSETS_SIZES_FILE_TYPE_MAP = FILE_TYPES.reduce((agg, fileType) => ({ ...agg,
26
26
  [`webpack.sizes.totalSizeByType${fileType}`]: fileType
27
27
  }), {});
28
- export const METRICS_WEBPACK_GENERAL = ['webpack.totalSizeByTypeALL', 'webpack.totalInitialSizeJS', 'webpack.totalInitialSizeCSS', 'webpack.cacheInvalidation'];
29
- export const METRICS_WEBPACK_ASSETS = ['webpack.assetCount', 'webpack.chunkCount'];
30
- export const METRICS_WEBPACK_MODULES = ['webpack.moduleCount', 'webpack.duplicateModulesCount', 'webpack.duplicateCode'];
31
- export const METRICS_WEBPACK_PACKAGES = ['webpack.packageCount', 'webpack.duplicatePackagesCount'];
28
+ export const METRICS_WEBPACK_GENERAL = ['totalSizeByTypeALL', 'totalInitialSizeJS', 'totalInitialSizeCSS', 'cacheInvalidation'];
29
+ export const METRICS_WEBPACK_ASSETS = ['assetCount', 'chunkCount'];
30
+ export const METRICS_WEBPACK_MODULES = ['moduleCount', 'duplicateModulesCount', 'duplicateCode'];
31
+ export const METRICS_WEBPACK_PACKAGES = ['packageCount', 'duplicatePackagesCount'];
@@ -1,3 +1,5 @@
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
+
1
3
  import React from 'react';
2
4
  import cx from 'classnames';
3
5
  import PropTypes from 'prop-types';
@@ -7,13 +9,14 @@ export const Alert = props => {
7
9
  const {
8
10
  className,
9
11
  kind,
10
- children
12
+ children,
13
+ ...restProps
11
14
  } = props;
12
- return /*#__PURE__*/React.createElement(Box, {
15
+ return /*#__PURE__*/React.createElement(Box, _extends({
13
16
  padding: ['xsmall', 'small'],
14
17
  outline: true,
15
18
  className: cx(css.root, className, css[kind])
16
- }, children);
19
+ }, restProps), children);
17
20
  };
18
21
  Alert.propTypes = {
19
22
  className: PropTypes.string,
@@ -5,11 +5,13 @@ import PropTypes from 'prop-types';
5
5
  import cx from 'classnames';
6
6
  import css from './icon.module.css';
7
7
  const ICONS = {
8
+ ALERT_CIRCLE: 'alert-circle',
8
9
  ARROW: 'arrow',
9
10
  ARROW_RIGHT_CIRLCE: 'arrow-right-circle',
10
11
  CANCEL: 'cancel',
11
12
  CLOSE: 'close',
12
13
  CLOCK: 'clock',
14
+ CHECK_CIRCLE: 'check-circle',
13
15
  COMMIT: 'commit',
14
16
  FILTER: 'filter',
15
17
  HELP: 'help',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bundle-stats/ui",
3
- "version": "3.3.0",
3
+ "version": "3.3.1-beta.8",
4
4
  "description": "UI components for BundleStats projects.",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib-esm/index.js",
@@ -37,7 +37,7 @@
37
37
  "homepage": "https://github.com/relative-ci/bundle-stats/blob/master/packages/ui#readme",
38
38
  "devDependencies": {
39
39
  "@babel/cli": "7.17.6",
40
- "@babel/core": "7.17.5",
40
+ "@babel/core": "7.17.7",
41
41
  "@babel/plugin-proposal-class-properties": "7.16.7",
42
42
  "@babel/preset-env": "7.16.11",
43
43
  "@babel/preset-react": "7.16.7",
@@ -54,7 +54,7 @@
54
54
  "babel-jest": "27.5.1",
55
55
  "babel-plugin-lodash": "3.3.4",
56
56
  "babel-plugin-require-context-hook": "1.0.0",
57
- "chromatic": "6.5.1",
57
+ "chromatic": "6.5.3",
58
58
  "classnames": "2.3.1",
59
59
  "colormap": "2.3.2",
60
60
  "core-js": "3.20.2",
@@ -67,7 +67,7 @@
67
67
  "jest": "27.5.1",
68
68
  "jest-date-mock": "1.0.8",
69
69
  "lodash": "4.17.21",
70
- "postcss": "8.4.7",
70
+ "postcss": "8.4.12",
71
71
  "prop-types": "15.8.1",
72
72
  "react": "16.14.0",
73
73
  "react-dom": "16.14.0",
@@ -76,21 +76,21 @@
76
76
  "rimraf": "3.0.2"
77
77
  },
78
78
  "peerDependencies": {
79
- "classnames": "2.2.6",
80
- "core-js": "3.20.2",
81
- "lodash": "4.17.21",
82
- "prop-types": "15.7.2",
83
- "react": "16.14.0",
84
- "react-dom": "16.14.0",
85
- "react-router-dom": "5.3.0"
79
+ "classnames": "^2.0.0",
80
+ "core-js": "^3.0.0",
81
+ "lodash": "^4.0.0",
82
+ "prop-types": "^15.0.0",
83
+ "react": "^16.0.0",
84
+ "react-dom": "^16.0.0",
85
+ "react-router-dom": "^5.0.0"
86
86
  },
87
87
  "dependencies": {
88
- "@bundle-stats/utils": "^3.3.0",
88
+ "@bundle-stats/utils": "^3.3.1-beta.8",
89
89
  "ariakit": "2.0.0-alpha.19",
90
90
  "date-fns": "2.28.0",
91
91
  "modern-css-reset": "1.4.0",
92
92
  "query-string": "7.1.1",
93
93
  "use-query-params": "1.2.3"
94
94
  },
95
- "gitHead": "deb44e2ce6e24626147917c578ade82cf8ad36b6"
95
+ "gitHead": "f9c5ac2bd0cd6019e8a32586fe4c23b38757efe9"
96
96
  }
package/src/app/app.jsx CHANGED
@@ -47,7 +47,7 @@ const Layout = ({ jobs, version, ...props }) => (
47
47
  );
48
48
 
49
49
  Layout.propTypes = {
50
- jobs: PropTypes.arrayOf(PropTypes.object),
50
+ jobs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
51
51
  version: PropTypes.string,
52
52
  };
53
53
 
@@ -63,7 +63,7 @@ const JobsProvider = ({ jobs, ...restProps }) => {
63
63
  };
64
64
 
65
65
  JobsProvider.propTypes = {
66
- jobs: PropTypes.arrayOf(PropTypes.object).isRequired,
66
+ jobs: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types
67
67
  };
68
68
 
69
69
  const OverviewContent = () => {
@@ -16,7 +16,7 @@ export const Header = ({ className, jobs }) => (
16
16
 
17
17
  Header.propTypes = {
18
18
  className: PropTypes.string,
19
- jobs: PropTypes.arrayOf(PropTypes.object),
19
+ jobs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
20
20
  };
21
21
 
22
22
  Header.defaultProps = {
@@ -146,7 +146,7 @@ RowHeader.propTypes = {
146
146
  isChunk: PropTypes.bool,
147
147
  isInitial: PropTypes.bool,
148
148
  isEntry: PropTypes.bool,
149
- runs: PropTypes.arrayOf(PropTypes.object),
149
+ runs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
150
150
  }).isRequired,
151
151
  chunks: PropTypes.arrayOf(
152
152
  PropTypes.shape({
Binary file