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
1cbc34ef
Commit
1cbc34ef
authored
Nov 27, 2020
by
Sarah Groff Hennigh-Palermo
Committed by
Andrew Fontaine
Nov 27, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add wrapper and GraphQL query
Adjusts mounting funcs, adds query, specs
parent
3ea3f960
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
516 additions
and
228 deletions
+516
-228
app/assets/javascripts/pipelines/components/graph/accessors.js
...ssets/javascripts/pipelines/components/graph/accessors.js
+10
-0
app/assets/javascripts/pipelines/components/graph/action_component.vue
...vascripts/pipelines/components/graph/action_component.vue
+1
-1
app/assets/javascripts/pipelines/components/graph/constants.js
...ssets/javascripts/pipelines/components/graph/constants.js
+3
-0
app/assets/javascripts/pipelines/components/graph/graph_component.vue
...avascripts/pipelines/components/graph/graph_component.vue
+10
-88
app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue
...pts/pipelines/components/graph/graph_component_legacy.vue
+3
-3
app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
...scripts/pipelines/components/graph/job_group_dropdown.vue
+10
-8
app/assets/javascripts/pipelines/components/graph/job_item.vue
...ssets/javascripts/pipelines/components/graph/job_item.vue
+8
-4
app/assets/javascripts/pipelines/components/graph/job_name_component.vue
...scripts/pipelines/components/graph/job_name_component.vue
+8
-4
app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
...pts/pipelines/components/graph/stage_column_component.vue
+57
-60
app/assets/javascripts/pipelines/components/graph/stage_column_component_legacy.vue
...elines/components/graph/stage_column_component_legacy.vue
+108
-0
app/assets/javascripts/pipelines/components/graph_shared/main_graph_wrapper.vue
.../pipelines/components/graph_shared/main_graph_wrapper.vue
+32
-0
app/assets/stylesheets/page_bundles/pipeline.scss
app/assets/stylesheets/page_bundles/pipeline.scss
+45
-0
spec/frontend/pipelines/graph/graph_component_legacy_spec.js
spec/frontend/pipelines/graph/graph_component_legacy_spec.js
+2
-2
spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js
...end/pipelines/graph/stage_column_component_legacy_spec.js
+135
-0
spec/frontend/pipelines/graph/stage_column_component_spec.js
spec/frontend/pipelines/graph/stage_column_component_spec.js
+84
-58
No files found.
app/assets/javascripts/pipelines/components/graph/accessors.js
0 → 100644
View file @
1cbc34ef
import
{
REST
,
GRAPHQL
}
from
'
./constants
'
;
export
const
accessors
=
{
[
REST
]:
{
groupId
:
'
id
'
,
},
[
GRAPHQL
]:
{
groupId
:
'
name
'
,
},
};
app/assets/javascripts/pipelines/components/graph/action_component.vue
View file @
1cbc34ef
...
...
@@ -87,7 +87,7 @@ export default {
:title="tooltipText"
:class="cssClass"
:disabled="isDisabled"
class="js-ci-action ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center"
class="js-ci-action
gl-ci-action-icon-container
ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center"
@click.stop="onClickAction"
>
<gl-loading-icon
v-if=
"isLoading"
class=
"js-action-icon-loading"
/>
...
...
app/assets/javascripts/pipelines/components/graph/constants.js
View file @
1cbc34ef
export
const
DOWNSTREAM
=
'
downstream
'
;
export
const
MAIN
=
'
main
'
;
export
const
UPSTREAM
=
'
upstream
'
;
export
const
REST
=
'
rest
'
;
export
const
GRAPHQL
=
'
graphql
'
;
app/assets/javascripts/pipelines/components/graph/graph_component.vue
View file @
1cbc34ef
<
script
>
import
{
escape
,
capitalize
}
from
'
lodash
'
;
import
StageColumnComponent
from
'
./stage_column_component.vue
'
;
import
GraphBundleMixin
from
'
../../mixins/graph_pipeline_bundle_mixin
'
;
import
{
MAIN
}
from
'
./constants
'
;
export
default
{
...
...
@@ -9,7 +7,6 @@ export default {
components
:
{
StageColumnComponent
,
},
mixins
:
[
GraphBundleMixin
],
props
:
{
isLinkedPipeline
:
{
type
:
Boolean
,
...
...
@@ -31,96 +28,21 @@ export default {
return
this
.
pipeline
.
stages
;
},
},
methods
:
{
capitalizeStageName
(
name
)
{
const
escapedName
=
escape
(
name
);
return
capitalize
(
escapedName
);
},
isFirstColumn
(
index
)
{
return
index
===
0
;
},
stageConnectorClass
(
index
,
stage
)
{
let
className
;
// If it's the first stage column and only has one job
if
(
this
.
isFirstColumn
(
index
)
&&
stage
.
groups
.
length
===
1
)
{
className
=
'
no-margin
'
;
}
else
if
(
index
>
0
)
{
// If it is not the first column
className
=
'
left-margin
'
;
}
return
className
;
},
refreshPipelineGraph
()
{
this
.
$emit
(
'
refreshPipelineGraph
'
);
},
/**
* CSS class is applied:
* - if pipeline graph contains only one stage column component
*
* @param {number} index
* @returns {boolean}
*/
shouldAddRightMargin
(
index
)
{
return
!
(
index
===
this
.
graph
.
length
-
1
);
},
handleClickedDownstream
(
pipeline
,
clickedIndex
,
downstreamNode
)
{
/**
* Calculates the margin top of the clicked downstream pipeline by
* subtracting the clicked downstream pipelines offsetTop by it's parent's
* offsetTop and then subtracting 15
*/
this
.
downstreamMarginTop
=
this
.
calculateMarginTop
(
downstreamNode
,
15
);
/**
* If the expanded trigger is defined and the id is different than the
* pipeline we clicked, then it means we clicked on a sibling downstream link
* and we want to reset the pipeline store. Triggering the reset without
* this condition would mean not allowing downstreams of downstreams to expand
*/
if
(
this
.
expandedDownstream
?.
id
!==
pipeline
.
id
)
{
this
.
$emit
(
'
onResetDownstream
'
,
this
.
pipeline
,
pipeline
);
}
this
.
$emit
(
'
onClickDownstreamPipeline
'
,
pipeline
);
},
calculateMarginTop
(
downstreamNode
,
pixelDiff
)
{
return
`
${
downstreamNode
.
offsetTop
-
downstreamNode
.
offsetParent
.
offsetTop
-
pixelDiff
}
px`
;
},
hasOnlyOneJob
(
stage
)
{
return
stage
.
groups
.
length
===
1
;
},
hasUpstreamColumn
(
index
)
{
return
index
===
0
&&
this
.
hasUpstream
;
},
},
};
</
script
>
<
template
>
<div
class=
"
build-content middle-block
js-pipeline-graph"
>
<div
class=
"js-pipeline-graph"
>
<div
class=
"
pipeline-visualization pipeline-graph
"
:class=
"
{ '
pipeline-tab-content
': !isLinkedPipeline }"
class=
"
gl-pipeline-min-h gl-display-flex gl-position-relative gl-overflow-auto gl-bg-gray-10 gl-white-space-nowrap
"
:class=
"
{ '
gl-py-5
': !isLinkedPipeline }"
>
<div>
<ul
class=
"stage-column-list align-top"
>
<stage-column-component
v-for=
"(stage, index) in graph"
:key=
"stage.name"
:class=
"
{
'has-only-one-job': hasOnlyOneJob(stage),
'gl-mr-26': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
:action="stage.status.action"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
</div>
<stage-column-component
v-for=
"stage in graph"
:key=
"stage.name"
:title=
"stage.name"
:groups=
"stage.groups"
:action=
"stage.status.action"
/>
</div>
</div>
</
template
>
app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue
View file @
1cbc34ef
<
script
>
import
{
escape
,
capitalize
}
from
'
lodash
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
StageColumnComponent
from
'
./stage_column_component
.vue
'
;
import
StageColumnComponent
Legacy
from
'
./stage_column_component_legacy
.vue
'
;
import
GraphWidthMixin
from
'
../../mixins/graph_width_mixin
'
;
import
LinkedPipelinesColumn
from
'
./linked_pipelines_column.vue
'
;
import
GraphBundleMixin
from
'
../../mixins/graph_pipeline_bundle_mixin
'
;
...
...
@@ -10,7 +10,7 @@ import { UPSTREAM, DOWNSTREAM, MAIN } from './constants';
export
default
{
name
:
'
PipelineGraphLegacy
'
,
components
:
{
StageColumnComponent
,
StageColumnComponent
Legacy
,
GlLoadingIcon
,
LinkedPipelinesColumn
,
},
...
...
@@ -220,7 +220,7 @@ export default {
}"
class="stage-column-list align-top"
>
<stage-column-component
<stage-column-component
-legacy
v-for=
"(stage, index) in graph"
:key=
"stage.name"
:class=
"
{
...
...
app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
View file @
1cbc34ef
...
...
@@ -44,17 +44,19 @@ export default {
type="button"
data-toggle="dropdown"
data-display="static"
class="dropdown-menu-toggle build-content"
class="dropdown-menu-toggle build-content
gl-build-content
"
>
<ci-icon
:status=
"group.status"
/>
<div
class=
"gl-display-flex gl-align-items-center gl-justify-content-space-between"
>
<span
class=
"gl-display-flex gl-align-items-center"
>
<ci-icon
:status=
"group.status"
:size=
"24"
/>
<span
class=
"gl-text-truncate mw-70p gl-pl-2 gl-display-inline-block gl-vertical-align-bottom"
>
{{
group
.
name
}}
</span>
<span
class=
"gl-text-truncate mw-70p gl-pl-3 gl-display-inline-block"
>
{{
group
.
name
}}
</span>
</span>
<span
class=
"dropdown-counter-badge"
>
{{
group
.
size
}}
</span>
<span
class=
"gl-font-weight-100 gl-font-size-lg gl-pr-2"
>
{{
group
.
size
}}
</span>
</div>
</button>
<ul
class=
"dropdown-menu big-pipeline-graph-dropdown-menu js-grouped-pipeline-dropdown"
>
...
...
app/assets/javascripts/pipelines/components/graph/job_item.vue
View file @
1cbc34ef
...
...
@@ -129,19 +129,23 @@ export default {
};
</
script
>
<
template
>
<div
class=
"ci-job-component"
data-qa-selector=
"job_item_container"
>
<div
class=
"ci-job-component gl-display-flex gl-align-items-center gl-justify-content-space-between"
data-qa-selector=
"job_item_container"
>
<gl-link
v-if=
"status.has_details"
v-gl-tooltip=
"
{ boundary, placement: 'bottom', customClass: 'gl-pointer-events-none' }"
:href="status.details_path"
:title="tooltipText"
:class="jobClasses"
class="js-pipeline-graph-job-link qa-job-link menu-item"
class="js-pipeline-graph-job-link qa-job-link menu-item gl-text-gray-900 gl-active-text-decoration-none
gl-focus-text-decoration-none"
data-testid="job-with-link"
@click.stop="hideTooltips"
@mouseout="hideTooltips"
>
<job-name-component
:name=
"job.name"
:status=
"job.status"
/>
<job-name-component
:name=
"job.name"
:status=
"job.status"
:icon-size=
"24"
/>
</gl-link>
<div
...
...
@@ -153,7 +157,7 @@ export default {
data-testid="job-without-link"
@mouseout="hideTooltips"
>
<job-name-component
:name=
"job.name"
:status=
"job.status"
/>
<job-name-component
:name=
"job.name"
:status=
"job.status"
:icon-size=
"24"
/>
</div>
<action-component
...
...
app/assets/javascripts/pipelines/components/graph/job_name_component.vue
View file @
1cbc34ef
...
...
@@ -16,18 +16,22 @@ export default {
type
:
String
,
required
:
true
,
},
status
:
{
type
:
Object
,
required
:
true
,
},
iconSize
:
{
type
:
Number
,
required
:
false
,
default
:
16
,
},
},
};
</
script
>
<
template
>
<span
class=
"ci-job-name-component mw-100"
>
<ci-icon
:status=
"status"
/>
<span
class=
"gl-text-truncate mw-70p gl-pl-
2 gl-display-inline-block gl-vertical-align-bottom
"
>
<span
class=
"ci-job-name-component mw-100
gl-display-flex gl-align-items-center
"
>
<ci-icon
:s
ize=
"iconSize"
:s
tatus=
"status"
/>
<span
class=
"gl-text-truncate mw-70p gl-pl-
3 gl-display-inline-block
"
>
{{
name
}}
</span>
</span>
...
...
app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
View file @
1cbc34ef
<
script
>
import
{
isEmpty
,
escape
}
from
'
lodash
'
;
import
stageColumnMixin
from
'
../../mixins/stage_column_mixin
'
;
import
{
capitalize
,
escape
,
isEmpty
}
from
'
lodash
'
;
import
MainGraphWrapper
from
'
../graph_shared/main_graph_wrapper.vue
'
;
import
JobItem
from
'
./job_item.vue
'
;
import
JobGroupDropdown
from
'
./job_group_dropdown.vue
'
;
import
ActionComponent
from
'
./action_component.vue
'
;
import
{
GRAPHQL
}
from
'
./constants
'
;
import
{
accessors
}
from
'
./accessors
'
;
export
default
{
components
:
{
JobItem
,
JobGroupDropdown
,
ActionComponent
,
JobGroupDropdown
,
JobItem
,
MainGraphWrapper
,
},
mixins
:
[
stageColumnMixin
],
props
:
{
title
:
{
type
:
String
,
...
...
@@ -21,16 +23,6 @@ export default {
type
:
Array
,
required
:
true
,
},
isFirstColumn
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
stageConnectorClass
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
action
:
{
type
:
Object
,
required
:
false
,
...
...
@@ -47,62 +39,67 @@ export default {
default
:
()
=>
({}),
},
},
accessors
,
titleClasses
:
[
'
gl-font-weight-bold
'
,
'
gl-pipeline-job-width
'
,
'
gl-text-truncate
'
,
'
gl-line-height-36
'
,
'
gl-pl-3
'
,
],
computed
:
{
formattedTitle
()
{
return
capitalize
(
escape
(
this
.
title
));
},
hasAction
()
{
return
!
isEmpty
(
this
.
action
);
},
},
methods
:
{
getAccessor
(
property
)
{
return
accessors
[
GRAPHQL
][
property
];
},
groupId
(
group
)
{
return
`ci-badge-
${
escape
(
group
.
name
)}
`
;
},
pipelineActionRequestComplete
()
{
this
.
$emit
(
'
refreshPipelineGraph
'
);
},
},
};
</
script
>
<
template
>
<li
:class=
"stageConnectorClass"
class=
"stage-column"
>
<div
class=
"stage-name position-relative"
data-testid=
"stage-column-title"
>
{{
title
}}
<action-component
v-if=
"hasAction"
:action-icon=
"action.icon"
:tooltip-text=
"action.title"
:link=
"action.path"
class=
"js-stage-action stage-action rounded"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
</div>
<div
class=
"builds-container"
>
<ul>
<li
v-for=
"(group, index) in groups"
:id=
"groupId(group)"
:key=
"group.id"
:class=
"buildConnnectorClass(index)"
class=
"build"
>
<div
class=
"curve"
></div>
<job-item
v-if=
"group.size === 1"
:job=
"group.jobs[0]"
:job-hovered=
"jobHovered"
:pipeline-expanded=
"pipelineExpanded"
css-class-job-name=
"build-content"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
<job-group-dropdown
v-if=
"group.size > 1"
:group=
"group"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
</li>
</ul>
</div>
</li>
<main-graph-wrapper>
<template
#stages
>
<div
data-testid=
"stage-column-title"
class=
"gl-display-flex gl-justify-content-space-between gl-relative"
:class=
"$options.titleClasses"
>
<div>
{{
formattedTitle
}}
</div>
<action-component
v-if=
"hasAction"
:action-icon=
"action.icon"
:tooltip-text=
"action.title"
:link=
"action.path"
class=
"js-stage-action stage-action rounded"
/>
</div>
</
template
>
<
template
#jobs
>
<div
v-for=
"group in groups"
:id=
"groupId(group)"
:key=
"group[getAccessor('groupId')]"
data-testid=
"stage-column-group"
class=
"gl-relative gl-mb-3 gl-white-space-normal gl-pipeline-job-width"
>
<job-item
v-if=
"group.size === 1"
:job=
"group.jobs[0]"
:job-hovered=
"jobHovered"
:pipeline-expanded=
"pipelineExpanded"
css-class-job-name=
"gl-build-content"
/>
<job-group-dropdown
v-else
:group=
"group"
/>
</div>
</
template
>
</main-graph-wrapper>
</template>
app/assets/javascripts/pipelines/components/graph/stage_column_component_legacy.vue
0 → 100644
View file @
1cbc34ef
<
script
>
import
{
isEmpty
,
escape
}
from
'
lodash
'
;
import
stageColumnMixin
from
'
../../mixins/stage_column_mixin
'
;
import
JobItem
from
'
./job_item.vue
'
;
import
JobGroupDropdown
from
'
./job_group_dropdown.vue
'
;
import
ActionComponent
from
'
./action_component.vue
'
;
export
default
{
components
:
{
JobItem
,
JobGroupDropdown
,
ActionComponent
,
},
mixins
:
[
stageColumnMixin
],
props
:
{
title
:
{
type
:
String
,
required
:
true
,
},
groups
:
{
type
:
Array
,
required
:
true
,
},
isFirstColumn
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
stageConnectorClass
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
action
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
jobHovered
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
pipelineExpanded
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
},
computed
:
{
hasAction
()
{
return
!
isEmpty
(
this
.
action
);
},
},
methods
:
{
groupId
(
group
)
{
return
`ci-badge-
${
escape
(
group
.
name
)}
`
;
},
pipelineActionRequestComplete
()
{
this
.
$emit
(
'
refreshPipelineGraph
'
);
},
},
};
</
script
>
<
template
>
<li
:class=
"stageConnectorClass"
class=
"stage-column"
>
<div
class=
"stage-name position-relative"
data-testid=
"stage-column-title"
>
{{
title
}}
<action-component
v-if=
"hasAction"
:action-icon=
"action.icon"
:tooltip-text=
"action.title"
:link=
"action.path"
class=
"js-stage-action stage-action rounded"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
</div>
<div
class=
"builds-container"
>
<ul>
<li
v-for=
"(group, index) in groups"
:id=
"groupId(group)"
:key=
"group.id"
:class=
"buildConnnectorClass(index)"
class=
"build"
>
<div
class=
"curve"
></div>
<job-item
v-if=
"group.size === 1"
:job=
"group.jobs[0]"
:job-hovered=
"jobHovered"
:pipeline-expanded=
"pipelineExpanded"
css-class-job-name=
"build-content"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
<job-group-dropdown
v-if=
"group.size > 1"
:group=
"group"
@
pipelineActionRequestComplete=
"pipelineActionRequestComplete"
/>
</li>
</ul>
</div>
</li>
</
template
>
app/assets/javascripts/pipelines/components/graph_shared/main_graph_wrapper.vue
0 → 100644
View file @
1cbc34ef
<
script
>
export
default
{
props
:
{
stageClasses
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
jobClasses
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
};
</
script
>
<
template
>
<div>
<div
class=
"gl-display-flex gl-align-items-center gl-w-full gl-px-8 gl-py-4 gl-mb-5"
:class=
"stageClasses"
>
<slot
name=
"stages"
>
</slot>
</div>
<div
class=
"gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full gl-px-8"
:class=
"jobClasses"
>
<slot
name=
"jobs"
>
</slot>
</div>
</div>
</
template
>
app/assets/stylesheets/page_bundles/pipeline.scss
View file @
1cbc34ef
...
...
@@ -129,6 +129,51 @@
overflow
:
auto
;
}
// Move to Gitlab UI
.gl-font-weight-100
{
font-weight
:
100
;
}
.gl-active-text-decoration-none
:active
,
.gl-focus-text-decoration-none
:focus
{
text-decoration
:
none
;
}
// These are single-value classes to use with utility-class style CSS
// but to still access this variable. Do not add other styles.
.gl-pipeline-min-h
{
min-height
:
$dropdown-max-height-lg
;
}
.gl-pipeline-job-width
{
width
:
186px
;
}
.gl-pipeline-title-width
{
width
:
176px
;
}
.gl-build-content
{
@include
build-content
();
}
.gl-ci-action-icon-container
{
position
:
absolute
;
right
:
5px
;
top
:
50%
!
important
;
transform
:
translateY
(
-50%
);
// Action Icons in big pipeline-graph nodes
&
.ci-action-icon-wrapper
{
height
:
30px
;
width
:
30px
;
border-radius
:
100%
;
display
:
block
;
padding
:
0
;
line-height
:
0
;
}
}
// Pipeline graph, used at
// app/assets/javascripts/pipelines/components/graph/graph_component.vue
.pipeline-graph
{
...
...
spec/frontend/pipelines/graph/graph_component_legacy_spec.js
View file @
1cbc34ef
...
...
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils';
import
{
setHTMLFixture
}
from
'
helpers/fixtures
'
;
import
PipelineStore
from
'
~/pipelines/stores/pipeline_store
'
;
import
GraphComponentLegacy
from
'
~/pipelines/components/graph/graph_component_legacy.vue
'
;
import
StageColumnComponent
from
'
~/pipelines/components/graph/stage_column_component
.vue
'
;
import
StageColumnComponent
Legacy
from
'
~/pipelines/components/graph/stage_column_component_legacy
.vue
'
;
import
linkedPipelinesColumn
from
'
~/pipelines/components/graph/linked_pipelines_column.vue
'
;
import
graphJSON
from
'
./mock_data_legacy
'
;
import
linkedPipelineJSON
from
'
./linked_pipelines_mock_data
'
;
...
...
@@ -16,7 +16,7 @@ describe('graph component', () => {
const
findExpandPipelineBtn
=
()
=>
wrapper
.
find
(
'
[data-testid="expandPipelineButton"]
'
);
const
findAllExpandPipelineBtns
=
()
=>
wrapper
.
findAll
(
'
[data-testid="expandPipelineButton"]
'
);
const
findStageColumns
=
()
=>
wrapper
.
findAll
(
StageColumnComponent
);
const
findStageColumns
=
()
=>
wrapper
.
findAll
(
StageColumnComponent
Legacy
);
const
findStageColumnAt
=
i
=>
findStageColumns
().
at
(
i
);
beforeEach
(()
=>
{
...
...
spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js
0 → 100644
View file @
1cbc34ef
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
StageColumnComponentLegacy
from
'
~/pipelines/components/graph/stage_column_component_legacy.vue
'
;
describe
(
'
stage column component
'
,
()
=>
{
const
mockJob
=
{
id
:
4250
,
name
:
'
test
'
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
details_path
:
'
/root/ci-mock/builds/4250
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4250/retry
'
,
method
:
'
post
'
,
},
},
};
let
wrapper
;
beforeEach
(()
=>
{
const
mockGroups
=
[];
for
(
let
i
=
0
;
i
<
3
;
i
+=
1
)
{
const
mockedJob
=
{
...
mockJob
};
mockedJob
.
id
+=
i
;
mockGroups
.
push
(
mockedJob
);
}
wrapper
=
shallowMount
(
StageColumnComponentLegacy
,
{
propsData
:
{
title
:
'
foo
'
,
groups
:
mockGroups
,
hasTriggeredBy
:
false
,
},
});
});
it
(
'
should render provided title
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.stage-name
'
)
.
text
()
.
trim
(),
).
toBe
(
'
foo
'
);
});
it
(
'
should render the provided groups
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
.builds-container > ul > li
'
).
length
).
toBe
(
wrapper
.
props
(
'
groups
'
).
length
,
);
});
describe
(
'
jobId
'
,
()
=>
{
it
(
'
escapes job name
'
,
()
=>
{
wrapper
=
shallowMount
(
StageColumnComponentLegacy
,
{
propsData
:
{
groups
:
[
{
id
:
4259
,
name
:
'
<img src=x onerror=alert(document.domain)>
'
,
status
:
{
icon
:
'
status_success
'
,
label
:
'
success
'
,
tooltip
:
'
<img src=x onerror=alert(document.domain)>
'
,
},
},
],
title
:
'
test
'
,
hasTriggeredBy
:
false
,
},
});
expect
(
wrapper
.
find
(
'
.builds-container li
'
).
attributes
(
'
id
'
)).
toBe
(
'
ci-badge-<img src=x onerror=alert(document.domain)>
'
,
);
});
});
describe
(
'
with action
'
,
()
=>
{
it
(
'
renders action button
'
,
()
=>
{
wrapper
=
shallowMount
(
StageColumnComponentLegacy
,
{
propsData
:
{
groups
:
[
{
id
:
4259
,
name
:
'
<img src=x onerror=alert(document.domain)>
'
,
status
:
{
icon
:
'
status_success
'
,
label
:
'
success
'
,
tooltip
:
'
<img src=x onerror=alert(document.domain)>
'
,
},
},
],
title
:
'
test
'
,
hasTriggeredBy
:
false
,
action
:
{
icon
:
'
play
'
,
title
:
'
Play all
'
,
path
:
'
action
'
,
},
},
});
expect
(
wrapper
.
find
(
'
.js-stage-action
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
without action
'
,
()
=>
{
it
(
'
does not render action button
'
,
()
=>
{
wrapper
=
shallowMount
(
StageColumnComponentLegacy
,
{
propsData
:
{
groups
:
[
{
id
:
4259
,
name
:
'
<img src=x onerror=alert(document.domain)>
'
,
status
:
{
icon
:
'
status_success
'
,
label
:
'
success
'
,
tooltip
:
'
<img src=x onerror=alert(document.domain)>
'
,
},
},
],
title
:
'
test
'
,
hasTriggeredBy
:
false
,
},
});
expect
(
wrapper
.
find
(
'
.js-stage-action
'
).
exists
()).
toBe
(
false
);
});
});
});
spec/frontend/pipelines/graph/stage_column_component_spec.js
View file @
1cbc34ef
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
mount
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
ActionComponent
from
'
~/pipelines/components/graph/action_component.vue
'
;
import
StageColumnComponent
from
'
~/pipelines/components/graph/stage_column_component.vue
'
;
import
stageColumnComponent
from
'
~/pipelines/components/graph/stage_column_component.vue
'
;
describe
(
'
stage column component
'
,
()
=>
{
const
mockJob
=
{
id
:
4250
,
name
:
'
test
'
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
details_path
:
'
/root/ci-mock/builds/4250
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4250/retry
'
,
method
:
'
post
'
,
},
const
mockJob
=
{
id
:
4250
,
name
:
'
test
'
,
status
:
{
icon
:
'
status_success
'
,
text
:
'
passed
'
,
label
:
'
passed
'
,
group
:
'
success
'
,
details_path
:
'
/root/ci-mock/builds/4250
'
,
action
:
{
icon
:
'
retry
'
,
title
:
'
Retry
'
,
path
:
'
/root/ci-mock/builds/4250/retry
'
,
method
:
'
post
'
,
},
};
},
};
const
mockGroups
=
Array
(
4
)
.
fill
(
0
)
.
map
((
item
,
idx
)
=>
{
return
{
...
mockJob
,
id
:
idx
,
name
:
`fish-
${
idx
}
`
};
});
const
defaultProps
=
{
title
:
'
Fish
'
,
groups
:
mockGroups
,
};
describe
(
'
stage column component
'
,
()
=>
{
let
wrapper
;
beforeEach
(()
=>
{
const
mockGroups
=
[];
for
(
let
i
=
0
;
i
<
3
;
i
+=
1
)
{
const
mockedJob
=
{
...
mockJob
};
mockedJob
.
id
+=
i
;
mockGroups
.
push
(
mockedJob
);
}
const
findStageColumnTitle
=
()
=>
wrapper
.
find
(
'
[data-testid="stage-column-title"]
'
);
const
findStageColumnGroup
=
()
=>
wrapper
.
find
(
'
[data-testid="stage-column-group"]
'
);
const
findAllStageColumnGroups
=
()
=>
wrapper
.
findAll
(
'
[data-testid="stage-column-group"]
'
);
const
findActionComponent
=
()
=>
wrapper
.
find
(
ActionComponent
);
wrapper
=
shallowMount
(
stageColumnComponent
,
{
const
createComponent
=
({
method
=
shallowMount
,
props
=
{}
}
=
{})
=>
{
wrapper
=
method
(
StageColumnComponent
,
{
propsData
:
{
title
:
'
foo
'
,
groups
:
mockGroups
,
hasTriggeredBy
:
false
,
...
defaultProps
,
...
props
,
},
});
}
)
;
};
it
(
'
should render provided title
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.stage-name
'
)
.
text
()
.
trim
(),
).
toBe
(
'
foo
'
);
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
should render the provided groups
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
.builds-container > ul > li
'
).
length
).
toBe
(
wrapper
.
props
(
'
groups
'
).
length
,
);
describe
(
'
when mounted
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
method
:
mount
});
});
it
(
'
should render provided title
'
,
()
=>
{
expect
(
findStageColumnTitle
().
text
()).
toBe
(
defaultProps
.
title
);
});
it
(
'
should render the provided groups
'
,
()
=>
{
expect
(
findAllStageColumnGroups
().
length
).
toBe
(
mockGroups
.
length
);
});
});
describe
(
'
jobId
'
,
()
=>
{
it
(
'
escapes job name
'
,
()
=>
{
wrapper
=
shallowMount
(
stageColumnComponent
,
{
propsData
:
{
describe
(
'
job
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
method
:
mount
,
props
:
{
groups
:
[
{
id
:
4259
,
...
...
@@ -70,21 +83,29 @@ describe('stage column component', () => {
},
},
],
title
:
'
test
'
,
hasTriggeredBy
:
false
,
title
:
'
test <img src=x onerror=alert(document.domain)>
'
,
},
});
});
it
(
'
capitalizes and escapes name
'
,
()
=>
{
expect
(
findStageColumnTitle
().
text
()).
toBe
(
'
Test <img src=x onerror=alert(document.domain)>
'
,
);
});
expect
(
wrapper
.
find
(
'
.builds-container li
'
).
attributes
(
'
id
'
)).
toBe
(
it
(
'
escapes id
'
,
()
=>
{
expect
(
findStageColumnGroup
().
attributes
(
'
id
'
)).
toBe
(
'
ci-badge-<img src=x onerror=alert(document.domain)>
'
,
);
});
});
describe
(
'
with action
'
,
()
=>
{
it
(
'
renders action button
'
,
()
=>
{
wrapper
=
shallowMount
(
stageColumnComponent
,
{
propsData
:
{
beforeEach
(()
=>
{
createComponent
({
method
:
mount
,
props
:
{
groups
:
[
{
id
:
4259
,
...
...
@@ -105,15 +126,18 @@ describe('stage column component', () => {
},
},
});
});
expect
(
wrapper
.
find
(
'
.js-stage-action
'
).
exists
()).
toBe
(
true
);
it
(
'
renders action button
'
,
()
=>
{
expect
(
findActionComponent
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
without action
'
,
()
=>
{
it
(
'
does not render action button
'
,
()
=>
{
wrapper
=
shallowMount
(
stageColumnComponent
,
{
propsData
:
{
beforeEach
(()
=>
{
createComponent
({
method
:
mount
,
props
:
{
groups
:
[
{
id
:
4259
,
...
...
@@ -129,8 +153,10 @@ describe('stage column component', () => {
hasTriggeredBy
:
false
,
},
});
});
expect
(
wrapper
.
find
(
'
.js-stage-action
'
).
exists
()).
toBe
(
false
);
it
(
'
does not render action button
'
,
()
=>
{
expect
(
findActionComponent
().
exists
()).
toBe
(
false
);
});
});
});
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