From efa0502386a1868f7120ffd4291175291f0094ed Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 3 Feb 2017 14:28:44 +0100 Subject: Enable grouping and pagination in environmnets API --- .../projects/environments_controller.rb | 4 +++- .../projects/environments_controller_spec.rb | 26 +++++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 0ec8f5bd64a..0d1095da6c2 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -16,7 +16,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController format.html format.json do render json: EnvironmentSerializer - .new(project: @project, user: current_user) + .new(project: @project, user: @current_user) + .with_pagination(request, response) + .within_folders .represent(@environments) end end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 7ac1d62d1b1..4ec91738b9b 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -3,9 +3,13 @@ require 'spec_helper' describe Projects::EnvironmentsController do include ApiHelpers - let(:environment) { create(:environment) } - let(:project) { environment.project } - let(:user) { create(:user) } + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + + let(:environment) do + create(:environment, name: 'production', + project: project) + end before do project.team << [user, :master] @@ -22,14 +26,20 @@ describe Projects::EnvironmentsController do end end - context 'when requesting JSON response' do + context 'when requesting JSON response for folders' do + before do + create(:environment, project: project, name: 'staging/review-1') + create(:environment, project: project, name: 'staging/review-2') + end + it 'responds with correct JSON' do get :index, environment_params(format: :json) - first_environment = json_response.first - - expect(first_environment).not_to be_empty - expect(first_environment['name']). to eq environment.name + expect(json_response.count).to eq 2 + expect(json_response.first['name']).to eq 'production' + expect(json_response.second['name']).to eq 'staging' + expect(json_response.second['size']).to eq 2 + expect(json_response.second['latest']['name']).to eq 'staging/review-2' end end end -- cgit v1.2.1 From b3309bb2fad36372b1e4821410691fa9f720bbe4 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 3 Feb 2017 19:47:56 +0000 Subject: Adjustments to receive new data schema --- .../environments/components/environment.js.es6 | 35 +--- .../components/environment_item.js.es6 | 211 ++++++++------------- .../environments/stores/environments_store.js.es6 | 168 ++-------------- app/assets/stylesheets/pages/environments.scss | 16 +- .../environments/environment_item_spec.js.es6 | 137 ++++++------- .../environments/environments_store_spec.js.es6 | 48 +---- spec/javascripts/environments/mock_data.js.es6 | 180 +++++------------- 7 files changed, 219 insertions(+), 576 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 91553bda4dc..93f65ba0ea8 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -69,12 +69,10 @@ require('./environment_item'); * Toggles loading property. */ created() { - gl.environmentsService = new EnvironmentsService(this.endpoint); + const scope = this.$options.getQueryParameter('scope') || this.visibility; + const endpoint = `${this.endpoint}?scope=${scope}`; - const scope = this.$options.getQueryParameter('scope'); - if (scope) { - this.store.storeVisibility(scope); - } + gl.environmentsService = new EnvironmentsService(endpoint); this.isLoading = true; @@ -82,6 +80,8 @@ require('./environment_item'); .then(resp => resp.json()) .then((json) => { this.store.storeEnvironments(json); + }) + .then(() => { this.isLoading = false; }) .catch(() => { @@ -165,8 +165,7 @@ require('./environment_item');

- New Environment @@ -174,7 +173,7 @@ require('./environment_item');
+ v-if="!isLoading && state.environments.length > 0"> @@ -187,31 +186,15 @@ require('./environment_item'); -
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 33a99231315..ae37bc24396 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -15,12 +15,7 @@ require('./environment_terminal_button'); /** * Envrionment Item Component * - * Used in a hierarchical structure to show folders with children - * in a table. - * Recursive component based on [Tree View](https://vuejs.org/examples/tree-view.html) - * - * See this [issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/22539) - * for more information.15 + * Renders a table row for each environment. */ window.gl = window.gl || {}; @@ -45,11 +40,6 @@ require('./environment_terminal_button'); default: () => ({}), }, - toggleRow: { - type: Function, - required: false, - }, - canCreateDeployment: { type: Boolean, required: false, @@ -76,50 +66,9 @@ require('./environment_terminal_button'); type: String, required: false, }, - - }, - - data() { - return { - rowClass: { - 'children-row': this.model['vue-isChildren'], - }, - }; }, computed: { - - /** - * If an item has a `children` entry it means it is a folder. - * Folder items have different behaviours - it is possible to toggle - * them and show their children. - * - * @returns {Boolean|Undefined} - */ - isFolder() { - return this.model.children && this.model.children.length > 0; - }, - - /** - * If an item is inside a folder structure will return true. - * Used for css purposes. - * - * @returns {Boolean|undefined} - */ - isChildren() { - return this.model['vue-isChildren']; - }, - - /** - * Counts the number of environments in each folder. - * Used to show a badge with the counter. - * - * @returns {Number|Undefined} The number of environments for the current folder. - */ - childrenCounter() { - return this.model.children && this.model.children.length; - }, - /** * Verifies if `last_deployment` key exists in the current Envrionment. * This key is required to render most of the html - this method works has @@ -128,8 +77,8 @@ require('./environment_terminal_button'); * @returns {Boolean} */ hasLastDeploymentKey() { - if (this.model.last_deployment && - !this.$options.isObjectEmpty(this.model.last_deployment)) { + if (this.model.latest.last_deployment && + !this.$options.isObjectEmpty(this.model.latest.last_deployment)) { return true; } return false; @@ -142,8 +91,9 @@ require('./environment_terminal_button'); * @returns {Boolean|Undefined} */ hasManualActions() { - return this.model.last_deployment && this.model.last_deployment.manual_actions && - this.model.last_deployment.manual_actions.length > 0; + return this.model.latest.last_deployment && + this.model.latest.last_deployment.manual_actions && + this.model.latest.last_deployment.manual_actions.length > 0; }, /** @@ -163,8 +113,8 @@ require('./environment_terminal_button'); */ canRetry() { return this.hasLastDeploymentKey && - this.model.last_deployment && - this.model.last_deployment.deployable; + this.model.latest.last_deployment && + this.model.latest.last_deployment.deployable; }, /** @@ -173,9 +123,9 @@ require('./environment_terminal_button'); * @returns {Boolean|Undefined} */ canShowDate() { - return this.model.last_deployment && - this.model.last_deployment.deployable && - this.model.last_deployment.deployable !== undefined; + return this.model.latest.last_deployment && + this.model.latest.last_deployment.deployable && + this.model.latest.last_deployment.deployable !== undefined; }, /** @@ -185,7 +135,7 @@ require('./environment_terminal_button'); */ createdDate() { return gl.environmentsList.timeagoInstance.format( - this.model.last_deployment.deployable.created_at, + this.model.latest.last_deployment.deployable.created_at, ); }, @@ -196,7 +146,7 @@ require('./environment_terminal_button'); */ manualActions() { if (this.hasManualActions) { - return this.model.last_deployment.manual_actions.map((action) => { + return this.model.latest.last_deployment.manual_actions.map((action) => { const parsedAction = { name: gl.text.humanize(action.name), play_path: action.play_path, @@ -213,10 +163,10 @@ require('./environment_terminal_button'); * @returns {String} */ userImageAltDescription() { - if (this.model.last_deployment && - this.model.last_deployment.user && - this.model.last_deployment.user.username) { - return `${this.model.last_deployment.user.username}'s avatar'`; + if (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'`; } return ''; }, @@ -227,9 +177,9 @@ require('./environment_terminal_button'); * @returns {String|Undefined} */ commitTag() { - if (this.model.last_deployment && - this.model.last_deployment.tag) { - return this.model.last_deployment.tag; + if (this.model.latest.last_deployment && + this.model.latest.last_deployment.tag) { + return this.model.latest.last_deployment.tag; } return undefined; }, @@ -240,8 +190,9 @@ require('./environment_terminal_button'); * @returns {Object|Undefined} */ commitRef() { - if (this.model.last_deployment && this.model.last_deployment.ref) { - return this.model.last_deployment.ref; + if (this.model.latest.last_deployment && + this.model.latest.last_deployment.ref) { + return this.model.latest.last_deployment.ref; } return undefined; }, @@ -252,10 +203,10 @@ require('./environment_terminal_button'); * @returns {String|Undefined} */ commitUrl() { - if (this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.commit_path) { - return this.model.last_deployment.commit.commit_path; + if (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; } return undefined; }, @@ -266,10 +217,10 @@ require('./environment_terminal_button'); * @returns {String|Undefined} */ commitShortSha() { - if (this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.short_id) { - return this.model.last_deployment.commit.short_id; + if (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; } return undefined; }, @@ -280,10 +231,10 @@ require('./environment_terminal_button'); * @returns {String|Undefined} */ commitTitle() { - if (this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.title) { - return this.model.last_deployment.commit.title; + if (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; } return undefined; }, @@ -294,10 +245,10 @@ require('./environment_terminal_button'); * @returns {Object|Undefined} */ commitAuthor() { - if (this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.author) { - return this.model.last_deployment.commit.author; + if (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; } return undefined; @@ -309,10 +260,10 @@ require('./environment_terminal_button'); * @returns {String|Undefined} */ retryUrl() { - if (this.model.last_deployment && - this.model.last_deployment.deployable && - this.model.last_deployment.deployable.retry_path) { - return this.model.last_deployment.deployable.retry_path; + if (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; } return undefined; }, @@ -323,7 +274,8 @@ require('./environment_terminal_button'); * @returns {Boolean|Undefined} */ isLastDeployment() { - return this.model.last_deployment && this.model.last_deployment['last?']; + return this.model.latest.last_deployment && + this.model.latest.last_deployment['last?']; }, /** @@ -332,9 +284,9 @@ require('./environment_terminal_button'); * @returns {String} */ buildName() { - if (this.model.last_deployment && - this.model.last_deployment.deployable) { - return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`; + if (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}`; } return ''; }, @@ -345,9 +297,9 @@ require('./environment_terminal_button'); * @returns {String} */ deploymentInternalId() { - if (this.model.last_deployment && - this.model.last_deployment.iid) { - return `#${this.model.last_deployment.iid}`; + if (this.model.latest.last_deployment && + this.model.latest.last_deployment.iid) { + return `#${this.model.latest.last_deployment.iid}`; } return ''; }, @@ -358,8 +310,8 @@ require('./environment_terminal_button'); * @returns {Boolean} */ deploymentHasUser() { - return !this.$options.isObjectEmpty(this.model.last_deployment) && - !this.$options.isObjectEmpty(this.model.last_deployment.user); + return !this.$options.isObjectEmpty(this.model.latest.last_deployment) && + !this.$options.isObjectEmpty(this.model.latest.last_deployment.user); }, /** @@ -369,9 +321,9 @@ require('./environment_terminal_button'); * @returns {Object} */ deploymentUser() { - if (!this.$options.isObjectEmpty(this.model.last_deployment) && - !this.$options.isObjectEmpty(this.model.last_deployment.user)) { - return this.model.last_deployment.user; + if (!this.$options.isObjectEmpty(this.model.latest.last_deployment) && + !this.$options.isObjectEmpty(this.model.latest.last_deployment.user)) { + return this.model.latest.last_deployment.user; } return {}; }, @@ -384,9 +336,9 @@ require('./environment_terminal_button'); * @returns {Boolean} */ shouldRenderBuildName() { - return !this.isFolder && - !this.$options.isObjectEmpty(this.model.last_deployment) && - !this.$options.isObjectEmpty(this.model.last_deployment.deployable); + return !this.model.isFolder && + !this.$options.isObjectEmpty(this.model.latest.last_deployment) && + !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); }, /** @@ -397,9 +349,9 @@ require('./environment_terminal_button'); * @returns {Boolean} */ shouldRenderDeploymentID() { - return !this.isFolder && - !this.$options.isObjectEmpty(this.model.last_deployment) && - this.model.last_deployment.iid !== undefined; + return !this.model.isFolder && + !this.$options.isObjectEmpty(this.model.latest.last_deployment) && + this.model.latest.last_deployment.iid !== undefined; }, }, @@ -420,16 +372,16 @@ require('./environment_terminal_button'); template: ` - -
+ + :href="model.latest.environment_path"> {{model.name}} - + - - + + @@ -437,9 +389,9 @@ require('./environment_terminal_button'); - {{childrenCounter}} + {{model.size}} - + @@ -447,7 +399,7 @@ require('./environment_terminal_button'); {{deploymentInternalId}} - + by + :href="model.latest.last_deployment.deployable.build_path"> {{buildName}} -
+
-

+

No deployments yet

- {{createdDate}} -
+
-
+ :external-url="model.latest.external_url">
+ :stop-url="model.latest.stop_path">
-
+ :terminal-path="model.latest.terminal_path">
diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index 9b4090100da..a533b8b61d6 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -10,181 +10,41 @@ this.state.environments = []; this.state.stoppedCounter = 0; this.state.availableCounter = 0; - this.state.visibility = 'available'; this.state.filteredEnvironments = []; return this; }, /** - * In order to display a tree view we need to modify the received - * data in to a tree structure based on `environment_type` - * sorted alphabetically. - * In each children a `vue-` property will be added. This property will be - * used to know if an item is a children mostly for css purposes. This is - * needed because the children row is a fragment instance and therfore does - * not accept non-prop attributes. * + * Stores the received environments. * - * @example - * it will transform this: - * [ - * { name: "environment", environment_type: "review" }, - * { name: "environment_1", environment_type: null } - * { name: "environment_2, environment_type: "review" } - * ] - * into this: - * [ - * { name: "review", children: - * [ - * { name: "environment", environment_type: "review", vue-isChildren: true}, - * { name: "environment_2", environment_type: "review", vue-isChildren: true} - * ] - * }, - * {name: "environment_1", environment_type: null} - * ] + * Each environment has the following schema + * { name: String, size: Number, latest: Object } * + * 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. * - * @param {Array} environments List of environments. - * @returns {Array} Tree structured array with the received environments. + * @param {Array} environments + * @returns {Array} */ storeEnvironments(environments = []) { - this.state.stoppedCounter = this.countByState(environments, 'stopped'); - this.state.availableCounter = this.countByState(environments, 'available'); - - const environmentsTree = environments.reduce((acc, environment) => { - if (environment.environment_type !== null) { - const occurs = acc.filter(element => element.children && - element.name === environment.environment_type); - - environment['vue-isChildren'] = true; - - if (occurs.length) { - acc[acc.indexOf(occurs[0])].children.push(environment); - acc[acc.indexOf(occurs[0])].children.slice().sort(this.sortByName); - } else { - acc.push({ - name: environment.environment_type, - children: [environment], - isOpen: false, - 'vue-isChildren': environment['vue-isChildren'], - }); - } - } else { - acc.push(environment); - } - - return acc; - }, []).slice().sort(this.sortByName); - - this.state.environments = environmentsTree; - - this.filterEnvironmentsByVisibility(this.state.environments); - - return environmentsTree; - }, - - storeVisibility(visibility) { - this.state.visibility = visibility; - }, - /** - * Given the visibility prop provided by the url query parameter and which - * changes according to the active tab we need to filter which environments - * should be visible. - * - * The environments array is a recursive tree structure and we need to filter - * both root level environments and children environments. - * - * In order to acomplish that, both `filterState` and `filterEnvironmentsByVisibility` - * functions work together. - * The first one works as the filter that verifies if the given environment matches - * the given state. - * The second guarantees both root level and children elements are filtered as well. - * - * Given array of environments will return only - * the environments that match the state stored. - * - * @param {Array} array - * @return {Array} - */ - filterEnvironmentsByVisibility(arr) { - const filteredEnvironments = arr.map((item) => { - if (item.children) { - const filteredChildren = this.filterEnvironmentsByVisibility( - item.children, - ).filter(Boolean); - - if (filteredChildren.length) { - item.children = filteredChildren; - return item; - } - } - - return this.filterState(this.state.visibility, item); - }).filter(Boolean); - - this.state.filteredEnvironments = filteredEnvironments; - return filteredEnvironments; - }, - - /** - * Given the state and the environment, - * returns only if the environment state matches the one provided. - * - * @param {String} state - * @param {Object} environment - * @return {Object} - */ - filterState(state, environment) { - return environment.state === state && environment; - }, - - /** - * Toggles folder open property given the environment type. - * - * @param {String} envType - * @return {Array} - */ - toggleFolder(envType) { - const environments = this.state.environments; - - const environmentsCopy = environments.map((env) => { - if (env['vue-isChildren'] && env.name === envType) { - env.isOpen = !env.isOpen; + const filteredEnvironments = environments.map((env) => { + if (env.size > 1) { + return Object.assign({}, env, { isFolder: true }); } return env; }); - this.state.environments = environmentsCopy; + this.state.environments = filteredEnvironments; - return environmentsCopy; + return filteredEnvironments; }, - /** - * Given an array of environments, returns the number of environments - * that have the given state. - * - * @param {Array} environments - * @param {String} state - * @returns {Number} - */ - countByState(environments, state) { - return environments.filter(env => env.state === state).length; + storeCounts() { + //TODO }, - /** - * Sorts the two objects provided by their name. - * - * @param {Object} a - * @param {Object} b - * @returns {Number} - */ - sortByName(a, b) { - const nameA = a.name.toUpperCase(); - const nameB = b.name.toUpperCase(); - - return nameA < nameB ? -1 : nameA > nameB ? 1 : 0; // eslint-disable-line - }, }; })(); diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 778ef01430e..1d4d85ba6de 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -110,17 +110,19 @@ } } - .children-row .environment-name { - margin-left: 17px; - margin-right: -17px; - } - .folder-icon { - padding: 0 5px 0 0; + margin-right: 3px; + color: $gl-text-color-secondary; + + .fa:nth-child(1) { + margin-right: 3px; + } } .folder-name { cursor: pointer; + text-decoration: none; + color: $gl-text-color-secondary; } } @@ -135,4 +137,4 @@ margin-right: 0; } } -} \ No newline at end of file +} diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index d87cc0996c9..14478f1401d 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -14,33 +14,13 @@ describe('Environment item', () => { beforeEach(() => { mockItem = { name: 'review', - children: [ - { - name: 'review-app', - id: 1, - state: 'available', - external_url: '', - last_deployment: {}, - created_at: '2016-11-07T11:11:16.525Z', - updated_at: '2016-11-10T15:55:58.778Z', - }, - { - name: 'production', - id: 2, - state: 'available', - external_url: '', - last_deployment: {}, - created_at: '2016-11-07T11:11:16.525Z', - updated_at: '2016-11-10T15:55:58.778Z', - }, - ], + size: 3 }; component = new window.gl.environmentsList.EnvironmentItem({ el: document.querySelector('tr#environment-row'), propsData: { model: mockItem, - toggleRow: () => {}, canCreateDeployment: false, canReadEnvironment: true, }, @@ -53,7 +33,7 @@ describe('Environment item', () => { }); it('Should render the number of children in a badge', () => { - expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.children.length); + expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.size); }); }); @@ -63,38 +43,23 @@ describe('Environment item', () => { beforeEach(() => { environment = { - id: 31, name: 'production', - 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: { + 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: { name: 'Administrator', username: 'root', id: 1, @@ -102,34 +67,50 @@ describe('Environment item', () => { 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: { + 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', + }, + ], + }, + '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', }, - '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 window.gl.environmentsList.EnvironmentItem({ el: document.querySelector('tr#environment-row'), propsData: { model: environment, - toggleRow: () => {}, canCreateDeployment: true, canReadEnvironment: true, }, @@ -144,7 +125,7 @@ describe('Environment item', () => { it('should render deployment internal id', () => { expect( component.$el.querySelector('.deployment-column span').textContent, - ).toContain(environment.last_deployment.iid); + ).toContain(environment.latest.last_deployment.iid); expect( component.$el.querySelector('.deployment-column span').textContent, @@ -154,7 +135,7 @@ describe('Environment item', () => { it('should render last deployment date', () => { const timeagoInstance = new timeago(); // eslint-disable-line const formatedDate = timeagoInstance.format( - environment.last_deployment.deployable.created_at, + environment.latest.last_deployment.deployable.created_at, ); expect( @@ -166,7 +147,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.last_deployment.user.web_url); + ).toEqual(environment.latest.last_deployment.user.web_url); }); }); @@ -174,13 +155,13 @@ describe('Environment item', () => { it('Should link to build url provided', () => { expect( component.$el.querySelector('.build-link').getAttribute('href'), - ).toEqual(environment.last_deployment.deployable.build_path); + ).toEqual(environment.latest.last_deployment.deployable.build_path); }); it('Should render deployable name and id', () => { expect( component.$el.querySelector('.build-link').getAttribute('href'), - ).toEqual(environment.last_deployment.deployable.build_path); + ).toEqual(environment.latest.last_deployment.deployable.build_path); }); }); diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6 index 9a8300d3832..d073e120290 100644 --- a/spec/javascripts/environments/environments_store_spec.js.es6 +++ b/spec/javascripts/environments/environments_store_spec.js.es6 @@ -20,50 +20,10 @@ require('./mock_data'); gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList); }); - it('should count stopped environments and save the count in the state', () => { - expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(1); - }); - - it('should count available environments and save the count in the state', () => { - expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(3); - }); - - it('should store environments with same environment_type as sibilings', () => { - expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(3); - - const parentFolder = gl.environmentsList.EnvironmentsStore.state.environments - .filter(env => env.children && env.children.length > 0); - - expect(parentFolder[0].children.length).toBe(2); - expect(parentFolder[0].children[0].environment_type).toBe('review'); - expect(parentFolder[0].children[1].environment_type).toBe('review'); - expect(parentFolder[0].children[0].name).toBe('test-environment'); - expect(parentFolder[0].children[1].name).toBe('test-environment-1'); - }); - - it('should sort the environments alphabetically', () => { - const { environments } = gl.environmentsList.EnvironmentsStore.state; - - expect(environments[0].name).toBe('production'); - expect(environments[1].name).toBe('review'); - expect(environments[1].children[0].name).toBe('test-environment'); - expect(environments[1].children[1].name).toBe('test-environment-1'); - expect(environments[2].name).toBe('review_app'); - }); - }); - - describe('toggleFolder', () => { - beforeEach(() => { - gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList); - }); - - it('should toggle the open property for the given environment', () => { - gl.environmentsList.EnvironmentsStore.toggleFolder('review'); - - const { environments } = gl.environmentsList.EnvironmentsStore.state; - const environment = environments.filter(env => env['vue-isChildren'] === true && env.name === 'review'); - - expect(environment[0].isOpen).toBe(true); + it('should store environments', () => { + expect( + gl.environmentsList.EnvironmentsStore.state.environments.length + ).toBe(environmentsList.length); }); }); }); diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6 index 80e1cbc6f4d..91595c049b0 100644 --- a/spec/javascripts/environments/mock_data.js.es6 +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -1,153 +1,59 @@ const environmentsList = [ { - id: 31, - name: 'production', - state: 'available', - external_url: 'https://www.gitlab.com', - environment_type: null, - last_deployment: { - id: 64, - iid: 5, - sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', - ref: { - name: 'master', - ref_url: 'http://localhost:3000/root/ci-folders/tree/master', - }, - tag: false, - '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, - 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: 1278, - name: 'build', - build_path: '/root/ci-folders/builds/1278', - retry_path: '/root/ci-folders/builds/1278/retry', - }, - manual_actions: [], + name: 'DEV', + size: 1, + latest: { + id: 7, + name: 'DEV', + 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', }, - 'stop_action?': true, - environment_path: '/root/ci-folders/environments/31', - created_at: '2016-11-07T11:11:16.525Z', - updated_at: '2016-11-07T11:11:16.525Z', }, { - id: 32, - name: 'review_app', - state: 'stopped', - external_url: 'https://www.gitlab.com', - environment_type: null, - last_deployment: { - id: 64, - iid: 5, - sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', - ref: { - name: 'master', - ref_url: 'http://localhost:3000/root/ci-folders/tree/master', - }, - tag: false, - '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, - 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: 1278, - name: 'build', - build_path: '/root/ci-folders/builds/1278', - retry_path: '/root/ci-folders/builds/1278/retry', - }, - manual_actions: [], + name: 'build', + size: 5, + latest: { + 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', }, - 'stop_action?': false, - environment_path: '/root/ci-folders/environments/31', - created_at: '2016-11-07T11:11:16.525Z', - updated_at: '2016-11-07T11:11:16.525Z', - }, - { - id: 33, - name: 'test-environment', - state: 'available', - environment_type: 'review', - last_deployment: null, - 'stop_action?': true, - environment_path: '/root/ci-folders/environments/31', - created_at: '2016-11-07T11:11:16.525Z', - updated_at: '2016-11-07T11:11:16.525Z', - }, - { - id: 34, - name: 'test-environment-1', - state: 'available', - environment_type: 'review', - last_deployment: null, - 'stop_action?': true, - environment_path: '/root/ci-folders/environments/31', - created_at: '2016-11-07T11:11:16.525Z', - updated_at: '2016-11-07T11:11:16.525Z', }, ]; window.environmentsList = environmentsList; const environment = { - id: 4, - name: 'production', - state: 'available', - external_url: 'http://production.', - environment_type: null, - last_deployment: {}, - 'stop_action?': false, - environment_path: '/root/review-app/environments/4', - stop_path: '/root/review-app/environments/4/stop', - created_at: '2016-12-16T11:51:04.690Z', - updated_at: '2016-12-16T12:04:51.133Z', + name: 'DEV', + size: 1, + latest: { + id: 7, + name: 'DEV', + 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', + }, }; window.environment = environment; -- cgit v1.2.1 From 2aeb45bdb55a634a490bf535242a6f8c10aaa938 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 4 Feb 2017 10:38:16 +0100 Subject: Add support for environment scopes in controller --- .../projects/environments_controller.rb | 19 ++++--- .../projects/environments_controller_spec.rb | 59 ++++++++++++++++++---- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 0d1095da6c2..34b081b3e41 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -9,17 +9,22 @@ class Projects::EnvironmentsController < Projects::ApplicationController before_action :verify_api_request!, only: :terminal_websocket_authorize def index - @scope = params[:scope] - @environments = project.environments.includes(:last_deployment) + @environments = project.environments + .includes(:last_deployment) + .with_state(params[:scope] || :available) respond_to do |format| format.html format.json do - render json: EnvironmentSerializer - .new(project: @project, user: @current_user) - .with_pagination(request, response) - .within_folders - .represent(@environments) + render json: { + environments: EnvironmentSerializer + .new(project: @project, user: @current_user) + .with_pagination(request, response) + .within_folders + .represent(@environments), + available_count: project.environments.available.count, + stopped_count: project.environments.stopped.count + } end end end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 4ec91738b9b..84d119f1867 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -7,8 +7,7 @@ describe Projects::EnvironmentsController do let(:project) { create(:empty_project) } let(:environment) do - create(:environment, name: 'production', - project: project) + create(:environment, name: 'production', project: project) end before do @@ -28,18 +27,56 @@ describe Projects::EnvironmentsController do context 'when requesting JSON response for folders' do before do - create(:environment, project: project, name: 'staging/review-1') - create(:environment, project: project, name: 'staging/review-2') + create(:environment, project: project, + name: 'staging/review-1', + state: :available) + + create(:environment, project: project, + name: 'staging/review-2', + state: :available) + + create(:environment, project: project, + name: 'staging/review-3', + state: :stopped) + end + + let(:environments) { json_response['environments'] } + + context 'when requesting available environments scope' do + before do + get :index, environment_params(format: :json, scope: :available) + end + + it 'responds with a payload describing available environments' do + expect(environments.count).to eq 2 + expect(environments.first['name']).to eq 'production' + expect(environments.second['name']).to eq 'staging' + expect(environments.second['size']).to eq 2 + expect(environments.second['latest']['name']).to eq 'staging/review-2' + end + + it 'contains values describing environment scopes sizes' do + expect(json_response['available_count']).to eq 3 + expect(json_response['stopped_count']).to eq 1 + end end - it 'responds with correct JSON' do - get :index, environment_params(format: :json) + context 'when requesting stopped environments scope' do + before do + get :index, environment_params(format: :json, scope: :stopped) + end - expect(json_response.count).to eq 2 - expect(json_response.first['name']).to eq 'production' - expect(json_response.second['name']).to eq 'staging' - expect(json_response.second['size']).to eq 2 - expect(json_response.second['latest']['name']).to eq 'staging/review-2' + it 'responds with a payload describing stopped environments' do + expect(environments.count).to eq 1 + expect(environments.first['name']).to eq 'staging' + expect(environments.first['size']).to eq 1 + expect(environments.first['latest']['name']).to eq 'staging/review-3' + end + + it 'contains values describing environment scopes sizes' do + expect(json_response['available_count']).to eq 3 + expect(json_response['stopped_count']).to eq 1 + end end end end -- cgit v1.2.1 From 71899e10878455277b7e2ed120d9424489a9d72b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 3 Feb 2017 21:10:50 +0000 Subject: Adjustments for the new response with counters a --- .../environments/components/environment.js.es6 | 8 ++++++-- .../environments/stores/environments_store.js.es6 | 22 ++++++++++++++++++++-- .../environments/environments_store_spec.js.es6 | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 93f65ba0ea8..3e899e5895b 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -7,6 +7,7 @@ window.Vue = require('vue'); window.Vue.use(require('vue-resource')); require('../services/environments_service'); require('./environment_item'); +require('../../vue_pagination/index'); (() => { window.gl = window.gl || {}; @@ -79,7 +80,9 @@ require('./environment_item'); return gl.environmentsService.all() .then(resp => resp.json()) .then((json) => { - this.store.storeEnvironments(json); + this.store.storeAvailableCount(json.available_count); + this.store.storeStoppedCount(json.stopped_count); + this.store.storeEnvironments(json.environments); }) .then(() => { this.isLoading = false; @@ -131,7 +134,8 @@ require('./environment_item'); {{state.availableCounter}} -
  • +
  • +
  • Stopped diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index a533b8b61d6..c05f353647c 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -42,8 +42,26 @@ return filteredEnvironments; }, - storeCounts() { - //TODO + /** + * Stores the number of available environments. + * + * @param {Number} count = 0 + * @return {Number} + */ + storeAvailableCount(count = 0) { + this.state.availableCounter = count; + return count; + }, + + /** + * Stores the number of closed environments. + * + * @param {Number} count = 0 + * @return {Number} + */ + storeStoppedCount(count = 0) { + this.state.stoppedCounter = count; + return count; }, }; diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6 index d073e120290..ef4b06dea40 100644 --- a/spec/javascripts/environments/environments_store_spec.js.es6 +++ b/spec/javascripts/environments/environments_store_spec.js.es6 @@ -22,7 +22,7 @@ require('./mock_data'); it('should store environments', () => { expect( - gl.environmentsList.EnvironmentsStore.state.environments.length + gl.environmentsList.EnvironmentsStore.state.environments.length, ).toBe(environmentsList.length); }); }); -- cgit v1.2.1 From 3c39cea60cede6399e7fae517b9ab3c5c86143a7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 6 Feb 2017 15:32:40 +0000 Subject: Environments folder should be underlined when on hover and on focus like a regular link --- app/assets/stylesheets/pages/environments.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 1d4d85ba6de..606cf501b82 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -121,7 +121,6 @@ .folder-name { cursor: pointer; - text-decoration: none; color: $gl-text-color-secondary; } } -- cgit v1.2.1 From 72f76c4d4981b14d1cc721b1f379832f74a509f8 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 11:08:44 +0000 Subject: Remove pagination from this MR --- app/assets/javascripts/environments/components/environment.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 3e899e5895b..073fbdffe21 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -7,7 +7,6 @@ window.Vue = require('vue'); window.Vue.use(require('vue-resource')); require('../services/environments_service'); require('./environment_item'); -require('../../vue_pagination/index'); (() => { window.gl = window.gl || {}; -- cgit v1.2.1 From acb68ae69e382a7bd949386f14d2d27f6cada086 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 11:16:11 +0000 Subject: Resolve 500 error --- app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 | 2 +- app/controllers/projects/environments_controller.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 index d3229f9f730..a575ae3e441 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 @@ -6,7 +6,7 @@ Vue.http.interceptors.push((request, next) => { Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; next((response) => { - if (typeof response.data === 'string') { + if (typeof response.data === 'string' && response.status !== 500) { response.data = JSON.parse(response.data); } diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 34b081b3e41..2252ece68ce 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -10,7 +10,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController def index @environments = project.environments - .includes(:last_deployment) .with_state(params[:scope] || :available) respond_to do |format| -- cgit v1.2.1 From 6077dea7b1f6fb857f786d86cd7cf9e5204ec9a9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 11:18:21 +0000 Subject: Remove store from global namespace, use CSJ instead --- .../environments/components/environment.js.es6 | 12 +-- .../environments/environments_bundle.js.es6 | 7 -- .../environments/stores/environments_store.js.es6 | 120 ++++++++++----------- .../vue_shared/vue_resource_interceptor.js.es6 | 2 +- 4 files changed, 65 insertions(+), 76 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 073fbdffe21..f07b650ca9f 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -7,18 +7,12 @@ window.Vue = require('vue'); window.Vue.use(require('vue-resource')); require('../services/environments_service'); require('./environment_item'); +const Store = require('../stores/environments_store'); (() => { window.gl = window.gl || {}; gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', { - props: { - store: { - type: Object, - required: true, - default: () => ({}), - }, - }, components: { 'environment-item': gl.environmentsList.EnvironmentItem, @@ -26,9 +20,11 @@ require('./environment_item'); data() { const environmentsData = document.querySelector('#environments-list-view').dataset; + const store = new Store(); return { - state: this.store.state, + store, + state: store.state, visibility: 'available', isLoading: false, cssContainerClass: environmentsData.cssClass, diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 05c59d92fd4..912f50aeec1 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -1,5 +1,4 @@ window.Vue = require('vue'); -require('./stores/environments_store'); require('./components/environment'); require('../vue_shared/vue_resource_interceptor'); @@ -9,14 +8,8 @@ $(() => { if (gl.EnvironmentsListApp) { gl.EnvironmentsListApp.$destroy(true); } - const Store = gl.environmentsList.EnvironmentsStore; gl.EnvironmentsListApp = new gl.environmentsList.EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), - - propsData: { - store: Store.create(), - }, - }); }); diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index c05f353647c..52bd6b94551 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -1,68 +1,68 @@ -/* eslint-disable no-param-reassign */ -(() => { - window.gl = window.gl || {}; - window.gl.environmentsList = window.gl.environmentsList || {}; +/** + * Environments Store. + * + * Stores received environments, count of stopped environments and count of + * available environments. + */ +class EnvironmentsStore { + constructor() { + this.state = {}; + this.state.environments = []; + this.state.stoppedCounter = 0; + this.state.availableCounter = 0; + this.state.filteredEnvironments = []; - gl.environmentsList.EnvironmentsStore = { - state: {}, + return this; + } - create() { - this.state.environments = []; - this.state.stoppedCounter = 0; - this.state.availableCounter = 0; - this.state.filteredEnvironments = []; + /** + * + * Stores the received environments. + * + * Each environment has the following schema + * { name: String, size: Number, latest: Object } + * + * 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. + * + * @param {Array} environments + * @returns {Array} + */ + storeEnvironments(environments = []) { + const filteredEnvironments = environments.map((env) => { + if (env.size > 1) { + return Object.assign({}, env, { isFolder: true }); + } - return this; - }, + return env; + }); - /** - * - * Stores the received environments. - * - * Each environment has the following schema - * { name: String, size: Number, latest: Object } - * - * 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. - * - * @param {Array} environments - * @returns {Array} - */ - storeEnvironments(environments = []) { - const filteredEnvironments = environments.map((env) => { - if (env.size > 1) { - return Object.assign({}, env, { isFolder: true }); - } + this.state.environments = filteredEnvironments; - return env; - }); + return filteredEnvironments; + } - this.state.environments = filteredEnvironments; + /** + * Stores the number of available environments. + * + * @param {Number} count = 0 + * @return {Number} + */ + storeAvailableCount(count = 0) { + this.state.availableCounter = count; + return count; + } - return filteredEnvironments; - }, + /** + * Stores the number of closed environments. + * + * @param {Number} count = 0 + * @return {Number} + */ + storeStoppedCount(count = 0) { + this.state.stoppedCounter = count; + return count; + } +} - /** - * Stores the number of available environments. - * - * @param {Number} count = 0 - * @return {Number} - */ - storeAvailableCount(count = 0) { - this.state.availableCounter = count; - return count; - }, - - /** - * Stores the number of closed environments. - * - * @param {Number} count = 0 - * @return {Number} - */ - storeStoppedCount(count = 0) { - this.state.stoppedCounter = count; - return count; - }, - - }; -})(); +module.exports = EnvironmentsStore; diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 index a575ae3e441..d3229f9f730 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 @@ -6,7 +6,7 @@ Vue.http.interceptors.push((request, next) => { Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; next((response) => { - if (typeof response.data === 'string' && response.status !== 500) { + if (typeof response.data === 'string') { response.data = JSON.parse(response.data); } -- cgit v1.2.1 From 26a951b7ab272b80df1281464aaf656570fe214e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 11:35:53 +0000 Subject: Use CJS for tests. Updates expected model in tests --- .../environments/stores/environments_store.js.es6 | 1 - .../environments/environment_item_spec.js.es6 | 8 +++-- .../environments/environment_spec.js.es6 | 18 ++++------- .../environments/environments_store_spec.js.es6 | 37 ++++++++++++---------- spec/javascripts/environments/mock_data.js.es6 | 8 ++--- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index 52bd6b94551..749dd882188 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -10,7 +10,6 @@ class EnvironmentsStore { this.state.environments = []; this.state.stoppedCounter = 0; this.state.availableCounter = 0; - this.state.filteredEnvironments = []; return this; } diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index 14478f1401d..5dc7ef5ad76 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -1,7 +1,7 @@ window.timeago = require('vendor/timeago'); require('~/environments/components/environment_item'); -describe('Environment item', () => { +fdescribe('Environment item', () => { preloadFixtures('static/environments/table.html.raw'); beforeEach(() => { loadFixtures('static/environments/table.html.raw'); @@ -14,7 +14,11 @@ describe('Environment item', () => { beforeEach(() => { mockItem = { name: 'review', - size: 3 + size: 3, + isFolder: true, + latest: { + environment_path: 'url', + }, }; component = new window.gl.environmentsList.EnvironmentItem({ diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 index 87eda136122..8b96f4b09db 100644 --- a/spec/javascripts/environments/environment_spec.js.es6 +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -1,9 +1,8 @@ /* global Vue, environment */ require('~/flash'); -require('~/environments/stores/environments_store'); require('~/environments/components/environment'); -require('./mock_data'); +const { environment } = require('./mock_data'); describe('Environment', () => { preloadFixtures('static/environments/environments.html.raw'); @@ -35,9 +34,6 @@ describe('Environment', () => { it('should render the empty state', (done) => { component = new gl.environmentsList.EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), - propsData: { - store: gl.environmentsList.EnvironmentsStore.create(), - }, }); setTimeout(() => { @@ -56,7 +52,11 @@ describe('Environment', () => { describe('with environments', () => { const environmentsResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([environment]), { + next(request.respondWith(JSON.stringify({ + environments: [environment], + stopped_count: 1, + available_count: 0, + }), { status: 200, })); }; @@ -74,9 +74,6 @@ describe('Environment', () => { it('should render a table with environments', (done) => { component = new gl.environmentsList.EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), - propsData: { - store: gl.environmentsList.EnvironmentsStore.create(), - }, }); setTimeout(() => { @@ -109,9 +106,6 @@ describe('Environment', () => { it('should render empty state', (done) => { component = new gl.environmentsList.EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), - propsData: { - store: gl.environmentsList.EnvironmentsStore.create(), - }, }); setTimeout(() => { diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6 index ef4b06dea40..861136c621f 100644 --- a/spec/javascripts/environments/environments_store_spec.js.es6 +++ b/spec/javascripts/environments/environments_store_spec.js.es6 @@ -1,30 +1,33 @@ -/* global environmentsList */ - -require('~/environments/stores/environments_store'); -require('./mock_data'); +const Store = require('~/environments/stores/environments_store'); +const { environmentsList } = require('./mock_data'); (() => { describe('Store', () => { + let store; + beforeEach(() => { - gl.environmentsList.EnvironmentsStore.create(); + store = new Store(); }); it('should start with a blank state', () => { - expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(0); - expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(0); - expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(0); + expect(store.state.environments.length).toBe(0); + expect(store.state.stoppedCounter).toBe(0); + expect(store.state.availableCounter).toBe(0); }); - describe('store environments', () => { - beforeEach(() => { - gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList); - }); + it('should store environments', () => { + store.storeEnvironments(environmentsList); + expect(store.state.environments.length).toBe(environmentsList.length); + }); + + it('should store available count', () => { + store.storeAvailableCount(2); + expect(store.state.availableCounter).toBe(2); + }); - it('should store environments', () => { - expect( - gl.environmentsList.EnvironmentsStore.state.environments.length, - ).toBe(environmentsList.length); - }); + it('should store stopped count', () => { + store.storeStoppedCount(2); + expect(store.state.stoppedCounter).toBe(2); }); }); })(); diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6 index 91595c049b0..bdecc95d219 100644 --- a/spec/javascripts/environments/mock_data.js.es6 +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -1,4 +1,3 @@ - const environmentsList = [ { name: 'DEV', @@ -36,8 +35,6 @@ const environmentsList = [ }, ]; -window.environmentsList = environmentsList; - const environment = { name: 'DEV', size: 1, @@ -56,4 +53,7 @@ const environment = { }, }; -window.environment = environment; +module.exports = { + environmentsList, + environment +}; -- cgit v1.2.1 From 8f3678f1dea1134ebc41f0d519424db673b5f764 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 11:38:59 +0000 Subject: Use CJS in environments service --- .../environments/components/environment.js.es6 | 6 +++--- .../services/environments_service.js.es6 | 20 ++++---------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index f07b650ca9f..6f84cc625ac 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -5,7 +5,7 @@ window.Vue = require('vue'); window.Vue.use(require('vue-resource')); -require('../services/environments_service'); +const EnvironmentsService = require('../services/environments_service'); require('./environment_item'); const Store = require('../stores/environments_store'); @@ -68,11 +68,11 @@ const Store = require('../stores/environments_store'); const scope = this.$options.getQueryParameter('scope') || this.visibility; const endpoint = `${this.endpoint}?scope=${scope}`; - gl.environmentsService = new EnvironmentsService(endpoint); + const service = new EnvironmentsService(endpoint); this.isLoading = true; - return gl.environmentsService.all() + return service.all() .then(resp => resp.json()) .then((json) => { this.store.storeAvailableCount(json.available_count); diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js.es6 index fab8d977f58..9b33fd02c53 100644 --- a/app/assets/javascripts/environments/services/environments_service.js.es6 +++ b/app/assets/javascripts/environments/services/environments_service.js.es6 @@ -1,20 +1,8 @@ -/* globals Vue */ -/* eslint-disable no-unused-vars, no-param-reassign */ +const Vue = window.Vue = require('vue'); class EnvironmentsService { - - constructor(root) { - Vue.http.options.root = root; - - this.environments = Vue.resource(root); - - Vue.http.interceptors.push((request, next) => { - // needed in order to not break the tests. - if ($.rails) { - request.headers['X-CSRF-Token'] = $.rails.csrfToken(); - } - next(); - }); + constructor(endpoint) { + this.environments = Vue.resource(endpoint); } all() { @@ -22,4 +10,4 @@ class EnvironmentsService { } } -window.EnvironmentsService = EnvironmentsService; +module.exports = EnvironmentsService; -- cgit v1.2.1 From d6ae01da55bc8e0903c9ff13211d2e5dd29bff1f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 11:52:22 +0000 Subject: Use CJS in all environments components --- .../environments/components/environment.js.es6 | 362 ++++---- .../components/environment_actions.js.es6 | 87 +- .../components/environment_external_url.js.es6 | 36 +- .../components/environment_item.js.es6 | 918 ++++++++++----------- .../components/environment_rollback.js.es6 | 55 +- .../components/environment_stop.js.es6 | 45 +- .../components/environment_terminal_button.js.es6 | 47 +- .../environments/environments_bundle.js.es6 | 5 +- .../services/environments_service.js.es6 | 2 +- .../vue_shared/components/commit.js.es6 | 1 + .../environments/environment_actions_spec.js.es6 | 6 +- .../environment_external_url_spec.js.es6 | 4 +- .../environments/environment_item_spec.js.es6 | 8 +- .../environments/environment_rollback_spec.js.es6 | 8 +- .../environments/environment_spec.js.es6 | 11 +- .../environments/environment_stop_spec.js.es6 | 4 +- 16 files changed, 782 insertions(+), 817 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 6f84cc625ac..11a965fcddf 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -1,205 +1,199 @@ /* eslint-disable no-param-reassign, no-new */ -/* global Vue */ -/* global EnvironmentsService */ /* global Flash */ -window.Vue = require('vue'); -window.Vue.use(require('vue-resource')); +const Vue = require('vue'); +Vue.use(require('vue-resource')); const EnvironmentsService = require('../services/environments_service'); -require('./environment_item'); +const EnvironmentItem = require('./environment_item'); const Store = require('../stores/environments_store'); -(() => { - window.gl = window.gl || {}; - - gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', { - - components: { - 'environment-item': gl.environmentsList.EnvironmentItem, - }, - - data() { - const environmentsData = document.querySelector('#environments-list-view').dataset; - const store = new Store(); - - return { - store, - state: store.state, - visibility: 'available', - isLoading: false, - cssContainerClass: environmentsData.cssClass, - endpoint: environmentsData.environmentsDataEndpoint, - canCreateDeployment: environmentsData.canCreateDeployment, - canReadEnvironment: environmentsData.canReadEnvironment, - canCreateEnvironment: environmentsData.canCreateEnvironment, - projectEnvironmentsPath: environmentsData.projectEnvironmentsPath, - projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, - newEnvironmentPath: environmentsData.newEnvironmentPath, - helpPagePath: environmentsData.helpPagePath, - commitIconSvg: environmentsData.commitIconSvg, - playIconSvg: environmentsData.playIconSvg, - terminalIconSvg: environmentsData.terminalIconSvg, - }; +module.exports = Vue.component('environment-component', { + + components: { + 'environment-item': EnvironmentItem, + }, + + data() { + const environmentsData = document.querySelector('#environments-list-view').dataset; + const store = new Store(); + + return { + store, + state: store.state, + visibility: 'available', + isLoading: false, + cssContainerClass: environmentsData.cssClass, + endpoint: environmentsData.environmentsDataEndpoint, + canCreateDeployment: environmentsData.canCreateDeployment, + canReadEnvironment: environmentsData.canReadEnvironment, + canCreateEnvironment: environmentsData.canCreateEnvironment, + projectEnvironmentsPath: environmentsData.projectEnvironmentsPath, + projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, + newEnvironmentPath: environmentsData.newEnvironmentPath, + helpPagePath: environmentsData.helpPagePath, + commitIconSvg: environmentsData.commitIconSvg, + playIconSvg: environmentsData.playIconSvg, + terminalIconSvg: environmentsData.terminalIconSvg, + }; + }, + + computed: { + scope() { + return this.$options.getQueryParameter('scope'); }, - computed: { - scope() { - return this.$options.getQueryParameter('scope'); - }, - - canReadEnvironmentParsed() { - return this.$options.convertPermissionToBoolean(this.canReadEnvironment); - }, - - canCreateDeploymentParsed() { - return this.$options.convertPermissionToBoolean(this.canCreateDeployment); - }, - - canCreateEnvironmentParsed() { - return this.$options.convertPermissionToBoolean(this.canCreateEnvironment); - }, + canReadEnvironmentParsed() { + return this.$options.convertPermissionToBoolean(this.canReadEnvironment); }, - /** - * Fetches all the environments and stores them. - * Toggles loading property. - */ - created() { - const scope = this.$options.getQueryParameter('scope') || this.visibility; - const endpoint = `${this.endpoint}?scope=${scope}`; - - 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(() => { - this.isLoading = false; - }) - .catch(() => { - this.isLoading = false; - new Flash('An error occurred while fetching the environments.', 'alert'); - }); + canCreateDeploymentParsed() { + return this.$options.convertPermissionToBoolean(this.canCreateDeployment); }, - /** - * 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]; + canCreateEnvironmentParsed() { + return this.$options.convertPermissionToBoolean(this.canCreateEnvironment); }, - - /** - * Converts permission provided as strings to booleans. - * @param {String} string - * @returns {Boolean} - */ - convertPermissionToBoolean(string) { - return string === 'true'; + }, + + /** + * Fetches all the environments and stores them. + * Toggles loading property. + */ + created() { + const scope = this.$options.getQueryParameter('scope') || this.visibility; + const endpoint = `${this.endpoint}?scope=${scope}`; + + 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(() => { + this.isLoading = false; + }) + .catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the environments.', 'alert'); + }); + }, + + /** + * 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); }, + }, + + template: ` +
    + - methods: { - toggleRow(model) { - return this.store.toggleFolder(model.name); - }, - }, +
    +
    + +
    - template: ` -
    -
    - - -
    -
    - -
    - -
    -

    - You don't have any environments right now. -

    -

    - Environments are places where code gets deployed, such as staging or production. -
    - - Read more about environments - -

    - - - New Environment - -
    - -
    - - - - - - - - - - - - - - -
    EnvironmentLast deploymentJobCommitUpdated
    -
    +
    + + + + + + + + + + + + + + +
    EnvironmentLast deploymentJobCommitUpdated
    - `, - }); -})(); +
    + `, +}); diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6 index ed1c78945db..c5a714d9673 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js.es6 +++ b/app/assets/javascripts/environments/components/environment_actions.js.es6 @@ -1,50 +1,43 @@ -/* global Vue */ - -window.Vue = require('vue'); - -(() => { - window.gl = window.gl || {}; - window.gl.environmentsList = window.gl.environmentsList || {}; - - gl.environmentsList.ActionsComponent = Vue.component('actions-component', { - props: { - actions: { - type: Array, - required: false, - default: () => [], - }, - - playIconSvg: { - type: String, - required: false, - }, +const Vue = require('vue'); + +module.exports = Vue.component('actions-component', { + props: { + actions: { + type: Array, + required: false, + default: () => [], }, - template: ` -
    - + playIconSvg: { + type: String, + required: false, + }, + }, + + template: ` +
    + - `, - }); -})(); +
    + `, +}); diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6 index 28cc0022d17..2599bba3c59 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.js.es6 +++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6 @@ -1,23 +1,19 @@ -/* global Vue */ +/** + * Renders the external url link in environments table. + */ +const Vue = require('vue'); -window.Vue = require('vue'); - -(() => { - window.gl = window.gl || {}; - window.gl.environmentsList = window.gl.environmentsList || {}; - - gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', { - props: { - externalUrl: { - type: String, - default: '', - }, +module.exports = Vue.component('external-url-component', { + props: { + externalUrl: { + type: String, + default: '', }, + }, - template: ` - - - - `, - }); -})(); + template: ` + + + + `, +}); diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index ae37bc24396..cf79969471e 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -1,489 +1,481 @@ -/* global Vue */ -/* global timeago */ -window.Vue = require('vue'); -window.timeago = require('vendor/timeago'); +const Vue = require('vue'); +const Timeago = require('vendor/timeago'); require('../../lib/utils/text_utility'); require('../../vue_shared/components/commit'); -require('./environment_actions'); -require('./environment_external_url'); -require('./environment_stop'); -require('./environment_rollback'); -require('./environment_terminal_button'); +const ActionsComponent = require('./environment_actions'); +const ExternalUrlComponent = require('./environment_external_url'); +const StopComponent = require('./environment_stop'); +const RollbackComponent = require('./environment_rollback'); +const TerminalButtonComponent = require('./environment_terminal_button'); + +/** + * Envrionment Item Component + * + * Renders a table row for each environment. + */ + +const timeagoInstance = new Timeago(); + +module.exports = Vue.component('environment-item', { + + components: { + 'commit-component': gl.CommitComponent, + 'actions-component': ActionsComponent, + 'external-url-component': ExternalUrlComponent, + 'stop-component': StopComponent, + 'rollback-component': RollbackComponent, + 'terminal-button-component': TerminalButtonComponent, + }, + + props: { + model: { + type: Object, + required: true, + default: () => ({}), + }, -(() => { - /** - * Envrionment Item Component - * - * Renders a table row for each environment. - */ + canCreateDeployment: { + type: Boolean, + required: false, + default: false, + }, - window.gl = window.gl || {}; - window.gl.environmentsList = window.gl.environmentsList || {}; - window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line + canReadEnvironment: { + type: Boolean, + required: false, + default: false, + }, - gl.environmentsList.EnvironmentItem = Vue.component('environment-item', { + commitIconSvg: { + type: String, + required: false, + }, - components: { - 'commit-component': gl.CommitComponent, - 'actions-component': gl.environmentsList.ActionsComponent, - 'external-url-component': gl.environmentsList.ExternalUrlComponent, - 'stop-component': gl.environmentsList.StopComponent, - 'rollback-component': gl.environmentsList.RollbackComponent, - 'terminal-button-component': gl.environmentsList.TerminalButtonComponent, + playIconSvg: { + type: String, + required: false, }, - props: { - model: { - type: Object, - required: true, - default: () => ({}), - }, - - canCreateDeployment: { - type: Boolean, - required: false, - default: false, - }, - - canReadEnvironment: { - type: Boolean, - required: false, - default: false, - }, - - commitIconSvg: { - type: String, - required: false, - }, - - playIconSvg: { - type: String, - required: false, - }, - - terminalIconSvg: { - type: String, - required: false, - }, + terminalIconSvg: { + type: String, + required: false, }, + }, - computed: { - /** - * Verifies if `last_deployment` key exists in the current Envrionment. - * This key is required to render most of the html - this method works has - * an helper. - * - * @returns {Boolean} - */ - hasLastDeploymentKey() { - if (this.model.latest.last_deployment && - !this.$options.isObjectEmpty(this.model.latest.last_deployment)) { - return true; - } - return false; - }, - - /** - * Verifies is the given environment has manual actions. - * Used to verify if we should render them or nor. - * - * @returns {Boolean|Undefined} - */ - hasManualActions() { - return this.model.latest.last_deployment && - this.model.latest.last_deployment.manual_actions && - this.model.latest.last_deployment.manual_actions.length > 0; - }, - - /** - * Returns the value of the `stop_action?` key provided in the response. - * - * @returns {Boolean} - */ - hasStopAction() { - return this.model['stop_action?']; - }, - - /** - * Verifies if the `deployable` key is present in `last_deployment` key. - * Used to verify whether we should or not render the rollback partial. - * - * @returns {Boolean|Undefined} - */ - canRetry() { - return this.hasLastDeploymentKey && - this.model.latest.last_deployment && - this.model.latest.last_deployment.deployable; - }, - - /** - * Verifies if the date to be shown is present. - * - * @returns {Boolean|Undefined} - */ - canShowDate() { - return this.model.latest.last_deployment && - this.model.latest.last_deployment.deployable && - this.model.latest.last_deployment.deployable !== undefined; - }, - - /** - * Human readable date. - * - * @returns {String} - */ - createdDate() { - return gl.environmentsList.timeagoInstance.format( - this.model.latest.last_deployment.deployable.created_at, - ); - }, - - /** - * Returns the manual actions with the name parsed. - * - * @returns {Array.|Undefined} - */ - manualActions() { - if (this.hasManualActions) { - return this.model.latest.last_deployment.manual_actions.map((action) => { - const parsedAction = { - name: gl.text.humanize(action.name), - play_path: action.play_path, - }; - return parsedAction; - }); - } - return []; - }, - - /** - * Builds the string used in the user image alt attribute. - * - * @returns {String} - */ - userImageAltDescription() { - if (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'`; - } - return ''; - }, - - /** - * If provided, returns the commit tag. - * - * @returns {String|Undefined} - */ - commitTag() { - if (this.model.latest.last_deployment && - this.model.latest.last_deployment.tag) { - return this.model.latest.last_deployment.tag; - } - return undefined; - }, - - /** - * If provided, returns the commit ref. - * - * @returns {Object|Undefined} - */ - commitRef() { - if (this.model.latest.last_deployment && - this.model.latest.last_deployment.ref) { - return this.model.latest.last_deployment.ref; - } - return undefined; - }, - - /** - * If provided, returns the commit url. - * - * @returns {String|Undefined} - */ - commitUrl() { - if (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; - } - return undefined; - }, - - /** - * If provided, returns the commit short sha. - * - * @returns {String|Undefined} - */ - commitShortSha() { - if (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; - } - return undefined; - }, - - /** - * If provided, returns the commit title. - * - * @returns {String|Undefined} - */ - commitTitle() { - if (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; - } - return undefined; - }, - - /** - * If provided, returns the commit tag. - * - * @returns {Object|Undefined} - */ - commitAuthor() { - if (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; - } - - return undefined; - }, - - /** - * Verifies if the `retry_path` key is present and returns its value. - * - * @returns {String|Undefined} - */ - retryUrl() { - if (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; - } - return undefined; - }, - - /** - * Verifies if the `last?` key is present and returns its value. - * - * @returns {Boolean|Undefined} - */ - isLastDeployment() { - return this.model.latest.last_deployment && - this.model.latest.last_deployment['last?']; - }, - - /** - * Builds the name of the builds needed to display both the name and the id. - * - * @returns {String} - */ - buildName() { - if (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}`; - } - return ''; - }, - - /** - * Builds the needed string to show the internal id. - * - * @returns {String} - */ - deploymentInternalId() { - if (this.model.latest.last_deployment && - this.model.latest.last_deployment.iid) { - return `#${this.model.latest.last_deployment.iid}`; - } - return ''; - }, - - /** - * Verifies if the user object is present under last_deployment object. - * - * @returns {Boolean} - */ - deploymentHasUser() { - return !this.$options.isObjectEmpty(this.model.latest.last_deployment) && - !this.$options.isObjectEmpty(this.model.latest.last_deployment.user); - }, - - /** - * Returns the user object nested with the last_deployment object. - * Used to render the template. - * - * @returns {Object} - */ - deploymentUser() { - if (!this.$options.isObjectEmpty(this.model.latest.last_deployment) && - !this.$options.isObjectEmpty(this.model.latest.last_deployment.user)) { - return this.model.latest.last_deployment.user; - } - return {}; - }, - - /** - * Verifies if the build name column should be rendered by verifing - * if all the information needed is present - * and if the environment is not a folder. - * - * @returns {Boolean} - */ - shouldRenderBuildName() { - return !this.model.isFolder && - !this.$options.isObjectEmpty(this.model.latest.last_deployment) && - !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); - }, - - /** - * Verifies if deplyment internal ID should be rendered by verifing - * if all the information needed is present - * and if the environment is not a folder. - * - * @returns {Boolean} - */ - shouldRenderDeploymentID() { - return !this.model.isFolder && - !this.$options.isObjectEmpty(this.model.latest.last_deployment) && - this.model.latest.last_deployment.iid !== undefined; - }, + computed: { + /** + * Verifies if `last_deployment` key exists in the current Envrionment. + * This key is required to render most of the html - this method works has + * an helper. + * + * @returns {Boolean} + */ + hasLastDeploymentKey() { + if (this.model.latest.last_deployment && + !this.$options.isObjectEmpty(this.model.latest.last_deployment)) { + return true; + } + return false; + }, + + /** + * Verifies is the given environment has manual actions. + * Used to verify if we should render them or nor. + * + * @returns {Boolean|Undefined} + */ + hasManualActions() { + return this.model.latest.last_deployment && + this.model.latest.last_deployment.manual_actions && + this.model.latest.last_deployment.manual_actions.length > 0; + }, + + /** + * Returns the value of the `stop_action?` key provided in the response. + * + * @returns {Boolean} + */ + hasStopAction() { + return this.model['stop_action?']; + }, + + /** + * Verifies if the `deployable` key is present in `last_deployment` key. + * Used to verify whether we should or not render the rollback partial. + * + * @returns {Boolean|Undefined} + */ + canRetry() { + return this.hasLastDeploymentKey && + this.model.latest.last_deployment && + this.model.latest.last_deployment.deployable; + }, + + /** + * Verifies if the date to be shown is present. + * + * @returns {Boolean|Undefined} + */ + canShowDate() { + return this.model.latest.last_deployment && + this.model.latest.last_deployment.deployable && + this.model.latest.last_deployment.deployable !== undefined; + }, + + /** + * Human readable date. + * + * @returns {String} + */ + createdDate() { + return timeagoInstance.format(this.model.latest.last_deployment.deployable.created_at); }, /** - * Helper to verify if certain given object are empty. - * Should be replaced by lodash _.isEmpty - https://lodash.com/docs/4.17.2#isEmpty - * @param {Object} object - * @returns {Bollean} + * Returns the manual actions with the name parsed. + * + * @returns {Array.|Undefined} */ - isObjectEmpty(object) { - for (const key in object) { // eslint-disable-line - if (hasOwnProperty.call(object, key)) { - return false; - } + manualActions() { + if (this.hasManualActions) { + return this.model.latest.last_deployment.manual_actions.map((action) => { + const parsedAction = { + name: gl.text.humanize(action.name), + play_path: action.play_path, + }; + return parsedAction; + }); } - return true; + return []; }, - template: ` - - - - {{model.name}} - - - - - - - - - {{model.name}} - - - - {{model.size}} - - - + /** + * Builds the string used in the user image alt attribute. + * + * @returns {String} + */ + userImageAltDescription() { + if (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'`; + } + return ''; + }, + + /** + * If provided, returns the commit tag. + * + * @returns {String|Undefined} + */ + commitTag() { + if (this.model.latest.last_deployment && + this.model.latest.last_deployment.tag) { + return this.model.latest.last_deployment.tag; + } + return undefined; + }, + + /** + * If provided, returns the commit ref. + * + * @returns {Object|Undefined} + */ + commitRef() { + if (this.model.latest.last_deployment && + this.model.latest.last_deployment.ref) { + return this.model.latest.last_deployment.ref; + } + return undefined; + }, + + /** + * If provided, returns the commit url. + * + * @returns {String|Undefined} + */ + commitUrl() { + if (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; + } + return undefined; + }, + + /** + * If provided, returns the commit short sha. + * + * @returns {String|Undefined} + */ + commitShortSha() { + if (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; + } + return undefined; + }, + + /** + * If provided, returns the commit title. + * + * @returns {String|Undefined} + */ + commitTitle() { + if (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; + } + return undefined; + }, + + /** + * If provided, returns the commit tag. + * + * @returns {Object|Undefined} + */ + commitAuthor() { + if (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; + } + + return undefined; + }, + + /** + * Verifies if the `retry_path` key is present and returns its value. + * + * @returns {String|Undefined} + */ + retryUrl() { + if (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; + } + return undefined; + }, + + /** + * Verifies if the `last?` key is present and returns its value. + * + * @returns {Boolean|Undefined} + */ + isLastDeployment() { + return this.model.latest.last_deployment && + this.model.latest.last_deployment['last?']; + }, + + /** + * Builds the name of the builds needed to display both the name and the id. + * + * @returns {String} + */ + buildName() { + if (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}`; + } + return ''; + }, - - - {{deploymentInternalId}} + /** + * Builds the needed string to show the internal id. + * + * @returns {String} + */ + deploymentInternalId() { + if (this.model.latest.last_deployment && + this.model.latest.last_deployment.iid) { + return `#${this.model.latest.last_deployment.iid}`; + } + return ''; + }, + + /** + * Verifies if the user object is present under last_deployment object. + * + * @returns {Boolean} + */ + deploymentHasUser() { + return !this.$options.isObjectEmpty(this.model.latest.last_deployment) && + !this.$options.isObjectEmpty(this.model.latest.last_deployment.user); + }, + + /** + * Returns the user object nested with the last_deployment object. + * Used to render the template. + * + * @returns {Object} + */ + deploymentUser() { + if (!this.$options.isObjectEmpty(this.model.latest.last_deployment) && + !this.$options.isObjectEmpty(this.model.latest.last_deployment.user)) { + return this.model.latest.last_deployment.user; + } + return {}; + }, + + /** + * Verifies if the build name column should be rendered by verifing + * if all the information needed is present + * and if the environment is not a folder. + * + * @returns {Boolean} + */ + shouldRenderBuildName() { + return !this.model.isFolder && + !this.$options.isObjectEmpty(this.model.latest.last_deployment) && + !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); + }, + + /** + * Verifies if deplyment internal ID should be rendered by verifing + * if all the information needed is present + * and if the environment is not a folder. + * + * @returns {Boolean} + */ + shouldRenderDeploymentID() { + return !this.model.isFolder && + !this.$options.isObjectEmpty(this.model.latest.last_deployment) && + this.model.latest.last_deployment.iid !== undefined; + }, + }, + + /** + * Helper to verify if certain given object are empty. + * Should be replaced by lodash _.isEmpty - https://lodash.com/docs/4.17.2#isEmpty + * @param {Object} object + * @returns {Bollean} + */ + isObjectEmpty(object) { + for (const key in object) { // eslint-disable-line + if (hasOwnProperty.call(object, key)) { + return false; + } + } + return true; + }, + + template: ` + + + + {{model.name}} + + + + + - - by - - - + + {{model.name}} - - - - {{buildName}} + + {{model.size}} + + + + + + + {{deploymentInternalId}} + + + + by + + - - - -
    - - + + + + + + {{buildName}} + + + + +
    + + +
    +

    + No deployments yet +

    + + + + + {{createdDate}} + + + + +
    +
    + +
    -

    - No deployments yet -

    - - - - - {{createdDate}} - - - - -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    - - - `, - }); -})(); +
    + + + `, +}); diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6 index 5938340a128..daf126eb4e8 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.js.es6 +++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6 @@ -1,33 +1,30 @@ -/* global Vue */ +/** + * Renders Rollback or Re deploy button in environments table depending + * of the provided property `isLastDeployment` + */ +const Vue = require('vue'); -window.Vue = require('vue'); - -(() => { - window.gl = window.gl || {}; - window.gl.environmentsList = window.gl.environmentsList || {}; - - gl.environmentsList.RollbackComponent = Vue.component('rollback-component', { - props: { - retryUrl: { - type: String, - default: '', - }, +module.exports = Vue.component('rollback-component', { + props: { + retryUrl: { + type: String, + default: '', + }, - isLastDeployment: { - type: Boolean, - default: true, - }, + isLastDeployment: { + type: Boolean, + default: true, }, + }, - template: ` - - - Re-deploy - - - Rollback - - - `, - }); -})(); + template: ` + + + Re-deploy + + + Rollback + + + `, +}); diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6 index be9526989a0..96983a19568 100644 --- a/app/assets/javascripts/environments/components/environment_stop.js.es6 +++ b/app/assets/javascripts/environments/components/environment_stop.js.es6 @@ -1,27 +1,24 @@ -/* global Vue */ +/** + * Renders the stop "button" that allows stop an environment. + * Used in environments table. + */ +const Vue = require('vue'); -window.Vue = require('vue'); - -(() => { - window.gl = window.gl || {}; - window.gl.environmentsList = window.gl.environmentsList || {}; - - gl.environmentsList.StopComponent = Vue.component('stop-component', { - props: { - stopUrl: { - type: String, - default: '', - }, +module.exports = Vue.component('stop-component', { + props: { + stopUrl: { + type: String, + default: '', }, + }, - template: ` - - - - `, - }); -})(); + template: ` + + + + `, +}); diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 index a3ad063f7cb..481e0d15e7a 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 +++ b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 @@ -1,28 +1,25 @@ -/* global Vue */ +/** + * Renders a terminal button to open a web terminal. + * Used in environments table. + */ +const Vue = require('vue'); -window.Vue = require('vue'); - -(() => { - window.gl = window.gl || {}; - window.gl.environmentsList = window.gl.environmentsList || {}; - - gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', { - props: { - terminalPath: { - type: String, - default: '', - }, - terminalIconSvg: { - type: String, - default: '', - }, +module.exports = Vue.component('terminal-button-component', { + props: { + terminalPath: { + type: String, + default: '', + }, + terminalIconSvg: { + type: String, + default: '', }, + }, - template: ` - - - - `, - }); -})(); + template: ` + + + + `, +}); diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 912f50aeec1..867eba1d384 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -1,5 +1,4 @@ -window.Vue = require('vue'); -require('./components/environment'); +const EnvironmentsComponent = require('./components/environment'); require('../vue_shared/vue_resource_interceptor'); $(() => { @@ -9,7 +8,7 @@ $(() => { gl.EnvironmentsListApp.$destroy(true); } - gl.EnvironmentsListApp = new gl.environmentsList.EnvironmentsComponent({ + gl.EnvironmentsListApp = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); }); diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js.es6 index 9b33fd02c53..9cef335868e 100644 --- a/app/assets/javascripts/environments/services/environments_service.js.es6 +++ b/app/assets/javascripts/environments/services/environments_service.js.es6 @@ -1,4 +1,4 @@ -const Vue = window.Vue = require('vue'); +const Vue = require('vue'); class EnvironmentsService { constructor(endpoint) { diff --git a/app/assets/javascripts/vue_shared/components/commit.js.es6 b/app/assets/javascripts/vue_shared/components/commit.js.es6 index 7f7c18ddeb1..ff88e236829 100644 --- a/app/assets/javascripts/vue_shared/components/commit.js.es6 +++ b/app/assets/javascripts/vue_shared/components/commit.js.es6 @@ -1,4 +1,5 @@ /* global Vue */ +window.Vue = require('vue'); (() => { window.gl = window.gl || {}; diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6 index b1838045a06..850586f9f3a 100644 --- a/spec/javascripts/environments/environment_actions_spec.js.es6 +++ b/spec/javascripts/environments/environment_actions_spec.js.es6 @@ -1,4 +1,4 @@ -require('~/environments/components/environment_actions'); +const ActionsComponent = require('~/environments/components/environment_actions'); describe('Actions Component', () => { preloadFixtures('static/environments/element.html.raw'); @@ -19,7 +19,7 @@ describe('Actions Component', () => { }, ]; - const component = new window.gl.environmentsList.ActionsComponent({ + const component = new ActionsComponent({ el: document.querySelector('.test-dom-element'), propsData: { actions: actionsMock, @@ -47,7 +47,7 @@ describe('Actions Component', () => { }, ]; - const component = new window.gl.environmentsList.ActionsComponent({ + const component = new ActionsComponent({ el: document.querySelector('.test-dom-element'), propsData: { actions: actionsMock, diff --git a/spec/javascripts/environments/environment_external_url_spec.js.es6 b/spec/javascripts/environments/environment_external_url_spec.js.es6 index a6a587e69f5..393dbb5aae0 100644 --- a/spec/javascripts/environments/environment_external_url_spec.js.es6 +++ b/spec/javascripts/environments/environment_external_url_spec.js.es6 @@ -1,4 +1,4 @@ -require('~/environments/components/environment_external_url'); +const ExternalUrlComponent = require('~/environments/components/environment_external_url'); describe('External URL Component', () => { preloadFixtures('static/environments/element.html.raw'); @@ -8,7 +8,7 @@ describe('External URL Component', () => { it('should link to the provided externalUrl prop', () => { const externalURL = 'https://gitlab.com'; - const component = new window.gl.environmentsList.ExternalUrlComponent({ + const component = new ExternalUrlComponent({ el: document.querySelector('.test-dom-element'), propsData: { externalUrl: externalURL, diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index 5dc7ef5ad76..dd614e50d7f 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -1,7 +1,7 @@ window.timeago = require('vendor/timeago'); -require('~/environments/components/environment_item'); +const EnvironmentItem = require('~/environments/components/environment_item'); -fdescribe('Environment item', () => { +describe('Environment item', () => { preloadFixtures('static/environments/table.html.raw'); beforeEach(() => { loadFixtures('static/environments/table.html.raw'); @@ -21,7 +21,7 @@ fdescribe('Environment item', () => { }, }; - component = new window.gl.environmentsList.EnvironmentItem({ + component = new EnvironmentItem({ el: document.querySelector('tr#environment-row'), propsData: { model: mockItem, @@ -111,7 +111,7 @@ fdescribe('Environment item', () => { }, }; - component = new window.gl.environmentsList.EnvironmentItem({ + component = new EnvironmentItem({ el: document.querySelector('tr#environment-row'), propsData: { model: environment, diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6 index 043b8708a6e..4a596baad09 100644 --- a/spec/javascripts/environments/environment_rollback_spec.js.es6 +++ b/spec/javascripts/environments/environment_rollback_spec.js.es6 @@ -1,4 +1,4 @@ -require('~/environments/components/environment_rollback'); +const RollbackComponent = require('~/environments/components/environment_rollback'); describe('Rollback Component', () => { preloadFixtures('static/environments/element.html.raw'); @@ -10,7 +10,7 @@ describe('Rollback Component', () => { }); it('Should link to the provided retryUrl', () => { - const component = new window.gl.environmentsList.RollbackComponent({ + const component = new RollbackComponent({ el: document.querySelector('.test-dom-element'), propsData: { retryUrl: retryURL, @@ -22,7 +22,7 @@ describe('Rollback Component', () => { }); it('Should render Re-deploy label when isLastDeployment is true', () => { - const component = new window.gl.environmentsList.RollbackComponent({ + const component = new RollbackComponent({ el: document.querySelector('.test-dom-element'), propsData: { retryUrl: retryURL, @@ -34,7 +34,7 @@ describe('Rollback Component', () => { }); it('Should render Rollback label when isLastDeployment is false', () => { - const component = new window.gl.environmentsList.RollbackComponent({ + const component = new RollbackComponent({ el: document.querySelector('.test-dom-element'), propsData: { retryUrl: retryURL, diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 index 8b96f4b09db..1d1a688a4a5 100644 --- a/spec/javascripts/environments/environment_spec.js.es6 +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -1,7 +1,6 @@ -/* global Vue, environment */ - +const Vue = require('vue'); require('~/flash'); -require('~/environments/components/environment'); +const EnvironmentsComponent = require('~/environments/components/environment'); const { environment } = require('./mock_data'); describe('Environment', () => { @@ -32,7 +31,7 @@ describe('Environment', () => { }); it('should render the empty state', (done) => { - component = new gl.environmentsList.EnvironmentsComponent({ + component = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); @@ -72,7 +71,7 @@ describe('Environment', () => { }); it('should render a table with environments', (done) => { - component = new gl.environmentsList.EnvironmentsComponent({ + component = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); @@ -104,7 +103,7 @@ describe('Environment', () => { }); it('should render empty state', (done) => { - component = new gl.environmentsList.EnvironmentsComponent({ + component = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); diff --git a/spec/javascripts/environments/environment_stop_spec.js.es6 b/spec/javascripts/environments/environment_stop_spec.js.es6 index 2dfce5ba824..5ca65b1debc 100644 --- a/spec/javascripts/environments/environment_stop_spec.js.es6 +++ b/spec/javascripts/environments/environment_stop_spec.js.es6 @@ -1,4 +1,4 @@ -require('~/environments/components/environment_stop'); +const StopComponent = require('~/environments/components/environment_stop'); describe('Stop Component', () => { preloadFixtures('static/environments/element.html.raw'); @@ -10,7 +10,7 @@ describe('Stop Component', () => { loadFixtures('static/environments/element.html.raw'); stopURL = '/stop'; - component = new window.gl.environmentsList.StopComponent({ + component = new StopComponent({ el: document.querySelector('.test-dom-element'), propsData: { stopUrl: stopURL, -- cgit v1.2.1 From 19bac884c67acebcc0e6dda2a3df824049b1f726 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 7 Feb 2017 15:06:31 +0100 Subject: Add route for environment folder and expose an API --- app/controllers/projects/environments_controller.rb | 18 ++++++++++++++++++ config/routes/project.rb | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 2252ece68ce..3b7240d8469 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -28,6 +28,24 @@ class Projects::EnvironmentsController < Projects::ApplicationController end end + def folder + @environments = project.environments + .where(environment_type: params[:id]) + .with_state(params[:scope] || :available) + + respond_to do |format| + format.html + format.json do + render json: { + environments: EnvironmentSerializer + .new(project: @project, user: @current_user) + .with_pagination(request, response) + .represent(@environments), + } + end + end + end + def show @deployments = environment.deployments.order(id: :desc).page(params[:page]) end diff --git a/config/routes/project.rb b/config/routes/project.rb index 2ac98cf3842..84f123ff717 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -156,6 +156,10 @@ constraints(ProjectUrlConstrainer.new) do get :terminal get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil } end + + collection do + get :folder, path: 'folders/:id' + end end resource :cycle_analytics, only: [:show] -- cgit v1.2.1 From 482e7ff01201dba89a13f5e9979ea17c202c87c7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 17:32:08 +0000 Subject: Fix broken test and linter error --- app/assets/javascripts/environments/components/environment_item.js.es6 | 2 +- spec/javascripts/environments/mock_data.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index cf79969471e..d0c5a343d71 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -97,7 +97,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean} */ hasStopAction() { - return this.model['stop_action?']; + return this.model.latest['stop_action?']; }, /** diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6 index bdecc95d219..081897f5456 100644 --- a/spec/javascripts/environments/mock_data.js.es6 +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -55,5 +55,5 @@ const environment = { module.exports = { environmentsList, - environment + environment, }; -- cgit v1.2.1 From d0d94e4f104c276ee4095a76d1204daae384c708 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 10 Feb 2017 09:36:52 +0100 Subject: Fix pagination headers in grouped environments API --- app/serializers/environment_serializer.rb | 23 ++++++++++++++--------- spec/serializers/environment_serializer_spec.rb | 11 +++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb index fe16a3784c4..d0a60f134da 100644 --- a/app/serializers/environment_serializer.rb +++ b/app/serializers/environment_serializer.rb @@ -20,8 +20,6 @@ class EnvironmentSerializer < BaseSerializer end def represent(resource, opts = {}) - resource = @paginator.paginate(resource) if paginated? - if itemized? itemize(resource).map do |item| { name: item.name, @@ -29,6 +27,8 @@ class EnvironmentSerializer < BaseSerializer latest: super(item.latest, opts) } end else + resource = @paginator.paginate(resource) if paginated? + super(resource, opts) end end @@ -36,15 +36,20 @@ class EnvironmentSerializer < BaseSerializer private def itemize(resource) - items = resource.group(:item_name).order('item_name ASC') - .pluck('COALESCE(environment_type, name) AS item_name', - 'COUNT(*) AS environments_count', - 'MAX(id) AS last_environment_id') + items = resource.order('folder_name ASC') + .group('COALESCE(environment_type, name)') + .select('COALESCE(environment_type, name) AS folder_name', + 'COUNT(*) AS size', 'MAX(id) AS last_id') + + # It makes a difference when you call `paginate` method, because + # although `page` is effective at the end, it calls counting methods + # immediately. + items = @paginator.paginate(items) if paginated? - environments = resource.where(id: items.map(&:last)).index_by(&:id) + environments = resource.where(id: items.map(&:last_id)).index_by(&:id) - items.map do |name, size, id| - Item.new(name, size, environments[id]) + items.map do |item| + Item.new(item.folder_name, item.size, environments[item.last_id]) end end end diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index 1b95f1ff198..6a6df377b35 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -181,6 +181,17 @@ describe EnvironmentSerializer do expect(subject.first[:name]).to eq 'production' expect(subject.second[:name]).to eq 'staging' end + + it 'appends correct total page count header' do + expect(subject).not_to be_empty + expect(response).to have_received(:[]=).with('X-Total', '3') + end + + it 'appends correct page count headers' do + expect(subject).not_to be_empty + expect(response).to have_received(:[]=).with('X-Total-Pages', '2') + expect(response).to have_received(:[]=).with('X-Per-Page', '2') + end end end end -- cgit v1.2.1 From f7d8c29e71dc3a5aed4cf7252beaee6fe634de6b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 12 Feb 2017 12:10:29 +0000 Subject: Fix broken path --- .../components/environment_item.js.es6 | 108 ++++++++++++++++----- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 399473bec65..391539232b1 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -1,5 +1,5 @@ const Vue = require('vue'); -const Timeago = require('timeago.j'); +const Timeago = require('timeago.js'); require('../../lib/utils/text_utility'); require('../../vue_shared/components/commit'); @@ -72,7 +72,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean} */ hasLastDeploymentKey() { - if (this.model.latest.last_deployment && + if (this.model.lastest && this.model.latest.last_deployment && !this.$options.isObjectEmpty(this.model.latest.last_deployment)) { return true; } @@ -86,7 +86,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ hasManualActions() { - return this.model.latest.last_deployment && + return this.model.lastest && this.model.latest.last_deployment && this.model.latest.last_deployment.manual_actions && this.model.latest.last_deployment.manual_actions.length > 0; }, @@ -107,7 +107,8 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ canRetry() { - return this.hasLastDeploymentKey && + return this.model.lastest && + this.hasLastDeploymentKey && this.model.latest.last_deployment && this.model.latest.last_deployment.deployable; }, @@ -118,7 +119,8 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ canShowDate() { - return this.model.latest.last_deployment && + return this.model.lastest && + this.model.latest.last_deployment && this.model.latest.last_deployment.deployable && this.model.latest.last_deployment.deployable !== undefined; }, @@ -129,7 +131,13 @@ module.exports = Vue.component('environment-item', { * @returns {String} */ createdDate() { - return timeagoInstance.format(this.model.latest.last_deployment.deployable.created_at); + if (this.model.lastest && + this.model.latest.last_deployment && + this.model.lastest.last_deployment.deployable && + this.model.latest.last_deployment.deployable.created_at) { + return timeagoInstance.format(this.model.latest.last_deployment.deployable.created_at); + } + return ''; }, /** @@ -156,7 +164,8 @@ module.exports = Vue.component('environment-item', { * @returns {String} */ userImageAltDescription() { - if (this.model.latest.last_deployment && + 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'`; @@ -170,7 +179,8 @@ module.exports = Vue.component('environment-item', { * @returns {String|Undefined} */ commitTag() { - if (this.model.latest.last_deployment && + if (this.model.latest && + this.model.latest.last_deployment && this.model.latest.last_deployment.tag) { return this.model.latest.last_deployment.tag; } @@ -183,7 +193,8 @@ module.exports = Vue.component('environment-item', { * @returns {Object|Undefined} */ commitRef() { - if (this.model.latest.last_deployment && + if (this.model.latest && + this.model.latest.last_deployment && this.model.latest.last_deployment.ref) { return this.model.latest.last_deployment.ref; } @@ -196,7 +207,8 @@ module.exports = Vue.component('environment-item', { * @returns {String|Undefined} */ commitUrl() { - if (this.model.latest.last_deployment && + 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; @@ -210,7 +222,8 @@ module.exports = Vue.component('environment-item', { * @returns {String|Undefined} */ commitShortSha() { - if (this.model.latest.last_deployment && + 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; @@ -224,7 +237,8 @@ module.exports = Vue.component('environment-item', { * @returns {String|Undefined} */ commitTitle() { - if (this.model.latest.last_deployment && + 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; @@ -238,7 +252,8 @@ module.exports = Vue.component('environment-item', { * @returns {Object|Undefined} */ commitAuthor() { - if (this.model.latest.last_deployment && + 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; @@ -253,7 +268,8 @@ module.exports = Vue.component('environment-item', { * @returns {String|Undefined} */ retryUrl() { - if (this.model.latest.last_deployment && + 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; @@ -267,7 +283,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ isLastDeployment() { - return this.model.latest.last_deployment && + return this.model.latest && this.model.latest.last_deployment && this.model.latest.last_deployment['last?']; }, @@ -277,7 +293,8 @@ module.exports = Vue.component('environment-item', { * @returns {String} */ buildName() { - if (this.model.latest.last_deployment && + 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}`; } @@ -290,7 +307,8 @@ module.exports = Vue.component('environment-item', { * @returns {String} */ deploymentInternalId() { - if (this.model.latest.last_deployment && + if (this.model.latest && + this.model.latest.last_deployment && this.model.latest.last_deployment.iid) { return `#${this.model.latest.last_deployment.iid}`; } @@ -303,7 +321,8 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean} */ deploymentHasUser() { - return !this.$options.isObjectEmpty(this.model.latest.last_deployment) && + return this.model.latest && + !this.$options.isObjectEmpty(this.model.latest.last_deployment) && !this.$options.isObjectEmpty(this.model.latest.last_deployment.user); }, @@ -314,7 +333,8 @@ module.exports = Vue.component('environment-item', { * @returns {Object} */ deploymentUser() { - if (!this.$options.isObjectEmpty(this.model.latest.last_deployment) && + 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; } @@ -330,10 +350,39 @@ module.exports = Vue.component('environment-item', { */ shouldRenderBuildName() { return !this.model.isFolder && + this.model.lastest && !this.$options.isObjectEmpty(this.model.latest.last_deployment) && !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); }, + /** + * Verifies the presence of all the keys needed to render the buil_path. + * + * @return {String} + */ + buildPath() { + if (this.model.latest && + this.model.lastest.last_deployment && + this.model.latest.last_deployment.deployable && + this.model.lastest.last_deployment.deployable.build_path) { + return this.model.lastest.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; + } + + return ''; + }, + /** * Verifies if deplyment internal ID should be rendered by verifing * if all the information needed is present @@ -343,9 +392,18 @@ module.exports = Vue.component('environment-item', { */ shouldRenderDeploymentID() { return !this.model.isFolder && + this.model.lastest && !this.$options.isObjectEmpty(this.model.latest.last_deployment) && this.model.latest.last_deployment.iid !== undefined; }, + + environmentPath() { + if (this.model && this.model.lastest && this.model.latest.environment_path) { + return this.model.latest.environment_path; + } + + return ''; + }, }, /** @@ -368,7 +426,7 @@ module.exports = Vue.component('environment-item', { + :href="environmentPath"> {{model.name}} @@ -406,7 +464,7 @@ module.exports = Vue.component('environment-item', { + :href="buildPath"> {{buildName}} @@ -445,21 +503,21 @@ module.exports = Vue.component('environment-item', {
    -
    + :external-url="externalURL">
    -
    -
    Date: Sun, 12 Feb 2017 13:43:46 +0000 Subject: Fix typo --- .../components/environment_item.js.es6 | 24 +++++++++++----------- spec/features/projects/builds_spec.rb | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 391539232b1..7805152b173 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -72,7 +72,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean} */ hasLastDeploymentKey() { - if (this.model.lastest && this.model.latest.last_deployment && + if (this.model.latest && this.model.latest.last_deployment && !this.$options.isObjectEmpty(this.model.latest.last_deployment)) { return true; } @@ -86,7 +86,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ hasManualActions() { - return this.model.lastest && this.model.latest.last_deployment && + 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; }, @@ -107,7 +107,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ canRetry() { - return this.model.lastest && + return this.model.latest && this.hasLastDeploymentKey && this.model.latest.last_deployment && this.model.latest.last_deployment.deployable; @@ -119,7 +119,7 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ canShowDate() { - return this.model.lastest && + return this.model.latest && this.model.latest.last_deployment && this.model.latest.last_deployment.deployable && this.model.latest.last_deployment.deployable !== undefined; @@ -131,9 +131,9 @@ module.exports = Vue.component('environment-item', { * @returns {String} */ createdDate() { - if (this.model.lastest && + if (this.model.latest && this.model.latest.last_deployment && - this.model.lastest.last_deployment.deployable && + 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); } @@ -350,7 +350,7 @@ module.exports = Vue.component('environment-item', { */ shouldRenderBuildName() { return !this.model.isFolder && - this.model.lastest && + this.model.latest && !this.$options.isObjectEmpty(this.model.latest.last_deployment) && !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); }, @@ -362,10 +362,10 @@ module.exports = Vue.component('environment-item', { */ buildPath() { if (this.model.latest && - this.model.lastest.last_deployment && + this.model.latest.last_deployment && this.model.latest.last_deployment.deployable && - this.model.lastest.last_deployment.deployable.build_path) { - return this.model.lastest.last_deployment.deployable.build_path; + this.model.latest.last_deployment.deployable.build_path) { + return this.model.latest.last_deployment.deployable.build_path; } return ''; @@ -392,13 +392,13 @@ module.exports = Vue.component('environment-item', { */ shouldRenderDeploymentID() { return !this.model.isFolder && - this.model.lastest && + this.model.latest && !this.$options.isObjectEmpty(this.model.latest.last_deployment) && this.model.latest.last_deployment.iid !== undefined; }, environmentPath() { - if (this.model && this.model.lastest && this.model.latest.environment_path) { + if (this.model && this.model.latest && this.model.latest.environment_path) { return this.model.latest.environment_path; } diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index f7e0115643e..80ba5ff1a63 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -271,7 +271,7 @@ feature 'Builds', :feature do let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } - it 'shows a link to lastest deployment' do + it 'shows a link to latest deployment' do visit namespace_project_build_path(project.namespace, project, build) expect(page).to have_link('latest deployment') -- cgit v1.2.1 From 991a7ae86055df2d0eb619e1d29a1e24e322c741 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 12:18:47 +0000 Subject: Adds pagination component --- .../environments/components/environment.js.es6 | 25 ++++++++++++++++++++-- .../environments/stores/environments_store.js.es6 | 13 +++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 11a965fcddf..f89dbdbda1d 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -6,11 +6,13 @@ Vue.use(require('vue-resource')); const EnvironmentsService = require('../services/environments_service'); const EnvironmentItem = require('./environment_item'); const Store = require('../stores/environments_store'); +require('../../vue_shared/components/table_pagination'); module.exports = Vue.component('environment-component', { components: { 'environment-item': EnvironmentItem, + 'table-pagination': gl.VueGlPagination, }, data() { @@ -34,6 +36,10 @@ module.exports = Vue.component('environment-component', { commitIconSvg: environmentsData.commitIconSvg, playIconSvg: environmentsData.playIconSvg, terminalIconSvg: environmentsData.terminalIconSvg, + + // Pagination Properties, + paginationInformation: {}, + pageNumber: 1, }; }, @@ -61,14 +67,18 @@ module.exports = Vue.component('environment-component', { */ created() { const scope = this.$options.getQueryParameter('scope') || this.visibility; - const endpoint = `${this.endpoint}?scope=${scope}`; + const pageNumber = this.$options.getQueryParameter('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((resp) => { + debugger; + return resp.json(); + }) .then((json) => { this.store.storeAvailableCount(json.available_count); this.store.storeStoppedCount(json.stopped_count); @@ -111,6 +121,10 @@ module.exports = Vue.component('environment-component', { toggleRow(model) { return this.store.toggleFolder(model.name); }, + + changePage(pageNumber, scope) { + gl.utils.visitUrl(`?scope=${scope}&p=${pageNumber}`); + }, }, template: ` @@ -192,6 +206,13 @@ module.exports = Vue.component('environment-component', { + + +
    diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index 749dd882188..e55b8624ac8 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; } @@ -41,6 +43,17 @@ class EnvironmentsStore { return filteredEnvironments; } + storePagination(pagination = {}) { + const normalizedHeaders = gl.utils.normalizedHeaders(pagination); + + const paginationInformation = { + + }; + + this.paginationInformation = paginationInformation; + return paginationInformation; + } + /** * Stores the number of available environments. * -- cgit v1.2.1 From fb35980893f918f7dbad0f433447c6df13a1c757 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 14:49:13 +0000 Subject: Verify `latest` key is present when needed --- .../environments/components/environment_item.js.es6 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 7805152b173..d3ca4cb8dde 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -355,6 +355,18 @@ module.exports = Vue.component('environment-item', { !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); }, + buildPath(){ + return this.model.latest && + this.model.latest.last_deployment && + this.model.latest.last_deployment.deployable && + this.model.latest.last_deployment.deployable.build_path || + ''; + }, + + externalURL() { + return this.model.latest && this.model.latest.external_url || ''; + }, + /** * Verifies the presence of all the keys needed to render the buil_path. * -- cgit v1.2.1 From 595afed2e3de93d1685b2f77dd8e72df2781a57b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 9 Feb 2017 15:10:16 +0000 Subject: Integrates pagination component with API Adds pagination tests Remove misplaced comment Fix broken store test --- .../environments/components/environment.js.es6 | 37 +++++++++++++--------- .../components/environment_item.js.es6 | 16 +++++++++- .../environments/stores/environments_store.js.es6 | 12 ++++--- .../vue_pipelines_index/pipelines.js.es6 | 1 + .../vue_shared/components/table_pagination.js.es6 | 4 +-- .../environments/environment_spec.js.es6 | 28 +++++++++++++--- .../environments/environments_store_spec.js.es6 | 36 +++++++++++++++++---- 7 files changed, 100 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index f89dbdbda1d..d5bb9f91e7f 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -59,6 +59,7 @@ module.exports = Vue.component('environment-component', { canCreateEnvironmentParsed() { return this.$options.convertPermissionToBoolean(this.canCreateEnvironment); }, + }, /** @@ -68,6 +69,7 @@ module.exports = Vue.component('environment-component', { created() { const scope = this.$options.getQueryParameter('scope') || this.visibility; const pageNumber = this.$options.getQueryParameter('page') || this.pageNumber; + const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`; const service = new EnvironmentsService(endpoint); @@ -75,14 +77,15 @@ module.exports = Vue.component('environment-component', { this.isLoading = true; return service.all() - .then((resp) => { - debugger; - return 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.storePagination(response.headers); }) .then(() => { this.isLoading = false; @@ -122,8 +125,14 @@ module.exports = Vue.component('environment-component', { return this.store.toggleFolder(model.name); }, - changePage(pageNumber, scope) { - gl.utils.visitUrl(`?scope=${scope}&p=${pageNumber}`); + /** + * Will change the page number and update the URL. + * + * @param {Number} pageNumber desired page to go to. + */ + changePage(pageNumber) { + const param = window.location.search.replace(/page=\d/g, `page=${pageNumber}`); + gl.utils.visitUrl(param); }, }, @@ -131,7 +140,7 @@ module.exports = Vue.component('environment-component', { diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index d3ca4cb8dde..1648f06a991 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -355,6 +355,11 @@ module.exports = Vue.component('environment-item', { !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); }, + /** + * Verifies the presence of all the keys needed to render the buil_path. + * + * @return {String} + */ buildPath(){ return this.model.latest && this.model.latest.last_deployment && @@ -363,8 +368,17 @@ module.exports = Vue.component('environment-item', { ''; }, + /** + * Verifies the presence of all the keys needed to render the external_url. + * + * @return {String} + */ externalURL() { - return this.model.latest && this.model.latest.external_url || ''; + if (this.model.latest && this.model.latest.external_url) { + return this.model.latest.external_url; + } + + return ''; }, /** diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index e55b8624ac8..252e349962e 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -44,13 +44,17 @@ class EnvironmentsStore { } storePagination(pagination = {}) { - const normalizedHeaders = gl.utils.normalizedHeaders(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), }; - this.paginationInformation = paginationInformation; + this.state.paginationInformation = paginationInformation; return paginationInformation; } diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index e47dc6935d6..9e816a285e4 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -36,6 +36,7 @@ require('../vue_shared/components/pipelines_table'); }, methods: { change(pagenum, apiScope) { + if (!apiScope) apiScope = 'all'; gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); }, }, 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 67c6cb73761..d8042a9b7fc 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/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 index 1d1a688a4a5..f557dcee91f 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,17 @@ describe('Environment', () => { done(); }, 0); }); + + describe('pagination', () => { + it('should render pagination', (done) => { + setTimeout(() => { + expect( + component.$el.querySelectorAll('.gl-pagination li').length, + ).toEqual(5); + done(); + }, 0); + }); + }); }); }); diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6 index 861136c621f..8fd660c3edb 100644 --- a/spec/javascripts/environments/environments_store_spec.js.es6 +++ b/spec/javascripts/environments/environments_store_spec.js.es6 @@ -10,24 +10,48 @@ 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); + expect(store.state.environments.length).toEqual(environmentsList.length); }); 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.storePagination(pagination); + expect(store.state.paginationInformation).toEqual(expectedResult); }); }); })(); -- cgit v1.2.1 From 27d7ec70b1efcaac73095e6ea3894b0eb9ba8473 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 10 Feb 2017 17:26:48 +0000 Subject: Remove spec checking for scope 'all' since it's no longer part of component Changes after review Fix typo --- .../environments/components/environment_item.js.es6 | 6 ++++-- spec/javascripts/environments/environment_spec.js.es6 | 6 +++--- .../vue_shared/components/table_pagination_spec.js.es6 | 12 ++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 1648f06a991..871a09977a4 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -72,7 +72,8 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean} */ hasLastDeploymentKey() { - if (this.model.latest && this.model.latest.last_deployment && + if (this.model.latest && + this.model.latest.last_deployment && !this.$options.isObjectEmpty(this.model.latest.last_deployment)) { return true; } @@ -86,7 +87,8 @@ module.exports = Vue.component('environment-item', { * @returns {Boolean|Undefined} */ hasManualActions() { - return this.model.latest && this.model.latest.last_deployment && + 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; }, diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 index f557dcee91f..657d8d2ab02 100644 --- a/spec/javascripts/environments/environment_spec.js.es6 +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -58,11 +58,11 @@ describe('Environment', () => { }), { status: 200, headers: { - 'X-Next-Page': '2', - 'X-Page': '1', + 'X-nExt-pAge': '2', + 'x-page': '1', 'X-Per-Page': '1', 'X-Prev-Page': '', - 'X-Total': '37', + 'X-TOTAL': '37', 'X-Total-Pages': '2', }, })); 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 e84f0dcfe67..dd495cb43bc 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); }); }); -- cgit v1.2.1 From c2fe699ac801fd2440cc4b57083a60a334cffa06 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 12 Feb 2017 13:04:00 +0000 Subject: Add pagination tests for environments table Remove fdescribe statement Fix conflict --- .../environments/components/environment.js.es6 | 20 ++++++++++- .../components/environment_item.js.es6 | 27 +------------- .../environments/environment_spec.js.es6 | 42 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index d5bb9f91e7f..6d9599e7645 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -128,11 +128,29 @@ 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. */ changePage(pageNumber) { - const param = window.location.search.replace(/page=\d/g, `page=${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}`; + } + gl.utils.visitUrl(param); + return param; }, }, diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 871a09977a4..fc45c3c5f53 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -357,32 +357,6 @@ module.exports = Vue.component('environment-item', { !this.$options.isObjectEmpty(this.model.latest.last_deployment.deployable); }, - /** - * Verifies the presence of all the keys needed to render the buil_path. - * - * @return {String} - */ - buildPath(){ - return this.model.latest && - this.model.latest.last_deployment && - this.model.latest.last_deployment.deployable && - this.model.latest.last_deployment.deployable.build_path || - ''; - }, - - /** - * 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; - } - - return ''; - }, - /** * Verifies the presence of all the keys needed to render the buil_path. * @@ -398,6 +372,7 @@ module.exports = Vue.component('environment-item', { return ''; }, + /** * Verifies the presence of all the keys needed to render the external_url. * diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 index 657d8d2ab02..edd0cad32d0 100644 --- a/spec/javascripts/environments/environment_spec.js.es6 +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -99,6 +99,48 @@ describe('Environment', () => { 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); + }); }); }); }); -- cgit v1.2.1 From a254dcf0edfb6aa4ea93fd0bfdb992565d6e8422 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 15 Feb 2017 19:59:30 +0100 Subject: Add count keys to response JSON --- app/controllers/projects/environments_controller.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 3b7240d8469..fed75396d6e 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -29,9 +29,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def folder - @environments = project.environments - .where(environment_type: params[:id]) - .with_state(params[:scope] || :available) + folder_environments = project.environments.where(environment_type: params[:id]) + @environments = folder_environments.with_state(params[:scope] || :available) respond_to do |format| format.html @@ -41,6 +40,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController .new(project: @project, user: @current_user) .with_pagination(request, response) .represent(@environments), + available_count: folder_environments.available.count, + stopped_count: folder_environments.stopped.count } end end -- cgit v1.2.1 From 7af6982e5fcf2b76906f33d5046dd9b2298ac32c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 12 Feb 2017 14:40:11 +0000 Subject: Extracts table into a reusable component --- .../environments/components/environment.js.es6 | 37 ++++------- .../components/environments_table.js.es6 | 74 ++++++++++++++++++++++ .../folder/environments_folder_bundle.js.es6 | 0 .../folder/environments_folder_view.js.es6 | 0 app/views/projects/environments/folder.html.haml | 0 5 files changed, 85 insertions(+), 26 deletions(-) create mode 100644 app/assets/javascripts/environments/components/environments_table.js.es6 create mode 100644 app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 create mode 100644 app/assets/javascripts/environments/folder/environments_folder_view.js.es6 create mode 100644 app/views/projects/environments/folder.html.haml diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 6d9599e7645..42f74e114c9 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -4,14 +4,14 @@ const Vue = require('vue'); Vue.use(require('vue-resource')); const EnvironmentsService = require('../services/environments_service'); -const EnvironmentItem = require('./environment_item'); +const EnvironmentTable = require('./environments_table'); const Store = require('../stores/environments_store'); require('../../vue_shared/components/table_pagination'); module.exports = Vue.component('environment-component', { components: { - 'environment-item': EnvironmentItem, + 'environment-table': EnvironmentTable, 'table-pagination': gl.VueGlPagination, }, @@ -209,30 +209,15 @@ module.exports = Vue.component('environment-component', {
    - - - - - - - - - - - - - - -
    EnvironmentLast deploymentJobCommitUpdated
    + + + ([]), + }, + + 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 00000000000..e69de29bb2d 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..e69de29bb2d diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.1 From 26d18387dea786ded85df3a429542c5d1e4f1ac1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 12 Feb 2017 14:55:18 +0000 Subject: First iteration --- .../folder/environments_folder_bundle.js.es6 | 14 ++ .../folder/environments_folder_view.js.es6 | 196 +++++++++++++++++++++ .../stores/environments_folder_store.js.es6 | 49 ++++++ app/views/projects/environments/folder.html.haml | 8 + config/webpack.config.js | 1 + 5 files changed, 268 insertions(+) create mode 100644 app/assets/javascripts/environments/stores/environments_folder_store.js.es6 diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 index e69de29bb2d..9cc1c2f4f18 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 +++ 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 index e69de29bb2d..070bc84bbe3 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 +++ b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 @@ -0,0 +1,196 @@ +/* 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('./environments_table'); +const Store = require('../stores/environments_folder_store'); +require('../../vue_shared/components/table_pagination'); + +module.exports = Vue.component('environment-folder-view', { + + components: { + 'environment-table': EnvironmentTable, + 'table-pagination': gl.VueGlPagination, + }, + + props: { + endpoint: { + type: String, + required: true, + default: '', + }, + + folderName: { + type: String, + required: true, + default: '', + }, + }, + + data() { + const store = new Store(); + + return { + store, + state: store.state, + isLoading: false, + + // Pagination Properties, + paginationInformation: {}, + pageNumber: 1, + }; + }, + + /** + * Fetches all the environments and stores them. + * Toggles loading property. + */ + created() { + const scope = this.$options.getQueryParameter('scope') || this.visibility; + const pageNumber = this.$options.getQueryParameter('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.storeEnvironments(response.body.environments); + this.store.storePagination(response.headers); + }) + .then(() => { + this.isLoading = false; + }) + .catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the environments.', 'alert'); + }); + }, + + /** + * 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]; + }, + + methods: { + /** + * 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. + */ + 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}`; + } + + gl.utils.visitUrl(param); + return param; + }, + }, + + template: ` +
    + + +
    +
    + +
    + +
    +

    + You don't have any environments right now. +

    +

    + Environments are places where code gets deployed, such as staging or production. +
    + + Read more about environments + +

    + + + New Environment + +
    + +
    + + + + + + +
    +
    +
    + `, +}); diff --git a/app/assets/javascripts/environments/stores/environments_folder_store.js.es6 b/app/assets/javascripts/environments/stores/environments_folder_store.js.es6 new file mode 100644 index 00000000000..005ed52d9a1 --- /dev/null +++ b/app/assets/javascripts/environments/stores/environments_folder_store.js.es6 @@ -0,0 +1,49 @@ +require('~/lib/utils/common_utils'); +/** + * Environments Folder Store. + * + * Stores received environments that belong to a parent store. + */ +class EnvironmentsFolderStore { + constructor() { + this.state = {}; + this.state.environments = []; + this.state.paginationInformation = {}; + + return this; + } + + /** + * + * Stores the received environments. + * + * Each environment has the following schema + * { name: String, size: Number, latest: Object } + * + * + * @param {Array} environments + * @returns {Array} + */ + storeEnvironments(environments = []) { + this.state.environments = environments; + + return environments; + } + + storePagination(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), + }; + + this.state.paginationInformation = paginationInformation; + return paginationInformation; + } +} + +module.exports = EnvironmentsFolderStore; diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index e69de29bb2d..452d32fc8b6 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -0,0 +1,8 @@ +- @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 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', -- cgit v1.2.1 From 082348491360d51ffaa737e3f266c4d1d6bdd946 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 12 Feb 2017 16:58:53 +0000 Subject: Adds url for folder; Creates new subview to show envirnoments that belong to a folder --- .../components/environment_item.js.es6 | 12 ++- .../folder/environments_folder_bundle.js.es6 | 1 - .../folder/environments_folder_view.js.es6 | 86 +++++++++++----------- .../stores/environments_folder_store.js.es6 | 49 ------------ app/views/projects/environments/folder.html.haml | 7 +- 5 files changed, 59 insertions(+), 96 deletions(-) delete mode 100644 app/assets/javascripts/environments/stores/environments_folder_store.js.es6 diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index fc45c3c5f53..e40c97130ad 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -407,6 +407,16 @@ module.exports = Vue.component('environment-item', { return ''; }, + + /** + * Constructs folder URL based on the current location and the folder id. + * + * @return {String} + */ + folderUrl() { + return `${window.location.pathname}/folders/${this.model.latest.id}`; + }, + }, /** @@ -432,7 +442,7 @@ module.exports = Vue.component('environment-item', { :href="environmentPath"> {{model.name}} - + diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 index 9cc1c2f4f18..d2ca465351a 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 @@ -1,5 +1,4 @@ const EnvironmentsFolderComponent = require('./environments_folder_view'); -require('../vue_shared/vue_resource_interceptor'); $(() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 index 070bc84bbe3..83643161056 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 +++ b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 @@ -4,9 +4,8 @@ const Vue = require('vue'); Vue.use(require('vue-resource')); const EnvironmentsService = require('../services/environments_service'); -const EnvironmentTable = require('./environments_table'); -const Store = require('../stores/environments_folder_store'); -require('../../vue_shared/components/table_pagination'); +const EnvironmentTable = require('../components/environments_table'); +const Store = require('../stores/environments_store'); module.exports = Vue.component('environment-folder-view', { @@ -15,27 +14,25 @@ module.exports = Vue.component('environment-folder-view', { 'table-pagination': gl.VueGlPagination, }, - props: { - endpoint: { - type: String, - required: true, - default: '', - }, - - folderName: { - type: String, - required: true, - default: '', - }, - }, - data() { + const environmentsData = document.querySelector('#environments-folder-list-view').dataset; const store = new Store(); + const endpoint = `${window.location.pathname}.json`; return { store, + 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: {}, @@ -43,6 +40,29 @@ module.exports = Vue.component('environment-folder-view', { }; }, + computed: { + scope() { + return this.$options.getQueryParameter('scope'); + }, + + canReadEnvironmentParsed() { + return this.$options.convertPermissionToBoolean(this.canReadEnvironment); + }, + + canCreateDeploymentParsed() { + return this.$options.convertPermissionToBoolean(this.canCreateDeployment); + }, + + stoppedPath() { + return `${window.location.pathname}?scope=stopped`; + }, + + availablePath() { + return window.location.pathname; + }, + + }, + /** * Fetches all the environments and stores them. * Toggles loading property. @@ -123,9 +143,12 @@ module.exports = Vue.component('environment-folder-view', { template: `
    @@ -153,26 +171,6 @@ module.exports = Vue.component('environment-folder-view', {
    -
    -

    - You don't have any environments right now. -

    -

    - Environments are places where code gets deployed, such as staging or production. -
    - - Read more about environments - -

    - - - New Environment - -
    -
    diff --git a/app/assets/javascripts/environments/stores/environments_folder_store.js.es6 b/app/assets/javascripts/environments/stores/environments_folder_store.js.es6 deleted file mode 100644 index 005ed52d9a1..00000000000 --- a/app/assets/javascripts/environments/stores/environments_folder_store.js.es6 +++ /dev/null @@ -1,49 +0,0 @@ -require('~/lib/utils/common_utils'); -/** - * Environments Folder Store. - * - * Stores received environments that belong to a parent store. - */ -class EnvironmentsFolderStore { - constructor() { - this.state = {}; - this.state.environments = []; - this.state.paginationInformation = {}; - - return this; - } - - /** - * - * Stores the received environments. - * - * Each environment has the following schema - * { name: String, size: Number, latest: Object } - * - * - * @param {Array} environments - * @returns {Array} - */ - storeEnvironments(environments = []) { - this.state.environments = environments; - - return environments; - } - - storePagination(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), - }; - - this.state.paginationInformation = paginationInformation; - return paginationInformation; - } -} - -module.exports = EnvironmentsFolderStore; diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index 452d32fc8b6..d9cb7bc0331 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -5,4 +5,9 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag("environments_folder") -#environments-folder-list-view +#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") } } -- cgit v1.2.1 From 17897c37f806e593359239ba09667081b88cb24b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Feb 2017 14:41:50 +0000 Subject: Fix underline style --- app/assets/stylesheets/pages/environments.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 606cf501b82..2f4a3c80aeb 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -122,6 +122,7 @@ .folder-name { cursor: pointer; color: $gl-text-color-secondary; + display: inline-block; } } -- cgit v1.2.1 From 73accafe430f56cd3065774c6118de3db0a45734 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Feb 2017 14:49:19 +0000 Subject: Use common util to get parameter name --- .../environments/components/environment.js.es6 | 24 +++++----------------- .../folder/environments_folder_view.js.es6 | 7 ++++--- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 42f74e114c9..2cbfbcad023 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -7,6 +7,7 @@ const EnvironmentsService = require('../services/environments_service'); const EnvironmentTable = require('./environments_table'); const Store = require('../stores/environments_store'); require('../../vue_shared/components/table_pagination'); +require('../../lib/utils/common_utils'); module.exports = Vue.component('environment-component', { @@ -45,7 +46,7 @@ module.exports = Vue.component('environment-component', { computed: { scope() { - return this.$options.getQueryParameter('scope'); + return gl.utils.getParameterByName('scope'); }, canReadEnvironmentParsed() { @@ -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,21 +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 @@ -158,7 +144,7 @@ module.exports = Vue.component('environment-component', {