mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-12 01:11:41 +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,
|
||||
BrowseV2ToggleNodeEvent,
|
||||
BrowseV2SelectNodeEvent,
|
||||
BrowseV2EntityLinkClickEvent,
|
||||
EntityViewEvent,
|
||||
EntitySectionViewEvent,
|
||||
EntityActionEvent,
|
||||
@ -252,6 +253,18 @@ export interface BrowseV2SelectNodeEvent extends BaseEvent {
|
||||
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.
|
||||
*/
|
||||
@ -613,6 +626,7 @@ export type Event =
|
||||
| BrowseV2ToggleSidebarEvent
|
||||
| BrowseV2ToggleNodeEvent
|
||||
| BrowseV2SelectNodeEvent
|
||||
| BrowseV2EntityLinkClickEvent
|
||||
| EntityViewEvent
|
||||
| EntitySectionViewEvent
|
||||
| EntityActionEvent
|
||||
|
||||
@ -8,7 +8,7 @@ export type LocalState = {
|
||||
selectedViewUrn?: string | null;
|
||||
selectedPath?: string | null;
|
||||
selectedSearch?: string | null;
|
||||
showBrowseV2Sidebar?: boolean | null;
|
||||
showBrowseV2Sidebar?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -40,7 +40,7 @@ const ToggleSidebarButton = ({ isOpen, onClick }: Props) => {
|
||||
if (pauseTooltip) return button;
|
||||
|
||||
return (
|
||||
<Tooltip title={title} placement={placement} arrowPointAtCenter>
|
||||
<Tooltip title={title} placement={placement} arrowPointAtCenter mouseEnterDelay={1}>
|
||||
{button}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
useBrowseDisplayName,
|
||||
} from './BrowseContext';
|
||||
import useSidebarAnalytics from './useSidebarAnalytics';
|
||||
import EntityLink from './EntityLink';
|
||||
|
||||
const FolderStyled = styled(FolderOutlined)`
|
||||
font-size: 16px;
|
||||
@ -29,6 +30,7 @@ const FolderStyled = styled(FolderOutlined)`
|
||||
const Count = styled(Typography.Text)`
|
||||
font-size: 12px;
|
||||
color: ${(props) => props.color};
|
||||
padding-right: 2px;
|
||||
`;
|
||||
|
||||
const BrowseNode = () => {
|
||||
@ -40,7 +42,8 @@ const BrowseNode = () => {
|
||||
const platformAggregation = usePlatformAggregation();
|
||||
const browseResultGroup = useBrowseResultGroup();
|
||||
const { trackToggleNodeEvent } = useSidebarAnalytics();
|
||||
const { count } = browseResultGroup;
|
||||
const { count, entity } = browseResultGroup;
|
||||
const hasEntityLink = !!entity;
|
||||
const displayName = useBrowseDisplayName();
|
||||
const { trackSelectNodeEvent } = useSidebarAnalytics();
|
||||
|
||||
@ -86,9 +89,16 @@ const BrowseNode = () => {
|
||||
dataTestId={`browse-node-expand-${displayName}`}
|
||||
/>
|
||||
<FolderStyled />
|
||||
<ExpandableNode.Title color={color} size={14} depth={browsePathLength}>
|
||||
<ExpandableNode.Title
|
||||
color={color}
|
||||
size={14}
|
||||
depth={browsePathLength}
|
||||
maxWidth={hasEntityLink ? 175 : 200}
|
||||
padLeft
|
||||
>
|
||||
{displayName}
|
||||
</ExpandableNode.Title>
|
||||
{hasEntityLink && <EntityLink entity={entity} targetNode="browse" />}
|
||||
</ExpandableNode.HeaderLeft>
|
||||
<Count color={color}>{formatNumber(count)}</Count>
|
||||
</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)`
|
||||
font-size: 12px;
|
||||
color: ${(props) => props.color};
|
||||
padding-left: 4px;
|
||||
`;
|
||||
|
||||
const EntityNode = () => {
|
||||
@ -62,10 +63,8 @@ const EntityNode = () => {
|
||||
data-testid={`browse-entity-${registry.getCollectionName(entityType)}`}
|
||||
>
|
||||
<ExpandableNode.HeaderLeft>
|
||||
<ExpandableNode.StaticButton
|
||||
icon={registry.getIcon(entityType, 16, IconStyleType.HIGHLIGHT, color)}
|
||||
/>
|
||||
<ExpandableNode.Title color={color} size={16}>
|
||||
{registry.getIcon(entityType, 16, IconStyleType.HIGHLIGHT, color)}
|
||||
<ExpandableNode.Title color={color} size={16} padLeft>
|
||||
{registry.getCollectionName(entityType)}
|
||||
</ExpandableNode.Title>
|
||||
<Count color={color}>{formatNumber(entityAggregation.count)}</Count>
|
||||
|
||||
@ -45,27 +45,27 @@ ExpandableNode.Header = styled.div<{ isOpen: boolean; isSelected?: boolean; show
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
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])};
|
||||
`;
|
||||
|
||||
ExpandableNode.SelectableHeader = styled(ExpandableNode.Header)<{ isSelected: boolean }>`
|
||||
&& {
|
||||
& {
|
||||
border: 1px solid ${(props) => (props.isSelected ? props.theme.styles['primary-color'] : 'transparent')};
|
||||
background-color: ${(props) => (props.isSelected ? props.theme.styles['primary-color-light'] : 'transparent')};
|
||||
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`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
`;
|
||||
|
||||
const BaseButton = styled(Button)`
|
||||
@ -84,8 +84,12 @@ const RotatingButton = styled(BaseButton)<{ deg: number }>`
|
||||
transition: transform 250ms;
|
||||
`;
|
||||
|
||||
ExpandableNode.StaticButton = ({ icon }: { icon: JSX.Element }) => {
|
||||
return <BaseButton ghost size="small" type="ghost" icon={icon} />;
|
||||
ExpandableNode.StaticButton = ({ icon, onClick }: { icon: JSX.Element; onClick?: () => void }) => {
|
||||
const onClickButton: MouseEventHandler = (e) => {
|
||||
e.stopPropagation();
|
||||
onClick?.();
|
||||
};
|
||||
return <BaseButton ghost size="small" type="ghost" icon={icon} onClick={onClickButton} />;
|
||||
};
|
||||
|
||||
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
|
||||
const BaseTitleContainer = styled.div<{ depth: number }>`
|
||||
max-width: ${(props) => 200 - props.depth * 8}px;
|
||||
const BaseTitleContainer = styled.div<{ depth: number; maxWidth: number; padLeft: boolean }>`
|
||||
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 }>`
|
||||
@ -143,15 +148,19 @@ ExpandableNode.Title = ({
|
||||
size,
|
||||
depth = 0,
|
||||
children,
|
||||
maxWidth = 200,
|
||||
padLeft = false,
|
||||
}: {
|
||||
color: string;
|
||||
size: number;
|
||||
depth?: number;
|
||||
children: ReactNode;
|
||||
maxWidth?: number;
|
||||
padLeft?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<BaseTitleContainer depth={depth}>
|
||||
<BaseTitle ellipsis={{ tooltip: true }} color={color} size={size}>
|
||||
<BaseTitleContainer depth={depth} maxWidth={maxWidth} padLeft={padLeft}>
|
||||
<BaseTitle ellipsis={{ tooltip: { mouseEnterDelay: 1 } }} color={color} size={size}>
|
||||
{children}
|
||||
</BaseTitle>
|
||||
</BaseTitleContainer>
|
||||
|
||||
@ -81,7 +81,7 @@ const PlatformNode = () => {
|
||||
dataTestId={`browse-platform-${label}`}
|
||||
/>
|
||||
<PlatformIconContainer>{icon}</PlatformIconContainer>
|
||||
<ExpandableNode.Title color={color} size={14}>
|
||||
<ExpandableNode.Title color={color} size={14} padLeft>
|
||||
{label}
|
||||
</ExpandableNode.Title>
|
||||
</ExpandableNode.HeaderLeft>
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Alert, Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledAlert = styled(Alert)`
|
||||
white-space: normal;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
onClickRetry?: () => void;
|
||||
@ -7,8 +12,8 @@ type Props = {
|
||||
|
||||
const SidebarLoadingError = ({ onClickRetry }: Props) => {
|
||||
return (
|
||||
<Alert
|
||||
message="There was a problem loading the sidebar."
|
||||
<StyledAlert
|
||||
message="The sidebar failed to load."
|
||||
showIcon
|
||||
type="error"
|
||||
action={
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { EntityType } from '../../../types.generated';
|
||||
import { BrowseV2SelectNodeEvent, BrowseV2ToggleNodeEvent, EventType } from '../../analytics';
|
||||
import {
|
||||
BrowseV2EntityLinkClickEvent,
|
||||
BrowseV2SelectNodeEvent,
|
||||
BrowseV2ToggleNodeEvent,
|
||||
EventType,
|
||||
} from '../../analytics';
|
||||
import analytics from '../../analytics/analytics';
|
||||
import { useEntityRegistry } from '../../useEntityRegistry';
|
||||
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;
|
||||
|
||||
@ -21,6 +21,7 @@ public enum DataHubUsageEventType {
|
||||
BROWSE_V2_TOGGLE_SIDEBAR_EVENT("BrowseV2ToggleSidebarEvent"),
|
||||
BROWSE_V2_TOGGLE_NODE_EVENT("BrowseV2ToggleNodeEvent"),
|
||||
BROWSE_V2_SELECT_NODE_EVENT("BrowseV2SelectNodeEvent"),
|
||||
BROWSE_V2_ENTITY_LINK_CLICK_EVENT("BrowseV2EntityLinkClickEvent"),
|
||||
ENTITY_VIEW_EVENT("EntityViewEvent"),
|
||||
ENTITY_SECTION_VIEW_EVENT("EntitySectionViewEvent"),
|
||||
ENTITY_ACTION_EVENT("EntityActionEvent"),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user