Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
fba4ebfb
Commit
fba4ebfb
authored
May 29, 2020
by
Sarah Groff Hennigh-Palermo
Committed by
Andrew Fontaine
May 29, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Adds the base graph
Adds new files, adapts previous
parent
6670a97f
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1213 additions
and
42 deletions
+1213
-42
app/assets/javascripts/pipelines/components/dag/constants.js
app/assets/javascripts/pipelines/components/dag/constants.js
+4
-0
app/assets/javascripts/pipelines/components/dag/dag.vue
app/assets/javascripts/pipelines/components/dag/dag.vue
+62
-12
app/assets/javascripts/pipelines/components/dag/dag_graph.vue
...assets/javascripts/pipelines/components/dag/dag_graph.vue
+381
-0
app/assets/javascripts/pipelines/components/dag/utils.js
app/assets/javascripts/pipelines/components/dag/utils.js
+7
-1
locale/gitlab.pot
locale/gitlab.pot
+9
-0
package.json
package.json
+1
-0
spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
...lines/components/dag/__snapshots__/dag_graph_spec.js.snap
+220
-0
spec/frontend/pipelines/components/dag/dag_graph_spec.js
spec/frontend/pipelines/components/dag/dag_graph_spec.js
+96
-0
spec/frontend/pipelines/components/dag/dag_spec.js
spec/frontend/pipelines/components/dag/dag_spec.js
+77
-19
spec/frontend/pipelines/components/dag/mock_data.js
spec/frontend/pipelines/components/dag/mock_data.js
+347
-1
spec/frontend/pipelines/components/dag/utils_spec.js
spec/frontend/pipelines/components/dag/utils_spec.js
+5
-5
yarn.lock
yarn.lock
+4
-4
No files found.
app/assets/javascripts/pipelines/components/dag/constants.js
0 → 100644
View file @
fba4ebfb
export
const
PARSE_FAILURE
=
'
parse_failure
'
;
export
const
LOAD_FAILURE
=
'
load_failure
'
;
export
const
UNSUPPORTED_DATA
=
'
unsupported_data
'
;
export
const
DEFAULT
=
'
default
'
;
app/assets/javascripts/pipelines/components/dag/dag.vue
View file @
fba4ebfb
<
script
>
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
DagGraph
from
'
./dag_graph.vue
'
;
import
{
DEFAULT
,
PARSE_FAILURE
,
LOAD_FAILURE
,
UNSUPPORTED_DATA
}
from
'
./constants
'
;
import
{
parseData
}
from
'
./utils
'
;
export
default
{
// eslint-disable-next-line @gitlab/require-i18n-strings
name
:
'
Dag
'
,
components
:
{
DagGraph
,
GlAlert
,
},
props
:
{
...
...
@@ -18,15 +23,47 @@ export default {
data
()
{
return
{
showFailureAlert
:
false
,
failureType
:
null
,
graphData
:
null
,
};
},
errorTexts
:
{
[
LOAD_FAILURE
]:
__
(
'
We are currently unable to fetch data for this graph.
'
),
[
PARSE_FAILURE
]:
__
(
'
There was an error parsing the data for this graph.
'
),
[
UNSUPPORTED_DATA
]:
__
(
'
A DAG must have two dependent jobs to be visualized on this tab.
'
),
[
DEFAULT
]:
__
(
'
An unknown error occurred while loading this graph.
'
),
},
computed
:
{
failure
()
{
switch
(
this
.
failureType
)
{
case
LOAD_FAILURE
:
return
{
text
:
this
.
$options
.
errorTexts
[
LOAD_FAILURE
],
variant
:
'
danger
'
,
};
case
PARSE_FAILURE
:
return
{
text
:
this
.
$options
.
errorTexts
[
PARSE_FAILURE
],
variant
:
'
danger
'
,
};
case
UNSUPPORTED_DATA
:
return
{
text
:
this
.
$options
.
errorTexts
[
UNSUPPORTED_DATA
],
variant
:
'
info
'
,
};
default
:
return
{
text
:
this
.
$options
.
errorTexts
[
DEFAULT
],
vatiant
:
'
danger
'
,
};
}
},
shouldDisplayGraph
()
{
return
!
this
.
showFailureAlert
;
return
Boolean
(
!
this
.
showFailureAlert
&&
this
.
graphData
)
;
},
},
mounted
()
{
const
{
drawGraph
,
reportFailure
}
=
this
;
const
{
processGraphData
,
reportFailure
}
=
this
;
if
(
!
this
.
graphUrl
)
{
reportFailure
();
...
...
@@ -36,30 +73,43 @@ export default {
axios
.
get
(
this
.
graphUrl
)
.
then
(
response
=>
{
drawGraph
(
response
.
data
);
processGraphData
(
response
.
data
);
})
.
catch
(
reportFailure
);
.
catch
(
()
=>
reportFailure
(
LOAD_FAILURE
)
);
},
methods
:
{
drawGraph
(
data
)
{
return
data
;
processGraphData
(
data
)
{
let
parsed
;
try
{
parsed
=
parseData
(
data
.
stages
);
}
catch
{
this
.
reportFailure
(
PARSE_FAILURE
);
return
;
}
if
(
parsed
.
links
.
length
<
2
)
{
this
.
reportFailure
(
UNSUPPORTED_DATA
);
return
;
}
this
.
graphData
=
parsed
;
},
hideAlert
()
{
this
.
showFailureAlert
=
false
;
},
reportFailure
()
{
reportFailure
(
type
)
{
this
.
showFailureAlert
=
true
;
this
.
failureType
=
type
;
},
},
};
</
script
>
<
template
>
<div>
<gl-alert
v-if=
"showFailureAlert"
variant=
"danger
"
@
dismiss=
"hideAlert"
>
{{
__
(
'
We are currently unable to fetch data for this graph.
'
)
}}
<gl-alert
v-if=
"showFailureAlert"
:variant=
"failure.variant
"
@
dismiss=
"hideAlert"
>
{{
failure
.
text
}}
</gl-alert>
<div
v-if=
"shouldDisplayGraph"
data-testid=
"dag-graph-container"
>
<!-- graph goes here -->
</div>
<dag-graph
v-if=
"shouldDisplayGraph"
:graph-data=
"graphData"
@
onFailure=
"reportFailure"
/>
</div>
</
template
>
app/assets/javascripts/pipelines/components/dag/dag_graph.vue
0 → 100644
View file @
fba4ebfb
<
script
>
import
*
as
d3
from
'
d3
'
;
import
{
uniqueId
}
from
'
lodash
'
;
import
{
PARSE_FAILURE
}
from
'
./constants
'
;
import
{
createSankey
,
getMaxNodes
,
removeOrphanNodes
}
from
'
./utils
'
;
export
default
{
viewOptions
:
{
baseHeight
:
300
,
baseWidth
:
1000
,
minNodeHeight
:
60
,
nodeWidth
:
16
,
nodePadding
:
25
,
paddingForLabels
:
100
,
labelMargin
:
8
,
// can plausibly applied through CSS instead, TBD
baseOpacity
:
0.8
,
highlightIn
:
1
,
highlightOut
:
0.2
,
containerClasses
:
[
'
dag-graph-container
'
,
'
gl-display-flex
'
,
'
gl-flex-direction-column
'
].
join
(
'
'
,
),
},
gitLabColorRotation
:
[
'
#e17223
'
,
'
#83ab4a
'
,
'
#5772ff
'
,
'
#b24800
'
,
'
#25d2d2
'
,
'
#006887
'
,
'
#487900
'
,
'
#d84280
'
,
'
#3547de
'
,
'
#6f3500
'
,
'
#006887
'
,
'
#275600
'
,
'
#b31756
'
,
],
props
:
{
graphData
:
{
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
color
:
()
=>
{},
width
:
0
,
height
:
0
,
};
},
mounted
()
{
let
countedAndTransformed
;
try
{
countedAndTransformed
=
this
.
transformData
(
this
.
graphData
);
}
catch
{
this
.
$emit
(
'
onFailure
'
,
PARSE_FAILURE
);
return
;
}
this
.
drawGraph
(
countedAndTransformed
);
},
methods
:
{
addSvg
()
{
return
d3
.
select
(
'
.dag-graph-container
'
)
.
append
(
'
svg
'
)
.
attr
(
'
viewBox
'
,
[
0
,
0
,
this
.
width
,
this
.
height
])
.
attr
(
'
width
'
,
this
.
width
)
.
attr
(
'
height
'
,
this
.
height
);
},
appendLinks
(
link
)
{
return
(
link
.
append
(
'
path
'
)
.
attr
(
'
d
'
,
this
.
createLinkPath
)
.
attr
(
'
stroke
'
,
({
gradId
})
=>
`url(#
${
gradId
}
)`
)
.
style
(
'
stroke-linejoin
'
,
'
round
'
)
// minus two to account for the rounded nodes
.
attr
(
'
stroke-width
'
,
({
width
})
=>
Math
.
max
(
1
,
width
-
2
))
.
attr
(
'
clip-path
'
,
({
clipId
})
=>
`url(#
${
clipId
}
)`
)
);
},
appendLabelAsForeignObject
(
d
,
i
,
n
)
{
const
currentNode
=
n
[
i
];
const
{
height
,
wrapperWidth
,
width
,
x
,
y
,
textAlign
}
=
this
.
labelPosition
(
d
);
const
labelClasses
=
[
'
gl-display-flex
'
,
'
gl-pointer-events-none
'
,
'
gl-flex-direction-column
'
,
'
gl-justify-content-center
'
,
'
gl-overflow-wrap-break
'
,
].
join
(
'
'
);
return
(
d3
.
select
(
currentNode
)
.
attr
(
'
requiredFeatures
'
,
'
http://www.w3.org/TR/SVG11/feature#Extensibility
'
)
.
attr
(
'
height
'
,
height
)
/*
items with a 'max-content' width will have a wrapperWidth for the foreignObject
*/
.
attr
(
'
width
'
,
wrapperWidth
||
width
)
.
attr
(
'
x
'
,
x
)
.
attr
(
'
y
'
,
y
)
.
classed
(
'
gl-overflow-visible
'
,
true
)
.
append
(
'
xhtml:div
'
)
.
classed
(
labelClasses
,
true
)
.
style
(
'
height
'
,
height
)
.
style
(
'
width
'
,
width
)
.
style
(
'
text-align
'
,
textAlign
)
.
text
(({
name
})
=>
name
)
);
},
createAndAssignId
(
datum
,
field
,
modifier
=
''
)
{
const
id
=
uniqueId
(
modifier
);
/* eslint-disable-next-line no-param-reassign */
datum
[
field
]
=
id
;
return
id
;
},
createClip
(
link
)
{
/*
Because large link values can overrun their box, we create a clip path
to trim off the excess in charts that have few nodes per column and are
therefore tall.
The box is created by
M: moving to outside midpoint of the source node
V: drawing a vertical line to maximum of the bottom link edge or
the lowest edge of the node (can be d.y0 or d.y1 depending on the link's path)
H: drawing a horizontal line to the outside edge of the destination node
V: drawing a vertical line back up to the minimum of the top link edge or
the highest edge of the node (can be d.y0 or d.y1 depending on the link's path)
H: drawing a horizontal line back to the outside edge of the source node
Z: closing the path, back to the start point
*/
const
clip
=
({
y0
,
y1
,
source
,
target
,
width
})
=>
{
const
bottomLinkEdge
=
Math
.
max
(
y1
,
y0
)
+
width
/
2
;
const
topLinkEdge
=
Math
.
min
(
y0
,
y1
)
-
width
/
2
;
/* eslint-disable @gitlab/require-i18n-strings */
return
`
M
${
source
.
x0
}
,
${
y1
}
V
${
Math
.
max
(
bottomLinkEdge
,
y0
,
y1
)}
H
${
target
.
x1
}
V
${
Math
.
min
(
topLinkEdge
,
y0
,
y1
)}
H
${
source
.
x0
}
Z`
;
/* eslint-enable @gitlab/require-i18n-strings */
};
return
link
.
append
(
'
clipPath
'
)
.
attr
(
'
id
'
,
d
=>
{
return
this
.
createAndAssignId
(
d
,
'
clipId
'
,
'
dag-clip
'
);
})
.
append
(
'
path
'
)
.
attr
(
'
d
'
,
clip
);
},
createGradient
(
link
)
{
const
gradient
=
link
.
append
(
'
linearGradient
'
)
.
attr
(
'
id
'
,
d
=>
{
return
this
.
createAndAssignId
(
d
,
'
gradId
'
,
'
dag-grad
'
);
})
.
attr
(
'
gradientUnits
'
,
'
userSpaceOnUse
'
)
.
attr
(
'
x1
'
,
({
source
})
=>
source
.
x1
)
.
attr
(
'
x2
'
,
({
target
})
=>
target
.
x0
);
gradient
.
append
(
'
stop
'
)
.
attr
(
'
offset
'
,
'
0%
'
)
.
attr
(
'
stop-color
'
,
({
source
})
=>
this
.
color
(
source
));
gradient
.
append
(
'
stop
'
)
.
attr
(
'
offset
'
,
'
100%
'
)
.
attr
(
'
stop-color
'
,
({
target
})
=>
this
.
color
(
target
));
},
createLinkPath
({
y0
,
y1
,
source
,
target
,
width
},
idx
)
{
const
{
nodeWidth
}
=
this
.
$options
.
viewOptions
;
/*
Creates a series of staggered midpoints for the link paths, so they
don't run along one channel and can be distinguished.
First, get a point staggered by index and link width, modulated by the link box
to find a point roughly between the nodes.
Then offset it by nodeWidth, so it doesn't run under any nodes at the left.
Determine where it would overlap at the right.
Finally, select the leftmost of these options:
- offset from the source node based on index + fudge;
- a fuzzy offset from the right node, using Math.random adds a little blur
- a hard offset from the end node, if random pushes it over
Then draw a line from the start node to the bottom-most point of the midline
up to the topmost point in that line and then to the middle of the end node
*/
const
xValRaw
=
source
.
x1
+
(((
idx
+
1
)
*
width
)
%
(
target
.
x1
-
source
.
x0
));
const
xValMin
=
xValRaw
+
nodeWidth
;
const
overlapPoint
=
source
.
x1
+
(
target
.
x0
-
source
.
x1
);
const
xValMax
=
overlapPoint
-
nodeWidth
*
1.4
;
const
midPointX
=
Math
.
min
(
xValMin
,
target
.
x0
-
nodeWidth
*
4
*
Math
.
random
(),
xValMax
);
return
d3
.
line
()([
[(
source
.
x0
+
source
.
x1
)
/
2
,
y0
],
[
midPointX
,
y0
],
[
midPointX
,
y1
],
[(
target
.
x0
+
target
.
x1
)
/
2
,
y1
],
]);
},
createLinks
(
svg
,
linksData
)
{
const
link
=
this
.
generateLinks
(
svg
,
linksData
);
this
.
createGradient
(
link
);
this
.
createClip
(
link
);
this
.
appendLinks
(
link
);
},
createNodes
(
svg
,
nodeData
)
{
this
.
generateNodes
(
svg
,
nodeData
);
this
.
labelNodes
(
svg
,
nodeData
);
},
drawGraph
({
maxNodesPerLayer
,
linksAndNodes
})
{
const
{
baseWidth
,
baseHeight
,
minNodeHeight
,
nodeWidth
,
nodePadding
,
paddingForLabels
,
}
=
this
.
$options
.
viewOptions
;
this
.
width
=
baseWidth
;
this
.
height
=
baseHeight
+
maxNodesPerLayer
*
minNodeHeight
;
this
.
color
=
this
.
initColors
();
const
{
links
,
nodes
}
=
createSankey
({
width
:
this
.
width
,
height
:
this
.
height
,
nodeWidth
,
nodePadding
,
paddingForLabels
,
})(
linksAndNodes
);
const
svg
=
this
.
addSvg
();
this
.
createLinks
(
svg
,
links
);
this
.
createNodes
(
svg
,
nodes
);
},
generateLinks
(
svg
,
linksData
)
{
const
linkContainerName
=
'
dag-link
'
;
return
svg
.
append
(
'
g
'
)
.
attr
(
'
fill
'
,
'
none
'
)
.
attr
(
'
stroke-opacity
'
,
this
.
$options
.
viewOptions
.
baseOpacity
)
.
selectAll
(
`.
${
linkContainerName
}
`
)
.
data
(
linksData
)
.
enter
()
.
append
(
'
g
'
)
.
attr
(
'
id
'
,
d
=>
{
return
this
.
createAndAssignId
(
d
,
'
uid
'
,
linkContainerName
);
})
.
classed
(
`
${
linkContainerName
}
gl-cursor-pointer`
,
true
);
},
generateNodes
(
svg
,
nodeData
)
{
const
nodeContainerName
=
'
dag-node
'
;
const
{
nodeWidth
}
=
this
.
$options
.
viewOptions
;
return
svg
.
append
(
'
g
'
)
.
selectAll
(
`.
${
nodeContainerName
}
`
)
.
data
(
nodeData
)
.
enter
()
.
append
(
'
line
'
)
.
classed
(
`
${
nodeContainerName
}
gl-cursor-pointer`
,
true
)
.
attr
(
'
id
'
,
d
=>
{
return
this
.
createAndAssignId
(
d
,
'
uid
'
,
nodeContainerName
);
})
.
attr
(
'
stroke
'
,
this
.
color
)
.
attr
(
'
stroke-width
'
,
nodeWidth
)
.
attr
(
'
stroke-linecap
'
,
'
round
'
)
.
attr
(
'
x1
'
,
d
=>
Math
.
floor
((
d
.
x1
+
d
.
x0
)
/
2
))
.
attr
(
'
x2
'
,
d
=>
Math
.
floor
((
d
.
x1
+
d
.
x0
)
/
2
))
.
attr
(
'
y1
'
,
d
=>
d
.
y0
+
4
)
.
attr
(
'
y2
'
,
d
=>
d
.
y1
-
4
);
},
labelNodes
(
svg
,
nodeData
)
{
return
svg
.
append
(
'
g
'
)
.
classed
(
'
gl-font-sm
'
,
true
)
.
selectAll
(
'
text
'
)
.
data
(
nodeData
)
.
enter
()
.
append
(
'
foreignObject
'
)
.
each
(
this
.
appendLabelAsForeignObject
);
},
initColors
()
{
const
colorFn
=
d3
.
scaleOrdinal
(
this
.
$options
.
gitLabColorRotation
);
return
({
name
})
=>
colorFn
(
name
);
},
labelPosition
({
x0
,
x1
,
y0
,
y1
})
{
const
{
paddingForLabels
,
labelMargin
,
nodePadding
}
=
this
.
$options
.
viewOptions
;
const
firstCol
=
x0
<=
paddingForLabels
;
const
lastCol
=
x1
>=
this
.
width
-
paddingForLabels
;
if
(
firstCol
)
{
return
{
x
:
0
+
labelMargin
,
y
:
y0
,
height
:
`
${
y1
-
y0
}
px`
,
width
:
paddingForLabels
-
2
*
labelMargin
,
textAlign
:
'
right
'
,
};
}
if
(
lastCol
)
{
return
{
x
:
this
.
width
-
paddingForLabels
+
labelMargin
,
y
:
y0
,
height
:
`
${
y1
-
y0
}
px`
,
width
:
paddingForLabels
-
2
*
labelMargin
,
textAlign
:
'
left
'
,
};
}
return
{
x
:
(
x1
+
x0
)
/
2
,
y
:
y0
-
nodePadding
,
height
:
`
${
nodePadding
}
px`
,
width
:
'
max-content
'
,
wrapperWidth
:
paddingForLabels
-
2
*
labelMargin
,
textAlign
:
x0
<
this
.
width
/
2
?
'
left
'
:
'
right
'
,
};
},
transformData
(
parsed
)
{
const
baseLayout
=
createSankey
()(
parsed
);
const
cleanedNodes
=
removeOrphanNodes
(
baseLayout
.
nodes
);
const
maxNodesPerLayer
=
getMaxNodes
(
cleanedNodes
);
return
{
maxNodesPerLayer
,
linksAndNodes
:
{
links
:
parsed
.
links
,
nodes
:
cleanedNodes
,
},
};
},
},
};
</
script
>
<
template
>
<div
:class=
"$options.viewOptions.containerClasses"
data-testid=
"dag-graph-container"
>
<!-- graph goes here -->
</div>
</
template
>
app/assets/javascripts/pipelines/components/dag/utils.js
View file @
fba4ebfb
...
...
@@ -141,7 +141,13 @@ export const parseData = data => {
values for the nodes and links in the graph.
*/
export
const
createSankey
=
({
width
,
height
,
nodeWidth
,
nodePadding
,
paddingForLabels
})
=>
{
export
const
createSankey
=
({
width
=
10
,
height
=
10
,
nodeWidth
=
10
,
nodePadding
=
10
,
paddingForLabels
=
1
,
}
=
{})
=>
{
const
sankeyGenerator
=
sankey
()
.
nodeId
(({
name
})
=>
name
)
.
nodeAlign
(
sankeyLeft
)
...
...
locale/gitlab.pot
View file @
fba4ebfb
...
...
@@ -867,6 +867,9 @@ msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
msgid "A DAG must have two dependent jobs to be visualized on this tab."
msgstr ""
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
...
...
@@ -2404,6 +2407,9 @@ msgstr ""
msgid "An unexpected error occurred while stopping the Web Terminal."
msgstr ""
msgid "An unknown error occurred while loading this graph."
msgstr ""
msgid "Analytics"
msgstr ""
...
...
@@ -22064,6 +22070,9 @@ msgstr ""
msgid "There was an error loading users activity calendar."
msgstr ""
msgid "There was an error parsing the data for this graph."
msgstr ""
msgid "There was an error removing the e-mail."
msgstr ""
...
...
package.json
View file @
fba4ebfb
...
...
@@ -70,6 +70,7 @@
"
core-js
"
:
"
^3.6.4
"
,
"
cropper
"
:
"
^2.3.0
"
,
"
css-loader
"
:
"
^2.1.1
"
,
"
d3
"
:
"
^5.16.0
"
,
"
d3-sankey
"
:
"
^0.12.3
"
,
"
d3-scale
"
:
"
^2.2.2
"
,
"
d3-selection
"
:
"
^1.2.0
"
,
...
...
spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
0 → 100644
View file @
fba4ebfb
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`The DAG graph in the basic case renders the graph svg 1`] = `
"<svg viewBox=\\"0,0,1000,540\\" width=\\"1000\\" height=\\"540\\">
<g fill=\\"none\\" stroke-opacity=\\"0.8\\">
<g id=\\"dag-link43\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad53\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#e17223\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#83ab4a\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip63\\">
<path d=\\"
M100, 129
V158
H377.3333333333333
V100
H100
Z\\"></path>
</clipPath>
<path d=\\"M108,129L190,129L190,129L369.3333333333333,129\\" stroke=\\"url(#dag-grad53)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip63)\\"></path>
</g>
<g id=\\"dag-link44\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad54\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#83ab4a\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip64\\">
<path d=\\"
M361.3333333333333, 129.0000000000002
V158.0000000000002
H638.6666666666666
V100
H361.3333333333333
Z\\"></path>
</clipPath>
<path d=\\"M369.3333333333333,129L509.3333333333333,129L509.3333333333333,129.0000000000002L630.6666666666666,129.0000000000002\\" stroke=\\"url(#dag-grad54)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip64)\\"></path>
</g>
<g id=\\"dag-link45\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad55\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#5772ff\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip65\\">
<path d=\\"
M100, 187.0000000000002
V241.00000000000003
H638.6666666666666
V158.0000000000002
H100
Z\\"></path>
</clipPath>
<path d=\\"M108,212.00000000000003L306,212.00000000000003L306,187.0000000000002L630.6666666666666,187.0000000000002\\" stroke=\\"url(#dag-grad55)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip65)\\"></path>
</g>
<g id=\\"dag-link46\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad56\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#b24800\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip66\\">
<path d=\\"
M100, 269.9999999999998
V324
H377.3333333333333
V240.99999999999977
H100
Z\\"></path>
</clipPath>
<path d=\\"M108,295L338.93333333333334,295L338.93333333333334,269.9999999999998L369.3333333333333,269.9999999999998\\" stroke=\\"url(#dag-grad56)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip66)\\"></path>
</g>
<g id=\\"dag-link47\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad57\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#25d2d2\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#487900\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip67\\">
<path d=\\"
M100, 352.99999999999994
V407.00000000000006
H377.3333333333333
V323.99999999999994
H100
Z\\"></path>
</clipPath>
<path d=\\"M108,378.00000000000006L144.66666666666669,378.00000000000006L144.66666666666669,352.99999999999994L369.3333333333333,352.99999999999994\\" stroke=\\"url(#dag-grad57)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip67)\\"></path>
</g>
<g id=\\"dag-link48\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad58\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#006887\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip68\\">
<path d=\\"
M361.3333333333333, 270.0000000000001
V299.0000000000001
H638.6666666666666
V240.99999999999977
H361.3333333333333
Z\\"></path>
</clipPath>
<path d=\\"M369.3333333333333,269.9999999999998L464,269.9999999999998L464,270.0000000000001L630.6666666666666,270.0000000000001\\" stroke=\\"url(#dag-grad58)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip68)\\"></path>
</g>
<g id=\\"dag-link49\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad59\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip69\\">
<path d=\\"
M361.3333333333333, 328.0000000000001
V381.99999999999994
H638.6666666666666
V299.0000000000001
H361.3333333333333
Z\\"></path>
</clipPath>
<path d=\\"M369.3333333333333,352.99999999999994L522,352.99999999999994L522,328.0000000000001L630.6666666666666,328.0000000000001\\" stroke=\\"url(#dag-grad59)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip69)\\"></path>
</g>
<g id=\\"dag-link50\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad60\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#3547de\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip70\\">
<path d=\\"
M361.3333333333333, 411
V440
H638.6666666666666
V381.99999999999994
H361.3333333333333
Z\\"></path>
</clipPath>
<path d=\\"M369.3333333333333,410.99999999999994L580,410.99999999999994L580,411L630.6666666666666,411\\" stroke=\\"url(#dag-grad60)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip70)\\"></path>
</g>
<g id=\\"dag-link51\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad61\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#d84280\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip71\\">
<path d=\\"
M622.6666666666666, 270.1890725105691
V299.1890725105691
H900
V241.0000000000001
H622.6666666666666
Z\\"></path>
</clipPath>
<path d=\\"M630.6666666666666,270.0000000000001L861.6,270.0000000000001L861.6,270.1890725105691L892,270.1890725105691\\" stroke=\\"url(#dag-grad61)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip71)\\"></path>
</g>
<g id=\\"dag-link52\\" class=\\"dag-link gl-cursor-pointer\\">
<linearGradient id=\\"dag-grad62\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#3547de\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#275600\\"></stop>
</linearGradient>
<clipPath id=\\"dag-clip72\\">
<path d=\\"
M622.6666666666666, 411
V440
H900
V382
H622.6666666666666
Z\\"></path>
</clipPath>
<path d=\\"M630.6666666666666,411L679.9999999999999,411L679.9999999999999,411L892,411\\" stroke=\\"url(#dag-grad62)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip72)\\"></path>
</g>
</g>
<g>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node73\\" stroke=\\"#e17223\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"104\\" y2=\\"154.00000000000003\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node74\\" stroke=\\"#83ab4a\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"104\\" y2=\\"154\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node75\\" stroke=\\"#5772ff\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"187.00000000000003\\" y2=\\"237.00000000000003\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node76\\" stroke=\\"#b24800\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"270\\" y2=\\"320.00000000000006\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node77\\" stroke=\\"#25d2d2\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"353.00000000000006\\" y2=\\"403.0000000000001\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node78\\" stroke=\\"#6f3500\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"104.0000000000002\\" y2=\\"212.00000000000009\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node79\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"244.99999999999977\\" y2=\\"294.99999999999994\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node80\\" stroke=\\"#487900\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"327.99999999999994\\" y2=\\"436\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node81\\" stroke=\\"#d84280\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"245.00000000000009\\" y2=\\"353\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node82\\" stroke=\\"#3547de\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"386\\" y2=\\"436\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node83\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"245.18907251056908\\" y2=\\"295.1890725105691\\"></line>
<line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node84\\" stroke=\\"#275600\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"386\\" y2=\\"436\\"></line>
</g>
<g class=\\"gl-font-sm\\">
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58.00000000000003px\\" width=\\"84\\" x=\\"8\\" y=\\"100\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 58.00000000000003px; text-align: right;\\">build_a</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"25px\\" width=\\"84\\" x=\\"369.3333333333333\\" y=\\"75\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 25px; text-align: left;\\">test_a</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58px\\" width=\\"84\\" x=\\"8\\" y=\\"183.00000000000003\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 58px; text-align: right;\\">test_b</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58.00000000000006px\\" width=\\"84\\" x=\\"8\\" y=\\"266\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 58.00000000000006px; text-align: right;\\">post_test_a</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58.00000000000006px\\" width=\\"84\\" x=\\"8\\" y=\\"349.00000000000006\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 58.00000000000006px; text-align: right;\\">post_test_b</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"25px\\" width=\\"84\\" x=\\"630.6666666666666\\" y=\\"75.0000000000002\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 25px; text-align: right;\\">post_test_c</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"25px\\" width=\\"84\\" x=\\"369.3333333333333\\" y=\\"215.99999999999977\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 25px; text-align: left;\\">staging_a</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"25px\\" width=\\"84\\" x=\\"369.3333333333333\\" y=\\"298.99999999999994\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 25px; text-align: left;\\">staging_b</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"25px\\" width=\\"84\\" x=\\"630.6666666666666\\" y=\\"216.00000000000009\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 25px; text-align: right;\\">canary_a</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"25px\\" width=\\"84\\" x=\\"630.6666666666666\\" y=\\"357\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 25px; text-align: right;\\">canary_c</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58px\\" width=\\"84\\" x=\\"908\\" y=\\"241.18907251056908\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 58px; text-align: left;\\">production_a</div>
</foreignObject>
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58px\\" width=\\"84\\" x=\\"908\\" y=\\"382\\" class=\\"gl-overflow-visible\\">
<div class=\\"gl-display-flex gl-pointer-events-none gl-flex-direction-column gl-justify-content-center gl-overflow-wrap-break\\" style=\\"height: 58px; text-align: left;\\">production_d</div>
</foreignObject>
</g>
</svg>"
`;
spec/frontend/pipelines/components/dag/dag_graph_spec.js
0 → 100644
View file @
fba4ebfb
import
{
mount
}
from
'
@vue/test-utils
'
;
import
DagGraph
from
'
~/pipelines/components/dag/dag_graph.vue
'
;
import
{
createSankey
,
removeOrphanNodes
}
from
'
~/pipelines/components/dag/utils
'
;
import
{
parsedData
}
from
'
./mock_data
'
;
describe
(
'
The DAG graph
'
,
()
=>
{
let
wrapper
;
const
getGraph
=
()
=>
wrapper
.
find
(
'
.dag-graph-container > svg
'
);
const
getAllLinks
=
()
=>
wrapper
.
findAll
(
'
.dag-link
'
);
const
getAllNodes
=
()
=>
wrapper
.
findAll
(
'
.dag-node
'
);
const
getAllLabels
=
()
=>
wrapper
.
findAll
(
'
foreignObject
'
);
const
createComponent
=
(
propsData
=
{})
=>
{
if
(
wrapper
?.
destroy
)
{
wrapper
.
destroy
();
}
wrapper
=
mount
(
DagGraph
,
{
attachToDocument
:
true
,
propsData
,
data
()
{
return
{
color
:
()
=>
{},
width
:
0
,
height
:
0
,
};
},
});
};
beforeEach
(()
=>
{
createComponent
({
graphData
:
parsedData
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
in the basic case
'
,
()
=>
{
beforeEach
(()
=>
{
/*
The graph uses random to offset links. To keep the snapshot consistent,
we mock Math.random. Wheeeee!
*/
const
randomNumber
=
jest
.
spyOn
(
global
.
Math
,
'
random
'
);
randomNumber
.
mockImplementation
(()
=>
0.2
);
createComponent
({
graphData
:
parsedData
});
});
it
(
'
renders the graph svg
'
,
()
=>
{
expect
(
getGraph
().
exists
()).
toBe
(
true
);
expect
(
getGraph
().
html
()).
toMatchSnapshot
();
});
});
describe
(
'
links
'
,
()
=>
{
it
(
'
renders the expected number of links
'
,
()
=>
{
expect
(
getAllLinks
()).
toHaveLength
(
parsedData
.
links
.
length
);
});
it
(
'
renders the expected number of gradients
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
linearGradient
'
)).
toHaveLength
(
parsedData
.
links
.
length
);
});
it
(
'
renders the expected number of clip paths
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
clipPath
'
)).
toHaveLength
(
parsedData
.
links
.
length
);
});
});
describe
(
'
nodes and labels
'
,
()
=>
{
const
sankeyNodes
=
createSankey
()(
parsedData
).
nodes
;
const
processedNodes
=
removeOrphanNodes
(
sankeyNodes
);
describe
(
'
nodes
'
,
()
=>
{
it
(
'
renders the expected number of nodes
'
,
()
=>
{
expect
(
getAllNodes
()).
toHaveLength
(
processedNodes
.
length
);
});
});
describe
(
'
labels
'
,
()
=>
{
it
(
'
renders the expected number of labels as foreignObjects
'
,
()
=>
{
expect
(
getAllLabels
()).
toHaveLength
(
processedNodes
.
length
);
});
it
(
'
renders the title as text
'
,
()
=>
{
expect
(
getAllLabels
()
.
at
(
0
)
.
text
(),
).
toBe
(
parsedData
.
nodes
[
0
].
name
);
});
});
});
});
spec/frontend/pipelines/components/dag/dag_spec.js
View file @
fba4ebfb
import
{
m
ount
}
from
'
@vue/test-utils
'
;
import
{
shallowM
ount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
Dag
from
'
~/pipelines/components/dag/dag.vue
'
;
import
DagGraph
from
'
~/pipelines/components/dag/dag_graph.vue
'
;
describe
(
'
Pipeline DAG graph
'
,
()
=>
{
import
{
DEFAULT
,
PARSE_FAILURE
,
LOAD_FAILURE
,
UNSUPPORTED_DATA
,
}
from
'
~/pipelines/components/dag//constants
'
;
import
{
mockBaseData
,
tooSmallGraph
,
unparseableGraph
}
from
'
./mock_data
'
;
describe
(
'
Pipeline DAG graph wrapper
'
,
()
=>
{
let
wrapper
;
let
axiosM
ock
;
let
m
ock
;
const
getAlert
=
()
=>
wrapper
.
find
(
GlAlert
);
const
getGraph
=
()
=>
wrapper
.
find
(
'
[data-testid="dag-graph-container"]
'
);
const
dataPath
=
'
root/test/pipelines/90/dag.json
'
;
const
getGraph
=
()
=>
wrapper
.
find
(
DagGraph
);
const
getErrorText
=
type
=>
wrapper
.
vm
.
$options
.
errorTexts
[
type
];
const
createComponent
=
(
propsData
=
{},
method
=
mount
)
=>
{
axiosMock
=
new
MockAdapter
(
axios
);
const
dataPath
=
'
/root/test/pipelines/90/dag.json
'
;
const
createComponent
=
(
propsData
=
{})
=>
{
if
(
wrapper
?.
destroy
)
{
wrapper
.
destroy
();
}
wrapper
=
method
(
Dag
,
{
wrapper
=
shallowMount
(
Dag
,
{
propsData
,
data
()
{
return
{
...
...
@@ -30,8 +38,12 @@ describe('Pipeline DAG graph', () => {
});
};
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
axiosM
ock
.
restore
();
m
ock
.
restore
();
wrapper
.
destroy
();
wrapper
=
null
;
});
...
...
@@ -41,34 +53,80 @@ describe('Pipeline DAG graph', () => {
createComponent
({
graphUrl
:
undefined
});
});
it
(
'
shows the alert and not the graph
'
,
()
=>
{
it
(
'
shows the
DEFAULT
alert and not the graph
'
,
()
=>
{
expect
(
getAlert
().
exists
()).
toBe
(
true
);
expect
(
getAlert
().
text
()).
toBe
(
getErrorText
(
DEFAULT
));
expect
(
getGraph
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when there is a dataUrl
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
graphUrl
:
dataPath
});
describe
(
'
but the data fetch fails
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
dataPath
).
replyOnce
(
500
);
createComponent
({
graphUrl
:
dataPath
});
});
it
(
'
shows the LOAD_FAILURE alert and not the graph
'
,
()
=>
{
return
wrapper
.
vm
.
$nextTick
()
.
then
(
waitForPromises
)
.
then
(()
=>
{
expect
(
getAlert
().
exists
()).
toBe
(
true
);
expect
(
getAlert
().
text
()).
toBe
(
getErrorText
(
LOAD_FAILURE
));
expect
(
getGraph
().
exists
()).
toBe
(
false
);
});
});
});
it
(
'
shows the graph and not the alert
'
,
()
=>
{
expect
(
getAlert
().
exists
()).
toBe
(
false
);
expect
(
getGraph
().
exists
()).
toBe
(
true
);
describe
(
'
the data fetch succeeds but the parse fails
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
dataPath
).
replyOnce
(
200
,
unparseableGraph
);
createComponent
({
graphUrl
:
dataPath
});
});
it
(
'
shows the PARSE_FAILURE alert and not the graph
'
,
()
=>
{
return
wrapper
.
vm
.
$nextTick
()
.
then
(
waitForPromises
)
.
then
(()
=>
{
expect
(
getAlert
().
exists
()).
toBe
(
true
);
expect
(
getAlert
().
text
()).
toBe
(
getErrorText
(
PARSE_FAILURE
));
expect
(
getGraph
().
exists
()).
toBe
(
false
);
});
});
});
describe
(
'
but the data fetch fails
'
,
()
=>
{
describe
(
'
and the data fetch and parse succeeds
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
dataPath
).
replyOnce
(
200
,
mockBaseData
);
createComponent
({
graphUrl
:
dataPath
});
});
it
(
'
shows the graph and not the alert
'
,
()
=>
{
return
wrapper
.
vm
.
$nextTick
()
.
then
(
waitForPromises
)
.
then
(()
=>
{
expect
(
getAlert
().
exists
()).
toBe
(
false
);
expect
(
getGraph
().
exists
()).
toBe
(
true
);
});
});
});
describe
(
'
the data fetch and parse succeeds, but the resulting graph is too small
'
,
()
=>
{
beforeEach
(()
=>
{
axiosMock
.
onGet
(
dataPath
).
replyOnce
(
500
);
mock
.
onGet
(
dataPath
).
replyOnce
(
200
,
tooSmallGraph
);
createComponent
({
graphUrl
:
dataPath
});
});
it
(
'
shows the alert and not the graph
'
,
()
=>
{
it
(
'
shows the
UNSUPPORTED_DATA
alert and not the graph
'
,
()
=>
{
return
wrapper
.
vm
.
$nextTick
()
.
then
(
waitForPromises
)
.
then
(()
=>
{
expect
(
getAlert
().
exists
()).
toBe
(
true
);
expect
(
getAlert
().
text
()).
toBe
(
getErrorText
(
UNSUPPORTED_DATA
));
expect
(
getGraph
().
exists
()).
toBe
(
false
);
});
});
...
...
spec/frontend/pipelines/components/dag/mock_data.js
View file @
fba4ebfb
...
...
@@ -3,7 +3,7 @@
as well as non-parallel jobs with spaces in the name to prevent
us relying on spaces as an indicator.
*/
export
default
{
export
const
mockBaseData
=
{
stages
:
[
{
name
:
'
test
'
,
...
...
@@ -42,3 +42,349 @@ export default {
},
],
};
export
const
tooSmallGraph
=
{
stages
:
[
{
name
:
'
test
'
,
groups
:
[
{
name
:
'
jest
'
,
size
:
2
,
jobs
:
[{
name
:
'
jest 1/2
'
},
{
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
'
}],
},
],
},
],
};
export
const
unparseableGraph
=
[
{
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
:
'
un-needed
'
,
groups
:
[
{
name
:
'
un-needed
'
,
size
:
1
,
jobs
:
[{
name
:
'
un-needed
'
}],
},
],
},
];
/*
This represents data that has been parsed by the wrapper
*/
export
const
parsedData
=
{
nodes
:
[
{
name
:
'
build_a
'
,
size
:
1
,
jobs
:
[
{
name
:
'
build_a
'
,
},
],
category
:
'
build
'
,
},
{
name
:
'
build_b
'
,
size
:
1
,
jobs
:
[
{
name
:
'
build_b
'
,
},
],
category
:
'
build
'
,
},
{
name
:
'
test_a
'
,
size
:
1
,
jobs
:
[
{
name
:
'
test_a
'
,
needs
:
[
'
build_a
'
],
},
],
category
:
'
test
'
,
},
{
name
:
'
test_b
'
,
size
:
1
,
jobs
:
[
{
name
:
'
test_b
'
,
},
],
category
:
'
test
'
,
},
{
name
:
'
test_c
'
,
size
:
1
,
jobs
:
[
{
name
:
'
test_c
'
,
},
],
category
:
'
test
'
,
},
{
name
:
'
test_d
'
,
size
:
1
,
jobs
:
[
{
name
:
'
test_d
'
,
},
],
category
:
'
test
'
,
},
{
name
:
'
post_test_a
'
,
size
:
1
,
jobs
:
[
{
name
:
'
post_test_a
'
,
},
],
category
:
'
post-test
'
,
},
{
name
:
'
post_test_b
'
,
size
:
1
,
jobs
:
[
{
name
:
'
post_test_b
'
,
},
],
category
:
'
post-test
'
,
},
{
name
:
'
post_test_c
'
,
size
:
1
,
jobs
:
[
{
name
:
'
post_test_c
'
,
needs
:
[
'
test_a
'
,
'
test_b
'
],
},
],
category
:
'
post-test
'
,
},
{
name
:
'
staging_a
'
,
size
:
1
,
jobs
:
[
{
name
:
'
staging_a
'
,
needs
:
[
'
post_test_a
'
],
},
],
category
:
'
staging
'
,
},
{
name
:
'
staging_b
'
,
size
:
1
,
jobs
:
[
{
name
:
'
staging_b
'
,
needs
:
[
'
post_test_b
'
],
},
],
category
:
'
staging
'
,
},
{
name
:
'
staging_c
'
,
size
:
1
,
jobs
:
[
{
name
:
'
staging_c
'
,
},
],
category
:
'
staging
'
,
},
{
name
:
'
staging_d
'
,
size
:
1
,
jobs
:
[
{
name
:
'
staging_d
'
,
},
],
category
:
'
staging
'
,
},
{
name
:
'
staging_e
'
,
size
:
1
,
jobs
:
[
{
name
:
'
staging_e
'
,
},
],
category
:
'
staging
'
,
},
{
name
:
'
canary_a
'
,
size
:
1
,
jobs
:
[
{
name
:
'
canary_a
'
,
needs
:
[
'
staging_a
'
,
'
staging_b
'
],
},
],
category
:
'
canary
'
,
},
{
name
:
'
canary_b
'
,
size
:
1
,
jobs
:
[
{
name
:
'
canary_b
'
,
},
],
category
:
'
canary
'
,
},
{
name
:
'
canary_c
'
,
size
:
1
,
jobs
:
[
{
name
:
'
canary_c
'
,
needs
:
[
'
staging_b
'
],
},
],
category
:
'
canary
'
,
},
{
name
:
'
production_a
'
,
size
:
1
,
jobs
:
[
{
name
:
'
production_a
'
,
needs
:
[
'
canary_a
'
],
},
],
category
:
'
production
'
,
},
{
name
:
'
production_b
'
,
size
:
1
,
jobs
:
[
{
name
:
'
production_b
'
,
},
],
category
:
'
production
'
,
},
{
name
:
'
production_c
'
,
size
:
1
,
jobs
:
[
{
name
:
'
production_c
'
,
},
],
category
:
'
production
'
,
},
{
name
:
'
production_d
'
,
size
:
1
,
jobs
:
[
{
name
:
'
production_d
'
,
needs
:
[
'
canary_c
'
],
},
],
category
:
'
production
'
,
},
],
links
:
[
{
source
:
'
build_a
'
,
target
:
'
test_a
'
,
value
:
10
,
},
{
source
:
'
test_a
'
,
target
:
'
post_test_c
'
,
value
:
10
,
},
{
source
:
'
test_b
'
,
target
:
'
post_test_c
'
,
value
:
10
,
},
{
source
:
'
post_test_a
'
,
target
:
'
staging_a
'
,
value
:
10
,
},
{
source
:
'
post_test_b
'
,
target
:
'
staging_b
'
,
value
:
10
,
},
{
source
:
'
staging_a
'
,
target
:
'
canary_a
'
,
value
:
10
,
},
{
source
:
'
staging_b
'
,
target
:
'
canary_a
'
,
value
:
10
,
},
{
source
:
'
staging_b
'
,
target
:
'
canary_c
'
,
value
:
10
,
},
{
source
:
'
canary_a
'
,
target
:
'
production_a
'
,
value
:
10
,
},
{
source
:
'
canary_c
'
,
target
:
'
production_d
'
,
value
:
10
,
},
],
};
spec/frontend/pipelines/components/dag/utils_spec.js
View file @
fba4ebfb
...
...
@@ -8,12 +8,12 @@ import {
getMaxNodes
,
}
from
'
~/pipelines/components/dag/utils
'
;
import
mockGraphData
from
'
./mock_data
'
;
import
{
mockBaseData
}
from
'
./mock_data
'
;
describe
(
'
DAG visualization parsing utilities
'
,
()
=>
{
const
{
nodes
,
nodeDict
}
=
createNodesStructure
(
mock
Graph
Data
.
stages
);
const
{
nodes
,
nodeDict
}
=
createNodesStructure
(
mock
Base
Data
.
stages
);
const
unfilteredLinks
=
makeLinksFromNodes
(
nodes
,
nodeDict
);
const
parsed
=
parseData
(
mock
Graph
Data
.
stages
);
const
parsed
=
parseData
(
mock
Base
Data
.
stages
);
const
layoutSettings
=
{
width
:
200
,
...
...
@@ -30,10 +30,10 @@ describe('DAG visualization parsing utilities', () => {
const
parallelJobName
=
'
jest 1/2
'
;
const
singleJobName
=
'
frontend fixtures
'
;
const
{
name
,
jobs
,
size
}
=
mock
Graph
Data
.
stages
[
0
].
groups
[
0
];
const
{
name
,
jobs
,
size
}
=
mock
Base
Data
.
stages
[
0
].
groups
[
0
];
it
(
'
returns the expected node structure
'
,
()
=>
{
expect
(
nodes
[
0
]).
toHaveProperty
(
'
category
'
,
mock
Graph
Data
.
stages
[
0
].
name
);
expect
(
nodes
[
0
]).
toHaveProperty
(
'
category
'
,
mock
Base
Data
.
stages
[
0
].
name
);
expect
(
nodes
[
0
]).
toHaveProperty
(
'
name
'
,
name
);
expect
(
nodes
[
0
]).
toHaveProperty
(
'
jobs
'
,
jobs
);
expect
(
nodes
[
0
]).
toHaveProperty
(
'
size
'
,
size
);
...
...
yarn.lock
View file @
fba4ebfb
...
...
@@ -3551,10 +3551,10 @@ d3-zoom@1:
d3-selection "1"
d3-transition "1"
d3@^5.14, d3@^5.7.0:
version "5.1
5
.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.1
5.0.tgz#ffd44958e6a3cb8a59a84429c45429b8bca5677a
"
integrity sha512-
C+E80SL2nLLtmykZ6klwYj5rPqB5nlfN5LdWEAVdWPppqTD8taoJi2PxLZjPeYT8FFRR2yucXq+kBlOnnvZeLg
==
d3@^5.14, d3@^5.
16.0, d3@^5.
7.0:
version "5.1
6
.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.1
6.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877
"
integrity sha512-
4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw
==
dependencies:
d3-array "1"
d3-axis "1"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment