diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-11-23 15:04:03 +0300 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-11-23 15:04:03 +0300 |
commit | 45631562565760add7a7c52a6137e891f3a0c8f4 (patch) | |
tree | 44915f7c46d05fb8c6197662a3341ea8fb610f06 /app/assets/javascripts/environments | |
parent | 1d8ab59ebfa0d57e4015665c470c8339cd258a2c (diff) |
Improve environments performance
Diffstat (limited to 'app/assets/javascripts/environments')
14 files changed, 535 insertions, 505 deletions
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue new file mode 100644 index 00000000000..3236077c3cf --- /dev/null +++ b/app/assets/javascripts/environments/components/container.vue @@ -0,0 +1,71 @@ +<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> diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue new file mode 100644 index 00000000000..2646f08c8e6 --- /dev/null +++ b/app/assets/javascripts/environments/components/empty_state.vue @@ -0,0 +1,42 @@ +<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> diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue deleted file mode 100644 index ffb7757bed8..00000000000 --- a/app/assets/javascripts/environments/components/environment.vue +++ /dev/null @@ -1,270 +0,0 @@ -<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> diff --git a/app/assets/javascripts/environments/components/environment_external_url.vue b/app/assets/javascripts/environments/components/environment_external_url.vue index 6b749814ea4..520c3ac8ace 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.vue +++ b/app/assets/javascripts/environments/components/environment_external_url.vue @@ -1,5 +1,6 @@ <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'); }, }, }; diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 9d25f806c0d..2f0e397aa45 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -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" diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue index 1655561cdd3..b45af1a5ebc 100644 --- a/app/assets/javascripts/environments/components/environment_monitoring.vue +++ b/app/assets/javascripts/environments/components/environment_monitoring.vue @@ -34,6 +34,7 @@ export default { :aria-label="title"> <i class="fa fa-area-chart" - aria-hidden="true" /> + aria-hidden="true" + /> </a> </template> diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 49dba38edfb..92a596bfd33 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -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" /> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue new file mode 100644 index 00000000000..2592909734f --- /dev/null +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -0,0 +1,128 @@ +<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> diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 175cc8f1f72..c04da4b81b7 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -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> diff --git a/app/assets/javascripts/environments/environments_bundle.js b/app/assets/javascripts/environments/environments_bundle.js index c0662125f28..2e0a4001b7c 100644 --- a/app/assets/javascripts/environments/environments_bundle.js +++ b/app/assets/javascripts/environments/environments_bundle.js @@ -1,10 +1,39 @@ 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'), })); diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index 9add8c3d721..5d2d14c7682 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -1,10 +1,35 @@ 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'), })); diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index b155560df9d..27418bad01a 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,168 +1,40 @@ <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'; - -export default { - components: { - environmentTable, - tablePagination, - loadingIcon, - }, - - 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, - }; - }, - - computed: { - scope() { - return getParameterByName('scope'); - }, - - canReadEnvironmentParsed() { - return convertPermissionToBoolean(this.canReadEnvironment); - }, - - canCreateDeploymentParsed() { - return convertPermissionToBoolean(this.canCreateDeployment); - }, - - /** - * URL to link in the stopped tab. - * - * @return {String} - */ - stoppedPath() { - return `${window.location.pathname}?scope=stopped`; - }, - - /** - * 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; + import environmentsMixin from '../mixins/environments_mixin'; + import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; + + export default { + props: { + endpoint: { + type: String, + required: true, + }, + folderName: { + type: String, + required: true, + }, + cssContainerClass: { + type: String, + required: true, + }, + canCreateDeployment: { + type: Boolean, + required: true, + }, + canReadEnvironment: { + type: Boolean, + required: true, }, - }); - - 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: { - /** - * 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.')); - } + mixins: [ + environmentsMixin, + CIPaginationMixin, + ], + methods: { + successCallback(resp) { + this.saveData(resp); + }, }, - }, -}; + }; </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 - class="table-holder" - v-if="!isLoading && state.environments.length > 0"> - - <environment-table - :environments="state.environments" - :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed" - /> - - <table-pagination - v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" - :change="changePage" - :pageInfo="state.paginationInformation"/> - </div> </div> + + <container + :is-loading="isLoading" + :environments="state.environments" + :pagination="state.paginationInformation" + :can-create-deployment="canCreateDeployment" + :can-read-environment="canReadEnvironment" + @onChangePage="onChangePage" + /> </div> </template> diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 8f4066e3a6e..7219b076721 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -1,15 +1,174 @@ +/** + * 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; - this.store.storeAvailableCount(response.available_count); - this.store.storeStoppedCount(response.stopped_count); - this.store.storeEnvironments(response.environments); - this.store.setPagination(headers); + 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'); }, }; diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index aff8227c38c..5f2989ab854 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -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 = {}; |