diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 11a965fcddf0da3fe07ab6bebce412f577dc4dde..0cbf952ea5c782eb3772aea58e32bde5ce4b398d 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -4,18 +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, @@ -34,25 +37,30 @@ module.exports = Vue.component('environment-component', { commitIconSvg: environmentsData.commitIconSvg, playIconSvg: environmentsData.playIconSvg, terminalIconSvg: environmentsData.terminalIconSvg, + + // Pagination Properties, + paginationInformation: {}, + pageNumber: 1, }; }, 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); }, + }, /** @@ -60,19 +68,25 @@ module.exports = Vue.component('environment-component', { * Toggles loading property. */ created() { - const scope = this.$options.getQueryParameter('scope') || this.visibility; - const endpoint = `${this.endpoint}?scope=${scope}`; + 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 => resp.json()) - .then((json) => { - this.store.storeAvailableCount(json.available_count); - this.store.storeStoppedCount(json.stopped_count); - this.store.storeEnvironments(json.environments); + .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; @@ -83,41 +97,30 @@ 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); }, + + /** + * Will change the page number and update the URL. + * + * @param {Number} pageNumber desired page to go to. + * @return {String} + */ + changePage(pageNumber) { + const param = gl.utils.setParamInURL('page', pageNumber); + + gl.utils.visitUrl(param); + return param; + }, }, template: `
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 7805152b173e561277cde0d3a13fd7d40cdfe1d8..24fd58a301ae7c610efa6ed2dc90562627c652b1 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -72,8 +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; @@ -86,9 +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; }, /** @@ -97,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?']; }, /** @@ -107,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; }, /** @@ -119,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; }, /** @@ -131,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 ''; }, @@ -147,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, @@ -164,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 ''; }, @@ -179,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; }, @@ -193,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; }, @@ -207,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; }, @@ -222,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; }, @@ -237,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; }, @@ -252,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; @@ -268,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; }, @@ -283,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?']; }, /** @@ -293,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 ''; }, @@ -307,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 ''; }, @@ -321,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); }, /** @@ -333,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 {}; }, @@ -350,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); }, /** @@ -361,23 +362,24 @@ 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 ''; }, + /** * Verifies the presence of all the keys needed to render the external_url. * * @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 ''; @@ -392,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}`; + }, + }, /** @@ -429,14 +440,13 @@ module.exports = Vue.component('environment-item', { :href="environmentPath"> {{model.name}} - + - - {{model.name}} + {{model.folderName}} @@ -510,18 +520,18 @@ module.exports = Vue.component('environment-item', { -
+ :stop-url="model.stop_path">
-
+ :terminal-path="model.terminal_path">
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 0000000000000000000000000000000000000000..fd35d77fd3d913b4080fcbbb232de3d67a761763 --- /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: ` + + + + + + + + + + + + + + +
EnvironmentLast deploymentJobCommitUpdated
+ `, +}); 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 0000000000000000000000000000000000000000..29f704c1a3740c3c24627ed04b1609adbfc47775 --- /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 0000000000000000000000000000000000000000..0b1204559daf2b07a43ab0da9e9786f853aa5dc9 --- /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: ` +
+
+ +

+ Environments / {{folderName}} +

+ +
+
+ +
+
+ +
+ +
+ + + + + + +
+
+
+ `, +}); diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index 749dd88218869087b0939fcb9b447e115f291168..15cd9bde08e8b4e7f4908f4eee51a512923b6a7f 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -1,3 +1,4 @@ +require('~/lib/utils/common_utils'); /** * Environments Store. * @@ -10,6 +11,7 @@ class EnvironmentsStore { this.state.environments = []; this.state.stoppedCounter = 0; this.state.availableCounter = 0; + this.state.paginationInformation = {}; return this; } @@ -18,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. @@ -29,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; @@ -41,6 +56,14 @@ class EnvironmentsStore { return filteredEnvironments; } + setPagination(pagination = {}) { + const normalizedHeaders = gl.utils.normalizeHeaders(pagination); + const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders); + + this.state.paginationInformation = paginationInformation; + return paginationInformation; + } + /** * Stores the number of available environments. * diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index bcb3a706b5122ed618e9af1b0499c03d5d5820f5..764aff51fee9f142d1eef32e2a063b68a3ef49ed 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -231,6 +231,21 @@ return upperCaseHeaders; }; + /** + * 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. * @@ -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/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index e47dc6935d66defaa108b25b23aa43037f16ba87..39935b08dc06cb8d92591ba757e98c95cf4dc98c 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -35,7 +35,16 @@ require('../vue_shared/components/pipelines_table'); this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope); }, methods: { - change(pagenum, apiScope) { + + /** + * Changes the URL according to the pagination component. + * + * If no scope is provided, 'all' is assumed. + * + * @param {Number} pagenum + * @param {String} apiScope = 'all' + */ + change(pagenum, apiScope = 'all') { gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); }, }, diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 0ee21f00fdc7d2ddd28cba99971ca7ef9c77f578..572f0493c9f2af045474621d1699d2ae189cb356 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/javascripts/vue_shared/components/table_pagination.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 index 67c6cb737613c64aa5b28a6326a65f07ee41a434..d8042a9b7fc42cf87eec7f5f7cf7bf1251134ae6 100644 --- a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 +++ b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 @@ -57,9 +57,7 @@ window.Vue = require('vue'); }, methods: { changePage(e) { - let apiScope = gl.utils.getParameterByName('scope'); - - if (!apiScope) apiScope = 'all'; + const apiScope = gl.utils.getParameterByName('scope'); const text = e.target.innerText; const { totalPages, nextPage, previousPage } = this.pageInfo; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 606cf501b82b40cb0578dd5669a2777d9f5ee22c..181dcb7721f5002e0ebfd32bde68bb678cd360f5 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 0000000000000000000000000000000000000000..d9cb7bc033101bfeeae3cdf2fbf0cef55ccccb8a --- /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 0000000000000000000000000000000000000000..7e626982de6bfa520ccdf524988b049cd3241c0f --- /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 00f448c1fbb7c9d3c0662580bc9983027c6e0b21..7fda5405ea2f43cb05f07ad2d9c14e7f178bbab6 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 88413d9ae2ba8f89af08c5521b8e1647ae6b2c95..7fea80ed799e579048f30b3439931314ba6e785f 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_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 index 1d1a688a4a5e1edfc54e7e935f63875cff387c9c..edd0cad32d08afaad849e021aef3537b7b291e90 100644 --- a/spec/javascripts/environments/environment_spec.js.es6 +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -49,7 +49,7 @@ describe('Environment', () => { }); }); - describe('with environments', () => { + describe('with paginated environments', () => { const environmentsResponseInterceptor = (request, next) => { next(request.respondWith(JSON.stringify({ environments: [environment], @@ -57,11 +57,22 @@ describe('Environment', () => { 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 EnvironmentsComponent({ + el: document.querySelector('#environments-list-view'), + }); }); afterEach(() => { @@ -71,10 +82,6 @@ describe('Environment', () => { }); it('should render a table with environments', (done) => { - component = new EnvironmentsComponent({ - el: document.querySelector('#environments-list-view'), - }); - setTimeout(() => { expect( component.$el.querySelectorAll('table tbody tr').length, @@ -82,6 +89,59 @@ describe('Environment', () => { 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); + }); + }); }); }); 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 0000000000000000000000000000000000000000..be4330b50124a9e87ae16fa871ef4f8728d85b89 --- /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 861136c621f98622ec0b4c843c5a3940d4424934..77e182b3830781efb874e687da73db79e3c94282 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', () => { @@ -10,24 +10,49 @@ const { environmentsList } = require('./mock_data'); }); it('should start with a blank state', () => { - expect(store.state.environments.length).toBe(0); - expect(store.state.stoppedCounter).toBe(0); - expect(store.state.availableCounter).toBe(0); + expect(store.state.environments.length).toEqual(0); + expect(store.state.stoppedCounter).toEqual(0); + expect(store.state.availableCounter).toEqual(0); + expect(store.state.paginationInformation).toEqual({}); }); it('should store environments', () => { - store.storeEnvironments(environmentsList); - expect(store.state.environments.length).toBe(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', () => { store.storeAvailableCount(2); - expect(store.state.availableCounter).toBe(2); + expect(store.state.availableCounter).toEqual(2); }); it('should store stopped count', () => { store.storeStoppedCount(2); - expect(store.state.stoppedCounter).toBe(2); + expect(store.state.stoppedCounter).toEqual(2); + }); + + it('should store pagination information', () => { + const pagination = { + 'X-nExt-pAge': '2', + 'X-page': '1', + 'X-Per-Page': '1', + 'X-Prev-Page': '2', + 'X-TOTAL': '37', + 'X-Total-Pages': '2', + }; + + const expectedResult = { + perPage: 1, + page: 1, + total: 37, + totalPages: 2, + nextPage: 2, + previousPage: 2, + }; + + store.setPagination(pagination); + expect(store.state.paginationInformation).toEqual(expectedResult); }); }); })(); 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 0000000000000000000000000000000000000000..d1335b5b3040da4c78500d601ca45dac0766accc --- /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 081897f5456459f84f80a187df51a6b3a78cd1f8..5c395c6b2d89b733ffc73a5f06f4c81764ec775a 100644 --- a/spec/javascripts/environments/mock_data.js.es6 +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -1,4 +1,36 @@ 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, @@ -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 0000000000000000000000000000000000000000..aceec1397303197c20bc047cb1267a05c584071d --- /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 006ede21093d313a66823351149b3dec0590520c..f4d3e77e515aa8f78f99c5ab1aa7f75392d6b4a9 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 = { diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 index e84f0dcfe6741aa1608069d691708b25e4c7e903..dd495cb43bc855a71dabc1be0622fdaa7d53f811 100644 --- a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 @@ -34,7 +34,7 @@ describe('Pagination component', () => { component.changePage({ target: { innerText: '1' } }); expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); + expect(changeChanges.two).toEqual(null); }); it('should go to the previous page', () => { @@ -55,7 +55,7 @@ describe('Pagination component', () => { component.changePage({ target: { innerText: 'Prev' } }); expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); + expect(changeChanges.two).toEqual(null); }); it('should go to the next page', () => { @@ -76,7 +76,7 @@ describe('Pagination component', () => { component.changePage({ target: { innerText: 'Next' } }); expect(changeChanges.one).toEqual(5); - expect(changeChanges.two).toEqual('all'); + expect(changeChanges.two).toEqual(null); }); it('should go to the last page', () => { @@ -97,7 +97,7 @@ describe('Pagination component', () => { component.changePage({ target: { innerText: 'Last >>' } }); expect(changeChanges.one).toEqual(10); - expect(changeChanges.two).toEqual('all'); + expect(changeChanges.two).toEqual(null); }); it('should go to the first page', () => { @@ -118,7 +118,7 @@ describe('Pagination component', () => { component.changePage({ target: { innerText: '<< First' } }); expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); + expect(changeChanges.two).toEqual(null); }); it('should do nothing', () => { @@ -139,7 +139,7 @@ describe('Pagination component', () => { component.changePage({ target: { innerText: '...' } }); expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); + expect(changeChanges.two).toEqual(null); }); });