summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/environments
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-11-23 12:04:03 +0000
committerPhil Hughes <me@iamphill.com>2017-11-23 12:04:03 +0000
commit45631562565760add7a7c52a6137e891f3a0c8f4 (patch)
tree44915f7c46d05fb8c6197662a3341ea8fb610f06 /app/assets/javascripts/environments
parent1d8ab59ebfa0d57e4015665c470c8339cd258a2c (diff)
downloadgitlab-ce-45631562565760add7a7c52a6137e891f3a0c8f4.tar.gz
Improve environments performance
Diffstat (limited to 'app/assets/javascripts/environments')
-rw-r--r--app/assets/javascripts/environments/components/container.vue71
-rw-r--r--app/assets/javascripts/environments/components/empty_state.vue42
-rw-r--r--app/assets/javascripts/environments/components/environment.vue270
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.vue3
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue8
-rw-r--r--app/assets/javascripts/environments/components/environment_monitoring.vue3
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue4
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue128
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue16
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js35
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js31
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue255
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js167
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js7
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 = {};