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
8d131eb8
Commit
8d131eb8
authored
Jun 01, 2017
by
Kamil Trzciński
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'zj-realtime-env-list' into 'master'
Realtime env list Closes #31701 See merge request !11333
parents
b92e3d74
696b0395
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
259 additions
and
57 deletions
+259
-57
app/assets/javascripts/environments/components/environment.vue
...ssets/javascripts/environments/components/environment.vue
+69
-26
app/assets/javascripts/environments/folder/environments_folder_view.vue
...ascripts/environments/folder/environments_folder_view.vue
+72
-28
app/assets/javascripts/environments/mixins/environments_mixin.js
...ets/javascripts/environments/mixins/environments_mixin.js
+17
-0
app/assets/javascripts/environments/services/environments_service.js
...javascripts/environments/services/environments_service.js
+2
-1
app/assets/javascripts/environments/stores/environments_store.js
...ets/javascripts/environments/stores/environments_store.js
+6
-0
app/controllers/projects/environments_controller.rb
app/controllers/projects/environments_controller.rb
+2
-0
app/models/deployment.rb
app/models/deployment.rb
+5
-0
app/models/environment.rb
app/models/environment.rb
+16
-0
changelogs/unreleased/zj-realtime-env-list.yml
changelogs/unreleased/zj-realtime-env-list.yml
+4
-0
lib/gitlab/etag_caching/router.rb
lib/gitlab/etag_caching/router.rb
+7
-1
spec/controllers/projects/environments_controller_spec.rb
spec/controllers/projects/environments_controller_spec.rb
+5
-0
spec/javascripts/environments/environments_store_spec.js
spec/javascripts/environments/environments_store_spec.js
+9
-0
spec/lib/gitlab/etag_caching/router_spec.rb
spec/lib/gitlab/etag_caching/router_spec.rb
+11
-0
spec/models/deployment_spec.rb
spec/models/deployment_spec.rb
+13
-0
spec/models/environment_spec.rb
spec/models/environment_spec.rb
+21
-1
No files found.
app/assets/javascripts/environments/components/environment.vue
View file @
8d131eb8
<
script
>
/* global Flash */
import
Visibility
from
'
visibilityjs
'
;
import
EnvironmentsService
from
'
../services/environments_service
'
;
import
environmentTable
from
'
./environments_table.vue
'
;
import
EnvironmentsStore
from
'
../stores/environments_store
'
;
...
...
@@ -7,6 +8,8 @@ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
'
../../lib/utils/common_utils
'
;
import
eventHub
from
'
../event_hub
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
environmentsMixin
from
'
../mixins/environments_mixin
'
;
export
default
{
...
...
@@ -16,6 +19,10 @@ export default {
loadingIcon
,
},
mixins
:
[
environmentsMixin
,
],
data
()
{
const
environmentsData
=
document
.
querySelector
(
'
#environments-list-view
'
).
dataset
;
const
store
=
new
EnvironmentsStore
();
...
...
@@ -35,6 +42,7 @@ export default {
projectStoppedEnvironmentsPath
:
environmentsData
.
projectStoppedEnvironmentsPath
,
newEnvironmentPath
:
environmentsData
.
newEnvironmentPath
,
helpPagePath
:
environmentsData
.
helpPagePath
,
isMakingRequest
:
false
,
// Pagination Properties,
paginationInformation
:
{},
...
...
@@ -65,17 +73,43 @@ export default {
* Toggles loading property.
*/
created
()
{
const
scope
=
gl
.
utils
.
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
page
=
gl
.
utils
.
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
this
.
service
=
new
EnvironmentsService
(
this
.
endpoint
);
this
.
fetchEnvironments
();
const
poll
=
new
Poll
({
resource
:
this
.
service
,
method
:
'
get
'
,
data
:
{
scope
,
page
},
successCallback
:
this
.
successCallback
,
errorCallback
:
this
.
errorCallback
,
notificationCallback
:
(
isMakingRequest
)
=>
{
this
.
isMakingRequest
=
isMakingRequest
;
// We need to verify if any folder is open to also fecth it
this
.
openFolders
=
this
.
store
.
getOpenFolders
();
},
});
if
(
!
Visibility
.
hidden
())
{
this
.
isLoading
=
true
;
poll
.
makeRequest
();
}
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
poll
.
restart
();
}
else
{
poll
.
stop
();
}
});
eventHub
.
$on
(
'
refreshEnvironments
'
,
this
.
fetchEnvironments
);
eventHub
.
$on
(
'
toggleFolder
'
,
this
.
toggleFolder
);
eventHub
.
$on
(
'
postAction
'
,
this
.
postAction
);
},
beforeDestroyed
()
{
eventHub
.
$off
(
'
refreshEnvironments
'
);
eventHub
.
$off
(
'
toggleFolder
'
);
eventHub
.
$off
(
'
postAction
'
);
},
...
...
@@ -104,29 +138,13 @@ export default {
fetchEnvironments
()
{
const
scope
=
gl
.
utils
.
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
page
Number
=
gl
.
utils
.
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
const
page
=
gl
.
utils
.
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
this
.
isLoading
=
true
;
return
this
.
service
.
get
(
scope
,
pageNumber
)
.
then
(
resp
=>
({
headers
:
resp
.
headers
,
body
:
resp
.
json
(),
}))
.
then
((
response
)
=>
{
this
.
store
.
storeAvailableCount
(
response
.
body
.
available_count
);
this
.
store
.
storeStoppedCount
(
response
.
body
.
stopped_count
);
this
.
store
.
storeEnvironments
(
response
.
body
.
environments
);
this
.
store
.
setPagination
(
response
.
headers
);
})
.
then
(()
=>
{
this
.
isLoading
=
false
;
})
.
catch
(()
=>
{
this
.
isLoading
=
false
;
// eslint-disable-next-line no-new
new
Flash
(
'
An error occurred while fetching the environments.
'
);
});
return
this
.
service
.
get
({
scope
,
page
})
.
then
(
this
.
successCallback
)
.
catch
(
this
.
errorCallback
);
},
fetchChildEnvironments
(
folder
,
folderUrl
)
{
...
...
@@ -146,9 +164,34 @@ export default {
},
postAction
(
endpoint
)
{
this
.
service
.
postAction
(
endpoint
)
.
then
(()
=>
this
.
fetchEnvironments
())
.
catch
(()
=>
new
Flash
(
'
An error occured while making the request.
'
));
if
(
!
this
.
isMakingRequest
)
{
this
.
isLoading
=
true
;
this
.
service
.
postAction
(
endpoint
)
.
then
(()
=>
this
.
fetchEnvironments
())
.
catch
(()
=>
new
Flash
(
'
An error occured while making the request.
'
));
}
},
successCallback
(
resp
)
{
this
.
saveData
(
resp
);
// If folders are open while polling we need to open them again
if
(
this
.
openFolders
.
length
)
{
this
.
openFolders
.
map
((
folder
)
=>
{
// TODO - Move this to the backend
const
folderUrl
=
`
${
window
.
location
.
pathname
}
/folders/
${
folder
.
folderName
}
`
;
this
.
store
.
updateFolder
(
folder
,
'
isOpen
'
,
true
);
return
this
.
fetchChildEnvironments
(
folder
,
folderUrl
);
});
}
},
errorCallback
()
{
this
.
isLoading
=
false
;
// eslint-disable-next-line no-new
new
Flash
(
'
An error occurred while fetching the environments.
'
);
},
},
};
...
...
app/assets/javascripts/environments/folder/environments_folder_view.vue
View file @
8d131eb8
<
script
>
/* global Flash */
import
Visibility
from
'
visibilityjs
'
;
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
'
../../lib/utils/common_utils
'
;
import
'
../../vue_shared/vue_resource_interceptor
'
;
export
default
{
components
:
{
...
...
@@ -15,6 +18,10 @@ export default {
loadingIcon
,
},
mixins
:
[
environmentsMixin
,
],
data
()
{
const
environmentsData
=
document
.
querySelector
(
'
#environments-folder-list-view
'
).
dataset
;
const
store
=
new
EnvironmentsStore
();
...
...
@@ -76,33 +83,39 @@ export default {
*/
created
()
{
const
scope
=
gl
.
utils
.
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
pageNumber
=
gl
.
utils
.
getParameterByName
(
'
page
'
)
||
this
.
pageNumber
;
const
endpoint
=
`
${
this
.
endpoint
}
?scope=
${
scope
}
&page=
${
pageNumber
}
`
;
this
.
service
=
new
EnvironmentsService
(
endpoint
);
this
.
isLoading
=
true
;
return
this
.
service
.
get
()
.
then
(
resp
=>
({
headers
:
resp
.
headers
,
body
:
resp
.
json
(),
}))
.
then
((
response
)
=>
{
this
.
store
.
storeAvailableCount
(
response
.
body
.
available_count
);
this
.
store
.
storeStoppedCount
(
response
.
body
.
stopped_count
);
this
.
store
.
storeEnvironments
(
response
.
body
.
environments
);
this
.
store
.
setPagination
(
response
.
headers
);
})
.
then
(()
=>
{
this
.
isLoading
=
false
;
})
.
catch
(()
=>
{
this
.
isLoading
=
false
;
// eslint-disable-next-line no-new
new
Flash
(
'
An error occurred while fetching the environments.
'
,
'
alert
'
);
});
const
page
=
gl
.
utils
.
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
'
);
},
methods
:
{
...
...
@@ -117,6 +130,37 @@ export default {
gl
.
utils
.
visitUrl
(
param
);
return
param
;
},
fetchEnvironments
()
{
const
scope
=
gl
.
utils
.
getParameterByName
(
'
scope
'
)
||
this
.
visibility
;
const
page
=
gl
.
utils
.
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 occured while making the request.
'
));
}
},
},
};
</
script
>
...
...
app/assets/javascripts/environments/mixins/environments_mixin.js
0 → 100644
View file @
8d131eb8
export
default
{
methods
:
{
saveData
(
resp
)
{
const
response
=
{
headers
:
resp
.
headers
,
body
:
resp
.
json
(),
};
this
.
isLoading
=
false
;
this
.
store
.
storeAvailableCount
(
response
.
body
.
available_count
);
this
.
store
.
storeStoppedCount
(
response
.
body
.
stopped_count
);
this
.
store
.
storeEnvironments
(
response
.
body
.
environments
);
this
.
store
.
setPagination
(
response
.
headers
);
},
},
};
app/assets/javascripts/environments/services/environments_service.js
View file @
8d131eb8
...
...
@@ -10,7 +10,8 @@ export default class EnvironmentsService {
this
.
folderResults
=
3
;
}
get
(
scope
,
page
)
{
get
(
options
=
{})
{
const
{
scope
,
page
}
=
options
;
return
this
.
environments
.
get
({
scope
,
page
});
}
...
...
app/assets/javascripts/environments/stores/environments_store.js
View file @
8d131eb8
...
...
@@ -153,4 +153,10 @@ export default class EnvironmentsStore {
return
updatedEnvironments
;
}
getOpenFolders
()
{
const
environments
=
this
.
state
.
environments
;
return
environments
.
filter
(
env
=>
env
.
isFolder
&&
env
.
isOpen
);
}
}
app/controllers/projects/environments_controller.rb
View file @
8d131eb8
...
...
@@ -15,6 +15,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to
do
|
format
|
format
.
html
format
.
json
do
Gitlab
::
PollingInterval
.
set_header
(
response
,
interval:
3_000
)
render
json:
{
environments:
EnvironmentSerializer
.
new
(
project:
@project
,
current_user:
@current_user
)
...
...
app/models/deployment.rb
View file @
8d131eb8
...
...
@@ -12,6 +12,7 @@ class Deployment < ActiveRecord::Base
delegate
:name
,
to: :environment
,
prefix:
true
after_create
:create_ref
after_create
:invalidate_cache
def
commit
project
.
commit
(
sha
)
...
...
@@ -33,6 +34,10 @@ class Deployment < ActiveRecord::Base
project
.
repository
.
create_ref
(
ref
,
ref_path
)
end
def
invalidate_cache
environment
.
expire_etag_cache
end
def
manual_actions
@manual_actions
||=
deployable
.
try
(
:other_actions
)
end
...
...
app/models/environment.rb
View file @
8d131eb8
...
...
@@ -57,6 +57,10 @@ class Environment < ActiveRecord::Base
state
:available
state
:stopped
after_transition
do
|
environment
|
environment
.
expire_etag_cache
end
end
def
predefined_variables
...
...
@@ -196,6 +200,18 @@ class Environment < ActiveRecord::Base
[
external_url
,
public_path
].
join
(
'/'
)
end
def
expire_etag_cache
Gitlab
::
EtagCaching
::
Store
.
new
.
tap
do
|
store
|
store
.
touch
(
etag_cache_key
)
end
end
def
etag_cache_key
Gitlab
::
Routing
.
url_helpers
.
namespace_project_environments_path
(
project
.
namespace
,
project
)
end
private
# Slugifying a name may remove the uniqueness guarantee afforded by it being
...
...
changelogs/unreleased/zj-realtime-env-list.yml
0 → 100644
View file @
8d131eb8
---
title
:
Make environment table realtime
merge_request
:
11333
author
:
lib/gitlab/etag_caching/router.rb
View file @
8d131eb8
...
...
@@ -9,9 +9,11 @@ module Gitlab
# - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route
USED_IN_ROUTES
=
%w[noteable issue notes issues realtime_changes
commit pipelines merge_requests new]
.
freeze
commit pipelines merge_requests new
environments]
.
freeze
RESERVED_WORDS
=
Gitlab
::
PathRegex
::
ILLEGAL_PROJECT_PATH_WORDS
-
USED_IN_ROUTES
RESERVED_WORDS_REGEX
=
Regexp
.
union
(
*
RESERVED_WORDS
.
map
(
&
Regexp
.
method
(
:escape
)))
ROUTES
=
[
Gitlab
::
EtagCaching
::
Router
::
Route
.
new
(
%r(^(?!.*(
#{
RESERVED_WORDS_REGEX
}
)).*/noteable/issue/
\d
+/notes
\z
)
,
...
...
@@ -40,6 +42,10 @@ module Gitlab
Gitlab
::
EtagCaching
::
Router
::
Route
.
new
(
%r(^(?!.*(
#{
RESERVED_WORDS_REGEX
}
)).*/pipelines/
\d
+
\.
json
\z
)
,
'project_pipeline'
),
Gitlab
::
EtagCaching
::
Router
::
Route
.
new
(
%r(^(?!.*(
#{
RESERVED_WORDS_REGEX
}
)).*/environments
\.
json
\z
)
,
'environments'
)
].
freeze
...
...
spec/controllers/projects/environments_controller_spec.rb
View file @
8d131eb8
...
...
@@ -57,6 +57,11 @@ describe Projects::EnvironmentsController do
expect
(
json_response
[
'available_count'
]).
to
eq
3
expect
(
json_response
[
'stopped_count'
]).
to
eq
1
end
it
'sets the polling interval header'
do
expect
(
response
).
to
have_http_status
(
:ok
)
expect
(
response
.
headers
[
'Poll-Interval'
]).
to
eq
(
"3000"
)
end
end
context
'when requesting stopped environments scope'
do
...
...
spec/javascripts/environments/environments_store_spec.js
View file @
8d131eb8
...
...
@@ -123,4 +123,13 @@ describe('Store', () => {
expect
(
store
.
state
.
paginationInformation
).
toEqual
(
expectedResult
);
});
});
describe
(
'
getOpenFolders
'
,
()
=>
{
it
(
'
should return open folder
'
,
()
=>
{
store
.
storeEnvironments
(
serverData
);
store
.
toggleFolder
(
store
.
state
.
environments
[
1
]);
expect
(
store
.
getOpenFolders
()[
0
]).
toEqual
(
store
.
state
.
environments
[
1
]);
});
});
});
spec/lib/gitlab/etag_caching/router_spec.rb
View file @
8d131eb8
...
...
@@ -77,6 +77,17 @@ describe Gitlab::EtagCaching::Router do
expect
(
result
).
to
be_blank
end
it
'matches the environments path'
do
env
=
build_env
(
'/my-group/my-project/environments.json'
)
result
=
described_class
.
match
(
env
)
expect
(
result
).
to
be_present
expect
(
result
.
name
).
to
eq
'environments'
end
it
'matches pipeline#show endpoint'
do
env
=
build_env
(
'/my-group/my-project/pipelines/2.json'
...
...
spec/models/deployment_spec.rb
View file @
8d131eb8
...
...
@@ -16,6 +16,19 @@ describe Deployment, models: true do
it
{
is_expected
.
to
validate_presence_of
(
:ref
)
}
it
{
is_expected
.
to
validate_presence_of
(
:sha
)
}
describe
'after_create callbacks'
do
let
(
:environment
)
{
create
(
:environment
)
}
let
(
:store
)
{
Gitlab
::
EtagCaching
::
Store
.
new
}
it
'invalidates the environment etag cache'
do
old_value
=
store
.
get
(
environment
.
etag_cache_key
)
create
(
:deployment
,
environment:
environment
)
expect
(
store
.
get
(
environment
.
etag_cache_key
)).
not_to
eq
(
old_value
)
end
end
describe
'#includes_commit?'
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
...
...
spec/models/environment_spec.rb
View file @
8d131eb8
require
'spec_helper'
describe
Environment
,
models:
true
do
l
et
(
:project
)
{
create
(
:empty_project
)
}
s
et
(
:project
)
{
create
(
:empty_project
)
}
subject
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
it
{
is_expected
.
to
belong_to
(
:project
)
}
...
...
@@ -34,6 +34,26 @@ describe Environment, models: true do
end
end
describe
'state machine'
do
it
'invalidates the cache after a change'
do
expect
(
environment
).
to
receive
(
:expire_etag_cache
)
environment
.
stop
end
end
describe
'#expire_etag_cache'
do
let
(
:store
)
{
Gitlab
::
EtagCaching
::
Store
.
new
}
it
'changes the cached value'
do
old_value
=
store
.
get
(
environment
.
etag_cache_key
)
environment
.
stop
expect
(
store
.
get
(
environment
.
etag_cache_key
)).
not_to
eq
(
old_value
)
end
end
describe
'#nullify_external_url'
do
it
'replaces a blank url with nil'
do
env
=
build
(
:environment
,
external_url:
""
)
...
...
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