diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.jsx b/packages/ui/src/components/bundle-assets/bundle-assets.jsx index 5ca8b633e6..66aacc8cf4 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.jsx +++ b/packages/ui/src/components/bundle-assets/bundle-assets.jsx @@ -49,10 +49,10 @@ const DISPLAY_TYPE_GROUPS = { const getFileTypeFilters = (filters) => Object.entries(FILE_TYPE_LABELS).map(([key, label]) => ({ - key, - label, - defaultValue: get(filters, `${ASSET_FILE_TYPE}.${key}`, true), -})); + key, + label, + defaultValue: get(filters, `${ASSET_FILE_TYPE}.${key}`, true), + })); const getFilters = ({ compareMode, filters }) => ({ [ASSET_FILTERS.CHANGED]: { @@ -163,9 +163,18 @@ RowHeader.defaultProps = { }; const ViewMetricsTreemap = (props) => { - const { metricsTableTitle, jobs, items, displayType, emptyMessage, showEntryInfo, updateSearch } = - props; + const { + metricsTableTitle, + jobs, + items, + displayType, + emptyMessage, + showEntryInfo, + updateSearch, + search, + } = props; + // Get treenodes based on group const treeNodes = useMemo(() => { if (displayType.groupBy === 'folder') { return getTreemapNodesGroupedByPath(items); @@ -174,19 +183,28 @@ const ViewMetricsTreemap = (props) => { return getTreemapNodes(items); }, [items, displayType.groupBy]); + // Search based on the group path on group title click const onGroupClick = useCallback( (groupPath) => { + // Clear seach when groupPath is emty (root) + if (groupPath === '') { + updateSearch(''); + return; + } // Search by group path // 1. use `^` to match only the string beggining - // 2. add `/` suffix to match only exact directories - // 3. if the group path is empty(root), clear search - if (groupPath) { - updateSearch(`^${groupPath}/`); - } else { + // 2. add `/` suffix to exactly match the directory + const newSearch = `^${groupPath}/`; + + // Reset search when toggling the same groupPath + if (newSearch === search) { updateSearch(''); + return; } + + updateSearch(`^${groupPath}/`); }, - [updateSearch], + [updateSearch, search], ); return ( @@ -215,6 +233,7 @@ ViewMetricsTreemap.propTypes = { emptyMessage: PropTypes.node.isRequired, showEntryInfo: PropTypes.func.isRequired, updateSearch: PropTypes.func.isRequired, + search: PropTypes.string.isRequired, }; export const BundleAssets = (props) => { @@ -348,6 +367,7 @@ export const BundleAssets = (props) => { emptyMessage={emptyMessage} showEntryInfo={showEntryInfo} updateSearch={updateSearch} + search={search} /> )} diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.tsx b/packages/ui/src/components/bundle-modules/bundle-modules.tsx index 2c168cb566..81e4dc0796 100644 --- a/packages/ui/src/components/bundle-modules/bundle-modules.tsx +++ b/packages/ui/src/components/bundle-modules/bundle-modules.tsx @@ -74,12 +74,22 @@ interface ViewMetricsTreemapProps { emptyMessage: React.ReactNode; showEntryInfo: React.ComponentProps['onItemClick']; updateSearch: (newSerarch: string) => void; + search: string; } const ViewMetricsTreemap = (props: ViewMetricsTreemapProps) => { - const { metricsTableTitle, jobs, items, displayType, emptyMessage, showEntryInfo, updateSearch } = - props; + const { + metricsTableTitle, + jobs, + items, + displayType, + emptyMessage, + showEntryInfo, + updateSearch, + search, + } = props; + // Get treenodes based on group const treeNodes = useMemo(() => { if (displayType.groupBy === 'folder') { return getTreemapNodesGroupedByPath(items); @@ -88,19 +98,28 @@ const ViewMetricsTreemap = (props: ViewMetricsTreemapProps) => { return getTreemapNodes(items); }, [items, displayType]); + // Search based on the group path on group title click const onGroupClick = useCallback( (groupPath: string) => { + // Clear seach when groupPath is emty (root) + if (groupPath === '') { + updateSearch(''); + return; + } // Search by group path // 1. use `^` to match only the string beggining - // 2. add `/` suffix to match only exact directories - // 3. if the group path is empty(root), clear search - if (groupPath) { - updateSearch(`^${groupPath}/`); - } else { + // 2. add `/` suffix to exactly match the directory + const newSearch = `^${groupPath}/`; + + // Reset search when toggling the same groupPath + if (newSearch === search) { updateSearch(''); + return; } + + updateSearch(newSearch); }, - [updateSearch], + [updateSearch, search], ); return ( @@ -306,6 +325,7 @@ export const BundleModules = (props: BundleModulesProps) => { emptyMessage={emptyMessage} showEntryInfo={showEntryInfo} updateSearch={updateSearch} + search={search} /> )} diff --git a/packages/ui/src/components/bundle-packages/bundle-packages.jsx b/packages/ui/src/components/bundle-packages/bundle-packages.jsx index f5e8e30e3b..7d2b80c2a0 100644 --- a/packages/ui/src/components/bundle-packages/bundle-packages.jsx +++ b/packages/ui/src/components/bundle-packages/bundle-packages.jsx @@ -120,8 +120,9 @@ RowHeader.propTypes = { }; const ViewMetricsTreemap = (props) => { - const { metricsTableTitle, jobs, items, displayType, emptyMessage, showEntryInfo, updateSearch } = props; + const { metricsTableTitle, jobs, items, displayType, emptyMessage, showEntryInfo, updateSearch, search } = props; + // Get treenodes based on group const treeNodes = useMemo(() => { if (displayType.groupBy === 'folder') { return getTreemapNodesGroupedByPath(items); @@ -130,19 +131,28 @@ const ViewMetricsTreemap = (props) => { return getTreemapNodes(items); }, [items, displayType.groupBy]); + // Search based on the group path on group title click const onGroupClick = useCallback( (groupPath) => { + // Clear seach when groupPath is emty (root) + if (groupPath === '') { + updateSearch(''); + return; + } // Search by group path // 1. use `^` to match only the string beggining - // 2. add `/` suffix to match only exact directories - // 3. if the group path is empty(root), clear search - if (groupPath) { - updateSearch(`^${groupPath}/`); - } else { + // 2. add `/` suffix to exactly match the directory + const newSearch = `^${groupPath}/`; + + // Reset search when toggling the same groupPath + if (newSearch === search) { updateSearch(''); + return; } + + updateSearch(newSearch); }, - [updateSearch], + [updateSearch, search], ); return ( @@ -171,6 +181,7 @@ ViewMetricsTreemap.propTypes = { emptyMessage: PropTypes.node.isRequired, showEntryInfo: PropTypes.func.isRequired, updateSearch: PropTypes.func.isRequired, + search: PropTypes.string.isRequired, }; export const BundlePackages = (props) => { @@ -300,6 +311,7 @@ export const BundlePackages = (props) => { emptyMessage={emptyMessage} showEntryInfo={showEntryInfo} updateSearch={updateSearch} + search={search} /> )} diff --git a/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css b/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css index a174766236..1664a1e6ce 100644 --- a/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css +++ b/packages/ui/src/components/metrics-treemap/metrics-treemap.module.css @@ -24,8 +24,9 @@ } .tileGroupTitle { + position: relative; width: 100%; - padding: 4px 2px 4px var(--space-xxsmall); + padding: var(--space-xxxsmall) 1px var(--space-xxxsmall) var(--space-xxsmall); color: var(--color-text); font-family: var(--font-family-fixed); font-size: 10px; @@ -36,7 +37,17 @@ } .tileGroupTitleTotal { - margin-left: var(--space-xxxsmall); + margin-left: var(--space-xxsmall); + color: var(--color-text-light); +} + +.tileGroupTitle { + transition: var(--ui-transition-out); +} + +.tileGroupTitle:hover .tileGroupTitleText { + text-decoration: underline; + transition: var(--ui-transition-in); } /** Tile group size variation */ @@ -141,7 +152,13 @@ flex: 0 1 auto; width: 100%; min-width: 0; - max-height: calc(2em * var(--line-height-heading)); + max-height: calc(2em * var(--line-height-heading)); /* render max 2 rows */ + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-all; + word-break: break-word; + hyphens: auto; + white-space: normal; overflow: hidden; } @@ -162,9 +179,8 @@ /** Tile size variation */ .tileSizeSmall .tileContent { - padding: var(--space-xxxsmall) var(--space-xxsmall); - gap: 0; - font-size: var(--size-xsmall); + padding: var(--space-xxxsmall); + font-size: 10px; } .tileSizeSmall .tileContentLabel { @@ -173,7 +189,7 @@ .tileSizeDefault { transition: var(--ui-transition-in); - transition-property: left top width height; + transition-property: left, top, width, height; } /** Tile diff variation */ @@ -196,7 +212,7 @@ } .tile-NO_CHANGE { - color: var(--color-text-ultra-light); + color: var(--color-text-light); } .tile-NO_CHANGE::before { diff --git a/packages/ui/src/components/metrics-treemap/metrics-treemap.tsx b/packages/ui/src/components/metrics-treemap/metrics-treemap.tsx index ea55df0a29..c6789e7747 100644 --- a/packages/ui/src/components/metrics-treemap/metrics-treemap.tsx +++ b/packages/ui/src/components/metrics-treemap/metrics-treemap.tsx @@ -41,16 +41,21 @@ const NESTED_PADDING_LEFT = 8; * Resolve the tile's size using predefined values to avoid * computing the size of the content for every tile */ -const PADDING_TOP = 4; -const PADDING_BOTTOM = 2; // substracted to allow longer texts to appear -const PADDING_LEFT = 2; -const PADDING_RIGHT = 4; // substracted to allow longer texts to not break -const VERTICAL_SPACING = 4; +const PADDING_TOP = 8; +const PADDING_BOTTOM = 8; +const PADDING_LEFT = 8; +const PADDING_RIGHT = 8; const LINE_HEIGHT = 16; const LINE_HEIGHT_SMALL = 13.3; -function resolveTileGroupSizeDisplay(width: number, height: number): TileSizeDisplay { - if (height < NESTED_PADDING_TOP || width < 24) { +type TileGroupSizeDisplay = 'minimal' | 'small' | 'default'; + +function resolveTileGroupSizeDisplay(width: number, height: number): TileGroupSizeDisplay { + if (height < NESTED_PADDING_TOP) { + return 'minimal'; + } + + if (width < 24) { return 'minimal'; } @@ -64,21 +69,23 @@ function resolveTileGroupSizeDisplay(width: number, height: number): TileSizeDis type TileSizeDisplay = 'minimal' | 'small' | 'default'; function resolveTileSizeDisplay(width: number, height: number): TileSizeDisplay { - if ( - height > PADDING_TOP + PADDING_BOTTOM + VERTICAL_SPACING + LINE_HEIGHT * 2 && - width > PADDING_LEFT + PADDING_RIGHT + 128 - ) { - return 'default'; + if (height < PADDING_TOP + PADDING_BOTTOM + LINE_HEIGHT_SMALL) { + return 'minimal'; + } + + if (width < PADDING_LEFT + PADDING_RIGHT + 42) { + return 'minimal'; + } + + if (height < PADDING_TOP + PADDING_TOP + LINE_HEIGHT * 2) { + return 'small'; } - if ( - height > PADDING_TOP + PADDING_BOTTOM + LINE_HEIGHT_SMALL * 2 && - width > PADDING_LEFT + PADDING_BOTTOM + 48 - ) { + if (width < PADDING_LEFT + PADDING_RIGHT + 96) { return 'small'; } - return 'minimal'; + return 'default'; } interface TileTooltipContentProps { @@ -118,7 +125,7 @@ interface TileContentProps { /** * The estimated size of the tile */ - sizeDisplay: 'minimal' | 'small' | 'default'; + sizeDisplay: TileSizeDisplay; /** * Metric run info */ @@ -128,23 +135,33 @@ interface TileContentProps { const TileContent = forwardRef((props: TileContentProps, ref: Ref) => { const { label, sizeDisplay, item, runInfo } = props; + // Render only the container if (sizeDisplay === 'minimal') { return
; } + const resolvedLabel = label || item.label; + + // Render only the label + if (sizeDisplay === 'small') { + return ( +
+

{resolvedLabel}

+
+ ); + } + return (
- - {sizeDisplay !== 'small' && ( -

- {runInfo.displayValue} - -

- )} +

{label || item.label}

+

+ {runInfo.displayValue} + +

); }); @@ -221,7 +238,7 @@ const Tile = (props: TileProps) => { css[`tile-${runInfo.deltaType}`], sizeDisplay === 'small' && css.tileSizeSmall, sizeDisplay === 'default' && css.tileSizeDefault, - left === 0 && css.tileFirstCol, + left === PADDING_INNER && css.tileFirstCol, ); /* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */ @@ -350,7 +367,9 @@ const TileGroup = (props: TileGroupProps) => { style={{ left, top, width, height }} className={rootClassName} > -
{title}
+
+ {title} +
); } @@ -365,11 +384,11 @@ const TileGroup = (props: TileGroupProps) => { > {title && (
- {title} + {title} {metricRunInfo && ( {`${metricRunInfo.displayValue}`} - {'displayDelta' in metricRunInfo && ` (${metricRunInfo.displayDeltaPercentage})`} + {'displayDelta' in metricRunInfo && `(${metricRunInfo.displayDeltaPercentage})`} )}