@bundle-stats/ui 3.2.0-beta.1 → 3.2.1-beta.3

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 (255) hide show
  1. package/.babelrc.js +3 -3
  2. package/CHANGELOG.md +20 -4
  3. package/lib/assets/icons/arrow.svg +1 -0
  4. package/lib/assets/icons/cancel.svg +1 -0
  5. package/lib/assets/icons/clock.svg +1 -0
  6. package/lib/assets/icons/close.svg +1 -0
  7. package/lib/assets/icons/commit.svg +1 -0
  8. package/lib/assets/icons/filter.svg +1 -0
  9. package/lib/assets/icons/help.svg +1 -0
  10. package/lib/assets/icons/menu.svg +1 -0
  11. package/lib/{ui/icon/assets → assets/icons}/sort.svg +0 -0
  12. package/lib/assets/icons/warning.svg +1 -0
  13. package/lib/assets/icons.svg +1 -0
  14. package/lib/components/bundle-assets/bundle-assets.js +3 -3
  15. package/lib/components/bundle-assets/bundle-assets.stories.js +19 -7
  16. package/lib/components/bundle-assets/bundle-assets.utils.js +95 -28
  17. package/lib/components/bundle-assets/index.js +115 -4
  18. package/lib/components/bundle-modules/bundle-modules.js +3 -3
  19. package/lib/components/bundle-modules/bundle-modules.utils.js +152 -0
  20. package/lib/components/bundle-modules/index.js +135 -4
  21. package/lib/components/bundle-packages/bundle-packages.js +3 -3
  22. package/lib/components/bundle-packages/{bundle-packages.container.js → bundle-packages.utils.js} +8 -42
  23. package/lib/components/bundle-packages/index.js +111 -3
  24. package/lib/components/metrics-table/metrics-table.js +3 -7
  25. package/lib/components/metrics-table-search/metrics-table-search.js +2 -1
  26. package/lib/components/metrics-table-search/metrics-table-search.module.css +2 -1
  27. package/lib/hooks/rows-filter.js +28 -0
  28. package/lib/hooks/rows-sort.js +54 -0
  29. package/lib/{hocs/with-search.js → hooks/search-params.js} +15 -64
  30. package/lib/ui/icon/icon.js +4 -191
  31. package/lib/ui/sort-dropdown/sort-dropdown.js +12 -13
  32. package/lib/ui/sort-dropdown/sort-dropdown.stories.js +2 -2
  33. package/lib/utils.js +6 -3
  34. package/lib-esm/app/app.js +60 -70
  35. package/lib-esm/app/app.stories.js +50 -71
  36. package/lib-esm/app/header/header.js +5 -3
  37. package/lib-esm/assets/icons/arrow.svg +1 -0
  38. package/lib-esm/assets/icons/cancel.svg +1 -0
  39. package/lib-esm/assets/icons/clock.svg +1 -0
  40. package/lib-esm/assets/icons/close.svg +1 -0
  41. package/lib-esm/assets/icons/commit.svg +1 -0
  42. package/lib-esm/assets/icons/filter.svg +1 -0
  43. package/lib-esm/assets/icons/help.svg +1 -0
  44. package/lib-esm/assets/icons/menu.svg +1 -0
  45. package/lib-esm/{ui/icon/assets → assets/icons}/sort.svg +0 -0
  46. package/lib-esm/assets/icons/warning.svg +1 -0
  47. package/lib-esm/assets/icons.svg +1 -0
  48. package/lib-esm/components/asset-info/asset-info.js +21 -17
  49. package/lib-esm/components/asset-info/asset-info.stories.js +29 -33
  50. package/lib-esm/components/browsertime-table/browsertime-table.js +6 -4
  51. package/lib-esm/components/browsertime-table/browsertime-table.stories.js +9 -13
  52. package/lib-esm/components/bundle-assets/bundle-assets.constants.js +17 -15
  53. package/lib-esm/components/bundle-assets/bundle-assets.js +107 -97
  54. package/lib-esm/components/bundle-assets/bundle-assets.stories.js +58 -63
  55. package/lib-esm/components/bundle-assets/bundle-assets.utils.js +65 -21
  56. package/lib-esm/components/bundle-assets/index.js +89 -2
  57. package/lib-esm/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js +49 -51
  58. package/lib-esm/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.stories.js +9 -13
  59. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +14 -13
  60. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +13 -19
  61. package/lib-esm/components/bundle-modules/bundle-modules.constants.js +17 -15
  62. package/lib-esm/components/bundle-modules/bundle-modules.js +112 -110
  63. package/lib-esm/components/bundle-modules/bundle-modules.stories.js +46 -58
  64. package/lib-esm/components/bundle-modules/bundle-modules.utils.js +98 -0
  65. package/lib-esm/components/bundle-modules/index.js +114 -3
  66. package/lib-esm/components/bundle-packages/bundle-packages.constants.js +17 -15
  67. package/lib-esm/components/bundle-packages/bundle-packages.js +71 -69
  68. package/lib-esm/components/bundle-packages/bundle-packages.stories.js +34 -46
  69. package/lib-esm/components/bundle-packages/bundle-packages.utils.js +44 -0
  70. package/lib-esm/components/bundle-packages/index.js +87 -2
  71. package/lib-esm/components/component-link.js +10 -13
  72. package/lib-esm/components/delta/delta.js +8 -6
  73. package/lib-esm/components/delta/delta.stories.js +26 -38
  74. package/lib-esm/components/duplicate-packages-warning/duplicate-packages-warning.js +11 -9
  75. package/lib-esm/components/duplicate-packages-warning/duplicate-packages-warning.stories.js +20 -26
  76. package/lib-esm/components/job-header/job-header.js +14 -11
  77. package/lib-esm/components/job-header/job-header.stories.js +12 -18
  78. package/lib-esm/components/job-meta/job-meta.js +15 -18
  79. package/lib-esm/components/job-meta/job-meta.stories.js +10 -12
  80. package/lib-esm/components/job-name/job-name.js +9 -12
  81. package/lib-esm/components/job-name/job-name.stories.js +15 -23
  82. package/lib-esm/components/jobs-header/jobs-header.js +12 -12
  83. package/lib-esm/components/jobs-header/jobs-header.stories.js +80 -86
  84. package/lib-esm/components/lighthouse-table/lighthouse-table.js +6 -4
  85. package/lib-esm/components/lighthouse-table/lighthouse-table.stories.js +10 -14
  86. package/lib-esm/components/metric/metric.js +15 -19
  87. package/lib-esm/components/metric/metric.stories.js +36 -58
  88. package/lib-esm/components/metrics-table/metrics-table.js +128 -139
  89. package/lib-esm/components/metrics-table/metrics-table.stories.js +27 -35
  90. package/lib-esm/components/metrics-table-options/metrics-table-options.js +16 -19
  91. package/lib-esm/components/metrics-table-options/metrics-table-options.stories.js +6 -12
  92. package/lib-esm/components/metrics-table-search/metrics-table-search.js +17 -18
  93. package/lib-esm/components/metrics-table-search/metrics-table-search.module.css +2 -1
  94. package/lib-esm/components/metrics-table-search/metrics-table-search.stories.js +4 -9
  95. package/lib-esm/components/metrics-table-title/metrics-table-title.js +9 -7
  96. package/lib-esm/components/module-info/module-info.js +15 -13
  97. package/lib-esm/components/module-info/module-info.stories.js +20 -22
  98. package/lib-esm/components/summary/summary.js +25 -27
  99. package/lib-esm/components/summary/summary.stories.js +31 -46
  100. package/lib-esm/components/summary-item/summary-item.js +27 -28
  101. package/lib-esm/components/summary-item/summary-item.stories.js +40 -50
  102. package/lib-esm/components/total-size-type-title/total-size-type-title.js +4 -2
  103. package/lib-esm/constants.js +25 -30
  104. package/lib-esm/hooks/rows-filter.js +20 -0
  105. package/lib-esm/hooks/rows-sort.js +22 -0
  106. package/lib-esm/hooks/search-params.js +170 -0
  107. package/lib-esm/layout/box/box.js +12 -19
  108. package/lib-esm/layout/box/box.stories.js +34 -46
  109. package/lib-esm/layout/flex-stack/flex-stack.js +9 -12
  110. package/lib-esm/layout/flex-stack/flex-stack.stories.js +39 -49
  111. package/lib-esm/layout/footer/footer.js +11 -11
  112. package/lib-esm/layout/footer/footer.stories.js +7 -11
  113. package/lib-esm/layout/header/header.js +10 -14
  114. package/lib-esm/layout/header/header.stories.js +50 -44
  115. package/lib-esm/layout/stack/stack.js +8 -6
  116. package/lib-esm/layout/stack/stack.stories.js +39 -49
  117. package/lib-esm/layout/sub-header/sub-header.js +14 -12
  118. package/lib-esm/layout/sub-header/sub-header.stories.js +57 -69
  119. package/lib-esm/prototypes/typography.stories.js +49 -58
  120. package/lib-esm/query-state.js +13 -16
  121. package/lib-esm/stories/wrapper.js +6 -12
  122. package/lib-esm/ui/alert/alert.js +6 -4
  123. package/lib-esm/ui/alert/alert.stories.js +14 -24
  124. package/lib-esm/ui/container/container.js +6 -9
  125. package/lib-esm/ui/container/container.stories.js +15 -17
  126. package/lib-esm/ui/dropdown/dropdown.js +13 -11
  127. package/lib-esm/ui/dropdown/dropdown.stories.js +22 -26
  128. package/lib-esm/ui/empty-set/empty-set.js +12 -12
  129. package/lib-esm/ui/empty-set/empty-set.stories.js +11 -19
  130. package/lib-esm/ui/file-name/file-name.js +6 -4
  131. package/lib-esm/ui/file-name/file-name.stories.js +12 -16
  132. package/lib-esm/ui/filters/filters.container.js +57 -76
  133. package/lib-esm/ui/filters/filters.js +71 -77
  134. package/lib-esm/ui/filters/filters.stories.js +82 -100
  135. package/lib-esm/ui/filters/filters.utils.js +26 -24
  136. package/lib-esm/ui/filters/index.js +1 -1
  137. package/lib-esm/ui/horizontal-bar-chart/horizontal-bar-chart.js +68 -78
  138. package/lib-esm/ui/horizontal-bar-chart/horizontal-bar-chart.stories.js +67 -77
  139. package/lib-esm/ui/icon/icon.js +13 -202
  140. package/lib-esm/ui/icon/icon.stories.js +16 -24
  141. package/lib-esm/ui/input/input.js +7 -10
  142. package/lib-esm/ui/input/input.stories.js +8 -12
  143. package/lib-esm/ui/loader/loader.js +6 -9
  144. package/lib-esm/ui/loader/loader.stories.js +5 -9
  145. package/lib-esm/ui/logo/logo.constants.js +6 -6
  146. package/lib-esm/ui/logo/logo.js +15 -15
  147. package/lib-esm/ui/logo/logo.stories.js +14 -22
  148. package/lib-esm/ui/panels/panels.js +13 -23
  149. package/lib-esm/ui/panels/panels.stories.js +14 -16
  150. package/lib-esm/ui/popover/popover.js +11 -9
  151. package/lib-esm/ui/popover/popover.stories.js +7 -11
  152. package/lib-esm/ui/skeleton/skeleton.js +6 -9
  153. package/lib-esm/ui/skeleton/skeleton.stories.js +2 -4
  154. package/lib-esm/ui/sort-dropdown/sort-dropdown.js +38 -38
  155. package/lib-esm/ui/sort-dropdown/sort-dropdown.stories.js +14 -16
  156. package/lib-esm/ui/table/table.js +26 -31
  157. package/lib-esm/ui/table/table.stories.js +84 -102
  158. package/lib-esm/ui/tabs/tabs.js +13 -21
  159. package/lib-esm/ui/tabs/tabs.stories.js +4 -6
  160. package/lib-esm/ui/toolbar/toolbar.js +7 -5
  161. package/lib-esm/ui/toolbar/toolbar.stories.js +14 -16
  162. package/lib-esm/ui/tooltip/tooltip.js +13 -16
  163. package/lib-esm/ui/tooltip/tooltip.stories.js +8 -12
  164. package/lib-esm/utils.js +20 -24
  165. package/package.json +25 -33
  166. package/src/assets/icons/arrow.svg +1 -0
  167. package/src/assets/icons/cancel.svg +1 -0
  168. package/src/assets/icons/clock.svg +1 -0
  169. package/src/assets/icons/close.svg +1 -0
  170. package/src/assets/icons/commit.svg +1 -0
  171. package/src/assets/icons/filter.svg +1 -0
  172. package/src/assets/icons/help.svg +1 -0
  173. package/src/assets/icons/menu.svg +1 -0
  174. package/src/{ui/icon/assets → assets/icons}/sort.svg +0 -0
  175. package/src/assets/icons/warning.svg +1 -0
  176. package/src/assets/icons.svg +1 -0
  177. package/src/components/bundle-assets/bundle-assets.jsx +3 -3
  178. package/src/components/bundle-assets/bundle-assets.utils.js +96 -17
  179. package/src/components/bundle-assets/index.jsx +89 -0
  180. package/src/components/bundle-modules/bundle-modules.jsx +3 -3
  181. package/src/components/bundle-modules/bundle-modules.utils.js +101 -0
  182. package/src/components/bundle-modules/index.jsx +107 -0
  183. package/src/components/bundle-packages/bundle-packages.jsx +3 -3
  184. package/src/components/bundle-packages/bundle-packages.utils.js +47 -0
  185. package/src/components/bundle-packages/index.jsx +84 -0
  186. package/src/components/metrics-table/metrics-table.jsx +2 -7
  187. package/src/components/metrics-table-search/metrics-table-search.jsx +1 -0
  188. package/src/components/metrics-table-search/metrics-table-search.module.css +2 -1
  189. package/src/hooks/rows-filter.js +16 -0
  190. package/src/hooks/rows-sort.js +24 -0
  191. package/src/hooks/search-params.js +153 -0
  192. package/src/ui/icon/icon.jsx +7 -42
  193. package/src/ui/sort-dropdown/sort-dropdown.jsx +11 -11
  194. package/src/utils.jsx +5 -3
  195. package/lib/components/bundle-assets/bundle-assets.container.js +0 -140
  196. package/lib/components/bundle-modules/bundle-modules.container.js +0 -222
  197. package/lib/hocs/with-custom-sort.js +0 -75
  198. package/lib/hocs/with-filtered-items.js +0 -54
  199. package/lib/ui/icon/assets/arrow.svg +0 -4
  200. package/lib/ui/icon/assets/branch.svg +0 -3
  201. package/lib/ui/icon/assets/cancel.svg +0 -4
  202. package/lib/ui/icon/assets/clock.svg +0 -3
  203. package/lib/ui/icon/assets/close.svg +0 -4
  204. package/lib/ui/icon/assets/commit.svg +0 -3
  205. package/lib/ui/icon/assets/filter.svg +0 -4
  206. package/lib/ui/icon/assets/help.svg +0 -4
  207. package/lib/ui/icon/assets/job.svg +0 -3
  208. package/lib/ui/icon/assets/menu.svg +0 -4
  209. package/lib/ui/icon/assets/package.svg +0 -3
  210. package/lib/ui/icon/assets/pull-request.svg +0 -3
  211. package/lib/ui/icon/assets/repository.svg +0 -3
  212. package/lib/ui/icon/assets/warning.svg +0 -4
  213. package/lib-esm/components/bundle-assets/bundle-assets.container.js +0 -119
  214. package/lib-esm/components/bundle-modules/bundle-modules.container.js +0 -175
  215. package/lib-esm/components/bundle-packages/bundle-packages.container.js +0 -91
  216. package/lib-esm/hocs/with-custom-sort.js +0 -43
  217. package/lib-esm/hocs/with-filtered-items.js +0 -35
  218. package/lib-esm/hocs/with-search.js +0 -214
  219. package/lib-esm/ui/icon/assets/arrow.svg +0 -4
  220. package/lib-esm/ui/icon/assets/branch.svg +0 -3
  221. package/lib-esm/ui/icon/assets/cancel.svg +0 -4
  222. package/lib-esm/ui/icon/assets/clock.svg +0 -3
  223. package/lib-esm/ui/icon/assets/close.svg +0 -4
  224. package/lib-esm/ui/icon/assets/commit.svg +0 -3
  225. package/lib-esm/ui/icon/assets/filter.svg +0 -4
  226. package/lib-esm/ui/icon/assets/help.svg +0 -4
  227. package/lib-esm/ui/icon/assets/job.svg +0 -3
  228. package/lib-esm/ui/icon/assets/menu.svg +0 -4
  229. package/lib-esm/ui/icon/assets/package.svg +0 -3
  230. package/lib-esm/ui/icon/assets/pull-request.svg +0 -3
  231. package/lib-esm/ui/icon/assets/repository.svg +0 -3
  232. package/lib-esm/ui/icon/assets/warning.svg +0 -4
  233. package/src/components/bundle-assets/bundle-assets.container.js +0 -146
  234. package/src/components/bundle-assets/index.js +0 -4
  235. package/src/components/bundle-modules/bundle-modules.container.js +0 -158
  236. package/src/components/bundle-modules/index.js +0 -4
  237. package/src/components/bundle-packages/bundle-packages.container.js +0 -93
  238. package/src/components/bundle-packages/index.js +0 -4
  239. package/src/hocs/with-custom-sort.jsx +0 -35
  240. package/src/hocs/with-filtered-items.jsx +0 -34
  241. package/src/hocs/with-search.jsx +0 -184
  242. package/src/ui/icon/assets/arrow.svg +0 -4
  243. package/src/ui/icon/assets/branch.svg +0 -3
  244. package/src/ui/icon/assets/cancel.svg +0 -4
  245. package/src/ui/icon/assets/clock.svg +0 -3
  246. package/src/ui/icon/assets/close.svg +0 -4
  247. package/src/ui/icon/assets/commit.svg +0 -3
  248. package/src/ui/icon/assets/filter.svg +0 -4
  249. package/src/ui/icon/assets/help.svg +0 -4
  250. package/src/ui/icon/assets/job.svg +0 -3
  251. package/src/ui/icon/assets/menu.svg +0 -4
  252. package/src/ui/icon/assets/package.svg +0 -3
  253. package/src/ui/icon/assets/pull-request.svg +0 -3
  254. package/src/ui/icon/assets/repository.svg +0 -3
  255. package/src/ui/icon/assets/warning.svg +0 -4
@@ -0,0 +1,107 @@
1
+ import React, { useMemo } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { uniqBy, map } from 'lodash';
4
+ import {
5
+ getModuleChunkFilters,
6
+ getModuleSourceTypeFilters,
7
+ getModuleFileTypeFilters,
8
+ } from '@bundle-stats/utils';
9
+ import * as webpack from '@bundle-stats/utils/lib-esm/webpack';
10
+
11
+ import { useRowsFilter } from '../../hooks/rows-filter';
12
+ import { useRowsSort } from '../../hooks/rows-sort';
13
+ import { useSearchParams } from '../../hooks/search-params';
14
+ import { BundleModules as BundleModulesComponent } from './bundle-modules';
15
+ import {
16
+ addRowSourceFlag,
17
+ getRowFilter,
18
+ getCustomSort,
19
+ useModuleFilterByChunk,
20
+ } from './bundle-modules.utils';
21
+ import { SORT_BY } from './bundle-modules.constants';
22
+
23
+ export const BundleModules = (props) => {
24
+ const { jobs, filters, search, setState, sortBy, direction, ...restProps } = props;
25
+
26
+ const chunks = uniqBy(jobs.map((job) => job?.meta?.webpack?.chunks || []).flat(), ({ id }) => id);
27
+ const chunkIds = map(chunks, 'id');
28
+
29
+ const { defaultFilters, allEntriesFilters } = useMemo(
30
+ () => ({
31
+ defaultFilters: {
32
+ changed: jobs?.length > 1,
33
+ ...getModuleSourceTypeFilters(true),
34
+ ...getModuleChunkFilters(chunkIds, true),
35
+ ...getModuleFileTypeFilters(true),
36
+ },
37
+ allEntriesFilters: {
38
+ changed: false,
39
+ ...getModuleSourceTypeFilters(true),
40
+ ...getModuleChunkFilters(chunkIds, true),
41
+ ...getModuleFileTypeFilters(true),
42
+ },
43
+ }),
44
+ [jobs],
45
+ );
46
+
47
+ const searchParams = useSearchParams({
48
+ search,
49
+ filters,
50
+ defaultFilters,
51
+ allEntriesFilters,
52
+ setState,
53
+ });
54
+
55
+ const filteredJobsByChunkIds = useModuleFilterByChunk({
56
+ jobs,
57
+ filters: searchParams.filters,
58
+ chunkIds,
59
+ });
60
+
61
+ const { rows, totalRowCount } = useMemo(() => {
62
+ const result = webpack.compareBySection.modules(filteredJobsByChunkIds, [addRowSourceFlag]);
63
+ return { rows: result, totalRowCount: result.length };
64
+ }, [filteredJobsByChunkIds]);
65
+
66
+ const filteredRows = useRowsFilter({
67
+ rows,
68
+ searchPattern: searchParams.searchPattern,
69
+ filters: searchParams.filters,
70
+ getRowFilter,
71
+ });
72
+
73
+ const sortParams = useRowsSort({
74
+ rows: filteredRows,
75
+ sortFields: SORT_BY,
76
+ sortBy,
77
+ sortDirection: direction,
78
+ getCustomSort,
79
+ });
80
+
81
+ return (
82
+ <BundleModulesComponent
83
+ jobs={jobs}
84
+ chunks={chunks}
85
+ {...restProps}
86
+ {...searchParams}
87
+ {...sortParams}
88
+ totalRowCount={totalRowCount}
89
+ />
90
+ );
91
+ };
92
+
93
+ BundleModules.propTypes = {
94
+ jobs: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types
95
+ filters: PropTypes.object, // eslint-disable-line react/forbid-prop-types
96
+ search: PropTypes.string,
97
+ sortBy: PropTypes.string,
98
+ direction: PropTypes.string,
99
+ setState: PropTypes.func.isRequired,
100
+ };
101
+
102
+ BundleModules.defaultProps = {
103
+ filters: undefined,
104
+ search: undefined,
105
+ sortBy: undefined,
106
+ direction: undefined,
107
+ };
@@ -149,7 +149,7 @@ export const BundlePackages = (props) => {
149
149
  resetAllFilters,
150
150
  totalRowCount,
151
151
  filters,
152
- sortItems,
152
+ sortFields,
153
153
  sort,
154
154
  updateSort,
155
155
  search,
@@ -178,7 +178,7 @@ export const BundlePackages = (props) => {
178
178
  className={css.toolbar}
179
179
  renderActions={({ actionClassName }) => (
180
180
  <FlexStack space="xxsmall" className={cx(css.dropdown, actionClassName)}>
181
- <SortDropdown items={sortItems} {...sort} onChange={updateSort} />
181
+ <SortDropdown fields={sortFields} {...sort} onChange={updateSort} />
182
182
  <MetricsTableOptions
183
183
  handleViewAll={resetAllFilters}
184
184
  handleResetFilters={resetFilters}
@@ -266,7 +266,7 @@ BundlePackages.propTypes = {
266
266
  changed: PropTypes.bool,
267
267
  }).isRequired,
268
268
  hasActiveFilters: PropTypes.bool,
269
- sortItems: PropTypes.shape({
269
+ sortFields: PropTypes.shape({
270
270
  [PropTypes.string]: PropTypes.shape({
271
271
  label: PropTypes.string,
272
272
  defaultDirection: PropTypes.bool,
@@ -0,0 +1,47 @@
1
+ import { get, flatten, uniq } from 'lodash';
2
+ import { PACKAGE_FILTERS } from '@bundle-stats/utils';
3
+
4
+ import { SORT_BY_NAME, SORT_BY_DELTA, SORT_BY_SIZE } from './bundle-packages.constants';
5
+
6
+ // Get a list of duplicate packages across jobs
7
+ export const getDuplicatePackages = (jobs) => {
8
+ const jobsDuplicatePackages = jobs.map((job) => {
9
+ const insightsData = job?.insights?.webpack?.duplicatePackages?.data || {};
10
+ return Object.values(insightsData).flat();
11
+ });
12
+
13
+ return uniq(flatten(jobsDuplicatePackages));
14
+ };
15
+
16
+ export const getRowFilter = (filters) => (item) => {
17
+ if (filters[PACKAGE_FILTERS.CHANGED] && !item.changed) {
18
+ return false;
19
+ }
20
+
21
+ if (filters[PACKAGE_FILTERS.DUPLICATE] && !item.duplicate) {
22
+ return false;
23
+ }
24
+
25
+ return true;
26
+ };
27
+
28
+ export const getCustomSort = (sortId) => (item) => {
29
+ if (sortId === SORT_BY_NAME) {
30
+ return item.key;
31
+ }
32
+
33
+ if (sortId === SORT_BY_DELTA) {
34
+ return get(item, 'runs[0].deltaPercentage', 0);
35
+ }
36
+
37
+ if (sortId === SORT_BY_SIZE) {
38
+ return get(item, 'runs[0].value', 0);
39
+ }
40
+
41
+ return [!item.changed, item.key];
42
+ };
43
+
44
+ export const getAddRowDuplicateFlag = (duplicateJobs) => (row) => ({
45
+ ...row,
46
+ duplicate: duplicateJobs.includes(row.key),
47
+ });
@@ -0,0 +1,84 @@
1
+ import React, { useMemo } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { PACKAGE_FILTERS } from '@bundle-stats/utils';
4
+ import * as webpack from '@bundle-stats/utils/lib-esm/webpack/compare';
5
+
6
+ import { useRowsFilter } from '../../hooks/rows-filter';
7
+ import { useRowsSort } from '../../hooks/rows-sort';
8
+ import { useSearchParams } from '../../hooks/search-params';
9
+ import { SORT_BY } from './bundle-packages.constants';
10
+ import { BundlePackages as BundlePackagesComponent } from './bundle-packages';
11
+ import { getDuplicatePackages, getAddRowDuplicateFlag, getRowFilter, getCustomSort } from './bundle-packages.utils';
12
+
13
+ export const BundlePackages = (props) => {
14
+ const { jobs, search, filters, setState, sortBy, direction, ...restProps } = props;
15
+
16
+ const { defaultFilters, allEntriesFilters } = useMemo(
17
+ () => ({
18
+ defaultFilters: {
19
+ [PACKAGE_FILTERS.CHANGED]: jobs?.length > 1,
20
+ [PACKAGE_FILTERS.DUPLICATE]: false,
21
+ },
22
+ allEntriesFilters: {
23
+ [PACKAGE_FILTERS.CHANGED]: false,
24
+ [PACKAGE_FILTERS.DUPLICATE]: false,
25
+ },
26
+ }),
27
+ [jobs],
28
+ );
29
+
30
+ const searchParams = useSearchParams({
31
+ search,
32
+ filters,
33
+ defaultFilters,
34
+ allEntriesFilters,
35
+ setState,
36
+ });
37
+
38
+ const { rows, totalRowCount } = useMemo(() => {
39
+ const duplicateJobs = getDuplicatePackages(jobs);
40
+ const result = webpack.compareBySection.packages(jobs, [getAddRowDuplicateFlag(duplicateJobs)]);
41
+ return { rows: result, totalRowCount: result.length };
42
+ }, [jobs]);
43
+
44
+ const filteredRows = useRowsFilter({
45
+ rows,
46
+ searchPattern: searchParams.searchPattern,
47
+ filters: searchParams.filters,
48
+ getRowFilter,
49
+ });
50
+
51
+ const sortParams = useRowsSort({
52
+ rows: filteredRows,
53
+ sortFields: SORT_BY,
54
+ sortBy,
55
+ sortDirection: direction,
56
+ getCustomSort,
57
+ });
58
+
59
+ return (
60
+ <BundlePackagesComponent
61
+ jobs={jobs}
62
+ {...restProps}
63
+ {...searchParams}
64
+ {...sortParams}
65
+ totalRowCount={totalRowCount}
66
+ />
67
+ );
68
+ };
69
+
70
+ BundlePackages.propTypes = {
71
+ jobs: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types
72
+ filters: PropTypes.object, // eslint-disable-line react/forbid-prop-types
73
+ search: PropTypes.string,
74
+ sortBy: PropTypes.string,
75
+ direction: PropTypes.string,
76
+ setState: PropTypes.func.isRequired,
77
+ };
78
+
79
+ BundlePackages.defaultProps = {
80
+ filters: undefined,
81
+ search: undefined,
82
+ sortBy: undefined,
83
+ direction: undefined,
84
+ };
@@ -1,8 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cx from 'classnames';
4
- import { get, isEmpty } from 'lodash';
5
- import { flow, map, sum } from 'lodash/fp';
4
+ import { get, isEmpty, sum } from 'lodash';
6
5
  import { METRIC_TYPE_FILE_SIZE, getGlobalMetricType, getMetricRunInfo } from '@bundle-stats/utils';
7
6
 
8
7
  import { Table } from '../../ui/table';
@@ -13,11 +12,7 @@ import styles from './metrics-table.module.css';
13
12
 
14
13
  const METRIC_TYPE_DATA = getGlobalMetricType(null, METRIC_TYPE_FILE_SIZE);
15
14
 
16
- const getRowsRunTotal = (rows, runIndex) =>
17
- flow(
18
- map((row) => get(row, `runs[${runIndex}].value`, 0)),
19
- sum,
20
- )(rows);
15
+ const getRowsRunTotal = (rows, runIndex) => sum(rows.map((row) => get(row, `runs[${runIndex}].value`, 0)));
21
16
 
22
17
  const getHeaderLabelCells = (rows) => (run, runIndex, runs) => {
23
18
  const isBaseline = runIndex === runs.length - 1;
@@ -48,6 +48,7 @@ export const MetricsTableSearch = (props) => {
48
48
  onChange={handleChangeValue}
49
49
  value={value}
50
50
  size="small"
51
+ autoComplete="off"
51
52
  />
52
53
  {search && (
53
54
  <button className={css.cancelButton} type="button" onClick={handleClearValue}>
@@ -5,6 +5,7 @@
5
5
  }
6
6
 
7
7
  .input {
8
+ height: var(--space-large);
8
9
  border-color: var(--color-outline);
9
10
  }
10
11
 
@@ -26,7 +27,7 @@
26
27
  padding: var(--space-xxsmall);
27
28
  width: var(--space-large);
28
29
  height: var(--space-large);
29
- background: radial-gradient(var(--color-background), transparent 75%);
30
+ background: radial-gradient(var(--color-background) 50%, transparent);
30
31
  color: var(--color-text-ultra-light);
31
32
  transition: var(--transition-in);
32
33
  }
@@ -0,0 +1,16 @@
1
+ import { useMemo } from 'react';
2
+
3
+ export const useRowsFilter = ({ rows, searchPattern, filters, getRowFilter }) => {
4
+ const filteredRows = useMemo(() => {
5
+ const filterRow = getRowFilter(filters);
6
+ return rows.filter((row) => {
7
+ if (searchPattern && !searchPattern.test(row?.key)) {
8
+ return false;
9
+ }
10
+
11
+ return filterRow(row);
12
+ });
13
+ }, [rows, searchPattern, filters, getRowFilter]);
14
+
15
+ return filteredRows;
16
+ };
@@ -0,0 +1,24 @@
1
+ import { useState, useMemo } from 'react';
2
+ import { orderBy } from 'lodash';
3
+
4
+ export const useRowsSort = ({
5
+ rows,
6
+ sortFields,
7
+ sortBy = 'size',
8
+ sortDirection = 'desc',
9
+ getCustomSort,
10
+ }) => {
11
+ const [sort, updateSort] = useState({ field: sortBy, direction: sortDirection });
12
+
13
+ const orderedRows = useMemo(
14
+ () => orderBy(rows, getCustomSort(sort.field), sort.direction),
15
+ [rows, sort],
16
+ );
17
+
18
+ return {
19
+ sortFields,
20
+ sort,
21
+ updateSort,
22
+ items: orderedRows,
23
+ };
24
+ };
@@ -0,0 +1,153 @@
1
+ import { useCallback, useLayoutEffect, useReducer } from 'react';
2
+ import { isEqual, merge } from 'lodash';
3
+
4
+ const ACTION_SET_FILTERS = 'SET_FILTERS';
5
+ const ACTION_SET_SEARCH = 'SET_SEARCH';
6
+ const ACTION_RESET_DEFAULT = 'RESET_DEFAULT';
7
+ const ACTION_RESET_ALL = 'RESET_ALL';
8
+ const ACTION_SET = 'SET';
9
+
10
+ const getSearchPattern = (search) => {
11
+ let searchPattern = new RegExp(/.*/);
12
+
13
+ if (!search || !search.trim()) {
14
+ return searchPattern;
15
+ }
16
+
17
+ try {
18
+ searchPattern = new RegExp(search, 'i');
19
+ } catch (err) {
20
+ // noop
21
+ console.error(err); // eslint-disable-line no-console
22
+ }
23
+
24
+ return searchPattern;
25
+ };
26
+
27
+ const getSearchReducer =
28
+ ({ defaultFilters, allEntriesFilters, setParentState }) => (state, action) => {
29
+ const { type, payload } = action;
30
+
31
+ switch (type) {
32
+ case ACTION_SET_FILTERS: {
33
+ setParentState({
34
+ filters: payload,
35
+ search: state.search,
36
+ });
37
+
38
+ return {
39
+ ...state,
40
+ filters: payload,
41
+ };
42
+ }
43
+
44
+ case ACTION_SET_SEARCH: {
45
+ setParentState({
46
+ filters: state.filters,
47
+ search: payload,
48
+ });
49
+
50
+ return {
51
+ ...state,
52
+ search: payload,
53
+ searchPattern: getSearchPattern(payload),
54
+ };
55
+ }
56
+
57
+ case ACTION_RESET_DEFAULT: {
58
+ setParentState({
59
+ filters: defaultFilters,
60
+ search: '',
61
+ });
62
+
63
+ return {
64
+ filters: defaultFilters,
65
+ search: '',
66
+ searchPattern: getSearchPattern(),
67
+ };
68
+ }
69
+
70
+ case ACTION_RESET_ALL: {
71
+ setParentState({
72
+ filters: allEntriesFilters,
73
+ search: '',
74
+ });
75
+
76
+ return {
77
+ filters: allEntriesFilters,
78
+ search: '',
79
+ searchPattern: getSearchPattern(),
80
+ };
81
+ }
82
+
83
+ case ACTION_SET: {
84
+ return payload;
85
+ }
86
+
87
+ default:
88
+ return state;
89
+ }
90
+ };
91
+
92
+ export const generateState = (filters, search) => ({
93
+ filters,
94
+ search,
95
+ searchPattern: getSearchPattern(search),
96
+ });
97
+
98
+ export const useSearchParams = ({
99
+ search: parentSearch,
100
+
101
+ filters: parentFilters,
102
+ defaultFilters,
103
+ allEntriesFilters,
104
+ setState: setParentState,
105
+ }) => {
106
+ // When we pass custom filters, set the other flags to true (allEntries)
107
+ const initialFilters = parentFilters
108
+ ? merge({}, allEntriesFilters, parentFilters)
109
+ : defaultFilters;
110
+
111
+ const [{ search, searchPattern, filters }, dispatch] = useReducer(
112
+ getSearchReducer({ defaultFilters, allEntriesFilters, setParentState }),
113
+ generateState(initialFilters, parentSearch),
114
+ );
115
+
116
+ // Update state when the custom filters/search are changing - initial load or route updates
117
+ useLayoutEffect(() => {
118
+ // Run a deep comparison to prevent circular setState triggering
119
+ if (parentSearch === search || isEqual(parentFilters, filters)) {
120
+ return;
121
+ }
122
+
123
+ dispatch({ type: ACTION_SET, payload: generateState(initialFilters, parentSearch) });
124
+ }, [search, filters, parentFilters, parentSearch]);
125
+
126
+ /** Callbacks */
127
+ const handleUpdateSearch = useCallback((newValue) => {
128
+ dispatch({ type: ACTION_SET_SEARCH, payload: newValue });
129
+ }, []);
130
+
131
+ const handleUpdateFilters = useCallback((newFilters) => {
132
+ dispatch({ type: ACTION_SET_FILTERS, payload: newFilters });
133
+ }, []);
134
+
135
+ const handleResetFilters = useCallback(() => {
136
+ dispatch({ type: ACTION_RESET_DEFAULT });
137
+ }, []);
138
+
139
+ const handleResetAllFilters = useCallback(() => {
140
+ dispatch({ type: ACTION_RESET_ALL });
141
+ }, []);
142
+
143
+ return {
144
+ search,
145
+ searchPattern,
146
+ filters,
147
+ hasActiveFilters: !isEqual(allEntriesFilters, filters),
148
+ updateSearch: handleUpdateSearch,
149
+ updateFilters: handleUpdateFilters,
150
+ resetFilters: handleResetFilters,
151
+ resetAllFilters: handleResetAllFilters,
152
+ };
153
+ };
@@ -2,25 +2,10 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cx from 'classnames';
4
4
 
5
- import ArrowIcon from './assets/arrow.svg';
6
- import BranchIcon from './assets/branch.svg';
7
- import CancelIcon from './assets/cancel.svg';
8
- import ClockIcon from './assets/clock.svg';
9
- import CloseIcon from './assets/close.svg';
10
- import CommitIcon from './assets/commit.svg';
11
- import FilterIcon from './assets/filter.svg';
12
- import HelpIcon from './assets/help.svg';
13
- import MenuIcon from './assets/menu.svg';
14
- import PackageIcon from './assets/package.svg';
15
- import PullRequestIcon from './assets/pull-request.svg';
16
- import SortIcon from './assets/sort.svg';
17
- import WarningIcon from './assets/warning.svg';
18
-
19
5
  import css from './icon.module.css';
20
6
 
21
7
  const ICONS = {
22
8
  ARROW: 'arrow',
23
- BRANCH: 'branch',
24
9
  CANCEL: 'cancel',
25
10
  CLOSE: 'close',
26
11
  CLOCK: 'clock',
@@ -28,37 +13,17 @@ const ICONS = {
28
13
  FILTER: 'filter',
29
14
  HELP: 'help',
30
15
  MENU: 'menu',
31
- PACKAGE: 'package',
32
- PR: 'pr',
33
16
  SORT: 'sort',
34
17
  WARNING: 'warning',
35
18
  };
36
19
 
37
- const ICON_MAP = {
38
- [ICONS.ARROW]: ArrowIcon,
39
- [ICONS.BRANCH]: BranchIcon,
40
- [ICONS.CANCEL]: CancelIcon,
41
- [ICONS.CLOCK]: ClockIcon,
42
- [ICONS.CLOSE]: CloseIcon,
43
- [ICONS.COMMIT]: CommitIcon,
44
- [ICONS.FILTER]: FilterIcon,
45
- [ICONS.HELP]: HelpIcon,
46
- [ICONS.MENU]: MenuIcon,
47
- [ICONS.PACKAGE]: PackageIcon,
48
- [ICONS.PR]: PullRequestIcon,
49
- [ICONS.SORT]: SortIcon,
50
- [ICONS.WARNING]: WarningIcon,
51
- };
52
-
53
- export const Icon = ({ className, glyph, as: Component, size, ...restProps }) => {
54
- const Svg = typeof glyph === 'string' ? ICON_MAP[glyph] : glyph;
55
-
56
- return (
57
- <Component className={cx(css.root, className, css[size])} {...restProps}>
58
- <Svg className={css.icon} />
59
- </Component>
60
- );
61
- };
20
+ export const Icon = ({ className, glyph, as: Component, size, ...restProps }) => (
21
+ <Component className={cx(css.root, className, css[size])} {...restProps}>
22
+ <svg className={css.icon}>
23
+ <use xlinkHref={`#${glyph}`} />
24
+ </svg>
25
+ </Component>
26
+ );
62
27
 
63
28
  Icon.ICONS = ICONS;
64
29
 
@@ -7,36 +7,35 @@ import { Icon } from '../icon';
7
7
  import css from './sort-dropdown.module.css';
8
8
 
9
9
  export const SortDropdown = (props) => {
10
- const { className, label, items, sortBy, direction, onChange } = props;
10
+ const { className, label, fields, field, direction, onChange } = props;
11
11
  const rootClassName = cx(css.root, className);
12
12
 
13
- const customLabel = items[sortBy] ? `Ordered by ${items[sortBy].label}` : label;
13
+ const customLabel = fields[field] ? `Ordered by ${fields[field].label}` : label;
14
14
 
15
15
  return (
16
16
  <Dropdown className={rootClassName} label={customLabel} glyph="sort" align="right">
17
17
  {({ MenuItem, menu, menuItemClassName, menuItemActiveClassName }) => {
18
- const getButtonOnClick = (newSortBy, newDirection) => () => {
19
- onChange({ sortBy: newSortBy, direction: newDirection });
18
+ const getButtonOnClick = (newField, newDirection) => () => {
19
+ onChange({ field: newField, direction: newDirection });
20
20
  menu.toggle();
21
21
  };
22
22
 
23
23
  return (
24
24
  <div className={css.items}>
25
- {Object.entries(items).map(([key, item]) => {
25
+ {Object.entries(fields).map(([key, item]) => {
26
26
  const buttonProps =
27
27
  direction === 'asc'
28
28
  ? {
29
- className: cx(css.itemButton, css.itemAsc),
29
+ className: css.itemAsc,
30
30
  onClick: getButtonOnClick(key, 'desc'),
31
31
  title: `Order data by ${item.label} descending`,
32
32
  }
33
33
  : {
34
- className: css.itemButton,
35
34
  onClick: getButtonOnClick(key, 'asc'),
36
35
  title: `Order data by ${item.label} ascending`,
37
36
  };
38
37
 
39
- const isActive = sortBy === key;
38
+ const isActive = field === key;
40
39
 
41
40
  return (
42
41
  <MenuItem
@@ -44,6 +43,7 @@ export const SortDropdown = (props) => {
44
43
  {...menu}
45
44
  {...buttonProps}
46
45
  className={cx(
46
+ buttonProps.className,
47
47
  menuItemClassName,
48
48
  css.item,
49
49
  isActive && menuItemActiveClassName,
@@ -66,17 +66,17 @@ SortDropdown.defaultProps = {
66
66
  className: '',
67
67
  label: 'Order by',
68
68
  onChange: () => {},
69
- sortBy: '',
69
+ field: '',
70
70
  direction: 'asc',
71
71
  };
72
72
 
73
73
  SortDropdown.propTypes = {
74
74
  className: PropTypes.string,
75
75
  label: PropTypes.string,
76
- items: PropTypes.shape({
76
+ fields: PropTypes.shape({
77
77
  [PropTypes.string]: PropTypes.string,
78
78
  }).isRequired,
79
79
  onChange: PropTypes.func,
80
- sortBy: PropTypes.string,
80
+ field: PropTypes.string,
81
81
  direction: PropTypes.string,
82
82
  };
package/src/utils.jsx CHANGED
@@ -25,12 +25,14 @@ export const getRenderChildWithClassName = (className) => (child) => {
25
25
  return null;
26
26
  }
27
27
 
28
- if (!child?.props) {
28
+ const { props } = child;
29
+
30
+ if (!props) {
29
31
  return <div className={className}>{child}</div>;
30
32
  }
31
33
 
32
34
  return React.cloneElement(child, {
33
- ...child.props,
34
- className: cx(className, child.props.className),
35
+ ...props,
36
+ className: cx(className, props.className), // eslint-disable-line react/prop-types
35
37
  });
36
38
  };