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

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 (266) hide show
  1. package/.babelrc.js +3 -3
  2. package/CHANGELOG.md +11 -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/browsertime-table/browsertime-table.stories.js +1 -0
  15. package/lib/components/bundle-assets/bundle-assets.js +3 -3
  16. package/lib/components/bundle-assets/bundle-assets.stories.js +19 -7
  17. package/lib/components/bundle-assets/bundle-assets.utils.js +95 -28
  18. package/lib/components/bundle-assets/index.js +115 -4
  19. package/lib/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js +17 -18
  20. package/lib/components/bundle-modules/bundle-modules.js +3 -3
  21. package/lib/components/bundle-modules/bundle-modules.utils.js +152 -0
  22. package/lib/components/bundle-modules/index.js +135 -4
  23. package/lib/components/bundle-packages/bundle-packages.js +3 -3
  24. package/lib/components/bundle-packages/{bundle-packages.container.js → bundle-packages.utils.js} +8 -42
  25. package/lib/components/bundle-packages/index.js +111 -3
  26. package/lib/components/job-meta/job-meta.js +1 -1
  27. package/lib/components/metrics-table/metrics-table.js +3 -7
  28. package/lib/components/metrics-table-search/metrics-table-search.js +2 -1
  29. package/lib/components/metrics-table-search/metrics-table-search.module.css +2 -1
  30. package/lib/components/summary/summary.js +52 -34
  31. package/lib/hooks/rows-filter.js +28 -0
  32. package/lib/hooks/rows-sort.js +54 -0
  33. package/lib/{hocs/with-search.js → hooks/search-params.js} +16 -65
  34. package/lib/layout/box/box.js +2 -2
  35. package/lib/layout/stack/stack.js +13 -3
  36. package/lib/ui/icon/icon.js +4 -191
  37. package/lib/ui/sort-dropdown/sort-dropdown.js +12 -13
  38. package/lib/ui/sort-dropdown/sort-dropdown.stories.js +2 -2
  39. package/lib/utils.js +6 -3
  40. package/lib-esm/app/app.js +60 -70
  41. package/lib-esm/app/app.stories.js +50 -71
  42. package/lib-esm/app/header/header.js +5 -3
  43. package/lib-esm/assets/icons/arrow.svg +1 -0
  44. package/lib-esm/assets/icons/cancel.svg +1 -0
  45. package/lib-esm/assets/icons/clock.svg +1 -0
  46. package/lib-esm/assets/icons/close.svg +1 -0
  47. package/lib-esm/assets/icons/commit.svg +1 -0
  48. package/lib-esm/assets/icons/filter.svg +1 -0
  49. package/lib-esm/assets/icons/help.svg +1 -0
  50. package/lib-esm/assets/icons/menu.svg +1 -0
  51. package/lib-esm/{ui/icon/assets → assets/icons}/sort.svg +0 -0
  52. package/lib-esm/assets/icons/warning.svg +1 -0
  53. package/lib-esm/assets/icons.svg +1 -0
  54. package/lib-esm/components/asset-info/asset-info.js +21 -17
  55. package/lib-esm/components/asset-info/asset-info.stories.js +29 -33
  56. package/lib-esm/components/browsertime-table/browsertime-table.js +6 -4
  57. package/lib-esm/components/browsertime-table/browsertime-table.stories.js +11 -14
  58. package/lib-esm/components/bundle-assets/bundle-assets.constants.js +17 -15
  59. package/lib-esm/components/bundle-assets/bundle-assets.js +107 -97
  60. package/lib-esm/components/bundle-assets/bundle-assets.stories.js +58 -63
  61. package/lib-esm/components/bundle-assets/bundle-assets.utils.js +65 -21
  62. package/lib-esm/components/bundle-assets/index.js +89 -2
  63. package/lib-esm/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.js +51 -53
  64. package/lib-esm/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.stories.js +9 -13
  65. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +14 -13
  66. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.stories.js +13 -19
  67. package/lib-esm/components/bundle-modules/bundle-modules.constants.js +17 -15
  68. package/lib-esm/components/bundle-modules/bundle-modules.js +112 -110
  69. package/lib-esm/components/bundle-modules/bundle-modules.stories.js +46 -58
  70. package/lib-esm/components/bundle-modules/bundle-modules.utils.js +98 -0
  71. package/lib-esm/components/bundle-modules/index.js +114 -3
  72. package/lib-esm/components/bundle-packages/bundle-packages.constants.js +17 -15
  73. package/lib-esm/components/bundle-packages/bundle-packages.js +71 -69
  74. package/lib-esm/components/bundle-packages/bundle-packages.stories.js +34 -46
  75. package/lib-esm/components/bundle-packages/bundle-packages.utils.js +44 -0
  76. package/lib-esm/components/bundle-packages/index.js +87 -2
  77. package/lib-esm/components/component-link.js +10 -13
  78. package/lib-esm/components/delta/delta.js +8 -6
  79. package/lib-esm/components/delta/delta.stories.js +26 -38
  80. package/lib-esm/components/duplicate-packages-warning/duplicate-packages-warning.js +11 -9
  81. package/lib-esm/components/duplicate-packages-warning/duplicate-packages-warning.stories.js +20 -26
  82. package/lib-esm/components/job-header/job-header.js +14 -11
  83. package/lib-esm/components/job-header/job-header.stories.js +12 -18
  84. package/lib-esm/components/job-meta/job-meta.js +15 -18
  85. package/lib-esm/components/job-meta/job-meta.stories.js +10 -12
  86. package/lib-esm/components/job-name/job-name.js +9 -12
  87. package/lib-esm/components/job-name/job-name.stories.js +15 -23
  88. package/lib-esm/components/jobs-header/jobs-header.js +12 -12
  89. package/lib-esm/components/jobs-header/jobs-header.stories.js +80 -86
  90. package/lib-esm/components/lighthouse-table/lighthouse-table.js +6 -4
  91. package/lib-esm/components/lighthouse-table/lighthouse-table.stories.js +10 -14
  92. package/lib-esm/components/metric/metric.js +15 -19
  93. package/lib-esm/components/metric/metric.stories.js +36 -58
  94. package/lib-esm/components/metrics-table/metrics-table.js +128 -139
  95. package/lib-esm/components/metrics-table/metrics-table.stories.js +27 -35
  96. package/lib-esm/components/metrics-table-options/metrics-table-options.js +16 -19
  97. package/lib-esm/components/metrics-table-options/metrics-table-options.stories.js +6 -12
  98. package/lib-esm/components/metrics-table-search/metrics-table-search.js +17 -18
  99. package/lib-esm/components/metrics-table-search/metrics-table-search.module.css +2 -1
  100. package/lib-esm/components/metrics-table-search/metrics-table-search.stories.js +4 -9
  101. package/lib-esm/components/metrics-table-title/metrics-table-title.js +9 -7
  102. package/lib-esm/components/module-info/module-info.js +15 -13
  103. package/lib-esm/components/module-info/module-info.stories.js +20 -22
  104. package/lib-esm/components/summary/summary.js +52 -36
  105. package/lib-esm/components/summary/summary.stories.js +31 -46
  106. package/lib-esm/components/summary-item/summary-item.js +27 -28
  107. package/lib-esm/components/summary-item/summary-item.stories.js +40 -50
  108. package/lib-esm/components/total-size-type-title/total-size-type-title.js +4 -2
  109. package/lib-esm/constants.js +25 -30
  110. package/lib-esm/hooks/rows-filter.js +20 -0
  111. package/lib-esm/hooks/rows-sort.js +22 -0
  112. package/lib-esm/hooks/search-params.js +170 -0
  113. package/lib-esm/layout/box/box.js +14 -21
  114. package/lib-esm/layout/box/box.stories.js +34 -46
  115. package/lib-esm/layout/flex-stack/flex-stack.js +9 -12
  116. package/lib-esm/layout/flex-stack/flex-stack.stories.js +39 -49
  117. package/lib-esm/layout/footer/footer.js +11 -11
  118. package/lib-esm/layout/footer/footer.stories.js +7 -11
  119. package/lib-esm/layout/header/header.js +10 -14
  120. package/lib-esm/layout/header/header.stories.js +50 -44
  121. package/lib-esm/layout/stack/stack.js +13 -8
  122. package/lib-esm/layout/stack/stack.stories.js +39 -49
  123. package/lib-esm/layout/sub-header/sub-header.js +14 -12
  124. package/lib-esm/layout/sub-header/sub-header.stories.js +57 -69
  125. package/lib-esm/prototypes/typography.stories.js +49 -58
  126. package/lib-esm/query-state.js +13 -16
  127. package/lib-esm/stories/wrapper.js +6 -12
  128. package/lib-esm/ui/alert/alert.js +6 -4
  129. package/lib-esm/ui/alert/alert.stories.js +14 -24
  130. package/lib-esm/ui/container/container.js +6 -9
  131. package/lib-esm/ui/container/container.stories.js +15 -17
  132. package/lib-esm/ui/dropdown/dropdown.js +13 -11
  133. package/lib-esm/ui/dropdown/dropdown.stories.js +22 -26
  134. package/lib-esm/ui/empty-set/empty-set.js +12 -12
  135. package/lib-esm/ui/empty-set/empty-set.stories.js +11 -19
  136. package/lib-esm/ui/file-name/file-name.js +6 -4
  137. package/lib-esm/ui/file-name/file-name.stories.js +12 -16
  138. package/lib-esm/ui/filters/filters.container.js +57 -76
  139. package/lib-esm/ui/filters/filters.js +71 -77
  140. package/lib-esm/ui/filters/filters.stories.js +82 -100
  141. package/lib-esm/ui/filters/filters.utils.js +26 -24
  142. package/lib-esm/ui/filters/index.js +1 -1
  143. package/lib-esm/ui/horizontal-bar-chart/horizontal-bar-chart.js +68 -78
  144. package/lib-esm/ui/horizontal-bar-chart/horizontal-bar-chart.stories.js +67 -77
  145. package/lib-esm/ui/icon/icon.js +13 -202
  146. package/lib-esm/ui/icon/icon.stories.js +16 -24
  147. package/lib-esm/ui/input/input.js +7 -10
  148. package/lib-esm/ui/input/input.stories.js +8 -12
  149. package/lib-esm/ui/loader/loader.js +6 -9
  150. package/lib-esm/ui/loader/loader.stories.js +5 -9
  151. package/lib-esm/ui/logo/logo.constants.js +6 -6
  152. package/lib-esm/ui/logo/logo.js +15 -15
  153. package/lib-esm/ui/logo/logo.stories.js +14 -22
  154. package/lib-esm/ui/panels/panels.js +13 -23
  155. package/lib-esm/ui/panels/panels.stories.js +14 -16
  156. package/lib-esm/ui/popover/popover.js +11 -9
  157. package/lib-esm/ui/popover/popover.stories.js +7 -11
  158. package/lib-esm/ui/skeleton/skeleton.js +6 -9
  159. package/lib-esm/ui/skeleton/skeleton.stories.js +2 -4
  160. package/lib-esm/ui/sort-dropdown/sort-dropdown.js +38 -38
  161. package/lib-esm/ui/sort-dropdown/sort-dropdown.stories.js +14 -16
  162. package/lib-esm/ui/table/table.js +26 -31
  163. package/lib-esm/ui/table/table.stories.js +84 -102
  164. package/lib-esm/ui/tabs/tabs.js +13 -21
  165. package/lib-esm/ui/tabs/tabs.stories.js +4 -6
  166. package/lib-esm/ui/toolbar/toolbar.js +7 -5
  167. package/lib-esm/ui/toolbar/toolbar.stories.js +14 -16
  168. package/lib-esm/ui/tooltip/tooltip.js +13 -16
  169. package/lib-esm/ui/tooltip/tooltip.stories.js +8 -12
  170. package/lib-esm/utils.js +20 -24
  171. package/package.json +30 -33
  172. package/src/assets/icons/arrow.svg +1 -0
  173. package/src/assets/icons/cancel.svg +1 -0
  174. package/src/assets/icons/clock.svg +1 -0
  175. package/src/assets/icons/close.svg +1 -0
  176. package/src/assets/icons/commit.svg +1 -0
  177. package/src/assets/icons/filter.svg +1 -0
  178. package/src/assets/icons/help.svg +1 -0
  179. package/src/assets/icons/menu.svg +1 -0
  180. package/src/{ui/icon/assets → assets/icons}/sort.svg +0 -0
  181. package/src/assets/icons/warning.svg +1 -0
  182. package/src/assets/icons.svg +1 -0
  183. package/src/components/bundle-assets/bundle-assets.jsx +3 -3
  184. package/src/components/bundle-assets/bundle-assets.utils.js +96 -17
  185. package/src/components/bundle-assets/index.jsx +89 -0
  186. package/src/components/bundle-assets-totals-chart-bars/bundle-assets-totals-chart-bars.jsx +15 -15
  187. package/src/components/bundle-modules/bundle-modules.jsx +3 -3
  188. package/src/components/bundle-modules/bundle-modules.utils.js +101 -0
  189. package/src/components/bundle-modules/index.jsx +107 -0
  190. package/src/components/bundle-packages/bundle-packages.jsx +3 -3
  191. package/src/components/bundle-packages/bundle-packages.utils.js +47 -0
  192. package/src/components/bundle-packages/index.jsx +84 -0
  193. package/src/components/job-meta/job-meta.jsx +1 -3
  194. package/src/components/metrics-table/metrics-table.jsx +2 -7
  195. package/src/components/metrics-table-search/metrics-table-search.jsx +1 -0
  196. package/src/components/metrics-table-search/metrics-table-search.module.css +2 -1
  197. package/src/components/summary/summary.jsx +26 -10
  198. package/src/hooks/rows-filter.js +16 -0
  199. package/src/hooks/rows-sort.js +24 -0
  200. package/src/hooks/search-params.js +153 -0
  201. package/src/layout/box/box.jsx +1 -1
  202. package/src/layout/stack/stack.jsx +2 -2
  203. package/src/ui/icon/icon.jsx +7 -42
  204. package/src/ui/sort-dropdown/sort-dropdown.jsx +11 -11
  205. package/src/utils.jsx +5 -3
  206. package/lib/components/bundle-assets/bundle-assets.container.js +0 -140
  207. package/lib/components/bundle-modules/bundle-modules.container.js +0 -222
  208. package/lib/hocs/with-custom-sort.js +0 -75
  209. package/lib/hocs/with-filtered-items.js +0 -54
  210. package/lib/ui/icon/assets/arrow.svg +0 -4
  211. package/lib/ui/icon/assets/branch.svg +0 -3
  212. package/lib/ui/icon/assets/cancel.svg +0 -4
  213. package/lib/ui/icon/assets/clock.svg +0 -3
  214. package/lib/ui/icon/assets/close.svg +0 -4
  215. package/lib/ui/icon/assets/commit.svg +0 -3
  216. package/lib/ui/icon/assets/filter.svg +0 -4
  217. package/lib/ui/icon/assets/help.svg +0 -4
  218. package/lib/ui/icon/assets/job.svg +0 -3
  219. package/lib/ui/icon/assets/menu.svg +0 -4
  220. package/lib/ui/icon/assets/package.svg +0 -3
  221. package/lib/ui/icon/assets/pull-request.svg +0 -3
  222. package/lib/ui/icon/assets/repository.svg +0 -3
  223. package/lib/ui/icon/assets/warning.svg +0 -4
  224. package/lib-esm/components/bundle-assets/bundle-assets.container.js +0 -119
  225. package/lib-esm/components/bundle-modules/bundle-modules.container.js +0 -175
  226. package/lib-esm/components/bundle-packages/bundle-packages.container.js +0 -91
  227. package/lib-esm/hocs/with-custom-sort.js +0 -43
  228. package/lib-esm/hocs/with-filtered-items.js +0 -35
  229. package/lib-esm/hocs/with-search.js +0 -214
  230. package/lib-esm/ui/icon/assets/arrow.svg +0 -4
  231. package/lib-esm/ui/icon/assets/branch.svg +0 -3
  232. package/lib-esm/ui/icon/assets/cancel.svg +0 -4
  233. package/lib-esm/ui/icon/assets/clock.svg +0 -3
  234. package/lib-esm/ui/icon/assets/close.svg +0 -4
  235. package/lib-esm/ui/icon/assets/commit.svg +0 -3
  236. package/lib-esm/ui/icon/assets/filter.svg +0 -4
  237. package/lib-esm/ui/icon/assets/help.svg +0 -4
  238. package/lib-esm/ui/icon/assets/job.svg +0 -3
  239. package/lib-esm/ui/icon/assets/menu.svg +0 -4
  240. package/lib-esm/ui/icon/assets/package.svg +0 -3
  241. package/lib-esm/ui/icon/assets/pull-request.svg +0 -3
  242. package/lib-esm/ui/icon/assets/repository.svg +0 -3
  243. package/lib-esm/ui/icon/assets/warning.svg +0 -4
  244. package/src/components/bundle-assets/bundle-assets.container.js +0 -146
  245. package/src/components/bundle-assets/index.js +0 -4
  246. package/src/components/bundle-modules/bundle-modules.container.js +0 -158
  247. package/src/components/bundle-modules/index.js +0 -4
  248. package/src/components/bundle-packages/bundle-packages.container.js +0 -93
  249. package/src/components/bundle-packages/index.js +0 -4
  250. package/src/hocs/with-custom-sort.jsx +0 -35
  251. package/src/hocs/with-filtered-items.jsx +0 -34
  252. package/src/hocs/with-search.jsx +0 -184
  253. package/src/ui/icon/assets/arrow.svg +0 -4
  254. package/src/ui/icon/assets/branch.svg +0 -3
  255. package/src/ui/icon/assets/cancel.svg +0 -4
  256. package/src/ui/icon/assets/clock.svg +0 -3
  257. package/src/ui/icon/assets/close.svg +0 -4
  258. package/src/ui/icon/assets/commit.svg +0 -3
  259. package/src/ui/icon/assets/filter.svg +0 -4
  260. package/src/ui/icon/assets/help.svg +0 -4
  261. package/src/ui/icon/assets/job.svg +0 -3
  262. package/src/ui/icon/assets/menu.svg +0 -4
  263. package/src/ui/icon/assets/package.svg +0 -3
  264. package/src/ui/icon/assets/pull-request.svg +0 -3
  265. package/src/ui/icon/assets/repository.svg +0 -3
  266. package/src/ui/icon/assets/warning.svg +0 -4
@@ -0,0 +1,101 @@
1
+ import { useMemo } from 'react';
2
+ import { get, intersection, merge } from 'lodash';
3
+ import { MODULE_PATH_PACKAGES } from '@bundle-stats/utils/lib-esm/webpack';
4
+ import {
5
+ MODULE_CHUNK,
6
+ MODULE_FILTERS,
7
+ MODULE_FILE_TYPE,
8
+ MODULE_SOURCE_TYPE,
9
+ getModuleSourceFileType,
10
+ } from '@bundle-stats/utils';
11
+
12
+ import { SORT_BY_NAME, SORT_BY_SIZE, SORT_BY_DELTA } from './bundle-modules.constants';
13
+
14
+ export const addRowSourceFlag = (row) => {
15
+ const { key } = row;
16
+ const thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES));
17
+ return { ...row, thirdParty };
18
+ };
19
+
20
+ export const getCustomSort = (sortBy) => (item) => {
21
+ if (sortBy === SORT_BY_NAME) {
22
+ return item.key;
23
+ }
24
+
25
+ if (sortBy === SORT_BY_SIZE) {
26
+ return get(item, 'runs[0].value', 0);
27
+ }
28
+
29
+ if (sortBy === SORT_BY_DELTA) {
30
+ return get(item, 'runs[0].deltaPercentage', 0);
31
+ }
32
+
33
+ return [!item.changed, item.key];
34
+ };
35
+
36
+ export const getRowFilter = (filters) => (row) => {
37
+ // Skip not changed rows
38
+ if (filters[MODULE_FILTERS.CHANGED] && !row.changed) {
39
+ return false;
40
+ }
41
+
42
+ // Skip not matching source type
43
+ if (
44
+ !(
45
+ (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`] && row.thirdParty === false) ||
46
+ (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`] && row.thirdParty === true)
47
+ )
48
+ ) {
49
+ return false;
50
+ }
51
+
52
+ // Skip not matching source file types
53
+ const fileType = getModuleSourceFileType(row.key);
54
+ if (!filters[`${MODULE_FILE_TYPE}.${fileType}`]) {
55
+ return false;
56
+ }
57
+
58
+ return true;
59
+ };
60
+
61
+ export const useModuleFilterByChunk = ({ jobs, filters, chunkIds }) => {
62
+ const filteredJobs = useMemo(() => {
63
+ // List of chunkIds with filter value set to `true`
64
+ const includedChunkIds = chunkIds.reduce((agg, chunkId) => {
65
+ if (get(filters, `${MODULE_CHUNK}.${chunkId}`)) {
66
+ return [...agg, chunkId];
67
+ }
68
+
69
+ return agg;
70
+ }, []);
71
+
72
+ // If all the filters are included, return jobs as they are
73
+ if (includedChunkIds.length === chunkIds.length) {
74
+ return jobs;
75
+ }
76
+
77
+ const jobsWithFilteredData = jobs.map((job) => {
78
+ const { modules } = job?.metrics?.webpack || {};
79
+
80
+ const filteredModules = Object.entries(modules).reduce((agg, [moduleId, moduleEntry]) => {
81
+ const match = intersection(moduleEntry.chunkIds, includedChunkIds);
82
+
83
+ if (match.length > 0) {
84
+ agg[moduleId] = moduleEntry; // eslint-disable-line no-param-reassign
85
+ }
86
+
87
+ return agg;
88
+ }, {});
89
+
90
+ // Copy job data into a new object to prevent mutations of the original data
91
+ const newJob = merge({}, job);
92
+ newJob.metrics.webpack.modules = filteredModules;
93
+
94
+ return newJob;
95
+ });
96
+
97
+ return jobsWithFilteredData;
98
+ }, [jobs, filters, chunkIds]);
99
+
100
+ return filteredJobs;
101
+ };
@@ -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
+ };
@@ -19,9 +19,7 @@ export const JobMeta = (props) => {
19
19
  return (
20
20
  <div className={css.item} key={key}>
21
21
  {title && (<h2 className={css.itemTitle}>{title}</h2>)}
22
- {content.map((item) => (
23
- <>{item}</>
24
- ))}
22
+ {content.map((item) => item)}
25
23
  </div>
26
24
  );
27
25
  })}
@@ -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
  }
@@ -11,6 +11,28 @@ import { ComponentLink } from '../component-link';
11
11
  import { SummaryItem } from '../summary-item';
12
12
  import css from './summary.module.css';
13
13
 
14
+ const CustomSummaryItem = ({ className, metricOptions, SummaryItemCustomLink, ...props }) => (
15
+ <Box
16
+ padding={['xsmall', 'small']}
17
+ className={cx(className, css.summaryItemLink)}
18
+ {...props}
19
+ as={SummaryItemCustomLink}
20
+ {...metricOptions.link}
21
+ />
22
+ );
23
+
24
+ CustomSummaryItem.propTypes = {
25
+ className: PropTypes.string,
26
+ metricOptions: PropTypes.shape({
27
+ link: PropTypes.any, // eslint-disable-line react/forbid-prop-types
28
+ }).isRequired,
29
+ SummaryItemCustomLink: PropTypes.elementType.isRequired,
30
+ };
31
+
32
+ CustomSummaryItem.defaultProps = {
33
+ className: '',
34
+ };
35
+
14
36
  export const Summary = ({
15
37
  className,
16
38
  size,
@@ -25,15 +47,10 @@ export const Summary = ({
25
47
  {Array.from(METRIC_COMPONENT_LINKS).filter(([metricId]) => keys.includes(metricId)).map(
26
48
  ([metricId, metricOptions]) => (
27
49
  <SummaryItem
28
- as={({ className: itemClassName, ...itemProps }) => (
29
- <Box
30
- padding={['xsmall', 'small']}
31
- className={cx(itemClassName, css.summaryItemLink)}
32
- {...itemProps}
33
- as={SummaryItemCustomLink}
34
- {...metricOptions.link}
35
- />
36
- )}
50
+ as={CustomSummaryItem}
51
+ className={css.item}
52
+ metricOptions={metricOptions}
53
+ SummaryItemCustomLink={SummaryItemCustomLink}
37
54
  size={size}
38
55
  key={metricId}
39
56
  id={metricId}
@@ -41,7 +58,6 @@ export const Summary = ({
41
58
  loading={loading}
42
59
  showMetricDescription
43
60
  showDelta={showSummaryItemDelta && metricOptions.showDelta !== false}
44
- className={css.item}
45
61
  />
46
62
  )
47
63
  )}
@@ -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 = /.*/;
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
+ };
@@ -31,7 +31,7 @@ export const Box = (props) => {
31
31
  outline && css.outline,
32
32
  );
33
33
 
34
- return <Component className={rootClassName} {...restProps} />;
34
+ return <Component {...restProps} className={rootClassName} />;
35
35
  };
36
36
 
37
37
  Box.defaultProps = {