Container link in browse v2 sidebar (#8305)

This commit is contained in:
Joshua Eilers 2023-06-28 15:20:31 -07:00 committed by GitHub
parent 6b8c4c83bc
commit 80fcb21169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 141 additions and 26 deletions

View File

@ -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

View File

@ -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;
}; };
/** /**

View File

@ -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>
); );

View File

@ -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>

View 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;

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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={

View File

@ -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;

View File

@ -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"),