From 58da8a88571a655fdb5adbb2799d2d3ea33a350c Mon Sep 17 00:00:00 2001 From: v-tarasevich-blitz-brain Date: Sun, 13 Jul 2025 22:25:29 +0300 Subject: [PATCH] feat(customHomePage): adjust rows component and add wrapping (#14025) --- .../module/components/ModuleContainer.tsx | 1 + .../src/app/homeV3/template/Template.tsx | 11 +- .../template/components/AddModuleButton.tsx | 5 +- .../src/app/homeV3/template/types.ts | 4 +- .../app/homeV3/templateRow/TemplateRow.tsx | 5 +- .../templateRow/__tests__/utils.test.ts | 108 ++++++++++++++++++ .../src/app/homeV3/templateRow/types.ts | 6 + .../src/app/homeV3/templateRow/utils.ts | 25 ++++ .../src/app/previewV2/EntityHeader.tsx | 4 +- .../autoCompleteV2/AutoCompleteEntityItem.tsx | 3 +- .../autoCompleteV2/components/DisplayName.tsx | 18 ++- 11 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 datahub-web-react/src/app/homeV3/templateRow/__tests__/utils.test.ts create mode 100644 datahub-web-react/src/app/homeV3/templateRow/types.ts create mode 100644 datahub-web-react/src/app/homeV3/templateRow/utils.ts diff --git a/datahub-web-react/src/app/homeV3/module/components/ModuleContainer.tsx b/datahub-web-react/src/app/homeV3/module/components/ModuleContainer.tsx index 0ce69f67a7..642c540cb3 100644 --- a/datahub-web-react/src/app/homeV3/module/components/ModuleContainer.tsx +++ b/datahub-web-react/src/app/homeV3/module/components/ModuleContainer.tsx @@ -6,6 +6,7 @@ const ModuleContainer = styled.div<{ $height: string }>` border: ${borders['1px']} ${colors.gray[100]}; border-radius: ${radius.lg}; flex: 1; + overflow-x: hidden; height: ${(props) => props.$height}; box-shadow: diff --git a/datahub-web-react/src/app/homeV3/template/Template.tsx b/datahub-web-react/src/app/homeV3/template/Template.tsx index 8cf54b3590..52b5c73f0c 100644 --- a/datahub-web-react/src/app/homeV3/template/Template.tsx +++ b/datahub-web-react/src/app/homeV3/template/Template.tsx @@ -1,11 +1,12 @@ import { spacing } from '@components'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import useModulesAvailableToAdd from '@app/homeV3/modules/hooks/useModulesAvailableToAdd'; import AddModuleButton from '@app/homeV3/template/components/AddModuleButton'; import { AddModuleHandlerInput } from '@app/homeV3/template/types'; import TemplateRow from '@app/homeV3/templateRow/TemplateRow'; +import { wrapRows } from '@app/homeV3/templateRow/utils'; import { DataHubPageTemplate } from '@types'; @@ -26,7 +27,10 @@ interface Props { } export default function Template({ template, className }: Props) { - const hasRows = !!template?.properties?.rows?.length; + const rows = useMemo(() => template?.properties?.rows ?? [], [template?.properties?.rows]); + const hasRows = useMemo(() => !!rows.length, [rows.length]); + const wrappedRows = useMemo(() => wrapRows(rows), [rows]); + const onAddModule = useCallback((input: AddModuleHandlerInput) => { // TODO: implement the real handler console.log('onAddModule handled with input', input); @@ -36,13 +40,14 @@ export default function Template({ template, className }: Props) { return ( - {template?.properties?.rows.map((row, i) => { + {wrappedRows.map((row, i) => { const key = `templateRow-${i}`; return ( diff --git a/datahub-web-react/src/app/homeV3/template/components/AddModuleButton.tsx b/datahub-web-react/src/app/homeV3/template/components/AddModuleButton.tsx index 21d30b8e36..46a7cc2860 100644 --- a/datahub-web-react/src/app/homeV3/template/components/AddModuleButton.tsx +++ b/datahub-web-react/src/app/homeV3/template/components/AddModuleButton.tsx @@ -47,6 +47,7 @@ interface Props { modulesAvailableToAdd: ModulesAvailableToAdd; onAddModule?: (input: AddModuleHandlerInput) => void; className?: string; + originRowIndex?: number; rowIndex?: number; rowSide?: RowSide; } @@ -56,6 +57,7 @@ export default function AddModuleButton({ modulesAvailableToAdd, onAddModule, className, + originRowIndex, rowIndex, rowSide, }: Props) { @@ -68,11 +70,12 @@ export default function AddModuleButton({ setIsOpened(false); onAddModule?.({ module, + originRowIndex, rowIndex, rowSide, }); }, - [onAddModule, rowIndex, rowSide], + [onAddModule, originRowIndex, rowIndex, rowSide], ); const menu = useAddModuleMenu(modulesAvailableToAdd, onAddModuleHandler); diff --git a/datahub-web-react/src/app/homeV3/template/types.ts b/datahub-web-react/src/app/homeV3/template/types.ts index 45394d1e09..0b838a4fe3 100644 --- a/datahub-web-react/src/app/homeV3/template/types.ts +++ b/datahub-web-react/src/app/homeV3/template/types.ts @@ -4,7 +4,9 @@ export type RowSide = 'left' | 'right'; export interface AddModuleHandlerInput { module: ModuleInfo; + // When these fields are empty it means adding a module to the new row - rowIndex?: number; + originRowIndex?: number; // Row index before wrapping + rowIndex?: number; // Row index after wrapping rowSide?: RowSide; } diff --git a/datahub-web-react/src/app/homeV3/templateRow/TemplateRow.tsx b/datahub-web-react/src/app/homeV3/templateRow/TemplateRow.tsx index 8574ed37c2..5f13339b42 100644 --- a/datahub-web-react/src/app/homeV3/templateRow/TemplateRow.tsx +++ b/datahub-web-react/src/app/homeV3/templateRow/TemplateRow.tsx @@ -20,15 +20,17 @@ interface Props { onAddModule?: (input: AddModuleHandlerInput) => void; modulesAvailableToAdd: ModulesAvailableToAdd; rowIndex: number; + originRowIndex: number; } -export default function TemplateRow({ row, onAddModule, modulesAvailableToAdd, rowIndex }: Props) { +export default function TemplateRow({ row, onAddModule, modulesAvailableToAdd, rowIndex, originRowIndex }: Props) { return ( @@ -41,6 +43,7 @@ export default function TemplateRow({ row, onAddModule, modulesAvailableToAdd, r orientation="vertical" modulesAvailableToAdd={modulesAvailableToAdd} onAddModule={onAddModule} + originRowIndex={originRowIndex} rowIndex={rowIndex} rowSide="right" /> diff --git a/datahub-web-react/src/app/homeV3/templateRow/__tests__/utils.test.ts b/datahub-web-react/src/app/homeV3/templateRow/__tests__/utils.test.ts new file mode 100644 index 0000000000..11586eafce --- /dev/null +++ b/datahub-web-react/src/app/homeV3/templateRow/__tests__/utils.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, it } from 'vitest'; + +import { WrappedRow } from '@app/homeV3/templateRow/types'; +import { wrapRows } from '@app/homeV3/templateRow/utils'; + +import { DataHubPageModuleType, DataHubPageTemplateRow, EntityType, PageModuleScope } from '@types'; + +const MOCKED_TIMESTAMP = 1752056099724; + +describe('wrapRows', () => { + const makeRow = (moduleCount: number): DataHubPageTemplateRow => ({ + modules: Array.from({ length: moduleCount }).map((_, i) => ({ + urn: `urn:li:module:${i}`, + type: EntityType.DatahubPageModule, + properties: { + name: `Module ${i}`, + type: DataHubPageModuleType.OwnedAssets, + visibility: { + scope: PageModuleScope.Global, + }, + created: { + time: MOCKED_TIMESTAMP, + }, + lastModified: { + time: MOCKED_TIMESTAMP, + }, + params: {}, + }, + })), + }); + + it('should split modules into chunks of default size (3)', () => { + const rows: DataHubPageTemplateRow[] = [ + makeRow(7), // 7 modules → [3, 3, 1] + ]; + + const result = wrapRows(rows); + + expect(result).toHaveLength(3); + expect(result[0].modules).toHaveLength(3); + expect(result[1].modules).toHaveLength(3); + expect(result[2].modules).toHaveLength(1); + expect(result[0].originRowIndex).toBe(0); + expect(result[0].rowIndex).toBe(0); + expect(result[1].rowIndex).toBe(1); + expect(result[2].rowIndex).toBe(2); + }); + + it('should respect custom chunk size', () => { + const rows: DataHubPageTemplateRow[] = [makeRow(5)]; + + const result = wrapRows(rows, 2); // chunk size = 2 → [2, 2, 1] + + expect(result).toHaveLength(3); + expect(result[0].modules).toHaveLength(2); + expect(result[1].modules).toHaveLength(2); + expect(result[2].modules).toHaveLength(1); + }); + + it('should handle multiple rows correctly', () => { + const rows: DataHubPageTemplateRow[] = [ + makeRow(4), // → [3, 1] + makeRow(5), // → [3, 2] + ]; + + const result = wrapRows(rows); + + expect(result).toHaveLength(4); + + // First row + expect(result[0].originRowIndex).toBe(0); + expect(result[0].rowIndex).toBe(0); + + expect(result[1].originRowIndex).toBe(0); + expect(result[1].rowIndex).toBe(1); + + // Second row + expect(result[2].originRowIndex).toBe(1); + expect(result[2].rowIndex).toBe(2); + + expect(result[3].originRowIndex).toBe(1); + expect(result[3].rowIndex).toBe(3); + }); + + it('should return empty array if no rows provided', () => { + const result = wrapRows([]); + expect(result).toEqual([]); + }); + + it('should return empty array if all rows have no modules', () => { + const rows: DataHubPageTemplateRow[] = [{ modules: [] }, { modules: [] }]; + + const result = wrapRows(rows); + expect(result).toEqual([]); + }); + + it('should handle exact multiples of chunk size', () => { + const rows: DataHubPageTemplateRow[] = [ + makeRow(6), // 6 modules → [3, 3] + ]; + + const result = wrapRows(rows); + + expect(result).toHaveLength(2); + expect(result[0].modules).toHaveLength(3); + expect(result[1].modules).toHaveLength(3); + }); +}); diff --git a/datahub-web-react/src/app/homeV3/templateRow/types.ts b/datahub-web-react/src/app/homeV3/templateRow/types.ts new file mode 100644 index 0000000000..3863be663d --- /dev/null +++ b/datahub-web-react/src/app/homeV3/templateRow/types.ts @@ -0,0 +1,6 @@ +import { DataHubPageTemplateRow } from '@types'; + +export interface WrappedRow extends DataHubPageTemplateRow { + originRowIndex: number; + rowIndex: number; +} diff --git a/datahub-web-react/src/app/homeV3/templateRow/utils.ts b/datahub-web-react/src/app/homeV3/templateRow/utils.ts new file mode 100644 index 0000000000..3f89f853cc --- /dev/null +++ b/datahub-web-react/src/app/homeV3/templateRow/utils.ts @@ -0,0 +1,25 @@ +import { WrappedRow } from '@app/homeV3/templateRow/types'; + +import { DataHubPageTemplateRow } from '@types'; + +const DEFAULT_ROW_MAX_SIZE = 3; + +export function wrapRows(rows: DataHubPageTemplateRow[], chunkSize = DEFAULT_ROW_MAX_SIZE): WrappedRow[] { + const result: WrappedRow[] = []; + let globalRowIndex = 0; + + rows.forEach((row, originRowIndex) => { + const { modules } = row; + + for (let i = 0; i < modules.length; i += chunkSize) { + const chunk = modules.slice(i, i + chunkSize); + result.push({ + originRowIndex, + rowIndex: globalRowIndex++, + modules: chunk, + }); + } + }); + + return result; +} diff --git a/datahub-web-react/src/app/previewV2/EntityHeader.tsx b/datahub-web-react/src/app/previewV2/EntityHeader.tsx index c9370b6bb4..f34073fb67 100644 --- a/datahub-web-react/src/app/previewV2/EntityHeader.tsx +++ b/datahub-web-react/src/app/previewV2/EntityHeader.tsx @@ -1,4 +1,4 @@ -import { Tooltip } from '@components'; +import { Tooltip, zIndices } from '@components'; import React from 'react'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; @@ -98,7 +98,7 @@ const EntityHeader: React.FC = ({ {previewType === PreviewType.HOVER_CARD ? ( - + {name || urn} diff --git a/datahub-web-react/src/app/searchV2/autoCompleteV2/AutoCompleteEntityItem.tsx b/datahub-web-react/src/app/searchV2/autoCompleteV2/AutoCompleteEntityItem.tsx index e8ff354051..36adf22798 100644 --- a/datahub-web-react/src/app/searchV2/autoCompleteV2/AutoCompleteEntityItem.tsx +++ b/datahub-web-react/src/app/searchV2/autoCompleteV2/AutoCompleteEntityItem.tsx @@ -36,7 +36,7 @@ const DisplayNameWithHover = styled(DisplayName)<{ $decorationColor?: string }>` `; const DisplayNameWrapper = styled.div` - width: fit-content; + white-space: nowrap; `; const ContentContainer = styled.div` @@ -131,6 +131,7 @@ export default function AutoCompleteEntityItem({ color={variantProps?.nameColor} colorLevel={variantProps?.nameColorLevel} weight={variantProps?.nameWeight} + showNameTooltipIfTruncated /> )} diff --git a/datahub-web-react/src/app/searchV2/autoCompleteV2/components/DisplayName.tsx b/datahub-web-react/src/app/searchV2/autoCompleteV2/components/DisplayName.tsx index 735a7654c8..78351c771c 100644 --- a/datahub-web-react/src/app/searchV2/autoCompleteV2/components/DisplayName.tsx +++ b/datahub-web-react/src/app/searchV2/autoCompleteV2/components/DisplayName.tsx @@ -25,15 +25,29 @@ interface Props { weight?: FontWeightOptions; fontSize?: FontSizeOptions; className?: string; + showNameTooltipIfTruncated?: boolean; } -export default function DisplayName({ displayName, highlight, color, colorLevel, weight, fontSize, className }: Props) { +export default function DisplayName({ + displayName, + highlight, + color, + colorLevel, + weight, + fontSize, + className, + showNameTooltipIfTruncated, +}: Props) { const { measuredRef, isHorizontallyTruncated } = useMeasureIfTrancated(); return ( {displayName} : undefined} + content={ + showNameTooltipIfTruncated && isHorizontallyTruncated ? ( + {displayName} + ) : undefined + } >