diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx index 91bb9064f896a..a6f4f4793266b 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx @@ -8,7 +8,11 @@ import {AssetNodeMenuProps, useAssetNodeMenu} from './AssetNodeMenu'; import {buildAssetNodeStatusContent} from './AssetNodeStatusContent'; import {ContextMenuWrapper} from './ContextMenuWrapper'; import {LiveDataForNode} from './Utils'; -import {ASSET_NODE_NAME_MAX_LENGTH} from './layout'; +import { + ASSET_NODE_NAME_MAX_LENGTH, + ASSET_NODE_STATUS_ROW_HEIGHT, + ASSET_NODE_TAGS_HEIGHT, +} from './layout'; import {gql} from '../apollo-client'; import {AssetNodeFragment} from './types/AssetNode.types'; import {withMiddleTruncation} from '../app/Util'; @@ -30,19 +34,23 @@ interface Props { export const AssetNode = React.memo(({definition, selected, kindFilter}: Props) => { const {liveData} = useAssetLiveData(definition.assetKey); + const hasChecks = (liveData?.assetChecks || []).length > 0; + + const marginTopForCenteringNode = !hasChecks ? ASSET_NODE_STATUS_ROW_HEIGHT / 2 : 0; + return ( - - - - + + + + @@ -59,16 +67,17 @@ export const AssetNode = React.memo(({definition, selected, kindFilter}: Props) - {(liveData?.assetChecks || []).length > 0 && ( - - )} + {hasChecks && } - + {definition.kinds.map((kind) => ( ))} @@ -272,7 +281,7 @@ export const ASSET_NODE_FRAGMENT = gql` `; export const AssetInsetForHoverEffect = styled.div` - padding: 10px 4px 2px 4px; + padding: 2px 4px 2px 4px; height: 100%; & *:focus { @@ -283,7 +292,7 @@ export const AssetInsetForHoverEffect = styled.div` export const AssetNodeContainer = styled.div<{$selected: boolean}>` user-select: none; cursor: pointer; - padding: 6px; + padding: 0 6px; overflow: clip; `; @@ -307,6 +316,7 @@ export const AssetNodeBox = styled.div<{ background: ${Colors.backgroundDefault()}; border-radius: 10px; position: relative; + margin: 6px 0; transition: all 150ms linear; &:hover { ${(p) => !p.$selected && `border: 2px solid ${Colors.lineageNodeBorderHover()};`}; @@ -322,6 +332,7 @@ export const AssetNodeBox = styled.div<{ const NameCSS: CSSObject = { padding: '3px 0 3px 6px', color: Colors.textDefault(), + fontSize: 14, fontFamily: FontFamily.monospace, fontWeight: 600, }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts index 959a3d4da23da..34e8e8f4872db 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts @@ -65,15 +65,7 @@ export const AssetNodeFragmentBasic: AssetNodeFragment = buildAssetNode({ jobNames: ['job1'], opNames: ['asset1'], opVersion: '1', - changedReasons: [ - ChangeReason.NEW, - ChangeReason.CODE_VERSION, - ChangeReason.DEPENDENCIES, - ChangeReason.PARTITIONS_DEFINITION, - ChangeReason.TAGS, - ChangeReason.METADATA, - ChangeReason.REMOVED, - ], + changedReasons: [], }); export const AssetNodeFragmentSource = buildAssetNode({ @@ -88,6 +80,7 @@ export const AssetNodeFragmentSource = buildAssetNode({ }); export const AssetNodeFragmentSourceOverdue = buildAssetNode({ + ...AssetNodeFragmentBasic, isMaterializable: false, isObservable: false, freshnessInfo: buildAssetFreshnessInfo({ @@ -95,6 +88,20 @@ export const AssetNodeFragmentSourceOverdue = buildAssetNode({ }), }); +export const AssetNodeFragmentChangedInBranch = buildAssetNode({ + ...AssetNodeFragmentBasic, + kinds: ['sql'], + changedReasons: [ + ChangeReason.NEW, + ChangeReason.CODE_VERSION, + ChangeReason.DEPENDENCIES, + ChangeReason.PARTITIONS_DEFINITION, + ChangeReason.TAGS, + ChangeReason.METADATA, + ChangeReason.REMOVED, + ], +}); + export const AssetNodeFragmentPartitioned: AssetNodeFragment = buildAssetNode({ ...AssetNodeFragmentBasic, assetKey: buildAssetKey({path: ['asset_partioned']}), @@ -789,6 +796,24 @@ export const AssetNodeScenariosBase = [ definition: AssetNodeFragmentBasic, expectedText: ['Materialized', 'Checks'], }, + { + title: 'Changed in Branch', + liveData: LiveDataForNodeMaterializedWithChecks, + definition: AssetNodeFragmentChangedInBranch, + expectedText: ['New in branch'], + }, + { + title: 'Very long key', + liveData: { + ...LiveDataForNodeMaterialized, + stepKey: 'very_long_asset_which_was_totally_reasonable_at_the_time', + }, + definition: { + ...AssetNodeFragmentBasic, + assetKey: buildAssetKey({path: ['very_long_asset_which_was_totally_reasonable_at_the_time']}), + }, + expectedText: [], + }, ]; export const AssetNodeScenariosSource = [ @@ -801,10 +826,11 @@ export const AssetNodeScenariosSource = [ { title: 'Source Asset - Not Observable', - liveData: undefined, + liveData: LiveDataForNodeNeverMaterialized, definition: { ...AssetNodeFragmentSource, isObservable: false, + isExecutable: false, id: '["source_asset_no"]', assetKey: buildAssetKey({path: ['source_asset_no']}), }, @@ -813,10 +839,11 @@ export const AssetNodeScenariosSource = [ { title: 'Source Asset - Not Observable, No Description', - liveData: undefined, + liveData: LiveDataForNodeNeverMaterialized, definition: { ...AssetNodeFragmentSource, isObservable: false, + isExecutable: false, description: null, id: '["source_asset_nono"]', assetKey: buildAssetKey({path: ['source_asset_nono']}), @@ -902,12 +929,27 @@ export const AssetNodeScenariosPartitioned = [ expectedText: ['1,500 partitions', 'All'], }, + { + title: 'Partitioned Asset - Checks and Tags', + liveData: { + ...LiveDataForNodePartitionedFresh, + assetChecks: LiveDataForNodeMaterializedWithChecks.assetChecks, + }, + definition: { + ...AssetNodeFragmentPartitioned, + changedReasons: [ChangeReason.NEW], + kinds: ['ipynb'], + }, + expectedText: ['1,500 partitions', 'All', 'Checks'], + }, + { title: 'Partitioned Asset - Last Run Failed', liveData: LiveDataForNodePartitionedLatestRunFailed, definition: AssetNodeFragmentPartitioned, expectedText: ['4', '999+', '1,500 partitions'], }, + { title: 'Partitioned Asset - Live Data Loading', liveData: undefined, diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx index 8174a9b4febb1..a2de7eef57244 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx @@ -56,24 +56,32 @@ export const LiveStates = () => { <> + + {scenario.title} +
-
-
+
+
- - {scenario.title} - ); @@ -82,13 +90,15 @@ export const LiveStates = () => { return ( +

Base Assets

{Mocks.AssetNodeScenariosBase.map(caseWithLiveData)} - +

Source Assets

{Mocks.AssetNodeScenariosSource.map(caseWithLiveData)} +

Partitioned Assets

{Mocks.AssetNodeScenariosPartitioned.map(caseWithLiveData)} @@ -107,9 +117,10 @@ export const Links = () => { ); }; + export const PartnerTags = () => { const caseWithComputeKind = (computeKind: string) => { - const def = {...Mocks.AssetNodeFragmentBasic, computeKind}; + const def = {...Mocks.AssetNodeFragmentBasic, kinds: [computeKind]}; const liveData = Mocks.LiveDataForNodeMaterialized; function SetCacheEntry() { @@ -142,6 +153,7 @@ export const PartnerTags = () => { width: 280, height: dimensions.height, overflowY: 'hidden', + background: `linear-gradient(to bottom, transparent 49%, gray 50%, transparent 51%)`, }} > diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__tests__/AssetNode.test.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__tests__/AssetNode.test.tsx index 58fcb3eb581ee..0116783bd5119 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__tests__/AssetNode.test.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__tests__/AssetNode.test.tsx @@ -2,6 +2,7 @@ import {MockedProvider} from '@apollo/client/testing'; import {render, screen, waitFor} from '@testing-library/react'; import {MemoryRouter} from 'react-router-dom'; +import {withMiddleTruncation} from '../../app/Util'; import {AssetBaseData} from '../../asset-data/AssetBaseDataProvider'; import {AssetLiveDataProvider} from '../../asset-data/AssetLiveDataProvider'; import {AssetStaleStatusData} from '../../asset-data/AssetStaleStatusDataProvider'; @@ -13,6 +14,7 @@ import { AssetNodeScenariosPartitioned, AssetNodeScenariosSource, } from '../__fixtures__/AssetNode.fixtures'; +import {ASSET_NODE_NAME_MAX_LENGTH} from '../layout'; const Scenarios = [ ...AssetNodeScenariosBase, @@ -66,7 +68,13 @@ describe('AssetNode', () => { await waitFor(() => { const assetKey = definitionCopy.assetKey; const displayName = assetKey.path[assetKey.path.length - 1]!; - expect(screen.getByText(displayName)).toBeVisible(); + expect( + screen.getByText( + withMiddleTruncation(displayName, { + maxLength: ASSET_NODE_NAME_MAX_LENGTH, + }), + ), + ).toBeVisible(); for (const text of scenario.expectedText) { expect(screen.getByText(new RegExp(text))).toBeVisible(); } diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/layout.ts b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/layout.ts index 17e0db7f2f637..56d9c67b07882 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/layout.ts +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/layout.ts @@ -71,7 +71,7 @@ export const Config = { nodesep: -10, nodeHeight: 'auto', groupPaddingTop: 65, - groupPaddingBottom: -15, + groupPaddingBottom: -4, groupRendering: 'if-varied', clusterpaddingtop: 100, }, @@ -86,7 +86,7 @@ export const Config = { edgesep: 10, nodeHeight: 'auto', groupPaddingTop: 55, - groupPaddingBottom: -5, + groupPaddingBottom: -4, groupRendering: 'if-varied', }, }; @@ -338,7 +338,10 @@ export const extendBounds = (a: IBounds, b: IBounds) => { }; export const ASSET_NODE_WIDTH = 320; -export const ASSET_NODE_NAME_MAX_LENGTH = 38; +export const ASSET_NODE_TAGS_HEIGHT = 28; +export const ASSET_NODE_STATUS_ROW_HEIGHT = 25; + +export const ASSET_NODE_NAME_MAX_LENGTH = 31; export const getAssetNodeDimensions = (def: { assetKey: {path: string[]}; @@ -351,24 +354,19 @@ export const getAssetNodeDimensions = (def: { computeKind: string | null; changedReasons?: ChangeReason[]; }) => { - const width = ASSET_NODE_WIDTH; + let height = 0; - let height = 106; // top tags area + name + description + height += ASSET_NODE_TAGS_HEIGHT; // top tags - if (!def.isMaterializable && def.isObservable) { - height += 30; // status row - } else { - height += 28; // status row - height += 28; // checks row - if (def.isPartitioned) { - height += 52; - } - } - if (def.changedReasons?.length) { - height += 30; + height += 76; // box padding + border + name + description + + if (def.isPartitioned && def.isMaterializable) { + height += ASSET_NODE_STATUS_ROW_HEIGHT; } - height += 36; // tags beneath + height += ASSET_NODE_STATUS_ROW_HEIGHT; // status row + height += ASSET_NODE_STATUS_ROW_HEIGHT; // checks row + height += ASSET_NODE_TAGS_HEIGHT; // bottom tags - return {width, height}; + return {width: ASSET_NODE_WIDTH, height}; }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/graph/asyncGraphLayout.ts b/js_modules/dagster-ui/packages/ui-core/src/graph/asyncGraphLayout.ts index e278d8cd78ffc..5ecd65b65199a 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/graph/asyncGraphLayout.ts +++ b/js_modules/dagster-ui/packages/ui-core/src/graph/asyncGraphLayout.ts @@ -63,7 +63,7 @@ const _assetLayoutCacheKey = (graphData: GraphData, opts: LayoutAssetGraphOption } return `${JSON.stringify(opts)}${JSON.stringify({ - version: 2, + version: 3, downstream: recreateObjectWithKeysSorted(graphData.downstream), upstream: recreateObjectWithKeysSorted(graphData.upstream), nodes: Object.keys(graphData.nodes)