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
71a44d34
Commit
71a44d34
authored
Jun 07, 2017
by
Bryce Johnson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add linked pipelines to mini pipeline graph in MR Widget
parent
dbc7bbc3
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
805 additions
and
231 deletions
+805
-231
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
...vue_merge_request_widget/components/mr_widget_pipeline.js
+29
-2
app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue
...ipts/vue_shared/components/linked_pipelines_mini_list.vue
+129
-0
app/assets/stylesheets/pages/pipelines.scss
app/assets/stylesheets/pages/pipelines.scss
+89
-1
app/views/shared/icons/_arrow_mini_pipeline_graph.svg
app/views/shared/icons/_arrow_mini_pipeline_graph.svg
+1
-0
spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js
...javascripts/pipelines/graph/linked_pipelines_mock_data.js
+387
-228
spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
...ripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+50
-0
spec/javascripts/vue_shared/components/linked_pipelines_mini_list_spec.js
.../vue_shared/components/linked_pipelines_mini_list_spec.js
+120
-0
No files found.
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
View file @
71a44d34
import
PipelineStage
from
'
../../pipelines/components/stage.vue
'
;
import
ciIcon
from
'
../../vue_shared/components/ci_icon.vue
'
;
import
{
statusIconEntityMap
}
from
'
../../vue_shared/ci_status_icons
'
;
import
linkedPipelinesMiniList
from
'
../../vue_shared/components/linked_pipelines_mini_list.vue
'
;
export
default
{
name
:
'
MRWidgetPipeline
'
,
...
...
@@ -10,6 +11,7 @@ export default {
components
:
{
'
pipeline-stage
'
:
PipelineStage
,
ciIcon
,
linkedPipelinesMiniList
,
},
computed
:
{
hasCIError
()
{
...
...
@@ -26,6 +28,18 @@ export default {
status
()
{
return
this
.
mr
.
pipeline
.
details
.
status
||
{};
},
/* We typically set defaults ([]) in the store or prop declarations, but because triggered
* and triggeredBy are appended to `pipeline`, we can't set defaults in the store, and we
* need to check their length here to prevent initializing linked-pipeline-mini-lists
* unneccessarily. */
triggered
()
{
return
this
.
mr
.
pipeline
.
triggered
||
[];
},
triggeredBy
()
{
return
this
.
mr
.
pipeline
.
triggered_by
||
[];
},
},
template
:
`
<div class="mr-widget-heading">
...
...
@@ -61,12 +75,25 @@ export default {
</span>
<div class="mr-widget-pipeline-graph">
<div class="stage-cell">
<linked-pipelines-mini-list
v-if="triggeredBy.length"
:triggered-by="triggeredBy"
/>
<div
v-if="mr.pipeline.details.stages.length > 0"
v-for="stage in mr.pipeline.details.stages"
class="stage-container dropdown js-mini-pipeline-graph">
v-for="(stage, index) in mr.pipeline.details.stages"
class="stage-container dropdown js-mini-pipeline-graph"
:class="{
'has-downstream': index === mr.pipeline.details.stages.length - 1 && triggered.length
}">
<pipeline-stage :stage="stage" />
</div>
<linked-pipelines-mini-list
v-if="triggered.length"
:triggered="triggered"
/>
</div>
</div>
<span>
...
...
app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue
0 → 100644
View file @
71a44d34
<
script
>
import
arrowSvg
from
'
icons/_arrow_mini_pipeline_graph.svg
'
;
import
{
borderlessStatusIconEntityMap
}
from
'
../../vue_shared/ci_status_icons
'
;
import
ciStatus
from
'
./ci_icon.vue
'
;
import
tooltipMixin
from
'
../mixins/tooltip
'
;
export
default
{
props
:
{
triggeredBy
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
triggered
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
pipelinePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
return
{
arrowSvg
,
maxRenderedPipelines
:
3
,
};
},
mixins
:
[
tooltipMixin
,
],
components
:
{
ciStatus
,
},
computed
:
{
// Exactly one of these (triggeredBy and triggered) must be truthy. Never both. Never neither.
isUpstream
()
{
return
!!
this
.
triggeredBy
.
length
&&
!
this
.
triggered
.
length
;
},
isDownstream
()
{
return
!
this
.
triggeredBy
.
length
&&
!!
this
.
triggered
.
length
;
},
linkedPipelines
()
{
return
this
.
isUpstream
?
this
.
triggeredBy
:
this
.
triggered
;
},
totalPipelineCount
()
{
return
this
.
linkedPipelines
.
length
;
},
linkedPipelinesTrimmed
()
{
return
(
this
.
totalPipelineCount
>
this
.
maxRenderedPipelines
)
?
this
.
linkedPipelines
.
slice
(
0
,
this
.
maxRenderedPipelines
)
:
this
.
linkedPipelines
;
},
shouldRenderCounter
()
{
return
this
.
isDownstream
&&
this
.
linkedPipelines
.
length
>
this
.
maxRenderedPipelines
;
},
counterLabel
()
{
return
`+
${
this
.
linkedPipelines
.
length
-
this
.
maxRenderedPipelines
}
`
;
},
counterTooltipText
()
{
return
`
${
this
.
counterLabel
}
more downstream pipelines`
;
},
},
methods
:
{
pipelineTooltipText
(
pipeline
)
{
return
`
${
pipeline
.
project
.
name
}
-
${
pipeline
.
details
.
status
.
label
}
`
;
},
getStatusIcon
(
icon
)
{
return
borderlessStatusIconEntityMap
[
icon
];
},
triggerButtonClass
(
group
)
{
return
`ci-status-icon-
${
group
}
`
;
},
},
};
</
script
>
<
template
>
<span
v-if=
"linkedPipelines"
class=
"linked-pipeline-mini-list"
:class=
"
{
'is-upstream' : isUpstream,
'is-downstream': isDownstream
}">
<span
class=
"arrow-icon"
v-if=
"isDownstream"
v-html=
"arrowSvg"
aria-hidden=
"true"
>
</span>
<a
class=
"linked-pipeline-mini-item"
v-for=
"(pipeline, index) in linkedPipelinesTrimmed"
:key=
"pipeline.id"
:href=
"pipeline.path"
:title=
"pipelineTooltipText(pipeline)"
data-toggle=
"tooltip"
data-placement=
"top"
data-container=
"body"
ref=
"tooltip"
:class=
"triggerButtonClass(pipeline.details.status.group)"
v-html=
"getStatusIcon(pipeline.details.status.icon)"
>
</a>
<a
v-if=
"shouldRenderCounter"
class=
"linked-pipelines-counter linked-pipeline-mini-item"
:title=
"counterTooltipText"
:href=
"pipelinePath"
data-toggle=
"tooltip"
data-placement=
"top"
data-container=
"body"
ref=
"tooltip"
>
{{
counterLabel
}}
</a>
<span
class=
"arrow-icon"
v-if=
"isUpstream"
v-html=
"arrowSvg"
aria-hidden=
"true"
>
</span>
</span>
</
template
>
app/assets/stylesheets/pages/pipelines.scss
View file @
71a44d34
...
...
@@ -644,7 +644,8 @@
}
// Dropdown button in mini pipeline graph
.mini-pipeline-graph-dropdown-toggle
{
.mini-pipeline-graph-dropdown-toggle
,
.linked-pipeline-mini-item
{
border-radius
:
100px
;
background-color
:
$white-light
;
border-width
:
1px
;
...
...
@@ -998,6 +999,93 @@
}
}
.linked-pipeline-mini-list
{
display
:
inline-block
;
&
.is-downstream
{
margin-left
:
-4px
;
}
.arrow-icon
{
display
:
inline-block
;
vertical-align
:
middle
;
margin
:
-2px
5px
0
;
svg
{
fill
:
$gray-darkest
;
}
}
&
:hover
{
.linked-pipeline-mini-item
{
margin-left
:
0
;
}
}
.linked-pipeline-mini-item
{
position
:
relative
;
display
:
inline-block
;
vertical-align
:
middle
;
height
:
20px
;
width
:
20px
;
transition
:
margin
.2s
linear
;
margin
:
2px
5px
3px
-12px
;
svg
{
top
:
0
;
right
:
0
;
width
:
18px
;
height
:
18px
;
}
// override dropdown-toggle width expansion
&
:hover
{
width
:
20px
;
}
&
:first-of-type:last-of-type
{
margin-right
:
1px
;
}
&
:nth-of-type
(
1
)
{
margin-left
:
0
;
z-index
:
100
;
}
&
:nth-of-type
(
2
)
:not
(
.linked-pipelines-counter
)
{
z-index
:
99
;
}
&
:nth-of-type
(
3
)
{
z-index
:
98
;
}
&
:nth-of-type
(
4
)
{
z-index
:
97
;
}
}
.linked-pipelines-counter
{
position
:
relative
;
font-size
:
12px
;
vertical-align
:
middle
;
line-height
:
20px
;
height
:
22px
;
width
:
22px
;
padding-left
:
1px
;
margin-left
:
-15px
;
border-radius
:
2em
;
background
:
$gray-darkest
;
color
:
$white-light
;
z-index
:
96
;
text-decoration
:
none
;
&
:hover
{
width
:
22px
;
background
:
darken
(
$gray-darkest
,
10%
);
}
}
}
/**
* Cross-project pipelines (applied conditionally to pipeline graph)
*/
...
...
app/views/shared/icons/_arrow_mini_pipeline_graph.svg
0 → 100644
View file @
71a44d34
<svg
width=
"16"
height=
"8"
viewBox=
"0 0 16 8"
xmlns=
"http://www.w3.org/2000/svg"
><path
d=
"M10 3H0v2h10v3l5.549-3.7c.251-.167.25-.435 0-.6L10 0v3z"
fill-rule=
"nonzero"
/></svg>
spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js
View file @
71a44d34
This diff is collapsed.
Click to expand it.
spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
View file @
71a44d34
...
...
@@ -2,6 +2,7 @@ import Vue from 'vue';
import
{
statusIconEntityMap
}
from
'
~/vue_shared/ci_status_icons
'
;
import
pipelineComponent
from
'
~/vue_merge_request_widget/components/mr_widget_pipeline
'
;
import
mockData
from
'
../mock_data
'
;
import
mockLinkedPipelines
from
'
../../pipelines/graph/linked_pipelines_mock_data
'
;
const
createComponent
=
(
mr
)
=>
{
const
Component
=
Vue
.
extend
(
pipelineComponent
);
...
...
@@ -77,6 +78,7 @@ describe('MRWidgetPipeline', () => {
});
it
(
'
should render template elements correctly
'
,
()
=>
{
// TODO: Break this into separate specs
expect
(
el
.
classList
.
contains
(
'
mr-widget-heading
'
)).
toBeTruthy
();
expect
(
el
.
querySelectorAll
(
'
.ci-status-icon.ci-status-icon-success
'
).
length
).
toEqual
(
1
);
expect
(
el
.
querySelector
(
'
.pipeline-id
'
).
textContent
).
toContain
(
`#
${
pipeline
.
id
}
`
);
...
...
@@ -127,5 +129,53 @@ describe('MRWidgetPipeline', () => {
done
();
});
});
it
(
'
should not render upstream or downstream pipelines
'
,
()
=>
{
expect
(
el
.
querySelector
(
'
.linked-pipeline-mini-list
'
)).
toBeNull
();
});
});
describe
(
'
when upstream pipelines are passed
'
,
function
()
{
beforeEach
(
function
()
{
const
pipeline
=
Object
.
assign
({},
mockData
.
pipeline
,
{
triggered_by
:
mockLinkedPipelines
.
triggered_by
,
});
this
.
vm
=
createComponent
({
pipeline
,
pipelineDetailedStatus
:
mockData
.
pipeline
.
details
.
status
,
hasCI
:
true
,
ciStatus
:
'
success
'
,
}).
$mount
();
});
it
(
'
should render the linked pipelines mini list
'
,
function
(
done
)
{
Vue
.
nextTick
(()
=>
{
expect
(
this
.
vm
.
$el
.
querySelector
(
'
.linked-pipeline-mini-list.is-upstream
'
)).
not
.
toBeNull
();
done
();
});
});
});
describe
(
'
when downstream pipelines are passed
'
,
function
()
{
beforeEach
(
function
()
{
const
pipeline
=
Object
.
assign
({},
mockData
.
pipeline
,
{
triggered
:
mockLinkedPipelines
.
triggered
,
});
this
.
vm
=
createComponent
({
pipeline
,
pipelineDetailedStatus
:
mockData
.
pipeline
.
details
.
status
,
hasCI
:
true
,
ciStatus
:
'
success
'
,
}).
$mount
();
});
it
(
'
should render the linked pipelines mini list
'
,
function
(
done
)
{
Vue
.
nextTick
(()
=>
{
expect
(
this
.
vm
.
$el
.
querySelector
(
'
.linked-pipeline-mini-list.is-downstream
'
)).
not
.
toBeNull
();
done
();
});
});
});
});
spec/javascripts/vue_shared/components/linked_pipelines_mini_list_spec.js
0 → 100644
View file @
71a44d34
import
Vue
from
'
vue
'
;
import
LinkedPipelinesMiniList
from
'
~/vue_shared/components/linked_pipelines_mini_list.vue
'
;
import
mockData
from
'
../../pipelines/graph/linked_pipelines_mock_data
'
;
const
ListComponent
=
Vue
.
extend
(
LinkedPipelinesMiniList
);
describe
(
'
Linked pipeline mini list
'
,
()
=>
{
describe
(
'
when passed an upstream pipeline as prop
'
,
()
=>
{
beforeEach
(()
=>
{
this
.
component
=
new
ListComponent
({
propsData
:
{
triggeredBy
:
mockData
.
triggered_by
,
},
}).
$mount
();
});
it
(
'
should render one linked pipeline item
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelectorAll
(
'
.linked-pipeline-mini-item
'
).
length
).
toBe
(
1
);
});
it
(
'
should render a linked pipeline with the correct href
'
,
()
=>
{
const
linkElement
=
this
.
component
.
$el
.
querySelector
(
'
.linked-pipeline-mini-item
'
);
expect
(
linkElement
.
getAttribute
(
'
href
'
)).
toBe
(
'
/gitlab-org/gitlab-ce/pipelines/129
'
);
});
it
(
'
should render one ci status icon
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelectorAll
(
'
.linked-pipeline-mini-item svg
'
).
length
).
toBe
(
1
);
});
it
(
'
should render the correct ci status icon
'
,
()
=>
{
const
iconElement
=
this
.
component
.
$el
.
querySelector
(
'
.linked-pipeline-mini-item
'
);
expect
(
iconElement
.
classList
.
contains
(
'
ci-status-icon-running
'
)).
toBe
(
true
);
expect
(
iconElement
.
innerHTML
).
toContain
(
'
<svg
'
);
});
it
(
'
should render an arrow icon
'
,
()
=>
{
const
iconElement
=
this
.
component
.
$el
.
querySelector
(
'
.arrow-icon
'
);
expect
(
iconElement
).
not
.
toBeNull
();
expect
(
iconElement
.
innerHTML
).
toContain
(
'
<svg
'
);
});
it
(
'
should have an activated tooltip
'
,
()
=>
{
const
itemElement
=
this
.
component
.
$el
.
querySelector
(
'
.linked-pipeline-mini-item
'
);
expect
(
itemElement
.
getAttribute
(
'
data-original-title
'
)).
toBe
(
'
GitLabCE - running
'
);
});
it
(
'
should correctly set is-upstream
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
classList
.
contains
(
'
is-upstream
'
)).
toBe
(
true
);
});
it
(
'
should correctly compute shouldRenderCounter
'
,
()
=>
{
expect
(
this
.
component
.
shouldRenderCounter
).
toBe
(
false
);
});
it
(
'
should not render the pipeline counter
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelector
(
'
.linked-pipelines-counter
'
)).
toBeNull
();
});
});
describe
(
'
when passed downstream pipelines as props
'
,
()
=>
{
beforeEach
(()
=>
{
this
.
component
=
new
ListComponent
({
propsData
:
{
triggered
:
mockData
.
triggered
,
pipelinePath
:
'
my/pipeline/path
'
,
},
}).
$mount
();
});
it
(
'
should render one linked pipeline item
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelectorAll
(
'
.linked-pipeline-mini-item:not(.linked-pipelines-counter)
'
).
length
).
toBe
(
3
);
});
it
(
'
should render three ci status icons
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelectorAll
(
'
.linked-pipeline-mini-item svg
'
).
length
).
toBe
(
3
);
});
it
(
'
should render the correct ci status icon
'
,
()
=>
{
const
iconElement
=
this
.
component
.
$el
.
querySelector
(
'
.linked-pipeline-mini-item
'
);
expect
(
iconElement
.
classList
.
contains
(
'
ci-status-icon-running
'
)).
toBe
(
true
);
expect
(
iconElement
.
innerHTML
).
toContain
(
'
<svg
'
);
});
it
(
'
should render an arrow icon
'
,
()
=>
{
const
iconElement
=
this
.
component
.
$el
.
querySelector
(
'
.arrow-icon
'
);
expect
(
iconElement
).
not
.
toBeNull
();
expect
(
iconElement
.
innerHTML
).
toContain
(
'
<svg
'
);
});
it
(
'
should have prepped tooltips
'
,
()
=>
{
const
itemElement
=
this
.
component
.
$el
.
querySelectorAll
(
'
.linked-pipeline-mini-item
'
)[
2
];
expect
(
itemElement
.
getAttribute
(
'
title
'
)).
toBe
(
'
GitLabCE - running
'
);
});
it
(
'
should correctly set is-downstream
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
classList
.
contains
(
'
is-downstream
'
)).
toBe
(
true
);
});
it
(
'
should correctly compute shouldRenderCounter
'
,
()
=>
{
expect
(
this
.
component
.
shouldRenderCounter
).
toBe
(
true
);
});
it
(
'
should correctly trim linkedPipelines
'
,
()
=>
{
expect
(
this
.
component
.
triggered
.
length
).
toBe
(
6
);
expect
(
this
.
component
.
linkedPipelinesTrimmed
.
length
).
toBe
(
3
);
});
it
(
'
should render the pipeline counter
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelector
(
'
.linked-pipelines-counter
'
)).
not
.
toBeNull
();
});
it
(
'
should set the correct pipeline path
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelector
(
'
.linked-pipelines-counter
'
).
getAttribute
(
'
href
'
)).
toBe
(
'
my/pipeline/path
'
);
});
it
(
'
should render the correct counterTooltipText
'
,
()
=>
{
expect
(
this
.
component
.
$el
.
querySelector
(
'
.linked-pipelines-counter
'
).
getAttribute
(
'
data-original-title
'
)).
toBe
(
this
.
component
.
counterTooltipText
);
});
});
});
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