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
773c29ee
Commit
773c29ee
authored
Jun 25, 2020
by
Sarah Groff Hennigh-Palermo
Committed by
Andrew Fontaine
Jun 25, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add annotation component
Adds and integrated component Fixes interaction bug on nodes
parent
db66b4e5
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
421 additions
and
37 deletions
+421
-37
app/assets/javascripts/pipelines/components/dag/constants.js
app/assets/javascripts/pipelines/components/dag/constants.js
+5
-0
app/assets/javascripts/pipelines/components/dag/dag.vue
app/assets/javascripts/pipelines/components/dag/dag.vue
+48
-4
app/assets/javascripts/pipelines/components/dag/dag_annotations.vue
.../javascripts/pipelines/components/dag/dag_annotations.vue
+73
-0
app/assets/javascripts/pipelines/components/dag/dag_graph.vue
...assets/javascripts/pipelines/components/dag/dag_graph.vue
+40
-15
app/assets/javascripts/pipelines/components/dag/interactions.js
...sets/javascripts/pipelines/components/dag/interactions.js
+35
-15
changelogs/unreleased/221225-dag-basic-annotations.yml
changelogs/unreleased/221225-dag-basic-annotations.yml
+5
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/frontend/pipelines/components/dag/dag_annotations_spec.js
...frontend/pipelines/components/dag/dag_annotations_spec.js
+112
-0
spec/frontend/pipelines/components/dag/dag_graph_spec.js
spec/frontend/pipelines/components/dag/dag_graph_spec.js
+2
-2
spec/frontend/pipelines/components/dag/dag_spec.js
spec/frontend/pipelines/components/dag/dag_spec.js
+55
-1
spec/frontend/pipelines/components/dag/mock_data.js
spec/frontend/pipelines/components/dag/mock_data.js
+40
-0
No files found.
app/assets/javascripts/pipelines/components/dag/constants.js
View file @
773c29ee
...
@@ -8,3 +8,8 @@ export const DEFAULT = 'default';
...
@@ -8,3 +8,8 @@ export const DEFAULT = 'default';
export
const
IS_HIGHLIGHTED
=
'
dag-highlighted
'
;
export
const
IS_HIGHLIGHTED
=
'
dag-highlighted
'
;
export
const
LINK_SELECTOR
=
'
dag-link
'
;
export
const
LINK_SELECTOR
=
'
dag-link
'
;
export
const
NODE_SELECTOR
=
'
dag-node
'
;
export
const
NODE_SELECTOR
=
'
dag-node
'
;
/* Annotation types */
export
const
ADD_NOTE
=
'
add
'
;
export
const
REMOVE_NOTE
=
'
remove
'
;
export
const
REPLACE_NOTES
=
'
replace
'
;
app/assets/javascripts/pipelines/components/dag/dag.vue
View file @
773c29ee
<
script
>
<
script
>
import
{
GlAlert
,
GlLink
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
,
GlLink
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
isEmpty
}
from
'
lodash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
DagGraph
from
'
./dag_graph.vue
'
;
import
DagGraph
from
'
./dag_graph.vue
'
;
import
{
DEFAULT
,
PARSE_FAILURE
,
LOAD_FAILURE
,
UNSUPPORTED_DATA
}
from
'
./constants
'
;
import
DagAnnotations
from
'
./dag_annotations.vue
'
;
import
{
DEFAULT
,
PARSE_FAILURE
,
LOAD_FAILURE
,
UNSUPPORTED_DATA
,
ADD_NOTE
,
REMOVE_NOTE
,
REPLACE_NOTES
,
}
from
'
./constants
'
;
import
{
parseData
}
from
'
./parsing_utils
'
;
import
{
parseData
}
from
'
./parsing_utils
'
;
export
default
{
export
default
{
// eslint-disable-next-line @gitlab/require-i18n-strings
// eslint-disable-next-line @gitlab/require-i18n-strings
name
:
'
Dag
'
,
name
:
'
Dag
'
,
components
:
{
components
:
{
DagAnnotations
,
DagGraph
,
DagGraph
,
GlAlert
,
GlAlert
,
GlLink
,
GlLink
,
...
@@ -24,10 +35,11 @@ export default {
...
@@ -24,10 +35,11 @@ export default {
},
},
data
()
{
data
()
{
return
{
return
{
showFailureAlert
:
false
,
annotationsMap
:
{},
showBetaInfo
:
true
,
failureType
:
null
,
failureType
:
null
,
graphData
:
null
,
graphData
:
null
,
showFailureAlert
:
false
,
showBetaInfo
:
true
,
};
};
},
},
errorTexts
:
{
errorTexts
:
{
...
@@ -66,6 +78,9 @@ export default {
...
@@ -66,6 +78,9 @@ export default {
};
};
}
}
},
},
shouldDisplayAnnotations
()
{
return
!
isEmpty
(
this
.
annotationsMap
);
},
shouldDisplayGraph
()
{
shouldDisplayGraph
()
{
return
Boolean
(
!
this
.
showFailureAlert
&&
this
.
graphData
);
return
Boolean
(
!
this
.
showFailureAlert
&&
this
.
graphData
);
},
},
...
@@ -86,6 +101,9 @@ export default {
...
@@ -86,6 +101,9 @@ export default {
.
catch
(()
=>
reportFailure
(
LOAD_FAILURE
));
.
catch
(()
=>
reportFailure
(
LOAD_FAILURE
));
},
},
methods
:
{
methods
:
{
addAnnotationToMap
({
uid
,
source
,
target
})
{
this
.
$set
(
this
.
annotationsMap
,
uid
,
{
source
,
target
});
},
processGraphData
(
data
)
{
processGraphData
(
data
)
{
let
parsed
;
let
parsed
;
...
@@ -109,10 +127,28 @@ export default {
...
@@ -109,10 +127,28 @@ export default {
hideBetaInfo
()
{
hideBetaInfo
()
{
this
.
showBetaInfo
=
false
;
this
.
showBetaInfo
=
false
;
},
},
removeAnnotationFromMap
({
uid
})
{
this
.
$delete
(
this
.
annotationsMap
,
uid
);
},
reportFailure
(
type
)
{
reportFailure
(
type
)
{
this
.
showFailureAlert
=
true
;
this
.
showFailureAlert
=
true
;
this
.
failureType
=
type
;
this
.
failureType
=
type
;
},
},
updateAnnotation
({
type
,
data
})
{
switch
(
type
)
{
case
ADD_NOTE
:
this
.
addAnnotationToMap
(
data
);
break
;
case
REMOVE_NOTE
:
this
.
removeAnnotationFromMap
(
data
);
break
;
case
REPLACE_NOTES
:
this
.
annotationsMap
=
data
;
break
;
default
:
break
;
}
},
},
},
};
};
</
script
>
</
script
>
...
@@ -131,6 +167,14 @@ export default {
...
@@ -131,6 +167,14 @@ export default {
</
template
>
</
template
>
</gl-sprintf>
</gl-sprintf>
</gl-alert>
</gl-alert>
<dag-graph
v-if=
"shouldDisplayGraph"
:graph-data=
"graphData"
@
onFailure=
"reportFailure"
/>
<div
class=
"gl-relative"
>
<dag-annotations
v-if=
"shouldDisplayAnnotations"
:annotations=
"annotationsMap"
/>
<dag-graph
v-if=
"shouldDisplayGraph"
:graph-data=
"graphData"
@
on-failure=
"reportFailure"
@
update-annotation=
"updateAnnotation"
/>
</div>
</div>
</div>
</template>
</template>
app/assets/javascripts/pipelines/components/dag/dag_annotations.vue
0 → 100644
View file @
773c29ee
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
__
}
from
'
~/locale
'
;
export
default
{
name
:
'
DagAnnotations
'
,
components
:
{
GlButton
,
},
props
:
{
annotations
:
{
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
showList
:
true
,
};
},
computed
:
{
linkText
()
{
return
this
.
showList
?
__
(
'
Hide list
'
)
:
__
(
'
Show list
'
);
},
shouldShowLink
()
{
return
Object
.
keys
(
this
.
annotations
).
length
>
1
;
},
wrapperClasses
()
{
return
[
'
gl-display-flex
'
,
'
gl-flex-direction-column
'
,
'
gl-absolute
'
,
'
gl-right-1
'
,
'
gl-top-0
'
,
'
gl-w-max-content
'
,
'
gl-px-5
'
,
'
gl-py-4
'
,
'
gl-rounded-base
'
,
'
gl-bg-white
'
,
].
join
(
'
'
);
},
},
methods
:
{
toggleList
()
{
this
.
showList
=
!
this
.
showList
;
},
},
};
</
script
>
<
template
>
<div
:class=
"wrapperClasses"
>
<div
v-if=
"showList"
>
<div
v-for=
"note in annotations"
:key=
"note.uid"
class=
"gl-display-flex gl-align-items-center"
>
<div
data-testid=
"dag-color-block"
class=
"gl-w-6 gl-h-5"
:style=
"
{
background: `linear-gradient(0.25turn, ${note.source.color} 40%, ${note.target.color} 60%)`,
}"
>
</div>
<div
data-testid=
"dag-note-text"
class=
"gl-px-2 gl-font-base gl-align-items-center"
>
{{
note
.
source
.
name
}}
→
{{
note
.
target
.
name
}}
</div>
</div>
</div>
<gl-button
v-if=
"shouldShowLink"
variant=
"link"
@
click=
"toggleList"
>
{{
linkText
}}
</gl-button>
</div>
</
template
>
app/assets/javascripts/pipelines/components/dag/dag_graph.vue
View file @
773c29ee
<
script
>
<
script
>
import
*
as
d3
from
'
d3
'
;
import
*
as
d3
from
'
d3
'
;
import
{
uniqueId
}
from
'
lodash
'
;
import
{
uniqueId
}
from
'
lodash
'
;
import
{
LINK_SELECTOR
,
NODE_SELECTOR
,
PARSE_FAILURE
}
from
'
./constants
'
;
import
{
import
{
LINK_SELECTOR
,
NODE_SELECTOR
,
PARSE_FAILURE
,
ADD_NOTE
,
REMOVE_NOTE
,
REPLACE_NOTES
,
}
from
'
./constants
'
;
import
{
currentIsLive
,
getLiveLinksAsDict
,
highlightLinks
,
highlightLinks
,
restoreLinks
,
restoreLinks
,
toggleLinkHighlight
,
toggleLinkHighlight
,
...
@@ -55,8 +64,8 @@ export default {
...
@@ -55,8 +64,8 @@ export default {
data
()
{
data
()
{
return
{
return
{
color
:
()
=>
{},
color
:
()
=>
{},
width
:
0
,
height
:
0
,
height
:
0
,
width
:
0
,
};
};
},
},
mounted
()
{
mounted
()
{
...
@@ -65,7 +74,7 @@ export default {
...
@@ -65,7 +74,7 @@ export default {
try
{
try
{
countedAndTransformed
=
this
.
transformData
(
this
.
graphData
);
countedAndTransformed
=
this
.
transformData
(
this
.
graphData
);
}
catch
{
}
catch
{
this
.
$emit
(
'
on
F
ailure
'
,
PARSE_FAILURE
);
this
.
$emit
(
'
on
-f
ailure
'
,
PARSE_FAILURE
);
return
;
return
;
}
}
...
@@ -95,17 +104,33 @@ export default {
...
@@ -95,17 +104,33 @@ export default {
},
},
appendLinkInteractions
(
link
)
{
appendLinkInteractions
(
link
)
{
const
{
baseOpacity
}
=
this
.
$options
.
viewOptions
;
return
link
return
link
.
on
(
'
mouseover
'
,
highlightLinks
)
.
on
(
'
mouseover
'
,
(
d
,
idx
,
collection
)
=>
{
.
on
(
'
mouseout
'
,
restoreLinks
.
bind
(
null
,
this
.
$options
.
viewOptions
.
baseOpacity
))
if
(
currentIsLive
(
idx
,
collection
))
{
.
on
(
'
click
'
,
toggleLinkHighlight
.
bind
(
null
,
this
.
$options
.
viewOptions
.
baseOpacity
));
return
;
}
this
.
$emit
(
'
update-annotation
'
,
{
type
:
ADD_NOTE
,
data
:
d
});
highlightLinks
(
d
,
idx
,
collection
);
})
.
on
(
'
mouseout
'
,
(
d
,
idx
,
collection
)
=>
{
if
(
currentIsLive
(
idx
,
collection
))
{
return
;
}
this
.
$emit
(
'
update-annotation
'
,
{
type
:
REMOVE_NOTE
,
data
:
d
});
restoreLinks
(
baseOpacity
);
})
.
on
(
'
click
'
,
(
d
,
idx
,
collection
)
=>
{
toggleLinkHighlight
(
baseOpacity
,
d
,
idx
,
collection
);
this
.
$emit
(
'
update-annotation
'
,
{
type
:
REPLACE_NOTES
,
data
:
getLiveLinksAsDict
()
});
});
},
},
appendNodeInteractions
(
node
)
{
appendNodeInteractions
(
node
)
{
return
node
.
on
(
return
node
.
on
(
'
click
'
,
(
d
,
idx
,
collection
)
=>
{
'
click
'
,
togglePathHighlights
(
this
.
$options
.
viewOptions
.
baseOpacity
,
d
,
idx
,
collection
);
t
ogglePathHighlights
.
bind
(
null
,
this
.
$options
.
viewOptions
.
baseOpacity
),
t
his
.
$emit
(
'
update-annotation
'
,
{
type
:
REPLACE_NOTES
,
data
:
getLiveLinksAsDict
()
});
);
}
);
},
},
appendLabelAsForeignObject
(
d
,
i
,
n
)
{
appendLabelAsForeignObject
(
d
,
i
,
n
)
{
...
@@ -271,6 +296,11 @@ export default {
...
@@ -271,6 +296,11 @@ export default {
.
attr
(
'
y2
'
,
d
=>
d
.
y1
-
4
);
.
attr
(
'
y2
'
,
d
=>
d
.
y1
-
4
);
},
},
initColors
()
{
const
colorFn
=
d3
.
scaleOrdinal
(
this
.
$options
.
gitLabColorRotation
);
return
({
name
})
=>
colorFn
(
name
);
},
labelNodes
(
svg
,
nodeData
)
{
labelNodes
(
svg
,
nodeData
)
{
return
svg
return
svg
.
append
(
'
g
'
)
.
append
(
'
g
'
)
...
@@ -282,11 +312,6 @@ export default {
...
@@ -282,11 +312,6 @@ export default {
.
each
(
this
.
appendLabelAsForeignObject
);
.
each
(
this
.
appendLabelAsForeignObject
);
},
},
initColors
()
{
const
colorFn
=
d3
.
scaleOrdinal
(
this
.
$options
.
gitLabColorRotation
);
return
({
name
})
=>
colorFn
(
name
);
},
transformData
(
parsed
)
{
transformData
(
parsed
)
{
const
baseLayout
=
createSankey
()(
parsed
);
const
baseLayout
=
createSankey
()(
parsed
);
const
cleanedNodes
=
removeOrphanNodes
(
baseLayout
.
nodes
);
const
cleanedNodes
=
removeOrphanNodes
(
baseLayout
.
nodes
);
...
...
app/assets/javascripts/pipelines/components/dag/interactions.js
View file @
773c29ee
...
@@ -5,10 +5,20 @@ export const highlightIn = 1;
...
@@ -5,10 +5,20 @@ export const highlightIn = 1;
export
const
highlightOut
=
0.2
;
export
const
highlightOut
=
0.2
;
const
getCurrent
=
(
idx
,
collection
)
=>
d3
.
select
(
collection
[
idx
]);
const
getCurrent
=
(
idx
,
collection
)
=>
d3
.
select
(
collection
[
idx
]);
const
currentIsLive
=
(
idx
,
collection
)
=>
getCurrent
(
idx
,
collection
).
classed
(
IS_HIGHLIGHTED
);
const
getLiveLinks
=
()
=>
d3
.
selectAll
(
`.
${
LINK_SELECTOR
}
.
${
IS_HIGHLIGHTED
}
`
);
const
getOtherLinks
=
()
=>
d3
.
selectAll
(
`.
${
LINK_SELECTOR
}
:not(.
${
IS_HIGHLIGHTED
}
)`
);
const
getOtherLinks
=
()
=>
d3
.
selectAll
(
`.
${
LINK_SELECTOR
}
:not(.
${
IS_HIGHLIGHTED
}
)`
);
const
getNodesNotLive
=
()
=>
d3
.
selectAll
(
`.
${
NODE_SELECTOR
}
:not(.
${
IS_HIGHLIGHTED
}
)`
);
const
getNodesNotLive
=
()
=>
d3
.
selectAll
(
`.
${
NODE_SELECTOR
}
:not(.
${
IS_HIGHLIGHTED
}
)`
);
export
const
getLiveLinksAsDict
=
()
=>
{
return
Object
.
fromEntries
(
getLiveLinks
()
.
data
()
.
map
(
d
=>
[
d
.
uid
,
d
]),
);
};
export
const
currentIsLive
=
(
idx
,
collection
)
=>
getCurrent
(
idx
,
collection
).
classed
(
IS_HIGHLIGHTED
);
const
backgroundLinks
=
selection
=>
selection
.
style
(
'
stroke-opacity
'
,
highlightOut
);
const
backgroundLinks
=
selection
=>
selection
.
style
(
'
stroke-opacity
'
,
highlightOut
);
const
backgroundNodes
=
selection
=>
selection
.
attr
(
'
stroke
'
,
'
#f2f2f2
'
);
const
backgroundNodes
=
selection
=>
selection
.
attr
(
'
stroke
'
,
'
#f2f2f2
'
);
const
foregroundLinks
=
selection
=>
selection
.
style
(
'
stroke-opacity
'
,
highlightIn
);
const
foregroundLinks
=
selection
=>
selection
.
style
(
'
stroke-opacity
'
,
highlightIn
);
...
@@ -16,10 +26,10 @@ const foregroundNodes = selection => selection.attr('stroke', d => d.color);
...
@@ -16,10 +26,10 @@ const foregroundNodes = selection => selection.attr('stroke', d => d.color);
const
renewLinks
=
(
selection
,
baseOpacity
)
=>
selection
.
style
(
'
stroke-opacity
'
,
baseOpacity
);
const
renewLinks
=
(
selection
,
baseOpacity
)
=>
selection
.
style
(
'
stroke-opacity
'
,
baseOpacity
);
const
renewNodes
=
selection
=>
selection
.
attr
(
'
stroke
'
,
d
=>
d
.
color
);
const
renewNodes
=
selection
=>
selection
.
attr
(
'
stroke
'
,
d
=>
d
.
color
);
const
getAllLinkAncestors
=
node
=>
{
export
const
getAllLinkAncestors
=
node
=>
{
if
(
node
.
targetLinks
)
{
if
(
node
.
targetLinks
)
{
return
node
.
targetLinks
.
flatMap
(
n
=>
{
return
node
.
targetLinks
.
flatMap
(
n
=>
{
return
[
n
.
uid
,
...
getAllLinkAncestors
(
n
.
source
)];
return
[
n
,
...
getAllLinkAncestors
(
n
.
source
)];
});
});
}
}
...
@@ -59,8 +69,8 @@ const highlightPath = (parentLinks, parentNodes) => {
...
@@ -59,8 +69,8 @@ const highlightPath = (parentLinks, parentNodes) => {
backgroundNodes
(
getNodesNotLive
());
backgroundNodes
(
getNodesNotLive
());
/* highlight correct links */
/* highlight correct links */
parentLinks
.
forEach
(
id
=>
{
parentLinks
.
forEach
(
({
uid
})
=>
{
foregroundLinks
(
d3
.
select
(
`#
${
id
}
`
)).
classed
(
IS_HIGHLIGHTED
,
true
);
foregroundLinks
(
d3
.
select
(
`#
${
u
id
}
`
)).
classed
(
IS_HIGHLIGHTED
,
true
);
});
});
/* highlight correct nodes */
/* highlight correct nodes */
...
@@ -69,9 +79,22 @@ const highlightPath = (parentLinks, parentNodes) => {
...
@@ -69,9 +79,22 @@ const highlightPath = (parentLinks, parentNodes) => {
});
});
};
};
const
restoreNodes
=
()
=>
{
/*
When paths are unclicked, they can take down nodes that
are still in use for other paths. This checks the live paths and
rehighlights their nodes.
*/
getLiveLinks
().
each
(
d
=>
{
foregroundNodes
(
d3
.
select
(
`#
${
d
.
source
.
uid
}
`
)).
classed
(
IS_HIGHLIGHTED
,
true
);
foregroundNodes
(
d3
.
select
(
`#
${
d
.
target
.
uid
}
`
)).
classed
(
IS_HIGHLIGHTED
,
true
);
});
};
const
restorePath
=
(
parentLinks
,
parentNodes
,
baseOpacity
)
=>
{
const
restorePath
=
(
parentLinks
,
parentNodes
,
baseOpacity
)
=>
{
parentLinks
.
forEach
(
id
=>
{
parentLinks
.
forEach
(
({
uid
})
=>
{
renewLinks
(
d3
.
select
(
`#
${
id
}
`
),
baseOpacity
).
classed
(
IS_HIGHLIGHTED
,
false
);
renewLinks
(
d3
.
select
(
`#
${
u
id
}
`
),
baseOpacity
).
classed
(
IS_HIGHLIGHTED
,
false
);
});
});
parentNodes
.
forEach
(
id
=>
{
parentNodes
.
forEach
(
id
=>
{
...
@@ -86,14 +109,10 @@ const restorePath = (parentLinks, parentNodes, baseOpacity) => {
...
@@ -86,14 +109,10 @@ const restorePath = (parentLinks, parentNodes, baseOpacity) => {
backgroundLinks
(
getOtherLinks
());
backgroundLinks
(
getOtherLinks
());
backgroundNodes
(
getNodesNotLive
());
backgroundNodes
(
getNodesNotLive
());
restoreNodes
();
};
};
export
const
restoreLinks
=
(
baseOpacity
,
d
,
idx
,
collection
)
=>
{
export
const
restoreLinks
=
baseOpacity
=>
{
/* in this case, it has just been clicked */
if
(
currentIsLive
(
idx
,
collection
))
{
return
;
}
/*
/*
if there exist live links, reset to highlight out / pale
if there exist live links, reset to highlight out / pale
otherwise, reset to base
otherwise, reset to base
...
@@ -111,11 +130,12 @@ export const restoreLinks = (baseOpacity, d, idx, collection) => {
...
@@ -111,11 +130,12 @@ export const restoreLinks = (baseOpacity, d, idx, collection) => {
export
const
toggleLinkHighlight
=
(
baseOpacity
,
d
,
idx
,
collection
)
=>
{
export
const
toggleLinkHighlight
=
(
baseOpacity
,
d
,
idx
,
collection
)
=>
{
if
(
currentIsLive
(
idx
,
collection
))
{
if
(
currentIsLive
(
idx
,
collection
))
{
restorePath
([
d
.
uid
],
[
d
.
source
.
uid
,
d
.
target
.
uid
],
baseOpacity
);
restorePath
([
d
],
[
d
.
source
.
uid
,
d
.
target
.
uid
],
baseOpacity
);
restoreNodes
();
return
;
return
;
}
}
highlightPath
([
d
.
uid
],
[
d
.
source
.
uid
,
d
.
target
.
uid
]);
highlightPath
([
d
],
[
d
.
source
.
uid
,
d
.
target
.
uid
]);
};
};
export
const
togglePathHighlights
=
(
baseOpacity
,
d
,
idx
,
collection
)
=>
{
export
const
togglePathHighlights
=
(
baseOpacity
,
d
,
idx
,
collection
)
=>
{
...
...
changelogs/unreleased/221225-dag-basic-annotations.yml
0 → 100644
View file @
773c29ee
---
title
:
Add annotation component for DAG
merge_request
:
35240
author
:
type
:
added
locale/gitlab.pot
View file @
773c29ee
...
@@ -11709,6 +11709,9 @@ msgstr ""
...
@@ -11709,6 +11709,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgid "Hide host keys manual input"
msgstr ""
msgstr ""
msgid "Hide list"
msgstr ""
msgid "Hide marketing-related entries from help"
msgid "Hide marketing-related entries from help"
msgstr ""
msgstr ""
...
@@ -20783,6 +20786,9 @@ msgstr ""
...
@@ -20783,6 +20786,9 @@ msgstr ""
msgid "Show latest version"
msgid "Show latest version"
msgstr ""
msgstr ""
msgid "Show list"
msgstr ""
msgid "Show me everything"
msgid "Show me everything"
msgstr ""
msgstr ""
...
...
spec/frontend/pipelines/components/dag/dag_annotations_spec.js
0 → 100644
View file @
773c29ee
import
{
shallowMount
,
mount
}
from
'
@vue/test-utils
'
;
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
DagAnnotations
from
'
~/pipelines/components/dag/dag_annotations.vue
'
;
import
{
singleNote
,
multiNote
}
from
'
./mock_data
'
;
describe
(
'
The DAG annotations
'
,
()
=>
{
let
wrapper
;
const
getColorBlock
=
()
=>
wrapper
.
find
(
'
[data-testid="dag-color-block"]
'
);
const
getAllColorBlocks
=
()
=>
wrapper
.
findAll
(
'
[data-testid="dag-color-block"]
'
);
const
getTextBlock
=
()
=>
wrapper
.
find
(
'
[data-testid="dag-note-text"]
'
);
const
getAllTextBlocks
=
()
=>
wrapper
.
findAll
(
'
[data-testid="dag-note-text"]
'
);
const
getToggleButton
=
()
=>
wrapper
.
find
(
GlButton
);
const
createComponent
=
(
propsData
=
{},
method
=
shallowMount
)
=>
{
if
(
wrapper
?.
destroy
)
{
wrapper
.
destroy
();
}
wrapper
=
method
(
DagAnnotations
,
{
propsData
,
data
()
{
return
{
showList
:
true
,
};
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
when there is one annotation
'
,
()
=>
{
const
currentNote
=
singleNote
[
'
dag-link103
'
];
beforeEach
(()
=>
{
createComponent
({
annotations
:
singleNote
});
});
it
(
'
displays the color block
'
,
()
=>
{
expect
(
getColorBlock
().
exists
()).
toBe
(
true
);
});
it
(
'
displays the text block
'
,
()
=>
{
expect
(
getTextBlock
().
exists
()).
toBe
(
true
);
expect
(
getTextBlock
().
text
()).
toBe
(
`
${
currentNote
.
source
.
name
}
→
${
currentNote
.
target
.
name
}
`
);
});
it
(
'
does not display the list toggle link
'
,
()
=>
{
expect
(
getToggleButton
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when there are multiple annoataions
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
annotations
:
multiNote
});
});
it
(
'
displays a color block for each link
'
,
()
=>
{
expect
(
getAllColorBlocks
().
length
).
toBe
(
Object
.
keys
(
multiNote
).
length
);
});
it
(
'
displays a text block for each link
'
,
()
=>
{
expect
(
getAllTextBlocks
().
length
).
toBe
(
Object
.
keys
(
multiNote
).
length
);
Object
.
values
(
multiNote
).
forEach
((
item
,
idx
)
=>
{
expect
(
getAllTextBlocks
()
.
at
(
idx
)
.
text
(),
).
toBe
(
`
${
item
.
source
.
name
}
→
${
item
.
target
.
name
}
`
);
});
});
it
(
'
displays the list toggle link
'
,
()
=>
{
expect
(
getToggleButton
().
exists
()).
toBe
(
true
);
expect
(
getToggleButton
().
text
()).
toBe
(
'
Hide list
'
);
});
});
describe
(
'
the list toggle
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
annotations
:
multiNote
},
mount
);
});
describe
(
'
clicking hide
'
,
()
=>
{
it
(
'
hides listed items and changes text to show
'
,
()
=>
{
expect
(
getAllTextBlocks
().
length
).
toBe
(
Object
.
keys
(
multiNote
).
length
);
expect
(
getToggleButton
().
text
()).
toBe
(
'
Hide list
'
);
getToggleButton
().
trigger
(
'
click
'
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
getAllTextBlocks
().
length
).
toBe
(
0
);
expect
(
getToggleButton
().
text
()).
toBe
(
'
Show list
'
);
});
});
});
describe
(
'
clicking show
'
,
()
=>
{
it
(
'
shows listed items and changes text to hide
'
,
()
=>
{
getToggleButton
().
trigger
(
'
click
'
);
getToggleButton
().
trigger
(
'
click
'
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
getAllTextBlocks
().
length
).
toBe
(
Object
.
keys
(
multiNote
).
length
);
expect
(
getToggleButton
().
text
()).
toBe
(
'
Hide list
'
);
});
});
});
});
});
spec/frontend/pipelines/components/dag/dag_graph_spec.js
View file @
773c29ee
import
{
m
ount
}
from
'
@vue/test-utils
'
;
import
{
shallowM
ount
}
from
'
@vue/test-utils
'
;
import
DagGraph
from
'
~/pipelines/components/dag/dag_graph.vue
'
;
import
DagGraph
from
'
~/pipelines/components/dag/dag_graph.vue
'
;
import
{
IS_HIGHLIGHTED
,
LINK_SELECTOR
,
NODE_SELECTOR
}
from
'
~/pipelines/components/dag/constants
'
;
import
{
IS_HIGHLIGHTED
,
LINK_SELECTOR
,
NODE_SELECTOR
}
from
'
~/pipelines/components/dag/constants
'
;
import
{
highlightIn
,
highlightOut
}
from
'
~/pipelines/components/dag/interactions
'
;
import
{
highlightIn
,
highlightOut
}
from
'
~/pipelines/components/dag/interactions
'
;
...
@@ -19,7 +19,7 @@ describe('The DAG graph', () => {
...
@@ -19,7 +19,7 @@ describe('The DAG graph', () => {
wrapper
.
destroy
();
wrapper
.
destroy
();
}
}
wrapper
=
m
ount
(
DagGraph
,
{
wrapper
=
shallowM
ount
(
DagGraph
,
{
attachToDocument
:
true
,
attachToDocument
:
true
,
propsData
,
propsData
,
data
()
{
data
()
{
...
...
spec/frontend/pipelines/components/dag/dag_spec.js
View file @
773c29ee
...
@@ -5,14 +5,18 @@ import waitForPromises from 'helpers/wait_for_promises';
...
@@ -5,14 +5,18 @@ import waitForPromises from 'helpers/wait_for_promises';
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
Dag
from
'
~/pipelines/components/dag/dag.vue
'
;
import
Dag
from
'
~/pipelines/components/dag/dag.vue
'
;
import
DagGraph
from
'
~/pipelines/components/dag/dag_graph.vue
'
;
import
DagGraph
from
'
~/pipelines/components/dag/dag_graph.vue
'
;
import
DagAnnotations
from
'
~/pipelines/components/dag/dag_annotations.vue
'
;
import
{
import
{
ADD_NOTE
,
REMOVE_NOTE
,
REPLACE_NOTES
,
DEFAULT
,
DEFAULT
,
PARSE_FAILURE
,
PARSE_FAILURE
,
LOAD_FAILURE
,
LOAD_FAILURE
,
UNSUPPORTED_DATA
,
UNSUPPORTED_DATA
,
}
from
'
~/pipelines/components/dag//constants
'
;
}
from
'
~/pipelines/components/dag//constants
'
;
import
{
mockBaseData
,
tooSmallGraph
,
unparseableGraph
}
from
'
./mock_data
'
;
import
{
mockBaseData
,
tooSmallGraph
,
unparseableGraph
,
singleNote
,
multiNote
}
from
'
./mock_data
'
;
describe
(
'
Pipeline DAG graph wrapper
'
,
()
=>
{
describe
(
'
Pipeline DAG graph wrapper
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
...
@@ -20,6 +24,7 @@ describe('Pipeline DAG graph wrapper', () => {
...
@@ -20,6 +24,7 @@ describe('Pipeline DAG graph wrapper', () => {
const
getAlert
=
()
=>
wrapper
.
find
(
GlAlert
);
const
getAlert
=
()
=>
wrapper
.
find
(
GlAlert
);
const
getAllAlerts
=
()
=>
wrapper
.
findAll
(
GlAlert
);
const
getAllAlerts
=
()
=>
wrapper
.
findAll
(
GlAlert
);
const
getGraph
=
()
=>
wrapper
.
find
(
DagGraph
);
const
getGraph
=
()
=>
wrapper
.
find
(
DagGraph
);
const
getNotes
=
()
=>
wrapper
.
find
(
DagAnnotations
);
const
getErrorText
=
type
=>
wrapper
.
vm
.
$options
.
errorTexts
[
type
];
const
getErrorText
=
type
=>
wrapper
.
vm
.
$options
.
errorTexts
[
type
];
const
dataPath
=
'
/root/test/pipelines/90/dag.json
'
;
const
dataPath
=
'
/root/test/pipelines/90/dag.json
'
;
...
@@ -134,4 +139,53 @@ describe('Pipeline DAG graph wrapper', () => {
...
@@ -134,4 +139,53 @@ describe('Pipeline DAG graph wrapper', () => {
});
});
});
});
});
});
describe
(
'
annotations
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
dataPath
).
replyOnce
(
200
,
mockBaseData
);
createComponent
({
graphUrl
:
dataPath
},
mount
);
});
it
(
'
toggles on link mouseover and mouseout
'
,
()
=>
{
const
currentNote
=
singleNote
[
'
dag-link103
'
];
expect
(
getNotes
().
exists
()).
toBe
(
false
);
return
wrapper
.
vm
.
$nextTick
()
.
then
(
waitForPromises
)
.
then
(()
=>
{
getGraph
().
vm
.
$emit
(
'
update-annotation
'
,
{
type
:
ADD_NOTE
,
data
:
currentNote
});
return
wrapper
.
vm
.
$nextTick
();
})
.
then
(()
=>
{
expect
(
getNotes
().
exists
()).
toBe
(
true
);
getGraph
().
vm
.
$emit
(
'
update-annotation
'
,
{
type
:
REMOVE_NOTE
,
data
:
currentNote
});
return
wrapper
.
vm
.
$nextTick
();
})
.
then
(()
=>
{
expect
(
getNotes
().
exists
()).
toBe
(
false
);
});
});
it
(
'
toggles on node and link click
'
,
()
=>
{
expect
(
getNotes
().
exists
()).
toBe
(
false
);
return
wrapper
.
vm
.
$nextTick
()
.
then
(
waitForPromises
)
.
then
(()
=>
{
getGraph
().
vm
.
$emit
(
'
update-annotation
'
,
{
type
:
REPLACE_NOTES
,
data
:
multiNote
});
return
wrapper
.
vm
.
$nextTick
();
})
.
then
(()
=>
{
expect
(
getNotes
().
exists
()).
toBe
(
true
);
getGraph
().
vm
.
$emit
(
'
update-annotation
'
,
{
type
:
REPLACE_NOTES
,
data
:
{}
});
return
wrapper
.
vm
.
$nextTick
();
})
.
then
(()
=>
{
expect
(
getNotes
().
exists
()).
toBe
(
false
);
});
});
});
});
});
spec/frontend/pipelines/components/dag/mock_data.js
View file @
773c29ee
...
@@ -388,3 +388,43 @@ export const parsedData = {
...
@@ -388,3 +388,43 @@ export const parsedData = {
},
},
],
],
};
};
export
const
singleNote
=
{
'
dag-link103
'
:
{
uid
:
'
dag-link103
'
,
source
:
{
name
:
'
canary_a
'
,
color
:
'
#b31756
'
,
},
target
:
{
name
:
'
production_a
'
,
color
:
'
#b24800
'
,
},
},
};
export
const
multiNote
=
{
...
singleNote
,
'
dag-link104
'
:
{
uid
:
'
dag-link104
'
,
source
:
{
name
:
'
build_a
'
,
color
:
'
#e17223
'
,
},
target
:
{
name
:
'
test_c
'
,
color
:
'
#006887
'
,
},
},
'
dag-link105
'
:
{
uid
:
'
dag-link105
'
,
source
:
{
name
:
'
test_c
'
,
color
:
'
#006887
'
,
},
target
:
{
name
:
'
post_test_c
'
,
color
:
'
#3547de
'
,
},
},
};
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