Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions datahub-graphql-core/src/main/resources/module.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ enum DataHubPageModuleType {
Module displaying the related terms of a given glossary term
"""
RELATED_TERMS
"""
Module displaying the platforms in the instance
"""
PLATFORMS
}

"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import { CorpUser, DataPlatform, ScenarioType } from '@types';

export const PLATFORMS_MODULE_ID = 'Platforms';

const MAX_PLATFORMS_TO_FETCH = 10;

export type PlatformAndCount = {
platform: DataPlatform;
count: number;
};

export const useGetPlatforms = (user?: CorpUser | null): { platforms: PlatformAndCount[]; loading: boolean } => {
export const useGetPlatforms = (
user?: CorpUser | null,
maxToFetch?: number,
): { platforms: PlatformAndCount[]; loading: boolean } => {
const { localState } = useUserContext();
const { selectedViewUrn } = localState;
const { data, loading } = useListRecommendationsQuery({
Expand All @@ -20,7 +25,7 @@ export const useGetPlatforms = (user?: CorpUser | null): { platforms: PlatformAn
requestContext: {
scenario: ScenarioType.Home,
},
limit: 10,
limit: maxToFetch || MAX_PLATFORMS_TO_FETCH,
viewUrn: selectedViewUrn,
},
},
Expand Down
2 changes: 2 additions & 0 deletions datahub-web-react/src/app/homeV3/module/Module.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import DocumentationModule from '@app/homeV3/modules/documentation/Documentation
import TopDomainsModule from '@app/homeV3/modules/domains/TopDomainsModule';
import HierarchyViewModule from '@app/homeV3/modules/hierarchyViewModule/HierarchyViewModule';
import LinkModule from '@app/homeV3/modules/link/LinkModule';
import PlatformsModule from '@app/homeV3/modules/platforms/PlatformsModule';

import { DataHubPageModuleType } from '@types';

Expand All @@ -32,6 +33,7 @@ function Module(props: ModuleProps) {
if (module.properties.type === DataHubPageModuleType.ChildHierarchy) return ChildHierarchyModule;
if (module.properties.type === DataHubPageModuleType.DataProducts) return DataProductsModule;
if (module.properties.type === DataHubPageModuleType.RelatedTerms) return RelatedTermsModule;
if (module.properties.type === DataHubPageModuleType.Platforms) return PlatformsModule;

// TODO: remove the sample large module once we have other modules to fill this out
console.error(`Issue finding module with type ${module.properties.type}`);
Expand Down
74 changes: 43 additions & 31 deletions datahub-web-react/src/app/homeV3/module/components/EntityItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ interface Props {
hideSubtitle?: boolean;
hideMatches?: boolean;
padding?: string;
// For custom click action on entity (either entire container or just name depending on navigateOnlyOnNameClick)
customOnEntityClick?: (entity: Entity) => void;
// For custom hover action on entity name
customHoverEntityName?: (entity: Entity, children: React.ReactNode) => React.ReactNode;
}

export default function EntityItem({
Expand All @@ -34,6 +38,8 @@ export default function EntityItem({
hideSubtitle,
hideMatches,
padding,
customOnEntityClick,
customHoverEntityName,
}: Props) {
const entityRegistry = useEntityRegistryV2();
const linkProps = useGetModalLinkProps();
Expand All @@ -50,37 +56,43 @@ export default function EntityItem({
[entity.urn, moduleType, templateType],
);

const autoCompleteItemProps = {
entity,
key: entity.urn,
hideSubtitle,
hideMatches,
padding,
customDetailsRenderer,
dragIconRenderer,
customHoverEntityName,
navigateOnlyOnNameClick,
customOnEntityClick,
};

if (customOnEntityClick && !navigateOnlyOnNameClick) {
return (
<div
role="button"
tabIndex={0}
onClick={() => customOnEntityClick(entity)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
customOnEntityClick(entity);
}
}}
>
<AutoCompleteEntityItem {...autoCompleteItemProps} onClick={sendAnalytics} />
</div>
);
}

if (navigateOnlyOnNameClick) {
return <AutoCompleteEntityItem {...autoCompleteItemProps} onClick={sendAnalytics} />;
}

return (
<>
{navigateOnlyOnNameClick ? (
<AutoCompleteEntityItem
entity={entity}
key={entity.urn}
customDetailsRenderer={customDetailsRenderer}
hideSubtitle={hideSubtitle}
hideMatches={hideMatches}
padding={padding}
navigateOnlyOnNameClick
dragIconRenderer={dragIconRenderer}
onClick={sendAnalytics}
/>
) : (
<StyledLink
to={entityRegistry.getEntityUrl(entity.type, entity.urn)}
onClick={sendAnalytics}
{...linkProps}
>
<AutoCompleteEntityItem
entity={entity}
key={entity.urn}
hideSubtitle={hideSubtitle}
hideMatches={hideMatches}
padding={padding}
customDetailsRenderer={customDetailsRenderer}
dragIconRenderer={dragIconRenderer}
/>
</StyledLink>
)}
</>
<StyledLink to={entityRegistry.getEntityUrl(entity.type, entity.urn)} onClick={sendAnalytics} {...linkProps}>
<AutoCompleteEntityItem {...autoCompleteItemProps} />
</StyledLink>
);
}
1 change: 1 addition & 0 deletions datahub-web-react/src/app/homeV3/modules/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const DEFAULT_MODULE_URNS = [
'urn:li:dataHubPageModule:child_hierarchy',
'urn:li:dataHubPageModule:data_products',
'urn:li:dataHubPageModule:related_terms',
'urn:li:dataHubPageModule:platforms',
];

export const DEFAULT_TEMPLATE_URN = 'urn:li:dataHubPageTemplate:home_default_1';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Text, Tooltip } from '@components';
import React from 'react';

import { useUserContext } from '@app/context/useUserContext';
import { useGetPlatforms } from '@app/homeV2/content/tabs/discovery/sections/platform/useGetPlatforms';
import EmptyContent from '@app/homeV3/module/components/EmptyContent';
import EntityItem from '@app/homeV3/module/components/EntityItem';
import LargeModule from '@app/homeV3/module/components/LargeModule';
import { ModuleProps } from '@app/homeV3/module/types';
import usePlatformModuleUtils from '@app/homeV3/modules/platforms/usePlatformsModuleUtils';
import { formatNumber, formatNumberWithoutAbbreviation } from '@app/shared/formatNumber';

import { DataHubPageModuleType, Entity } from '@types';

const NUMBER_OF_PLATFORMS = 15;

const PlatformsModule = (props: ModuleProps) => {
const { user } = useUserContext();
const { platforms, loading } = useGetPlatforms(user, NUMBER_OF_PLATFORMS);
const { navigateToDataSources, handleEntityClick } = usePlatformModuleUtils();

const renderAssetCount = (entity: Entity) => {
const platformEntity = platforms.find((platform) => platform.platform.urn === entity.urn);
const assetCount = platformEntity?.count || 0;

return (
<>
{assetCount > 0 && (
<Text size="sm" color="gray">
{formatNumber(assetCount)}
</Text>
)}
</>
);
};

const renderCustomTooltip = (entity: Entity, children: React.ReactNode) => {
const platformEntity = platforms.find((platform) => platform.platform.urn === entity.urn);
return (
<Tooltip
title={`View ${formatNumberWithoutAbbreviation(platformEntity?.count)} ${platformEntity?.platform.name} assets`}
placement="bottom"
>
{children}
</Tooltip>
);
};

return (
<LargeModule {...props} loading={loading} dataTestId="platforms-module">
{platforms.length === 0 ? (
<EmptyContent
icon="Database"
title="No Platforms Yet"
description="You have not ingested any data."
linkText="Add data sources"
onLinkClick={navigateToDataSources}
/>
) : (
<div data-testid="platform-entities">
{platforms.map((platform) => (
<EntityItem
entity={platform.platform}
key={platform.platform.urn}
moduleType={DataHubPageModuleType.Platforms}
customDetailsRenderer={renderAssetCount}
customOnEntityClick={handleEntityClick}
customHoverEntityName={renderCustomTooltip}
/>
))}
</div>
)}
</LargeModule>
);
};

export default PlatformsModule;
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { type Mock, vi } from 'vitest';

import usePlatformsModuleUtils from '@app/homeV3/modules/platforms/usePlatformsModuleUtils';
import { PLATFORM_FILTER_NAME } from '@app/search/utils/constants';
import { navigateToSearchUrl } from '@app/searchV2/utils/navigateToSearchUrl';
import { PageRoutes } from '@conf/Global';

// Mocks
const mockPush = vi.fn();
const mockHistory = { push: mockPush };

vi.mock('react-router', () => ({
useHistory: () => mockHistory,
}));

vi.mock('@app/searchV2/utils/navigateToSearchUrl', () => ({
navigateToSearchUrl: vi.fn(),
}));

const entityMock = { urn: 'urn:li:entity', type: 'test-type' } as any;

describe('usePlatformsModuleUtils', () => {
beforeEach(() => {
mockPush.mockClear();
(navigateToSearchUrl as unknown as Mock).mockClear();
});

it('should provide navigateToDataSources', () => {
const { result } = renderHook(() => usePlatformsModuleUtils());
expect(result.current.navigateToDataSources).toBeInstanceOf(Function);
});

it('should provide handleEntityClick', () => {
const { result } = renderHook(() => usePlatformsModuleUtils());
expect(result.current.handleEntityClick).toBeInstanceOf(Function);
});

it('navigateToDataSources should call history.push with INGESTION route', () => {
const { result } = renderHook(() => usePlatformsModuleUtils());
act(() => {
result.current.navigateToDataSources();
});
expect(mockPush).toHaveBeenCalledWith({ pathname: PageRoutes.INGESTION });
});

it('handleEntityClick should call navigateToSearchUrl with correct params', () => {
const { result } = renderHook(() => usePlatformsModuleUtils());
act(() => {
result.current.handleEntityClick(entityMock);
});
expect(navigateToSearchUrl).toHaveBeenCalledWith({
history: mockHistory,
filters: [
{
field: PLATFORM_FILTER_NAME,
values: [entityMock.urn],
},
],
});
});

it('handleEntityClick should call navigateToSearchUrl with different entity', () => {
const anotherEntity = { urn: 'urn:li:other-entity', type: 'other-type' } as any;
const { result } = renderHook(() => usePlatformsModuleUtils());
act(() => {
result.current.handleEntityClick(anotherEntity);
});
expect(navigateToSearchUrl).toHaveBeenCalledWith({
history: mockHistory,
filters: [
{
field: PLATFORM_FILTER_NAME,
values: [anotherEntity.urn],
},
],
});
});

it('should not call navigateToSearchUrl if entity is undefined', () => {
const { result } = renderHook(() => usePlatformsModuleUtils());
act(() => {
result.current.handleEntityClick(undefined as any);
});
expect(navigateToSearchUrl).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useHistory } from 'react-router';

import { PLATFORM_FILTER_NAME } from '@app/search/utils/constants';
import { navigateToSearchUrl } from '@app/searchV2/utils/navigateToSearchUrl';
import { PageRoutes } from '@conf/Global';

import { Entity } from '@types';

const usePlatformsModuleUtils = () => {
const history = useHistory();

const navigateToDataSources = () => {
history.push({
pathname: `${PageRoutes.INGESTION}`,
});
};

const handleEntityClick = (entity: Entity) => {
if (!entity?.urn) return;
navigateToSearchUrl({
history,
filters: [
{
field: PLATFORM_FILTER_NAME,
values: [entity.urn],
},
],
});
};

return { navigateToDataSources, handleEntityClick };
};

export default usePlatformsModuleUtils;
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,13 @@ describe('useAddModuleMenu', () => {
// Check "Default" group - HomePage should have yourAssets and domains
expect(items?.[1]).toHaveProperty('key', 'customLargeModulesGroup');
// @ts-expect-error SubMenuItem should have children
expect(items?.[1]?.children).toHaveLength(2);
expect(items?.[1]?.children).toHaveLength(3);
// @ts-expect-error SubMenuItem should have children
expect(items?.[1]?.children?.[0]).toHaveProperty('key', 'your-assets');
// @ts-expect-error SubMenuItem should have children
expect(items?.[1]?.children?.[1]).toHaveProperty('key', 'domains');
// @ts-expect-error SubMenuItem should have children
expect(items?.[1]?.children?.[2]).toHaveProperty('key', 'platforms');
});

it('should include admin created modules when available in global template for HomePage', () => {
Expand Down Expand Up @@ -576,7 +578,7 @@ describe('useAddModuleMenu', () => {

// Default modules should be HomePage defaults (not entity-specific)
const defaultChildren = getChildren(result.current.items?.[1]);
expect(defaultChildren).toHaveLength(2);
expect(defaultChildren).toHaveLength(3);
expect(defaultChildren[0]).toHaveProperty('key', 'your-assets');
expect(defaultChildren[1]).toHaveProperty('key', 'domains');
});
Expand Down
Loading
Loading