From e3fc3edbaeb227ee33c5b1caed95a5a5e8465b05 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sat, 16 Nov 2024 00:55:53 +0100 Subject: [PATCH] refactor(ui): bundle-assets - convert to ts --- .../src/components/asset-info/asset-info.tsx | 18 +- ....stories.jsx => bundle-assets.stories.tsx} | 85 +++--- .../{bundle-assets.jsx => bundle-assets.tsx} | 222 ++++----------- .../bundle-assets/bundle-assets.utils.js | 178 ------------ .../bundle-assets/bundle-assets.utils.ts | 260 ++++++++++++++++++ packages/ui/src/types.d.ts | 6 +- 6 files changed, 375 insertions(+), 394 deletions(-) rename packages/ui/src/components/bundle-assets/{bundle-assets.stories.jsx => bundle-assets.stories.tsx} (62%) rename packages/ui/src/components/bundle-assets/{bundle-assets.jsx => bundle-assets.tsx} (60%) delete mode 100644 packages/ui/src/components/bundle-assets/bundle-assets.utils.js create mode 100644 packages/ui/src/components/bundle-assets/bundle-assets.utils.ts diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index db33d5e303..450a3ff0d6 100644 --- a/packages/ui/src/components/asset-info/asset-info.tsx +++ b/packages/ui/src/components/asset-info/asset-info.tsx @@ -2,15 +2,14 @@ import type { ComponentProps, ElementType, ReactNode } from 'react'; import React, { useMemo } from 'react'; import cx from 'classnames'; import noop from 'lodash/noop'; +import type { WebpackChunk } from '@bundle-stats/utils'; import { FILE_TYPE_LABELS, - MetricRunInfo, getBundleModulesByChunk, getBundleAssetsByEntryType, getBundleAssetsFileTypeComponentLink, getModuleFileType, } from '@bundle-stats/utils'; -import type { AssetMetricRun, MetaChunk } from '@bundle-stats/utils/types/webpack'; import type { ReportMetricAssetRow } from '../../types'; import { FlexStack } from '../../layout/flex-stack'; @@ -21,7 +20,7 @@ import css from './asset-info.module.css'; interface ChunkModulesLinkProps { as: ElementType; - chunks: Array; + chunks: Array; chunkId: string; name: string; } @@ -74,17 +73,8 @@ const AssetRunName = (props: AssetRunNameProps) => { }; interface AssetInfoProps { - item: { - label: string; - changed?: boolean; - isChunk?: boolean; - isEntry?: boolean; - isInitial?: boolean; - isNotPredictive?: boolean; - fileType?: string; - runs: Array; - }; - chunks?: Array; + item: ReportMetricAssetRow; + chunks?: Array; labels: Array; customComponentLink?: ElementType; onClose: () => void; diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.stories.jsx b/packages/ui/src/components/bundle-assets/bundle-assets.stories.tsx similarity index 62% rename from packages/ui/src/components/bundle-assets/bundle-assets.stories.jsx rename to packages/ui/src/components/bundle-assets/bundle-assets.stories.tsx index 9ca6156131..7aa28c8232 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.stories.jsx +++ b/packages/ui/src/components/bundle-assets/bundle-assets.stories.tsx @@ -8,6 +8,8 @@ import { } from '@bundle-stats/utils'; import merge from 'lodash/merge'; import set from 'lodash/set'; +import type { Meta, StoryObj } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; /* eslint-disable import/no-relative-packages */ import baselineStats from '../../../../../fixtures/webpack-stats.baseline.json'; @@ -20,52 +22,66 @@ const JOBS = createJobs([{ webpack: currentStats }, { webpack: baselineStats }]) const [currentJob, baselineJob] = JOBS; const setState = (params) => console.info(params); -export default { +const meta: Meta = { title: 'Components/BundleAssets', component: BundleAssets, decorators: [getWrapperDecorator()], + args: { + setState: action('STATE'), + }, }; -export const Default = () => ; +export default meta; -export const MultipleJobs = () => ; +type Story = StoryObj; -export const CustomFilters = () => ( - -); - -const JOBS_EMPTY_BASELINE = createJobs([{ webpack: currentStats }, {}]); + }, + }, +}; -export const EmptyBaseline = () => ; +export const EmptyBaseline: Story = { + args: { + jobs: createJobs([{ webpack: currentStats }, {}]), + }, +}; -export const NoAssets = () => ( - set(merge({}, job), 'metrics.webpack.assets', {}))} - setState={setState} - /> -); +export const NoAssets: Story = { + args: { + jobs: JOBS.map((job) => set(merge({}, job), 'metrics.webpack.assets', {})), + }, +}; -export const EmptyFilteredData = () => ( - -); + ], + search: 'vendors', + }, +}; -export const NotPredictive = () => ( - ( value: 2988, }, }), - ]} - setState={setState} - /> -); + ], + }, +}; diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.jsx b/packages/ui/src/components/bundle-assets/bundle-assets.tsx similarity index 60% rename from packages/ui/src/components/bundle-assets/bundle-assets.jsx rename to packages/ui/src/components/bundle-assets/bundle-assets.tsx index f24081bee0..d916415ecf 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.jsx +++ b/packages/ui/src/components/bundle-assets/bundle-assets.tsx @@ -1,20 +1,10 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import PropTypes from 'prop-types'; +import type { ElementType, ReactNode } from 'react'; +import React, { ComponentProps, useCallback, useMemo, useState } from 'react'; import cx from 'classnames'; -import get from 'lodash/get'; -import isEmpty from 'lodash/isEmpty'; -import orderBy from 'lodash/orderBy'; import escapeRegExp from 'lodash/escapeRegExp'; -import { - ASSET_CHUNK, - ASSET_ENTRY_TYPE, - ASSET_FILE_TYPE, - ASSET_FILTERS, - COMPONENT, - FILE_TYPE_LABELS, - SECTIONS, -} from '@bundle-stats/utils'; +import { COMPONENT, ReportMetricRow, SECTIONS, WebpackChunk } from '@bundle-stats/utils'; +import type { ReportMetricAssetRow, SortAction } from '../../types'; import config from '../../config.json'; import I18N from '../../i18n'; import { MetricsDisplayType } from '../../constants'; @@ -29,6 +19,7 @@ import { Filters } from '../../ui/filters'; import { EmptySet } from '../../ui/empty-set'; import { Toolbar } from '../../ui/toolbar'; import { AssetInfo } from '../asset-info'; +import { AssetName } from '../asset-name'; import { ComponentLink } from '../component-link'; import { MetricsTable } from '../metrics-table'; import { MetricsTableExport } from '../metrics-table-export'; @@ -38,79 +29,25 @@ import { MetricsDisplaySelector } from '../metrics-display-selector'; import { MetricsTableHeader } from '../metrics-table-header'; import { MetricsTreemap, getTreemapNodes, getTreemapNodesGroupedByPath } from '../metrics-treemap'; import { SEARCH_PLACEHOLDER } from './bundle-assets.i18n'; +import { getFilters } from './bundle-assets.utils'; import css from './bundle-assets.module.css'; -import { AssetName } from '../asset-name'; const DISPLAY_TYPE_GROUPS = { [MetricsDisplayType.TREEMAP]: ['folder'], }; -const getFileTypeFilters = (filters) => - Object.entries(FILE_TYPE_LABELS).map(([key, label]) => ({ - key, - label, - defaultValue: get(filters, `${ASSET_FILE_TYPE}.${key}`, true), -})); - -const getFilters = ({ compareMode, filters, chunks }) => { - const result = { - [ASSET_FILTERS.CHANGED]: { - label: 'Changed', - defaultValue: filters[ASSET_FILTERS.CHANGED], - disabled: !compareMode, - }, - }; - - if (!isEmpty(chunks)) { - const chunksFilter = { label: 'Chunks', children: [] }; - const chunksOrderedByName = orderBy(chunks, 'name'); - - chunksOrderedByName.forEach((chunk) => { - chunksFilter.children.push({ - key: chunk.id, - label: chunk.name, - defaultValue: filters[`${ASSET_CHUNK}.${chunk.id}`] ?? true, - }); - }); - - result[ASSET_CHUNK] = chunksFilter; - } - - result[ASSET_ENTRY_TYPE] = { - label: 'Type', - children: [ - { - key: ASSET_FILTERS.ENTRY, - label: 'Entry', - defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.ENTRY}`, true), - }, - { - key: ASSET_FILTERS.INITIAL, - label: 'Initial', - defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.INITIAL}`, true), - }, - { - key: ASSET_FILTERS.CHUNK, - label: 'Chunk', - defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.CHUNK}`, true), - }, - { - key: ASSET_FILTERS.OTHER, - label: 'Other', - defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.OTHER}`, true), - }, - ], - }; - - result[ASSET_FILE_TYPE] = { - label: 'File type', - children: getFileTypeFilters(filters), - }; - - return result; -}; - -const ViewMetricsTreemap = (props) => { +interface ViewMetricsTreemapProps { + metricsTableTitle: ReactNode; + jobs: Array; + items: Array; + displayType: { value: MetricsDisplayType; groupBy?: string }; + emptyMessage: ReactNode; + showEntryInfo: (entryId: string) => void; + updateSearch: (search: string) => void; + search: string; +} + +const ViewMetricsTreemap = (props: ViewMetricsTreemapProps) => { const { metricsTableTitle, jobs, @@ -133,7 +70,7 @@ const ViewMetricsTreemap = (props) => { // Search based on the group path on group title click const onGroupClick = useCallback( - (groupPath) => { + (groupPath: string) => { // Clear seach when groupPath is emty (root) if (groupPath === '') { updateSearch(''); @@ -171,22 +108,29 @@ const ViewMetricsTreemap = (props) => { ); }; -ViewMetricsTreemap.propTypes = { - jobs: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types - items: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types - metricsTableTitle: PropTypes.node.isRequired, - displayType: PropTypes.shape({ - groupBy: PropTypes.string, - }).isRequired, - emptyMessage: PropTypes.node.isRequired, - showEntryInfo: PropTypes.func.isRequired, - updateSearch: PropTypes.func.isRequired, - search: PropTypes.string.isRequired, -}; - -export const BundleAssets = (props) => { +interface BundleAssetsProps extends ComponentProps { + jobs: Array; + chunks: Array; + items: Array; + allItems: Array; + filters: Record; + updateFilters: (newFilters: any) => void; + resetFilters: () => void; + resetAllFilters: () => void; + totalRowCount?: number; + entryId?: string; + sort: SortAction; + updateSort: () => void; + search: string; + updateSearch: (search: string) => void; + customComponentLink?: ElementType; + hideEntryInfo: () => void; + showEntryInfo: (entryId: string) => void; +} + +export const BundleAssets = (props: BundleAssetsProps) => { const { - className, + className = '', jobs, chunks, items, @@ -194,15 +138,14 @@ export const BundleAssets = (props) => { updateFilters, resetFilters, resetAllFilters, - totalRowCount, + totalRowCount = 0, filters, - entryId, - hasActiveFilters, + entryId = '', sort, updateSort, search, updateSearch, - customComponentLink: CustomComponentLink, + customComponentLink: CustomComponentLink = ComponentLink, hideEntryInfo, showEntryInfo, } = props; @@ -229,7 +172,10 @@ export const BundleAssets = (props) => { ); const EntryComponentLink = useCallback( - ({ entryId: assetEntryId, ...assetNameRestProps }) => ( + ({ + entryId: assetEntryId, + ...assetNameRestProps + }: { entryId: string } & ComponentProps) => ( { ); const renderRowHeader = useCallback( - (row) => ( - + (row: ReportMetricRow) => ( + ), [EntryComponentLink], ); @@ -275,9 +225,9 @@ export const BundleAssets = (props) => { }, [allItems, entryId]); const exportDialog = useDialogState(); - const [exportSourceType, setExportSourceType] = useState(undefined); + const [exportSourceType, setExportSourceType] = useState(); - const handleExportClick = useCallback((sourceType) => { + const handleExportClick = useCallback((sourceType: string) => { setExportSourceType(sourceType); exportDialog.toggle(); }, []); @@ -312,7 +262,6 @@ export const BundleAssets = (props) => { @@ -368,62 +317,3 @@ export const BundleAssets = (props) => { ); }; - -BundleAssets.defaultProps = { - className: '', - totalRowCount: 0, - hasActiveFilters: false, - customComponentLink: ComponentLink, - entryId: '', -}; - -BundleAssets.propTypes = { - className: PropTypes.string, - jobs: PropTypes.arrayOf( - PropTypes.shape({ - internalBuildNumber: PropTypes.number, - label: PropTypes.string, - }), - ).isRequired, - chunks: PropTypes.arrayOf({ - id: PropTypes.string, - name: PropTypes.string, - }).isRequired, - items: PropTypes.arrayOf( - PropTypes.shape({ - key: PropTypes.string, - label: PropTypes.string, - runs: PropTypes.arrayOf( - PropTypes.shape({ - displayValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - displayDelta: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - }), - ), - }), - ).isRequired, - updateFilters: PropTypes.func.isRequired, - resetFilters: PropTypes.func.isRequired, - resetAllFilters: PropTypes.func.isRequired, - totalRowCount: PropTypes.number, - filters: PropTypes.shape({ - changed: PropTypes.bool, - id: PropTypes.string, - }).isRequired, - entryId: PropTypes.string, - hasActiveFilters: PropTypes.bool, - search: PropTypes.string.isRequired, - updateSearch: PropTypes.func.isRequired, - sort: PropTypes.shape({ - field: PropTypes.string, - direction: PropTypes.string, - }).isRequired, - updateSort: PropTypes.func.isRequired, - allItems: PropTypes.arrayOf( - PropTypes.shape({ - key: PropTypes.string, - }), - ).isRequired, - customComponentLink: PropTypes.elementType, - hideEntryInfo: PropTypes.func.isRequired, - showEntryInfo: PropTypes.func.isRequired, -}; diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js deleted file mode 100644 index bee9b9fb6f..0000000000 --- a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * @type {import('@bundle-stats/utils').ReportMetricRow} ReportMetricRow - * @type {import('../../types').ReportMetricAssetRow} ReportMetricAssetRow - * @type {import('../../types').ReportMetricAssetRowMetaStatus} ReportMetricAssetRowFlagStatus - */ - -import intersection from 'lodash/intersection'; -import { - ASSET_CHUNK, - ASSET_ENTRY_TYPE, - ASSET_FILE_TYPE, - ASSET_FILTERS, - getFileType, -} from '@bundle-stats/utils'; - -/** - * Check if the asset cache is not predictive - * - * @param {ReportMetricRow} row - * @returns {boolean} - */ -export const getIsNotPredictive = (row) => { - const { key, runs } = row; - - return runs.reduce((agg, current, index) => { - if (agg) { - return agg; - } - - if (index + 1 === runs.length) { - return agg; - } - - if ( - current && - runs[index + 1] && - key !== current.name && - current.name === runs[index + 1].name && - current.value !== runs[index + 1].value - ) { - return true; - } - - return agg; - }, false); -}; - -/** - * @param {Array} - * @returns {ReportMetricAssetRowFlagStatus | boolean} - */ -export const getAssetMetaStatus = (values) => { - if (!values.includes(true)) { - return false; - } - - // filter empty runs - const metaValues = values.filter((value) => typeof value !== 'undefined'); - - const current = metaValues[0]; - const metaValuesLength = metaValues.length; - - if (metaValuesLength === 1) { - return Boolean(current); - } - - const baseline = metaValues[metaValuesLength - 1]; - - if (current && !baseline) { - return 'added'; - } - - if (!current && baseline) { - return 'removed'; - } - - return true; -}; - -/** - * Add asset row flags - * - * @param {ReportMetricRow} row - * @returns {ReportMetricAssetRow} - */ -export const addMetricReportAssetRowData = (row) => { - const { changed, runs } = row; - - // Collect meta for each run - const runsEntry = []; - const runsInitial = []; - const runsChunk = []; - - runs.forEach((run) => { - runsEntry.push(run?.isEntry); - runsInitial.push(run?.isInitial); - runsChunk.push(run?.isChunk); - }); - - const isEntry = getAssetMetaStatus(runsEntry); - const isInitial = getAssetMetaStatus(runsInitial); - const isChunk = getAssetMetaStatus(runsChunk); - const isAsset = !(isEntry || isInitial || isChunk); - const isNotPredictive = getIsNotPredictive(row); - const fileType = getFileType(row.key); - - // Flag asset as changed if name and value are identical, if one of the meta tags is changed - const assetChanged = - changed || - typeof isEntry !== 'boolean' || - typeof isInitial !== 'boolean' || - typeof isChunk !== 'boolean'; - - return { - ...row, - changed: assetChanged, - isEntry, - isInitial, - isChunk, - isAsset, - isNotPredictive, - fileType, - }; -}; - -/* eslint-disable prettier/prettier */ -export const generateGetRowFilter = - ({ chunkIds }) => (filters) => { - // List of chunkIds with filter value set to `true` - const checkedChunkIds = []; - - chunkIds.forEach((chunkId) => { - if (filters[`${ASSET_CHUNK}.${chunkId}`]) { - checkedChunkIds.push(chunkId); - } - }); - - const hasChunkFilters = - checkedChunkIds.length > 0 && checkedChunkIds.length !== chunkIds.length; - - return (item) => { - if (filters[ASSET_FILTERS.CHANGED] && !item.changed) { - return false; - } - - if ( - !( - (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.ENTRY}`] && item.isEntry) || - (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.INITIAL}`] && item.isInitial) || - (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.CHUNK}`] && item.isChunk) || - (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.OTHER}`] && item.isAsset) - ) - ) { - return false; - } - - if (!filters[`${ASSET_FILE_TYPE}.${item.fileType}`]) { - return false; - } - - // Filter if any of the chunkIds are checked - if (hasChunkFilters) { - const rowRunsChunkIds = item?.runs?.map((run) => run?.chunkId) || []; - return intersection(rowRunsChunkIds, checkedChunkIds).length > 0; - } - - return true; - }; - }; - -export const getCustomSort = (item) => [ - !item.isNotPredictive, - !item.changed, - !item.isInitial, - !item.isEntry, - !item.isChunk, - item.key, -]; diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.utils.ts b/packages/ui/src/components/bundle-assets/bundle-assets.utils.ts new file mode 100644 index 0000000000..147ec4ec00 --- /dev/null +++ b/packages/ui/src/components/bundle-assets/bundle-assets.utils.ts @@ -0,0 +1,260 @@ +import get from 'lodash/get'; +import intersection from 'lodash/intersection'; +import isEmpty from 'lodash/isEmpty'; +import orderBy from 'lodash/orderBy'; +import type { ReportMetricRow, WebpackChunk } from '@bundle-stats/utils'; +import type { AssetMetricRun } from '@bundle-stats/utils/types/webpack'; +import { + ASSET_CHUNK, + ASSET_ENTRY_TYPE, + ASSET_FILE_TYPE, + ASSET_FILTERS, + FILE_TYPE_LABELS, + getFileType, +} from '@bundle-stats/utils'; +import type { + FilterFieldsData, + ReportMetricAssetRow, + ReportMetricAssetRowMetaStatus, + FilterGroupFieldData, +} from '../../types'; + +/** + * Check if the asset cache is not predictive + */ +export const getIsNotPredictive = (row: ReportMetricRow): boolean => { + const { key, runs } = row; + + return runs.reduce((agg, run, index) => { + if (agg) { + return agg; + } + + if (index + 1 === runs.length) { + return agg; + } + + const preRun = runs[index + 1]; + + if ( + run && + preRun && + key !== run.name && + run.name === preRun.name && + run.value !== preRun.value + ) { + return true; + } + + return agg; + }, false); +}; + +/** + * Return asset meta status + */ +export const getAssetMetaStatus = ( + values: Array, +): ReportMetricAssetRowMetaStatus | boolean => { + if (!values.includes(true)) { + return false; + } + + // filter empty runs + const metaValues = values.filter((value) => typeof value !== 'undefined'); + + const current = metaValues[0]; + const metaValuesLength = metaValues.length; + + if (metaValuesLength === 1) { + return Boolean(current); + } + + const baseline = metaValues[metaValuesLength - 1]; + + if (current && !baseline) { + return 'added'; + } + + if (!current && baseline) { + return 'removed'; + } + + return true; +}; + +/** + * Add asset row flags + */ +export const addMetricReportAssetRowData = (row: ReportMetricRow): ReportMetricAssetRow => { + const { changed, runs } = row; + + // Collect meta for each run + const runsEntry: Array = []; + const runsInitial: Array = []; + const runsChunk: Array = []; + + (runs as Array).forEach((run) => { + runsEntry.push(run?.isEntry || false); + runsInitial.push(run?.isInitial || false); + runsChunk.push(run?.isChunk || false); + }); + + const isEntry = getAssetMetaStatus(runsEntry); + const isInitial = getAssetMetaStatus(runsInitial); + const isChunk = getAssetMetaStatus(runsChunk); + const isAsset = !(isEntry || isInitial || isChunk); + const isNotPredictive = getIsNotPredictive(row); + const fileType = getFileType(row.key); + + // Flag asset as changed if name and value are identical, if one of the meta tags is changed + const assetChanged = + changed || + typeof isEntry !== 'boolean' || + typeof isInitial !== 'boolean' || + typeof isChunk !== 'boolean'; + + return { + ...row, + changed: assetChanged, + isEntry, + isInitial, + isChunk, + isAsset, + isNotPredictive, + fileType, + }; +}; + +type GenerateGetRowFilterOptions = { + chunkIds: Array; +}; + +export const getCustomSort = (item: ReportMetricAssetRow): Array => [ + !item.isNotPredictive, + !item.changed, + !item.isInitial, + !item.isEntry, + !item.isChunk, + item.key, +]; + +const getFileTypeFilters = (filters: Record): FilterGroupFieldData['children'] => + Object.entries(FILE_TYPE_LABELS).map(([key, label]) => ({ + key, + label, + defaultValue: get(filters, `${ASSET_FILE_TYPE}.${key}`, true) as boolean, +})); + +type GetFiltersOptions = { + compareMode: boolean; + filters: Record; + chunks: Array; +}; + +export const getFilters = ({ + compareMode, + filters, + chunks, +}: GetFiltersOptions): FilterFieldsData => { + const result: FilterFieldsData = { + [ASSET_FILTERS.CHANGED]: { + label: 'Changed', + defaultValue: filters[ASSET_FILTERS.CHANGED], + disabled: !compareMode, + }, + }; + + if (!isEmpty(chunks)) { + const chunksFilter: FilterGroupFieldData = { label: 'Chunks', children: [] }; + const chunksOrderedByName = orderBy(chunks, 'name'); + + chunksOrderedByName.forEach((chunk) => { + chunksFilter.children.push({ + key: chunk.id, + label: chunk.name, + defaultValue: filters[`${ASSET_CHUNK}.${chunk.id}`] ?? true, + }); + }); + + result[ASSET_CHUNK] = chunksFilter; + } + + result[ASSET_ENTRY_TYPE] = { + label: 'Type', + children: [ + { + key: ASSET_FILTERS.ENTRY, + label: 'Entry', + defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.ENTRY}`, true), + }, + { + key: ASSET_FILTERS.INITIAL, + label: 'Initial', + defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.INITIAL}`, true), + }, + { + key: ASSET_FILTERS.CHUNK, + label: 'Chunk', + defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.CHUNK}`, true), + }, + { + key: ASSET_FILTERS.OTHER, + label: 'Other', + defaultValue: get(filters, `${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.OTHER}`, true), + }, + ], + }; + + result[ASSET_FILE_TYPE] = { + label: 'File type', + children: getFileTypeFilters(filters), + }; + + return result; +}; + +/* eslint-disable prettier/prettier */ +export const generateGetRowFilter = + ({ chunkIds }: GenerateGetRowFilterOptions) => (filters: Record) => { + // List of chunkIds with filter value set to `true` + const checkedChunkIds: Array = []; + + chunkIds.forEach((chunkId) => { + if (filters[`${ASSET_CHUNK}.${chunkId}`]) { + checkedChunkIds.push(chunkId); + } + }); + + const hasChunkFilters = + checkedChunkIds.length > 0 && checkedChunkIds.length !== chunkIds.length; + + return (item: ReportMetricAssetRow) => { + if (filters[ASSET_FILTERS.CHANGED] && !item.changed) { + return false; + } + + if ( + !( + (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.ENTRY}`] && item.isEntry) || + (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.INITIAL}`] && item.isInitial) || + (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.CHUNK}`] && item.isChunk) || + (filters[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.OTHER}`] && item.isAsset) + ) + ) { + return false; + } + + if (!filters[`${ASSET_FILE_TYPE}.${item.fileType}`]) { + return false; + } + + // Filter if any of the chunkIds are checked + if (hasChunkFilters) { + const rowRunsChunkIds = item?.runs?.map((run) => run?.chunkId) || []; + return intersection(rowRunsChunkIds, checkedChunkIds).length > 0; + } + + return true; + }; + }; diff --git a/packages/ui/src/types.d.ts b/packages/ui/src/types.d.ts index 2a1b3a644f..474db350ef 100644 --- a/packages/ui/src/types.d.ts +++ b/packages/ui/src/types.d.ts @@ -12,9 +12,13 @@ interface FilterFieldData { disabled?: boolean; } +interface ChildFilterFieldData extends FilterFieldData { + key: string; +} + type FilterGroupFieldData = { label: string; - children: Array<{ key: string } & FilterFieldData>; + children: Array; }; type FilterFieldsData = Record;