summaryrefslogtreecommitdiff
path: root/spec/frontend/pipelines/components/dag
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-22 15:08:09 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-22 15:08:09 +0000
commitd6e421b21ed5574700c165cd3361094f4093ac72 (patch)
treed3c2da7baa477e210c7538bbab1acfa13167411f /spec/frontend/pipelines/components/dag
parent808c799a67a1cf2489a343a6976f55c74aec398b (diff)
downloadgitlab-ce-d6e421b21ed5574700c165cd3361094f4093ac72.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/pipelines/components/dag')
-rw-r--r--spec/frontend/pipelines/components/dag/mock_data.js44
-rw-r--r--spec/frontend/pipelines/components/dag/utils_spec.js171
2 files changed, 215 insertions, 0 deletions
diff --git a/spec/frontend/pipelines/components/dag/mock_data.js b/spec/frontend/pipelines/components/dag/mock_data.js
new file mode 100644
index 00000000000..723cdd3f525
--- /dev/null
+++ b/spec/frontend/pipelines/components/dag/mock_data.js
@@ -0,0 +1,44 @@
+/*
+ It is important that the simple base include parallel jobs
+ as well as non-parallel jobs with spaces in the name to prevent
+ us relying on spaces as an indicator.
+*/
+export default {
+ stages: [
+ {
+ name: 'test',
+ groups: [
+ {
+ name: 'jest',
+ size: 2,
+ jobs: [{ name: 'jest 1/2', needs: ['frontend fixtures'] }, { name: 'jest 2/2' }],
+ },
+ {
+ name: 'rspec',
+ size: 1,
+ jobs: [{ name: 'rspec', needs: ['frontend fixtures'] }],
+ },
+ ],
+ },
+ {
+ name: 'fixtures',
+ groups: [
+ {
+ name: 'frontend fixtures',
+ size: 1,
+ jobs: [{ name: 'frontend fixtures' }],
+ },
+ ],
+ },
+ {
+ name: 'un-needed',
+ groups: [
+ {
+ name: 'un-needed',
+ size: 1,
+ jobs: [{ name: 'un-needed' }],
+ },
+ ],
+ },
+ ],
+};
diff --git a/spec/frontend/pipelines/components/dag/utils_spec.js b/spec/frontend/pipelines/components/dag/utils_spec.js
new file mode 100644
index 00000000000..41bb91b4800
--- /dev/null
+++ b/spec/frontend/pipelines/components/dag/utils_spec.js
@@ -0,0 +1,171 @@
+import {
+ createNodesStructure,
+ makeLinksFromNodes,
+ filterByAncestors,
+ parseData,
+ createSankey,
+ removeOrphanNodes,
+ getMaxNodes,
+} from '~/pipelines/components/dag/utils';
+
+import mockGraphData from './mock_data';
+
+describe('DAG visualization parsing utilities', () => {
+ const { nodes, nodeDict } = createNodesStructure(mockGraphData.stages);
+ const unfilteredLinks = makeLinksFromNodes(nodes, nodeDict);
+ const parsed = parseData(mockGraphData.stages);
+
+ const layoutSettings = {
+ width: 200,
+ height: 200,
+ nodeWidth: 10,
+ nodePadding: 20,
+ paddingForLabels: 100,
+ };
+
+ const sankeyLayout = createSankey(layoutSettings)(parsed);
+
+ describe('createNodesStructure', () => {
+ const parallelGroupName = 'jest';
+ const parallelJobName = 'jest 1/2';
+ const singleJobName = 'frontend fixtures';
+
+ const { name, jobs, size } = mockGraphData.stages[0].groups[0];
+
+ it('returns the expected node structure', () => {
+ expect(nodes[0]).toHaveProperty('category', mockGraphData.stages[0].name);
+ expect(nodes[0]).toHaveProperty('name', name);
+ expect(nodes[0]).toHaveProperty('jobs', jobs);
+ expect(nodes[0]).toHaveProperty('size', size);
+ });
+
+ it('adds needs to top level of nodeDict entries', () => {
+ expect(nodeDict[parallelGroupName]).toHaveProperty('needs');
+ expect(nodeDict[parallelJobName]).toHaveProperty('needs');
+ expect(nodeDict[singleJobName]).toHaveProperty('needs');
+ });
+
+ it('makes entries in nodeDict for jobs and parallel jobs', () => {
+ const nodeNames = Object.keys(nodeDict);
+
+ expect(nodeNames.includes(parallelGroupName)).toBe(true);
+ expect(nodeNames.includes(parallelJobName)).toBe(true);
+ expect(nodeNames.includes(singleJobName)).toBe(true);
+ });
+ });
+
+ describe('makeLinksFromNodes', () => {
+ it('returns the expected link structure', () => {
+ expect(unfilteredLinks[0]).toHaveProperty('source', 'frontend fixtures');
+ expect(unfilteredLinks[0]).toHaveProperty('target', 'jest');
+ expect(unfilteredLinks[0]).toHaveProperty('value', 10);
+ });
+ });
+
+ describe('filterByAncestors', () => {
+ const allLinks = [
+ { source: 'job1', target: 'job4' },
+ { source: 'job1', target: 'job2' },
+ { source: 'job2', target: 'job4' },
+ ];
+
+ const dedupedLinks = [{ source: 'job1', target: 'job2' }, { source: 'job2', target: 'job4' }];
+
+ const nodeLookup = {
+ job1: {
+ name: 'job1',
+ },
+ job2: {
+ name: 'job2',
+ needs: ['job1'],
+ },
+ job4: {
+ name: 'job4',
+ needs: ['job1', 'job2'],
+ category: 'build',
+ },
+ };
+
+ it('dedupes links', () => {
+ expect(filterByAncestors(allLinks, nodeLookup)).toMatchObject(dedupedLinks);
+ });
+ });
+
+ describe('parseData parent function', () => {
+ it('returns an object containing a list of nodes and links', () => {
+ // an array of nodes exist and the values are defined
+ expect(parsed).toHaveProperty('nodes');
+ expect(Array.isArray(parsed.nodes)).toBe(true);
+ expect(parsed.nodes.filter(Boolean)).not.toHaveLength(0);
+
+ // an array of links exist and the values are defined
+ expect(parsed).toHaveProperty('links');
+ expect(Array.isArray(parsed.links)).toBe(true);
+ expect(parsed.links.filter(Boolean)).not.toHaveLength(0);
+ });
+ });
+
+ describe('createSankey', () => {
+ it('returns a nodes data structure with expected d3-added properties', () => {
+ expect(sankeyLayout.nodes[0]).toHaveProperty('sourceLinks');
+ expect(sankeyLayout.nodes[0]).toHaveProperty('targetLinks');
+ expect(sankeyLayout.nodes[0]).toHaveProperty('depth');
+ expect(sankeyLayout.nodes[0]).toHaveProperty('layer');
+ expect(sankeyLayout.nodes[0]).toHaveProperty('x0');
+ expect(sankeyLayout.nodes[0]).toHaveProperty('x1');
+ expect(sankeyLayout.nodes[0]).toHaveProperty('y0');
+ expect(sankeyLayout.nodes[0]).toHaveProperty('y1');
+ });
+
+ it('returns a links data structure with expected d3-added properties', () => {
+ expect(sankeyLayout.links[0]).toHaveProperty('source');
+ expect(sankeyLayout.links[0]).toHaveProperty('target');
+ expect(sankeyLayout.links[0]).toHaveProperty('width');
+ expect(sankeyLayout.links[0]).toHaveProperty('y0');
+ expect(sankeyLayout.links[0]).toHaveProperty('y1');
+ });
+
+ describe('data structure integrity', () => {
+ const newObject = { name: 'bad-actor' };
+
+ beforeEach(() => {
+ sankeyLayout.nodes.unshift(newObject);
+ });
+
+ it('sankey does not propagate changes back to the original', () => {
+ expect(sankeyLayout.nodes[0]).toBe(newObject);
+ expect(parsed.nodes[0]).not.toBe(newObject);
+ });
+
+ afterEach(() => {
+ sankeyLayout.nodes.shift();
+ });
+ });
+ });
+
+ describe('removeOrphanNodes', () => {
+ it('removes sankey nodes that have no needs and are not needed', () => {
+ const cleanedNodes = removeOrphanNodes(sankeyLayout.nodes);
+ expect(cleanedNodes).toHaveLength(sankeyLayout.nodes.length - 1);
+ });
+ });
+
+ describe('getMaxNodes', () => {
+ it('returns the number of nodes in the most populous generation', () => {
+ const layerNodes = [
+ { layer: 0 },
+ { layer: 0 },
+ { layer: 1 },
+ { layer: 1 },
+ { layer: 0 },
+ { layer: 3 },
+ { layer: 2 },
+ { layer: 4 },
+ { layer: 1 },
+ { layer: 3 },
+ { layer: 4 },
+ ];
+ expect(getMaxNodes(layerNodes)).toBe(3);
+ });
+ });
+});