Skip to content

Commit

Permalink
[ui] Refactoring to support asset health overview (#21081)
Browse files Browse the repository at this point in the history
## Summary & Motivation

This PR extracts the fetching and filtering portions of the asset
catalog into a high-level hook so that it's easier to build UI that
re-uses the asset catalog's index caching and filtering. I also exported
a few pieces of the left asset sidebar and updated asset context menus
to allow them to appear in more places.

## How I Tested These Changes

Tested this as part of the new asset health feature in cloud

---------

Co-authored-by: bengotow <[email protected]>
  • Loading branch information
bengotow and bengotow authored Apr 22, 2024
1 parent d519ecb commit 530fae2
Show file tree
Hide file tree
Showing 22 changed files with 423 additions and 302 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ export const CollapsibleSection = ({
name="arrow_drop_down"
style={{transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)'}}
/>
<div>{header}</div>
<div style={{userSelect: 'none'}}>{header}</div>
</>
) : (
<Box flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div>{header}</div>
<Box style={{flex: 1}} flex={{justifyContent: 'space-between', alignItems: 'center'}}>
<div style={{userSelect: 'none'}}>{header}</div>
<Icon
name="arrow_drop_down"
style={{transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)'}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Box, Menu, MenuDivider, MenuItem, Spinner} from '@dagster-io/ui-components';
import * as React from 'react';

import {GraphData, GraphNode, tokenForAssetKey} from './Utils';
import {GraphData, tokenForAssetKey} from './Utils';
import {StatusDot} from './sidebar/StatusDot';
import {useAssetBaseData} from '../asset-data/AssetBaseDataProvider';
import {useExecuteAssetMenuItem} from '../assets/AssetActionMenu';
Expand All @@ -11,13 +11,24 @@ import {
AssetKeysDialogHeader,
} from '../assets/AutoMaterializePolicyPage/AssetKeysDialog';
import {assetDetailsPathForKey} from '../assets/assetDetailsPathForKey';
import {AssetKeyInput} from '../graphql/types';
import {ExplorerPath} from '../pipelines/PipelinePathUtils';
import {MenuLink} from '../ui/MenuLink';
import {VirtualizedItemListForDialog} from '../ui/VirtualizedItemListForDialog';

export type AssetNodeMenuNode = {
id: string;
assetKey: AssetKeyInput;
definition: {
isSource: boolean;
isExecutable: boolean;
hasMaterializePermission: boolean;
};
};

export type AssetNodeMenuProps = {
graphData: GraphData;
node: GraphNode;
node: AssetNodeMenuNode;
graphData?: GraphData;
explorerPath?: ExplorerPath;
onChangeExplorerPath?: (path: ExplorerPath, mode: 'replace' | 'push') => void;
selectNode?: (e: React.MouseEvent<any> | React.KeyboardEvent<any>, nodeId: string) => void;
Expand All @@ -30,8 +41,8 @@ export const useAssetNodeMenu = ({
explorerPath,
onChangeExplorerPath,
}: AssetNodeMenuProps) => {
const upstream = Object.keys(graphData.upstream[node.id] ?? {});
const downstream = Object.keys(graphData.downstream[node.id] ?? {});
const upstream = graphData ? Object.keys(graphData.upstream[node.id] ?? {}) : [];
const downstream = graphData ? Object.keys(graphData.downstream[node.id] ?? {}) : [];

const {executeItem, launchpadElement} = useExecuteAssetMenuItem(
node.assetKey.path,
Expand Down Expand Up @@ -80,7 +91,7 @@ export const useAssetNodeMenu = ({
<MenuDivider />
{executeItem}
{executeItem && (upstream.length || downstream.length) ? <MenuDivider /> : null}
{upstream.length ? (
{upstream.length && graphData ? (
<MenuItem
text={`View parents (${upstream.length})`}
icon="list"
Expand All @@ -89,14 +100,14 @@ export const useAssetNodeMenu = ({
}}
/>
) : null}
{upstream.length ? (
{upstream.length || !graphData ? (
<MenuItem
text="Show upstream graph"
icon="arrow_back"
onClick={() => showGraph(`*\"${tokenForAssetKey(node.assetKey)}\"`)}
/>
) : null}
{downstream.length ? (
{downstream.length || !graphData ? (
<MenuItem
text="Show downstream graph"
icon="arrow_forward"
Expand All @@ -107,14 +118,16 @@ export const useAssetNodeMenu = ({
),
dialog: (
<>
<UpstreamDownstreamDialog
title="Parent assets"
graphData={graphData}
assetKeys={upstream}
isOpen={showParents}
setIsOpen={setShowParents}
selectNode={selectNode}
/>
{graphData && (
<UpstreamDownstreamDialog
title="Parent assets"
graphData={graphData}
assetKeys={upstream}
isOpen={showParents}
setIsOpen={setShowParents}
selectNode={selectNode}
/>
)}
{launchpadElement}
</>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const LOADING_STATUS_CONTENT = {
),
};

type StatusContentArgs = {
export type StatusContentArgs = {
assetKey: AssetKeyInput;
definition: {opNames: string[]; isSource: boolean; isObservable: boolean};
liveData: LiveDataForNode | null | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,24 @@ export const ContextMenuWrapper = ({
wrapperInnerStyles?: React.CSSProperties;
}) => {
const [menuVisible, setMenuVisible] = React.useState(false);
const [menuPosition, setMenuPosition] = React.useState<{top: number; left: number}>({
top: 0,
left: 0,
const [menuPosition, setMenuPosition] = React.useState<{
x: number;
y: number;
anchor: 'left' | 'right';
}>({
anchor: 'left',
x: 0,
y: 0,
});

const showMenu = (e: React.MouseEvent) => {
const anchor = window.innerWidth - e.pageX < 240 ? 'right' : 'left';
e.preventDefault();
setMenuPosition({top: e.pageY, left: e.pageX});
setMenuPosition({
x: anchor === 'left' ? e.pageX : window.innerWidth - e.pageX,
y: e.pageY,
anchor,
});

if (!menuVisible) {
setMenuVisible(true);
Expand Down Expand Up @@ -78,8 +88,9 @@ export const ContextMenuWrapper = ({
<div
style={{
position: 'absolute',
top: menuPosition.top,
left: menuPosition.left,
top: menuPosition.y,
left: menuPosition.anchor === 'left' ? menuPosition.x : 'unset',
right: menuPosition.anchor === 'right' ? menuPosition.x : 'unset',
backgroundColor: Colors.popoverBackground(),
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
zIndex: 10,
Expand All @@ -102,4 +113,5 @@ export const triggerContextMenu = (e: React.MouseEvent) => {
const evt = new MouseEvent('contextmenu', e.nativeEvent);
e.target.dispatchEvent(evt);
e.stopPropagation();
e.preventDefault();
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Box, Colors, Icon, MiddleTruncate, UnstyledButton} from '@dagster-io/ui-
import * as React from 'react';
import styled from 'styled-components';

import {StatusDot} from './StatusDot';
import {StatusDot, StatusDotNode} from './StatusDot';
import {
FolderNodeCodeLocationType,
FolderNodeGroupType,
Expand All @@ -11,13 +11,13 @@ import {
} from './util';
import {ExplorerPath} from '../../pipelines/PipelinePathUtils';
import {AssetGroup} from '../AssetGraphExplorer';
import {useAssetNodeMenu} from '../AssetNodeMenu';
import {AssetNodeMenuProps, useAssetNodeMenu} from '../AssetNodeMenu';
import {useGroupNodeContextMenu} from '../CollapsedGroupNode';
import {ContextMenuWrapper, triggerContextMenu} from '../ContextMenuWrapper';
import {GraphData, GraphNode} from '../Utils';

type AssetSidebarNodeProps = {
fullAssetGraphData: GraphData;
fullAssetGraphData?: GraphData;
node: GraphNode | FolderNodeNonAssetType;
level: number;
toggleOpen: () => void;
Expand All @@ -42,10 +42,9 @@ export const AssetSidebarNode = (props: AssetSidebarNodeProps) => {
const showArrow = !isAssetNode;

return (
<Box ref={elementRef} padding={{left: 8}}>
<Box ref={elementRef} padding={{left: 8, right: 12}}>
<BoxWrapper level={level}>
<ItemContainer
padding={{right: 12}}
flex={{direction: 'row', alignItems: 'center'}}
onClick={selectThisNode}
onDoubleClick={(e) => !e.metaKey && toggleOpen()}
Expand Down Expand Up @@ -93,6 +92,16 @@ export const AssetSidebarNode = (props: AssetSidebarNodeProps) => {
);
};

type AssetSidebarAssetLabelProps = {
fullAssetGraphData?: GraphData;
node: AssetNodeMenuProps['node'] & StatusDotNode;
selectNode: (e: React.MouseEvent<any> | React.KeyboardEvent<any>, nodeId: string) => void;
isLastSelected: boolean;
isSelected: boolean;
explorerPath: ExplorerPath;
onChangeExplorerPath: (path: ExplorerPath, mode: 'replace' | 'push') => void;
};

const AssetSidebarAssetLabel = ({
node,
isSelected,
Expand All @@ -101,7 +110,7 @@ const AssetSidebarAssetLabel = ({
selectNode,
explorerPath,
onChangeExplorerPath,
}: Omit<AssetSidebarNodeProps, 'node'> & {node: GraphNode}) => {
}: AssetSidebarAssetLabelProps) => {
const {menu, dialog} = useAssetNodeMenu({
graphData: fullAssetGraphData,
node,
Expand Down Expand Up @@ -196,6 +205,7 @@ const FocusableLabelContainer = ({
<GrayOnHoverBox
ref={ref}
style={{
gridTemplateColumns: icon ? 'auto minmax(0, 1fr)' : 'minmax(0, 1fr)',
...(isSelected ? {background: Colors.backgroundBlue()} : {}),
}}
>
Expand Down Expand Up @@ -231,7 +241,7 @@ const BoxWrapper = ({level, children}: {level: number; children: React.ReactNode
const ExpandMore = styled(UnstyledButton)`
position: absolute;
top: 8px;
right: 20px;
right: 8px;
visibility: hidden;
`;

Expand All @@ -240,8 +250,8 @@ const GrayOnHoverBox = styled(UnstyledButton)`
user-select: none;
width: 100%;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
flex-direction: row;
height: 32px;
align-items: center;
padding: 5px 8px;
justify-content: space-between;
Expand All @@ -251,7 +261,7 @@ const GrayOnHoverBox = styled(UnstyledButton)`
transition: background 100ms linear;
`;

const ItemContainer = styled(Box)`
export const ItemContainer = styled(Box)`
height: 32px;
position: relative;
cursor: pointer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import {StatusCaseDot} from './util';
import {useAssetBaseData} from '../../asset-data/AssetBaseDataProvider';
import {StatusCase, buildAssetNodeStatusContent} from '../AssetNodeStatusContent';
import {GraphNode} from '../Utils';
import {AssetKeyInput} from '../../graphql/types';
import {
StatusCase,
StatusContentArgs,
buildAssetNodeStatusContent,
} from '../AssetNodeStatusContent';

export function StatusDot({node}: {node: Pick<GraphNode, 'assetKey' | 'definition'>}) {
export type StatusDotNode = {assetKey: AssetKeyInput; definition: StatusContentArgs['definition']};

export function StatusDot({node}: {node: StatusDotNode}) {
const {liveData} = useAssetBaseData(node.assetKey);

if (!liveData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Colors, Spinner, Tooltip} from '@dagster-io/ui-components';
import {useMemo} from 'react';
import styled, {keyframes} from 'styled-components';

import {AssetKeyInput} from '../../graphql/types';
import {StatusCase} from '../AssetNodeStatusContent';
import {GraphNode} from '../Utils';

Expand All @@ -28,7 +29,7 @@ export function nodePathKey(node: {path: string; id: string} | {id: string}) {
return 'path' in node ? node.path : node.id;
}

export function getDisplayName(node: GraphNode) {
export function getDisplayName(node: {assetKey: AssetKeyInput}) {
return node.assetKey.path[node.assetKey.path.length - 1]!;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type AssetFeatureContextType = {
useTabBuilder: (input: AssetTabConfigInput) => AssetTabConfig[];
renderFeatureView: (input: AssetViewFeatureInput) => React.ReactNode;
AssetColumnLinksCell: (input: {column: string | null}) => React.ReactNode;

enableAssetHealthOverviewPreview: boolean;
};

export const AssetFeatureContext = React.createContext<AssetFeatureContextType>({
Expand All @@ -35,6 +37,7 @@ export const AssetFeatureContext = React.createContext<AssetFeatureContextType>(
AssetColumnLinksCell: () => undefined,
LineageOptions: undefined,
LineageGraph: undefined,
enableAssetHealthOverviewPreview: false,
});

const renderFeatureView = () => <span />;
Expand All @@ -47,6 +50,7 @@ export const AssetFeatureProvider = ({children}: {children: React.ReactNode}) =>
AssetColumnLinksCell: () => undefined,
LineageOptions: undefined,
LineageGraph: undefined,
enableAssetHealthOverviewPreview: false,
};
}, []);

Expand Down
Loading

2 comments on commit 530fae2

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for dagit-storybook ready!

✅ Preview
https://dagit-storybook-hwopey8fd-elementl.vercel.app

Built with commit 530fae2.
This pull request is being automatically deployed with vercel-action

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for dagit-core-storybook ready!

✅ Preview
https://dagit-core-storybook-kd6truht5-elementl.vercel.app

Built with commit 530fae2.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.