From 4535d520327206e49e438abe8759ccb134955e54 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 12 May 2017 15:19:27 +0200 Subject: Use etag caching for environments JSON For the index view, the environments can now be requested every 15 seconds. Any transition state of a projects environments will trigger a cache invalidation action. Fixes gitlab-org/gitlab-ce#31701 --- app/controllers/projects/environments_controller.rb | 2 ++ app/models/environment.rb | 12 ++++++++++++ lib/gitlab/etag_caching/router.rb | 7 ++++++- .../projects/environments_controller_spec.rb | 20 ++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index fd57afbd05f..537c74d5231 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -15,6 +15,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController respond_to do |format| format.html format.json do + Gitlab::PollingInterval.set_header(response, interval: 15_000) + render json: { environments: EnvironmentSerializer .new(project: @project, current_user: @current_user) diff --git a/app/models/environment.rb b/app/models/environment.rb index 61efc1b2d17..959129da67f 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -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 @@ -205,4 +209,12 @@ class Environment < ActiveRecord::Base def random_suffix (0..5).map { SUFFIX_CHARS.sample }.join end + + def expire_etag_cache + Gitlab::EtagCaching::Store.new.tap do |store| + store.touch( + Gitlab::Routing.url_helpers.namespace_project_environments_path(project) + ) + end + end end diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb index 31a5b9d108b..174fee5ee24 100644 --- a/lib/gitlab/etag_caching/router.rb +++ b/lib/gitlab/etag_caching/router.rb @@ -9,7 +9,8 @@ module Gitlab # - Ending in `noteable/issue//notes` for the `issue_notes` route # - Ending in `issues/id`/rendered_title` for the `issue_title` route USED_IN_ROUTES = %w[noteable issue notes issues rendered_title - commit pipelines merge_requests new].freeze + commit pipelines merge_requests new + environments].freeze RESERVED_WORDS = DynamicPathValidator::WILDCARD_ROUTES - USED_IN_ROUTES RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS) ROUTES = [ @@ -40,6 +41,10 @@ module Gitlab Gitlab::EtagCaching::Router::Route.new( %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines/\d+\.json\z), 'project_pipeline' + ), + Gitlab::EtagCaching::Router::Route.new( + %r(^(?!.*(#{RESERVED_WORDS})).*/environments\.json\z), + 'environments' ) ].freeze diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index c0f8c36a018..19cea1adbf6 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -76,6 +76,26 @@ describe Projects::EnvironmentsController do expect(json_response['stopped_count']).to eq 1 end end + + context "when using etag caching" do + before do + RequestStore.begin! + end + + after do + RequestStore.end! + RequestStore.clear! + end + + it "limits the queries being executed" do + control_count = ActiveRecord::QueryRecorder.new { get :index, environment_params }.count + + expect do + get :index, environment_params + get :index, environment_params + end.not_to exceed_query_limit(control_count) + end + end end end -- cgit v1.2.1 From 1525853f6fe09e83d897a5380baa923e3b680b50 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 12 May 2017 23:02:41 +0100 Subject: Adds polling function to environments table Adds polling function to folder table Adds missing eventhub to folder table --- .../environments/components/environment.vue | 100 ++++++++++++++------ .../folder/environments_folder_view.vue | 102 +++++++++++++++------ .../environments/services/environments_service.js | 3 +- .../environments/stores/environments_store.js | 6 ++ changelogs/unreleased/zj-realtime-env-list.yml | 4 + spec/javascripts/environments/environment_spec.js | 2 + .../environments/environments_store_spec.js | 9 ++ .../folder/environments_folder_view_spec.js | 1 + 8 files changed, 174 insertions(+), 53 deletions(-) create mode 100644 changelogs/unreleased/zj-realtime-env-list.yml diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index d4e13f3c84a..5fad3fb9742 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -1,5 +1,6 @@ diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js index 8adb53ea86d..03ab74b3338 100644 --- a/app/assets/javascripts/environments/services/environments_service.js +++ b/app/assets/javascripts/environments/services/environments_service.js @@ -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 }); } diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index 158e7922e3c..8a2f6a473de 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -153,4 +153,10 @@ export default class EnvironmentsStore { return updatedEnvironments; } + getOpenFolders() { + const environments = this.state.environments; + + return environments.filter(env => env.isFolder && env.isOpen); + } + } diff --git a/changelogs/unreleased/zj-realtime-env-list.yml b/changelogs/unreleased/zj-realtime-env-list.yml new file mode 100644 index 00000000000..f2233e9ba50 --- /dev/null +++ b/changelogs/unreleased/zj-realtime-env-list.yml @@ -0,0 +1,4 @@ +--- +title: Makes environment table realtime +merge_request: +author: diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js index 1c54cc3054c..ef799fcdcb1 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environment_spec.js @@ -76,6 +76,8 @@ describe('Environment', () => { component = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); + + // component.fetchEnvironments(); }); afterEach(() => { diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js index f617c4bdffe..6e855530b21 100644 --- a/spec/javascripts/environments/environments_store_spec.js +++ b/spec/javascripts/environments/environments_store_spec.js @@ -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]); + }); + }); }); diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 350078ad5f5..3052c27993c 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -39,6 +39,7 @@ describe('Environments Folder View', () => { component = new EnvironmentsFolderViewComponent({ el: document.querySelector('#environments-folder-list-view'), }); + // component.fetchEnvironments(); }); afterEach(() => { -- cgit v1.2.1 From 8fd954961219169676804b85653ff7c69b8367f4 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 14 May 2017 17:23:42 +0100 Subject: Remove commented code --- spec/javascripts/environments/environment_spec.js | 2 -- spec/javascripts/environments/folder/environments_folder_view_spec.js | 1 - 2 files changed, 3 deletions(-) diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js index ef799fcdcb1..1c54cc3054c 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environment_spec.js @@ -76,8 +76,6 @@ describe('Environment', () => { component = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); - - // component.fetchEnvironments(); }); afterEach(() => { diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 3052c27993c..350078ad5f5 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -39,7 +39,6 @@ describe('Environments Folder View', () => { component = new EnvironmentsFolderViewComponent({ el: document.querySelector('#environments-folder-list-view'), }); - // component.fetchEnvironments(); }); afterEach(() => { -- cgit v1.2.1 From 41bedd9cb7b24a8bc1a82d3bfaff5e10b49e0f6e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 15 May 2017 11:36:02 +0100 Subject: Creates mixin to hold data common to both environments apps --- .../environments/components/environment.vue | 19 +++++++------------ .../environments/folder/environments_folder_view.vue | 18 ++++++------------ .../environments/mixins/environments_mixin.js | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 24 deletions(-) create mode 100644 app/assets/javascripts/environments/mixins/environments_mixin.js diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index 5fad3fb9742..86d8fe89010 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -9,6 +9,7 @@ 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 { @@ -18,6 +19,10 @@ export default { loadingIcon, }, + mixins: [ + environmentsMixin, + ], + data() { const environmentsData = document.querySelector('#environments-list-view').dataset; const store = new EnvironmentsStore(); @@ -169,19 +174,9 @@ export default { }, successCallback(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); + this.saveData(resp); - // If it were any open folders while polling we need to set them open again + // 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 diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 23a316e4a32..925503a01c4 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -8,8 +8,8 @@ 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: { @@ -18,6 +18,10 @@ export default { loadingIcon, }, + mixins: [ + environmentsMixin, + ], + data() { const environmentsData = document.querySelector('#environments-folder-list-view').dataset; const store = new EnvironmentsStore(); @@ -139,17 +143,7 @@ export default { }, successCallback(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); + this.saveData(resp); }, errorCallback() { diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js new file mode 100644 index 00000000000..25b24fbd6dc --- /dev/null +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -0,0 +1,17 @@ +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); + }, + }, +}; -- cgit v1.2.1 From 0f465b247883e932e1759fd066e46864d6834741 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 18 May 2017 10:56:29 +0100 Subject: Guarantees first request is made even when the tab is not visible --- app/assets/javascripts/environments/components/environment.vue | 2 ++ app/assets/javascripts/environments/folder/environments_folder_view.vue | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index 86d8fe89010..9cd73d1f718 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -95,6 +95,8 @@ export default { if (!Visibility.hidden()) { this.isLoading = true; poll.makeRequest(); + } else { + this.fetchEnvironments(); } Visibility.change(() => { diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 925503a01c4..1778bdae99f 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -101,6 +101,8 @@ export default { if (!Visibility.hidden()) { this.isLoading = true; poll.makeRequest(); + } else { + this.fetchEnvironments(); } Visibility.change(() => { -- cgit v1.2.1