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
f72a1bf0
Commit
f72a1bf0
authored
Oct 03, 2018
by
Filipa Lacerda
Committed by
Phil Hughes
Oct 03, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moves stages dropdown into the new vue app
parent
c4d9f402
Changes
19
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1727 additions
and
259 deletions
+1727
-259
app/assets/javascripts/job.js
app/assets/javascripts/job.js
+0
-23
app/assets/javascripts/jobs/components/jobs_container.vue
app/assets/javascripts/jobs/components/jobs_container.vue
+22
-8
app/assets/javascripts/jobs/components/sidebar.vue
app/assets/javascripts/jobs/components/sidebar.vue
+297
-0
app/assets/javascripts/jobs/components/stages_dropdown.vue
app/assets/javascripts/jobs/components/stages_dropdown.vue
+58
-58
app/assets/javascripts/jobs/job_details_bundle.js
app/assets/javascripts/jobs/job_details_bundle.js
+18
-8
app/assets/javascripts/jobs/store/actions.js
app/assets/javascripts/jobs/store/actions.js
+20
-8
app/assets/stylesheets/pages/builds.scss
app/assets/stylesheets/pages/builds.scss
+1
-22
app/views/projects/jobs/_sidebar.html.haml
app/views/projects/jobs/_sidebar.html.haml
+0
-38
app/views/projects/jobs/show.html.haml
app/views/projects/jobs/show.html.haml
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+3
-3
spec/features/projects/environments/environment_spec.rb
spec/features/projects/environments/environment_spec.rb
+8
-4
spec/features/projects/jobs/user_browses_job_spec.rb
spec/features/projects/jobs/user_browses_job_spec.rb
+4
-2
spec/features/projects/jobs_spec.rb
spec/features/projects/jobs_spec.rb
+5
-3
spec/javascripts/job_spec.js
spec/javascripts/job_spec.js
+0
-19
spec/javascripts/jobs/components/jobs_container_spec.js
spec/javascripts/jobs/components/jobs_container_spec.js
+14
-9
spec/javascripts/jobs/components/sidebar_spec.js
spec/javascripts/jobs/components/sidebar_spec.js
+196
-0
spec/javascripts/jobs/components/stages_dropdown_spec.js
spec/javascripts/jobs/components/stages_dropdown_spec.js
+19
-13
spec/javascripts/jobs/mock_data.js
spec/javascripts/jobs/mock_data.js
+1040
-7
spec/javascripts/jobs/store/actions_spec.js
spec/javascripts/jobs/store/actions_spec.js
+21
-33
No files found.
app/assets/javascripts/job.js
View file @
f72a1bf0
...
...
@@ -24,7 +24,6 @@ export default class Job extends LogOutputBehaviours {
this
.
$document
=
$
(
document
);
this
.
$window
=
$
(
window
);
this
.
logBytes
=
0
;
this
.
updateDropdown
=
this
.
updateDropdown
.
bind
(
this
);
this
.
$buildTrace
=
$
(
'
#build-trace
'
);
this
.
$buildRefreshAnimation
=
$
(
'
.js-build-refresh
'
);
...
...
@@ -35,18 +34,12 @@ export default class Job extends LogOutputBehaviours {
clearTimeout
(
this
.
timeout
);
this
.
initSidebar
();
this
.
populateJobs
(
this
.
buildStage
);
this
.
updateStageDropdownText
(
this
.
buildStage
);
this
.
sidebarOnResize
();
this
.
$document
.
off
(
'
click
'
,
'
.js-sidebar-build-toggle
'
)
.
on
(
'
click
'
,
'
.js-sidebar-build-toggle
'
,
this
.
sidebarOnClick
.
bind
(
this
));
this
.
$document
.
off
(
'
click
'
,
'
.stage-item
'
)
.
on
(
'
click
'
,
'
.stage-item
'
,
this
.
updateDropdown
);
this
.
scrollThrottled
=
_
.
throttle
(
this
.
toggleScroll
.
bind
(
this
),
100
);
this
.
$window
...
...
@@ -194,20 +187,4 @@ export default class Job extends LogOutputBehaviours {
if
(
this
.
shouldHideSidebarForViewport
())
this
.
toggleSidebar
();
}
// eslint-disable-next-line class-methods-use-this
populateJobs
(
stage
)
{
$
(
'
.build-job
'
).
hide
();
$
(
`.build-job[data-stage="
${
stage
}
"]`
).
show
();
}
// eslint-disable-next-line class-methods-use-this
updateStageDropdownText
(
stage
)
{
$
(
'
.stage-selection
'
).
text
(
stage
);
}
updateDropdown
(
e
)
{
e
.
preventDefault
();
const
stage
=
e
.
currentTarget
.
text
;
this
.
updateStageDropdownText
(
stage
);
this
.
populateJobs
(
stage
);
}
}
app/assets/javascripts/jobs/components/jobs_container.vue
View file @
f72a1bf0
<
script
>
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
...
...
@@ -16,26 +17,39 @@
type
:
Array
,
required
:
true
,
},
jobId
:
{
type
:
Number
,
required
:
true
,
},
},
methods
:
{
isJobActive
(
currentJobId
)
{
return
this
.
jobId
===
currentJobId
;
},
tooltipText
(
job
)
{
return
`
${
_
.
escape
(
job
.
name
)}
-
${
job
.
status
.
tooltip
}
`
;
},
},
};
</
script
>
<
template
>
<div
class=
"builds-container"
>
<div
class=
"
js-jobs-container
builds-container"
>
<div
v-for=
"job in jobs"
:key=
"job.id"
class=
"build-job"
:class=
"
{ retried: job.retried, active: isJobActive(job.id) }"
>
<a
v-for=
"job in jobs"
:key=
"job.id"
v-tooltip
:href=
"job.path"
:title=
"
job.tooltip
"
:class=
"
{ active: job.active, retried: job.retried }
"
:href=
"job.
status.details_
path"
:title=
"
tooltipText(job)
"
data-container=
"body
"
>
<icon
v-if=
"
job.active
"
v-if=
"
isJobActive(job.id)
"
name=
"arrow-right"
class=
"js-arrow-right"
class=
"js-arrow-right
icon-arrow-right
"
/>
<ci-icon
:status=
"job.status"
/>
...
...
app/assets/javascripts/jobs/components/sidebar
_details_block
.vue
→
app/assets/javascripts/jobs/components/sidebar.vue
View file @
f72a1bf0
This diff is collapsed.
Click to expand it.
app/assets/javascripts/jobs/components/stages_dropdown.vue
View file @
f72a1bf0
<
script
>
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
{
sprintf
,
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
export
default
{
components
:
{
...
...
@@ -10,30 +10,14 @@
Icon
,
},
props
:
{
pipelineId
:
{
type
:
Number
,
required
:
true
,
},
pipelinePath
:
{
type
:
String
,
required
:
true
,
},
pipelineRef
:
{
type
:
String
,
required
:
true
,
},
pipelineRefPath
:
{
type
:
String
,
pipeline
:
{
type
:
Object
,
required
:
true
,
},
stages
:
{
type
:
Array
,
required
:
true
,
},
pipelineStatus
:
{
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
...
...
@@ -41,57 +25,73 @@
};
},
computed
:
{
pipelineLink
()
{
return
sprintf
(
__
(
'
Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}
'
),
{
pipelineLinkStart
:
`<a href=
${
this
.
pipelinePath
}
class="js-pipeline-path link-commit">`
,
pipelineId
:
this
.
pipelineId
,
pipelineLinkEnd
:
'
</a>
'
,
pipelineLinkRefStart
:
`<a href=
${
this
.
pipelineRefPath
}
class="link-commit ref-name">`
,
pipelineRef
:
this
.
pipelineRef
,
pipelineLinkRefEnd
:
'
</a>
'
,
},
false
);
hasRef
()
{
return
!
_
.
isEmpty
(
this
.
pipeline
.
ref
);
},
},
watch
:
{
// When the component is initially mounted it may start with an empty stages array.
// Once the prop is updated, we set the first stage as the selected one
stages
(
newVal
)
{
if
(
newVal
.
length
)
{
this
.
selectedStage
=
newVal
[
0
].
name
;
}
},
},
methods
:
{
onStageClick
(
stage
)
{
// todo: consider moving into store
this
.
selectedStage
=
stage
.
name
;
// update dropdown with jobs
// jobs container is a new component.
this
.
$emit
(
'
requestSidebarStageDropdown
'
,
stage
);
this
.
selectedStage
=
stage
.
name
;
},
},
};
</
script
>
<
template
>
<div
class=
"block-last"
>
<ci-icon
:status=
"pipelineStatus"
/>
<div
class=
"block-last dropdown"
>
<ci-icon
:status=
"pipeline.details.status"
class=
"vertical-align-middle"
/>
{{
__
(
'
Pipeline
'
)
}}
<a
:href=
"pipeline.path"
class=
"js-pipeline-path link-commit"
>
#
{{
pipeline
.
id
}}
</a>
<template
v-if=
"hasRef"
>
{{
__
(
'
from
'
)
}}
<a
:href=
"pipeline.ref.path"
class=
"link-commit ref-name"
>
{{
pipeline
.
ref
.
name
}}
</a>
</
template
>
<p
v-html=
"pipelineLink"
></p>
<button
type=
"button"
data-toggle=
"dropdown"
class=
"js-selected-stage dropdown-menu-toggle prepend-top-8"
>
{{ selectedStage }}
<i
class=
"fa fa-chevron-down"
></i>
</button>
<
div
class=
"dropdown
"
>
<
button
type=
"button
"
data-toggle=
"dropdown
"
<
ul
class=
"dropdown-menu
"
>
<
li
v-for=
"stage in stages
"
:key=
"stage.name
"
>
{{
selectedStage
}}
<icon
name=
"chevron-down"
/>
</button>
<ul
class=
"dropdown-menu"
>
<li
v-for=
"(stage, index) in stages"
:key=
"index"
<button
type=
"button"
class=
"js-stage-item stage-item"
@
click=
"onStageClick(stage)"
>
<button
type=
"button"
class=
"stage-item"
@
click=
"onStageClick(stage)"
>
{{
stage
.
name
}}
</button>
</li>
</ul>
</div>
{{ stage.name }}
</button>
</li>
</ul>
</div>
</template>
app/assets/javascripts/jobs/job_details_bundle.js
View file @
f72a1bf0
import
{
mapState
}
from
'
vuex
'
;
import
_
from
'
underscore
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
Vue
from
'
vue
'
;
import
Job
from
'
../job
'
;
import
JobHeader
from
'
./components/header.vue
'
;
import
DetailsBlock
from
'
./components/sidebar_details_block
.vue
'
;
import
Sidebar
from
'
./components/sidebar
.vue
'
;
import
createStore
from
'
./store
'
;
export
default
()
=>
{
...
...
@@ -13,6 +14,7 @@ export default () => {
const
store
=
createStore
();
store
.
dispatch
(
'
setJobEndpoint
'
,
dataset
.
endpoint
);
store
.
dispatch
(
'
fetchJob
'
);
// Header
...
...
@@ -43,17 +45,25 @@ export default () => {
new
Vue
({
el
:
detailsBlockElement
,
components
:
{
DetailsBlock
,
Sidebar
,
},
store
,
computed
:
{
...
mapState
([
'
job
'
,
'
isLoading
'
]),
...
mapState
([
'
job
'
]),
},
watch
:
{
job
(
newVal
,
oldVal
)
{
if
(
_
.
isEmpty
(
oldVal
)
&&
!
_
.
isEmpty
(
newVal
.
pipeline
))
{
this
.
fetchStages
();
}
},
},
methods
:
{
...
mapActions
([
'
fetchStages
'
]),
},
store
,
render
(
createElement
)
{
return
createElement
(
'
details-block
'
,
{
return
createElement
(
'
sidebar
'
,
{
props
:
{
isLoading
:
this
.
isLoading
,
job
:
this
.
job
,
runnerHelpUrl
:
dataset
.
runnerHelpUrl
,
terminalPath
:
detailsBlockDataset
.
terminalPath
,
},
...
...
app/assets/javascripts/jobs/store/actions.js
View file @
f72a1bf0
...
...
@@ -62,7 +62,9 @@ export const fetchJob = ({ state, dispatch }) => {
});
};
export
const
receiveJobSuccess
=
({
commit
},
data
)
=>
commit
(
types
.
RECEIVE_JOB_SUCCESS
,
data
);
export
const
receiveJobSuccess
=
({
commit
},
data
)
=>
{
commit
(
types
.
RECEIVE_JOB_SUCCESS
,
data
);
};
export
const
receiveJobError
=
({
commit
})
=>
{
commit
(
types
.
RECEIVE_JOB_ERROR
);
flash
(
__
(
'
An error occurred while fetching the job.
'
));
...
...
@@ -137,8 +139,11 @@ export const fetchStages = ({ state, dispatch }) => {
dispatch
(
'
requestStages
'
);
axios
.
get
(
state
.
stagesEndpoint
)
.
then
(({
data
})
=>
dispatch
(
'
receiveStagesSuccess
'
,
data
))
.
get
(
state
.
job
.
pipeline
.
path
)
.
then
(({
data
})
=>
{
dispatch
(
'
receiveStagesSuccess
'
,
data
.
details
.
stages
);
dispatch
(
'
fetchJobsForStage
'
,
data
.
details
.
stages
[
0
]);
})
.
catch
(()
=>
dispatch
(
'
receiveStagesError
'
));
};
export
const
receiveStagesSuccess
=
({
commit
},
data
)
=>
...
...
@@ -152,16 +157,23 @@ export const receiveStagesError = ({ commit }) => {
* Jobs list on sidebar - depend on stages dropdown
*/
export
const
requestJobsForStage
=
({
commit
})
=>
commit
(
types
.
REQUEST_JOBS_FOR_STAGE
);
export
const
setSelectedStage
=
({
commit
},
stage
)
=>
commit
(
types
.
SET_SELECTED_STAGE
,
stage
);
// On stage click, set selected stage + fetch job
export
const
fetchJobsForStage
=
({
state
,
dispatch
},
stage
)
=>
{
dispatch
(
'
setSelectedStage
'
,
stage
);
export
const
fetchJobsForStage
=
({
dispatch
},
stage
)
=>
{
dispatch
(
'
requestJobsForStage
'
);
axios
.
get
(
state
.
stageJobsEndpoint
)
.
then
(({
data
})
=>
dispatch
(
'
receiveJobsForStageSuccess
'
,
data
))
.
get
(
stage
.
dropdown_path
,
{
params
:
{
retried
:
1
,
},
})
.
then
(({
data
})
=>
{
const
retriedJobs
=
data
.
retried
.
map
(
job
=>
Object
.
assign
({},
job
,
{
retried
:
true
}));
const
jobs
=
data
.
latest_statuses
.
concat
(
retriedJobs
);
dispatch
(
'
receiveJobsForStageSuccess
'
,
jobs
);
})
.
catch
(()
=>
dispatch
(
'
receiveJobsForStageError
'
));
};
export
const
receiveJobsForStageSuccess
=
({
commit
},
data
)
=>
...
...
app/assets/stylesheets/pages/builds.scss
View file @
f72a1bf0
...
...
@@ -328,23 +328,6 @@
}
}
.build-dropdown
{
margin
:
$gl-padding
0
;
padding
:
0
;
.dropdown-menu-toggle
{
margin-top
:
#{
$gl-padding
/
2
}
;
}
svg
{
position
:
relative
;
top
:
3px
;
margin-right
:
3px
;
width
:
14px
;
height
:
14px
;
}
}
.builds-container
{
background-color
:
$white-light
;
border-top
:
1px
solid
$border-color
;
...
...
@@ -381,15 +364,11 @@
position
:
absolute
;
left
:
15px
;
top
:
20px
;
display
:
none
;
display
:
block
;
}
&
.active
{
font-weight
:
$gl-font-weight-bold
;
.icon-arrow-right
{
display
:
block
;
}
}
&
.retried
{
...
...
app/views/projects/jobs/_sidebar.html.haml
deleted
100644 → 0
View file @
c4d9f402
%aside
.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar
{
data:
{
"offset-top"
=>
"101"
,
"spy"
=>
"affix"
}
}
.sidebar-container
.blocks-container
#js-details-block-vue
{
data:
{
terminal_path:
can?
(
current_user
,
:create_build_terminal
,
@build
)
&&
@build
.
has_terminal?
?
terminal_project_job_path
(
@project
,
@build
)
:
nil
}
}
-
if
@build
.
pipeline
.
stages_count
>
1
.block-last.dropdown.build-dropdown
%div
%span
{
class:
"ci-status-icon-#{@build.pipeline.status}"
}
=
ci_icon_for_status
(
@build
.
pipeline
.
status
)
Pipeline
=
link_to
"#
#{
@build
.
pipeline
.
id
}
"
,
project_pipeline_path
(
@project
,
@build
.
pipeline
),
class:
'link-commit'
from
=
link_to
"
#{
@build
.
pipeline
.
ref
}
"
,
project_ref_path
(
@project
,
@build
.
pipeline
.
ref
),
class:
'link-commit ref-name'
%button
.dropdown-menu-toggle
{
type:
'button'
,
'data-toggle'
=>
'dropdown'
}
%span
.stage-selection
More
=
icon
(
'chevron-down'
)
%ul
.dropdown-menu
-
@build
.
pipeline
.
legacy_stages
.
each
do
|
stage
|
%li
%a
.stage-item
=
stage
.
name
.builds-container
-
HasStatus
::
ORDERED_STATUSES
.
each
do
|
build_status
|
-
builds
.
select
{
|
build
|
build
.
status
==
build_status
}.
each
do
|
build
|
.build-job
{
class:
sidebar_build_class
(
build
,
@build
),
data:
{
stage:
build
.
stage
}
}
-
tooltip
=
sanitize
(
build
.
tooltip_message
.
dup
)
=
link_to
(
project_job_path
(
@project
,
build
),
data:
{
toggle:
'tooltip'
,
title:
tooltip
,
container:
'body'
})
do
=
sprite_icon
(
'arrow-right'
,
size
:
16
,
css_class:
'icon-arrow-right'
)
%span
{
class:
"ci-status-icon-#{build.status}"
}
=
ci_icon_for_status
(
build
.
status
)
%span
-
if
build
.
name
=
build
.
name
-
else
=
build
.
id
-
if
build
.
retried?
=
sprite_icon
(
'retry'
,
size
:
16
,
css_class:
'icon-retry'
)
app/views/projects/jobs/show.html.haml
View file @
f72a1bf0
...
...
@@ -93,7 +93,7 @@
-
else
=
render
"empty_states"
=
render
"sidebar"
,
builds:
@builds
#js-details-block-vue
{
data:
{
terminal_path:
can?
(
current_user
,
:create_build_terminal
,
@build
)
&&
@build
.
has_terminal?
?
terminal_project_job_path
(
@project
,
@build
)
:
nil
}
}
.js-build-options
{
data:
javascript_build_options
}
...
...
locale/gitlab.pot
View file @
f72a1bf0
...
...
@@ -4310,9 +4310,6 @@ msgstr ""
msgid "Pipeline"
msgstr ""
msgid "Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}"
msgstr ""
msgid "Pipeline Health"
msgstr ""
...
...
@@ -7039,6 +7036,9 @@ msgstr ""
msgid "for this project"
msgstr ""
msgid "from"
msgstr ""
msgid "here"
msgstr ""
...
...
spec/features/projects/environments/environment_spec.rb
View file @
f72a1bf0
...
...
@@ -60,7 +60,7 @@ describe 'Environment' do
context
'with manual action'
do
let
(
:action
)
do
create
(
:ci_build
,
:manual
,
pipeline:
pipeline
,
name:
'deploy to production'
)
name:
'deploy to production'
,
environment:
environment
.
name
)
end
context
'when user has ability to trigger deployment'
do
...
...
@@ -73,12 +73,16 @@ describe 'Environment' do
expect
(
page
).
to
have_link
(
action
.
name
.
humanize
)
end
it
'does allow to play manual action'
do
it
'does allow to play manual action'
,
:js
do
expect
(
action
).
to
be_manual
find
(
'button.dropdown'
).
click
expect
{
click_link
(
action
.
name
.
humanize
)
}
.
not_to
change
{
Ci
::
Pipeline
.
count
}
wait_for_all_requests
expect
(
page
).
to
have_content
(
action
.
name
)
expect
(
action
.
reload
).
to
be_pending
end
...
...
@@ -165,10 +169,10 @@ describe 'Environment' do
name:
action
.
ref
,
project:
project
)
end
it
'allows to stop environment'
do
it
'allows to stop environment'
,
:js
do
click_button
(
'Stop'
)
click_button
(
'Stop environment'
)
# confirm modal
wait_for_all_requests
expect
(
page
).
to
have_content
(
'close_app'
)
end
end
...
...
spec/features/projects/jobs/user_browses_job_spec.rb
View file @
f72a1bf0
...
...
@@ -38,9 +38,10 @@ describe 'User browses a job', :js do
let!
(
:build
)
{
create
(
:ci_build
,
:failed
,
:trace_artifact
,
pipeline:
pipeline
)
}
it
'displays the failure reason'
do
wait_for_all_requests
within
(
'.builds-container'
)
do
build_link
=
first
(
'.build-job > a'
)
expect
(
build_link
[
'data-title'
]).
to
eq
(
'test - failed - (unknown failure)'
)
expect
(
build_link
[
'data-
original-
title'
]).
to
eq
(
'test - failed - (unknown failure)'
)
end
end
end
...
...
@@ -49,9 +50,10 @@ describe 'User browses a job', :js do
let!
(
:build
)
{
create
(
:ci_build
,
:failed
,
:retried
,
:trace_artifact
,
pipeline:
pipeline
)
}
it
'displays the failure reason and retried label'
do
wait_for_all_requests
within
(
'.builds-container'
)
do
build_link
=
first
(
'.build-job > a'
)
expect
(
build_link
[
'data-title'
]).
to
eq
(
'test - failed - (unknown failure) (retried)'
)
expect
(
build_link
[
'data-
original-
title'
]).
to
eq
(
'test - failed - (unknown failure) (retried)'
)
end
end
end
...
...
spec/features/projects/jobs_spec.rb
View file @
f72a1bf0
...
...
@@ -134,23 +134,25 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect
(
page
).
to
have_content
pipeline
.
commit
.
title
end
it
'shows active job'
do
it
'shows active job'
,
:js
do
visit
project_job_path
(
project
,
job
)
wait_for_requests
expect
(
page
).
to
have_selector
(
'.build-job.active'
)
end
end
context
'sidebar'
do
context
'sidebar'
,
:js
do
let
(
:job
)
{
create
(
:ci_build
,
:success
,
:trace_live
,
pipeline:
pipeline
,
name:
'<img src=x onerror=alert(document.domain)>'
)
}
before
do
visit
project_job_path
(
project
,
job
)
wait_for_requests
end
it
'renders escaped tooltip name'
do
page
.
within
(
'aside.right-sidebar'
)
do
expect
(
find
(
'.active.build-job a'
)[
'data-
title'
]).
to
eq
(
'<img src="x">
- passed'
)
expect
(
find
(
'.active.build-job a'
)[
'data-
original-title'
]).
to
eq
(
'<img src=x onerror=alert(document.domain)>
- passed'
)
end
end
end
...
...
spec/javascripts/job_spec.js
View file @
f72a1bf0
...
...
@@ -57,25 +57,6 @@ describe('Job', () => {
expect
(
job
.
buildStage
).
toBe
(
'
test
'
);
expect
(
job
.
state
).
toBe
(
''
);
});
it
(
'
only shows the jobs matching the current stage
'
,
()
=>
{
expect
(
$
(
'
.build-job[data-stage="build"]
'
).
is
(
'
:visible
'
)).
toBe
(
false
);
expect
(
$
(
'
.build-job[data-stage="test"]
'
).
is
(
'
:visible
'
)).
toBe
(
true
);
expect
(
$
(
'
.build-job[data-stage="deploy"]
'
).
is
(
'
:visible
'
)).
toBe
(
false
);
});
it
(
'
selects the current stage in the build dropdown menu
'
,
()
=>
{
expect
(
$
(
'
.stage-selection
'
).
text
()).
toBe
(
'
test
'
);
});
it
(
'
updates the jobs when the build dropdown changes
'
,
()
=>
{
$
(
'
.stage-item:contains("build")
'
).
click
();
expect
(
$
(
'
.stage-selection
'
).
text
()).
toBe
(
'
build
'
);
expect
(
$
(
'
.build-job[data-stage="build"]
'
).
is
(
'
:visible
'
)).
toBe
(
true
);
expect
(
$
(
'
.build-job[data-stage="test"]
'
).
is
(
'
:visible
'
)).
toBe
(
false
);
expect
(
$
(
'
.build-job[data-stage="deploy"]
'
).
is
(
'
:visible
'
)).
toBe
(
false
);
});
});
describe
(
'
running build
'
,
()
=>
{
...
...
spec/javascripts/jobs/components/jobs_container_spec.js
View file @
f72a1bf0
...
...
@@ -2,7 +2,7 @@ import Vue from 'vue';
import
component
from
'
~/jobs/components/jobs_container.vue
'
;
import
mountComponent
from
'
../../helpers/vue_mount_component_helper
'
;
describe
(
'
Artifacts
block
'
,
()
=>
{
describe
(
'
Jobs List
block
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
component
);
let
vm
;
...
...
@@ -16,8 +16,7 @@ describe('Artifacts block', () => {
text
:
'
passed
'
,
tooltip
:
'
passed
'
,
},
path
:
'
job/233432756
'
,
id
:
'
233432756
'
,
id
:
233432756
,
tooltip
:
'
build - passed
'
,
retried
:
true
,
};
...
...
@@ -33,8 +32,7 @@ describe('Artifacts block', () => {
text
:
'
passed
'
,
tooltip
:
'
passed
'
,
},
path
:
'
job/2322756
'
,
id
:
'
2322756
'
,
id
:
2322756
,
tooltip
:
'
build - passed
'
,
active
:
true
,
};
...
...
@@ -50,8 +48,7 @@ describe('Artifacts block', () => {
text
:
'
passed
'
,
tooltip
:
'
passed
'
,
},
path
:
'
job/232153
'
,
id
:
'
232153
'
,
id
:
232153
,
tooltip
:
'
build - passed
'
,
};
...
...
@@ -62,14 +59,16 @@ describe('Artifacts block', () => {
it
(
'
renders list of jobs
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
job
,
retried
,
active
],
jobId
:
12313
,
});
expect
(
vm
.
$el
.
querySelectorAll
(
'
a
'
).
length
).
toEqual
(
3
);
});
it
(
'
renders arrow right when job i
s active
'
,
()
=>
{
it
(
'
renders arrow right when job i
d matches `jobId`
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
active
],
jobId
:
active
.
id
,
});
expect
(
vm
.
$el
.
querySelector
(
'
a .js-arrow-right
'
)).
not
.
toBeNull
();
...
...
@@ -78,6 +77,7 @@ describe('Artifacts block', () => {
it
(
'
does not render arrow right when job is not active
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
job
],
jobId
:
active
.
id
,
});
expect
(
vm
.
$el
.
querySelector
(
'
a .js-arrow-right
'
)).
toBeNull
();
...
...
@@ -86,6 +86,7 @@ describe('Artifacts block', () => {
it
(
'
renders job name when present
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
job
],
jobId
:
active
.
id
,
});
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
textContent
.
trim
()).
toContain
(
job
.
name
);
...
...
@@ -95,6 +96,7 @@ describe('Artifacts block', () => {
it
(
'
renders job id when job name is not available
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
retried
],
jobId
:
active
.
id
,
});
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
textContent
.
trim
()).
toContain
(
retried
.
id
);
...
...
@@ -103,14 +105,16 @@ describe('Artifacts block', () => {
it
(
'
links to the job page
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
job
],
jobId
:
active
.
id
,
});
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
job
.
path
);
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
job
.
status
.
details_
path
);
});
it
(
'
renders retry icon when job was retried
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
retried
],
jobId
:
active
.
id
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-retry-icon
'
)).
not
.
toBeNull
();
...
...
@@ -119,6 +123,7 @@ describe('Artifacts block', () => {
it
(
'
does not render retry icon when job was not retried
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
jobs
:
[
job
],
jobId
:
active
.
id
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-retry-icon
'
)).
toBeNull
();
...
...
spec/javascripts/jobs/components/sidebar_
details_block_
spec.js
→
spec/javascripts/jobs/components/sidebar_spec.js
View file @
f72a1bf0
import
Vue
from
'
vue
'
;
import
sidebarDetailsBlock
from
'
~/jobs/components/sidebar_details_block.vue
'
;
import
job
from
'
../mock_data
'
;
import
mountComponent
from
'
../../helpers/vue_mount_component_helper
'
;
import
sidebarDetailsBlock
from
'
~/jobs/components/sidebar.vue
'
;
import
createStore
from
'
~/jobs/store
'
;
import
job
,
{
stages
,
jobsInStage
}
from
'
../mock_data
'
;
import
{
mountComponentWithStore
}
from
'
../../helpers/vue_mount_component_helper
'
;
import
{
trimText
}
from
'
../../helpers/vue_component_helper
'
;
describe
(
'
Sidebar details block
'
,
()
=>
{
let
SidebarComponent
;
const
SidebarComponent
=
Vue
.
extend
(
sidebarDetailsBlock
)
;
let
vm
;
function
trimWhitespace
(
element
)
{
return
element
.
textContent
.
replace
(
/
\s
+/g
,
'
'
).
trim
();
}
let
store
;
beforeEach
(()
=>
{
SidebarComponent
=
Vue
.
extend
(
sidebarDetailsBlock
);
store
=
createStore
(
);
});
afterEach
(()
=>
{
...
...
@@ -21,19 +20,21 @@ describe('Sidebar details block', () => {
describe
(
'
when it is loading
'
,
()
=>
{
it
(
'
should render a loading spinner
'
,
()
=>
{
vm
=
mountComponent
(
SidebarComponent
,
{
job
:
{},
isLoading
:
true
,
});
store
.
dispatch
(
'
requestJob
'
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
});
expect
(
vm
.
$el
.
querySelector
(
'
.fa-spinner
'
)).
toBeDefined
();
});
});
describe
(
'
when there is no retry path retry
'
,
()
=>
{
it
(
'
should not render a retry button
'
,
()
=>
{
vm
=
mountComponent
(
SidebarComponent
,
{
job
:
{},
isLoading
:
false
,
const
copy
=
Object
.
assign
({},
job
);
delete
copy
.
retry_path
;
store
.
dispatch
(
'
receiveJobSuccess
'
,
copy
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-retry-job
'
)).
toBeNull
();
...
...
@@ -42,10 +43,8 @@ describe('Sidebar details block', () => {
describe
(
'
without terminal path
'
,
()
=>
{
it
(
'
does not render terminal link
'
,
()
=>
{
vm
=
mountComponent
(
SidebarComponent
,
{
job
,
isLoading
:
false
,
});
store
.
dispatch
(
'
receiveJobSuccess
'
,
job
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-terminal-link
'
)).
toBeNull
();
});
...
...
@@ -53,10 +52,12 @@ describe('Sidebar details block', () => {
describe
(
'
with terminal path
'
,
()
=>
{
it
(
'
renders terminal link
'
,
()
=>
{
vm
=
mountComponent
(
SidebarComponent
,
{
job
,
isLoading
:
false
,
terminalPath
:
'
job/43123/terminal
'
,
store
.
dispatch
(
'
receiveJobSuccess
'
,
job
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
,
props
:
{
terminalPath
:
'
job/43123/terminal
'
,
},
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-terminal-link
'
)).
not
.
toBeNull
();
...
...
@@ -64,10 +65,8 @@ describe('Sidebar details block', () => {
});
beforeEach
(()
=>
{
vm
=
mountComponent
(
SidebarComponent
,
{
job
,
isLoading
:
false
,
});
store
.
dispatch
(
'
receiveJobSuccess
'
,
job
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
});
});
describe
(
'
actions
'
,
()
=>
{
...
...
@@ -89,7 +88,7 @@ describe('Sidebar details block', () => {
describe
(
'
information
'
,
()
=>
{
it
(
'
should render merge request link
'
,
()
=>
{
expect
(
trim
Whitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-mr
'
)
)).
toEqual
(
'
Merge Request: !2
'
);
expect
(
trim
Text
(
vm
.
$el
.
querySelector
(
'
.js-job-mr
'
).
textContent
)).
toEqual
(
'
Merge Request: !2
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-job-mr a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
job
.
merge_request
.
path
,
...
...
@@ -97,43 +96,101 @@ describe('Sidebar details block', () => {
});
it
(
'
should render job duration
'
,
()
=>
{
expect
(
trim
Whitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-duration
'
)
)).
toEqual
(
expect
(
trim
Text
(
vm
.
$el
.
querySelector
(
'
.js-job-duration
'
).
textContent
)).
toEqual
(
'
Duration: 6 seconds
'
,
);
});
it
(
'
should render erased date
'
,
()
=>
{
expect
(
trimWhitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-erased
'
))).
toEqual
(
'
Erased: 3 weeks ago
'
);
expect
(
trimText
(
vm
.
$el
.
querySelector
(
'
.js-job-erased
'
).
textContent
)).
toEqual
(
'
Erased: 3 weeks ago
'
,
);
});
it
(
'
should render finished date
'
,
()
=>
{
expect
(
trim
Whitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-finished
'
)
)).
toEqual
(
expect
(
trim
Text
(
vm
.
$el
.
querySelector
(
'
.js-job-finished
'
).
textContent
)).
toEqual
(
'
Finished: 3 weeks ago
'
,
);
});
it
(
'
should render queued date
'
,
()
=>
{
expect
(
trimWhitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-queued
'
))).
toEqual
(
'
Queued: 9 seconds
'
);
expect
(
trimText
(
vm
.
$el
.
querySelector
(
'
.js-job-queued
'
).
textContent
)).
toEqual
(
'
Queued: 9 seconds
'
,
);
});
it
(
'
should render runner ID
'
,
()
=>
{
expect
(
trim
Whitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-runner
'
)
)).
toEqual
(
expect
(
trim
Text
(
vm
.
$el
.
querySelector
(
'
.js-job-runner
'
).
textContent
)).
toEqual
(
'
Runner: local ci runner (#1)
'
,
);
});
it
(
'
should render timeout information
'
,
()
=>
{
expect
(
trim
Whitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-timeout
'
)
)).
toEqual
(
expect
(
trim
Text
(
vm
.
$el
.
querySelector
(
'
.js-job-timeout
'
).
textContent
)).
toEqual
(
'
Timeout: 1m 40s (from runner)
'
,
);
});
it
(
'
should render coverage
'
,
()
=>
{
expect
(
trimWhitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-coverage
'
))).
toEqual
(
'
Coverage: 20%
'
);
expect
(
trimText
(
vm
.
$el
.
querySelector
(
'
.js-job-coverage
'
).
textContent
)).
toEqual
(
'
Coverage: 20%
'
,
);
});
it
(
'
should render tags
'
,
()
=>
{
expect
(
trimWhitespace
(
vm
.
$el
.
querySelector
(
'
.js-job-tags
'
))).
toEqual
(
'
Tags: tag
'
);
expect
(
trimText
(
vm
.
$el
.
querySelector
(
'
.js-job-tags
'
).
textContent
)).
toEqual
(
'
Tags: tag
'
);
});
});
describe
(
'
stages dropdown
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
dispatch
(
'
receiveJobSuccess
'
,
job
);
});
describe
(
'
while fetching stages
'
,
()
=>
{
it
(
'
renders dropdown with More label
'
,
()
=>
{
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-selected-stage
'
).
textContent
.
trim
()).
toEqual
(
'
More
'
);
});
});
describe
(
'
with stages
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
dispatch
(
'
receiveStagesSuccess
'
,
stages
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
});
});
it
(
'
renders first stage as selected
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-selected-stage
'
).
textContent
.
trim
()).
toEqual
(
stages
[
0
].
name
,
);
});
});
describe
(
'
without jobs for stages
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
dispatch
(
'
receiveJobSuccess
'
,
job
);
store
.
dispatch
(
'
receiveStagesSuccess
'
,
stages
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
});
});
it
(
'
does not render job container
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-jobs-container
'
)).
toBeNull
();
});
});
describe
(
'
with jobs for stages
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
dispatch
(
'
receiveJobSuccess
'
,
job
);
store
.
dispatch
(
'
receiveStagesSuccess
'
,
stages
);
store
.
dispatch
(
'
receiveJobsForStageSuccess
'
,
jobsInStage
.
latest_statuses
);
vm
=
mountComponentWithStore
(
SidebarComponent
,
{
store
});
});
it
(
'
renders list of jobs
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-jobs-container
'
)).
not
.
toBeNull
();
});
});
});
});
spec/javascripts/jobs/components/stages_dropdown_spec.js
View file @
f72a1bf0
...
...
@@ -8,10 +8,25 @@ describe('Artifacts block', () => {
beforeEach
(()
=>
{
vm
=
mountComponent
(
Component
,
{
pipelineId
:
28029444
,
pipelinePath
:
'
pipeline/28029444
'
,
pipelineRef
:
'
50101-truncated-job-information
'
,
pipelineRefPath
:
'
commits/50101-truncated-job-information
'
,
pipeline
:
{
id
:
28029444
,
details
:
{
status
:
{
details_path
:
'
/gitlab-org/gitlab-ce/pipelines/28029444
'
,
group
:
'
success
'
,
has_details
:
true
,
icon
:
'
status_success
'
,
label
:
'
passed
'
,
text
:
'
passed
'
,
tooltip
:
'
passed
'
,
},
},
path
:
'
pipeline/28029444
'
,
},
ref
:
{
path
:
'
commits/50101-truncated-job-information
'
,
name
:
'
50101-truncated-job-information
'
,
},
stages
:
[
{
name
:
'
build
'
,
...
...
@@ -20,15 +35,6 @@ describe('Artifacts block', () => {
name
:
'
test
'
,
},
],
pipelineStatus
:
{
details_path
:
'
/gitlab-org/gitlab-ce/pipelines/28029444
'
,
group
:
'
success
'
,
has_details
:
true
,
icon
:
'
status_success
'
,
label
:
'
passed
'
,
text
:
'
passed
'
,
tooltip
:
'
passed
'
,
},
});
});
...
...
spec/javascripts/jobs/mock_data.js
View file @
f72a1bf0
This diff is collapsed.
Click to expand it.
spec/javascripts/jobs/store/actions_spec.js
View file @
f72a1bf0
...
...
@@ -27,7 +27,6 @@ import {
receiveStagesSuccess
,
receiveStagesError
,
requestJobsForStage
,
setSelectedStage
,
fetchJobsForStage
,
receiveJobsForStageSuccess
,
receiveJobsForStageError
,
...
...
@@ -236,7 +235,8 @@ describe('Job State actions', () => {
},
{
payload
:
{
html
:
'
I, [2018-08-17T22:57:45.707325 #1841] INFO -- :
'
,
complete
:
true
,
html
:
'
I, [2018-08-17T22:57:45.707325 #1841] INFO -- :
'
,
complete
:
true
,
},
type
:
'
receiveTraceSuccess
'
,
},
...
...
@@ -421,7 +421,9 @@ describe('Job State actions', () => {
let
mock
;
beforeEach
(()
=>
{
mockedState
.
stagesEndpoint
=
`
${
TEST_HOST
}
/endpoint.json`
;
mockedState
.
job
.
pipeline
=
{
path
:
`
${
TEST_HOST
}
/endpoint.json/stages`
,
};
mock
=
new
MockAdapter
(
axios
);
});
...
...
@@ -430,8 +432,10 @@ describe('Job State actions', () => {
});
describe
(
'
success
'
,
()
=>
{
it
(
'
dispatches requestStages and receiveStagesSuccess
'
,
done
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/endpoint.json`
).
replyOnce
(
200
,
[{
id
:
121212
,
name
:
'
build
'
}]);
it
(
'
dispatches requestStages and receiveStagesSuccess, fetchJobsForStage
'
,
done
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/endpoint.json/stages`
)
.
replyOnce
(
200
,
{
details
:
{
stages
:
[{
id
:
121212
,
name
:
'
build
'
}]
}
});
testAction
(
fetchStages
,
...
...
@@ -446,6 +450,10 @@ describe('Job State actions', () => {
payload
:
[{
id
:
121212
,
name
:
'
build
'
}],
type
:
'
receiveStagesSuccess
'
,
},
{
payload
:
{
id
:
121212
,
name
:
'
build
'
},
type
:
'
fetchJobsForStage
'
,
},
],
done
,
);
...
...
@@ -516,24 +524,10 @@ describe('Job State actions', () => {
});
});
describe
(
'
setSelectedStage
'
,
()
=>
{
it
(
'
should commit SET_SELECTED_STAGE mutation
'
,
done
=>
{
testAction
(
setSelectedStage
,
{
name
:
'
build
'
},
mockedState
,
[{
type
:
types
.
SET_SELECTED_STAGE
,
payload
:
{
name
:
'
build
'
}
}],
[],
done
,
);
});
});
describe
(
'
fetchJobsForStage
'
,
()
=>
{
let
mock
;
beforeEach
(()
=>
{
mockedState
.
stageJobsEndpoint
=
`
${
TEST_HOST
}
/endpoint.json`
;
mock
=
new
MockAdapter
(
axios
);
});
...
...
@@ -542,19 +536,17 @@ describe('Job State actions', () => {
});
describe
(
'
success
'
,
()
=>
{
it
(
'
dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageSuccess
'
,
done
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/endpoint.json`
).
replyOnce
(
200
,
[{
id
:
121212
,
name
:
'
build
'
}]);
it
(
'
dispatches requestJobsForStage and receiveJobsForStageSuccess
'
,
done
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/jobs.json`
)
.
replyOnce
(
200
,
{
latest_statuses
:
[{
id
:
121212
,
name
:
'
build
'
}],
retried
:
[]
});
testAction
(
fetchJobsForStage
,
null
,
{
dropdown_path
:
`
${
TEST_HOST
}
/jobs.json`
}
,
mockedState
,
[],
[
{
type
:
'
setSelectedStage
'
,
payload
:
null
,
},
{
type
:
'
requestJobsForStage
'
,
},
...
...
@@ -570,20 +562,16 @@ describe('Job State actions', () => {
describe
(
'
error
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/
endpoint
.json`
).
reply
(
500
);
mock
.
onGet
(
`
${
TEST_HOST
}
/
jobs
.json`
).
reply
(
500
);
});
it
(
'
dispatches
setSelectedStage,
requestJobsForStage and receiveJobsForStageError
'
,
done
=>
{
it
(
'
dispatches requestJobsForStage and receiveJobsForStageError
'
,
done
=>
{
testAction
(
fetchJobsForStage
,
null
,
{
dropdown_path
:
`
${
TEST_HOST
}
/jobs.json`
}
,
mockedState
,
[],
[
{
payload
:
null
,
type
:
'
setSelectedStage
'
,
},
{
type
:
'
requestJobsForStage
'
,
},
...
...
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