@bundle-stats/ui 3.3.0-alpha.1 → 3.4.0-alpha.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 (190) hide show
  1. package/CHANGELOG.md +65 -13
  2. package/lib/app/app.js +137 -102
  3. package/lib/app/app.module.css +11 -4
  4. package/lib/app/header/header.js +1 -10
  5. package/lib/components/asset-info/asset-info.module.css +1 -0
  6. package/lib/components/bundle-assets/bundle-assets.js +104 -52
  7. package/lib/components/bundle-assets/bundle-assets.module.css +24 -26
  8. package/lib/components/bundle-assets-totals-table/bundle-assets-totals-table.js +13 -3
  9. package/lib/components/bundle-modules/bundle-modules.js +68 -68
  10. package/lib/components/bundle-modules/bundle-modules.module.css +11 -2
  11. package/lib/components/bundle-modules/bundle-modules.utils.js +14 -10
  12. package/lib/components/bundle-modules/index.js +4 -4
  13. package/lib/components/bundle-packages/bundle-packages.js +100 -44
  14. package/lib/components/bundle-packages/bundle-packages.module.css +17 -21
  15. package/lib/components/delta/delta.module.css +1 -0
  16. package/lib/components/job-header/job-header.js +3 -1
  17. package/lib/components/job-header/job-header.module.css +6 -8
  18. package/lib/components/jobs-header/jobs-header.module.css +5 -0
  19. package/lib/components/metrics-table/metrics-table.js +72 -59
  20. package/lib/components/metrics-table/metrics-table.module.css +18 -7
  21. package/lib/components/metrics-table-search/metrics-table-search.module.css +5 -4
  22. package/lib/components/metrics-table-title/metrics-table-title.js +9 -7
  23. package/lib/components/metrics-table-title/metrics-table-title.module.css +2 -0
  24. package/lib/components/module-info/module-info.js +2 -2
  25. package/lib/components/module-info/module-info.module.css +1 -0
  26. package/lib/components/summary/summary.js +1 -1
  27. package/lib/components/summary-item/summary-item.js +15 -18
  28. package/lib/components/summary-item/summary-item.module.css +1 -23
  29. package/lib/components/total-size-type-title/total-size-type-title.js +7 -3
  30. package/lib/constants.js +1 -1
  31. package/lib/css/default.css +14 -3
  32. package/lib/css/variables.css +17 -17
  33. package/lib/hooks/search-params.js +31 -31
  34. package/lib/layout/box/box.module.css +9 -0
  35. package/lib/layout/footer/footer.module.css +1 -1
  36. package/lib/layout/index.js +8 -0
  37. package/lib/layout/separator.js +48 -0
  38. package/lib/layout/separator.module.css +5 -0
  39. package/lib/ui/dropdown/dropdown.js +21 -26
  40. package/lib/ui/dropdown/dropdown.module.css +11 -12
  41. package/lib/ui/dropdown/dropdown.stories.js +2 -9
  42. package/lib/ui/empty-set/empty-set.js +5 -4
  43. package/lib/ui/empty-set/empty-set.module.css +2 -2
  44. package/lib/ui/filters/filters.container.js +1 -1
  45. package/lib/ui/filters/filters.js +22 -7
  46. package/lib/ui/filters/filters.module.css +3 -3
  47. package/lib/ui/horizontal-bar-chart/horizontal-bar-chart.js +1 -1
  48. package/lib/ui/hover-card/hover-card.js +102 -0
  49. package/lib/ui/hover-card/hover-card.module.css +29 -0
  50. package/lib/ui/hover-card/hover-card.stories.js +56 -0
  51. package/lib/ui/hover-card/index.js +18 -0
  52. package/lib/ui/icon/icon.js +6 -6
  53. package/lib/ui/icon/icon.module.css +1 -0
  54. package/lib/ui/index.js +8 -8
  55. package/lib/ui/table/table.js +5 -74
  56. package/lib/ui/table/table.module.css +41 -25
  57. package/lib/ui/table/table.stories.js +1 -1
  58. package/lib/ui/{popover → tag}/index.js +4 -4
  59. package/lib/ui/tag/tag.js +72 -0
  60. package/lib/ui/tag/tag.module.css +55 -0
  61. package/lib/ui/tag/tag.stories.js +38 -0
  62. package/lib/ui/tooltip/tooltip.js +10 -9
  63. package/lib/ui/tooltip/tooltip.module.css +2 -1
  64. package/lib-esm/app/app.js +132 -102
  65. package/lib-esm/app/app.module.css +11 -4
  66. package/lib-esm/app/header/header.js +1 -9
  67. package/lib-esm/components/asset-info/asset-info.module.css +1 -0
  68. package/lib-esm/components/bundle-assets/bundle-assets.js +97 -56
  69. package/lib-esm/components/bundle-assets/bundle-assets.module.css +24 -26
  70. package/lib-esm/components/bundle-assets-totals-table/bundle-assets-totals-table.js +7 -3
  71. package/lib-esm/components/bundle-modules/bundle-modules.js +50 -51
  72. package/lib-esm/components/bundle-modules/bundle-modules.module.css +11 -2
  73. package/lib-esm/components/bundle-modules/bundle-modules.utils.js +14 -6
  74. package/lib-esm/components/bundle-modules/index.js +5 -3
  75. package/lib-esm/components/bundle-packages/bundle-packages.js +88 -41
  76. package/lib-esm/components/bundle-packages/bundle-packages.module.css +17 -21
  77. package/lib-esm/components/delta/delta.module.css +1 -0
  78. package/lib-esm/components/job-header/job-header.js +2 -1
  79. package/lib-esm/components/job-header/job-header.module.css +6 -8
  80. package/lib-esm/components/jobs-header/jobs-header.module.css +5 -0
  81. package/lib-esm/components/metrics-table/metrics-table.js +61 -53
  82. package/lib-esm/components/metrics-table/metrics-table.module.css +18 -7
  83. package/lib-esm/components/metrics-table-search/metrics-table-search.module.css +5 -4
  84. package/lib-esm/components/metrics-table-title/metrics-table-title.js +8 -7
  85. package/lib-esm/components/metrics-table-title/metrics-table-title.module.css +2 -0
  86. package/lib-esm/components/module-info/module-info.js +2 -2
  87. package/lib-esm/components/module-info/module-info.module.css +1 -0
  88. package/lib-esm/components/summary/summary.js +1 -1
  89. package/lib-esm/components/summary-item/summary-item.js +14 -18
  90. package/lib-esm/components/summary-item/summary-item.module.css +1 -23
  91. package/lib-esm/components/total-size-type-title/total-size-type-title.js +6 -3
  92. package/lib-esm/constants.js +1 -1
  93. package/lib-esm/css/default.css +14 -3
  94. package/lib-esm/css/variables.css +17 -17
  95. package/lib-esm/hooks/search-params.js +27 -28
  96. package/lib-esm/layout/box/box.module.css +9 -0
  97. package/lib-esm/layout/footer/footer.module.css +1 -1
  98. package/lib-esm/layout/index.js +1 -0
  99. package/lib-esm/layout/separator.js +28 -0
  100. package/lib-esm/layout/separator.module.css +5 -0
  101. package/lib-esm/ui/dropdown/dropdown.js +20 -25
  102. package/lib-esm/ui/dropdown/dropdown.module.css +11 -12
  103. package/lib-esm/ui/dropdown/dropdown.stories.js +2 -7
  104. package/lib-esm/ui/empty-set/empty-set.js +4 -4
  105. package/lib-esm/ui/empty-set/empty-set.module.css +2 -2
  106. package/lib-esm/ui/filters/filters.js +20 -5
  107. package/lib-esm/ui/filters/filters.module.css +3 -3
  108. package/lib-esm/ui/hover-card/hover-card.js +83 -0
  109. package/lib-esm/ui/hover-card/hover-card.module.css +29 -0
  110. package/lib-esm/ui/hover-card/hover-card.stories.js +35 -0
  111. package/lib-esm/ui/hover-card/index.js +1 -0
  112. package/lib-esm/ui/icon/icon.js +3 -3
  113. package/lib-esm/ui/icon/icon.module.css +1 -0
  114. package/lib-esm/ui/index.js +1 -1
  115. package/lib-esm/ui/table/table.js +5 -65
  116. package/lib-esm/ui/table/table.module.css +41 -25
  117. package/lib-esm/ui/table/table.stories.js +1 -1
  118. package/lib-esm/ui/tag/index.js +1 -0
  119. package/lib-esm/ui/tag/tag.js +45 -0
  120. package/lib-esm/ui/tag/tag.module.css +55 -0
  121. package/lib-esm/ui/tag/tag.stories.js +23 -0
  122. package/lib-esm/ui/tooltip/tooltip.js +10 -9
  123. package/lib-esm/ui/tooltip/tooltip.module.css +2 -1
  124. package/package.json +33 -31
  125. package/src/app/app.jsx +142 -145
  126. package/src/app/app.module.css +11 -4
  127. package/src/app/header/header.jsx +0 -10
  128. package/src/components/asset-info/asset-info.module.css +1 -0
  129. package/src/components/bundle-assets/bundle-assets.jsx +85 -31
  130. package/src/components/bundle-assets/bundle-assets.module.css +24 -26
  131. package/src/components/bundle-assets-totals-table/bundle-assets-totals-table.jsx +2 -0
  132. package/src/components/bundle-modules/bundle-modules.jsx +53 -36
  133. package/src/components/bundle-modules/bundle-modules.module.css +11 -2
  134. package/src/components/bundle-modules/bundle-modules.utils.js +16 -4
  135. package/src/components/bundle-modules/index.jsx +3 -0
  136. package/src/components/bundle-packages/bundle-packages.jsx +89 -39
  137. package/src/components/bundle-packages/bundle-packages.module.css +17 -21
  138. package/src/components/delta/delta.module.css +1 -0
  139. package/src/components/job-header/job-header.jsx +2 -1
  140. package/src/components/job-header/job-header.module.css +6 -8
  141. package/src/components/jobs-header/jobs-header.module.css +5 -0
  142. package/src/components/metrics-table/metrics-table.jsx +53 -44
  143. package/src/components/metrics-table/metrics-table.module.css +18 -7
  144. package/src/components/metrics-table-search/metrics-table-search.module.css +5 -4
  145. package/src/components/metrics-table-title/metrics-table-title.jsx +20 -21
  146. package/src/components/metrics-table-title/metrics-table-title.module.css +2 -0
  147. package/src/components/module-info/module-info.jsx +3 -7
  148. package/src/components/module-info/module-info.module.css +1 -0
  149. package/src/components/summary/summary.jsx +1 -1
  150. package/src/components/summary-item/summary-item.jsx +21 -27
  151. package/src/components/summary-item/summary-item.module.css +1 -23
  152. package/src/components/total-size-type-title/total-size-type-title.jsx +6 -9
  153. package/src/constants.js +1 -1
  154. package/src/css/default.css +14 -3
  155. package/src/css/variables.css +17 -17
  156. package/src/hooks/search-params.js +42 -33
  157. package/src/layout/box/box.module.css +9 -0
  158. package/src/layout/footer/footer.module.css +1 -1
  159. package/src/layout/index.js +1 -0
  160. package/src/layout/separator.jsx +25 -0
  161. package/src/layout/separator.module.css +5 -0
  162. package/src/ui/dropdown/dropdown.jsx +16 -22
  163. package/src/ui/dropdown/dropdown.module.css +11 -12
  164. package/src/ui/empty-set/empty-set.jsx +25 -21
  165. package/src/ui/empty-set/empty-set.module.css +2 -2
  166. package/src/ui/filters/filters.jsx +64 -53
  167. package/src/ui/filters/filters.module.css +3 -3
  168. package/src/ui/hover-card/hover-card.jsx +73 -0
  169. package/src/ui/hover-card/hover-card.module.css +29 -0
  170. package/src/ui/hover-card/index.js +1 -0
  171. package/src/ui/icon/icon.jsx +11 -7
  172. package/src/ui/icon/icon.module.css +1 -0
  173. package/src/ui/index.js +1 -1
  174. package/src/ui/table/table.jsx +5 -52
  175. package/src/ui/table/table.module.css +41 -25
  176. package/src/ui/tag/index.js +1 -0
  177. package/src/ui/tag/tag.jsx +49 -0
  178. package/src/ui/tag/tag.module.css +55 -0
  179. package/src/ui/tooltip/tooltip.jsx +10 -14
  180. package/src/ui/tooltip/tooltip.module.css +2 -1
  181. package/lib/ui/popover/popover.js +0 -92
  182. package/lib/ui/popover/popover.module.css +0 -66
  183. package/lib/ui/popover/popover.stories.js +0 -26
  184. package/lib-esm/ui/popover/index.js +0 -1
  185. package/lib-esm/ui/popover/popover.js +0 -71
  186. package/lib-esm/ui/popover/popover.module.css +0 -66
  187. package/lib-esm/ui/popover/popover.stories.js +0 -14
  188. package/src/ui/popover/index.js +0 -1
  189. package/src/ui/popover/popover.jsx +0 -79
  190. package/src/ui/popover/popover.module.css +0 -66
@@ -1,16 +1,22 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cx from 'classnames';
4
4
  import { get, map } from 'lodash';
5
- import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, FILE_TYPE_LABELS } from '@bundle-stats/utils';
5
+ import {
6
+ ASSET_ENTRY_TYPE,
7
+ ASSET_FILE_TYPE,
8
+ ASSET_FILTERS,
9
+ FILE_TYPE_LABELS,
10
+ } from '@bundle-stats/utils';
6
11
 
7
12
  import config from '../../config.json';
8
13
  import I18N from '../../i18n';
9
14
  import { FlexStack } from '../../layout/flex-stack';
10
15
  import { Icon } from '../../ui/icon';
11
16
  import { FileName } from '../../ui/file-name';
12
- import { Popover } from '../../ui/popover';
17
+ import { HoverCard } from '../../ui/hover-card';
13
18
  import { Tooltip } from '../../ui/tooltip';
19
+ import { Tag } from '../../ui/tag';
14
20
  import { Filters } from '../../ui/filters';
15
21
  import { SortDropdown } from '../../ui/sort-dropdown';
16
22
  import { EmptySet } from '../../ui/empty-set';
@@ -71,48 +77,91 @@ TooltipNotPredictive.propTypes = {
71
77
  runs: PropTypes.array, // eslint-disable-line react/forbid-prop-types
72
78
  };
73
79
 
74
- const getRenderRowHeader = ({ labels, CustomComponentLink, chunks }) => (item) => {
75
- const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = item;
80
+ const RowHeader = ({ row, labels, chunks, CustomComponentLink }) => {
81
+ const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = row;
82
+ const [showHoverCard, setHoverCard] = useState(false);
83
+ const handleOnMouseEnter = useCallback(() => setHoverCard(true), [showHoverCard]);
76
84
 
77
- return (
78
- <Popover
79
- label={<FileName name={label} />}
80
- icon={
81
- <div className={css.assetInfoFlags}>
85
+ const content = useMemo(
86
+ () => (
87
+ <span className={css.assetName}>
88
+ <span className={css.assetNameTags}>
82
89
  {isNotPredictive && (
83
90
  <Tooltip className={css.notPredictive} title={<TooltipNotPredictive runs={runs} />}>
84
91
  <Icon className={css.notPredictiveIcon} glyph="warning" />
85
92
  </Tooltip>
86
93
  )}
87
94
  {isChunk && (
88
- <span title="Chunk" className={css.flagChunk}>
89
- c
90
- </span>
95
+ <Tag
96
+ className={cx(css.assetNameTag, css.assetNameTagChunk)}
97
+ title="Chunk"
98
+ size={Tag.SIZES.SMALL}
99
+ kind={Tag.KINDS.INFO}
100
+ />
91
101
  )}
92
102
  {isEntry && (
93
- <span title="Entrypoint" className={css.flagEntry}>
94
- e
95
- </span>
103
+ <Tag
104
+ className={cx(css.assetNameTag, css.assetNameTagEntry)}
105
+ title="Entrypoint"
106
+ size={Tag.SIZES.SMALL}
107
+ kind={Tag.KINDS.INFO}
108
+ />
96
109
  )}
97
110
  {isInitial && (
98
- <span title="Initial" className={css.flagInitial}>
99
- i
100
- </span>
111
+ <Tag
112
+ className={cx(css.assetNameTag, css.assetNameTagInitial)}
113
+ title="Initial"
114
+ size={Tag.SIZES.SMALL}
115
+ kind={Tag.KINDS.INFO}
116
+ />
101
117
  )}
102
- </div>
103
- }
104
- >
118
+ </span>
119
+ <FileName className={css.assetNameText} name={label} />
120
+ </span>
121
+ ),
122
+ [isChunk, isEntry, isInitial, isNotPredictive, runs],
123
+ );
124
+
125
+ if (!showHoverCard) {
126
+ return <span onMouseEnter={handleOnMouseEnter}>{content}</span>;
127
+ }
128
+
129
+ return (
130
+ <HoverCard label={content}>
105
131
  <AssetInfo
106
132
  className={css.assetInfo}
107
- item={item}
133
+ item={row}
108
134
  labels={labels}
109
135
  chunks={chunks}
110
136
  CustomComponentLink={CustomComponentLink}
111
137
  />
112
- </Popover>
138
+ </HoverCard>
113
139
  );
114
140
  };
115
141
 
142
+ RowHeader.propTypes = {
143
+ row: PropTypes.shape({
144
+ label: PropTypes.string,
145
+ isNotPredictive: PropTypes.bool,
146
+ isChunk: PropTypes.bool,
147
+ isInitial: PropTypes.bool,
148
+ isEntry: PropTypes.bool,
149
+ runs: PropTypes.arrayOf(PropTypes.object),
150
+ }).isRequired,
151
+ chunks: PropTypes.arrayOf(
152
+ PropTypes.shape({
153
+ id: PropTypes.string,
154
+ name: PropTypes.string,
155
+ }),
156
+ ),
157
+ labels: PropTypes.arrayOf(PropTypes.string).isRequired,
158
+ CustomComponentLink: PropTypes.elementType.isRequired,
159
+ };
160
+
161
+ RowHeader.defaultProps = {
162
+ chunks: [],
163
+ };
164
+
116
165
  export const BundleAssets = (props) => {
117
166
  const {
118
167
  className,
@@ -141,11 +190,16 @@ export const BundleAssets = (props) => {
141
190
  />
142
191
  );
143
192
 
144
- const chunks = jobs[0]?.meta?.webpack?.chunks || [];
145
-
146
- const renderRowHeader = useMemo(
147
- () => getRenderRowHeader({ labels: map(jobs, 'label'), CustomComponentLink, chunks }),
148
- [jobs, chunks],
193
+ const renderRowHeader = useCallback(
194
+ (row) => (
195
+ <RowHeader
196
+ row={row}
197
+ labels={map(jobs, 'label')}
198
+ chunks={jobs[0]?.meta?.webpack?.chunks || []}
199
+ CustomComponentLink={CustomComponentLink}
200
+ />
201
+ ),
202
+ [jobs, CustomComponentLink],
149
203
  );
150
204
 
151
205
  return (
@@ -153,7 +207,7 @@ export const BundleAssets = (props) => {
153
207
  <Toolbar
154
208
  className={css.toolbar}
155
209
  renderActions={({ actionClassName }) => (
156
- <FlexStack space="xsmall" className={cx(css.dropdown, actionClassName)}>
210
+ <FlexStack space="xxsmall" className={cx(css.dropdown, actionClassName)}>
157
211
  <SortDropdown fields={sortFields} {...sort} onChange={updateSort} />
158
212
  <MetricsTableOptions
159
213
  handleViewAll={resetAllFilters}
@@ -162,7 +216,7 @@ export const BundleAssets = (props) => {
162
216
  </FlexStack>
163
217
  )}
164
218
  >
165
- <FlexStack>
219
+ <FlexStack space="xxsmall">
166
220
  <MetricsTableSearch
167
221
  className={css.toolbarSearch}
168
222
  placeholder="Search by name"
@@ -8,6 +8,8 @@
8
8
  margin-right: -1px; /** collapse border */
9
9
  }
10
10
 
11
+ .assetName {}
12
+
11
13
  .notPredictive {
12
14
  margin-right: var(--space-xxsmall);
13
15
  display: inline-block;
@@ -21,50 +23,46 @@
21
23
  vertical-align: top;
22
24
  }
23
25
 
24
- .assetInfoFlags {
25
- display: inline-block;
26
+ .assetNameTags {
27
+ display: inline;
26
28
  margin-right: var(--space-xxxsmall);
27
29
  }
28
30
 
29
- .assetInfoFlags:empty {
31
+ .assetNameTags:empty {
30
32
  display: none;
31
33
  }
32
34
 
33
- .flag {
34
- display: inline-block;
35
- width: var(--space-xsmall);
36
- height: var(--space-xsmall);
37
- font-family: var(--font-family);
38
- font-size: 8px;
39
- font-weight: bold;
40
- background: var(--color-highlight);
41
- color: var(--color-text-light);
42
- line-height: var(--space-xsmall);
43
- text-align: center;
44
- text-transform: uppercase;
35
+ .assetNameTag {
45
36
  vertical-align: middle;
46
37
  }
47
38
 
48
- .flag + .flag {
39
+ .assetNameTag + .assetNameTag {
49
40
  margin-left: 2px;
50
41
  }
51
42
 
52
- .flagEntry {
53
- composes: flag;
43
+ .assetNameTagEntry {
54
44
  background: var(--color-info-light);
55
- color: var(--color-background);
56
45
  }
57
46
 
58
- .flagInitial {
59
- composes: flag;
47
+ .assetNameTagEntry::before {
48
+ content: 'e';
49
+ }
50
+
51
+ .assetNameTagInitial {
60
52
  background: var(--color-info-light);
61
- color: var(--color-background);
62
53
  }
63
54
 
64
- .flagChunk {
65
- composes: flag;
66
- background: var(--color-info);
67
- color: var(--color-background);
55
+ .assetNameTagInitial::before {
56
+ content: 'i';
57
+ }
58
+
59
+ .assetNameTagChunk::before {
60
+ content: 'c';
61
+ }
62
+
63
+ .assetNameText {
64
+ display: inline;
65
+ vertical-align: middle;
68
66
  }
69
67
 
70
68
  /* Tooltip not predictive */
@@ -15,6 +15,7 @@ export const BundleAssetsTotalsTable = ({
15
15
  className,
16
16
  jobs,
17
17
  customComponentLink: CustomComponentLink,
18
+ ...restProps
18
19
  }) => {
19
20
  const items = useMemo(() => webpack.compareBySection.sizes(jobs), [jobs]);
20
21
  const budgets = jobs[0].insights?.webpack?.budgets;
@@ -46,6 +47,7 @@ export const BundleAssetsTotalsTable = ({
46
47
  items={items}
47
48
  renderRowHeader={renderRowHeader}
48
49
  showHeaderSum
50
+ {...restProps}
49
51
  />
50
52
  );
51
53
  };
@@ -18,8 +18,9 @@ import { FlexStack } from '../../layout/flex-stack';
18
18
  import { EmptySet } from '../../ui/empty-set';
19
19
  import { FileName } from '../../ui/file-name';
20
20
  import { Filters } from '../../ui/filters';
21
- import { Popover } from '../../ui/popover';
21
+ import { HoverCard } from '../../ui/hover-card';
22
22
  import { SortDropdown } from '../../ui/sort-dropdown';
23
+ import { Tag } from '../../ui/tag';
23
24
  import { Toolbar } from '../../ui/toolbar';
24
25
  import { MetricsTable } from '../metrics-table';
25
26
  import { MetricsTableSearch } from '../metrics-table-search';
@@ -31,36 +32,42 @@ import css from './bundle-modules.module.css';
31
32
  const RowHeader = ({ row, chunks, labels, CustomComponentLink }) => {
32
33
  const chunkIds = map(chunks, 'id');
33
34
 
34
- const [showPopopver, setPopover] = useState(false);
35
- const handleOnMouseEnter = useCallback(() => setPopover(true), [showPopopver]);
36
- const content = <FileName name={row.label} />;
35
+ const [showHoverCard, setHoverCard] = useState(false);
36
+ const handleOnMouseEnter = useCallback(() => setHoverCard(true), [showHoverCard]);
37
+ const content = useMemo(
38
+ () => (
39
+ <span className={css.name}>
40
+ {row.duplicated && (
41
+ <Tag className={css.nameTagDuplicated} size="small" kind={Tag.KINDS.DANGER} />
42
+ )}
43
+ <FileName className={css.nameText} name={row.label} />
44
+ </span>
45
+ ),
46
+ [row],
47
+ );
48
+
49
+ if (!showHoverCard) {
50
+ return <div onMouseEnter={handleOnMouseEnter}>{content}</div>;
51
+ }
37
52
 
38
53
  return (
39
- <div onMouseEnter={handleOnMouseEnter}>
40
- {!showPopopver ?
41
- content
42
- : (
43
- <Popover ariaLabel="View module info" label={content}>
44
- {({ popoverToggle }) => (
45
- <ModuleInfo
46
- className={css.namePopover}
47
- item={row}
48
- chunks={chunks}
49
- chunkIds={chunkIds}
50
- labels={labels}
51
- customComponentLink={CustomComponentLink}
52
- onClick={popoverToggle}
53
- />
54
- )}
55
- </Popover>
56
- )}
57
- </div>
54
+ <HoverCard label={content}>
55
+ <ModuleInfo
56
+ className={css.namePopover}
57
+ item={row}
58
+ chunks={chunks}
59
+ chunkIds={chunkIds}
60
+ labels={labels}
61
+ customComponentLink={CustomComponentLink}
62
+ />
63
+ </HoverCard>
58
64
  );
59
65
  };
60
66
 
61
67
  RowHeader.propTypes = {
62
68
  row: PropTypes.shape({
63
69
  label: PropTypes.string,
70
+ duplicated: PropTypes.bool,
64
71
  }).isRequired,
65
72
  chunks: PropTypes.arrayOf(
66
73
  PropTypes.shape({
@@ -76,10 +83,6 @@ RowHeader.defaultProps = {
76
83
  chunks: [],
77
84
  };
78
85
 
79
- const getRenderRowHeader = ({ labels, chunks, CustomComponentLink }) => (row) => (
80
- <RowHeader row={row} chunks={chunks} labels={labels} CustomComponentLink={CustomComponentLink} />
81
- );
82
-
83
86
  export const BundleModules = ({
84
87
  className,
85
88
  jobs,
@@ -100,11 +103,18 @@ export const BundleModules = ({
100
103
  }) => {
101
104
  const rootClassName = cx(css.root, className);
102
105
 
103
- const labels = useMemo(() => map(jobs, 'label'), [jobs]);
104
- const renderRowHeader = useMemo(
105
- () => getRenderRowHeader({ labels, chunks, CustomComponentLink }),
106
- [labels, chunks],
106
+ const renderRowHeader = useCallback(
107
+ (row) => (
108
+ <RowHeader
109
+ row={row}
110
+ chunks={chunks}
111
+ labels={map(jobs, 'label')}
112
+ CustomComponentLink={CustomComponentLink}
113
+ />
114
+ ),
115
+ [jobs, chunks, CustomComponentLink],
107
116
  );
117
+
108
118
  const emptyMessage = (
109
119
  <EmptySet
110
120
  resources="modules"
@@ -120,6 +130,11 @@ export const BundleModules = ({
120
130
  defaultValue: filters.changed,
121
131
  disabled: jobs.length <= 1,
122
132
  },
133
+ [MODULE_FILTERS.DUPLICATED]: {
134
+ label: 'Duplicated',
135
+ defaultValue: filters[MODULE_FILTERS.DUPLICATED],
136
+ disabled: jobs.length <= 1,
137
+ },
123
138
  [MODULE_SOURCE_TYPE]: {
124
139
  label: 'Source',
125
140
  [MODULE_FILTERS.FIRST_PARTY]: {
@@ -184,7 +199,7 @@ export const BundleModules = ({
184
199
  </FlexStack>
185
200
  )}
186
201
  >
187
- <FlexStack>
202
+ <FlexStack space="xxsmall">
188
203
  <MetricsTableSearch
189
204
  className={css.toolbarSearch}
190
205
  search={search}
@@ -240,10 +255,12 @@ BundleModules.propTypes = {
240
255
  jobs: PropTypes.array, // eslint-disable-line react/forbid-prop-types
241
256
 
242
257
  /** Chunks data */
243
- chunks: PropTypes.arrayOf(PropTypes.shape({
244
- id: PropTypes.string,
245
- name: PropTypes.string,
246
- })).isRequired,
258
+ chunks: PropTypes.arrayOf(
259
+ PropTypes.shape({
260
+ id: PropTypes.string,
261
+ name: PropTypes.string,
262
+ }),
263
+ ).isRequired,
247
264
 
248
265
  /** total row count */
249
266
  totalRowCount: PropTypes.number,
@@ -12,6 +12,15 @@
12
12
  margin-right: -1px; /** collapse border */
13
13
  }
14
14
 
15
- .namePopover {
16
- min-width: 280px;
15
+ .nameTagDuplicated {
16
+ margin-right: var(--space-xxxsmall);
17
+ vertical-align: baseline;
18
+ }
19
+
20
+ .nameTagDuplicated::before {
21
+ content: 'D';
22
+ }
23
+
24
+ .nameText {
25
+ vertical-align: middle;
17
26
  }
@@ -12,9 +12,15 @@ import {
12
12
  import { SORT_BY_NAME, SORT_BY_SIZE, SORT_BY_DELTA } from './bundle-modules.constants';
13
13
 
14
14
  export const addRowSourceFlag = (row) => {
15
- const { key } = row;
16
- const thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES));
17
- return { ...row, thirdParty };
15
+ const { key, runs } = row;
16
+
17
+ // @NOTE Assign instead destructuring for perf reasons
18
+ // eslint-disable-next-line no-param-reassign
19
+ row.thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES));
20
+ // eslint-disable-next-line no-param-reassign
21
+ row.duplicated = Boolean(runs.find((run) => run?.duplicated === true));
22
+
23
+ return row;
18
24
  };
19
25
 
20
26
  export const getCustomSort = (sortBy) => (item) => {
@@ -39,10 +45,16 @@ export const getRowFilter = (filters) => (row) => {
39
45
  return false;
40
46
  }
41
47
 
48
+ // Skip not duplicated rows
49
+ if (filters[MODULE_FILTERS.DUPLICATED] && !row.duplicated) {
50
+ return false;
51
+ }
52
+
42
53
  // Skip not matching source type
43
54
  if (
44
55
  !(
45
- (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`] && row.thirdParty === false) ||
56
+ (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`] &&
57
+ row.thirdParty === false) ||
46
58
  (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`] && row.thirdParty === true)
47
59
  )
48
60
  ) {
@@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { uniqBy, map } from 'lodash';
4
4
  import {
5
+ MODULE_FILTERS,
5
6
  getModuleChunkFilters,
6
7
  getModuleSourceTypeFilters,
7
8
  getModuleFileTypeFilters,
@@ -33,12 +34,14 @@ export const BundleModules = (props) => {
33
34
  ...getModuleSourceTypeFilters(true),
34
35
  ...getModuleChunkFilters(chunkIds, true),
35
36
  ...getModuleFileTypeFilters(true),
37
+ [MODULE_FILTERS.DUPLICATED]: false,
36
38
  },
37
39
  allEntriesFilters: {
38
40
  changed: false,
39
41
  ...getModuleSourceTypeFilters(true),
40
42
  ...getModuleChunkFilters(chunkIds, true),
41
43
  ...getModuleFileTypeFilters(true),
44
+ [MODULE_FILTERS.DUPLICATED]: false,
42
45
  },
43
46
  }),
44
47
  [jobs],