diff --git a/app/components/pipeline/workflow/graph/component.js b/app/components/pipeline/workflow/graph/component.js index d02796579..4f44d7dbb 100644 --- a/app/components/pipeline/workflow/graph/component.js +++ b/app/components/pipeline/workflow/graph/component.js @@ -11,14 +11,22 @@ import { getElementSizes, getGraphSvg, getMaximumJobNameLength, - getNodeWidth + getNodeWidth, + icon } from 'screwdriver-ui/utils/pipeline/graph/d3-graph-util'; import { nodeCanShowTooltip } from 'screwdriver-ui/utils/pipeline/graph/tooltip'; export default class PipelineWorkflowGraphComponent extends Component { - @action - draw(element) { - const data = decorateGraph({ + decoratedGraph; + + constructor() { + super(...arguments); + + this.getDecoratedGraph(); + } + + getDecoratedGraph() { + this.decoratedGraph = decorateGraph({ inputGraph: this.args.workflowGraph, builds: this.args.builds, jobs: this.args.jobs.map(job => { @@ -29,11 +37,14 @@ export default class PipelineWorkflowGraphComponent extends Component { prNum: this.args.event.prNum, stages: this.args.stages }); + } - const isSkippedEvent = isSkipped(this.args.event, this.args.builds); + @action + draw(element) { + const isSkippedEvent = isSkipped(this.args.event); const elementSizes = getElementSizes(); const maximumJobNameLength = getMaximumJobNameLength( - data, + this.decoratedGraph, this.args.displayJobNameLength ); const nodeWidth = getNodeWidth(elementSizes, maximumJobNameLength); @@ -56,7 +67,7 @@ export default class PipelineWorkflowGraphComponent extends Component { // Add the SVG element const svg = getGraphSvg( element, - data, + this.decoratedGraph, elementSizes, maximumJobNameLength, onClickGraph @@ -67,7 +78,7 @@ export default class PipelineWorkflowGraphComponent extends Component { this.args.stages.length > 0 ? addStages( svg, - data, + this.decoratedGraph, elementSizes, nodeWidth, onClickStageMenu, @@ -78,7 +89,7 @@ export default class PipelineWorkflowGraphComponent extends Component { // edges addEdges( svg, - data, + this.decoratedGraph, elementSizes, nodeWidth, isSkippedEvent, @@ -88,7 +99,7 @@ export default class PipelineWorkflowGraphComponent extends Component { // Jobs Icons addJobIcons( svg, - data, + this.decoratedGraph, elementSizes, nodeWidth, verticalDisplacements, @@ -98,10 +109,40 @@ export default class PipelineWorkflowGraphComponent extends Component { addJobNames( svg, - data, + this.decoratedGraph, elementSizes, maximumJobNameLength, verticalDisplacements ); } + + @action + redraw(element) { + if ( + this.decoratedGraph.nodes.length !== this.args.workflowGraph.nodes.length + ) { + this.getDecoratedGraph(); + element.replaceChildren(); + this.draw(element); + + return; + } + + this.getDecoratedGraph(); + this.decoratedGraph.nodes.forEach(node => { + const n = element.querySelector(`g.graph-node[data-job="${node.name}"]`); + + if (n) { + const txt = n.querySelector('text'); + + txt.firstChild.textContent = icon(node.status); + n.setAttribute( + 'class', + `graph-node${ + node.status ? ` build-${node.status.toLowerCase()}` : '' + }` + ); + } + }); + } } diff --git a/app/components/pipeline/workflow/graph/template.hbs b/app/components/pipeline/workflow/graph/template.hbs index ebd30c855..b58ebb990 100644 --- a/app/components/pipeline/workflow/graph/template.hbs +++ b/app/components/pipeline/workflow/graph/template.hbs @@ -1 +1,4 @@ -
+
diff --git a/tests/integration/components/pipeline/workflow/graph/component-test.js b/tests/integration/components/pipeline/workflow/graph/component-test.js index d4495f6aa..3dc5d23e0 100644 --- a/tests/integration/components/pipeline/workflow/graph/component-test.js +++ b/tests/integration/components/pipeline/workflow/graph/component-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'screwdriver-ui/tests/helpers'; -import { render } from '@ember/test-helpers'; +import { render, rerender } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; module('Integration | Component | pipeline/workflow/graph', function (hooks) { @@ -252,4 +252,100 @@ module('Integration | Component | pipeline/workflow/graph', function (hooks) { assert.equal(this.element.querySelector('svg').children.length, 8); }); + + test('it rerenders graph when workflowGraph changes', async function (assert) { + const workflowGraph = { + nodes: [{ name: '~commit' }, { name: 'main' }], + edges: [{ src: '~commit', dest: 'main' }] + }; + const workflowGraphWithDownstreamTriggers = { + nodes: [ + { name: '~commit' }, + { name: 'main' }, + { name: 'sd-main-triggers', status: 'DOWNSTREAM_TRIGGER' } + ], + edges: [ + { src: '~commit', dest: 'main' }, + { src: 'main', dest: 'sd-main-triggers' } + ] + }; + const event = { startFrom: '~commit' }; + const jobs = [{ id: 1 }]; + const builds = [{ id: 1, jobId: 1, status: 'SUCCESS' }]; + const stages = []; + const displayJobNameLength = 20; + + this.setProperties({ + workflowGraph, + event, + jobs, + builds, + stages, + displayJobNameLength + }); + await render( + hbs`` + ); + + assert.equal(this.element.querySelector('svg').children.length, 5); + + this.setProperties({ workflowGraph: workflowGraphWithDownstreamTriggers }); + await rerender(); + + assert.equal(this.element.querySelector('svg').children.length, 8); + }); + + test('it rerenders graph when builds update', async function (assert) { + const workflowGraph = { + nodes: [{ name: '~commit' }, { name: 'main', id: 123 }], + edges: [{ src: '~commit', dest: 'main' }] + }; + const event = { startFrom: '~commit' }; + const jobs = [{ id: 123 }]; + const builds = [{ id: 1, jobId: 123, status: 'RUNNING' }]; + const stages = []; + const displayJobNameLength = 20; + + this.setProperties({ + workflowGraph, + event, + jobs, + builds, + stages, + displayJobNameLength + }); + await render( + hbs`` + ); + + assert.dom('svg [data-job=main]').hasClass('build-running'); + + this.setProperties({ + builds: [{ id: 1, jobId: 123, status: 'SUCCESS' }], + workflowGraph, + event, + jobs, + stages, + displayJobNameLength + }); + await rerender(); + + assert.dom('svg [data-job=main]').hasClass('build-success'); + }); });