Commit 0f3aadf4 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '340057-alias-the-needs-in-the-pipeline-graph' into 'master'

Fix job depencies view in the pipeline graph

See merge request gitlab-org/gitlab!75139
parents a158bec3 42b12e94
import { memoize } from 'lodash'; import { memoize } from 'lodash';
import { createNodeDict } from '../utils'; import { createNodeDict } from '../utils';
import { EXPLICIT_NEEDS_PROPERTY, NEEDS_PROPERTY } from '../constants';
import { createSankey } from './dag/drawing_utils'; import { createSankey } from './dag/drawing_utils';
/* /*
...@@ -15,12 +16,14 @@ const deduplicate = (item, itemIndex, arr) => { ...@@ -15,12 +16,14 @@ const deduplicate = (item, itemIndex, arr) => {
return foundIdx === itemIndex; return foundIdx === itemIndex;
}; };
export const makeLinksFromNodes = (nodes, nodeDict) => { export const makeLinksFromNodes = (nodes, nodeDict, { needsKey = NEEDS_PROPERTY } = {}) => {
const constantLinkValue = 10; // all links are the same weight const constantLinkValue = 10; // all links are the same weight
return nodes return nodes
.map(({ jobs, name: groupName }) => .map(({ jobs, name: groupName }) =>
jobs.map(({ needs = [] }) => jobs.map((job) => {
needs.reduce((acc, needed) => { const needs = job[needsKey] || [];
return needs.reduce((acc, needed) => {
// It's possible that we have an optional job, which // It's possible that we have an optional job, which
// is being needed by another job. In that scenario, // is being needed by another job. In that scenario,
// the needed job doesn't exist, so we don't want to // the needed job doesn't exist, so we don't want to
...@@ -34,8 +37,8 @@ export const makeLinksFromNodes = (nodes, nodeDict) => { ...@@ -34,8 +37,8 @@ export const makeLinksFromNodes = (nodes, nodeDict) => {
} }
return acc; return acc;
}, []), }, []);
), }),
) )
.flat(2); .flat(2);
}; };
...@@ -76,9 +79,9 @@ export const filterByAncestors = (links, nodeDict) => ...@@ -76,9 +79,9 @@ export const filterByAncestors = (links, nodeDict) =>
return !allAncestors.includes(source); return !allAncestors.includes(source);
}); });
export const parseData = (nodes) => { export const parseData = (nodes, { needsKey = NEEDS_PROPERTY } = {}) => {
const nodeDict = createNodeDict(nodes); const nodeDict = createNodeDict(nodes, { needsKey });
const allLinks = makeLinksFromNodes(nodes, nodeDict); const allLinks = makeLinksFromNodes(nodes, nodeDict, { needsKey });
const filteredLinks = allLinks.filter(deduplicate); const filteredLinks = allLinks.filter(deduplicate);
const links = filterByAncestors(filteredLinks, nodeDict); const links = filterByAncestors(filteredLinks, nodeDict);
...@@ -123,7 +126,8 @@ export const removeOrphanNodes = (sankeyfiedNodes) => { ...@@ -123,7 +126,8 @@ export const removeOrphanNodes = (sankeyfiedNodes) => {
export const listByLayers = ({ stages }) => { export const listByLayers = ({ stages }) => {
const arrayOfJobs = stages.flatMap(({ groups }) => groups); const arrayOfJobs = stages.flatMap(({ groups }) => groups);
const parsedData = parseData(arrayOfJobs); const parsedData = parseData(arrayOfJobs);
const dataWithLayers = createSankey()(parsedData); const explicitParsedData = parseData(arrayOfJobs, { needsKey: EXPLICIT_NEEDS_PROPERTY });
const dataWithLayers = createSankey()(explicitParsedData);
const pipelineLayers = dataWithLayers.nodes.reduce((acc, { layer, name }) => { const pipelineLayers = dataWithLayers.nodes.reduce((acc, { layer, name }) => {
/* sort groups by layer */ /* sort groups by layer */
......
import { reportToSentry } from '../utils'; import { reportToSentry } from '../utils';
import { EXPLICIT_NEEDS_PROPERTY, NEEDS_PROPERTY } from '../constants';
const unwrapGroups = (stages) => { const unwrapGroups = (stages) => {
return stages.map((stage, idx) => { return stages.map((stage, idx) => {
...@@ -27,12 +28,16 @@ const unwrapNodesWithName = (jobArray, prop, field = 'name') => { ...@@ -27,12 +28,16 @@ const unwrapNodesWithName = (jobArray, prop, field = 'name') => {
} }
return jobArray.map((job) => { return jobArray.map((job) => {
return { ...job, [prop]: job[prop].nodes.map((item) => item[field] || '') }; if (job[prop]) {
return { ...job, [prop]: job[prop].nodes.map((item) => item[field] || '') };
}
return job;
}); });
}; };
const unwrapJobWithNeeds = (denodedJobArray) => { const unwrapJobWithNeeds = (denodedJobArray) => {
return unwrapNodesWithName(denodedJobArray, 'needs'); const explicitNeedsUnwrapped = unwrapNodesWithName(denodedJobArray, EXPLICIT_NEEDS_PROPERTY);
return unwrapNodesWithName(explicitNeedsUnwrapped, NEEDS_PROPERTY);
}; };
const unwrapStagesWithNeedsAndLookup = (denodedStages) => { const unwrapStagesWithNeedsAndLookup = (denodedStages) => {
......
...@@ -7,6 +7,8 @@ export const ANY_TRIGGER_AUTHOR = 'Any'; ...@@ -7,6 +7,8 @@ export const ANY_TRIGGER_AUTHOR = 'Any';
export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status', 'source']; export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status', 'source'];
export const FILTER_TAG_IDENTIFIER = 'tag'; export const FILTER_TAG_IDENTIFIER = 'tag';
export const SCHEDULE_ORIGIN = 'schedule'; export const SCHEDULE_ORIGIN = 'schedule';
export const NEEDS_PROPERTY = 'needs';
export const EXPLICIT_NEEDS_PROPERTY = 'previousStageJobsOrNeeds';
export const TestStatus = { export const TestStatus = {
FAILED: 'failed', FAILED: 'failed',
......
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { pickBy } from 'lodash'; import { pickBy } from 'lodash';
import { SUPPORTED_FILTER_PARAMETERS } from './constants'; import { SUPPORTED_FILTER_PARAMETERS, NEEDS_PROPERTY } from './constants';
/* /*
The following functions are the main engine in transforming the data as The following functions are the main engine in transforming the data as
...@@ -35,11 +35,11 @@ import { SUPPORTED_FILTER_PARAMETERS } from './constants'; ...@@ -35,11 +35,11 @@ import { SUPPORTED_FILTER_PARAMETERS } from './constants';
10 -> value (constant) 10 -> value (constant)
*/ */
export const createNodeDict = (nodes) => { export const createNodeDict = (nodes, { needsKey = NEEDS_PROPERTY } = {}) => {
return nodes.reduce((acc, node) => { return nodes.reduce((acc, node) => {
const newNode = { const newNode = {
...node, ...node,
needs: node.jobs.map((job) => job.needs || []).flat(), needs: node.jobs.map((job) => job[needsKey] || []).flat(),
}; };
if (node.size > 1) { if (node.size > 1) {
......
...@@ -91,6 +91,14 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) { ...@@ -91,6 +91,14 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
name name
} }
} }
previousStageJobsOrNeeds {
__typename
nodes {
__typename
id
name
}
}
status: detailedStatus { status: detailedStatus {
__typename __typename
id id
......
...@@ -13,6 +13,7 @@ Array [ ...@@ -13,6 +13,7 @@ Array [
"id": "6", "id": "6",
"name": "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", "name": "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
"needs": Array [], "needs": Array [],
"previousStageJobsOrNeeds": Array [],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -53,6 +54,7 @@ Array [ ...@@ -53,6 +54,7 @@ Array [
"id": "11", "id": "11",
"name": "build_b", "name": "build_b",
"needs": Array [], "needs": Array [],
"previousStageJobsOrNeeds": Array [],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -93,6 +95,7 @@ Array [ ...@@ -93,6 +95,7 @@ Array [
"id": "16", "id": "16",
"name": "build_c", "name": "build_c",
"needs": Array [], "needs": Array [],
"previousStageJobsOrNeeds": Array [],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -133,6 +136,7 @@ Array [ ...@@ -133,6 +136,7 @@ Array [
"id": "21", "id": "21",
"name": "build_d 1/3", "name": "build_d 1/3",
"needs": Array [], "needs": Array [],
"previousStageJobsOrNeeds": Array [],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -157,6 +161,7 @@ Array [ ...@@ -157,6 +161,7 @@ Array [
"id": "24", "id": "24",
"name": "build_d 2/3", "name": "build_d 2/3",
"needs": Array [], "needs": Array [],
"previousStageJobsOrNeeds": Array [],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -181,6 +186,7 @@ Array [ ...@@ -181,6 +186,7 @@ Array [
"id": "27", "id": "27",
"name": "build_d 3/3", "name": "build_d 3/3",
"needs": Array [], "needs": Array [],
"previousStageJobsOrNeeds": Array [],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -221,6 +227,7 @@ Array [ ...@@ -221,6 +227,7 @@ Array [
"id": "59", "id": "59",
"name": "test_c", "name": "test_c",
"needs": Array [], "needs": Array [],
"previousStageJobsOrNeeds": Array [],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -267,6 +274,11 @@ Array [ ...@@ -267,6 +274,11 @@ Array [
"build_b", "build_b",
"build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
], ],
"previousStageJobsOrNeeds": Array [
"build_c",
"build_b",
"build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -313,6 +325,13 @@ Array [ ...@@ -313,6 +325,13 @@ Array [
"build_b", "build_b",
"build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
], ],
"previousStageJobsOrNeeds": Array [
"build_d 3/3",
"build_d 2/3",
"build_d 1/3",
"build_b",
"build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -343,6 +362,13 @@ Array [ ...@@ -343,6 +362,13 @@ Array [
"build_b", "build_b",
"build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
], ],
"previousStageJobsOrNeeds": Array [
"build_d 3/3",
"build_d 2/3",
"build_d 1/3",
"build_b",
"build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
...@@ -385,6 +411,9 @@ Array [ ...@@ -385,6 +411,9 @@ Array [
"needs": Array [ "needs": Array [
"build_b", "build_b",
], ],
"previousStageJobsOrNeeds": Array [
"build_b",
],
"scheduledAt": null, "scheduledAt": null,
"status": Object { "status": Object {
"__typename": "DetailedStatus", "__typename": "DetailedStatus",
......
...@@ -73,6 +73,10 @@ export const mockPipelineResponse = { ...@@ -73,6 +73,10 @@ export const mockPipelineResponse = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
}, },
], ],
}, },
...@@ -118,6 +122,10 @@ export const mockPipelineResponse = { ...@@ -118,6 +122,10 @@ export const mockPipelineResponse = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
}, },
], ],
}, },
...@@ -163,6 +171,10 @@ export const mockPipelineResponse = { ...@@ -163,6 +171,10 @@ export const mockPipelineResponse = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
}, },
], ],
}, },
...@@ -208,6 +220,10 @@ export const mockPipelineResponse = { ...@@ -208,6 +220,10 @@ export const mockPipelineResponse = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
}, },
{ {
__typename: 'CiJob', __typename: 'CiJob',
...@@ -235,6 +251,10 @@ export const mockPipelineResponse = { ...@@ -235,6 +251,10 @@ export const mockPipelineResponse = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
}, },
{ {
__typename: 'CiJob', __typename: 'CiJob',
...@@ -262,6 +282,10 @@ export const mockPipelineResponse = { ...@@ -262,6 +282,10 @@ export const mockPipelineResponse = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
}, },
], ],
}, },
...@@ -339,6 +363,27 @@ export const mockPipelineResponse = { ...@@ -339,6 +363,27 @@ export const mockPipelineResponse = {
}, },
], ],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [
{
__typename: 'CiBuildNeed',
id: '37',
name: 'build_c',
},
{
__typename: 'CiBuildNeed',
id: '38',
name: 'build_b',
},
{
__typename: 'CiBuildNeed',
id: '39',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
],
},
}, },
], ],
}, },
...@@ -411,6 +456,37 @@ export const mockPipelineResponse = { ...@@ -411,6 +456,37 @@ export const mockPipelineResponse = {
}, },
], ],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [
{
__typename: 'CiBuildNeed',
id: '45',
name: 'build_d 3/3',
},
{
__typename: 'CiBuildNeed',
id: '46',
name: 'build_d 2/3',
},
{
__typename: 'CiBuildNeed',
id: '47',
name: 'build_d 1/3',
},
{
__typename: 'CiBuildNeed',
id: '48',
name: 'build_b',
},
{
__typename: 'CiBuildNeed',
id: '49',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
],
},
}, },
{ {
__typename: 'CiJob', __typename: 'CiJob',
...@@ -465,6 +541,37 @@ export const mockPipelineResponse = { ...@@ -465,6 +541,37 @@ export const mockPipelineResponse = {
}, },
], ],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [
{
__typename: 'CiBuildNeed',
id: '52',
name: 'build_d 3/3',
},
{
__typename: 'CiBuildNeed',
id: '53',
name: 'build_d 2/3',
},
{
__typename: 'CiBuildNeed',
id: '54',
name: 'build_d 1/3',
},
{
__typename: 'CiBuildNeed',
id: '55',
name: 'build_b',
},
{
__typename: 'CiBuildNeed',
id: '56',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
],
},
}, },
], ],
}, },
...@@ -503,6 +610,10 @@ export const mockPipelineResponse = { ...@@ -503,6 +610,10 @@ export const mockPipelineResponse = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
}, },
], ],
}, },
...@@ -547,6 +658,16 @@ export const mockPipelineResponse = { ...@@ -547,6 +658,16 @@ export const mockPipelineResponse = {
}, },
], ],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [
{
__typename: 'CiBuildNeed',
id: '65',
name: 'build_b',
},
],
},
}, },
], ],
}, },
...@@ -720,6 +841,10 @@ export const wrappedPipelineReturn = { ...@@ -720,6 +841,10 @@ export const wrappedPipelineReturn = {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [], nodes: [],
}, },
previousStageJobsOrNeeds: {
__typename: 'CiJobConnection',
nodes: [],
},
status: { status: {
__typename: 'DetailedStatus', __typename: 'DetailedStatus',
id: '84', id: '84',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment