summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <lacerda.filipa@gmail.com>2017-02-16 12:28:05 +0000
committerFilipa Lacerda <lacerda.filipa@gmail.com>2017-02-16 12:28:05 +0000
commit25c929387225394baf39420bb98d2c0f35eeab43 (patch)
tree6990df66de41df4c75411b50a400ac08a5cdba81
parent19791b65b20a21f1d12315b5b75f52a01531693b (diff)
parentab3c546ff50d24e12c0883f1d7a0f749e46b5199 (diff)
downloadgitlab-ce-25c929387225394baf39420bb98d2c0f35eeab43.tar.gz
Merge branch 'fe-paginated-environments-api-add-subview' into 'fe-paginated-environments-api-add-pagination'
Adds sub view to list environments under a folder See merge request !9169
-rw-r--r--app/assets/javascripts/environments/components/environment.js.es699
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js.es6203
-rw-r--r--app/assets/javascripts/environments/components/environments_table.js.es674
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js.es614
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.js.es6181
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js.es628
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.es655
-rw-r--r--app/assets/javascripts/vue_pipelines_index/store.js.es611
-rw-r--r--app/assets/stylesheets/pages/environments.scss7
-rw-r--r--app/views/projects/environments/folder.html.haml13
-rw-r--r--changelogs/unreleased/fe-paginated-environments-api-add-subview.yml4
-rw-r--r--config/webpack.config.js1
-rw-r--r--spec/javascripts/environments/environment_item_spec.js.es6115
-rw-r--r--spec/javascripts/environments/environment_table_spec.js.es630
-rw-r--r--spec/javascripts/environments/environments_store_spec.js.es67
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js.es6202
-rw-r--r--spec/javascripts/environments/mock_data.js.es633
-rw-r--r--spec/javascripts/fixtures/environments/environments_folder_view.html.haml7
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js.es624
19 files changed, 851 insertions, 257 deletions
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index d3d4efb8289..0cbf952ea5c 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -4,20 +4,21 @@
const Vue = require('vue');
Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
-const EnvironmentItem = require('./environment_item');
-const Store = require('../stores/environments_store');
+const EnvironmentTable = require('./environments_table');
+const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
+require('../../lib/utils/common_utils');
module.exports = Vue.component('environment-component', {
components: {
- 'environment-item': EnvironmentItem,
+ 'environment-table': EnvironmentTable,
'table-pagination': gl.VueGlPagination,
},
data() {
const environmentsData = document.querySelector('#environments-list-view').dataset;
- const store = new Store();
+ const store = new EnvironmentsStore();
return {
store,
@@ -45,19 +46,19 @@ module.exports = Vue.component('environment-component', {
computed: {
scope() {
- return this.$options.getQueryParameter('scope');
+ return gl.utils.getParameterByName('scope');
},
canReadEnvironmentParsed() {
- return this.$options.convertPermissionToBoolean(this.canReadEnvironment);
+ return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
- return this.$options.convertPermissionToBoolean(this.canCreateDeployment);
+ return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
},
canCreateEnvironmentParsed() {
- return this.$options.convertPermissionToBoolean(this.canCreateEnvironment);
+ return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment);
},
},
@@ -67,8 +68,8 @@ module.exports = Vue.component('environment-component', {
* Toggles loading property.
*/
created() {
- const scope = this.$options.getQueryParameter('scope') || this.visibility;
- const pageNumber = this.$options.getQueryParameter('page') || this.pageNumber;
+ const scope = gl.utils.getParameterByName('scope') || this.visibility;
+ const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
@@ -96,30 +97,6 @@ module.exports = Vue.component('environment-component', {
});
},
- /**
- * Transforms the url parameter into an object and
- * returns the one requested.
- *
- * @param {String} param
- * @returns {String} The value of the requested parameter.
- */
- getQueryParameter(parameter) {
- return window.location.search.substring(1).split('&').reduce((acc, param) => {
- const paramSplited = param.split('=');
- acc[paramSplited[0]] = paramSplited[1];
- return acc;
- }, {})[parameter];
- },
-
- /**
- * Converts permission provided as strings to booleans.
- * @param {String} string
- * @returns {Boolean}
- */
- convertPermissionToBoolean(string) {
- return string === 'true';
- },
-
methods: {
toggleRow(model) {
return this.store.toggleFolder(model.name);
@@ -128,26 +105,11 @@ module.exports = Vue.component('environment-component', {
/**
* Will change the page number and update the URL.
*
- * If no search params are present, we'll add param for page
- * If param for page is already present, we'll update it
- * If there are params but none for page, we'll add it at the end.
- *
* @param {Number} pageNumber desired page to go to.
+ * @return {String}
*/
changePage(pageNumber) {
- let param;
- if (window.location.search.length === 0) {
- param = `?page=${pageNumber}`;
- }
-
- if (window.location.search.indexOf('page') !== -1) {
- param = window.location.search.replace(/page=\d/g, `page=${pageNumber}`);
- }
-
- if (window.location.search.length &&
- window.location.search.indexOf('page') === -1) {
- param = `${window.location.search}&page=${pageNumber}`;
- }
+ const param = gl.utils.setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
@@ -158,7 +120,7 @@ module.exports = Vue.component('environment-component', {
<div :class="cssContainerClass">
<div class="top-area">
<ul v-if="!isLoading" class="nav-links">
- <li v-bind:class="{ 'active': scope === undefined || scope === 'available' }">
+ <li v-bind:class="{ 'active': scope === null || scope === 'available' }">
<a :href="projectEnvironmentsPath">
Available
<span class="badge js-available-environments-count">
@@ -209,30 +171,15 @@ module.exports = Vue.component('environment-component', {
<div class="table-holder"
v-if="!isLoading && state.environments.length > 0">
- <table class="table ci-table environments">
- <thead>
- <tr>
- <th class="environments-name">Environment</th>
- <th class="environments-deploy">Last deployment</th>
- <th class="environments-build">Job</th>
- <th class="environments-commit">Commit</th>
- <th class="environments-date">Updated</th>
- <th class="hidden-xs environments-actions"></th>
- </tr>
- </thead>
- <tbody>
- <template v-for="model in state.environments"
- v-bind:model="model">
- <tr is="environment-item"
- :model="model"
- :can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"
- :play-icon-svg="playIconSvg"
- :terminal-icon-svg="terminalIconSvg"
- :commit-icon-svg="commitIconSvg"></tr>
- </template>
- </tbody>
- </table>
+
+ <environment-table
+ :environments="state.environments"
+ :can-create-deployment="canCreateDeploymentParsed"
+ :can-read-environment="canReadEnvironmentParsed"
+ :play-icon-svg="playIconSvg"
+ :terminal-icon-svg="terminalIconSvg"
+ :commit-icon-svg="commitIconSvg">
+ </environment-table>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6
index fc45c3c5f53..24fd58a301a 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js.es6
@@ -72,9 +72,9 @@ module.exports = Vue.component('environment-item', {
* @returns {Boolean}
*/
hasLastDeploymentKey() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment)) {
+ if (this.model &&
+ this.model.last_deployment &&
+ !this.$options.isObjectEmpty(this.model.last_deployment)) {
return true;
}
return false;
@@ -87,10 +87,10 @@ module.exports = Vue.component('environment-item', {
* @returns {Boolean|Undefined}
*/
hasManualActions() {
- return this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.manual_actions &&
- this.model.latest.last_deployment.manual_actions.length > 0;
+ return this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.manual_actions &&
+ this.model.last_deployment.manual_actions.length > 0;
},
/**
@@ -99,7 +99,7 @@ module.exports = Vue.component('environment-item', {
* @returns {Boolean}
*/
hasStopAction() {
- return this.model.latest['stop_action?'];
+ return this.model && this.model['stop_action?'];
},
/**
@@ -109,10 +109,10 @@ module.exports = Vue.component('environment-item', {
* @returns {Boolean|Undefined}
*/
canRetry() {
- return this.model.latest &&
+ return this.model &&
this.hasLastDeploymentKey &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.deployable;
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable;
},
/**
@@ -121,10 +121,10 @@ module.exports = Vue.component('environment-item', {
* @returns {Boolean|Undefined}
*/
canShowDate() {
- return this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.deployable &&
- this.model.latest.last_deployment.deployable !== undefined;
+ return this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable !== undefined;
},
/**
@@ -133,11 +133,11 @@ module.exports = Vue.component('environment-item', {
* @returns {String}
*/
createdDate() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.deployable &&
- this.model.latest.last_deployment.deployable.created_at) {
- return timeagoInstance.format(this.model.latest.last_deployment.deployable.created_at);
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.created_at) {
+ return timeagoInstance.format(this.model.last_deployment.deployable.created_at);
}
return '';
},
@@ -149,7 +149,7 @@ module.exports = Vue.component('environment-item', {
*/
manualActions() {
if (this.hasManualActions) {
- return this.model.latest.last_deployment.manual_actions.map((action) => {
+ return this.model.last_deployment.manual_actions.map((action) => {
const parsedAction = {
name: gl.text.humanize(action.name),
play_path: action.play_path,
@@ -166,11 +166,11 @@ module.exports = Vue.component('environment-item', {
* @returns {String}
*/
userImageAltDescription() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.user &&
- this.model.latest.last_deployment.user.username) {
- return `${this.model.latest.last_deployment.user.username}'s avatar'`;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.user &&
+ this.model.last_deployment.user.username) {
+ return `${this.model.last_deployment.user.username}'s avatar'`;
}
return '';
},
@@ -181,10 +181,10 @@ module.exports = Vue.component('environment-item', {
* @returns {String|Undefined}
*/
commitTag() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.tag) {
- return this.model.latest.last_deployment.tag;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.tag) {
+ return this.model.last_deployment.tag;
}
return undefined;
},
@@ -195,10 +195,10 @@ module.exports = Vue.component('environment-item', {
* @returns {Object|Undefined}
*/
commitRef() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.ref) {
- return this.model.latest.last_deployment.ref;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.ref) {
+ return this.model.last_deployment.ref;
}
return undefined;
},
@@ -209,11 +209,11 @@ module.exports = Vue.component('environment-item', {
* @returns {String|Undefined}
*/
commitUrl() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.commit &&
- this.model.latest.last_deployment.commit.commit_path) {
- return this.model.latest.last_deployment.commit.commit_path;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.commit_path) {
+ return this.model.last_deployment.commit.commit_path;
}
return undefined;
},
@@ -224,11 +224,11 @@ module.exports = Vue.component('environment-item', {
* @returns {String|Undefined}
*/
commitShortSha() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.commit &&
- this.model.latest.last_deployment.commit.short_id) {
- return this.model.latest.last_deployment.commit.short_id;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.short_id) {
+ return this.model.last_deployment.commit.short_id;
}
return undefined;
},
@@ -239,11 +239,11 @@ module.exports = Vue.component('environment-item', {
* @returns {String|Undefined}
*/
commitTitle() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.commit &&
- this.model.latest.last_deployment.commit.title) {
- return this.model.latest.last_deployment.commit.title;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.title) {
+ return this.model.last_deployment.commit.title;
}
return undefined;
},
@@ -254,11 +254,11 @@ module.exports = Vue.component('environment-item', {
* @returns {Object|Undefined}
*/
commitAuthor() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.commit &&
- this.model.latest.last_deployment.commit.author) {
- return this.model.latest.last_deployment.commit.author;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.commit &&
+ this.model.last_deployment.commit.author) {
+ return this.model.last_deployment.commit.author;
}
return undefined;
@@ -270,11 +270,11 @@ module.exports = Vue.component('environment-item', {
* @returns {String|Undefined}
*/
retryUrl() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.deployable &&
- this.model.latest.last_deployment.deployable.retry_path) {
- return this.model.latest.last_deployment.deployable.retry_path;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.retry_path) {
+ return this.model.last_deployment.deployable.retry_path;
}
return undefined;
},
@@ -285,8 +285,8 @@ module.exports = Vue.component('environment-item', {
* @returns {Boolean|Undefined}
*/
isLastDeployment() {
- return this.model.latest && this.model.latest.last_deployment &&
- this.model.latest.last_deployment['last?'];
+ return this.model && this.model.last_deployment &&
+ this.model.last_deployment['last?'];
},
/**
@@ -295,10 +295,10 @@ module.exports = Vue.component('environment-item', {
* @returns {String}
*/
buildName() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.deployable) {
- return `${this.model.latest.last_deployment.deployable.name} #${this.model.latest.last_deployment.deployable.id}`;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable) {
+ return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`;
}
return '';
},
@@ -309,10 +309,10 @@ module.exports = Vue.component('environment-item', {
* @returns {String}
*/
deploymentInternalId() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.iid) {
- return `#${this.model.latest.last_deployment.iid}`;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.iid) {
+ return `#${this.model.last_deployment.iid}`;
}
return '';
},
@@ -323,9 +323,9 @@ module.exports = Vue.component('environment-item', {
* @returns {Boolean}
*/
deploymentHasUser() {
- return this.model.latest &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment) &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment.user);
+ return this.model &&
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ !this.$options.isObjectEmpty(this.model.last_deployment.user);
},
/**
@@ -335,10 +335,10 @@ module.exports = Vue.component('environment-item', {
* @returns {Object}
*/
deploymentUser() {
- if (this.model.latest &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment) &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment.user)) {
- return this.model.latest.last_deployment.user;
+ if (this.model &&
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ !this.$options.isObjectEmpty(this.model.last_deployment.user)) {
+ return this.model.last_deployment.user;
}
return {};
},
@@ -352,9 +352,8 @@ module.exports = Vue.component('environment-item', {
*/
shouldRenderBuildName() {
return !this.model.isFolder &&
- this.model.latest &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment) &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable);
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ !this.$options.isObjectEmpty(this.model.last_deployment.deployable);
},
/**
@@ -363,11 +362,11 @@ module.exports = Vue.component('environment-item', {
* @return {String}
*/
buildPath() {
- if (this.model.latest &&
- this.model.latest.last_deployment &&
- this.model.latest.last_deployment.deployable &&
- this.model.latest.last_deployment.deployable.build_path) {
- return this.model.latest.last_deployment.deployable.build_path;
+ if (this.model &&
+ this.model.last_deployment &&
+ this.model.last_deployment.deployable &&
+ this.model.last_deployment.deployable.build_path) {
+ return this.model.last_deployment.deployable.build_path;
}
return '';
@@ -379,8 +378,8 @@ module.exports = Vue.component('environment-item', {
* @return {String}
*/
externalURL() {
- if (this.model.latest && this.model.latest.external_url) {
- return this.model.latest.external_url;
+ if (this.model && this.model.external_url) {
+ return this.model.external_url;
}
return '';
@@ -395,18 +394,27 @@ module.exports = Vue.component('environment-item', {
*/
shouldRenderDeploymentID() {
return !this.model.isFolder &&
- this.model.latest &&
- !this.$options.isObjectEmpty(this.model.latest.last_deployment) &&
- this.model.latest.last_deployment.iid !== undefined;
+ !this.$options.isObjectEmpty(this.model.last_deployment) &&
+ this.model.last_deployment.iid !== undefined;
},
environmentPath() {
- if (this.model && this.model.latest && this.model.latest.environment_path) {
- return this.model.latest.environment_path;
+ if (this.model && this.model.environment_path) {
+ return this.model.environment_path;
}
return '';
},
+
+ /**
+ * Constructs folder URL based on the current location and the folder id.
+ *
+ * @return {String}
+ */
+ folderUrl() {
+ return `${window.location.pathname}/folders/${this.model.folderName}`;
+ },
+
},
/**
@@ -432,14 +440,13 @@ module.exports = Vue.component('environment-item', {
:href="environmentPath">
{{model.name}}
</a>
- <a v-else class="folder-name">
+ <a v-else class="folder-name" :href="folderUrl">
<span class="folder-icon">
- <i class="fa fa-caret-right" aria-hidden="true"></i>
<i class="fa fa-folder" aria-hidden="true"></i>
</span>
<span>
- {{model.name}}
+ {{model.folderName}}
</span>
<span class="badge">
@@ -513,18 +520,18 @@ module.exports = Vue.component('environment-item', {
</external-url-component>
</div>
- <div v-if="hasStopAction && canCreateDeployment && model.latest"
+ <div v-if="hasStopAction && canCreateDeployment"
class="inline js-stop-component-container">
<stop-component
- :stop-url="model.latest.stop_path">
+ :stop-url="model.stop_path">
</stop-component>
</div>
- <div v-if="model.latest && model.latest.terminal_path"
+ <div v-if="model && model.terminal_path"
class="inline js-terminal-button-container">
<terminal-button-component
:terminal-icon-svg="terminalIconSvg"
- :terminal-path="model.latest.terminal_path">
+ :terminal-path="model.terminal_path">
</terminal-button-component>
</div>
diff --git a/app/assets/javascripts/environments/components/environments_table.js.es6 b/app/assets/javascripts/environments/components/environments_table.js.es6
new file mode 100644
index 00000000000..fd35d77fd3d
--- /dev/null
+++ b/app/assets/javascripts/environments/components/environments_table.js.es6
@@ -0,0 +1,74 @@
+/**
+ * Render environments table.
+ */
+const Vue = require('vue');
+const EnvironmentItem = require('./environment_item');
+
+module.exports = Vue.component('environment-table-component', {
+
+ components: {
+ 'environment-item': EnvironmentItem,
+ },
+
+ props: {
+ environments: {
+ type: Array,
+ required: true,
+ default: () => ([]),
+ },
+
+ canReadEnvironment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ canCreateDeployment: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ commitIconSvg: {
+ type: String,
+ required: false,
+ },
+
+ playIconSvg: {
+ type: String,
+ required: false,
+ },
+
+ terminalIconSvg: {
+ type: String,
+ required: false,
+ },
+ },
+
+ template: `
+ <table class="table ci-table environments">
+ <thead>
+ <tr>
+ <th class="environments-name">Environment</th>
+ <th class="environments-deploy">Last deployment</th>
+ <th class="environments-build">Job</th>
+ <th class="environments-commit">Commit</th>
+ <th class="environments-date">Updated</th>
+ <th class="hidden-xs environments-actions"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <template v-for="model in environments"
+ v-bind:model="model">
+ <tr is="environment-item"
+ :model="model"
+ :can-create-deployment="canCreateDeployment"
+ :can-read-environment="canReadEnvironment"
+ :play-icon-svg="playIconSvg"
+ :terminal-icon-svg="terminalIconSvg"
+ :commit-icon-svg="commitIconSvg"></tr>
+ </template>
+ </tbody>
+ </table>
+ `,
+});
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
new file mode 100644
index 00000000000..29f704c1a37
--- /dev/null
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
@@ -0,0 +1,14 @@
+const EnvironmentsFolderComponent = require('./environments_folder_view');
+require('../../vue_shared/vue_resource_interceptor');
+
+$(() => {
+ window.gl = window.gl || {};
+
+ if (gl.EnvironmentsListFolderApp) {
+ gl.EnvironmentsListFolderApp.$destroy(true);
+ }
+
+ gl.EnvironmentsListFolderApp = new EnvironmentsFolderComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+});
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
new file mode 100644
index 00000000000..0b1204559da
--- /dev/null
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
@@ -0,0 +1,181 @@
+/* eslint-disable no-param-reassign, no-new */
+/* global Flash */
+
+const Vue = require('vue');
+Vue.use(require('vue-resource'));
+const EnvironmentsService = require('../services/environments_service');
+const EnvironmentTable = require('../components/environments_table');
+const EnvironmentsStore = require('../stores/environments_store');
+require('../../vue_shared/components/table_pagination');
+require('../../lib/utils/common_utils');
+
+module.exports = Vue.component('environment-folder-view', {
+
+ components: {
+ 'environment-table': EnvironmentTable,
+ 'table-pagination': gl.VueGlPagination,
+ },
+
+ 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,
+
+ // svgs
+ commitIconSvg: environmentsData.commitIconSvg,
+ playIconSvg: environmentsData.playIconSvg,
+ terminalIconSvg: environmentsData.terminalIconSvg,
+
+ // Pagination Properties,
+ paginationInformation: {},
+ pageNumber: 1,
+ };
+ },
+
+ computed: {
+ scope() {
+ return gl.utils.getParameterByName('scope');
+ },
+
+ canReadEnvironmentParsed() {
+ return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
+ },
+
+ canCreateDeploymentParsed() {
+ return gl.utils.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 = gl.utils.getParameterByName('scope') || this.visibility;
+ const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
+
+ const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
+
+ const service = new EnvironmentsService(endpoint);
+
+ this.isLoading = true;
+
+ return service.all()
+ .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;
+ new Flash('An error occurred while fetching the environments.', 'alert');
+ });
+ },
+
+ methods: {
+ /**
+ * Will change the page number and update the URL.
+ *
+ * @param {Number} pageNumber desired page to go to.
+ */
+ changePage(pageNumber) {
+ const param = gl.utils.setParamInURL('page', pageNumber);
+
+ gl.utils.visitUrl(param);
+ return param;
+ },
+ },
+
+ template: `
+ <div :class="cssContainerClass">
+ <div class="top-area" v-if="!isLoading">
+
+ <h4 class="js-folder-name environments-folder-name">
+ Environments / <b>{{folderName}}</b>
+ </h4>
+
+ <ul class="nav-links">
+ <li v-bind: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 v-bind: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">
+ <div class="environments-list-loading text-center" v-if="isLoading">
+ <i class="fa fa-spinner fa-spin"></i>
+ </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"
+ :play-icon-svg="playIconSvg"
+ :terminal-icon-svg="terminalIconSvg"
+ :commit-icon-svg="commitIconSvg">
+ </environment-table>
+
+ <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
+ :change="changePage"
+ :pageInfo="state.paginationInformation">
+ </table-pagination>
+ </div>
+ </div>
+ </div>
+ `,
+});
diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6
index 2dc6bbe4761..15cd9bde08e 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js.es6
+++ b/app/assets/javascripts/environments/stores/environments_store.js.es6
@@ -20,8 +20,12 @@ class EnvironmentsStore {
*
* Stores the received environments.
*
- * Each environment has the following schema
+ * In the main environments endpoint, each environment has the following schema
* { name: String, size: Number, latest: Object }
+ * In the endpoint to retrieve environments from each folder, the environment does
+ * not have the `latest` key and the data is all in the root level.
+ * To avoid doing this check in the view, we store both cases the same by extracting
+ * what is inside the `latest` key.
*
* If the `size` is bigger than 1, it means it should be rendered as a folder.
* In those cases we add `isFolder` key in order to render it properly.
@@ -31,11 +35,20 @@ class EnvironmentsStore {
*/
storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => {
+ let filtered = {};
+
if (env.size > 1) {
- return Object.assign({}, env, { isFolder: true });
+ filtered = Object.assign({}, env, { isFolder: true, folderName: env.name });
+ }
+
+ if (env.latest) {
+ filtered = Object.assign(filtered, env, env.latest);
+ delete filtered.latest;
+ } else {
+ filtered = Object.assign(filtered, env);
}
- return env;
+ return filtered;
});
this.state.environments = filteredEnvironments;
@@ -45,14 +58,7 @@ class EnvironmentsStore {
setPagination(pagination = {}) {
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
- const paginationInformation = {
- perPage: parseInt(normalizedHeaders['X-PER-PAGE'], 10),
- page: parseInt(normalizedHeaders['X-PAGE'], 10),
- total: parseInt(normalizedHeaders['X-TOTAL'], 10),
- totalPages: parseInt(normalizedHeaders['X-TOTAL-PAGES'], 10),
- nextPage: parseInt(normalizedHeaders['X-NEXT-PAGE'], 10),
- previousPage: parseInt(normalizedHeaders['X-PREV-PAGE'], 10),
- };
+ const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders);
this.state.paginationInformation = paginationInformation;
return paginationInformation;
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6
index bcb3a706b51..764aff51fee 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -232,6 +232,21 @@
};
/**
+ * Parses pagination object string values into numbers.
+ *
+ * @param {Object} paginationInformation
+ * @returns {Object}
+ */
+ w.gl.utils.parseIntPagination = paginationInformation => ({
+ perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
+ page: parseInt(paginationInformation['X-PAGE'], 10),
+ total: parseInt(paginationInformation['X-TOTAL'], 10),
+ totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10),
+ nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
+ previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
+ });
+
+ /**
* Transforms a DOMStringMap into a plain object.
*
* @param {DOMStringMap} DOMStringMapObject
@@ -241,5 +256,45 @@
acc[element] = DOMStringMapObject[element];
return acc;
}, {});
+
+ /**
+ * Updates the search parameter of a URL given the parameter and values provided.
+ *
+ * If no search params are present we'll add it.
+ * If param for page is already present, we'll update it
+ * If there are params but not for the given one, we'll add it at the end.
+ * Returns the new search parameters.
+ *
+ * @param {String} param
+ * @param {Number|String|Undefined|Null} value
+ * @return {String}
+ */
+ w.gl.utils.setParamInURL = (param, value) => {
+ let search;
+ const locationSearch = window.location.search;
+
+ if (locationSearch.length === 0) {
+ search = `?${param}=${value}`;
+ }
+
+ if (locationSearch.indexOf(param) !== -1) {
+ const regex = new RegExp(param + '=\\d');
+ search = locationSearch.replace(regex, `${param}=${value}`);
+ }
+
+ if (locationSearch.length && locationSearch.indexOf(param) === -1) {
+ search = `${locationSearch}&${param}=${value}`;
+ }
+
+ return search;
+ };
+
+ /**
+ * Converts permission provided as strings to booleans.
+ *
+ * @param {String} string
+ * @returns {Boolean}
+ */
+ w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
})(window);
}).call(this);
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6
index 0ee21f00fdc..572f0493c9f 100644
--- a/app/assets/javascripts/vue_pipelines_index/store.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6
@@ -5,16 +5,7 @@ require('../vue_realtime_listener');
((gl) => {
const pageValues = (headers) => {
const normalized = gl.utils.normalizeHeaders(headers);
-
- const paginationInfo = {
- perPage: +normalized['X-PER-PAGE'],
- page: +normalized['X-PAGE'],
- total: +normalized['X-TOTAL'],
- totalPages: +normalized['X-TOTAL-PAGES'],
- nextPage: +normalized['X-NEXT-PAGE'],
- previousPage: +normalized['X-PREV-PAGE'],
- };
-
+ const paginationInfo = gl.utils.normalizeHeaders(normalized);
return paginationInfo;
};
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 606cf501b82..181dcb7721f 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -10,6 +10,11 @@
font-size: 34px;
}
+.environments-folder-name {
+ font-weight: normal;
+ padding-top: 20px;
+}
+
@media (max-width: $screen-xs-max) {
.environments-container {
width: 100%;
@@ -113,6 +118,7 @@
.folder-icon {
margin-right: 3px;
color: $gl-text-color-secondary;
+ display: inline-block;
.fa:nth-child(1) {
margin-right: 3px;
@@ -122,6 +128,7 @@
.folder-name {
cursor: pointer;
color: $gl-text-color-secondary;
+ display: inline-block;
}
}
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
new file mode 100644
index 00000000000..d9cb7bc0331
--- /dev/null
+++ b/app/views/projects/environments/folder.html.haml
@@ -0,0 +1,13 @@
+- @no_container = true
+- page_title "Environments"
+= render "projects/pipelines/head"
+
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag("environments_folder")
+
+#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
+ "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
+ "css-class" => container_class,
+ "commit-icon-svg" => custom_icon("icon_commit"),
+ "terminal-icon-svg" => custom_icon("icon_terminal"),
+ "play-icon-svg" => custom_icon("icon_play") } }
diff --git a/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml b/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml
new file mode 100644
index 00000000000..7e626982de6
--- /dev/null
+++ b/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml
@@ -0,0 +1,4 @@
+---
+title: Adds paginationd and folders view to environments table
+merge_request:
+author:
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 00f448c1fbb..7fda5405ea2 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -22,6 +22,7 @@ var config = {
commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js',
environments: './environments/environments_bundle.js',
+ environments_folder: './environments/folder/environments_folder_bundle.js',
filtered_search: './filtered_search/filtered_search_bundle.js',
graphs: './graphs/graphs_bundle.js',
issuable: './issuable/issuable_bundle.js',
diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6
index 88413d9ae2b..7fea80ed799 100644
--- a/spec/javascripts/environments/environment_item_spec.js.es6
+++ b/spec/javascripts/environments/environment_item_spec.js.es6
@@ -14,11 +14,10 @@ describe('Environment item', () => {
beforeEach(() => {
mockItem = {
name: 'review',
+ folderName: 'review',
size: 3,
isFolder: true,
- latest: {
- environment_path: 'url',
- },
+ environment_path: 'url',
};
component = new EnvironmentItem({
@@ -49,21 +48,36 @@ describe('Environment item', () => {
environment = {
name: 'production',
size: 1,
- latest: {
- state: 'stopped',
- external_url: 'http://external.com',
- environment_type: null,
- last_deployment: {
- id: 66,
- iid: 6,
- sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- ref: {
- name: 'master',
- ref_path: 'root/ci-folders/tree/master',
- },
- tag: true,
- 'last?': true,
- user: {
+ state: 'stopped',
+ external_url: 'http://external.com',
+ environment_type: null,
+ last_deployment: {
+ id: 66,
+ iid: 6,
+ sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
+ ref: {
+ name: 'master',
+ ref_path: 'root/ci-folders/tree/master',
+ },
+ tag: true,
+ 'last?': true,
+ user: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ commit: {
+ id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
+ short_id: '500aabcb',
+ title: 'Update .gitlab-ci.yml',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ created_at: '2016-11-07T18:28:13.000+00:00',
+ message: 'Update .gitlab-ci.yml',
+ author: {
name: 'Administrator',
username: 'root',
id: 1,
@@ -71,44 +85,27 @@ describe('Environment item', () => {
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
- commit: {
- id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- short_id: '500aabcb',
- title: 'Update .gitlab-ci.yml',
- author_name: 'Administrator',
- author_email: 'admin@example.com',
- created_at: '2016-11-07T18:28:13.000+00:00',
- message: 'Update .gitlab-ci.yml',
- author: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- },
- deployable: {
- id: 1279,
- name: 'deploy',
- build_path: '/root/ci-folders/builds/1279',
- retry_path: '/root/ci-folders/builds/1279/retry',
- created_at: '2016-11-29T18:11:58.430Z',
- updated_at: '2016-11-29T18:11:58.430Z',
- },
- manual_actions: [
- {
- name: 'action',
- play_path: '/play',
- },
- ],
+ commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
},
- 'stop_action?': true,
- environment_path: 'root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-10T15:55:58.778Z',
+ deployable: {
+ id: 1279,
+ name: 'deploy',
+ build_path: '/root/ci-folders/builds/1279',
+ retry_path: '/root/ci-folders/builds/1279/retry',
+ created_at: '2016-11-29T18:11:58.430Z',
+ updated_at: '2016-11-29T18:11:58.430Z',
+ },
+ manual_actions: [
+ {
+ name: 'action',
+ play_path: '/play',
+ },
+ ],
},
+ 'stop_action?': true,
+ environment_path: 'root/ci-folders/environments/31',
+ created_at: '2016-11-07T11:11:16.525Z',
+ updated_at: '2016-11-10T15:55:58.778Z',
};
component = new EnvironmentItem({
@@ -129,7 +126,7 @@ describe('Environment item', () => {
it('should render deployment internal id', () => {
expect(
component.$el.querySelector('.deployment-column span').textContent,
- ).toContain(environment.latest.last_deployment.iid);
+ ).toContain(environment.last_deployment.iid);
expect(
component.$el.querySelector('.deployment-column span').textContent,
@@ -139,7 +136,7 @@ describe('Environment item', () => {
it('should render last deployment date', () => {
const timeagoInstance = new timeago(); // eslint-disable-line
const formatedDate = timeagoInstance.format(
- environment.latest.last_deployment.deployable.created_at,
+ environment.last_deployment.deployable.created_at,
);
expect(
@@ -151,7 +148,7 @@ describe('Environment item', () => {
it('should render user avatar with link to profile', () => {
expect(
component.$el.querySelector('.js-deploy-user-container').getAttribute('href'),
- ).toEqual(environment.latest.last_deployment.user.web_url);
+ ).toEqual(environment.last_deployment.user.web_url);
});
});
@@ -159,13 +156,13 @@ describe('Environment item', () => {
it('Should link to build url provided', () => {
expect(
component.$el.querySelector('.build-link').getAttribute('href'),
- ).toEqual(environment.latest.last_deployment.deployable.build_path);
+ ).toEqual(environment.last_deployment.deployable.build_path);
});
it('Should render deployable name and id', () => {
expect(
component.$el.querySelector('.build-link').getAttribute('href'),
- ).toEqual(environment.latest.last_deployment.deployable.build_path);
+ ).toEqual(environment.last_deployment.deployable.build_path);
});
});
diff --git a/spec/javascripts/environments/environment_table_spec.js.es6 b/spec/javascripts/environments/environment_table_spec.js.es6
new file mode 100644
index 00000000000..be4330b5012
--- /dev/null
+++ b/spec/javascripts/environments/environment_table_spec.js.es6
@@ -0,0 +1,30 @@
+const EnvironmentTable = require('~/environments/components/environments_table');
+
+describe('Environment item', () => {
+ preloadFixtures('static/environments/element.html.raw');
+ beforeEach(() => {
+ loadFixtures('static/environments/element.html.raw');
+ });
+
+ it('Should render a table', () => {
+ const mockItem = {
+ name: 'review',
+ size: 3,
+ isFolder: true,
+ latest: {
+ environment_path: 'url',
+ },
+ };
+
+ const component = new EnvironmentTable({
+ el: document.querySelector('.test-dom-element'),
+ propsData: {
+ environments: [{ mockItem }],
+ canCreateDeployment: false,
+ canReadEnvironment: true,
+ },
+ });
+
+ expect(component.$el.tagName).toEqual('TABLE');
+ });
+});
diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6
index 71c1abd0c91..77e182b3830 100644
--- a/spec/javascripts/environments/environments_store_spec.js.es6
+++ b/spec/javascripts/environments/environments_store_spec.js.es6
@@ -1,5 +1,5 @@
const Store = require('~/environments/stores/environments_store');
-const { environmentsList } = require('./mock_data');
+const { environmentsList, serverData } = require('./mock_data');
(() => {
describe('Store', () => {
@@ -17,8 +17,9 @@ const { environmentsList } = require('./mock_data');
});
it('should store environments', () => {
- store.storeEnvironments(environmentsList);
- expect(store.state.environments.length).toEqual(environmentsList.length);
+ store.storeEnvironments(serverData);
+ expect(store.state.environments.length).toEqual(serverData.length);
+ expect(store.state.environments[0]).toEqual(environmentsList[0]);
});
it('should store available count', () => {
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6 b/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6
new file mode 100644
index 00000000000..d1335b5b304
--- /dev/null
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6
@@ -0,0 +1,202 @@
+const Vue = require('vue');
+require('~/flash');
+const EnvironmentsFolderViewComponent = require('~/environments/folder/environments_folder_view');
+const { environmentsList } = require('../mock_data');
+
+describe('Environments Folder View', () => {
+ preloadFixtures('static/environments/environments_folder_view.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/environments/environments_folder_view.html.raw');
+ window.history.pushState({}, null, 'environments/folders/build');
+ });
+
+ let component;
+
+ describe('successfull request', () => {
+ const environmentsResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ environments: environmentsList,
+ stopped_count: 1,
+ available_count: 0,
+ }), {
+ status: 200,
+ headers: {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ },
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsResponseInterceptor);
+ component = new EnvironmentsFolderViewComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsResponseInterceptor,
+ );
+ });
+
+ it('should render a table with environments', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('table tbody tr').length,
+ ).toEqual(2);
+ done();
+ }, 0);
+ });
+
+ it('should render available tab with count', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ ).toContain('Available');
+
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+
+ it('should render stopped tab with count', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ ).toContain('Stopped');
+
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ ).toContain('1');
+ done();
+ }, 0);
+ });
+
+ it('should render parent folder name', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-folder-name').textContent,
+ ).toContain('Environments / build');
+ done();
+ }, 0);
+ });
+
+ describe('pagination', () => {
+ it('should render pagination', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('.gl-pagination li').length,
+ ).toEqual(5);
+ done();
+ }, 0);
+ });
+
+ it('should update url when no search params are present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page is already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?scope=all&page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present and page is first param', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1&scope=all');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
+ done();
+ }, 0);
+ });
+ });
+ });
+
+ describe('unsuccessfull request', () => {
+ const environmentsErrorResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify([]), {
+ status: 500,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsErrorResponseInterceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsErrorResponseInterceptor,
+ );
+ });
+
+ it('should not render a table', (done) => {
+ component = new EnvironmentsFolderViewComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('table'),
+ ).toBe(null);
+ done();
+ }, 0);
+ });
+
+ it('should render available tab with count 0', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ ).toContain('Available');
+
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+
+ it('should render stopped tab with count 0', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ ).toContain('Stopped');
+
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+ });
+});
diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6
index 081897f5456..5c395c6b2d8 100644
--- a/spec/javascripts/environments/mock_data.js.es6
+++ b/spec/javascripts/environments/mock_data.js.es6
@@ -2,6 +2,38 @@ const environmentsList = [
{
name: 'DEV',
size: 1,
+ id: 7,
+ state: 'available',
+ external_url: null,
+ environment_type: null,
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/7',
+ stop_path: '/root/review-app/environments/7/stop',
+ created_at: '2017-01-31T10:53:46.894Z',
+ updated_at: '2017-01-31T10:53:46.894Z',
+ },
+ {
+ folderName: 'build',
+ size: 5,
+ id: 12,
+ name: 'build/update-README',
+ state: 'available',
+ external_url: null,
+ environment_type: 'build',
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/12',
+ stop_path: '/root/review-app/environments/12/stop',
+ created_at: '2017-02-01T19:42:18.400Z',
+ updated_at: '2017-02-01T19:42:18.400Z',
+ },
+];
+
+const serverData = [
+ {
+ name: 'DEV',
+ size: 1,
latest: {
id: 7,
name: 'DEV',
@@ -56,4 +88,5 @@ const environment = {
module.exports = {
environmentsList,
environment,
+ serverData,
};
diff --git a/spec/javascripts/fixtures/environments/environments_folder_view.html.haml b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
new file mode 100644
index 00000000000..aceec139730
--- /dev/null
+++ b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
@@ -0,0 +1,7 @@
+%div
+ #environments-folder-list-view{ data: { "can-create-deployment" => "true",
+ "can-read-environment" => "true",
+ "css-class" => "",
+ "commit-icon-svg" => custom_icon("icon_commit"),
+ "terminal-icon-svg" => custom_icon("icon_terminal"),
+ "play-icon-svg" => custom_icon("icon_play") } }
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6
index 006ede21093..f4d3e77e515 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js.es6
+++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6
@@ -108,6 +108,30 @@ require('~/lib/utils/common_utils');
});
});
+ describe('gl.utils.parseIntPagination', () => {
+ it('should parse to integers all string values and return pagination object', () => {
+ const pagination = {
+ 'X-PER-PAGE': 10,
+ 'X-PAGE': 2,
+ 'X-TOTAL': 30,
+ 'X-TOTAL-PAGES': 3,
+ 'X-NEXT-PAGE': 3,
+ 'X-PREV-PAGE': 1,
+ };
+
+ const expectedPagination = {
+ perPage: 10,
+ page: 2,
+ total: 30,
+ totalPages: 3,
+ nextPage: 3,
+ previousPage: 1,
+ };
+
+ expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
+ });
+ });
+
describe('gl.utils.isMetaClick', () => {
it('should identify meta click on Windows/Linux', () => {
const e = {