diff --git a/frontend/src/lib/ParserUtils.ts b/frontend/src/lib/ParserUtils.ts new file mode 100644 index 00000000000..0442b0604d8 --- /dev/null +++ b/frontend/src/lib/ParserUtils.ts @@ -0,0 +1,16 @@ +import { Metadata } from 'third_party/argo-ui/argo_template'; + +export function parseTaskDisplayName(metadata?: Metadata): string | undefined { + if (!metadata?.annotations) { + return undefined; + } + const taskDisplayName = metadata.annotations['pipelines.kubeflow.org/task_display_name']; + let componentDisplayName: string | undefined; + try { + componentDisplayName = JSON.parse(metadata.annotations['pipelines.kubeflow.org/component_spec']) + .name; + } catch (err) { + // Expected error: metadata is missing or malformed + } + return taskDisplayName || componentDisplayName; +} diff --git a/frontend/src/lib/StaticGraphParser.test.ts b/frontend/src/lib/StaticGraphParser.test.ts index 17da3076024..b85c44d5c58 100644 --- a/frontend/src/lib/StaticGraphParser.test.ts +++ b/frontend/src/lib/StaticGraphParser.test.ts @@ -143,6 +143,28 @@ describe('StaticGraphParser', () => { expect(g.node('/task-1').label).toEqual('TaskDisplayName'); }); + it("uses task's annotation component_name as node label when present", () => { + const workflow = newWorkflow(); + workflow.spec.templates[1].metadata = { + annotations: { + 'pipelines.kubeflow.org/component_spec': '{"name":"Component Display Name"}', + }, + }; + const g = createGraph(workflow); + expect(g.node('/task-1').label).toEqual('Component Display Name'); + }); + + it("uses task's default node label when component_name is malformed", () => { + const workflow = newWorkflow(); + workflow.spec.templates[1].metadata = { + annotations: { + 'pipelines.kubeflow.org/component_spec': '"name":"Component Display Name"}', + }, + }; + const g = createGraph(workflow); + expect(g.node('/task-1').label).not.toEqual('Component Display Name'); + }); + it('adds an unconnected node for the onExit template, if onExit is specified', () => { const workflow = newWorkflow(); workflow.spec.onExit = 'on-exit'; diff --git a/frontend/src/lib/StaticGraphParser.ts b/frontend/src/lib/StaticGraphParser.ts index f5f82ea4c03..5bc3e26226e 100644 --- a/frontend/src/lib/StaticGraphParser.ts +++ b/frontend/src/lib/StaticGraphParser.ts @@ -19,6 +19,7 @@ import { Template, Workflow } from '../../third_party/argo-ui/argo_template'; import { color } from '../Css'; import { Constants } from './Constants'; import { logger } from './Utils'; +import { parseTaskDisplayName } from './ParserUtils'; export type nodeType = 'container' | 'resource' | 'dag' | 'unknown'; @@ -177,13 +178,7 @@ function buildDag( if (child.nodeType === 'dag') { buildDag(graph, task.template, templates, alreadyVisited, nodeId); } else if (child.nodeType === 'container' || child.nodeType === 'resource') { - const metadata = child.template.metadata; - if (metadata && metadata.annotations) { - const displayName = metadata.annotations['pipelines.kubeflow.org/task_display_name']; - if (displayName) { - nodeLabel = displayName; - } - } + nodeLabel = parseTaskDisplayName(child.template.metadata) || nodeLabel; _populateInfoFromTemplate(info, child.template); } else { throw new Error( diff --git a/frontend/src/lib/WorkflowParser.test.ts b/frontend/src/lib/WorkflowParser.test.ts index f54214a88ec..7dc0cb8ac15 100644 --- a/frontend/src/lib/WorkflowParser.test.ts +++ b/frontend/src/lib/WorkflowParser.test.ts @@ -18,6 +18,7 @@ import { color } from '../Css'; import { NodePhase } from '../lib/StatusUtils'; import { Constants } from './Constants'; import WorkflowParser, { StorageService } from './WorkflowParser'; +import { Workflow } from 'third_party/argo-ui/argo_template'; describe('WorkflowParser', () => { describe('createRuntimeGraph', () => { @@ -310,15 +311,15 @@ describe('WorkflowParser', () => { expect(g.node('exitNode').label).toEqual('onExit - clean'); }); - it('gives nodes customized labels based on template annotation', () => { - const workflow = { + function singleNodeWorkflow() { + return { metadata: { name: 'testWorkflow' }, spec: { templates: [ { metadata: { annotations: { - 'pipelines.kubeflow.org/task_display_name': 'Customized name', + // 'pipelines.kubeflow.org/task_display_name': 'Customized name', }, }, name: 'some-template', @@ -337,8 +338,22 @@ describe('WorkflowParser', () => { }, }, }; - const g = WorkflowParser.createRuntimeGraph(workflow as any); + } + + it('gives nodes customized labels based on template annotation', () => { + const workflow1 = singleNodeWorkflow(); + workflow1.spec.templates[0].metadata.annotations = { + 'pipelines.kubeflow.org/task_display_name': 'Customized name', + }; + const g = WorkflowParser.createRuntimeGraph(workflow1 as any); expect(g.node('node1').label).toEqual('Customized name'); + + const workflow2 = singleNodeWorkflow(); + workflow2.spec.templates[0].metadata.annotations = { + 'pipelines.kubeflow.org/component_spec': '{"name":"Component Name"}', + }; + const g2 = WorkflowParser.createRuntimeGraph(workflow2 as any); + expect(g2.node('node1').label).toEqual('Component Name'); }); }); diff --git a/frontend/src/lib/WorkflowParser.ts b/frontend/src/lib/WorkflowParser.ts index 257b24024d8..c48a378b894 100644 --- a/frontend/src/lib/WorkflowParser.ts +++ b/frontend/src/lib/WorkflowParser.ts @@ -28,6 +28,7 @@ import { statusToIcon } from '../pages/Status'; import { Constants } from './Constants'; import { KeyValue } from './StaticGraphParser'; import { hasFinished, NodePhase, statusToBgColor } from './StatusUtils'; +import { parseTaskDisplayName } from './ParserUtils'; export enum StorageService { GCS = 'gcs', @@ -96,12 +97,7 @@ export default class WorkflowParser { const tmpl = workflow.spec.templates.find( t => !!t && !!t.name && t.name === node.templateName, ); - if (tmpl && tmpl.metadata && tmpl.metadata.annotations) { - const displayName = tmpl.metadata.annotations['pipelines.kubeflow.org/task_display_name']; - if (displayName) { - nodeLabel = displayName; - } - } + nodeLabel = parseTaskDisplayName(tmpl?.metadata) || nodeLabel; } g.setNode(node.id, {