diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetGraphExplorer.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetGraphExplorer.tsx index 33174e4debfe0..d08d44fa261d2 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetGraphExplorer.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetGraphExplorer.tsx @@ -75,7 +75,10 @@ type Props = { explorerPath: ExplorerPath; onChangeExplorerPath: (path: ExplorerPath, mode: 'replace' | 'push') => void; - onNavigateToSourceAssetNode: (node: AssetLocation) => void; + onNavigateToSourceAssetNode: ( + e: Pick, 'metaKey'>, + node: AssetLocation, + ) => void; isGlobalGraph?: boolean; trace?: PageLoadTrace; }; @@ -237,7 +240,7 @@ const AssetGraphExplorerWithData = ({ if (!nodeIsInDisplayedGraph) { // The asset's definition was not provided in our query for job.assetNodes. It's either // in another job or asset group, or is a source asset not defined in any repository. - return onNavigateToSourceAssetNode(await findAssetLocation(assetKey)); + return onNavigateToSourceAssetNode(e, await findAssetLocation(assetKey)); } // This asset is in a job and we can stay in the job graph explorer! diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetGroupRoot.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetGroupRoot.tsx index d006935df8057..c9b6ae6502e7c 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetGroupRoot.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetGroupRoot.tsx @@ -72,18 +72,22 @@ export const AssetGroupRoot = ({ ); const onNavigateToSourceAssetNode = useCallback( - (node: AssetLocation) => { + (e: Pick, 'metaKey'>, node: AssetLocation) => { + let path; if (node.groupName && node.repoAddress) { - history.push( - workspacePathFromAddress( - node.repoAddress, - `/asset-groups/${node.groupName}/lineage/${node.assetKey.path - .map(encodeURIComponent) - .join('/')}`, - ), + path = workspacePathFromAddress( + node.repoAddress, + `/asset-groups/${node.groupName}/lineage/${node.assetKey.path + .map(encodeURIComponent) + .join('/')}`, ); } else { - history.push(assetDetailsPathForKey(node.assetKey, {view: 'definition'})); + path = assetDetailsPathForKey(node.assetKey, {view: 'definition'}); + } + if (e.metaKey) { + window.open(path, '_blank'); + } else { + history.push(path); } }, [history], diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeLineageGraph.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeLineageGraph.tsx index 95148484674f0..e193457993271 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeLineageGraph.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeLineageGraph.tsx @@ -1,5 +1,5 @@ import {Box, Spinner} from '@dagster-io/ui-components'; -import {useMemo, useRef, useState} from 'react'; +import React, {useMemo, useRef, useState} from 'react'; import {useHistory} from 'react-router-dom'; import styled from 'styled-components'; @@ -53,10 +53,17 @@ export const AssetNodeLineageGraph = ({ const viewportEl = useRef(); const history = useHistory(); - const onClickAsset = (key: AssetKey) => { - history.push( - assetDetailsPathForKey(key, {...params, lineageScope: 'neighbors', lineageDepth: 1}), - ); + const onClickAsset = (e: React.MouseEvent, key: AssetKey) => { + const path = assetDetailsPathForKey(key, { + ...params, + lineageScope: 'neighbors', + lineageDepth: 1, + }); + if (e.metaKey) { + window.open(document.location.host + path); + } else { + history.push(path); + } }; useLastSavedZoomLevel(viewportEl, layout, assetGraphId); @@ -149,7 +156,7 @@ export const AssetNodeLineageGraph = ({ style={{overflow: 'visible'}} onMouseEnter={() => setHighlighted([id])} onMouseLeave={() => setHighlighted(null)} - onClick={() => onClickAsset({path})} + onClick={(e) => onClickAsset(e, {path})} onDoubleClick={(e) => { viewportEl.current?.zoomToSVGBox(bounds, true, 1.2); e.stopPropagation(); diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsGroupsGlobalGraphRoot.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsGroupsGlobalGraphRoot.tsx index 07b26bc861480..e93a6568783a2 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsGroupsGlobalGraphRoot.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsGroupsGlobalGraphRoot.tsx @@ -42,8 +42,13 @@ export const AssetsGroupsGlobalGraphRoot = () => { ); const onNavigateToSourceAssetNode = useCallback( - (node: AssetLocation) => { - history.push(assetDetailsPathForKey(node.assetKey, {view: 'definition'})); + (e: Pick, 'metaKey'>, node: AssetLocation) => { + const path = assetDetailsPathForKey(node.assetKey, {view: 'definition'}); + if (e.metaKey) { + window.open(path, '_blank'); + } else { + history.push(path); + } }, [history], ); diff --git a/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineExplorerRoot.tsx b/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineExplorerRoot.tsx index fc44e5a0893e8..4911059f2702a 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineExplorerRoot.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineExplorerRoot.tsx @@ -44,8 +44,13 @@ export const PipelineExplorerSnapshotRoot = () => { onChangeExplorerPath={(path, mode) => { history[mode](`/snapshots/${explorerPathToString(path)}`); }} - onNavigateToSourceAssetNode={({assetKey}) => { - history.push(assetDetailsPathForKey(assetKey)); + onNavigateToSourceAssetNode={(e, {assetKey}) => { + const path = assetDetailsPathForKey(assetKey); + if (e.metaKey) { + window.open(path, '_blank'); + } else { + history.push(assetDetailsPathForKey(assetKey)); + } }} /> ); @@ -60,7 +65,10 @@ export const PipelineExplorerContainer = ({ }: { explorerPath: ExplorerPath; onChangeExplorerPath: (path: ExplorerPath, mode: 'replace' | 'push') => void; - onNavigateToSourceAssetNode: (node: AssetLocation) => void; + onNavigateToSourceAssetNode: ( + e: Pick, 'metaKey'>, + node: AssetLocation, + ) => void; repoAddress?: RepoAddress; isGraph?: boolean; }) => { diff --git a/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineOverviewRoot.tsx b/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineOverviewRoot.tsx index 0b11e96eb725a..ece0d2113314a 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineOverviewRoot.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/pipelines/PipelineOverviewRoot.tsx @@ -51,12 +51,17 @@ export const PipelineOverviewRoot = (props: Props) => { ); const onNavigateToSourceAssetNode = useCallback( - (node: AssetLocation) => { + (e: Pick, 'metaKey'>, node: AssetLocation) => { if (!node.jobName || !node.opNames.length || !node.repoAddress) { // This op has no definition in any loaded repository (source asset). // The best we can do is show the asset page. This will still be mostly empty, // but there can be a description. - history.push(assetDetailsPathForKey(node.assetKey, {view: 'definition'})); + const path = assetDetailsPathForKey(node.assetKey, {view: 'definition'}); + if (e.metaKey) { + window.open(path, '_blank'); + } else { + history.push(path); + } return; }