@bundle-stats/ui 4.8.2 → 4.8.4-beta.0

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 (124) hide show
  1. package/lib/components/bundle-assets/bundle-assets.js +28 -27
  2. package/lib/components/bundle-assets/bundle-assets.js.map +1 -1
  3. package/lib/components/bundle-modules/bundle-modules.i18n.js +12 -0
  4. package/lib/components/bundle-modules/bundle-modules.i18n.js.map +1 -0
  5. package/lib/components/bundle-modules/bundle-modules.js +12 -99
  6. package/lib/components/bundle-modules/bundle-modules.js.map +1 -1
  7. package/lib/components/bundle-modules/bundle-modules.utils.js +108 -1
  8. package/lib/components/bundle-modules/bundle-modules.utils.js.map +1 -1
  9. package/lib/components/bundle-modules/index.js +6 -45
  10. package/lib/components/bundle-modules/index.js.map +1 -1
  11. package/lib/components/entry-info/entry-info.i18n.js +7 -0
  12. package/lib/components/entry-info/entry-info.i18n.js.map +1 -0
  13. package/lib/components/entry-info/entry-info.js +27 -3
  14. package/lib/components/entry-info/entry-info.js.map +1 -1
  15. package/lib/components/job-name/job-name.js +1 -1
  16. package/lib/components/job-name/job-name.js.map +1 -1
  17. package/lib/components/metric-run-info/metric-run-info.js +10 -5
  18. package/lib/components/metric-run-info/metric-run-info.js.map +1 -1
  19. package/lib/components/metrics-table/index.js +1 -1
  20. package/lib/components/metrics-table/index.js.map +1 -1
  21. package/lib/components/metrics-table/metrics-table.i18n.js +12 -0
  22. package/lib/components/metrics-table/metrics-table.i18n.js.map +1 -0
  23. package/lib/components/metrics-table/metrics-table.js +50 -69
  24. package/lib/components/metrics-table/metrics-table.js.map +1 -1
  25. package/lib/components/metrics-table/metrics-table.module.css +2 -2
  26. package/lib/components/sort-button/sort-button.js +10 -4
  27. package/lib/components/sort-button/sort-button.js.map +1 -1
  28. package/lib/components/sort-button/sort-button.module.css +9 -9
  29. package/lib/hooks/rows-sort.js.map +1 -1
  30. package/lib/ui/filters/filters.i18n.js +6 -0
  31. package/lib/ui/filters/filters.i18n.js.map +1 -0
  32. package/lib/ui/filters/filters.js +16 -54
  33. package/lib/ui/filters/filters.js.map +1 -1
  34. package/lib/ui/filters/filters.utils.js +27 -11
  35. package/lib/ui/filters/filters.utils.js.map +1 -1
  36. package/lib/ui/filters/index.js +0 -8
  37. package/lib/ui/filters/index.js.map +1 -1
  38. package/lib-esm/components/bundle-assets/bundle-assets.js +28 -27
  39. package/lib-esm/components/bundle-assets/bundle-assets.js.map +1 -1
  40. package/lib-esm/components/bundle-modules/bundle-modules.i18n.js +9 -0
  41. package/lib-esm/components/bundle-modules/bundle-modules.i18n.js.map +1 -0
  42. package/lib-esm/components/bundle-modules/bundle-modules.js +13 -101
  43. package/lib-esm/components/bundle-modules/bundle-modules.js.map +1 -1
  44. package/lib-esm/components/bundle-modules/bundle-modules.utils.js +83 -1
  45. package/lib-esm/components/bundle-modules/bundle-modules.utils.js.map +1 -1
  46. package/lib-esm/components/bundle-modules/index.js +9 -45
  47. package/lib-esm/components/bundle-modules/index.js.map +1 -1
  48. package/lib-esm/components/entry-info/entry-info.i18n.js +4 -0
  49. package/lib-esm/components/entry-info/entry-info.i18n.js.map +1 -0
  50. package/lib-esm/components/entry-info/entry-info.js +4 -3
  51. package/lib-esm/components/entry-info/entry-info.js.map +1 -1
  52. package/lib-esm/components/job-name/job-name.js +1 -1
  53. package/lib-esm/components/job-name/job-name.js.map +1 -1
  54. package/lib-esm/components/metric-run-info/metric-run-info.js +10 -5
  55. package/lib-esm/components/metric-run-info/metric-run-info.js.map +1 -1
  56. package/lib-esm/components/metrics-table/index.js +1 -1
  57. package/lib-esm/components/metrics-table/index.js.map +1 -1
  58. package/lib-esm/components/metrics-table/metrics-table.i18n.js +9 -0
  59. package/lib-esm/components/metrics-table/metrics-table.i18n.js.map +1 -0
  60. package/lib-esm/components/metrics-table/metrics-table.js +37 -79
  61. package/lib-esm/components/metrics-table/metrics-table.js.map +1 -1
  62. package/lib-esm/components/metrics-table/metrics-table.module.css +2 -2
  63. package/lib-esm/components/sort-button/sort-button.js +10 -4
  64. package/lib-esm/components/sort-button/sort-button.js.map +1 -1
  65. package/lib-esm/components/sort-button/sort-button.module.css +9 -9
  66. package/lib-esm/hooks/rows-sort.js.map +1 -1
  67. package/lib-esm/ui/filters/filters.i18n.js +3 -0
  68. package/lib-esm/ui/filters/filters.i18n.js.map +1 -0
  69. package/lib-esm/ui/filters/filters.js +16 -54
  70. package/lib-esm/ui/filters/filters.js.map +1 -1
  71. package/lib-esm/ui/filters/filters.utils.js +27 -11
  72. package/lib-esm/ui/filters/filters.utils.js.map +1 -1
  73. package/lib-esm/ui/filters/index.js +0 -8
  74. package/lib-esm/ui/filters/index.js.map +1 -1
  75. package/package.json +10 -10
  76. package/src/components/bundle-assets/bundle-assets.jsx +28 -30
  77. package/src/components/bundle-modules/bundle-modules.i18n.ts +8 -0
  78. package/src/components/bundle-modules/bundle-modules.tsx +200 -0
  79. package/src/components/bundle-modules/bundle-modules.types.d.ts +24 -0
  80. package/src/components/bundle-modules/bundle-modules.utils.ts +208 -0
  81. package/src/components/bundle-modules/index.tsx +102 -0
  82. package/src/components/entry-info/entry-info.i18n.ts +3 -0
  83. package/src/components/entry-info/entry-info.tsx +10 -4
  84. package/src/components/job-name/job-name.jsx +7 -1
  85. package/src/components/metric-run-info/metric-run-info.tsx +12 -5
  86. package/src/components/metrics-table/index.tsx +14 -0
  87. package/src/components/metrics-table/metrics-table.i18n.ts +10 -0
  88. package/src/components/metrics-table/metrics-table.module.css +2 -2
  89. package/src/components/metrics-table/metrics-table.stories.tsx +225 -0
  90. package/src/components/metrics-table/metrics-table.tsx +301 -0
  91. package/src/components/sort-button/sort-button.module.css +9 -9
  92. package/src/components/sort-button/sort-button.tsx +14 -6
  93. package/src/hooks/rows-sort.ts +8 -8
  94. package/src/types.d.ts +11 -0
  95. package/src/ui/filters/__tests__/filters.utils.ts +98 -0
  96. package/src/ui/filters/filters.i18n.ts +2 -0
  97. package/src/ui/filters/{filters.jsx → filters.tsx} +63 -72
  98. package/src/ui/filters/{filters.utils.js → filters.utils.ts} +38 -21
  99. package/src/ui/filters/{index.jsx → index.tsx} +14 -17
  100. package/types/components/bundle-modules/bundle-modules.d.ts +21 -62
  101. package/types/components/bundle-modules/bundle-modules.i18n.d.ts +8 -0
  102. package/types/components/bundle-modules/bundle-modules.utils.d.ts +28 -5
  103. package/types/components/bundle-modules/index.d.ts +13 -22
  104. package/types/components/entry-info/entry-info.i18n.d.ts +3 -0
  105. package/types/components/job-name/job-name.d.ts +1 -1
  106. package/types/components/metrics-table/index.d.ts +4 -1
  107. package/types/components/metrics-table/metrics-table.d.ts +25 -47
  108. package/types/components/metrics-table/metrics-table.i18n.d.ts +8 -0
  109. package/types/components/sort-button/sort-button.d.ts +2 -2
  110. package/types/hooks/rows-sort.d.ts +3 -3
  111. package/types/layout/box/box.d.ts +1 -1
  112. package/types/layout/flex-stack/flex-stack.d.ts +1 -1
  113. package/types/ui/filters/filters.d.ts +24 -14
  114. package/types/ui/filters/filters.i18n.d.ts +2 -0
  115. package/types/ui/filters/filters.utils.d.ts +7 -6
  116. package/types/ui/filters/index.d.ts +8 -12
  117. package/types/ui/table/table.d.ts +5 -5
  118. package/types/ui/tooltip/tooltip.d.ts +1 -1
  119. package/src/components/bundle-modules/bundle-modules.jsx +0 -291
  120. package/src/components/bundle-modules/bundle-modules.utils.js +0 -82
  121. package/src/components/bundle-modules/index.jsx +0 -113
  122. package/src/components/metrics-table/index.jsx +0 -11
  123. package/src/components/metrics-table/metrics-table.jsx +0 -309
  124. package/src/ui/filters/__tests__/filters-dropdown.utils.js +0 -74
@@ -0,0 +1,200 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import cx from 'classnames';
3
+ import { SECTIONS, COMPONENT } from '@bundle-stats/utils';
4
+
5
+ import type { SortAction } from '../../types';
6
+ import config from '../../config.json';
7
+ import I18N from '../../i18n';
8
+ import { ComponentLink } from '../component-link';
9
+ import { FlexStack } from '../../layout/flex-stack';
10
+ import { EmptySet } from '../../ui/empty-set';
11
+ import { FileName } from '../../ui/file-name';
12
+ import { Filters } from '../../ui/filters';
13
+ import { Tag } from '../../ui/tag';
14
+ import { Toolbar } from '../../ui/toolbar';
15
+ import { MetricsTable } from '../metrics-table';
16
+ import { MetricsTableSearch } from '../metrics-table-search';
17
+ import { MetricsTableOptions } from '../metrics-table-options';
18
+ import { MetricsTableTitle } from '../metrics-table-title';
19
+ import { ModuleInfo } from '../module-info';
20
+ import { generateFilterFieldsData } from './bundle-modules.utils';
21
+ import type { Chunk, Job, ReportMetricModuleRow } from './bundle-modules.types';
22
+ import * as I18N_MODULES from './bundle-modules.i18n';
23
+ import css from './bundle-modules.module.css';
24
+
25
+ interface RowHeaderProps {
26
+ row: ReportMetricModuleRow;
27
+ filters?: any;
28
+ search?: string;
29
+ customComponentLink: React.ElementType;
30
+ }
31
+
32
+ const RowHeader = (props: RowHeaderProps) => {
33
+ const { row, filters, search, customComponentLink: CustomComponentLink } = props;
34
+
35
+ return (
36
+ <CustomComponentLink
37
+ section={SECTIONS.MODULES}
38
+ params={{ [COMPONENT.BUNDLE_MODULES]: { filters, search, entryId: row.key } }}
39
+ className={css.name}
40
+ >
41
+ {row.duplicated && (
42
+ <Tag className={css.nameTagDuplicated} size="small" kind={Tag.KINDS.DANGER} />
43
+ )}
44
+ <FileName className={css.nameText} name={row.label} />
45
+ </CustomComponentLink>
46
+ );
47
+ };
48
+
49
+ interface BundleModulesProps extends React.ComponentProps<'div'> {
50
+ jobs?: Array<Job>;
51
+ items?: Array<ReportMetricModuleRow>;
52
+ allItems?: Array<ReportMetricModuleRow>;
53
+ chunks?: Array<Chunk>;
54
+
55
+ totalRowCount: number;
56
+
57
+ updateFilters: () => void;
58
+ resetFilters: () => void;
59
+ resetAllFilters: () => void;
60
+ filters: Record<string, boolean>;
61
+
62
+ sort: any;
63
+ updateSort: ({ field, direction }: SortAction) => void;
64
+
65
+ search: string;
66
+ updateSearch: (newSerarch: string) => void;
67
+
68
+ entryId?: string;
69
+ hideEntryInfo: () => void;
70
+
71
+ customComponentLink: React.ElementType;
72
+ }
73
+
74
+ export const BundleModules = (props: BundleModulesProps) => {
75
+ const {
76
+ className = '',
77
+ jobs = [],
78
+ items = [],
79
+ allItems = [],
80
+ chunks = [],
81
+ totalRowCount = 0,
82
+ updateFilters,
83
+ resetFilters,
84
+ resetAllFilters,
85
+ filters,
86
+ sort,
87
+ updateSort,
88
+ search,
89
+ updateSearch,
90
+ entryId,
91
+ hideEntryInfo,
92
+ customComponentLink: CustomComponentLink = ComponentLink,
93
+ } = props;
94
+
95
+ const rootClassName = cx(css.root, className);
96
+ const jobLabels = jobs?.map((job) => job?.label);
97
+
98
+ const filterFieldsData = useMemo(
99
+ () => generateFilterFieldsData({ filters, chunks, compareMode: jobs.length > 1 }),
100
+ [jobs, filters, chunks],
101
+ );
102
+
103
+ const metricsTableTitle = useMemo(
104
+ () => (
105
+ <MetricsTableTitle
106
+ title={I18N.MODULES}
107
+ info={`${items.length}/${totalRowCount}`}
108
+ popoverInfo={I18N.MODULES_INFO}
109
+ popoverHref={config.documentation.modules}
110
+ />
111
+ ),
112
+ [items, totalRowCount],
113
+ );
114
+
115
+ const renderRowHeader = useCallback(
116
+ (row: ReportMetricModuleRow) => (
117
+ <RowHeader
118
+ row={row}
119
+ filters={filters}
120
+ search={search}
121
+ customComponentLink={CustomComponentLink}
122
+ />
123
+ ),
124
+ [jobs, chunks, CustomComponentLink, filters, search],
125
+ );
126
+
127
+ const emptyMessage = useMemo(
128
+ () => (
129
+ <EmptySet
130
+ resources="modules"
131
+ filtered={totalRowCount !== 0}
132
+ handleResetFilters={resetFilters}
133
+ handleViewAll={resetAllFilters}
134
+ />
135
+ ),
136
+ [totalRowCount, resetFilters, resetAllFilters],
137
+ );
138
+
139
+ const entryItem = useMemo(() => {
140
+ if (!entryId) {
141
+ return null;
142
+ }
143
+
144
+ return allItems.find(({ key }) => key === entryId);
145
+ }, [allItems, entryId]);
146
+
147
+ return (
148
+ <>
149
+ <div className={rootClassName}>
150
+ <Toolbar
151
+ className={css.toolbar}
152
+ renderActions={({ actionClassName }) => (
153
+ <FlexStack space="xxsmall" className={cx(css.dropdown, actionClassName)}>
154
+ <MetricsTableOptions
155
+ handleViewAll={resetAllFilters}
156
+ handleResetFilters={resetFilters}
157
+ />
158
+ </FlexStack>
159
+ )}
160
+ >
161
+ <FlexStack space="xxsmall">
162
+ <MetricsTableSearch
163
+ className={css.toolbarSearch}
164
+ search={search}
165
+ updateSearch={updateSearch}
166
+ placeholder={I18N_MODULES.SEARCH}
167
+ />
168
+ <Filters
169
+ className={css.tableDropdown}
170
+ filters={filterFieldsData}
171
+ onChange={updateFilters}
172
+ />
173
+ </FlexStack>
174
+ </Toolbar>
175
+ <MetricsTable
176
+ className={css.table}
177
+ items={items}
178
+ runs={jobs}
179
+ renderRowHeader={renderRowHeader}
180
+ emptyMessage={emptyMessage}
181
+ showHeaderSum
182
+ title={metricsTableTitle}
183
+ sort={sort}
184
+ updateSort={updateSort}
185
+ />
186
+ </div>
187
+ {entryItem && (
188
+ <ModuleInfo
189
+ className={css.moduleInfo}
190
+ item={entryItem}
191
+ chunks={chunks}
192
+ chunkIds={chunks?.map(({ id }) => id)}
193
+ labels={jobLabels}
194
+ customComponentLink={CustomComponentLink}
195
+ onClose={hideEntryInfo}
196
+ />
197
+ )}
198
+ </>
199
+ );
200
+ };
@@ -0,0 +1,24 @@
1
+ import { Module } from '@bundle-stats/utils/lib-esm/webpack/types';
2
+ import { ReportMetricRun } from '@bundle-stats/utils/types/report/types';
3
+
4
+ export interface Chunk {
5
+ id: string;
6
+ name: string;
7
+ }
8
+
9
+ export type ReportMetricModuleRow = {
10
+ thirdParty: boolean;
11
+ duplicated: boolean;
12
+ fileType: string;
13
+ } & ReportMetricRun &
14
+ Module;
15
+
16
+ interface Job {
17
+ label: string;
18
+ internalBuildNumber: number;
19
+ meta?: {
20
+ webpack?: {
21
+ chunks?: Array<Chunk>;
22
+ };
23
+ };
24
+ }
@@ -0,0 +1,208 @@
1
+ import intersection from 'lodash/intersection';
2
+ import get from 'lodash/get';
3
+ import isEmpty from 'lodash/isEmpty';
4
+ import union from 'lodash/union';
5
+ import uniqBy from 'lodash/uniqBy';
6
+ import orderBy from 'lodash/orderBy';
7
+ import type { MetricRunInfo, ReportMetricRow } from '@bundle-stats/utils';
8
+ // @ts-ignore
9
+ import { MODULE_PATH_PACKAGES, type Module } from '@bundle-stats/utils/lib-esm/webpack';
10
+ import {
11
+ FILE_TYPE_LABELS,
12
+ MODULE_CHUNK,
13
+ MODULE_FILTERS,
14
+ MODULE_FILE_TYPE,
15
+ MODULE_SOURCE_TYPE,
16
+ MODULE_SOURCE_FILE_TYPES,
17
+ getModuleSourceFileType,
18
+ getModuleChunkFilters,
19
+ getModuleSourceTypeFilters,
20
+ getModuleFileTypeFilters,
21
+ } from '@bundle-stats/utils';
22
+ import type { ReportMetricRun } from '@bundle-stats/utils/types/report/types';
23
+
24
+ import type { FilterFieldsData, FilterGroupFieldData } from '../../types';
25
+ import type { Chunk, Job, ReportMetricModuleRow } from './bundle-modules.types';
26
+ import * as I18N from './bundle-modules.i18n';
27
+
28
+ export const addRowFlags = (row: Module & ReportMetricRow): ReportMetricModuleRow => {
29
+ const { key, runs } = row;
30
+
31
+ // @NOTE Assign instead destructuring for perf reasons
32
+ // eslint-disable-next-line no-param-reassign
33
+ row.thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES));
34
+ // eslint-disable-next-line no-param-reassign
35
+ row.duplicated = Boolean(runs.find((run: Module & ReportMetricRun) => run?.duplicated === true));
36
+ // eslint-disable-next-line no-param-reassign
37
+ row.fileType = getModuleSourceFileType(row.key);
38
+
39
+ return row;
40
+ };
41
+
42
+ export const getCustomSort = (item: ReportMetricRow) => [!item.changed, item.key];
43
+
44
+ /* eslint-disable prettier/prettier */
45
+ export const generateGetRowFilter =
46
+ ({ chunkIds }: { chunkIds: Array<string> }) => (filters: Record<string, unknown>) => {
47
+ // eslint-disable-line prettier/prettier
48
+ // List of chunkIds with filter value set to `true`
49
+ const checkedChunkIds: Array<string> = [];
50
+
51
+ chunkIds.forEach((chunkId) => {
52
+ if (filters[`${MODULE_CHUNK}.${chunkId}`]) {
53
+ checkedChunkIds.push(chunkId);
54
+ }
55
+ });
56
+
57
+ const hasChunkFilters = checkedChunkIds.length !== chunkIds.length;
58
+
59
+ return (row: ReportMetricModuleRow) => {
60
+ // Skip not changed rows
61
+ if (filters[MODULE_FILTERS.CHANGED] && !row.changed) {
62
+ return false;
63
+ }
64
+
65
+ // Skip not duplicated rows
66
+ if (filters[MODULE_FILTERS.DUPLICATED] && !row.duplicated) {
67
+ return false;
68
+ }
69
+
70
+ // Skip not matching source type
71
+ if (
72
+ !(
73
+ (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`] &&
74
+ row.thirdParty === false) ||
75
+ (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`] &&
76
+ row.thirdParty === true)
77
+ )
78
+ ) {
79
+ return false;
80
+ }
81
+
82
+ // Skip not matching source file types
83
+ if (!filters[`${MODULE_FILE_TYPE}.${row.fileType}`]) {
84
+ return false;
85
+ }
86
+
87
+ // Filter if any of the chunkIds are checked
88
+ if (hasChunkFilters) {
89
+ const rowRunsChunkIds =
90
+ row?.runs?.map((run: Module & MetricRunInfo) => run?.chunkIds || []) || [];
91
+ const rowChunkIds = union(...rowRunsChunkIds);
92
+ const matchedChunkIds = intersection(rowChunkIds, checkedChunkIds);
93
+ return matchedChunkIds.length > 0;
94
+ }
95
+
96
+ return true;
97
+ };
98
+ };
99
+ /* eslint-enable prettier/prettier */
100
+
101
+ export const extractChunkData = (jobs: Array<Job>) => {
102
+ const jobChunks = jobs.map((job) => job?.meta?.webpack?.chunks || []);
103
+ const chunks = uniqBy(jobChunks.flat(), ({ id }) => id);
104
+
105
+ const chunkIds = chunks?.map(({ id }) => id);
106
+
107
+ return {
108
+ chunks,
109
+ chunkIds,
110
+ };
111
+ };
112
+
113
+ export const generateFilters = (chunkIds: Array<string>, multipleJobs: boolean) => {
114
+ const allEntriesFilters = {
115
+ [MODULE_FILTERS.CHANGED]: false,
116
+ [MODULE_FILTERS.DUPLICATED]: false,
117
+ ...getModuleSourceTypeFilters(true),
118
+ ...getModuleChunkFilters(chunkIds, true),
119
+ ...getModuleFileTypeFilters(true),
120
+ };
121
+
122
+ return {
123
+ defaultFilters: {
124
+ ...allEntriesFilters,
125
+ // By default, set 'changed' filter to `true` only when we have multiple jobs
126
+ [MODULE_FILTERS.CHANGED]: multipleJobs,
127
+ },
128
+ allEntriesFilters,
129
+ };
130
+ };
131
+
132
+ interface GetFiltersFormDataParams {
133
+ /**
134
+ * Filter values
135
+ */
136
+ filters: Record<string, boolean>;
137
+ /**
138
+ * Chunk data
139
+ */
140
+ chunks: Array<Chunk>;
141
+ /**
142
+ * Compare mode
143
+ */
144
+ compareMode: boolean;
145
+ }
146
+
147
+ /**
148
+ * Generate filter fields data
149
+ * @NOTE use assignments instead of Object.assign/destructuring for performance reson
150
+ */
151
+ export const generateFilterFieldsData = (params: GetFiltersFormDataParams): FilterFieldsData => {
152
+ const { filters, chunks, compareMode } = params;
153
+
154
+ const result: FilterFieldsData = {
155
+ [MODULE_FILTERS.CHANGED]: {
156
+ label: I18N.CHANGED,
157
+ defaultValue: filters[MODULE_FILTERS.CHANGED],
158
+ disabled: !compareMode,
159
+ },
160
+ [MODULE_FILTERS.DUPLICATED]: {
161
+ label: I18N.DUPLICATE,
162
+ defaultValue: filters[MODULE_FILTERS.DUPLICATED],
163
+ },
164
+ };
165
+
166
+ result[MODULE_SOURCE_TYPE] = {
167
+ label: I18N.SOURCE,
168
+ children: [
169
+ {
170
+ key: MODULE_FILTERS.FIRST_PARTY,
171
+ label: I18N.SOURCE_FIRST_PARTY,
172
+ defaultValue: get(filters, `${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`, true),
173
+ },
174
+ {
175
+ key: MODULE_FILTERS.THIRD_PARTY,
176
+ label: I18N.SOURCE_THIRD_PARTY,
177
+ defaultValue: get(filters, `${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`, true),
178
+ },
179
+ ],
180
+ } as FilterGroupFieldData;
181
+
182
+ if (!isEmpty(chunks)) {
183
+ const chunksFilter = { label: I18N.CHUNK, children: [] } as FilterGroupFieldData;
184
+ const chunksOrderedByName = orderBy(chunks, 'name');
185
+ chunksOrderedByName.forEach((chunk) => {
186
+ chunksFilter.children.push({
187
+ key: chunk.id,
188
+ label: chunk.name,
189
+ defaultValue: filters[`${MODULE_CHUNK}.${chunk.id}`] ?? true,
190
+ });
191
+ });
192
+
193
+ result[MODULE_CHUNK] = chunksFilter;
194
+ }
195
+
196
+ const moduleFileTypeFilter = { label: I18N.FILE_TYPE, children: [] } as FilterGroupFieldData;
197
+ MODULE_SOURCE_FILE_TYPES.forEach((fileType: string) => {
198
+ moduleFileTypeFilter.children.push({
199
+ key: fileType,
200
+ label: FILE_TYPE_LABELS[fileType as keyof typeof FILE_TYPE_LABELS],
201
+ defaultValue: get(filters, `${MODULE_FILE_TYPE}.${fileType}`, true),
202
+ });
203
+ });
204
+
205
+ result[MODULE_FILE_TYPE] = moduleFileTypeFilter;
206
+
207
+ return result;
208
+ };
@@ -0,0 +1,102 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ // @ts-ignore
3
+ import * as webpack from '@bundle-stats/utils/lib-esm/webpack';
4
+
5
+ import type { SortAction } from '../../types';
6
+ import { useRowsFilter } from '../../hooks/rows-filter';
7
+ import { useRowsSort } from '../../hooks/rows-sort';
8
+ import { useSearchParams } from '../../hooks/search-params';
9
+ import { BundleModules as BaseComponent } from './bundle-modules';
10
+ import {
11
+ addRowFlags,
12
+ extractChunkData,
13
+ generateGetRowFilter,
14
+ generateFilters,
15
+ getCustomSort,
16
+ } from './bundle-modules.utils';
17
+ import * as types from './bundle-modules.types';
18
+
19
+ interface BundleModulesProps
20
+ extends Omit<
21
+ React.ComponentProps<typeof BaseComponent>,
22
+ | 'jobs'
23
+ | 'items'
24
+ | 'chunks'
25
+ | 'updateFilters'
26
+ | 'resetFilters'
27
+ | 'resetAllFilters'
28
+ | 'filters'
29
+ | 'sort'
30
+ | 'updateSort'
31
+ | 'search'
32
+ | 'updateSearch'
33
+ | 'entryId'
34
+ | 'allItems'
35
+ | 'totalRowCount'
36
+ | 'hideEntryInfo'
37
+ > {
38
+ jobs: Array<types.Job>;
39
+ filters: Record<string, boolean>;
40
+ search?: string;
41
+ setState: (params: any) => void;
42
+ sortBy?: string;
43
+ direction?: SortAction['direction'];
44
+ }
45
+
46
+ export const BundleModules = (props: BundleModulesProps) => {
47
+ const { jobs, filters, search, setState, sortBy, direction, ...restProps } = props;
48
+
49
+ const { chunks, chunkIds } = useMemo(() => extractChunkData(jobs), [jobs]);
50
+
51
+ const { defaultFilters, allEntriesFilters } = useMemo(
52
+ () => generateFilters(chunkIds, jobs.length > 1),
53
+ [chunkIds, jobs],
54
+ );
55
+
56
+ const searchParams = useSearchParams({
57
+ search,
58
+ filters,
59
+ defaultFilters,
60
+ allEntriesFilters,
61
+ setState,
62
+ });
63
+
64
+ const { rows, totalRowCount } = useMemo(() => {
65
+ const result = webpack.compareBySection.modules(jobs, [addRowFlags]);
66
+ return {
67
+ rows: result as Array<types.ReportMetricModuleRow>,
68
+ totalRowCount: result.length,
69
+ };
70
+ }, [jobs]);
71
+
72
+ const filteredRows = useRowsFilter({
73
+ rows,
74
+ searchPattern: searchParams.searchPattern,
75
+ filters: searchParams.filters,
76
+ getRowFilter: generateGetRowFilter({ chunkIds }),
77
+ });
78
+
79
+ const sortParams = useRowsSort({
80
+ rows: filteredRows,
81
+ initialField: sortBy,
82
+ initialDirection: direction,
83
+ getCustomSort,
84
+ });
85
+
86
+ const hideEntryInfo = useCallback(() => {
87
+ setState({ entryId: '' });
88
+ }, [setState]);
89
+
90
+ return (
91
+ <BaseComponent
92
+ jobs={jobs}
93
+ chunks={chunks}
94
+ {...restProps}
95
+ {...searchParams}
96
+ {...sortParams}
97
+ allItems={rows}
98
+ totalRowCount={totalRowCount}
99
+ hideEntryInfo={hideEntryInfo}
100
+ />
101
+ );
102
+ };
@@ -0,0 +1,3 @@
1
+ export const PATH = 'Path';
2
+ export const CLOSE_TITLE = 'Close side panel';
3
+ export const SIZE = 'Size';
@@ -9,6 +9,7 @@ import { FileName } from '../../ui/file-name';
9
9
  import { Icon } from '../../ui/icon';
10
10
  import { Table } from '../../ui/table';
11
11
  import { RunInfo } from '../run-info';
12
+ import * as I18N from './entry-info.i18n';
12
13
  import css from './entry-info.module.css';
13
14
 
14
15
  interface EntryInfoMetaLinkProps {
@@ -59,7 +60,7 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) =
59
60
  item,
60
61
  labels,
61
62
  runNameSelector = 'name',
62
- runNameLabel = 'Path',
63
+ runNameLabel = I18N.PATH,
63
64
  children,
64
65
  tags = null,
65
66
  onClose,
@@ -72,7 +73,7 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) =
72
73
  METRIC_TYPE_CONFIGS.METRIC_TYPE_FILE_SIZE,
73
74
  item.runs?.[0]?.value,
74
75
  baselineRun?.value || 0,
75
- );
76
+ ) as MetricRunInfo;
76
77
 
77
78
  return (
78
79
  <Portal className={cx(css.root, className)}>
@@ -93,7 +94,12 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) =
93
94
  size="large"
94
95
  />
95
96
  </Stack>
96
- <button type="button" onClick={onClose} className={css.headerClose}>
97
+ <button
98
+ type="button"
99
+ onClick={onClose}
100
+ aria-label={I18N.CLOSE_TITLE}
101
+ className={css.headerClose}
102
+ >
97
103
  <Icon glyph={Icon.ICONS.CLOSE} size="large" />
98
104
  </button>
99
105
  </Box>
@@ -106,7 +112,7 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) =
106
112
  <Table.Tr>
107
113
  <Table.Th className={cx(css.runsCell, css.runsColJob)}>&nbsp;</Table.Th>
108
114
  <Table.Th className={cx(css.runsCell, css.runsColName)}>{runNameLabel}</Table.Th>
109
- <Table.Th className={cx(css.runsCell, css.runsColSize)}>Size</Table.Th>
115
+ <Table.Th className={cx(css.runsCell, css.runsColSize)}>{I18N.SIZE}</Table.Th>
110
116
  </Table.Tr>
111
117
  </Table.THead>
112
118
  <Table.TBody>
@@ -4,7 +4,13 @@ import PropTypes from 'prop-types';
4
4
  import { Tooltip } from '../../ui/tooltip';
5
5
  import css from './job-name.module.css';
6
6
 
7
- export const JobName = ({ as: Component, title, internalBuildNumber, children, ...restProps }) => (
7
+ export const JobName = ({
8
+ as: Component = 'span',
9
+ title,
10
+ internalBuildNumber,
11
+ children,
12
+ ...restProps
13
+ }) => (
8
14
  <Tooltip className={css.root} as={Component} title={title} {...restProps}>
9
15
  {children || `Job #${internalBuildNumber}`}
10
16
  </Tooltip>
@@ -76,6 +76,17 @@ export const MetricRunInfo = (props: MetricRunInfoProps & React.ComponentProps<'
76
76
  return null;
77
77
  }, [showMetricDescription, metric]);
78
78
 
79
+ const deltaProps = useMemo(() => {
80
+ if (!showDelta || metric.skipDelta || !('delta' in metricRunInfo)) {
81
+ return {};
82
+ }
83
+
84
+ return {
85
+ delta: metricRunInfo.displayDeltaPercentage,
86
+ deltaType: metricRunInfo.deltaType,
87
+ };
88
+ }, [metricRunInfo]);
89
+
79
90
  return (
80
91
  <RunInfo
81
92
  title={metric.label}
@@ -86,11 +97,7 @@ export const MetricRunInfo = (props: MetricRunInfoProps & React.ComponentProps<'
86
97
  {...(showBaseline && {
87
98
  baseline: metric.formatter(baseline),
88
99
  })}
89
- {...(showDelta &&
90
- !metric.skipDelta && {
91
- delta: metricRunInfo.displayDeltaPercentage,
92
- deltaType: metricRunInfo.deltaType,
93
- })}
100
+ {...deltaProps}
94
101
  {...restProps}
95
102
  />
96
103
  );
@@ -0,0 +1,14 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { MetricsTable as BaseComponent } from './metrics-table';
4
+
5
+ type MetricsTableProps = Omit<
6
+ React.ComponentProps<typeof BaseComponent>,
7
+ 'showAllItems' | 'setShowAllItems'
8
+ >;
9
+
10
+ export const MetricsTable = (props: MetricsTableProps) => {
11
+ const [showAllItems, setShowAllItems] = useState(false);
12
+
13
+ return <BaseComponent {...props} showAllItems={showAllItems} setShowAllItems={setShowAllItems} />;
14
+ };
@@ -0,0 +1,10 @@
1
+ export const BASELINE_TITLE = 'Baseline';
2
+ export const CURRENT_TITLE = 'Current';
3
+
4
+ export const SORT_ABSOLUTE_VALUE = 'absolute value';
5
+ export const SORT_ABSOLUTE_CHANGE = 'absolute change';
6
+ export const SORT_PERCENTUAL_CHANGE = 'absolute percentual change';
7
+
8
+ export const EMPTY_MESSAGE = 'No items found.';
9
+ export const SHOW_ALL = 'Show all';
10
+ export const SHOW_LESS = 'Show less';
@@ -15,7 +15,7 @@
15
15
  width: 55%;
16
16
  }
17
17
 
18
- .root .job {
18
+ .root .jobCol {
19
19
  text-align: right;
20
20
  }
21
21
 
@@ -46,7 +46,7 @@
46
46
  }
47
47
 
48
48
  /* columns overrides for showHeaderSum */
49
- .showHeaderSum .headerRow .job {
49
+ .showHeaderSum .headerRow .jobCol {
50
50
  border-bottom: 0;
51
51
  padding-bottom: 0;
52
52
  }