mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-11 08:52:58 +00:00
Container link in browse v2 sidebar (#8305)
This commit is contained in:
parent
6b8c4c83bc
commit
80fcb21169
@ -23,6 +23,7 @@ export enum EventType {
|
|||||||
BrowseV2ToggleSidebarEvent,
|
BrowseV2ToggleSidebarEvent,
|
||||||
BrowseV2ToggleNodeEvent,
|
BrowseV2ToggleNodeEvent,
|
||||||
BrowseV2SelectNodeEvent,
|
BrowseV2SelectNodeEvent,
|
||||||
|
BrowseV2EntityLinkClickEvent,
|
||||||
EntityViewEvent,
|
EntityViewEvent,
|
||||||
EntitySectionViewEvent,
|
EntitySectionViewEvent,
|
||||||
EntityActionEvent,
|
EntityActionEvent,
|
||||||
@ -252,6 +253,18 @@ export interface BrowseV2SelectNodeEvent extends BaseEvent {
|
|||||||
targetDepth: number;
|
targetDepth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logged when a user clicks a container link in the sidebar
|
||||||
|
*/
|
||||||
|
export interface BrowseV2EntityLinkClickEvent extends BaseEvent {
|
||||||
|
type: EventType.BrowseV2EntityLinkClickEvent;
|
||||||
|
targetNode: 'browse';
|
||||||
|
entity: string;
|
||||||
|
environment?: string;
|
||||||
|
platform?: string;
|
||||||
|
targetDepth: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logged when user views an entity profile.
|
* Logged when user views an entity profile.
|
||||||
*/
|
*/
|
||||||
@ -613,6 +626,7 @@ export type Event =
|
|||||||
| BrowseV2ToggleSidebarEvent
|
| BrowseV2ToggleSidebarEvent
|
||||||
| BrowseV2ToggleNodeEvent
|
| BrowseV2ToggleNodeEvent
|
||||||
| BrowseV2SelectNodeEvent
|
| BrowseV2SelectNodeEvent
|
||||||
|
| BrowseV2EntityLinkClickEvent
|
||||||
| EntityViewEvent
|
| EntityViewEvent
|
||||||
| EntitySectionViewEvent
|
| EntitySectionViewEvent
|
||||||
| EntityActionEvent
|
| EntityActionEvent
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export type LocalState = {
|
|||||||
selectedViewUrn?: string | null;
|
selectedViewUrn?: string | null;
|
||||||
selectedPath?: string | null;
|
selectedPath?: string | null;
|
||||||
selectedSearch?: string | null;
|
selectedSearch?: string | null;
|
||||||
showBrowseV2Sidebar?: boolean | null;
|
showBrowseV2Sidebar?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const ToggleSidebarButton = ({ isOpen, onClick }: Props) => {
|
|||||||
if (pauseTooltip) return button;
|
if (pauseTooltip) return button;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={title} placement={placement} arrowPointAtCenter>
|
<Tooltip title={title} placement={placement} arrowPointAtCenter mouseEnterDelay={1}>
|
||||||
{button}
|
{button}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
useBrowseDisplayName,
|
useBrowseDisplayName,
|
||||||
} from './BrowseContext';
|
} from './BrowseContext';
|
||||||
import useSidebarAnalytics from './useSidebarAnalytics';
|
import useSidebarAnalytics from './useSidebarAnalytics';
|
||||||
|
import EntityLink from './EntityLink';
|
||||||
|
|
||||||
const FolderStyled = styled(FolderOutlined)`
|
const FolderStyled = styled(FolderOutlined)`
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@ -29,6 +30,7 @@ const FolderStyled = styled(FolderOutlined)`
|
|||||||
const Count = styled(Typography.Text)`
|
const Count = styled(Typography.Text)`
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: ${(props) => props.color};
|
color: ${(props) => props.color};
|
||||||
|
padding-right: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BrowseNode = () => {
|
const BrowseNode = () => {
|
||||||
@ -40,7 +42,8 @@ const BrowseNode = () => {
|
|||||||
const platformAggregation = usePlatformAggregation();
|
const platformAggregation = usePlatformAggregation();
|
||||||
const browseResultGroup = useBrowseResultGroup();
|
const browseResultGroup = useBrowseResultGroup();
|
||||||
const { trackToggleNodeEvent } = useSidebarAnalytics();
|
const { trackToggleNodeEvent } = useSidebarAnalytics();
|
||||||
const { count } = browseResultGroup;
|
const { count, entity } = browseResultGroup;
|
||||||
|
const hasEntityLink = !!entity;
|
||||||
const displayName = useBrowseDisplayName();
|
const displayName = useBrowseDisplayName();
|
||||||
const { trackSelectNodeEvent } = useSidebarAnalytics();
|
const { trackSelectNodeEvent } = useSidebarAnalytics();
|
||||||
|
|
||||||
@ -86,9 +89,16 @@ const BrowseNode = () => {
|
|||||||
dataTestId={`browse-node-expand-${displayName}`}
|
dataTestId={`browse-node-expand-${displayName}`}
|
||||||
/>
|
/>
|
||||||
<FolderStyled />
|
<FolderStyled />
|
||||||
<ExpandableNode.Title color={color} size={14} depth={browsePathLength}>
|
<ExpandableNode.Title
|
||||||
|
color={color}
|
||||||
|
size={14}
|
||||||
|
depth={browsePathLength}
|
||||||
|
maxWidth={hasEntityLink ? 175 : 200}
|
||||||
|
padLeft
|
||||||
|
>
|
||||||
{displayName}
|
{displayName}
|
||||||
</ExpandableNode.Title>
|
</ExpandableNode.Title>
|
||||||
|
{hasEntityLink && <EntityLink entity={entity} targetNode="browse" />}
|
||||||
</ExpandableNode.HeaderLeft>
|
</ExpandableNode.HeaderLeft>
|
||||||
<Count color={color}>{formatNumber(count)}</Count>
|
<Count color={color}>{formatNumber(count)}</Count>
|
||||||
</ExpandableNode.SelectableHeader>
|
</ExpandableNode.SelectableHeader>
|
||||||
|
|||||||
61
datahub-web-react/src/app/search/sidebar/EntityLink.tsx
Normal file
61
datahub-web-react/src/app/search/sidebar/EntityLink.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Tooltip } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import Icon from '@ant-design/icons/lib/components/Icon';
|
||||||
|
import { useBrowseDisplayName, useIsBrowsePathSelected } from './BrowseContext';
|
||||||
|
import ExpandableNode from './ExpandableNode';
|
||||||
|
import { ReactComponent as ExternalLink } from '../../../images/link-out.svg';
|
||||||
|
import { useEntityRegistry } from '../../useEntityRegistry';
|
||||||
|
import { Entity, Maybe } from '../../../types.generated';
|
||||||
|
import useSidebarAnalytics from './useSidebarAnalytics';
|
||||||
|
import { BrowseV2EntityLinkClickEvent } from '../../analytics';
|
||||||
|
|
||||||
|
const Linkicon = styled(Icon)<{ isSelected: boolean }>`
|
||||||
|
&& {
|
||||||
|
color: ${(props) => props.theme.styles['primary-color']};
|
||||||
|
${(props) => !props.isSelected && 'display: none;'}
|
||||||
|
${ExpandableNode.SelectableHeader}:hover & {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
entity?: Maybe<Entity>;
|
||||||
|
targetNode: BrowseV2EntityLinkClickEvent['targetNode'];
|
||||||
|
};
|
||||||
|
|
||||||
|
// The tooltip needs some text to hold onto
|
||||||
|
const EmptySpace = styled.span`
|
||||||
|
display: 'none';
|
||||||
|
content: ' ';
|
||||||
|
`;
|
||||||
|
|
||||||
|
const EntityLink = ({ entity, targetNode }: Props) => {
|
||||||
|
const registry = useEntityRegistry();
|
||||||
|
const isBrowsePathSelected = useIsBrowsePathSelected();
|
||||||
|
const displayName = useBrowseDisplayName();
|
||||||
|
const { trackEntityLinkClickEvent } = useSidebarAnalytics();
|
||||||
|
const entityUrl = entity ? registry.getEntityUrl(entity.type, entity.urn) : null;
|
||||||
|
|
||||||
|
const onClickButton = () => {
|
||||||
|
trackEntityLinkClickEvent(targetNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!entityUrl) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip placement="top" title={`View ${displayName} profile`} mouseEnterDelay={1}>
|
||||||
|
<Link to={entityUrl}>
|
||||||
|
<ExpandableNode.StaticButton
|
||||||
|
icon={<Linkicon isSelected={isBrowsePathSelected} component={ExternalLink} />}
|
||||||
|
onClick={onClickButton}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<EmptySpace />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntityLink;
|
||||||
@ -19,6 +19,7 @@ import { useHasFilterField } from './SidebarContext';
|
|||||||
const Count = styled(Typography.Text)`
|
const Count = styled(Typography.Text)`
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: ${(props) => props.color};
|
color: ${(props) => props.color};
|
||||||
|
padding-left: 4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EntityNode = () => {
|
const EntityNode = () => {
|
||||||
@ -62,10 +63,8 @@ const EntityNode = () => {
|
|||||||
data-testid={`browse-entity-${registry.getCollectionName(entityType)}`}
|
data-testid={`browse-entity-${registry.getCollectionName(entityType)}`}
|
||||||
>
|
>
|
||||||
<ExpandableNode.HeaderLeft>
|
<ExpandableNode.HeaderLeft>
|
||||||
<ExpandableNode.StaticButton
|
{registry.getIcon(entityType, 16, IconStyleType.HIGHLIGHT, color)}
|
||||||
icon={registry.getIcon(entityType, 16, IconStyleType.HIGHLIGHT, color)}
|
<ExpandableNode.Title color={color} size={16} padLeft>
|
||||||
/>
|
|
||||||
<ExpandableNode.Title color={color} size={16}>
|
|
||||||
{registry.getCollectionName(entityType)}
|
{registry.getCollectionName(entityType)}
|
||||||
</ExpandableNode.Title>
|
</ExpandableNode.Title>
|
||||||
<Count color={color}>{formatNumber(entityAggregation.count)}</Count>
|
<Count color={color}>{formatNumber(entityAggregation.count)}</Count>
|
||||||
|
|||||||
@ -45,27 +45,27 @@ ExpandableNode.Header = styled.div<{ isOpen: boolean; isSelected?: boolean; show
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: 2px 4px 2px 4px;
|
padding-top: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
padding-right: 2px;
|
||||||
border-bottom: 1px solid ${(props) => (props.isOpen || !props.showBorder ? 'transparent' : ANTD_GRAY[4])};
|
border-bottom: 1px solid ${(props) => (props.isOpen || !props.showBorder ? 'transparent' : ANTD_GRAY[4])};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
ExpandableNode.SelectableHeader = styled(ExpandableNode.Header)<{ isSelected: boolean }>`
|
ExpandableNode.SelectableHeader = styled(ExpandableNode.Header)<{ isSelected: boolean }>`
|
||||||
&& {
|
& {
|
||||||
border: 1px solid ${(props) => (props.isSelected ? props.theme.styles['primary-color'] : 'transparent')};
|
border: 1px solid ${(props) => (props.isSelected ? props.theme.styles['primary-color'] : 'transparent')};
|
||||||
background-color: ${(props) => (props.isSelected ? props.theme.styles['primary-color-light'] : 'transparent')};
|
background-color: ${(props) => (props.isSelected ? props.theme.styles['primary-color-light'] : 'transparent')};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: box-shadow 100ms ease-in-out;
|
|
||||||
box-shadow: 'none';
|
|
||||||
}
|
}
|
||||||
&&:hover {
|
|
||||||
box-shadow: ${(props) => props.theme.styles['box-shadow-hover']};
|
&:hover {
|
||||||
|
background-color: ${(props) => props.theme.styles['primary-color-light']};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
ExpandableNode.HeaderLeft = styled.div`
|
ExpandableNode.HeaderLeft = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BaseButton = styled(Button)`
|
const BaseButton = styled(Button)`
|
||||||
@ -84,8 +84,12 @@ const RotatingButton = styled(BaseButton)<{ deg: number }>`
|
|||||||
transition: transform 250ms;
|
transition: transform 250ms;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
ExpandableNode.StaticButton = ({ icon }: { icon: JSX.Element }) => {
|
ExpandableNode.StaticButton = ({ icon, onClick }: { icon: JSX.Element; onClick?: () => void }) => {
|
||||||
return <BaseButton ghost size="small" type="ghost" icon={icon} />;
|
const onClickButton: MouseEventHandler = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClick?.();
|
||||||
|
};
|
||||||
|
return <BaseButton ghost size="small" type="ghost" icon={icon} onClick={onClickButton} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
ExpandableNode.TriangleButton = ({
|
ExpandableNode.TriangleButton = ({
|
||||||
@ -129,8 +133,9 @@ ExpandableNode.CircleButton = ({ isOpen, color }: { isOpen: boolean; color: stri
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Reduce the ellipsis tolerance the deeper we get into the browse path
|
// Reduce the ellipsis tolerance the deeper we get into the browse path
|
||||||
const BaseTitleContainer = styled.div<{ depth: number }>`
|
const BaseTitleContainer = styled.div<{ depth: number; maxWidth: number; padLeft: boolean }>`
|
||||||
max-width: ${(props) => 200 - props.depth * 8}px;
|
max-width: ${(props) => props.maxWidth - props.depth * 8}px;
|
||||||
|
padding-left: ${(props) => (props.padLeft ? 4 : 0)}px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BaseTitle = styled(Typography.Text)<{ color: string; size: number }>`
|
const BaseTitle = styled(Typography.Text)<{ color: string; size: number }>`
|
||||||
@ -143,15 +148,19 @@ ExpandableNode.Title = ({
|
|||||||
size,
|
size,
|
||||||
depth = 0,
|
depth = 0,
|
||||||
children,
|
children,
|
||||||
|
maxWidth = 200,
|
||||||
|
padLeft = false,
|
||||||
}: {
|
}: {
|
||||||
color: string;
|
color: string;
|
||||||
size: number;
|
size: number;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
maxWidth?: number;
|
||||||
|
padLeft?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<BaseTitleContainer depth={depth}>
|
<BaseTitleContainer depth={depth} maxWidth={maxWidth} padLeft={padLeft}>
|
||||||
<BaseTitle ellipsis={{ tooltip: true }} color={color} size={size}>
|
<BaseTitle ellipsis={{ tooltip: { mouseEnterDelay: 1 } }} color={color} size={size}>
|
||||||
{children}
|
{children}
|
||||||
</BaseTitle>
|
</BaseTitle>
|
||||||
</BaseTitleContainer>
|
</BaseTitleContainer>
|
||||||
|
|||||||
@ -81,7 +81,7 @@ const PlatformNode = () => {
|
|||||||
dataTestId={`browse-platform-${label}`}
|
dataTestId={`browse-platform-${label}`}
|
||||||
/>
|
/>
|
||||||
<PlatformIconContainer>{icon}</PlatformIconContainer>
|
<PlatformIconContainer>{icon}</PlatformIconContainer>
|
||||||
<ExpandableNode.Title color={color} size={14}>
|
<ExpandableNode.Title color={color} size={14} padLeft>
|
||||||
{label}
|
{label}
|
||||||
</ExpandableNode.Title>
|
</ExpandableNode.Title>
|
||||||
</ExpandableNode.HeaderLeft>
|
</ExpandableNode.HeaderLeft>
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Alert, Button } from 'antd';
|
import { Alert, Button } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledAlert = styled(Alert)`
|
||||||
|
white-space: normal;
|
||||||
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClickRetry?: () => void;
|
onClickRetry?: () => void;
|
||||||
@ -7,8 +12,8 @@ type Props = {
|
|||||||
|
|
||||||
const SidebarLoadingError = ({ onClickRetry }: Props) => {
|
const SidebarLoadingError = ({ onClickRetry }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Alert
|
<StyledAlert
|
||||||
message="There was a problem loading the sidebar."
|
message="The sidebar failed to load."
|
||||||
showIcon
|
showIcon
|
||||||
type="error"
|
type="error"
|
||||||
action={
|
action={
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import { EntityType } from '../../../types.generated';
|
import { EntityType } from '../../../types.generated';
|
||||||
import { BrowseV2SelectNodeEvent, BrowseV2ToggleNodeEvent, EventType } from '../../analytics';
|
import {
|
||||||
|
BrowseV2EntityLinkClickEvent,
|
||||||
|
BrowseV2SelectNodeEvent,
|
||||||
|
BrowseV2ToggleNodeEvent,
|
||||||
|
EventType,
|
||||||
|
} from '../../analytics';
|
||||||
import analytics from '../../analytics/analytics';
|
import analytics from '../../analytics/analytics';
|
||||||
import { useEntityRegistry } from '../../useEntityRegistry';
|
import { useEntityRegistry } from '../../useEntityRegistry';
|
||||||
import {
|
import {
|
||||||
@ -48,7 +53,18 @@ const useSidebarAnalytics = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return { trackToggleNodeEvent, trackSelectNodeEvent } as const;
|
const trackEntityLinkClickEvent = (targetNode: BrowseV2EntityLinkClickEvent['targetNode']) => {
|
||||||
|
analytics.event({
|
||||||
|
type: EventType.BrowseV2EntityLinkClickEvent,
|
||||||
|
targetNode,
|
||||||
|
entity: entityDisplayName,
|
||||||
|
environment: environmentDisplayName,
|
||||||
|
platform: platformDisplayName,
|
||||||
|
targetDepth,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { trackToggleNodeEvent, trackSelectNodeEvent, trackEntityLinkClickEvent } as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useSidebarAnalytics;
|
export default useSidebarAnalytics;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ public enum DataHubUsageEventType {
|
|||||||
BROWSE_V2_TOGGLE_SIDEBAR_EVENT("BrowseV2ToggleSidebarEvent"),
|
BROWSE_V2_TOGGLE_SIDEBAR_EVENT("BrowseV2ToggleSidebarEvent"),
|
||||||
BROWSE_V2_TOGGLE_NODE_EVENT("BrowseV2ToggleNodeEvent"),
|
BROWSE_V2_TOGGLE_NODE_EVENT("BrowseV2ToggleNodeEvent"),
|
||||||
BROWSE_V2_SELECT_NODE_EVENT("BrowseV2SelectNodeEvent"),
|
BROWSE_V2_SELECT_NODE_EVENT("BrowseV2SelectNodeEvent"),
|
||||||
|
BROWSE_V2_ENTITY_LINK_CLICK_EVENT("BrowseV2EntityLinkClickEvent"),
|
||||||
ENTITY_VIEW_EVENT("EntityViewEvent"),
|
ENTITY_VIEW_EVENT("EntityViewEvent"),
|
||||||
ENTITY_SECTION_VIEW_EVENT("EntitySectionViewEvent"),
|
ENTITY_SECTION_VIEW_EVENT("EntitySectionViewEvent"),
|
||||||
ENTITY_ACTION_EVENT("EntityActionEvent"),
|
ENTITY_ACTION_EVENT("EntityActionEvent"),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user