From b64555d36a0b931dc087ddb48674c0fe36581e8c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 28 Mar 2017 11:56:24 +0100 Subject: Adds show all button --- .../environments/components/environment.js | 14 +-- .../environments/components/environment_item.js | 13 ++- .../environments/components/environments_table.js | 43 ++++++-- .../environments/services/environments_service.js | 14 +-- .../environments/stores/environments_store.js | 46 +++++++-- app/assets/stylesheets/pages/environments.scss | 8 ++ changelogs/unreleased/28732-expandable-folders.yml | 4 + spec/javascripts/environments/environment_spec.js | 99 +++++++++++++++++- .../environments/environments_store_spec.js | 111 ++++++++++++--------- spec/javascripts/environments/mock_data.js | 16 +++ 10 files changed, 287 insertions(+), 81 deletions(-) create mode 100644 changelogs/unreleased/28732-expandable-folders.yml diff --git a/app/assets/javascripts/environments/components/environment.js b/app/assets/javascripts/environments/components/environment.js index 57599876f5a..9760b30f30c 100644 --- a/app/assets/javascripts/environments/components/environment.js +++ b/app/assets/javascripts/environments/components/environment.js @@ -78,11 +78,11 @@ export default Vue.component('environment-component', { }, methods: { - toggleFolder(folder) { + toggleFolder(folder, folderUrl) { this.store.toggleFolder(folder); if (!folder.isOpen) { - this.fetchChildEnvironments(folder); + this.fetchChildEnvironments(folder, folderUrl); } }, @@ -125,14 +125,13 @@ export default Vue.component('environment-component', { }); }, - fetchChildEnvironments(folder) { + fetchChildEnvironments(folder, folderUrl) { this.isLoadingFolderContent = true; - this.service.getFolderContent(folder.folderName) + this.service.getFolderContent(folderUrl) .then(resp => resp.json()) .then((response) => { - console.log(response); - this.store.folderContent(folder, response.environments); + this.store.setfolderContent(folder, response.environments); }) .then(() => { this.isLoadingFolderContent = false; @@ -204,7 +203,8 @@ export default Vue.component('environment-component', { :environments="state.environments" :can-create-deployment="canCreateDeploymentParsed" :can-read-environment="canReadEnvironmentParsed" - :service="service"/> + :service="service" + :is-loading-folder-content="isLoadingFolderContent" /> + - + + diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js index d5729c9d1c5..4917df64a56 100644 --- a/app/assets/javascripts/environments/services/environments_service.js +++ b/app/assets/javascripts/environments/services/environments_service.js @@ -6,14 +6,7 @@ Vue.use(VueResource); export default class EnvironmentsService { constructor(endpoint) { - /** - * FIX ME: This should be sent by backend. - */ - const customActions = { - folderContent: { method: 'GET', url: `${window.location.pathname}/folders{/name}?perPage=2` }, - }; - - this.environments = Vue.resource(endpoint, {}, customActions); + this.environments = Vue.resource(endpoint); } get(scope, page) { @@ -24,8 +17,7 @@ export default class EnvironmentsService { return Vue.http.post(endpoint, {}, { emulateJSON: true }); } - getFolderContent(folderName) { - debugger - return this.environments.folderContent({ name: folderName }); + getFolderContent(folderUrl) { + return Vue.http.get(`${folderUrl}.json?per_page=3`); } } diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index a1c0988f1f5..158e7922e3c 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -92,23 +92,53 @@ export default class EnvironmentsStore { } /** - * Toggles folder open property given the given folder. + * Toggles folder open property for the given folder. * - * @param {String} envType + * @param {Object} folder * @return {Array} */ toggleFolder(folder) { return this.updateFolder(folder, 'isOpen', !folder.isOpen); } - folderContent(folder, environments) { - debugger; - return this.updateFolder(folder, 'children', environments); + /** + * Updates the folder with the received environments. + * + * + * @param {Object} folder Folder to update + * @param {Array} environments Received environments + * @return {Object} + */ + setfolderContent(folder, environments) { + const updatedEnvironments = environments.map((env) => { + let updated = env; + + if (env.latest) { + updated = Object.assign({}, env, env.latest); + delete updated.latest; + } else { + updated = env; + } + + updated.isChildren = true; + + return updated; + }); + + return this.updateFolder(folder, 'children', updatedEnvironments); } + /** + * Given a folder a prop and a new value updates the correct folder. + * + * @param {Object} folder + * @param {String} prop + * @param {String|Boolean|Object|Array} newValue + * @return {Array} + */ updateFolder(folder, prop, newValue) { const environments = this.state.environments; - debugger; + const updatedEnvironments = environments.map((env) => { const updateEnv = Object.assign({}, env); if (env.isFolder && env.id === folder.id) { @@ -117,10 +147,10 @@ export default class EnvironmentsStore { return updateEnv; }); - debugger; - console.log(updatedEnvironments); this.state.environments = updatedEnvironments; + + return updatedEnvironments; } } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 25be7f408d0..b2ec8f38e6a 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -54,6 +54,14 @@ } } + .child-row td:first-child a { + margin-left: 17px; + } + + .environments-show-all { + text-align: center; + } + .btn-group { > a { diff --git a/changelogs/unreleased/28732-expandable-folders.yml b/changelogs/unreleased/28732-expandable-folders.yml new file mode 100644 index 00000000000..9ae30ba6253 --- /dev/null +++ b/changelogs/unreleased/28732-expandable-folders.yml @@ -0,0 +1,4 @@ +--- +title: Add back expandable folder behavior +merge_request: +author: diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js index 9601575577e..37e01ef7516 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environment_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import '~/flash'; import EnvironmentsComponent from '~/environments/components/environment'; -import { environment } from './mock_data'; +import { environment, folder } from './mock_data'; describe('Environment', () => { preloadFixtures('static/environments/environments.html.raw'); @@ -175,4 +175,101 @@ describe('Environment', () => { }, 0); }); }); + + describe('expandable folders', () => { + const environmentsResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify({ + environments: [folder], + stopped_count: 0, + available_count: 1, + }), { + 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(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsResponseInterceptor, + ); + }); + + it('should open a closed folder', (done) => { + setTimeout(() => { + component.$el.querySelector('.folder-name').click(); + + Vue.nextTick(() => { + expect( + component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'), + ).toContain('display: none'); + expect( + component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'), + ).not.toContain('display: none'); + done(); + }); + }); + }); + + it('should close an opened folder', (done) => { + setTimeout(() => { + // open folder + component.$el.querySelector('.folder-name').click(); + + Vue.nextTick(() => { + // close folder + component.$el.querySelector('.folder-name').click(); + + Vue.nextTick(() => { + expect( + component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'), + ).toContain('display: none'); + expect( + component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'), + ).not.toContain('display: none'); + done(); + }); + }); + }); + }); + + it('should show children environments and a button to show all environments', (done) => { + setTimeout(() => { + // open folder + component.$el.querySelector('.folder-name').click(); + + Vue.nextTick(() => { + const folderInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify({ + environments: [environment], + }), { status: 200 })); + }; + + Vue.http.interceptors.push(folderInterceptor); + + // wait for next async request + setTimeout(() => { + expect(component.$el.querySelectorAll('.child-row').length).toEqual(1); + expect(component.$el.querySelector('td.text-center > a.btn').textContent).toContain('Show all'); + + Vue.http.interceptors = _.without(Vue.http.interceptors, folderInterceptor); + done(); + }); + }); + }); + }); + }); }); diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js index 115d84b50f5..55181621e8d 100644 --- a/spec/javascripts/environments/environments_store_spec.js +++ b/spec/javascripts/environments/environments_store_spec.js @@ -1,58 +1,79 @@ import Store from '~/environments/stores/environments_store'; import { environmentsList, serverData } from './mock_data'; -(() => { - describe('Store', () => { - let store; +describe('Store', () => { + let store; - beforeEach(() => { - store = new Store(); - }); + beforeEach(() => { + store = new Store(); + }); - it('should start with a blank state', () => { - 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 start with a blank state', () => { + 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(serverData); + expect(store.state.environments.length).toEqual(serverData.length); + expect(store.state.environments[0]).toEqual(environmentsList[0]); + }); + + it('should store available count', () => { + store.storeAvailableCount(2); + expect(store.state.availableCounter).toEqual(2); + }); + + it('should store stopped count', () => { + store.storeStoppedCount(2); + expect(store.state.stoppedCounter).toEqual(2); + }); - it('should store environments', () => { + it('should store pagination information', () => { + const pagination = { + 'X-nExt-pAge': '2', + 'X-page': '1', + 'X-Per-Page': '1', + 'X-Prev-Page': '2', + 'X-TOTAL': '37', + 'X-Total-Pages': '2', + }; + + const expectedResult = { + perPage: 1, + page: 1, + total: 37, + totalPages: 2, + nextPage: 2, + previousPage: 2, + }; + + store.setPagination(pagination); + expect(store.state.paginationInformation).toEqual(expectedResult); + }); + + describe('toggleFolder', () => { + it('should toggle folder', () => { store.storeEnvironments(serverData); - expect(store.state.environments.length).toEqual(serverData.length); - expect(store.state.environments[0]).toEqual(environmentsList[0]); - }); - it('should store available count', () => { - store.storeAvailableCount(2); - expect(store.state.availableCounter).toEqual(2); - }); + store.toggleFolder(store.state.environments[1]); + expect(store.state.environments[1].isOpen).toEqual(true); - it('should store stopped count', () => { - store.storeStoppedCount(2); - expect(store.state.stoppedCounter).toEqual(2); + store.toggleFolder(store.state.environments[1]); + expect(store.state.environments[1].isOpen).toEqual(false); }); + }); + + describe('setfolderContent', () => { + it('should store folder content', () => { + store.storeEnvironments(serverData); + + store.setfolderContent(store.state.environments[1], serverData); - it('should store pagination information', () => { - const pagination = { - 'X-nExt-pAge': '2', - 'X-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '2', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', - }; - - const expectedResult = { - perPage: 1, - page: 1, - total: 37, - totalPages: 2, - nextPage: 2, - previousPage: 2, - }; - - store.setPagination(pagination); - expect(store.state.paginationInformation).toEqual(expectedResult); + expect(store.state.environments[1].children.length).toEqual(serverData.length); + expect(store.state.environments[1].children[0].isChildren).toEqual(true); }); }); -})(); +}); diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js index 30861481cc5..15e11aa686b 100644 --- a/spec/javascripts/environments/mock_data.js +++ b/spec/javascripts/environments/mock_data.js @@ -84,3 +84,19 @@ export const environment = { updated_at: '2017-01-31T10:53:46.894Z', }, }; + +export const folder = { + folderName: 'build', + size: 5, + id: 12, + name: 'build/update-README', + state: 'available', + external_url: null, + environment_type: 'build', + last_deployment: null, + 'stop_action?': false, + environment_path: '/root/review-app/environments/12', + stop_path: '/root/review-app/environments/12/stop', + created_at: '2017-02-01T19:42:18.400Z', + updated_at: '2017-02-01T19:42:18.400Z', +}; -- cgit v1.2.1