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
0
Merge Requests
0
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
Léo-Paul Géneau
gitlab-ce
Commits
45631562
Commit
45631562
authored
Nov 23, 2017
by
Filipa Lacerda
Committed by
Phil Hughes
Nov 23, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improve environments performance
parent
1d8ab59e
Changes
32
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
933 additions
and
804 deletions
+933
-804
app/assets/javascripts/environments/components/container.vue
app/assets/javascripts/environments/components/container.vue
+71
-0
app/assets/javascripts/environments/components/empty_state.vue
...ssets/javascripts/environments/components/empty_state.vue
+42
-0
app/assets/javascripts/environments/components/environment.vue
...ssets/javascripts/environments/components/environment.vue
+0
-270
app/assets/javascripts/environments/components/environment_external_url.vue
...ipts/environments/components/environment_external_url.vue
+2
-1
app/assets/javascripts/environments/components/environment_item.vue
.../javascripts/environments/components/environment_item.vue
+4
-4
app/assets/javascripts/environments/components/environment_monitoring.vue
...cripts/environments/components/environment_monitoring.vue
+2
-1
app/assets/javascripts/environments/components/environment_rollback.vue
...ascripts/environments/components/environment_rollback.vue
+2
-2
app/assets/javascripts/environments/components/environments_app.vue
.../javascripts/environments/components/environments_app.vue
+128
-0
app/assets/javascripts/environments/components/environments_table.vue
...avascripts/environments/components/environments_table.vue
+8
-8
app/assets/javascripts/environments/environments_bundle.js
app/assets/javascripts/environments/environments_bundle.js
+32
-3
app/assets/javascripts/environments/folder/environments_folder_bundle.js
...scripts/environments/folder/environments_folder_bundle.js
+28
-3
app/assets/javascripts/environments/folder/environments_folder_view.vue
...ascripts/environments/folder/environments_folder_view.vue
+47
-208
app/assets/javascripts/environments/mixins/environments_mixin.js
...ets/javascripts/environments/mixins/environments_mixin.js
+163
-4
app/assets/javascripts/environments/stores/environments_store.js
...ets/javascripts/environments/stores/environments_store.js
+6
-1
app/assets/javascripts/lib/utils/common_utils.js
app/assets/javascripts/lib/utils/common_utils.js
+0
-40
app/assets/javascripts/pipelines/components/pipelines.vue
app/assets/javascripts/pipelines/components/pipelines.vue
+5
-26
app/assets/javascripts/vue_shared/components/navigation_tabs.vue
...ets/javascripts/vue_shared/components/navigation_tabs.vue
+27
-2
app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
.../javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
+42
-0
app/controllers/projects/environments_controller.rb
app/controllers/projects/environments_controller.rb
+1
-0
app/views/projects/environments/folder.html.haml
app/views/projects/environments/folder.html.haml
+3
-1
app/views/projects/environments/index.html.haml
app/views/projects/environments/index.html.haml
+1
-3
spec/features/projects/environments/environments_spec.rb
spec/features/projects/environments/environments_spec.rb
+29
-6
spec/javascripts/environments/emtpy_state_spec.js
spec/javascripts/environments/emtpy_state_spec.js
+57
-0
spec/javascripts/environments/environment_table_spec.js
spec/javascripts/environments/environment_table_spec.js
+16
-15
spec/javascripts/environments/environments_app_spec.js
spec/javascripts/environments/environments_app_spec.js
+79
-53
spec/javascripts/environments/folder/environments_folder_view_spec.js
...ipts/environments/folder/environments_folder_view_spec.js
+95
-62
spec/javascripts/fixtures/environments/element.html.haml
spec/javascripts/fixtures/environments/element.html.haml
+0
-1
spec/javascripts/fixtures/environments/environments.html.haml
.../javascripts/fixtures/environments/environments.html.haml
+0
-9
spec/javascripts/fixtures/environments/environments_folder_view.html.haml
.../fixtures/environments/environments_folder_view.html.haml
+0
-7
spec/javascripts/lib/utils/common_utils_spec.js
spec/javascripts/lib/utils/common_utils_spec.js
+0
-41
spec/javascripts/pipelines/pipelines_spec.js
spec/javascripts/pipelines/pipelines_spec.js
+33
-29
spec/javascripts/vue_shared/components/navigation_tabs_spec.js
...javascripts/vue_shared/components/navigation_tabs_spec.js
+10
-4
No files found.
app/assets/javascripts/environments/components/container.vue
0 → 100644
View file @
45631562
<
script
>
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
environmentTable
from
'
../components/environments_table.vue
'
;
export
default
{
props
:
{
isLoading
:
{
type
:
Boolean
,
required
:
true
,
},
environments
:
{
type
:
Array
,
required
:
true
,
},
pagination
:
{
type
:
Object
,
required
:
true
,
},
canCreateDeployment
:
{
type
:
Boolean
,
required
:
true
,
},
canReadEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
},
components
:
{
environmentTable
,
loadingIcon
,
tablePagination
,
},
methods
:
{
onChangePage
(
page
)
{
this
.
$emit
(
'
onChangePage
'
,
page
);
},
},
};
</
script
>
<
template
>
<div
class=
"environments-container"
>
<loading-icon
label=
"Loading environments"
v-if=
"isLoading"
size=
"3"
/>
<slot
name=
"emptyState"
></slot>
<div
class=
"table-holder"
v-if=
"!isLoading && environments.length > 0"
>
<environment-table
:environments=
"environments"
:can-create-deployment=
"canCreateDeployment"
:can-read-environment=
"canReadEnvironment"
/>
<table-pagination
v-if=
"pagination && pagination.totalPages > 1"
:change=
"onChangePage"
:pageInfo=
"pagination"
/>
</div>
</div>
</
template
>
app/assets/javascripts/environments/components/empty_state.vue
0 → 100644
View file @
45631562
<
script
>
export
default
{
name
:
'
environmentsEmptyState
'
,
props
:
{
newPath
:
{
type
:
String
,
required
:
true
,
},
canCreateEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
helpPath
:
{
type
:
String
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<div
class=
"blank-state-row"
>
<div
class=
"blank-state-center"
>
<h2
class=
"blank-state-title js-blank-state-title"
>
{{
s__
(
"
Environments|You don't have any environments right now.
"
)
}}
</h2>
<p
class=
"blank-state-text"
>
{{
s__
(
"
Environments|Environments are places where code gets deployed, such as staging or production.
"
)
}}
<br
/>
<a
:href=
"helpPath"
>
{{
s__
(
"
Environments|Read more about environments
"
)
}}
</a>
</p>
<a
v-if=
"canCreateEnvironment"
:href=
"newPath"
class=
"btn btn-create js-new-environment-button"
>
{{
s__
(
"
Environments|New environment
"
)
}}
</a>
</div>
</div>
</
template
>
app/assets/javascripts/environments/components/environment.vue
deleted
100644 → 0
View file @
1d8ab59e
<
script
>
import
Visibility
from
'
visibilityjs
'
;
import
Flash
from
'
../../flash
'
;
import
EnvironmentsService
from
'
../services/environments_service
'
;
import
environmentTable
from
'
./environments_table.vue
'
;
import
EnvironmentsStore
from
'
../stores/environments_store
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
{
convertPermissionToBoolean
,
getParameterByName
,
setParamInURL
}
from
'
../../lib/utils/common_utils
'
;
import
eventHub
from
'
../event_hub
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
environmentsMixin
from
'
../mixins/environments_mixin
'
;
export
default
{
components
:
{
environmentTable
,
tablePagination
,
loadingIcon
,
},
mixins
:
[
environmentsMixin
,
],
data
()
{
const
environmentsData
=
document
.
querySelector
(
'
#environments-list-view
'
).
dataset
;
const
store
=
new
EnvironmentsStore
();
return
{
store
,
state
:
store
.
state
,
visibility
:
'
available
'
,
isLoading
:
false
,
cssContainerClass
:
environmentsData
.
cssClass
,
endpoint
:
environmentsData
.
environmentsDataEndpoint
,
canCreateDeployment
:
environmentsData
.
canCreateDeployment
,
canReadEnvironment
:
environmentsData
.
canReadEnvironment
,
canCreateEnvironment
:
environmentsData
.
canCreateEnvironment
,
projectEnvironmentsPath
:
environmentsData
.
projectEnvironmentsPath
,
projectStoppedEnvironmentsPath
:
environmentsData
.
projectStoppedEnvironmentsPath
,
newEnvironmentPath
:
environmentsData
.
newEnvironmentPath
,
helpPagePath
:
environmentsData
.
helpPagePath
,
isMakingRequest
:
false
,
// Pagination Properties,
paginationInformation
:
{},
pageNumber
:
1
,
};
},
computed
:
{
scope
()
{
return
getParameterByName
(
'
scope
'
);
},
canReadEnvironmentParsed
()
{
return
convertPermissionToBoolean
(
this
.
canReadEnvironment
);
},
canCreateDeploymentParsed
()
{
return
convertPermissionToBoolean
(
this
.
canCreateDeployment
);
},
canCreateEnvironmentParsed
()
{
return
convertPermissionToBoolean
(
this
.
canCreateEnvironment
);
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created
()
{
const
scope
=
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
page
=
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
this
.
service
=
new
EnvironmentsService
(
this
.
endpoint
);
const
poll
=
new
Poll
({
resource
:
this
.
service
,
method
:
'
get
'
,
data
:
{
scope
,
page
},
successCallback
:
this
.
successCallback
,
errorCallback
:
this
.
errorCallback
,
notificationCallback
:
(
isMakingRequest
)
=>
{
this
.
isMakingRequest
=
isMakingRequest
;
},
});
if
(
!
Visibility
.
hidden
())
{
this
.
isLoading
=
true
;
poll
.
makeRequest
();
}
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
poll
.
restart
();
}
else
{
poll
.
stop
();
}
});
eventHub
.
$on
(
'
toggleFolder
'
,
this
.
toggleFolder
);
eventHub
.
$on
(
'
postAction
'
,
this
.
postAction
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
toggleFolder
'
);
eventHub
.
$off
(
'
postAction
'
);
},
methods
:
{
toggleFolder
(
folder
)
{
this
.
store
.
toggleFolder
(
folder
);
if
(
!
folder
.
isOpen
)
{
this
.
fetchChildEnvironments
(
folder
,
true
);
}
},
/**
* Will change the page number and update the URL.
*
* @param {Number} pageNumber desired page to go to.
* @return {String}
*/
changePage
(
pageNumber
)
{
const
param
=
setParamInURL
(
'
page
'
,
pageNumber
);
gl
.
utils
.
visitUrl
(
param
);
return
param
;
},
fetchEnvironments
()
{
const
scope
=
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
page
=
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
this
.
isLoading
=
true
;
return
this
.
service
.
get
({
scope
,
page
})
.
then
(
this
.
successCallback
)
.
catch
(
this
.
errorCallback
);
},
fetchChildEnvironments
(
folder
,
showLoader
=
false
)
{
this
.
store
.
updateEnvironmentProp
(
folder
,
'
isLoadingFolderContent
'
,
showLoader
);
this
.
service
.
getFolderContent
(
folder
.
folder_path
)
.
then
(
resp
=>
resp
.
json
())
.
then
(
response
=>
this
.
store
.
setfolderContent
(
folder
,
response
.
environments
))
.
then
(()
=>
this
.
store
.
updateEnvironmentProp
(
folder
,
'
isLoadingFolderContent
'
,
false
))
.
catch
(()
=>
{
// eslint-disable-next-line no-new
new
Flash
(
'
An error occurred while fetching the environments.
'
);
this
.
store
.
updateEnvironmentProp
(
folder
,
'
isLoadingFolderContent
'
,
false
);
});
},
postAction
(
endpoint
)
{
if
(
!
this
.
isMakingRequest
)
{
this
.
isLoading
=
true
;
this
.
service
.
postAction
(
endpoint
)
.
then
(()
=>
this
.
fetchEnvironments
())
.
catch
(()
=>
new
Flash
(
'
An error occurred while making the request.
'
));
}
},
successCallback
(
resp
)
{
this
.
saveData
(
resp
);
// We need to verify if any folder is open to also update it
const
openFolders
=
this
.
store
.
getOpenFolders
();
if
(
openFolders
.
length
)
{
openFolders
.
forEach
(
folder
=>
this
.
fetchChildEnvironments
(
folder
));
}
},
errorCallback
()
{
this
.
isLoading
=
false
;
// eslint-disable-next-line no-new
new
Flash
(
'
An error occurred while fetching the environments.
'
);
},
},
};
</
script
>
<
template
>
<div
:class=
"cssContainerClass"
>
<div
class=
"top-area"
>
<ul
v-if=
"!isLoading"
class=
"nav-links"
>
<li
:class=
"
{ active: scope === null || scope === 'available' }">
<a
:href=
"projectEnvironmentsPath"
>
Available
<span
class=
"badge js-available-environments-count"
>
{{
state
.
availableCounter
}}
</span>
</a>
</li>
<li
:class=
"
{ active : scope === 'stopped' }">
<a
:href=
"projectStoppedEnvironmentsPath"
>
Stopped
<span
class=
"badge js-stopped-environments-count"
>
{{
state
.
stoppedCounter
}}
</span>
</a>
</li>
</ul>
<div
v-if=
"canCreateEnvironmentParsed && !isLoading"
class=
"nav-controls"
>
<a
:href=
"newEnvironmentPath"
class=
"btn btn-create"
>
New environment
</a>
</div>
</div>
<div
class=
"environments-container"
>
<loading-icon
label=
"Loading environments"
size=
"3"
v-if=
"isLoading"
/>
<div
class=
"blank-state-row"
v-if=
"!isLoading && state.environments.length === 0"
>
<div
class=
"blank-state-center"
>
<h2
class=
"blank-state-title js-blank-state-title"
>
You don't have any environments right now.
</h2>
<p
class=
"blank-state-text"
>
Environments are places where code gets deployed, such as staging or production.
<br
/>
<a
:href=
"helpPagePath"
>
Read more about environments
</a>
</p>
<a
v-if=
"canCreateEnvironmentParsed"
:href=
"newEnvironmentPath"
class=
"btn btn-create js-new-environment-button"
>
New environment
</a>
</div>
</div>
<div
class=
"table-holder"
v-if=
"!isLoading && state.environments.length > 0"
>
<environment-table
:environments=
"state.environments"
:can-create-deployment=
"canCreateDeploymentParsed"
:can-read-environment=
"canReadEnvironmentParsed"
/>
</div>
<table-pagination
v-if=
"state.paginationInformation && state.paginationInformation.totalPages > 1"
:change=
"changePage"
:pageInfo=
"state.paginationInformation"
/>
</div>
</div>
</
template
>
app/assets/javascripts/environments/components/environment_external_url.vue
View file @
45631562
<
script
>
import
tooltip
from
'
../../vue_shared/directives/tooltip
'
;
import
{
s__
}
from
'
../../locale
'
;
/**
* Renders the external url link in environments table.
...
...
@@ -18,7 +19,7 @@ export default {
computed
:
{
title
()
{
return
'
Open
'
;
return
s__
(
'
Environments|Open
'
)
;
},
},
};
...
...
app/assets/javascripts/environments/components/environment_item.vue
View file @
45631562
...
...
@@ -432,7 +432,7 @@ export default {
v-if=
"!model.isFolder"
class=
"table-mobile-header"
role=
"rowheader"
>
Environment
{{
s__
(
"
Environments|Environment
"
)
}}
</div>
<a
v-if=
"!model.isFolder"
...
...
@@ -505,7 +505,7 @@ export default {
<div
role=
"rowheader"
class=
"table-mobile-header"
>
Commit
{{
s__
(
"
Environments|Commit
"
)
}}
</div>
<div
v-if=
"hasLastDeploymentKey"
...
...
@@ -521,7 +521,7 @@ export default {
<div
v-if=
"!hasLastDeploymentKey"
class=
"commit-title table-mobile-content"
>
No deployments yet
{{
s__
(
"
Environments|No deployments yet
"
)
}}
</div>
</div>
...
...
@@ -531,7 +531,7 @@ export default {
<div
role=
"rowheader"
class=
"table-mobile-header"
>
Updated
{{
s__
(
"
Environments|Updated
"
)
}}
</div>
<span
v-if=
"canShowDate"
...
...
app/assets/javascripts/environments/components/environment_monitoring.vue
View file @
45631562
...
...
@@ -34,6 +34,7 @@ export default {
:aria-label=
"title"
>
<i
class=
"fa fa-area-chart"
aria-hidden=
"true"
/>
aria-hidden=
"true"
/>
</a>
</
template
>
app/assets/javascripts/environments/components/environment_rollback.vue
View file @
45631562
...
...
@@ -48,10 +48,10 @@ export default {
:disabled=
"isLoading"
>
<span
v-if=
"isLastDeployment"
>
Re-deploy
{{
s__
(
"
Environments|Re-deploy
"
)
}}
</span>
<span
v-else
>
Rollback
{{
s__
(
"
Environments|Rollback
"
)
}}
</span>
<loading-icon
v-if=
"isLoading"
/>
...
...
app/assets/javascripts/environments/components/environments_app.vue
0 → 100644
View file @
45631562
<
script
>
import
Flash
from
'
../../flash
'
;
import
{
s__
}
from
'
../../locale
'
;
import
emptyState
from
'
./empty_state.vue
'
;
import
eventHub
from
'
../event_hub
'
;
import
environmentsMixin
from
'
../mixins/environments_mixin
'
;
import
CIPaginationMixin
from
'
../../vue_shared/mixins/ci_pagination_api_mixin
'
;
export
default
{
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
canCreateEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
canCreateDeployment
:
{
type
:
Boolean
,
required
:
true
,
},
canReadEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
cssContainerClass
:
{
type
:
String
,
required
:
true
,
},
newEnvironmentPath
:
{
type
:
String
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
true
,
},
},
components
:
{
emptyState
,
},
mixins
:
[
CIPaginationMixin
,
environmentsMixin
,
],
created
()
{
eventHub
.
$on
(
'
toggleFolder
'
,
this
.
toggleFolder
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
toggleFolder
'
);
},
methods
:
{
toggleFolder
(
folder
)
{
this
.
store
.
toggleFolder
(
folder
);
if
(
!
folder
.
isOpen
)
{
this
.
fetchChildEnvironments
(
folder
,
true
);
}
},
fetchChildEnvironments
(
folder
,
showLoader
=
false
)
{
this
.
store
.
updateEnvironmentProp
(
folder
,
'
isLoadingFolderContent
'
,
showLoader
);
this
.
service
.
getFolderContent
(
folder
.
folder_path
)
.
then
(
resp
=>
resp
.
json
())
.
then
(
response
=>
this
.
store
.
setfolderContent
(
folder
,
response
.
environments
))
.
then
(()
=>
this
.
store
.
updateEnvironmentProp
(
folder
,
'
isLoadingFolderContent
'
,
false
))
.
catch
(()
=>
{
Flash
(
s__
(
'
Environments|An error occurred while fetching the environments.
'
));
this
.
store
.
updateEnvironmentProp
(
folder
,
'
isLoadingFolderContent
'
,
false
);
});
},
successCallback
(
resp
)
{
this
.
saveData
(
resp
);
// We need to verify if any folder is open to also update it
const
openFolders
=
this
.
store
.
getOpenFolders
();
if
(
openFolders
.
length
)
{
openFolders
.
forEach
(
folder
=>
this
.
fetchChildEnvironments
(
folder
));
}
},
},
};
</
script
>
<
template
>
<div
:class=
"cssContainerClass"
>
<div
class=
"top-area"
>
<tabs
:tabs=
"tabs"
@
onChangeTab=
"onChangeTab"
scope=
"environments"
/>
<div
v-if=
"canCreateEnvironment && !isLoading"
class=
"nav-controls"
>
<a
:href=
"newEnvironmentPath"
class=
"btn btn-create"
>
{{
s__
(
"
Environments|New environment
"
)
}}
</a>
</div>
</div>
<container
:is-loading=
"isLoading"
:environments=
"state.environments"
:pagination=
"state.paginationInformation"
:can-create-deployment=
"canCreateDeployment"
:can-read-environment=
"canReadEnvironment"
@
onChangePage=
"onChangePage"
>
<empty-state
slot=
"emptyState"
v-if=
"!isLoading && state.environments.length === 0"
:new-path=
"newEnvironmentPath"
:help-path=
"helpPagePath"
:can-create-environment=
"canCreateEnvironment"
/>
</container>
</div>
</
template
>
app/assets/javascripts/environments/components/environments_table.vue
View file @
45631562
...
...
@@ -2,12 +2,12 @@
/**
* Render environments table.
*/
import
EnvironmentTableRowComponent
from
'
./environment_item.vue
'
;
import
environmentItem
from
'
./environment_item.vue
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
export
default
{
components
:
{
'
environment-item
'
:
EnvironmentTableRowComponent
,
environmentItem
,
loadingIcon
,
},
...
...
@@ -42,19 +42,19 @@ export default {
<div
class=
"ci-table"
role=
"grid"
>
<div
class=
"gl-responsive-table-row table-row-header"
role=
"row"
>
<div
class=
"table-section section-10 environments-name"
role=
"columnheader"
>
Environment
{{
s__
(
"
Environments|Environment
"
)
}}
</div>
<div
class=
"table-section section-10 environments-deploy"
role=
"columnheader"
>
Deployment
{{
s__
(
"
Environments|Deployment
"
)
}}
</div>
<div
class=
"table-section section-15 environments-build"
role=
"columnheader"
>
Job
{{
s__
(
"
Environments|Job
"
)
}}
</div>
<div
class=
"table-section section-25 environments-commit"
role=
"columnheader"
>
Commit
{{
s__
(
"
Environments|Commit
"
)
}}
</div>
<div
class=
"table-section section-10 environments-date"
role=
"columnheader"
>
Updated
{{
s__
(
"
Environments|Updated
"
)
}}
</div>
</div>
<template
...
...
@@ -86,7 +86,7 @@ export default {
<a
:href=
"folderUrl(model)"
class=
"btn btn-default"
>
Show all
{{
s__
(
"
Environments|Show all
"
)
}}
</a>
</div>
</div>
...
...
app/assets/javascripts/environments/environments_bundle.js
View file @
45631562
import
Vue
from
'
vue
'
;
import
EnvironmentsComponent
from
'
./components/environment.vue
'
;
import
environmentsComponent
from
'
./components/environments_app.vue
'
;
import
{
convertPermissionToBoolean
}
from
'
../lib/utils/common_utils
'
;
import
Translate
from
'
../vue_shared/translate
'
;
Vue
.
use
(
Translate
);
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
new
Vue
({
el
:
'
#environments-list-view
'
,
components
:
{
'
environments-table-app
'
:
EnvironmentsComponent
,
environmentsComponent
,
},
data
()
{
const
environmentsData
=
document
.
querySelector
(
this
.
$options
.
el
).
dataset
;
return
{
endpoint
:
environmentsData
.
environmentsDataEndpoint
,
newEnvironmentPath
:
environmentsData
.
newEnvironmentPath
,
helpPagePath
:
environmentsData
.
helpPagePath
,
cssContainerClass
:
environmentsData
.
cssClass
,
canCreateEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateEnvironment
),
canCreateDeployment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateDeployment
),
canReadEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canReadEnvironment
),
};
},
render
(
createElement
)
{
return
createElement
(
'
environments-component
'
,
{
props
:
{
endpoint
:
this
.
endpoint
,
newEnvironmentPath
:
this
.
newEnvironmentPath
,
helpPagePath
:
this
.
helpPagePath
,
cssContainerClass
:
this
.
cssContainerClass
,
canCreateEnvironment
:
this
.
canCreateEnvironment
,
canCreateDeployment
:
this
.
canCreateDeployment
,
canReadEnvironment
:
this
.
canReadEnvironment
,
},
});
},
render
:
createElement
=>
createElement
(
'
environments-table-app
'
),
}));
app/assets/javascripts/environments/folder/environments_folder_bundle.js
View file @
45631562
import
Vue
from
'
vue
'
;
import
EnvironmentsFolderComponent
from
'
./environments_folder_view.vue
'
;
import
environmentsFolderApp
from
'
./environments_folder_view.vue
'
;
import
{
convertPermissionToBoolean
}
from
'
../../lib/utils/common_utils
'
;
import
Translate
from
'
../../vue_shared/translate
'
;
Vue
.
use
(
Translate
);
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
new
Vue
({
el
:
'
#environments-folder-list-view
'
,
components
:
{
'
environments-folder-app
'
:
EnvironmentsFolderComponent
,
environmentsFolderApp
,
},
data
()
{
const
environmentsData
=
document
.
querySelector
(
this
.
$options
.
el
).
dataset
;
return
{
endpoint
:
environmentsData
.
endpoint
,
folderName
:
environmentsData
.
folderName
,
cssContainerClass
:
environmentsData
.
cssClass
,
canCreateDeployment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateDeployment
),
canReadEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canReadEnvironment
),
};
},
render
(
createElement
)
{
return
createElement
(
'
environments-folder-app
'
,
{
props
:
{
endpoint
:
this
.
endpoint
,
folderName
:
this
.
folderName
,
cssContainerClass
:
this
.
cssContainerClass
,
canCreateDeployment
:
this
.
canCreateDeployment
,
canReadEnvironment
:
this
.
canReadEnvironment
,
},
});
},
render
:
createElement
=>
createElement
(
'
environments-folder-app
'
),
}));
app/assets/javascripts/environments/folder/environments_folder_view.vue
View file @
45631562
<
script
>
import
Visibility
from
'
visibilityjs
'
;
import
Flash
from
'
../../flash
'
;
import
EnvironmentsService
from
'
../services/environments_service
'
;
import
environmentTable
from
'
../components/environments_table.vue
'
;
import
EnvironmentsStore
from
'
../stores/environments_store
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
eventHub
from
'
../event_hub
'
;
import
environmentsMixin
from
'
../mixins/environments_mixin
'
;
import
{
convertPermissionToBoolean
,
getParameterByName
,
setParamInURL
}
from
'
../../lib/utils/common_utils
'
;
import
environmentsMixin
from
'
../mixins/environments_mixin
'
;
import
CIPaginationMixin
from
'
../../vue_shared/mixins/ci_pagination_api_mixin
'
;
export
default
{
component
s
:
{
environmentTable
,
tablePagination
,
loadingIcon
,
export
default
{
prop
s
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
mixins
:
[
environmentsMixin
,
],
data
()
{
const
environmentsData
=
document
.
querySelector
(
'
#environments-folder-list-view
'
).
dataset
;
const
store
=
new
EnvironmentsStore
();
const
pathname
=
window
.
location
.
pathname
;
const
endpoint
=
`
${
pathname
}
.json`
;
const
folderName
=
pathname
.
substr
(
pathname
.
lastIndexOf
(
'
/
'
)
+
1
);
return
{
store
,
folderName
,
endpoint
,
state
:
store
.
state
,
visibility
:
'
available
'
,
isLoading
:
false
,
cssContainerClass
:
environmentsData
.
cssClass
,
canCreateDeployment
:
environmentsData
.
canCreateDeployment
,
canReadEnvironment
:
environmentsData
.
canReadEnvironment
,
// Pagination Properties,
paginationInformation
:
{},
pageNumber
:
1
,
};
folderName
:
{
type
:
String
,
required
:
true
,
},
computed
:
{
scope
()
{
return
getParameterByName
(
'
scope
'
);
},
canReadEnvironmentParsed
()
{
return
convertPermissionToBoolean
(
this
.
canReadEnvironment
);
cssContainerClass
:
{
type
:
String
,
required
:
true
,
},
canCreateDeploymentParsed
()
{
return
convertPermissionToBoolean
(
this
.
canCreateDeployment
);
canCreateDeployment
:
{
type
:
Boolean
,
required
:
true
,
},
/**
* URL to link in the stopped tab.
*
* @return {String}
*/
stoppedPath
()
{
return
`
${
window
.
location
.
pathname
}
?scope=stopped`
;
canReadEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
/**
* URL to link in the available tab.
*
* @return {String}
*/
availablePath
()
{
return
window
.
location
.
pathname
;
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created
()
{
const
scope
=
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
page
=
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
this
.
service
=
new
EnvironmentsService
(
this
.
endpoint
);
const
poll
=
new
Poll
({
resource
:
this
.
service
,
method
:
'
get
'
,
data
:
{
scope
,
page
},
successCallback
:
this
.
successCallback
,
errorCallback
:
this
.
errorCallback
,
notificationCallback
:
(
isMakingRequest
)
=>
{
this
.
isMakingRequest
=
isMakingRequest
;
},
});
if
(
!
Visibility
.
hidden
())
{
this
.
isLoading
=
true
;
poll
.
makeRequest
();
}
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
poll
.
restart
();
}
else
{
poll
.
stop
();
}
});
eventHub
.
$on
(
'
postAction
'
,
this
.
postAction
);
},
beforeDestroyed
()
{
eventHub
.
$off
(
'
postAction
'
);
},
mixins
:
[
environmentsMixin
,
CIPaginationMixin
,
],
methods
:
{
/**
* Will change the page number and update the URL.
*
* @param {Number} pageNumber desired page to go to.
*/
changePage
(
pageNumber
)
{
const
param
=
setParamInURL
(
'
page
'
,
pageNumber
);
gl
.
utils
.
visitUrl
(
param
);
return
param
;
},
fetchEnvironments
()
{
const
scope
=
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
page
=
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
this
.
isLoading
=
true
;
return
this
.
service
.
get
({
scope
,
page
})
.
then
(
this
.
successCallback
)
.
catch
(
this
.
errorCallback
);
},
successCallback
(
resp
)
{
this
.
saveData
(
resp
);
},
errorCallback
()
{
this
.
isLoading
=
false
;
// eslint-disable-next-line no-new
new
Flash
(
'
An error occurred while fetching the environments.
'
);
},
postAction
(
endpoint
)
{
if
(
!
this
.
isMakingRequest
)
{
this
.
isLoading
=
true
;
this
.
service
.
postAction
(
endpoint
)
.
then
(()
=>
this
.
fetchEnvironments
())
.
catch
(()
=>
new
Flash
(
'
An error occurred while making the request.
'
));
}
},
},
};
};
</
script
>
<
template
>
<div
:class=
"cssContainerClass"
>
...
...
@@ -171,56 +43,23 @@ export default {
v-if=
"!isLoading"
>
<h4
class=
"js-folder-name environments-folder-name"
>
Environments
/
<b>
{{
folderName
}}
</b>
{{
s__
(
"
Environments|Environments
"
)
}}
/
<b>
{{
folderName
}}
</b>
</h4>
<ul
class=
"nav-links"
>
<li
:class=
"
{ active: scope === null || scope === 'available' }">
<a
:href=
"availablePath"
class=
"js-available-environments-folder-tab"
>
Available
<span
class=
"badge js-available-environments-count"
>
{{
state
.
availableCounter
}}
</span>
</a>
</li>
<li
:class=
"
{ active : scope === 'stopped' }">
<a
:href=
"stoppedPath"
class=
"js-stopped-environments-folder-tab"
>
Stopped
<span
class=
"badge js-stopped-environments-count"
>
{{
state
.
stoppedCounter
}}
</span>
</a>
</li>
</ul>
</div>
<div
class=
"environments-container"
>
<loading-icon
label=
"Loading environments"
v-if=
"isLoading"
size=
"3"
<tabs
:tabs=
"tabs"
@
onChangeTab=
"onChangeTab"
scope=
"environments"
/>
</div>
<div
class=
"table-holder"
v-if=
"!isLoading && state.environments.length > 0"
>
<environment-table
<container
:is-loading=
"isLoading"
:environments=
"state.environments"
:can-create-deployment=
"canCreateDeploymentParsed"
:can-read-environment=
"canReadEnvironmentParsed"
:pagination=
"state.paginationInformation"
:can-create-deployment=
"canCreateDeployment"
:can-read-environment=
"canReadEnvironment"
@
onChangePage=
"onChangePage"
/>
<table-pagination
v-if=
"state.paginationInformation && state.paginationInformation.totalPages > 1"
:change=
"changePage"
:pageInfo=
"state.paginationInformation"
/>
</div>
</div>
</div>
</
template
>
app/assets/javascripts/environments/mixins/environments_mixin.js
View file @
45631562
/**
* Common code between environmets app and folder view
*/
import
Visibility
from
'
visibilityjs
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
{
getParameterByName
,
parseQueryStringIntoObject
,
}
from
'
../../lib/utils/common_utils
'
;
import
{
s__
}
from
'
../../locale
'
;
import
Flash
from
'
../../flash
'
;
import
eventHub
from
'
../event_hub
'
;
import
EnvironmentsStore
from
'
../stores/environments_store
'
;
import
EnvironmentsService
from
'
../services/environments_service
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
environmentTable
from
'
../components/environments_table.vue
'
;
import
tabs
from
'
../../vue_shared/components/navigation_tabs.vue
'
;
import
container
from
'
../components/container.vue
'
;
export
default
{
components
:
{
environmentTable
,
container
,
loadingIcon
,
tabs
,
tablePagination
,
},
data
()
{
const
store
=
new
EnvironmentsStore
();
return
{
store
,
state
:
store
.
state
,
isLoading
:
false
,
isMakingRequest
:
false
,
scope
:
getParameterByName
(
'
scope
'
)
||
'
available
'
,
page
:
getParameterByName
(
'
page
'
)
||
'
1
'
,
requestData
:
{},
};
},
methods
:
{
saveData
(
resp
)
{
const
headers
=
resp
.
headers
;
return
resp
.
json
().
then
((
response
)
=>
{
this
.
isLoading
=
false
;
if
(
_
.
isEqual
(
parseQueryStringIntoObject
(
resp
.
url
.
split
(
'
?
'
)[
1
]),
this
.
requestData
))
{
this
.
store
.
storeAvailableCount
(
response
.
available_count
);
this
.
store
.
storeStoppedCount
(
response
.
stopped_count
);
this
.
store
.
storeEnvironments
(
response
.
environments
);
this
.
store
.
setPagination
(
headers
);
}
});
},
/**
* 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
)
{
this
.
updateInternalState
(
parameters
);
// fetch new data
return
this
.
service
.
get
(
this
.
requestData
)
.
then
(
response
=>
this
.
successCallback
(
response
))
.
then
(()
=>
{
// restart polling
this
.
poll
.
restart
({
data
:
this
.
requestData
});
})
.
catch
(()
=>
{
this
.
errorCallback
();
// restart polling
this
.
poll
.
restart
();
});
},
errorCallback
()
{
this
.
isLoading
=
false
;
Flash
(
s__
(
'
Environments|An error occurred while fetching the environments.
'
));
},
postAction
(
endpoint
)
{
if
(
!
this
.
isMakingRequest
)
{
this
.
isLoading
=
true
;
this
.
service
.
postAction
(
endpoint
)
.
then
(()
=>
this
.
fetchEnvironments
())
.
catch
(()
=>
{
this
.
isLoading
=
false
;
Flash
(
s__
(
'
Environments|An error occurred while making the request.
'
));
});
}
},
fetchEnvironments
()
{
this
.
isLoading
=
true
;
return
this
.
service
.
get
(
this
.
requestData
)
.
then
(
this
.
successCallback
)
.
catch
(
this
.
errorCallback
);
},
},
computed
:
{
tabs
()
{
return
[
{
name
:
s__
(
'
Available
'
),
scope
:
'
available
'
,
count
:
this
.
state
.
availableCounter
,
isActive
:
this
.
scope
===
'
available
'
,
},
{
name
:
s__
(
'
Stopped
'
),
scope
:
'
stopped
'
,
count
:
this
.
state
.
stoppedCounter
,
isActive
:
this
.
scope
===
'
stopped
'
,
},
];
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created
()
{
this
.
service
=
new
EnvironmentsService
(
this
.
endpoint
);
this
.
requestData
=
{
page
:
this
.
page
,
scope
:
this
.
scope
};
this
.
poll
=
new
Poll
({
resource
:
this
.
service
,
method
:
'
get
'
,
data
:
this
.
requestData
,
successCallback
:
this
.
successCallback
,
errorCallback
:
this
.
errorCallback
,
notificationCallback
:
(
isMakingRequest
)
=>
{
this
.
isMakingRequest
=
isMakingRequest
;
},
});
if
(
!
Visibility
.
hidden
())
{
this
.
isLoading
=
true
;
this
.
poll
.
makeRequest
();
}
else
{
this
.
fetchEnvironments
();
}
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
restart
();
}
else
{
this
.
poll
.
stop
();
}
});
eventHub
.
$on
(
'
postAction
'
,
this
.
postAction
);
},
beforeDestroyed
()
{
eventHub
.
$off
(
'
postAction
'
);
},
};
app/assets/javascripts/environments/stores/environments_store.js
View file @
45631562
...
...
@@ -36,7 +36,12 @@ export default class EnvironmentsStore {
storeEnvironments
(
environments
=
[])
{
const
filteredEnvironments
=
environments
.
map
((
env
)
=>
{
const
oldEnvironmentState
=
this
.
state
.
environments
.
find
(
element
=>
element
.
id
===
env
.
latest
.
id
)
||
{};
.
find
((
element
)
=>
{
if
(
env
.
latest
)
{
return
element
.
id
===
env
.
latest
.
id
;
}
return
element
.
id
===
env
.
id
;
})
||
{};
let
filtered
=
{};
...
...
app/assets/javascripts/lib/utils/common_utils.js
View file @
45631562
...
...
@@ -269,46 +269,6 @@ export const parseIntPagination = paginationInformation => ({
previousPage
:
parseInt
(
paginationInformation
[
'
X-PREV-PAGE
'
],
10
),
});
/**
* Updates the search parameter of a URL given the parameter and value provided.
*
* If no search params are present we'll add it.
* If param for page is already present, we'll update it
* If there are params but not for the given one, we'll add it at the end.
* Returns the new search parameters.
*
* @param {String} param
* @param {Number|String|Undefined|Null} value
* @return {String}
*/
export
const
setParamInURL
=
(
param
,
value
)
=>
{
let
search
;
const
locationSearch
=
window
.
location
.
search
;
if
(
locationSearch
.
length
)
{
const
parameters
=
locationSearch
.
substring
(
1
,
locationSearch
.
length
)
.
split
(
'
&
'
)
.
reduce
((
acc
,
element
)
=>
{
const
val
=
element
.
split
(
'
=
'
);
// eslint-disable-next-line no-param-reassign
acc
[
val
[
0
]]
=
decodeURIComponent
(
val
[
1
]);
return
acc
;
},
{});
parameters
[
param
]
=
value
;
const
toString
=
Object
.
keys
(
parameters
)
.
map
(
val
=>
`
${
val
}
=
${
encodeURIComponent
(
parameters
[
val
])}
`
)
.
join
(
'
&
'
);
search
=
`?
${
toString
}
`
;
}
else
{
search
=
`?
${
param
}
=
${
value
}
`
;
}
return
search
;
};
/**
* Given a string of query parameters creates an object.
*
...
...
app/assets/javascripts/pipelines/components/pipelines.vue
View file @
45631562
...
...
@@ -3,15 +3,14 @@
import
PipelinesService
from
'
../services/pipelines_service
'
;
import
pipelinesMixin
from
'
../mixins/pipelines
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
navigationTabs
from
'
./navigation_tabs.vue
'
;
import
navigationTabs
from
'
.
./../vue_shared/components
/navigation_tabs.vue
'
;
import
navigationControls
from
'
./nav_controls.vue
'
;
import
{
convertPermissionToBoolean
,
getParameterByName
,
historyPushState
,
buildUrlWithCurrentLocation
,
parseQueryStringIntoObject
,
}
from
'
../../lib/utils/common_utils
'
;
import
CIPaginationMixin
from
'
../../vue_shared/mixins/ci_pagination_api_mixin
'
;
export
default
{
props
:
{
...
...
@@ -36,6 +35,7 @@
},
mixins
:
[
pipelinesMixin
,
CIPaginationMixin
,
],
data
()
{
const
pipelinesData
=
document
.
querySelector
(
'
#pipelines-list-vue
'
).
dataset
;
...
...
@@ -170,22 +170,8 @@
* - Update the internal state
*/
updateContent
(
parameters
)
{
// stop polling
this
.
poll
.
stop
();
this
.
updateInternalState
(
parameters
);
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
)
=>
{
...
...
@@ -203,14 +189,6 @@
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
>
...
...
@@ -235,6 +213,7 @@
<navigation-tabs
:tabs=
"tabs"
@
onChangeTab=
"onChangeTab"
scope=
"pipelines"
/>
<navigation-controls
...
...
app/assets/javascripts/
pipelines
/components/navigation_tabs.vue
→
app/assets/javascripts/
vue_shared
/components/navigation_tabs.vue
View file @
45631562
<
script
>
/**
* Given an array of tabs, renders non linked bootstrap tabs.
* When a tab is clicked it will trigger an event and provide the clicked scope.
*
* This component is used in apps that handle the API call.
* If you only need to change the URL this component should not be used.
*
* @example
* <navigation-tabs
* :tabs="[
* {
* name: String,
* scope: String,
* count: Number || Undefined,
* isActive: Boolean,
* },
* ]"
* @onChangeTab="onChangeTab"
* />
*/
export
default
{
name
:
'
Pipeline
NavigationTabs
'
,
name
:
'
NavigationTabs
'
,
props
:
{
tabs
:
{
type
:
Array
,
required
:
true
,
},
scope
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
mounted
()
{
$
(
document
).
trigger
(
'
init.scrolling-tabs
'
);
...
...
@@ -34,7 +59,7 @@
<a
role=
"button"
@
click=
"onTabClick(tab)"
:class=
"`js-
pipelines
-tab-$
{tab.scope}`"
:class=
"`js-
$
{scope}
-tab-${tab.scope}`"
>
{{
tab
.
name
}}
...
...
app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
0 → 100644
View file @
45631562
/**
* API callbacks for pagination and tabs
* shared between Pipelines and Environments table.
*
* Components need to have `scope`, `page` and `requestData`
*/
import
{
historyPushState
,
buildUrlWithCurrentLocation
,
}
from
'
../../lib/utils/common_utils
'
;
export
default
{
methods
:
{
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
()
});
},
updateInternalState
(
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
;
},
},
};
app/controllers/projects/environments_controller.rb
View file @
45631562
...
...
@@ -34,6 +34,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
folder_environments
=
project
.
environments
.
where
(
environment_type:
params
[
:id
])
@environments
=
folder_environments
.
with_state
(
params
[
:scope
]
||
:available
)
.
order
(
:name
)
@folder
=
params
[
:id
]
respond_to
do
|
format
|
format
.
html
...
...
app/views/projects/environments/folder.html.haml
View file @
45631562
...
...
@@ -5,6 +5,8 @@
=
page_specific_javascript_bundle_tag
(
'common_vue'
)
=
page_specific_javascript_bundle_tag
(
"environments_folder"
)
#environments-folder-list-view
{
data:
{
"can-create-deployment"
=>
can?
(
current_user
,
:create_deployment
,
@project
).
to_s
,
#environments-folder-list-view
{
data:
{
endpoint:
folder_project_environments_path
(
@project
,
@folder
,
format: :json
),
"folder-name"
=>
@folder
,
"can-create-deployment"
=>
can?
(
current_user
,
:create_deployment
,
@project
).
to_s
,
"can-read-environment"
=>
can?
(
current_user
,
:read_environment
,
@project
).
to_s
,
"css-class"
=>
container_class
}
}
app/views/projects/environments/index.html.haml
View file @
45631562
...
...
@@ -3,15 +3,13 @@
-
add_to_breadcrumbs
(
"Pipelines"
,
project_pipelines_path
(
@project
))
-
content_for
:page_specific_javascripts
do
=
page_specific_javascript_bundle_tag
(
'common_vue'
)
=
page_specific_javascript_bundle_tag
(
"common_vue"
)
=
page_specific_javascript_bundle_tag
(
"environments"
)
#environments-list-view
{
data:
{
environments_data:
environments_list_data
,
"can-create-deployment"
=>
can?
(
current_user
,
:create_deployment
,
@project
).
to_s
,
"can-read-environment"
=>
can?
(
current_user
,
:read_environment
,
@project
).
to_s
,
"can-create-environment"
=>
can?
(
current_user
,
:create_environment
,
@project
).
to_s
,
"project-environments-path"
=>
project_environments_path
(
@project
),
"project-stopped-environments-path"
=>
project_environments_path
(
@project
,
scope: :stopped
),
"new-environment-path"
=>
new_project_environment_path
(
@project
),
"help-page-path"
=>
help_page_path
(
"ci/environments"
),
"css-class"
=>
container_class
}
}
spec/features/projects/environments/environments_spec.rb
View file @
45631562
...
...
@@ -14,8 +14,10 @@ feature 'Environments page', :js do
it
'shows "Available" and "Stopped" tab with links'
do
visit_environments
(
project
)
expect
(
page
).
to
have_link
(
'Available'
)
expect
(
page
).
to
have_link
(
'Stopped'
)
expect
(
page
).
to
have_selector
(
'.js-environments-tab-available'
)
expect
(
page
).
to
have_content
(
'Available'
)
expect
(
page
).
to
have_selector
(
'.js-environments-tab-stopped'
)
expect
(
page
).
to
have_content
(
'Stopped'
)
end
describe
'with one available environment'
do
...
...
@@ -75,8 +77,8 @@ feature 'Environments page', :js do
it
'does not show environments and counters are set to zero'
do
expect
(
page
).
to
have_content
(
'You don\'t have any environments right now.'
)
expect
(
page
.
find
(
'.js-
available-environments-count
'
).
text
).
to
eq
(
'0'
)
expect
(
page
.
find
(
'.js-
stopped-environments-count
'
).
text
).
to
eq
(
'0'
)
expect
(
page
.
find
(
'.js-
environments-tab-available .badge
'
).
text
).
to
eq
(
'0'
)
expect
(
page
.
find
(
'.js-
environments-tab-stopped .badge
'
).
text
).
to
eq
(
'0'
)
end
end
...
...
@@ -93,8 +95,8 @@ feature 'Environments page', :js do
it
'shows environments names and counters'
do
expect
(
page
).
to
have_link
(
environment
.
name
)
expect
(
page
.
find
(
'.js-
available-environments-count
'
).
text
).
to
eq
(
'1'
)
expect
(
page
.
find
(
'.js-
stopped-environments-count
'
).
text
).
to
eq
(
'0'
)
expect
(
page
.
find
(
'.js-
environments-tab-available .badge
'
).
text
).
to
eq
(
'1'
)
expect
(
page
.
find
(
'.js-
environments-tab-stopped .badge
'
).
text
).
to
eq
(
'0'
)
end
it
'does not show deployments'
do
...
...
@@ -294,11 +296,32 @@ feature 'Environments page', :js do
end
end
describe
'environments folders view'
do
before
do
create
(
:environment
,
project:
project
,
name:
'staging.review/review-1'
,
state: :available
)
create
(
:environment
,
project:
project
,
name:
'staging.review/review-2'
,
state: :available
)
end
scenario
'user opens folder view'
do
visit
folder_project_environments_path
(
project
,
'staging.review'
)
wait_for_requests
expect
(
page
).
to
have_content
(
'Environments / staging.review'
)
expect
(
page
).
to
have_content
(
'review-1'
)
expect
(
page
).
to
have_content
(
'review-2'
)
end
end
def
have_terminal_button
have_link
(
nil
,
href:
terminal_project_environment_path
(
project
,
environment
))
end
def
visit_environments
(
project
,
**
opts
)
visit
project_environments_path
(
project
,
**
opts
)
wait_for_requests
end
end
spec/javascripts/environments/emtpy_state_spec.js
0 → 100644
View file @
45631562
import
Vue
from
'
vue
'
;
import
emptyState
from
'
~/environments/components/empty_state.vue
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
describe
(
'
environments empty state
'
,
()
=>
{
let
vm
;
let
Component
;
beforeEach
(()
=>
{
Component
=
Vue
.
extend
(
emptyState
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
With permissions
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Component
,
{
newPath
:
'
foo
'
,
canCreateEnvironment
:
true
,
helpPath
:
'
bar
'
,
});
});
it
(
'
renders empty state and new environment button
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-blank-state-title
'
).
textContent
.
trim
(),
).
toEqual
(
'
You don
\'
t have any environments right now.
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-new-environment-button
'
).
getAttribute
(
'
href
'
),
).
toEqual
(
'
foo
'
);
});
});
describe
(
'
Without permission
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Component
,
{
newPath
:
'
foo
'
,
canCreateEnvironment
:
false
,
helpPath
:
'
bar
'
,
});
});
it
(
'
renders empty state without new button
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-blank-state-title
'
).
textContent
.
trim
(),
).
toEqual
(
'
You don
\'
t have any environments right now.
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-new-environment-button
'
),
).
toBeNull
();
});
});
});
spec/javascripts/environments/environment_table_spec.js
View file @
45631562
import
Vue
from
'
vue
'
;
import
environmentTableComp
from
'
~/environments/components/environments_table.vue
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
describe
(
'
Environment table
'
,
()
=>
{
let
Component
;
let
vm
;
describe
(
'
Environment item
'
,
()
=>
{
preloadFixtures
(
'
static/environments/element.html.raw
'
);
beforeEach
(()
=>
{
loadFixtures
(
'
static/environments/element.html.raw
'
);
Component
=
Vue
.
extend
(
environmentTableComp
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
Should render a table
'
,
()
=>
{
...
...
@@ -17,18 +24,12 @@ describe('Environment item', () => {
},
};
const
EnvironmentTable
=
Vue
.
extend
(
environmentTableComp
);
const
component
=
new
EnvironmentTable
({
el
:
document
.
querySelector
(
'
.test-dom-element
'
),
propsData
:
{
environments
:
[{
mockItem
}],
vm
=
mountComponent
(
Component
,
{
environments
:
[
mockItem
],
canCreateDeployment
:
false
,
canReadEnvironment
:
true
,
service
:
{},
},
}).
$mount
();
});
expect
(
component
.
$el
.
getAttribute
(
'
class
'
)).
toContain
(
'
ci-table
'
);
expect
(
vm
.
$el
.
getAttribute
(
'
class
'
)).
toContain
(
'
ci-table
'
);
});
});
spec/javascripts/environments/environment_spec.js
→
spec/javascripts/environments/environment
s_app
_spec.js
View file @
45631562
import
Vue
from
'
vue
'
;
import
'
~/flash
'
;
import
environmentsComponent
from
'
~/environments/components/environment.vue
'
;
import
environmentsComponent
from
'
~/environments/components/environments_app.vue
'
;
import
{
environment
,
folder
}
from
'
./mock_data
'
;
import
{
headersInterceptor
}
from
'
../helpers/vue_resource_helper
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
describe
(
'
Environment
'
,
()
=>
{
preloadFixtures
(
'
static/environments/environments.html.raw
'
);
const
mockData
=
{
endpoint
:
'
environments.json
'
,
canCreateEnvironment
:
true
,
canCreateDeployment
:
true
,
canReadEnvironment
:
true
,
cssContainerClass
:
'
container
'
,
newEnvironmentPath
:
'
environments/new
'
,
helpPagePath
:
'
help
'
,
};
let
EnvironmentsComponent
;
let
component
;
beforeEach
(()
=>
{
loadFixtures
(
'
static/environments/environments.html.raw
'
);
EnvironmentsComponent
=
Vue
.
extend
(
environmentsComponent
);
});
...
...
@@ -37,9 +43,7 @@ describe('Environment', () => {
});
it
(
'
should render the empty state
'
,
(
done
)
=>
{
component
=
new
EnvironmentsComponent
({
el
:
document
.
querySelector
(
'
#environments-list-view
'
),
});
component
=
mountComponent
(
EnvironmentsComponent
,
mockData
);
setTimeout
(()
=>
{
expect
(
...
...
@@ -81,9 +85,7 @@ describe('Environment', () => {
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
environmentsResponseInterceptor
);
Vue
.
http
.
interceptors
.
push
(
headersInterceptor
);
component
=
new
EnvironmentsComponent
({
el
:
document
.
querySelector
(
'
#environments-list-view
'
),
});
component
=
mountComponent
(
EnvironmentsComponent
,
mockData
);
});
afterEach
(()
=>
{
...
...
@@ -95,7 +97,7 @@ describe('Environment', () => {
it
(
'
should render a table with environments
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
component
.
$el
.
querySelectorAll
(
'
table
'
)).
toBeDefined
();
expect
(
component
.
$el
.
querySelectorAll
(
'
table
'
)).
not
.
toBeNull
();
expect
(
component
.
$el
.
querySelector
(
'
.environment-name
'
).
textContent
.
trim
(),
).
toEqual
(
environment
.
name
);
...
...
@@ -104,10 +106,6 @@ describe('Environment', () => {
});
describe
(
'
pagination
'
,
()
=>
{
afterEach
(()
=>
{
window
.
history
.
pushState
({},
null
,
''
);
});
it
(
'
should render pagination
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
...
...
@@ -117,46 +115,23 @@ describe('Environment', () => {
},
0
);
});
it
(
'
should
update url when no search params are present
'
,
(
done
)
=>
{
spyOn
(
gl
.
utils
,
'
visitUrl
'
);
it
(
'
should
make an API request when page is clicked
'
,
(
done
)
=>
{
spyOn
(
component
,
'
updateContent
'
);
setTimeout
(()
=>
{
component
.
$el
.
querySelector
(
'
.gl-pagination li:nth-child(5) a
'
).
click
();
expect
(
gl
.
utils
.
visitUrl
).
toHaveBeenCalledWith
(
'
?page=2
'
);
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
'
available
'
,
page
:
'
2
'
}
);
done
();
},
0
);
});
it
(
'
should update url when page is already present
'
,
(
done
)
=>
{
spyOn
(
gl
.
utils
,
'
visitUrl
'
);
window
.
history
.
pushState
({},
null
,
'
?page=1
'
);
it
(
'
should make an API request when using tabs
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
component
.
$el
.
querySelector
(
'
.gl-pagination li:nth-child(5) a
'
).
click
();
expect
(
gl
.
utils
.
visitUrl
).
toHaveBeenCalledWith
(
'
?page=2
'
);
done
();
},
0
);
});
it
(
'
should update url when page and scope are already present
'
,
(
done
)
=>
{
spyOn
(
gl
.
utils
,
'
visitUrl
'
);
window
.
history
.
pushState
({},
null
,
'
?scope=all&page=1
'
);
spyOn
(
component
,
'
updateContent
'
);
component
.
$el
.
querySelector
(
'
.js-environments-tab-stopped
'
).
click
();
setTimeout
(()
=>
{
component
.
$el
.
querySelector
(
'
.gl-pagination li:nth-child(5) a
'
).
click
();
expect
(
gl
.
utils
.
visitUrl
).
toHaveBeenCalledWith
(
'
?scope=all&page=2
'
);
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
'
stopped
'
,
page
:
'
1
'
});
done
();
},
0
);
});
it
(
'
should update url when page and scope are already present and page is first param
'
,
(
done
)
=>
{
spyOn
(
gl
.
utils
,
'
visitUrl
'
);
window
.
history
.
pushState
({},
null
,
'
?page=1&scope=all
'
);
setTimeout
(()
=>
{
component
.
$el
.
querySelector
(
'
.gl-pagination li:nth-child(5) a
'
).
click
();
expect
(
gl
.
utils
.
visitUrl
).
toHaveBeenCalledWith
(
'
?page=2&scope=all
'
);
done
();
},
0
);
});
});
});
...
...
@@ -180,9 +155,7 @@ describe('Environment', () => {
});
it
(
'
should render empty state
'
,
(
done
)
=>
{
component
=
new
EnvironmentsComponent
({
el
:
document
.
querySelector
(
'
#environments-list-view
'
),
});
component
=
mountComponent
(
EnvironmentsComponent
,
mockData
);
setTimeout
(()
=>
{
expect
(
...
...
@@ -214,9 +187,7 @@ describe('Environment', () => {
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
environmentsResponseInterceptor
);
component
=
new
EnvironmentsComponent
({
el
:
document
.
querySelector
(
'
#environments-list-view
'
),
});
component
=
mountComponent
(
EnvironmentsComponent
,
mockData
);
});
afterEach
(()
=>
{
...
...
@@ -289,4 +260,59 @@ describe('Environment', () => {
});
});
});
describe
(
'
methods
'
,
()
=>
{
const
environmentsEmptyResponseInterceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
200
,
}));
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
environmentsEmptyResponseInterceptor
);
Vue
.
http
.
interceptors
.
push
(
headersInterceptor
);
component
=
mountComponent
(
EnvironmentsComponent
,
mockData
);
spyOn
(
history
,
'
pushState
'
).
and
.
stub
();
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
environmentsEmptyResponseInterceptor
,
);
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
headersInterceptor
);
});
describe
(
'
updateContent
'
,
()
=>
{
it
(
'
should set given parameters
'
,
(
done
)
=>
{
component
.
updateContent
({
scope
:
'
stopped
'
,
page
:
'
3
'
})
.
then
(()
=>
{
expect
(
component
.
page
).
toEqual
(
'
3
'
);
expect
(
component
.
scope
).
toEqual
(
'
stopped
'
);
expect
(
component
.
requestData
.
scope
).
toEqual
(
'
stopped
'
);
expect
(
component
.
requestData
.
page
).
toEqual
(
'
3
'
);
done
();
})
.
catch
(
done
.
fail
);
});
});
describe
(
'
onChangeTab
'
,
()
=>
{
it
(
'
should set page to 1
'
,
()
=>
{
spyOn
(
component
,
'
updateContent
'
);
component
.
onChangeTab
(
'
stopped
'
);
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
'
stopped
'
,
page
:
'
1
'
});
});
});
describe
(
'
onChangePage
'
,
()
=>
{
it
(
'
should update page and keep scope
'
,
()
=>
{
spyOn
(
component
,
'
updateContent
'
);
component
.
onChangePage
(
4
);
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
component
.
scope
,
page
:
'
4
'
});
});
});
});
});
spec/javascripts/environments/folder/environments_folder_view_spec.js
View file @
45631562
This diff is collapsed.
Click to expand it.
spec/javascripts/fixtures/environments/element.html.haml
deleted
100644 → 0
View file @
1d8ab59e
.test-dom-element
spec/javascripts/fixtures/environments/environments.html.haml
deleted
100644 → 0
View file @
1d8ab59e
%div
#environments-list-view
{
data:
{
environments_data:
"foo/environments"
,
"can-create-deployment"
=>
"true"
,
"can-read-environment"
=>
"true"
,
"can-create-environment"
=>
"true"
,
"project-environments-path"
=>
"https://gitlab.com/foo/environments"
,
"project-stopped-environments-path"
=>
"https://gitlab.com/foo/environments?scope=stopped"
,
"new-environment-path"
=>
"https://gitlab.com/foo/environments/new"
,
"help-page-path"
=>
"https://gitlab.com/help_page"
}}
spec/javascripts/fixtures/environments/environments_folder_view.html.haml
deleted
100644 → 0
View file @
1d8ab59e
%div
#environments-folder-list-view
{
data:
{
"can-create-deployment"
=>
"true"
,
"can-read-environment"
=>
"true"
,
"css-class"
=>
""
,
"commit-icon-svg"
=>
custom_icon
(
"icon_commit"
),
"terminal-icon-svg"
=>
custom_icon
(
"icon_terminal"
),
"play-icon-svg"
=>
custom_icon
(
"icon_play"
)
}
}
spec/javascripts/lib/utils/common_utils_spec.js
View file @
45631562
...
...
@@ -142,47 +142,6 @@ describe('common_utils', () => {
});
});
describe
(
'
setParamInURL
'
,
()
=>
{
afterEach
(()
=>
{
window
.
history
.
pushState
({},
null
,
''
);
});
it
(
'
should return the parameter
'
,
()
=>
{
window
.
history
.
replaceState
({},
null
,
''
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
156
)).
toBe
(
'
?page=156
'
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
'
156
'
)).
toBe
(
'
?page=156
'
);
});
it
(
'
should update the existing parameter when its a number
'
,
()
=>
{
window
.
history
.
pushState
({},
null
,
'
?page=15
'
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
16
)).
toBe
(
'
?page=16
'
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
'
16
'
)).
toBe
(
'
?page=16
'
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
true
)).
toBe
(
'
?page=true
'
);
});
it
(
'
should update the existing parameter when its a string
'
,
()
=>
{
window
.
history
.
pushState
({},
null
,
'
?scope=all
'
);
expect
(
commonUtils
.
setParamInURL
(
'
scope
'
,
'
finished
'
)).
toBe
(
'
?scope=finished
'
);
});
it
(
'
should update the existing parameter when more than one parameter exists
'
,
()
=>
{
window
.
history
.
pushState
({},
null
,
'
?scope=all&page=15
'
);
expect
(
commonUtils
.
setParamInURL
(
'
scope
'
,
'
finished
'
)).
toBe
(
'
?scope=finished&page=15
'
);
});
it
(
'
should add a new parameter to the end of the existing ones
'
,
()
=>
{
window
.
history
.
pushState
({},
null
,
'
?scope=all
'
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
16
)).
toBe
(
'
?scope=all&page=16
'
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
'
16
'
)).
toBe
(
'
?scope=all&page=16
'
);
expect
(
commonUtils
.
setParamInURL
(
'
page
'
,
true
)).
toBe
(
'
?scope=all&page=true
'
);
});
});
describe
(
'
historyPushState
'
,
()
=>
{
afterEach
(()
=>
{
window
.
history
.
replaceState
({},
null
,
null
);
...
...
spec/javascripts/pipelines/pipelines_spec.js
View file @
45631562
...
...
@@ -176,6 +176,11 @@ describe('Pipelines', () => {
});
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(()
=>
{
spyOn
(
history
,
'
pushState
'
).
and
.
stub
();
});
describe
(
'
updateContent
'
,
()
=>
{
it
(
'
should set given parameters
'
,
()
=>
{
component
=
mountComponent
(
PipelinesComponent
,
{
...
...
@@ -195,7 +200,6 @@ describe('Pipelines', () => {
component
=
mountComponent
(
PipelinesComponent
,
{
store
:
new
Store
(),
});
spyOn
(
component
,
'
updateContent
'
);
component
.
onChangeTab
(
'
running
'
);
...
...
@@ -209,7 +213,6 @@ describe('Pipelines', () => {
component
=
mountComponent
(
PipelinesComponent
,
{
store
:
new
Store
(),
});
spyOn
(
component
,
'
updateContent
'
);
component
.
onChangePage
(
4
);
...
...
@@ -217,4 +220,5 @@ describe('Pipelines', () => {
expect
(
component
.
updateContent
).
toHaveBeenCalledWith
({
scope
:
component
.
scope
,
page
:
'
4
'
});
});
});
});
});
spec/javascripts/
pipeline
s/navigation_tabs_spec.js
→
spec/javascripts/
vue_shared/component
s/navigation_tabs_spec.js
View file @
45631562
import
Vue
from
'
vue
'
;
import
navigationTabs
from
'
~/
pipelines
/components/navigation_tabs.vue
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
import
navigationTabs
from
'
~/
vue_shared
/components/navigation_tabs.vue
'
;
import
mountComponent
from
'
../
../
helpers/vue_mount_component_helper
'
;
describe
(
'
navigation tabs
pipeline
component
'
,
()
=>
{
describe
(
'
navigation tabs component
'
,
()
=>
{
let
vm
;
let
Component
;
let
data
;
...
...
@@ -29,7 +29,7 @@ describe('navigation tabs pipeline component', () => {
];
Component
=
Vue
.
extend
(
navigationTabs
);
vm
=
mountComponent
(
Component
,
{
tabs
:
data
});
vm
=
mountComponent
(
Component
,
{
tabs
:
data
,
scope
:
'
pipelines
'
});
});
afterEach
(()
=>
{
...
...
@@ -52,4 +52,10 @@ describe('navigation tabs pipeline component', () => {
it
(
'
should not render badge
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-running .badge
'
)).
toEqual
(
null
);
});
it
(
'
should trigger onTabClick
'
,
()
=>
{
spyOn
(
vm
,
'
$emit
'
);
vm
.
$el
.
querySelector
(
'
.js-pipelines-tab-pending
'
).
click
();
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
onChangeTab
'
,
'
pending
'
);
});
});
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