Skip to content

Commit

Permalink
Merge pull request #7 from screwdriver-cd/logicalOR
Browse files Browse the repository at this point in the history
feat(770): support external pipelines as nodes, logical OR
  • Loading branch information
d2lam committed Oct 27, 2017
2 parents 87834ff + c4f34a1 commit db42e0d
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 10 deletions.
28 changes: 18 additions & 10 deletions lib/getWorkflow.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
'use strict';

/**
* Remove the ~ prefix for logical OR on select node names.
* @method filterNodeName
* @param {String} name A Node Name, e.g. foo, ~foo, ~pr, ~commit, ~sd@1234:foo
* @return {String} A filtered node name, e.g. foo, foo, ~pr, ~commit, ~sd@1234:foo
*/
const filterNodeName = name => (/^~(pr|commit|sd@)/.test(name) ? name : name.replace('~', ''));

/**
* Get the list of nodes for the graph
* @method calculateNodes
* @param {Object} jobs Hash of job configs
* @return {Array} List of nodes (jobs)
*/
const calculateNodes = (jobs) => {
const nodes = [
{ name: '~pr' },
{ name: '~commit' }
];
const nodes = new Set(['~pr', '~commit']);

Object.keys(jobs).forEach((name) => {
nodes.push({ name });
nodes.add(name);
if (Array.isArray(jobs[name].requires)) {
jobs[name].requires.forEach(n => nodes.add(filterNodeName(n)));
}
});

return nodes;
return [...nodes].map(name => ({ name }));
};

/**
Expand Down Expand Up @@ -57,12 +65,12 @@ const calculateEdges = (jobs) => {
const dest = j;

if (Array.isArray(job.requires)) {
const specialTriggers = job.requires.filter(name => name.charAt(0) === '~');
const normalTriggers = job.requires.filter(name => name.charAt(0) !== '~');
const isJoin = normalTriggers.length > 1;
const specialTriggers = new Set(job.requires.filter(name => name.charAt(0) === '~'));
const normalTriggers = new Set(job.requires.filter(name => name.charAt(0) !== '~'));
const isJoin = normalTriggers.size > 1;

specialTriggers.forEach((src) => {
edges.push({ src, dest });
edges.push({ src: filterNodeName(src), dest });
});

normalTriggers.forEach((src) => {
Expand Down
17 changes: 17 additions & 0 deletions test/data/expected-external.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"nodes": [
{ "name": "~pr" },
{ "name": "~commit" },
{ "name": "main" },
{ "name": "foo" },
{ "name": "bar" },
{ "name": "~sd@1234:foo" }
],
"edges": [
{ "src": "~pr", "dest": "main" },
{ "src": "~commit", "dest": "main" },
{ "src": "main", "dest": "foo" },
{ "src": "~sd@1234:foo", "dest": "bar" },
{ "src": "foo", "dest": "bar" }
]
}
7 changes: 7 additions & 0 deletions test/data/requires-workflow-exttrigger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"jobs": {
"main": { "requires": ["~pr", "~commit"] },
"foo": { "requires": ["main"] },
"bar": { "requires": ["foo", "~sd@1234:foo"] }
}
}
95 changes: 95 additions & 0 deletions test/lib/getWorkflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ const LEGACY_AND_REQUIRES_WORKFLOW = Object.assign({}, REQUIRES_WORKFLOW);

LEGACY_WITH_WORKFLOW.workflow = ['foo', 'bar'];

const EXTERNAL_TRIGGER = require('../data/requires-workflow-exttrigger');

const EXPECTED_OUTPUT = require('../data/expected-output');
const NO_EDGES = Object.assign({}, EXPECTED_OUTPUT);
const EXPECTED_EXTERNAL = require('../data/expected-external');

NO_EDGES.edges = [];

Expand All @@ -34,6 +37,9 @@ describe('getWorkflow', () => {
assert.deepEqual(getWorkflow(REQUIRES_WORKFLOW, {
useLegacy: true
}), EXPECTED_OUTPUT, 'requires-style workflow');
assert.deepEqual(getWorkflow(EXTERNAL_TRIGGER, {
useLegacy: true
}), EXPECTED_EXTERNAL, 'requires-style workflow with external trigger');
assert.deepEqual(getWorkflow(LEGACY_AND_REQUIRES_WORKFLOW, {
useLegacy: true
}), EXPECTED_OUTPUT, 'both legacy and non-legacy workflows');
Expand All @@ -51,6 +57,8 @@ describe('getWorkflow', () => {
EXPECTED_OUTPUT, 'requires-style workflow');
assert.deepEqual(getWorkflow(LEGACY_AND_REQUIRES_WORKFLOW),
EXPECTED_OUTPUT, 'both legacy and non-legacy workflows');
assert.deepEqual(getWorkflow(EXTERNAL_TRIGGER),
EXPECTED_EXTERNAL, 'requires-style workflow with external trigger');
});

it('should handle detatched jobs', () => {
Expand All @@ -67,6 +75,93 @@ describe('getWorkflow', () => {
});
});

it('should handle logical OR requires', () => {
const result = getWorkflow({
jobs: {
foo: { requires: ['~commit'] },
A: { requires: ['foo'] },
B: { requires: ['foo'] },
C: { requires: ['~A', '~B', '~sd@1234:foo'] }
}
});

assert.deepEqual(result, {
nodes: [
{ name: '~pr' },
{ name: '~commit' },
{ name: 'foo' },
{ name: 'A' },
{ name: 'B' },
{ name: 'C' },
{ name: '~sd@1234:foo' }
],
edges: [
{ src: '~commit', dest: 'foo' },
{ src: 'foo', dest: 'A' },
{ src: 'foo', dest: 'B' },
{ src: 'A', dest: 'C' },
{ src: 'B', dest: 'C' },
{ src: '~sd@1234:foo', dest: 'C' }
]
});
});

it('should handle logical OR and logial AND requires', () => {
const result = getWorkflow({
jobs: {
foo: { requires: ['~commit'] },
A: { requires: ['foo'] },
B: { requires: ['foo'] },
C: { requires: ['~A', '~B', 'D', 'E'] },
D: {},
E: {}
}
});

assert.deepEqual(result, {
nodes: [
{ name: '~pr' },
{ name: '~commit' },
{ name: 'foo' },
{ name: 'A' },
{ name: 'B' },
{ name: 'C' },
{ name: 'D' },
{ name: 'E' }
],
edges: [
{ src: '~commit', dest: 'foo' },
{ src: 'foo', dest: 'A' },
{ src: 'foo', dest: 'B' },
{ src: 'A', dest: 'C' },
{ src: 'B', dest: 'C' },
{ src: 'D', dest: 'C', join: true },
{ src: 'E', dest: 'C', join: true }
]
});
});

it('should dedupe requires', () => {
const result = getWorkflow({
jobs: {
foo: { requires: ['A', 'A', 'A'] },
A: {}
}
});

assert.deepEqual(result, {
nodes: [
{ name: '~pr' },
{ name: '~commit' },
{ name: 'foo' },
{ name: 'A' }
],
edges: [
{ src: 'A', dest: 'foo' }
]
});
});

it('should handle joins', () => {
const result = getWorkflow({
jobs: {
Expand Down

0 comments on commit db42e0d

Please sign in to comment.