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
be4abe77
Commit
be4abe77
authored
Nov 14, 2017
by
Filipa Lacerda
Committed by
Phil Hughes
Nov 14, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Stops page reload when changing tabs or pages - uses API requests instead
parent
62287fec
Changes
12
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
385 additions
and
252 deletions
+385
-252
app/assets/javascripts/lib/utils/common_utils.js
app/assets/javascripts/lib/utils/common_utils.js
+36
-0
app/assets/javascripts/lib/utils/poll.js
app/assets/javascripts/lib/utils/poll.js
+6
-2
app/assets/javascripts/pipelines/components/navigation_tabs.vue
...sets/javascripts/pipelines/components/navigation_tabs.vue
+24
-63
app/assets/javascripts/pipelines/components/pipelines.vue
app/assets/javascripts/pipelines/components/pipelines.vue
+114
-49
app/views/projects/pipelines/index.html.haml
app/views/projects/pipelines/index.html.haml
+0
-6
changelogs/unreleased/32098-pipelines-navigation.yml
changelogs/unreleased/32098-pipelines-navigation.yml
+6
-0
spec/features/projects/pipelines/pipelines_spec.rb
spec/features/projects/pipelines/pipelines_spec.rb
+20
-6
spec/javascripts/fixtures/pipelines.html.haml
spec/javascripts/fixtures/pipelines.html.haml
+1
-7
spec/javascripts/lib/utils/common_utils_spec.js
spec/javascripts/lib/utils/common_utils_spec.js
+30
-0
spec/javascripts/lib/utils/poll_spec.js
spec/javascripts/lib/utils/poll_spec.js
+3
-3
spec/javascripts/pipelines/navigation_tabs_spec.js
spec/javascripts/pipelines/navigation_tabs_spec.js
+28
-100
spec/javascripts/pipelines/pipelines_spec.js
spec/javascripts/pipelines/pipelines_spec.js
+117
-16
No files found.
app/assets/javascripts/lib/utils/common_utils.js
View file @
be4abe77
...
@@ -309,6 +309,42 @@ export const setParamInURL = (param, value) => {
...
@@ -309,6 +309,42 @@ export const setParamInURL = (param, value) => {
return
search
;
return
search
;
};
};
/**
* Given a string of query parameters creates an object.
*
* @example
* `scope=all&page=2` -> { scope: 'all', page: '2'}
* `scope=all` -> { scope: 'all' }
* ``-> {}
* @param {String} query
* @returns {Object}
*/
export
const
parseQueryStringIntoObject
=
(
query
=
''
)
=>
{
if
(
query
===
''
)
return
{};
return
query
.
split
(
'
&
'
)
.
reduce
((
acc
,
element
)
=>
{
const
val
=
element
.
split
(
'
=
'
);
Object
.
assign
(
acc
,
{
[
val
[
0
]]:
decodeURIComponent
(
val
[
1
]),
});
return
acc
;
},
{});
};
export
const
buildUrlWithCurrentLocation
=
param
=>
(
param
?
`
${
window
.
location
.
pathname
}${
param
}
`
:
window
.
location
.
pathname
);
/**
* Based on the current location and the string parameters provided
* creates a new entry in the history without reloading the page.
*
* @param {String} param
*/
export
const
historyPushState
=
(
newUrl
)
=>
{
window
.
history
.
pushState
({},
document
.
title
,
newUrl
);
};
/**
/**
* Converts permission provided as strings to booleans.
* Converts permission provided as strings to booleans.
*
*
...
...
app/assets/javascripts/lib/utils/poll.js
View file @
be4abe77
...
@@ -60,7 +60,6 @@ export default class Poll {
...
@@ -60,7 +60,6 @@ export default class Poll {
checkConditions
(
response
)
{
checkConditions
(
response
)
{
const
headers
=
normalizeHeaders
(
response
.
headers
);
const
headers
=
normalizeHeaders
(
response
.
headers
);
const
pollInterval
=
parseInt
(
headers
[
this
.
intervalHeader
],
10
);
const
pollInterval
=
parseInt
(
headers
[
this
.
intervalHeader
],
10
);
if
(
pollInterval
>
0
&&
response
.
status
===
httpStatusCodes
.
OK
&&
this
.
canPoll
)
{
if
(
pollInterval
>
0
&&
response
.
status
===
httpStatusCodes
.
OK
&&
this
.
canPoll
)
{
this
.
timeoutID
=
setTimeout
(()
=>
{
this
.
timeoutID
=
setTimeout
(()
=>
{
this
.
makeRequest
();
this
.
makeRequest
();
...
@@ -102,7 +101,12 @@ export default class Poll {
...
@@ -102,7 +101,12 @@ export default class Poll {
/**
/**
* Restarts polling after it has been stoped
* Restarts polling after it has been stoped
*/
*/
restart
()
{
restart
(
options
)
{
// update data
if
(
options
&&
options
.
data
)
{
this
.
options
.
data
=
options
.
data
;
}
this
.
canPoll
=
true
;
this
.
canPoll
=
true
;
this
.
makeRequest
();
this
.
makeRequest
();
}
}
...
...
app/assets/javascripts/pipelines/components/navigation_tabs.vue
View file @
be4abe77
...
@@ -2,16 +2,8 @@
...
@@ -2,16 +2,8 @@
export
default
{
export
default
{
name
:
'
PipelineNavigationTabs
'
,
name
:
'
PipelineNavigationTabs
'
,
props
:
{
props
:
{
scope
:
{
tabs
:
{
type
:
String
,
type
:
Array
,
required
:
true
,
},
count
:
{
type
:
Object
,
required
:
true
,
},
paths
:
{
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
},
},
...
@@ -23,68 +15,37 @@
...
@@ -23,68 +15,37 @@
// 0 is valid in a badge, but evaluates to false, we need to check for undefined
// 0 is valid in a badge, but evaluates to false, we need to check for undefined
return
count
!==
undefined
;
return
count
!==
undefined
;
},
},
onTabClick
(
tab
)
{
this
.
$emit
(
'
onChangeTab
'
,
tab
.
scope
);
},
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
<ul
class=
"nav-links scrolling-tabs"
>
<ul
class=
"nav-links scrolling-tabs"
>
<li
<li
class=
"js-pipelines-tab-all"
v-for=
"(tab, i) in tabs"
:class=
"
{ active: scope === 'all'}">
:key=
"i"
<a
:href=
"paths.allPath"
>
:class=
"
{
All
active: tab.isActive,
<span
}"
v-if=
"shouldRenderBadge(count.all)"
>
class=
"badge js-totalbuilds-count"
>
<a
{{
count
.
all
}}
role=
"button"
</span>
@
click=
"onTabClick(tab)"
</a>
:class=
"`js-pipelines-tab-$
{tab.scope}`"
</li>
>
<li
{{
tab
.
name
}}
class=
"js-pipelines-tab-pending"
:class=
"
{ active: scope === 'pending'}">
<a
:href=
"paths.pendingPath"
>
Pending
<span
v-if=
"shouldRenderBadge(count.pending)"
class=
"badge"
>
{{
count
.
pending
}}
</span>
</a>
</li>
<li
class=
"js-pipelines-tab-running"
:class=
"
{ active: scope === 'running'}">
<a
:href=
"paths.runningPath"
>
Running
<span
v-if=
"shouldRenderBadge(count.running)"
class=
"badge"
>
{{
count
.
running
}}
</span>
</a>
</li>
<li
class=
"js-pipelines-tab-finished"
:class=
"
{ active: scope === 'finished'}">
<a
:href=
"paths.finishedPath"
>
Finished
<span
<span
v-if=
"shouldRenderBadge(count.finished)"
v-if=
"shouldRenderBadge(tab.count)"
class=
"badge"
>
class=
"badge"
{{
count
.
finished
}}
>
{{
tab
.
count
}}
</span>
</span>
</a>
</a>
</li>
</li>
<li
class=
"js-pipelines-tab-branches"
:class=
"
{ active: scope === 'branches'}">
<a
:href=
"paths.branchesPath"
>
Branches
</a>
</li>
<li
class=
"js-pipelines-tab-tags"
:class=
"
{ active: scope === 'tags'}">
<a
:href=
"paths.tagsPath"
>
Tags
</a>
</li>
</ul>
</ul>
</
template
>
</
template
>
app/assets/javascripts/pipelines/components/pipelines.vue
View file @
be4abe77
<
script
>
<
script
>
import
_
from
'
underscore
'
;
import
PipelinesService
from
'
../services/pipelines_service
'
;
import
PipelinesService
from
'
../services/pipelines_service
'
;
import
pipelinesMixin
from
'
../mixins/pipelines
'
;
import
pipelinesMixin
from
'
../mixins/pipelines
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
navigationTabs
from
'
./navigation_tabs.vue
'
;
import
navigationTabs
from
'
./navigation_tabs.vue
'
;
import
navigationControls
from
'
./nav_controls.vue
'
;
import
navigationControls
from
'
./nav_controls.vue
'
;
import
{
convertPermissionToBoolean
,
getParameterByName
,
setParamInURL
}
from
'
../../lib/utils/common_utils
'
;
import
{
convertPermissionToBoolean
,
getParameterByName
,
historyPushState
,
buildUrlWithCurrentLocation
,
parseQueryStringIntoObject
,
}
from
'
../../lib/utils/common_utils
'
;
export
default
{
export
default
{
props
:
{
props
:
{
...
@@ -41,27 +48,18 @@
...
@@ -41,27 +48,18 @@
autoDevopsPath
:
pipelinesData
.
helpAutoDevopsPath
,
autoDevopsPath
:
pipelinesData
.
helpAutoDevopsPath
,
newPipelinePath
:
pipelinesData
.
newPipelinePath
,
newPipelinePath
:
pipelinesData
.
newPipelinePath
,
canCreatePipeline
:
pipelinesData
.
canCreatePipeline
,
canCreatePipeline
:
pipelinesData
.
canCreatePipeline
,
allPath
:
pipelinesData
.
allPath
,
pendingPath
:
pipelinesData
.
pendingPath
,
runningPath
:
pipelinesData
.
runningPath
,
finishedPath
:
pipelinesData
.
finishedPath
,
branchesPath
:
pipelinesData
.
branchesPath
,
tagsPath
:
pipelinesData
.
tagsPath
,
hasCi
:
pipelinesData
.
hasCi
,
hasCi
:
pipelinesData
.
hasCi
,
ciLintPath
:
pipelinesData
.
ciLintPath
,
ciLintPath
:
pipelinesData
.
ciLintPath
,
state
:
this
.
store
.
state
,
state
:
this
.
store
.
state
,
apiScope
:
'
all
'
,
scope
:
getParameterByName
(
'
scope
'
)
||
'
all
'
,
pagenum
:
1
,
page
:
getParameterByName
(
'
page
'
)
||
'
1
'
,
requestData
:
{},
};
};
},
},
computed
:
{
computed
:
{
canCreatePipelineParsed
()
{
canCreatePipelineParsed
()
{
return
convertPermissionToBoolean
(
this
.
canCreatePipeline
);
return
convertPermissionToBoolean
(
this
.
canCreatePipeline
);
},
},
scope
()
{
const
scope
=
getParameterByName
(
'
scope
'
);
return
scope
===
null
?
'
all
'
:
scope
;
},
/**
/**
* The empty state should only be rendered when the request is made to fetch all pipelines
* The empty state should only be rendered when the request is made to fetch all pipelines
...
@@ -106,47 +104,113 @@
...
@@ -106,47 +104,113 @@
hasCiEnabled
()
{
hasCiEnabled
()
{
return
this
.
hasCi
!==
undefined
;
return
this
.
hasCi
!==
undefined
;
},
},
paths
()
{
return
{
tabs
()
{
allPath
:
this
.
allPath
,
const
{
count
}
=
this
.
state
;
pendingPath
:
this
.
pendingPath
,
return
[
finishedPath
:
this
.
finishedPath
,
{
runningPath
:
this
.
runningPath
,
name
:
'
All
'
,
branchesPath
:
this
.
branchesPath
,
scope
:
'
all
'
,
tagsPath
:
this
.
tagsPath
,
count
:
count
.
all
,
};
isActive
:
this
.
scope
===
'
all
'
,
},
{
name
:
'
Pending
'
,
scope
:
'
pending
'
,
count
:
count
.
pending
,
isActive
:
this
.
scope
===
'
pending
'
,
},
{
name
:
'
Running
'
,
scope
:
'
running
'
,
count
:
count
.
running
,
isActive
:
this
.
scope
===
'
running
'
,
},
{
name
:
'
Finished
'
,
scope
:
'
finished
'
,
count
:
count
.
finished
,
isActive
:
this
.
scope
===
'
finished
'
,
},
},
pageParameter
()
{
{
return
getParameterByName
(
'
page
'
)
||
this
.
pagenum
;
name
:
'
Branches
'
,
scope
:
'
branches
'
,
isActive
:
this
.
scope
===
'
branches
'
,
},
},
scopeParameter
()
{
{
return
getParameterByName
(
'
scope
'
)
||
this
.
apiScope
;
name
:
'
Tags
'
,
scope
:
'
tags
'
,
isActive
:
this
.
scope
===
'
tags
'
,
},
];
},
},
},
},
created
()
{
created
()
{
this
.
service
=
new
PipelinesService
(
this
.
endpoint
);
this
.
service
=
new
PipelinesService
(
this
.
endpoint
);
this
.
requestData
=
{
page
:
this
.
page
Parameter
,
scope
:
this
.
scopeParameter
};
this
.
requestData
=
{
page
:
this
.
page
,
scope
:
this
.
scope
};
},
},
methods
:
{
methods
:
{
/**
* Will change the page number and update the URL.
*
* @param {Number} pageNumber desired page to go to.
*/
change
(
pageNumber
)
{
const
param
=
setParamInURL
(
'
page
'
,
pageNumber
);
gl
.
utils
.
visitUrl
(
param
);
return
param
;
},
successCallback
(
resp
)
{
successCallback
(
resp
)
{
return
resp
.
json
().
then
((
response
)
=>
{
return
resp
.
json
().
then
((
response
)
=>
{
// Because we are polling & the user is interacting verify if the response received
// matches the last request made
if
(
_
.
isEqual
(
parseQueryStringIntoObject
(
resp
.
url
.
split
(
'
?
'
)[
1
]),
this
.
requestData
))
{
this
.
store
.
storeCount
(
response
.
count
);
this
.
store
.
storeCount
(
response
.
count
);
this
.
store
.
storePagination
(
resp
.
headers
);
this
.
store
.
storePagination
(
resp
.
headers
);
this
.
setCommonData
(
response
.
pipelines
);
this
.
setCommonData
(
response
.
pipelines
);
}
});
});
},
},
/**
* Handles URL and query parameter changes.
* When the user uses the pagination or the tabs,
* - update URL
* - Make API request to the server with new parameters
* - Update the polling function
* - Update the internal state
*/
updateContent
(
parameters
)
{
// stop polling
this
.
poll
.
stop
();
const
queryString
=
Object
.
keys
(
parameters
).
map
((
parameter
)
=>
{
const
value
=
parameters
[
parameter
];
// update internal state for UI
this
[
parameter
]
=
value
;
return
`
${
parameter
}
=
${
encodeURIComponent
(
value
)}
`
;
}).
join
(
'
&
'
);
// update polling parameters
this
.
requestData
=
parameters
;
historyPushState
(
buildUrlWithCurrentLocation
(
`?
${
queryString
}
`
));
this
.
isLoading
=
true
;
// fetch new data
return
this
.
service
.
getPipelines
(
this
.
requestData
)
.
then
((
response
)
=>
{
this
.
isLoading
=
false
;
this
.
successCallback
(
response
);
// restart polling
this
.
poll
.
restart
({
data
:
this
.
requestData
});
})
.
catch
(()
=>
{
this
.
isLoading
=
false
;
this
.
errorCallback
();
// restart polling
this
.
poll
.
restart
();
});
},
onChangeTab
(
scope
)
{
this
.
updateContent
({
scope
,
page
:
'
1
'
});
},
onChangePage
(
page
)
{
/* URLS parameters are strings, we need to parse to match types */
this
.
updateContent
({
scope
:
this
.
scope
,
page
:
Number
(
page
).
toString
()
});
},
},
},
};
};
</
script
>
</
script
>
...
@@ -154,7 +218,7 @@
...
@@ -154,7 +218,7 @@
<div
class=
"pipelines-container"
>
<div
class=
"pipelines-container"
>
<div
<div
class=
"top-area scrolling-tabs-container inner-page-scroll-tabs"
class=
"top-area scrolling-tabs-container inner-page-scroll-tabs"
v-if=
"!
isLoading && !
shouldRenderEmptyState"
>
v-if=
"!shouldRenderEmptyState"
>
<div
class=
"fade-left"
>
<div
class=
"fade-left"
>
<i
<i
class=
"fa fa-angle-left"
class=
"fa fa-angle-left"
...
@@ -167,17 +231,17 @@
...
@@ -167,17 +231,17 @@
aria-hidden=
"true"
>
aria-hidden=
"true"
>
</i>
</i>
</div>
</div>
<navigation-tabs
<navigation-tabs
:scope=
"scope"
:tabs=
"tabs"
:count=
"state.count"
@
onChangeTab=
"onChangeTab"
:paths=
"paths"
/>
/>
<navigation-controls
<navigation-controls
:new-pipeline-path=
"newPipelinePath"
:new-pipeline-path=
"newPipelinePath"
:has-ci-enabled=
"hasCiEnabled"
:has-ci-enabled=
"hasCiEnabled"
:help-page-path=
"helpPagePath"
:help-page-path=
"helpPagePath"
:ci
LintP
ath=
"ciLintPath"
:ci
-lint-p
ath=
"ciLintPath"
:can-create-pipeline=
"canCreatePipelineParsed "
:can-create-pipeline=
"canCreatePipelineParsed "
/>
/>
</div>
</div>
...
@@ -188,6 +252,7 @@
...
@@ -188,6 +252,7 @@
label=
"Loading Pipelines"
label=
"Loading Pipelines"
size=
"3"
size=
"3"
v-if=
"isLoading"
v-if=
"isLoading"
class=
"prepend-top-20"
/>
/>
<empty-state
<empty-state
...
@@ -221,8 +286,8 @@
...
@@ -221,8 +286,8 @@
<table-pagination
<table-pagination
v-if=
"shouldRenderPagination"
v-if=
"shouldRenderPagination"
:change=
"
chan
ge"
:change=
"
onChangePa
ge"
:page
I
nfo=
"state.pageInfo"
:page
-i
nfo=
"state.pageInfo"
/>
/>
</div>
</div>
</div>
</div>
...
...
app/views/projects/pipelines/index.html.haml
View file @
be4abe77
...
@@ -9,12 +9,6 @@
...
@@ -9,12 +9,6 @@
"error-state-svg-path"
=>
image_path
(
'illustrations/pipelines_failed.svg'
),
"error-state-svg-path"
=>
image_path
(
'illustrations/pipelines_failed.svg'
),
"new-pipeline-path"
=>
new_project_pipeline_path
(
@project
),
"new-pipeline-path"
=>
new_project_pipeline_path
(
@project
),
"can-create-pipeline"
=>
can?
(
current_user
,
:create_pipeline
,
@project
).
to_s
,
"can-create-pipeline"
=>
can?
(
current_user
,
:create_pipeline
,
@project
).
to_s
,
"all-path"
=>
project_pipelines_path
(
@project
),
"pending-path"
=>
project_pipelines_path
(
@project
,
scope: :pending
),
"running-path"
=>
project_pipelines_path
(
@project
,
scope: :running
),
"finished-path"
=>
project_pipelines_path
(
@project
,
scope: :finished
),
"branches-path"
=>
project_pipelines_path
(
@project
,
scope: :branches
),
"tags-path"
=>
project_pipelines_path
(
@project
,
scope: :tags
),
"has-ci"
=>
@repository
.
gitlab_ci_yml
,
"has-ci"
=>
@repository
.
gitlab_ci_yml
,
"ci-lint-path"
=>
ci_lint_path
}
}
"ci-lint-path"
=>
ci_lint_path
}
}
...
...
changelogs/unreleased/32098-pipelines-navigation.yml
0 → 100644
View file @
be4abe77
---
title
:
Stop reloading the page when using pagination and tabs - use API calls - in
Pipelines table
merge_request
:
author
:
type
:
other
spec/features/projects/pipelines/pipelines_spec.rb
View file @
be4abe77
...
@@ -56,31 +56,37 @@ describe 'Pipelines', :js do
...
@@ -56,31 +56,37 @@ describe 'Pipelines', :js do
end
end
it
'shows a tab for All pipelines and count'
do
it
'shows a tab for All pipelines and count'
do
expect
(
page
.
find
(
'.js-pipelines-tab-all
a
'
).
text
).
to
include
(
'All'
)
expect
(
page
.
find
(
'.js-pipelines-tab-all'
).
text
).
to
include
(
'All'
)
expect
(
page
.
find
(
'.js-pipelines-tab-all .badge'
).
text
).
to
include
(
'1'
)
expect
(
page
.
find
(
'.js-pipelines-tab-all .badge'
).
text
).
to
include
(
'1'
)
end
end
it
'shows a tab for Pending pipelines and count'
do
it
'shows a tab for Pending pipelines and count'
do
expect
(
page
.
find
(
'.js-pipelines-tab-pending
a
'
).
text
).
to
include
(
'Pending'
)
expect
(
page
.
find
(
'.js-pipelines-tab-pending'
).
text
).
to
include
(
'Pending'
)
expect
(
page
.
find
(
'.js-pipelines-tab-pending .badge'
).
text
).
to
include
(
'0'
)
expect
(
page
.
find
(
'.js-pipelines-tab-pending .badge'
).
text
).
to
include
(
'0'
)
end
end
it
'shows a tab for Running pipelines and count'
do
it
'shows a tab for Running pipelines and count'
do
expect
(
page
.
find
(
'.js-pipelines-tab-running
a
'
).
text
).
to
include
(
'Running'
)
expect
(
page
.
find
(
'.js-pipelines-tab-running'
).
text
).
to
include
(
'Running'
)
expect
(
page
.
find
(
'.js-pipelines-tab-running .badge'
).
text
).
to
include
(
'1'
)
expect
(
page
.
find
(
'.js-pipelines-tab-running .badge'
).
text
).
to
include
(
'1'
)
end
end
it
'shows a tab for Finished pipelines and count'
do
it
'shows a tab for Finished pipelines and count'
do
expect
(
page
.
find
(
'.js-pipelines-tab-finished
a
'
).
text
).
to
include
(
'Finished'
)
expect
(
page
.
find
(
'.js-pipelines-tab-finished'
).
text
).
to
include
(
'Finished'
)
expect
(
page
.
find
(
'.js-pipelines-tab-finished .badge'
).
text
).
to
include
(
'0'
)
expect
(
page
.
find
(
'.js-pipelines-tab-finished .badge'
).
text
).
to
include
(
'0'
)
end
end
it
'shows a tab for Branches'
do
it
'shows a tab for Branches'
do
expect
(
page
.
find
(
'.js-pipelines-tab-branches
a
'
).
text
).
to
include
(
'Branches'
)
expect
(
page
.
find
(
'.js-pipelines-tab-branches'
).
text
).
to
include
(
'Branches'
)
end
end
it
'shows a tab for Tags'
do
it
'shows a tab for Tags'
do
expect
(
page
.
find
(
'.js-pipelines-tab-tags a'
).
text
).
to
include
(
'Tags'
)
expect
(
page
.
find
(
'.js-pipelines-tab-tags'
).
text
).
to
include
(
'Tags'
)
end
it
'updates content when tab is clicked'
do
page
.
find
(
'.js-pipelines-tab-pending'
).
click
wait_for_requests
expect
(
page
).
to
have_content
(
'No pipelines to show.'
)
end
end
end
end
...
@@ -396,6 +402,14 @@ describe 'Pipelines', :js do
...
@@ -396,6 +402,14 @@ describe 'Pipelines', :js do
expect
(
page
).
to
have_selector
(
'.gl-pagination .page'
,
count:
2
)
expect
(
page
).
to
have_selector
(
'.gl-pagination .page'
,
count:
2
)
end
end
it
'should show updated content'
do
visit
project_pipelines_path
(
project
)
wait_for_requests
page
.
find
(
'.js-next-button a'
).
click
expect
(
page
).
to
have_selector
(
'.gl-pagination .page'
,
count:
2
)
end
end
end
end
end
...
...
spec/javascripts/fixtures/pipelines.html.haml
View file @
be4abe77
%div
%div
#pipelines-list-vue
{
data:
{
endpoint:
'foo'
,
#pipelines-list-vue
{
data:
{
endpoint:
'foo'
,
"css-class"
=>
'foo'
,
"help-page-path"
=>
'foo'
,
"help-page-path"
=>
'foo'
,
"help-auto-devops-path"
=>
'foo'
,
"empty-state-svg-path"
=>
'foo'
,
"empty-state-svg-path"
=>
'foo'
,
"error-state-svg-path"
=>
'foo'
,
"error-state-svg-path"
=>
'foo'
,
"new-pipeline-path"
=>
'foo'
,
"new-pipeline-path"
=>
'foo'
,
"can-create-pipeline"
=>
'true'
,
"can-create-pipeline"
=>
'true'
,
"all-path"
=>
'foo'
,
"pending-path"
=>
'foo'
,
"running-path"
=>
'foo'
,
"finished-path"
=>
'foo'
,
"branches-path"
=>
'foo'
,
"tags-path"
=>
'foo'
,
"has-ci"
=>
'foo'
,
"has-ci"
=>
'foo'
,
"ci-lint-path"
=>
'foo'
}
}
"ci-lint-path"
=>
'foo'
}
}
spec/javascripts/lib/utils/common_utils_spec.js
View file @
be4abe77
...
@@ -183,6 +183,36 @@ describe('common_utils', () => {
...
@@ -183,6 +183,36 @@ describe('common_utils', () => {
});
});
});
});
describe
(
'
historyPushState
'
,
()
=>
{
afterEach
(()
=>
{
window
.
history
.
replaceState
({},
null
,
null
);
});
it
(
'
should call pushState with the correct path
'
,
()
=>
{
spyOn
(
window
.
history
,
'
pushState
'
);
commonUtils
.
historyPushState
(
'
newpath?page=2
'
);
expect
(
window
.
history
.
pushState
).
toHaveBeenCalled
();
expect
(
window
.
history
.
pushState
.
calls
.
allArgs
()[
0
][
2
]).
toContain
(
'
newpath?page=2
'
);
});
});
describe
(
'
parseQueryStringIntoObject
'
,
()
=>
{
it
(
'
should return object with query parameters
'
,
()
=>
{
expect
(
commonUtils
.
parseQueryStringIntoObject
(
'
scope=all&page=2
'
)).
toEqual
({
scope
:
'
all
'
,
page
:
'
2
'
});
expect
(
commonUtils
.
parseQueryStringIntoObject
(
'
scope=all
'
)).
toEqual
({
scope
:
'
all
'
});
expect
(
commonUtils
.
parseQueryStringIntoObject
()).
toEqual
({});
});
});
describe
(
'
buildUrlWithCurrentLocation
'
,
()
=>
{
it
(
'
should build an url with current location and given parameters
'
,
()
=>
{
expect
(
commonUtils
.
buildUrlWithCurrentLocation
()).
toEqual
(
window
.
location
.
pathname
);
expect
(
commonUtils
.
buildUrlWithCurrentLocation
(
'
?page=2
'
)).
toEqual
(
`
${
window
.
location
.
pathname
}
?page=2`
);
});
});
describe
(
'
getParameterByName
'
,
()
=>
{
describe
(
'
getParameterByName
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
window
.
history
.
pushState
({},
null
,
'
?scope=all&p=2
'
);
window
.
history
.
pushState
({},
null
,
'
?scope=all&p=2
'
);
...
...
spec/javascripts/lib/utils/poll_spec.js
View file @
be4abe77
...
@@ -155,7 +155,7 @@ describe('Poll', () => {
...
@@ -155,7 +155,7 @@ describe('Poll', () => {
successCallback
:
()
=>
{
successCallback
:
()
=>
{
Polling
.
stop
();
Polling
.
stop
();
setTimeout
(()
=>
{
setTimeout
(()
=>
{
Polling
.
restart
();
Polling
.
restart
(
{
data
:
{
page
:
4
}
}
);
},
0
);
},
0
);
},
},
errorCallback
:
callbacks
.
error
,
errorCallback
:
callbacks
.
error
,
...
@@ -170,10 +170,10 @@ describe('Poll', () => {
...
@@ -170,10 +170,10 @@ describe('Poll', () => {
Polling
.
stop
();
Polling
.
stop
();
expect
(
service
.
fetch
.
calls
.
count
()).
toEqual
(
2
);
expect
(
service
.
fetch
.
calls
.
count
()).
toEqual
(
2
);
expect
(
service
.
fetch
).
toHaveBeenCalledWith
({
page
:
1
});
expect
(
service
.
fetch
).
toHaveBeenCalledWith
({
page
:
4
});
expect
(
Polling
.
stop
).
toHaveBeenCalled
();
expect
(
Polling
.
stop
).
toHaveBeenCalled
();
expect
(
Polling
.
restart
).
toHaveBeenCalled
();
expect
(
Polling
.
restart
).
toHaveBeenCalled
();
expect
(
Polling
.
options
.
data
).
toEqual
({
page
:
4
});
done
();
done
();
});
});
});
});
...
...
spec/javascripts/pipelines/navigation_tabs_spec.js
View file @
be4abe77
...
@@ -8,120 +8,48 @@ describe('navigation tabs pipeline component', () => {
...
@@ -8,120 +8,48 @@ describe('navigation tabs pipeline component', () => {
let
data
;
let
data
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
data
=
{
data
=
[
{
name
:
'
All
'
,
scope
:
'
all
'
,
scope
:
'
all
'
,
count
:
{
count
:
1
,
all
:
16
,
isActive
:
true
,
running
:
1
,
pending
:
10
,
finished
:
0
,
},
},
paths
:
{
{
allPath
:
'
/gitlab-org/gitlab-ce/pipelines
'
,
name
:
'
Pending
'
,
pendingPath
:
'
/gitlab-org/gitlab-ce/pipelines?scope=pending
'
,
scope
:
'
pending
'
,
finishedPath
:
'
/gitlab-org/gitlab-ce/pipelines?scope=finished
'
,
count
:
0
,
runningPath
:
'
/gitlab-org/gitlab-ce/pipelines?scope=running
'
,
isActive
:
false
,
branchesPath
:
'
/gitlab-org/gitlab-ce/pipelines?scope=branches
'
,
tagsPath
:
'
/gitlab-org/gitlab-ce/pipelines?scope=tags
'
,
},
},
};
{
name
:
'
Running
'
,
scope
:
'
running
'
,
isActive
:
false
,
},
];
Component
=
Vue
.
extend
(
navigationTabs
);
Component
=
Vue
.
extend
(
navigationTabs
);
vm
=
mountComponent
(
Component
,
{
tabs
:
data
});
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
vm
.
$destroy
();
vm
.
$destroy
();
});
});
it
(
'
should render tabs with correct paths
'
,
()
=>
{
it
(
'
should render tabs
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
data
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
li
'
).
length
).
toEqual
(
data
.
length
);
// All
const
allTab
=
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-all a
'
);
expect
(
allTab
.
textContent
.
trim
()).
toContain
(
'
All
'
);
expect
(
allTab
.
getAttribute
(
'
href
'
)).
toEqual
(
data
.
paths
.
allPath
);
// Pending
const
pendingTab
=
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-pending a
'
);
expect
(
pendingTab
.
textContent
.
trim
()).
toContain
(
'
Pending
'
);
expect
(
pendingTab
.
getAttribute
(
'
href
'
)).
toEqual
(
data
.
paths
.
pendingPath
);
// Running
const
runningTab
=
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-running a
'
);
expect
(
runningTab
.
textContent
.
trim
()).
toContain
(
'
Running
'
);
expect
(
runningTab
.
getAttribute
(
'
href
'
)).
toEqual
(
data
.
paths
.
runningPath
);
// Finished
const
finishedTab
=
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-finished a
'
);
expect
(
finishedTab
.
textContent
.
trim
()).
toContain
(
'
Finished
'
);
expect
(
finishedTab
.
getAttribute
(
'
href
'
)).
toEqual
(
data
.
paths
.
finishedPath
);
// Branches
const
branchesTab
=
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-branches a
'
);
expect
(
branchesTab
.
textContent
.
trim
()).
toContain
(
'
Branches
'
);
// Tags
const
tagsTab
=
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-tags a
'
);
expect
(
tagsTab
.
textContent
.
trim
()).
toContain
(
'
Tags
'
);
});
describe
(
'
scope
'
,
()
=>
{
it
(
'
should render scope provided as active tab
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
data
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-all
'
).
className
).
toContain
(
'
active
'
);
});
});
});
describe
(
'
badges
'
,
()
=>
{
it
(
'
should render provided number
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
data
);
// All
expect
(
vm
.
$el
.
querySelector
(
'
.js-totalbuilds-count
'
).
textContent
.
trim
(),
).
toContain
(
data
.
count
.
all
);
// Pending
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-pending .badge
'
).
textContent
.
trim
(),
).
toContain
(
data
.
count
.
pending
);
// Running
it
(
'
should render active tab
'
,
()
=>
{
expect
(
expect
(
vm
.
$el
.
querySelector
(
'
.active .js-pipelines-tab-all
'
)).
toBeDefined
();
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-running .badge
'
).
textContent
.
trim
(),
).
toContain
(
data
.
count
.
running
);
// Finished
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-finished .badge
'
).
textContent
.
trim
(),
).
toContain
(
data
.
count
.
finished
);
});
});
it
(
'
should not render badge when number is undefined
'
,
()
=>
{
it
(
'
should render badge
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-all .badge
'
).
textContent
.
trim
()).
toEqual
(
'
1
'
);
scope
:
'
all
'
,
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-pending .badge
'
).
textContent
.
trim
()).
toEqual
(
'
0
'
);
paths
:
{},
count
:
{},
});
});
// All
it
(
'
should not render badge
'
,
()
=>
{
expect
(
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-running .badge
'
)).
toEqual
(
null
);
vm
.
$el
.
querySelector
(
'
.js-totalbuilds-count
'
),
).
toEqual
(
null
);
// Pending
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-pending .badge
'
),
).
toEqual
(
null
);
// Running
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-running .badge
'
),
).
toEqual
(
null
);
// Finished
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-finished .badge
'
),
).
toEqual
(
null
);
});
});
});
});
});
spec/javascripts/pipelines/pipelines_spec.js
View file @
be4abe77
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
pipelinesComp
from
'
~/pipelines/components/pipelines.vue
'
;
import
pipelinesComp
from
'
~/pipelines/components/pipelines.vue
'
;
import
Store
from
'
~/pipelines/stores/pipelines_store
'
;
import
Store
from
'
~/pipelines/stores/pipelines_store
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
describe
(
'
Pipelines
'
,
()
=>
{
describe
(
'
Pipelines
'
,
()
=>
{
const
jsonFixtureName
=
'
pipelines/pipelines.json
'
;
const
jsonFixtureName
=
'
pipelines/pipelines.json
'
;
...
@@ -9,26 +10,33 @@ describe('Pipelines', () => {
...
@@ -9,26 +10,33 @@ describe('Pipelines', () => {
preloadFixtures
(
jsonFixtureName
);
preloadFixtures
(
jsonFixtureName
);
let
PipelinesComponent
;
let
PipelinesComponent
;
let
pipeline
;
let
pipelines
;
let
component
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
loadFixtures
(
'
static/pipelines.html.raw
'
);
loadFixtures
(
'
static/pipelines.html.raw
'
);
const
pipelines
=
getJSONFixture
(
jsonFixtureName
).
pipelines
;
pipelines
=
getJSONFixture
(
jsonFixtureName
);
pipeline
=
pipelines
.
find
(
p
=>
p
.
id
===
1
);
PipelinesComponent
=
Vue
.
extend
(
pipelinesComp
);
PipelinesComponent
=
Vue
.
extend
(
pipelinesComp
);
});
});
afterEach
(()
=>
{
component
.
$destroy
();
});
describe
(
'
successfull request
'
,
()
=>
{
describe
(
'
successfull request
'
,
()
=>
{
describe
(
'
with pipelines
'
,
()
=>
{
describe
(
'
with pipelines
'
,
()
=>
{
const
pipelinesInterceptor
=
(
request
,
next
)
=>
{
const
pipelinesInterceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
pipeline
),
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
pipeline
s
),
{
status
:
200
,
status
:
200
,
}));
}));
};
};
beforeEach
(()
=>
{
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
pipelinesInterceptor
);
Vue
.
http
.
interceptors
.
push
(
pipelinesInterceptor
);
component
=
mountComponent
(
PipelinesComponent
,
{
store
:
new
Store
(),
});
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -38,18 +46,71 @@ describe('Pipelines', () => {
...
@@ -38,18 +46,71 @@ describe('Pipelines', () => {
});
});
it
(
'
should render table
'
,
(
done
)
=>
{
it
(
'
should render table
'
,
(
done
)
=>
{
const
component
=
new
PipelinesComponent
({
propsData
:
{
store
:
new
Store
(),
},
}).
$mount
();
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.table-holder
'
)).
toBeDefined
();
expect
(
component
.
$el
.
querySelector
(
'
.table-holder
'
)).
toBeDefined
();
expect
(
component
.
$el
.
querySelector
(
'
.realtime-loading
'
)).
toBe
(
null
);
expect
(
component
.
$el
.
querySelectorAll
(
'
.gl-responsive-table-row
'
).
length
,
).
toEqual
(
pipelines
.
pipelines
.
length
+
1
);
done
();
done
();
});
});
});
});
it
(
'
should render navigation tabs
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-tab-pending
'
).
textContent
.
trim
(),
).
toContain
(
'
Pending
'
);
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-tab-all
'
).
textContent
.
trim
(),
).
toContain
(
'
All
'
);
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-tab-running
'
).
textContent
.
trim
(),
).
toContain
(
'
Running
'
);
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-tab-finished
'
).
textContent
.
trim
(),
).
toContain
(
'
Finished
'
);
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-tab-branches
'
).
textContent
.
trim
(),
).
toContain
(
'
Branches
'
);
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-tab-tags
'
).
textContent
.
trim
(),
).
toContain
(
'
Tags
'
);
done
();
});
});
it
(
'
should make an API request when using tabs
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
spyOn
(
component
,
'
updateContent
'
);
component
.
$el
.
querySelector
(
'
.js-pipelines-tab-finished
'
).
click
();
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
'
finished
'
,
page
:
'
1
'
});
done
();
});
});
describe
(
'
with pagination
'
,
()
=>
{
it
(
'
should make an API request when using pagination
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
spyOn
(
component
,
'
updateContent
'
);
// Mock pagination
component
.
store
.
state
.
pageInfo
=
{
page
:
1
,
total
:
10
,
perPage
:
2
,
nextPage
:
2
,
totalPages
:
5
,
};
Vue
.
nextTick
(()
=>
{
component
.
$el
.
querySelector
(
'
.js-next-button a
'
).
click
();
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
'
all
'
,
page
:
'
2
'
});
done
();
});
});
});
});
});
});
describe
(
'
without pipelines
'
,
()
=>
{
describe
(
'
without pipelines
'
,
()
=>
{
...
@@ -70,15 +131,14 @@ describe('Pipelines', () => {
...
@@ -70,15 +131,14 @@ describe('Pipelines', () => {
});
});
it
(
'
should render empty state
'
,
(
done
)
=>
{
it
(
'
should render empty state
'
,
(
done
)
=>
{
co
nst
co
mponent
=
new
PipelinesComponent
({
component
=
new
PipelinesComponent
({
propsData
:
{
propsData
:
{
store
:
new
Store
(),
store
:
new
Store
(),
},
},
}).
$mount
();
}).
$mount
();
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.empty-state
'
)).
toBeDefined
();
expect
(
component
.
$el
.
querySelector
(
'
.empty-state
'
)).
not
.
toBe
(
null
);
expect
(
component
.
$el
.
querySelector
(
'
.realtime-loading
'
)).
toBe
(
null
);
done
();
done
();
});
});
});
});
...
@@ -103,7 +163,7 @@ describe('Pipelines', () => {
...
@@ -103,7 +163,7 @@ describe('Pipelines', () => {
});
});
it
(
'
should render error state
'
,
(
done
)
=>
{
it
(
'
should render error state
'
,
(
done
)
=>
{
co
nst
co
mponent
=
new
PipelinesComponent
({
component
=
new
PipelinesComponent
({
propsData
:
{
propsData
:
{
store
:
new
Store
(),
store
:
new
Store
(),
},
},
...
@@ -111,9 +171,50 @@ describe('Pipelines', () => {
...
@@ -111,9 +171,50 @@ describe('Pipelines', () => {
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-error-state
'
)).
toBeDefined
();
expect
(
component
.
$el
.
querySelector
(
'
.js-pipelines-error-state
'
)).
toBeDefined
();
expect
(
component
.
$el
.
querySelector
(
'
.realtime-loading
'
)).
toBe
(
null
);
done
();
done
();
});
});
});
});
});
});
describe
(
'
updateContent
'
,
()
=>
{
it
(
'
should set given parameters
'
,
()
=>
{
component
=
mountComponent
(
PipelinesComponent
,
{
store
:
new
Store
(),
});
component
.
updateContent
({
scope
:
'
finished
'
,
page
:
'
4
'
});
expect
(
component
.
page
).
toEqual
(
'
4
'
);
expect
(
component
.
scope
).
toEqual
(
'
finished
'
);
expect
(
component
.
requestData
.
scope
).
toEqual
(
'
finished
'
);
expect
(
component
.
requestData
.
page
).
toEqual
(
'
4
'
);
});
});
describe
(
'
onChangeTab
'
,
()
=>
{
it
(
'
should set page to 1
'
,
()
=>
{
component
=
mountComponent
(
PipelinesComponent
,
{
store
:
new
Store
(),
});
spyOn
(
component
,
'
updateContent
'
);
component
.
onChangeTab
(
'
running
'
);
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
'
running
'
,
page
:
'
1
'
});
});
});
describe
(
'
onChangePage
'
,
()
=>
{
it
(
'
should update page and keep scope
'
,
()
=>
{
component
=
mountComponent
(
PipelinesComponent
,
{
store
:
new
Store
(),
});
spyOn
(
component
,
'
updateContent
'
);
component
.
onChangePage
(
4
);
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
component
.
scope
,
page
:
'
4
'
});
});
});
});
});
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