@bundle-stats/ui 4.8.3 → 4.8.4
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.
- package/lib/components/bundle-assets/bundle-assets.js +28 -27
- package/lib/components/bundle-assets/bundle-assets.js.map +1 -1
- package/lib/components/bundle-modules/bundle-modules.i18n.js +12 -0
- package/lib/components/bundle-modules/bundle-modules.i18n.js.map +1 -0
- package/lib/components/bundle-modules/bundle-modules.js +12 -99
- package/lib/components/bundle-modules/bundle-modules.js.map +1 -1
- package/lib/components/bundle-modules/bundle-modules.utils.js +108 -1
- package/lib/components/bundle-modules/bundle-modules.utils.js.map +1 -1
- package/lib/components/bundle-modules/index.js +6 -45
- package/lib/components/bundle-modules/index.js.map +1 -1
- package/lib/components/entry-info/entry-info.i18n.js +7 -0
- package/lib/components/entry-info/entry-info.i18n.js.map +1 -0
- package/lib/components/entry-info/entry-info.js +27 -3
- package/lib/components/entry-info/entry-info.js.map +1 -1
- package/lib/components/job-name/job-name.js +1 -1
- package/lib/components/job-name/job-name.js.map +1 -1
- package/lib/components/metric-run-info/metric-run-info.js +10 -5
- package/lib/components/metric-run-info/metric-run-info.js.map +1 -1
- package/lib/components/metrics-table/index.js +1 -1
- package/lib/components/metrics-table/index.js.map +1 -1
- package/lib/components/metrics-table/metrics-table.i18n.js +12 -0
- package/lib/components/metrics-table/metrics-table.i18n.js.map +1 -0
- package/lib/components/metrics-table/metrics-table.js +50 -69
- package/lib/components/metrics-table/metrics-table.js.map +1 -1
- package/lib/components/metrics-table/metrics-table.module.css +2 -2
- package/lib/components/sort-button/sort-button.js +10 -4
- package/lib/components/sort-button/sort-button.js.map +1 -1
- package/lib/components/sort-button/sort-button.module.css +9 -9
- package/lib/hooks/rows-sort.js +1 -1
- package/lib/hooks/rows-sort.js.map +1 -1
- package/lib/ui/filters/filters.i18n.js +6 -0
- package/lib/ui/filters/filters.i18n.js.map +1 -0
- package/lib/ui/filters/filters.js +16 -54
- package/lib/ui/filters/filters.js.map +1 -1
- package/lib/ui/filters/filters.utils.js +27 -11
- package/lib/ui/filters/filters.utils.js.map +1 -1
- package/lib/ui/filters/index.js +0 -8
- package/lib/ui/filters/index.js.map +1 -1
- package/lib-esm/components/bundle-assets/bundle-assets.js +28 -27
- package/lib-esm/components/bundle-assets/bundle-assets.js.map +1 -1
- package/lib-esm/components/bundle-modules/bundle-modules.i18n.js +9 -0
- package/lib-esm/components/bundle-modules/bundle-modules.i18n.js.map +1 -0
- package/lib-esm/components/bundle-modules/bundle-modules.js +13 -101
- package/lib-esm/components/bundle-modules/bundle-modules.js.map +1 -1
- package/lib-esm/components/bundle-modules/bundle-modules.utils.js +83 -1
- package/lib-esm/components/bundle-modules/bundle-modules.utils.js.map +1 -1
- package/lib-esm/components/bundle-modules/index.js +9 -45
- package/lib-esm/components/bundle-modules/index.js.map +1 -1
- package/lib-esm/components/entry-info/entry-info.i18n.js +4 -0
- package/lib-esm/components/entry-info/entry-info.i18n.js.map +1 -0
- package/lib-esm/components/entry-info/entry-info.js +4 -3
- package/lib-esm/components/entry-info/entry-info.js.map +1 -1
- package/lib-esm/components/job-name/job-name.js +1 -1
- package/lib-esm/components/job-name/job-name.js.map +1 -1
- package/lib-esm/components/metric-run-info/metric-run-info.js +10 -5
- package/lib-esm/components/metric-run-info/metric-run-info.js.map +1 -1
- package/lib-esm/components/metrics-table/index.js +1 -1
- package/lib-esm/components/metrics-table/index.js.map +1 -1
- package/lib-esm/components/metrics-table/metrics-table.i18n.js +9 -0
- package/lib-esm/components/metrics-table/metrics-table.i18n.js.map +1 -0
- package/lib-esm/components/metrics-table/metrics-table.js +37 -79
- package/lib-esm/components/metrics-table/metrics-table.js.map +1 -1
- package/lib-esm/components/metrics-table/metrics-table.module.css +2 -2
- package/lib-esm/components/sort-button/sort-button.js +10 -4
- package/lib-esm/components/sort-button/sort-button.js.map +1 -1
- package/lib-esm/components/sort-button/sort-button.module.css +9 -9
- package/lib-esm/hooks/rows-sort.js +1 -1
- package/lib-esm/hooks/rows-sort.js.map +1 -1
- package/lib-esm/ui/filters/filters.i18n.js +3 -0
- package/lib-esm/ui/filters/filters.i18n.js.map +1 -0
- package/lib-esm/ui/filters/filters.js +16 -54
- package/lib-esm/ui/filters/filters.js.map +1 -1
- package/lib-esm/ui/filters/filters.utils.js +27 -11
- package/lib-esm/ui/filters/filters.utils.js.map +1 -1
- package/lib-esm/ui/filters/index.js +0 -8
- package/lib-esm/ui/filters/index.js.map +1 -1
- package/package.json +10 -10
- package/src/components/bundle-assets/bundle-assets.jsx +28 -30
- package/src/components/bundle-modules/bundle-modules.i18n.ts +8 -0
- package/src/components/bundle-modules/bundle-modules.tsx +200 -0
- package/src/components/bundle-modules/bundle-modules.types.d.ts +24 -0
- package/src/components/bundle-modules/bundle-modules.utils.ts +208 -0
- package/src/components/bundle-modules/index.tsx +102 -0
- package/src/components/entry-info/entry-info.i18n.ts +3 -0
- package/src/components/entry-info/entry-info.tsx +10 -4
- package/src/components/job-name/job-name.jsx +7 -1
- package/src/components/metric-run-info/metric-run-info.tsx +12 -5
- package/src/components/metrics-table/index.tsx +14 -0
- package/src/components/metrics-table/metrics-table.i18n.ts +10 -0
- package/src/components/metrics-table/metrics-table.module.css +2 -2
- package/src/components/metrics-table/metrics-table.stories.tsx +225 -0
- package/src/components/metrics-table/metrics-table.tsx +301 -0
- package/src/components/sort-button/sort-button.module.css +9 -9
- package/src/components/sort-button/sort-button.tsx +14 -6
- package/src/hooks/rows-sort.ts +9 -9
- package/src/types.d.ts +11 -0
- package/src/ui/filters/__tests__/filters.utils.ts +98 -0
- package/src/ui/filters/filters.i18n.ts +2 -0
- package/src/ui/filters/{filters.jsx → filters.tsx} +63 -72
- package/src/ui/filters/{filters.utils.js → filters.utils.ts} +38 -21
- package/src/ui/filters/{index.jsx → index.tsx} +14 -17
- package/types/components/bundle-modules/bundle-modules.d.ts +21 -62
- package/types/components/bundle-modules/bundle-modules.i18n.d.ts +8 -0
- package/types/components/bundle-modules/bundle-modules.utils.d.ts +28 -5
- package/types/components/bundle-modules/index.d.ts +13 -22
- package/types/components/entry-info/entry-info.i18n.d.ts +3 -0
- package/types/components/job-name/job-name.d.ts +1 -1
- package/types/components/metrics-table/index.d.ts +4 -1
- package/types/components/metrics-table/metrics-table.d.ts +25 -47
- package/types/components/metrics-table/metrics-table.i18n.d.ts +8 -0
- package/types/components/sort-button/sort-button.d.ts +2 -2
- package/types/hooks/rows-sort.d.ts +4 -4
- package/types/layout/box/box.d.ts +1 -1
- package/types/layout/flex-stack/flex-stack.d.ts +1 -1
- package/types/ui/filters/filters.d.ts +24 -14
- package/types/ui/filters/filters.i18n.d.ts +2 -0
- package/types/ui/filters/filters.utils.d.ts +7 -6
- package/types/ui/filters/index.d.ts +8 -12
- package/types/ui/table/table.d.ts +5 -5
- package/types/ui/tooltip/tooltip.d.ts +1 -1
- package/src/components/bundle-modules/bundle-modules.jsx +0 -291
- package/src/components/bundle-modules/bundle-modules.utils.js +0 -82
- package/src/components/bundle-modules/index.jsx +0 -113
- package/src/components/metrics-table/index.jsx +0 -11
- package/src/components/metrics-table/metrics-table.jsx +0 -309
- 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
|
+
};
|
|
@@ -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 =
|
|
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
|
|
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)}> </Table.Th>
|
|
108
114
|
<Table.Th className={cx(css.runsCell, css.runsColName)}>{runNameLabel}</Table.Th>
|
|
109
|
-
<Table.Th className={cx(css.runsCell, css.runsColSize)}>
|
|
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 = ({
|
|
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
|
-
{...
|
|
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_VALUE = 'value';
|
|
5
|
+
export const SORT_CHANGE = 'change';
|
|
6
|
+
export const SORT_PERCENTUAL_CHANGE = '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 .
|
|
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 .
|
|
49
|
+
.showHeaderSum .headerRow .jobCol {
|
|
50
50
|
border-bottom: 0;
|
|
51
51
|
padding-bottom: 0;
|
|
52
52
|
}
|