diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEventMetadataEntriesTable.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEventMetadataEntriesTable.tsx index 8f3d6a7d28673..c25d230244ddc 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEventMetadataEntriesTable.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEventMetadataEntriesTable.tsx @@ -24,7 +24,11 @@ import { } from './types/useRecentAssetEvents.types'; import {Timestamp} from '../app/time/Timestamp'; import {HIDDEN_METADATA_ENTRY_LABELS, MetadataEntry} from '../metadata/MetadataEntry'; -import {isCanonicalColumnLineageEntry, isCanonicalColumnSchemaEntry} from '../metadata/TableSchema'; +import { + isCanonicalCodeSourceEntry, + isCanonicalColumnLineageEntry, + isCanonicalColumnSchemaEntry, +} from '../metadata/TableSchema'; import {MetadataEntryFragment} from '../metadata/types/MetadataEntryFragment.types'; import {titleForRun} from '../runs/RunUtils'; import {repoAddressAsHumanString} from '../workspace/repoAddressAsString'; @@ -131,7 +135,8 @@ export const AssetEventMetadataEntriesTable = ({ (row) => !HIDDEN_METADATA_ENTRY_LABELS.has(row.entry.label) && !(isCanonicalColumnSchemaEntry(row.entry) && hideTableSchema) && - !isCanonicalColumnLineageEntry(row.entry), + !isCanonicalColumnLineageEntry(row.entry) && + !isCanonicalCodeSourceEntry(row.entry), ), [allRows, filter, hideTableSchema], ); diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx index 53b7f5bc69041..81c88a8da38ba 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx @@ -8,6 +8,7 @@ import {useSetRecoilState} from 'recoil'; import {AssetEvents} from './AssetEvents'; import {AssetFeatureContext} from './AssetFeatureContext'; +import {metadataForAssetNode} from './AssetMetadata'; import {ASSET_NODE_DEFINITION_FRAGMENT, AssetNodeDefinition} from './AssetNodeDefinition'; import {ASSET_NODE_INSTIGATORS_FRAGMENT, AssetNodeInstigatorTag} from './AssetNodeInstigatorTag'; import {AssetNodeLineage} from './AssetNodeLineage'; @@ -51,8 +52,11 @@ import { } from '../asset-graph/Utils'; import {useAssetGraphData} from '../asset-graph/useAssetGraphData'; import {StaleReasonsTag} from '../assets/Stale'; +import {CodeLink} from '../code-links/CodeLink'; import {AssetComputeKindTag} from '../graph/OpTags'; +import {CodeReferencesMetadataEntry} from '../graphql/types'; import {useQueryPersistedState} from '../hooks/useQueryPersistedState'; +import {isCanonicalCodeSourceEntry} from '../metadata/TableSchema'; import {RepositoryLink} from '../nav/RepositoryLink'; import {PageLoadTrace} from '../performance'; import {useBlockTraceOnQueryResult} from '../performance/TraceContext'; @@ -284,6 +288,12 @@ export const AssetView = ({assetKey, trace, headerBreadcrumbs}: Props) => { refresh, ); + const assetMetadata = definition && metadataForAssetNode(definition).assetMetadata; + const codeSource = assetMetadata?.find((m) => isCanonicalCodeSourceEntry(m)) as + | CodeReferencesMetadataEntry + | undefined; + console.log(codeSource); + return ( { } right={ - + + {codeSource && codeSource.codeReferences && codeSource.codeReferences.length > 0 && ( + + )} {definition && definition.isObservable ? ( { +const getCodeReferenceEntryLabel = (codeReference: SourceLocation): string => { + return codeReference.label || (codeReference.filePath.split('/').pop() as string); +}; + +const getCodeReferenceLink = ( + codeLinkProtocol: ProtocolData, + codeReference: SourceLocation, +): string => { + return codeLinkProtocol.protocol + .replace('{FILE}', codeReference.filePath) + .replace('{LINE}', codeReference.lineNumber.toString()); +}; + +export const CodeLink = ({codeLinkData}: {codeLinkData: CodeReferencesMetadataEntry}) => { const [codeLinkProtocol, _] = React.useContext(CodeLinkProtocolContext); - const codeLink = codeLinkProtocol.protocol - .replace('{FILE}', file) - .replace('{LINE}', lineNumber.toString()); + const sources = codeLinkData.codeReferences; + + const hasMultipleCodeSources = sources.length > 1; + return ( - } href={codeLink}> - Open in editor - + + {hasMultipleCodeSources ? ( + + {sources.map((source) => ( + { + const codeLink = getCodeReferenceLink(codeLinkProtocol, source); + window.open(codeLink, '_blank'); + }} + /> + ))} + + } + > + + + ) : ( + } + href={getCodeReferenceLink(codeLinkProtocol, sources[0] as SourceLocation)} + style={ + hasMultipleCodeSources + ? { + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + borderRight: '0px', + } + : {} + } + > + Open {getCodeReferenceEntryLabel(sources[0] as SourceLocation)} in editor + + )} + ); }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/code-links/CodeLinkProtocol.tsx b/js_modules/dagster-ui/packages/ui-core/src/code-links/CodeLinkProtocol.tsx index f9a815508b82c..994a8e2c591cd 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/code-links/CodeLinkProtocol.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/code-links/CodeLinkProtocol.tsx @@ -21,7 +21,7 @@ const POPULAR_PROTOCOLS: {[name: string]: string} = { const DEFAULT_PROTOCOL = {protocol: Object.keys(POPULAR_PROTOCOLS)[0]!, custom: false}; -type ProtocolData = { +export type ProtocolData = { protocol: string; custom: boolean; }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/metadata/MetadataEntryFragment.tsx b/js_modules/dagster-ui/packages/ui-core/src/metadata/MetadataEntryFragment.tsx index 3d9f581fcee68..d29898e18d266 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/metadata/MetadataEntryFragment.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/metadata/MetadataEntryFragment.tsx @@ -54,6 +54,15 @@ export const METADATA_ENTRY_FRAGMENT = gql` repositoryName locationName } + ... on CodeReferencesMetadataEntry { + codeReferences { + ... on LocalFileCodeReference { + filePath + lineNumber + label + } + } + } ... on TableColumnLineageMetadataEntry { lineage { columnName diff --git a/js_modules/dagster-ui/packages/ui-core/src/metadata/TableSchema.tsx b/js_modules/dagster-ui/packages/ui-core/src/metadata/TableSchema.tsx index cc2f1d08e5f10..644d510beb769 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/metadata/TableSchema.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/metadata/TableSchema.tsx @@ -18,6 +18,7 @@ import {StyledTableWithHeader} from '../assets/AssetEventMetadataEntriesTable'; import {AssetFeatureContext} from '../assets/AssetFeatureContext'; import { AssetKeyInput, + CodeReferencesMetadataEntry, MaterializationEvent, TableColumnLineageMetadataEntry, TableSchemaMetadataEntry, @@ -49,6 +50,11 @@ export const isCanonicalColumnLineageEntry = ( ): m is TableColumnLineageMetadataEntry => m.__typename === 'TableColumnLineageMetadataEntry' && m.label === 'dagster/column_lineage'; +export const isCanonicalCodeSourceEntry = ( + m: MetadataEntryLabelOnly, +): m is CodeReferencesMetadataEntry => + m && m.__typename === 'CodeReferencesMetadataEntry' && m.label === 'dagster/code_references'; + export const TableSchemaAssetContext = createContext<{ assetKey: AssetKeyInput | undefined; materializationMetadataEntries: MetadataEntryLabelOnly[] | undefined;