2021-02-14 18:09:20 +00:00
|
|
|
import { createSankey } from '~/pipelines/components/dag/drawing_utils';
|
2020-05-22 15:08:09 +00:00
|
|
|
import {
|
|
|
|
makeLinksFromNodes,
|
|
|
|
filterByAncestors,
|
2021-04-21 18:11:09 +00:00
|
|
|
generateColumnsFromLayersListBare,
|
|
|
|
listByLayers,
|
2020-05-22 15:08:09 +00:00
|
|
|
parseData,
|
|
|
|
removeOrphanNodes,
|
|
|
|
getMaxNodes,
|
2020-10-06 15:08:33 +00:00
|
|
|
} from '~/pipelines/components/parsing_utils';
|
2021-09-09 15:09:24 +00:00
|
|
|
import { createNodeDict } from '~/pipelines/utils';
|
2020-05-22 15:08:09 +00:00
|
|
|
|
2021-05-19 18:10:39 +00:00
|
|
|
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
|
2021-04-21 18:11:09 +00:00
|
|
|
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
|
2020-05-22 15:08:09 +00:00
|
|
|
|
|
|
|
describe('DAG visualization parsing utilities', () => {
|
2020-08-18 18:10:10 +00:00
|
|
|
const nodeDict = createNodeDict(mockParsedGraphQLNodes);
|
|
|
|
const unfilteredLinks = makeLinksFromNodes(mockParsedGraphQLNodes, nodeDict);
|
|
|
|
const parsed = parseData(mockParsedGraphQLNodes);
|
2020-05-22 15:08:09 +00:00
|
|
|
|
|
|
|
describe('makeLinksFromNodes', () => {
|
|
|
|
it('returns the expected link structure', () => {
|
2020-08-18 18:10:10 +00:00
|
|
|
expect(unfilteredLinks[0]).toHaveProperty('source', 'build_a');
|
|
|
|
expect(unfilteredLinks[0]).toHaveProperty('target', 'test_a');
|
2020-05-22 15:08:09 +00:00
|
|
|
expect(unfilteredLinks[0]).toHaveProperty('value', 10);
|
|
|
|
});
|
2021-05-19 18:10:39 +00:00
|
|
|
|
|
|
|
it('does not generate a link for non-existing jobs', () => {
|
|
|
|
const sources = unfilteredLinks.map(({ source }) => source);
|
|
|
|
|
|
|
|
expect(sources.includes(missingJob)).toBe(false);
|
|
|
|
});
|
2020-05-22 15:08:09 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('filterByAncestors', () => {
|
|
|
|
const allLinks = [
|
|
|
|
{ source: 'job1', target: 'job4' },
|
|
|
|
{ source: 'job1', target: 'job2' },
|
|
|
|
{ source: 'job2', target: 'job4' },
|
|
|
|
];
|
|
|
|
|
2020-12-23 12:10:26 +00:00
|
|
|
const dedupedLinks = [
|
|
|
|
{ source: 'job1', target: 'job2' },
|
|
|
|
{ source: 'job2', target: 'job4' },
|
|
|
|
];
|
2020-05-22 15:08:09 +00:00
|
|
|
|
|
|
|
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('removeOrphanNodes', () => {
|
|
|
|
it('removes sankey nodes that have no needs and are not needed', () => {
|
2020-08-18 18:10:10 +00:00
|
|
|
const layoutSettings = {
|
|
|
|
width: 200,
|
|
|
|
height: 200,
|
|
|
|
nodeWidth: 10,
|
|
|
|
nodePadding: 20,
|
|
|
|
paddingForLabels: 100,
|
|
|
|
};
|
|
|
|
|
|
|
|
const sankeyLayout = createSankey(layoutSettings)(parsed);
|
2020-05-22 15:08:09 +00:00
|
|
|
const cleanedNodes = removeOrphanNodes(sankeyLayout.nodes);
|
2020-08-18 18:10:10 +00:00
|
|
|
/*
|
|
|
|
These lengths are determined by the mock data.
|
|
|
|
If the data changes, the numbers may also change.
|
|
|
|
*/
|
2021-05-19 18:10:39 +00:00
|
|
|
expect(parsed.nodes).toHaveLength(mockParsedGraphQLNodes.length);
|
2020-08-18 18:10:10 +00:00
|
|
|
expect(cleanedNodes).toHaveLength(12);
|
2020-05-22 15:08:09 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
});
|
2021-04-21 18:11:09 +00:00
|
|
|
|
|
|
|
describe('generateColumnsFromLayersList', () => {
|
|
|
|
const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo');
|
2021-08-11 21:10:33 +00:00
|
|
|
const { pipelineLayers } = listByLayers(pipeline);
|
|
|
|
const columns = generateColumnsFromLayersListBare(pipeline, pipelineLayers);
|
2021-04-21 18:11:09 +00:00
|
|
|
|
|
|
|
it('returns stage-like objects with default name, id, and status', () => {
|
|
|
|
columns.forEach((col, idx) => {
|
|
|
|
expect(col).toMatchObject({
|
|
|
|
name: '',
|
|
|
|
status: { action: null },
|
|
|
|
id: `layer-${idx}`,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('creates groups that match the list created in listByLayers', () => {
|
|
|
|
columns.forEach((col, idx) => {
|
|
|
|
const groupNames = col.groups.map(({ name }) => name);
|
2021-08-11 21:10:33 +00:00
|
|
|
expect(groupNames).toEqual(pipelineLayers[idx]);
|
2021-04-21 18:11:09 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('looks up the correct group object', () => {
|
|
|
|
columns.forEach((col) => {
|
|
|
|
col.groups.forEach((group) => {
|
|
|
|
const groupStage = pipeline.stages.find((el) => el.name === group.stageName);
|
|
|
|
const groupObject = groupStage.groups.find((el) => el.name === group.name);
|
|
|
|
expect(group).toBe(groupObject);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
/*
|
|
|
|
Just as a fallback in case multiple functions change, so tests pass
|
|
|
|
but the implementation moves away from case.
|
|
|
|
*/
|
|
|
|
it('matches the snapshot', () => {
|
|
|
|
expect(columns).toMatchSnapshot();
|
|
|
|
});
|
|
|
|
});
|
2020-05-22 15:08:09 +00:00
|
|
|
});
|