Commit 19d7a99f authored by Phil Hughes's avatar Phil Hughes

Merge branch '2677-deploy-boards' into 'master'

Improve state management for deploy boards with realtime data

Closes #2677

See merge request !2311
parents d5628865 e71d8d94
<script> <script>
/* eslint-disable no-new, no-undef */ /**
/* global Flash */ * Renders a deploy board.
/** *
* Renders a deploy board. * A deploy board is composed by:
* * - Information area with percentage of completion.
* A deploy board is composed by: * - Instances with status.
* - Information area with percentage of completion. * - Button Actions.
* - Instances with status. * [Mockup](https://gitlab.com/gitlab-org/gitlab-ce/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png)
* - Button Actions. */
* [Mockup](https://gitlab.com/gitlab-org/gitlab-ce/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png) import deployBoardSvg from 'empty_states/icons/_deploy_board.svg';
* import instanceComponent from './deploy_board_instance_component.vue';
* The data of each deploy board needs to be fetched when we render the component. import loadingIcon from '../../vue_shared/components/loading_icon.vue';
*
* The endpoint response can sometimes be 204, in those cases we need to retry the request. export default {
* This should be done using backoff pooling and we should make no more than 3 request components: {
* for each deploy board. instanceComponent,
* After the third request we need to show a message saying we can't fetch the data. loadingIcon,
* Please refer to this [comment](https://gitlab.com/gitlab-org/gitlab-ee/issues/1589#note_23630610)
* for more information
*/
import Visibility from 'visibilityjs';
import deployBoardSvg from 'empty_states/icons/_deploy_board.svg';
import instanceComponent from './deploy_board_instance_component.vue';
import Poll from '../../lib/utils/poll';
import '../../flash';
export default {
components: {
instanceComponent,
},
props: {
store: {
type: Object,
required: true,
},
service: {
type: Object,
required: true,
}, },
props: {
deployBoardData: { deployBoardData: {
type: Object, type: Object,
required: true, required: true,
},
isLoading: {
type: Boolean,
required: true,
},
hasError: {
type: Boolean,
required: true,
},
}, },
data() {
environmentID: { return {
type: Number, deployBoardSvg,
required: true, };
}, },
computed: {
endpoint: { canRenderDeployBoard() {
type: String, return !this.isLoading && !this.hasError && this.deployBoardData.valid;
required: true, },
}, canRenderEmptyState() {
}, return !this.isLoading && !this.hasError && !this.deployBoardData.valid;
},
data() { canRenderErrorState() {
return { return !this.isLoading && this.hasError;
isLoading: false, },
hasError: false, instanceTitle() {
deployBoardSvg, let title;
};
}, if (this.deployBoardData.instances.length === 1) {
title = 'Instance';
created() { } else {
const poll = new Poll({ title = 'Instances';
resource: this.service, }
method: 'getDeployBoard',
data: this.endpoint, return title;
successCallback: this.successCallback, },
errorCallback: this.errorCallback, projectName() {
}); return '<projectname>';
},
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
},
methods: {
successCallback(response) {
const data = response.json();
this.store.storeDeployBoard(this.environmentID, data);
this.isLoading = false;
},
errorCallback() {
this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the deploy board.');
},
},
computed: {
canRenderDeployBoard() {
return !this.isLoading && !this.hasError && this.deployBoardData.valid;
},
canRenderEmptyState() {
return !this.isLoading && !this.hasError && !this.deployBoardData.valid;
},
canRenderErrorState() {
return !this.isLoading && this.hasError;
},
instanceTitle() {
let title;
if (this.deployBoardData.instances.length === 1) {
title = 'Instance';
} else {
title = 'Instances';
}
return title;
},
projectName() {
return '<projectname>';
}, },
}, };
};
</script> </script>
<template> <template>
<div class="js-deploy-board deploy-board"> <div class="js-deploy-board deploy-board">
<div v-if="isLoading"> <div v-if="isLoading">
<i <loading-icon />
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</div> </div>
<div v-if="canRenderDeployBoard"> <div v-if="canRenderDeployBoard">
...@@ -161,7 +87,8 @@ export default { ...@@ -161,7 +87,8 @@ export default {
<instance-component <instance-component
:status="instance.status" :status="instance.status"
:tooltip-text="instance.tooltip" :tooltip-text="instance.tooltip"
:stable="instance.stable" /> :stable="instance.stable"
/>
</template> </template>
</div> </div>
</section> </section>
......
...@@ -33,7 +33,6 @@ export default { ...@@ -33,7 +33,6 @@ export default {
state: store.state, state: store.state,
visibility: 'available', visibility: 'available',
isLoading: false, isLoading: false,
isLoadingFolderContent: false,
cssContainerClass: environmentsData.cssClass, cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint, endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment, canCreateDeployment: environmentsData.canCreateDeployment,
...@@ -97,9 +96,6 @@ export default { ...@@ -97,9 +96,6 @@ export default {
errorCallback: this.errorCallback, errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => { notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest; this.isMakingRequest = isMakingRequest;
// We need to verify if any folder is open to also fecth it
this.openFolders = this.store.getOpenFolders();
}, },
}); });
...@@ -118,11 +114,13 @@ export default { ...@@ -118,11 +114,13 @@ export default {
eventHub.$on('toggleFolder', this.toggleFolder); eventHub.$on('toggleFolder', this.toggleFolder);
eventHub.$on('postAction', this.postAction); eventHub.$on('postAction', this.postAction);
eventHub.$on('toggleDeployBoard', this.toggleDeployBoard);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('toggleFolder'); eventHub.$off('toggleFolder');
eventHub.$off('postAction'); eventHub.$off('postAction');
eventHub.$off('toggleDeployBoard');
}, },
methods: { methods: {
...@@ -134,14 +132,18 @@ export default { ...@@ -134,14 +132,18 @@ export default {
* @return {Object} * @return {Object}
*/ */
toggleDeployBoard(model) { toggleDeployBoard(model) {
return this.store.toggleDeployBoard(model.id); this.store.toggleDeployBoard(model.id);
if (!model.isDeployboardVisible) {
this.fetchDeployBoard(model, true);
}
}, },
toggleFolder(folder, folderUrl) { toggleFolder(folder, folderUrl) {
this.store.toggleFolder(folder); this.store.toggleFolder(folder);
if (!folder.isOpen) { if (!folder.isOpen) {
this.fetchChildEnvironments(folder, folderUrl); this.fetchChildEnvironments(folder, folderUrl, true);
} }
}, },
...@@ -169,19 +171,17 @@ export default { ...@@ -169,19 +171,17 @@ export default {
.catch(this.errorCallback); .catch(this.errorCallback);
}, },
fetchChildEnvironments(folder, folderUrl) { fetchChildEnvironments(folder, folderUrl, showLoader = false) {
this.isLoadingFolderContent = true; this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folderUrl) this.service.getFolderContent(folderUrl)
.then(resp => resp.json()) .then(resp => resp.json())
.then((response) => { .then(response => this.store.setfolderContent(folder, response.environments))
this.store.setfolderContent(folder, response.environments); .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
this.isLoadingFolderContent = false;
})
.catch(() => { .catch(() => {
this.isLoadingFolderContent = false;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.'); new Flash('An error occurred while fetching the environments.');
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
}); });
}, },
...@@ -198,16 +198,21 @@ export default { ...@@ -198,16 +198,21 @@ export default {
successCallback(resp) { successCallback(resp) {
this.saveData(resp); this.saveData(resp);
// If folders are open while polling we need to open them again // We need to verify if any folder is open to also update it
if (this.openFolders.length) { const openFolders = this.store.getOpenFolders();
this.openFolders.map((folder) => { if (openFolders.length) {
openFolders.forEach((folder) => {
// TODO - Move this to the backend // TODO - Move this to the backend
const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`; const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
this.store.updateFolder(folder, 'isOpen', true);
return this.fetchChildEnvironments(folder, folderUrl); return this.fetchChildEnvironments(folder, folderUrl);
}); });
} }
const openDeployBoards = this.store.getOpenDeployBoards();
if (openDeployBoards.length) {
openDeployBoards.forEach(env => this.fetchDeployBoard(env));
}
}, },
errorCallback() { errorCallback() {
...@@ -215,6 +220,23 @@ export default { ...@@ -215,6 +220,23 @@ export default {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.'); new Flash('An error occurred while fetching the environments.');
}, },
fetchDeployBoard(environment, showLoader = false) {
this.store.updateEnvironmentProp(environment, 'isLoadingDeployBoard', showLoader);
this.service.getDeployBoard(environment.rollout_status_path)
.then(resp => resp.json())
.then((data) => {
this.store.storeDeployBoard(environment.id, data);
this.store.updateEnvironmentProp(environment, 'isLoadingDeployBoard', false);
})
.catch(() => {
this.store.updateEnvironmentProp(environment, 'isLoadingDeployBoard', false);
this.store.updateEnvironmentProp(environment, 'hasErrorDeployBoard', true);
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the deploy board.');
});
},
}, },
}; };
</script> </script>
...@@ -289,10 +311,7 @@ export default { ...@@ -289,10 +311,7 @@ export default {
:environments="state.environments" :environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:toggleDeployBoard="toggleDeployBoard" />
:store="store"
:service="service"
:is-loading-folder-content="isLoadingFolderContent" />
</div> </div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
...@@ -44,12 +44,6 @@ export default { ...@@ -44,12 +44,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
toggleDeployBoard: {
type: Function,
required: false,
},
}, },
computed: { computed: {
...@@ -426,6 +420,9 @@ export default { ...@@ -426,6 +420,9 @@ export default {
onClickFolder() { onClickFolder() {
eventHub.$emit('toggleFolder', this.model, this.folderUrl); eventHub.$emit('toggleFolder', this.model, this.folderUrl);
}, },
toggleDeployBoard() {
eventHub.$emit('toggleDeployBoard', this.model);
},
}, },
}; };
</script> </script>
...@@ -443,17 +440,19 @@ export default { ...@@ -443,17 +440,19 @@ export default {
<span <span
class="deploy-board-icon" class="deploy-board-icon"
v-if="model.hasDeployBoard" v-if="model.hasDeployBoard"
@click="toggleDeployBoard(model)"> @click="toggleDeployBoard">
<i <i
v-show="!model.isDeployBoardVisible" v-show="!model.isDeployBoardVisible"
class="fa fa-caret-right" class="fa fa-caret-right"
aria-hidden="true" /> aria-hidden="true">
</i>
<i <i
v-show="model.isDeployBoardVisible" v-show="model.isDeployBoardVisible"
class="fa fa-caret-down" class="fa fa-caret-down"
aria-hidden="true" /> aria-hidden="true">
</i>
</span> </span>
<a <a
v-if="!model.isFolder" v-if="!model.isFolder"
......
...@@ -31,29 +31,6 @@ export default { ...@@ -31,29 +31,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isLoadingFolderContent: {
type: Boolean,
required: false,
default: false,
},
toggleDeployBoard: {
type: Function,
required: false,
default: () => {},
},
store: {
type: Object,
required: false,
default: () => ({}),
},
service: {
type: Object,
required: true,
},
}, },
methods: { methods: {
...@@ -90,23 +67,20 @@ export default { ...@@ -90,23 +67,20 @@ export default {
:model="model" :model="model"
:can-create-deployment="canCreateDeployment" :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment" :can-read-environment="canReadEnvironment"
:toggleDeployBoard="toggleDeployBoard"
/> />
<div v-if="model.hasDeployBoard && model.isDeployBoardVisible" class="js-deploy-board-row"> <div v-if="model.hasDeployBoard && model.isDeployBoardVisible" class="js-deploy-board-row">
<div class="deploy-board-container"> <div class="deploy-board-container">
<deploy-board <deploy-board
:store="store" :deploy-board-data="model.deployBoardData"
:service="service" :is-loading="model.isLoadingDeployBoard"
:environmentID="model.id" :has-error="model.hasErrorDeployBoard"
:deployBoardData="model.deployBoardData"
:endpoint="model.rollout_status_path"
/> />
</div> </div>
</div> </div>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0"> <template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<div v-if="isLoadingFolderContent"> <div v-if="model.isLoadingFolderContent">
<loading-icon size="2" /> <loading-icon size="2" />
</div> </div>
......
...@@ -43,14 +43,18 @@ export default class EnvironmentsStore { ...@@ -43,14 +43,18 @@ export default class EnvironmentsStore {
*/ */
storeEnvironments(environments = []) { storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => { const filteredEnvironments = environments.map((env) => {
const oldEnvironmentState = this.state.environments
.find(element => element.id === env.latest.id) || {};
let filtered = {}; let filtered = {};
if (env.size > 1) { if (env.size > 1) {
filtered = Object.assign({}, env, { filtered = Object.assign({}, env, {
isFolder: true, isFolder: true,
isLoadingFolderContent: oldEnvironmentState.isLoading || false,
folderName: env.name, folderName: env.name,
isOpen: false, isOpen: oldEnvironmentState.isOpen || false,
children: [], children: oldEnvironmentState.children || [],
}); });
} }
...@@ -64,8 +68,12 @@ export default class EnvironmentsStore { ...@@ -64,8 +68,12 @@ export default class EnvironmentsStore {
if (filtered.size === 1 && filtered.rollout_status_path) { if (filtered.size === 1 && filtered.rollout_status_path) {
filtered = Object.assign({}, filtered, { filtered = Object.assign({}, filtered, {
hasDeployBoard: true, hasDeployBoard: true,
isDeployBoardVisible: true, isDeployBoardVisible: oldEnvironmentState.isDeployBoardVisible === false ?
deployBoardData: {}, oldEnvironmentState.isDeployBoardVisible :
true,
deployBoardData: oldEnvironmentState.deployBoardData || {},
isLoadingDeployBoard: oldEnvironmentState.isLoadingDeployBoard || false,
hasErrorDeployBoard: oldEnvironmentState.hasErrorDeployBoard || false,
}); });
} }
return filtered; return filtered;
...@@ -169,7 +177,7 @@ export default class EnvironmentsStore { ...@@ -169,7 +177,7 @@ export default class EnvironmentsStore {
* @return {Array} * @return {Array}
*/ */
toggleFolder(folder) { toggleFolder(folder) {
return this.updateFolder(folder, 'isOpen', !folder.isOpen); return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen);
} }
/** /**
...@@ -196,23 +204,23 @@ export default class EnvironmentsStore { ...@@ -196,23 +204,23 @@ export default class EnvironmentsStore {
return updated; return updated;
}); });
return this.updateFolder(folder, 'children', updatedEnvironments); return this.updateEnvironmentProp(folder, 'children', updatedEnvironments);
} }
/** /**
* Given a folder a prop and a new value updates the correct folder. * Given a environment, a prop and a new value updates the correct environment.
* *
* @param {Object} folder * @param {Object} environment
* @param {String} prop * @param {String} prop
* @param {String|Boolean|Object|Array} newValue * @param {String|Boolean|Object|Array} newValue
* @return {Array} * @return {Array}
*/ */
updateFolder(folder, prop, newValue) { updateEnvironmentProp(environment, prop, newValue) {
const environments = this.state.environments; const environments = this.state.environments;
const updatedEnvironments = environments.map((env) => { const updatedEnvironments = environments.map((env) => {
const updateEnv = Object.assign({}, env); const updateEnv = Object.assign({}, env);
if (env.isFolder && env.id === folder.id) { if (env.id === environment.id) {
updateEnv[prop] = newValue; updateEnv[prop] = newValue;
} }
...@@ -220,8 +228,6 @@ export default class EnvironmentsStore { ...@@ -220,8 +228,6 @@ export default class EnvironmentsStore {
}); });
this.state.environments = updatedEnvironments; this.state.environments = updatedEnvironments;
return updatedEnvironments;
} }
getOpenFolders() { getOpenFolders() {
...@@ -229,4 +235,8 @@ export default class EnvironmentsStore { ...@@ -229,4 +235,8 @@ export default class EnvironmentsStore {
return environments.filter(env => env.isFolder && env.isOpen); return environments.filter(env => env.isFolder && env.isOpen);
} }
getOpenDeployBoards() {
return this.state.environments.filter(env => env.isDeployBoardVisible);
}
} }
---
title: Merge states to allow realtime with deploy boards
merge_request:
author:
import Vue from 'vue'; import Vue from 'vue';
import DeployBoard from '~/environments/components/deploy_board_component.vue'; import DeployBoard from '~/environments/components/deploy_board_component.vue';
import Service from '~/environments/services/environments_service';
import { deployBoardMockData, invalidDeployBoardMockData } from './mock_data'; import { deployBoardMockData, invalidDeployBoardMockData } from './mock_data';
describe('Deploy Board', () => { describe('Deploy Board', () => {
...@@ -10,147 +9,77 @@ describe('Deploy Board', () => { ...@@ -10,147 +9,77 @@ describe('Deploy Board', () => {
DeployBoardComponent = Vue.extend(DeployBoard); DeployBoardComponent = Vue.extend(DeployBoard);
}); });
describe('successfull request', () => { describe('with valid data', () => {
const deployBoardInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify(deployBoardMockData), {
status: 200,
}));
};
let component; let component;
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(deployBoardInterceptor);
this.service = new Service('environments');
component = new DeployBoardComponent({ component = new DeployBoardComponent({
propsData: { propsData: {
store: {},
service: this.service,
deployBoardData: deployBoardMockData, deployBoardData: deployBoardMockData,
environmentID: 1, isLoading: false,
endpoint: 'endpoint', hasError: false,
}, },
}).$mount(); }).$mount();
}); });
afterEach(() => { it('should render percentage with completion value provided', () => {
Vue.http.interceptors = _.without( expect(
Vue.http.interceptors, deployBoardInterceptor, component.$el.querySelector('.deploy-board-information .percentage').textContent,
); ).toEqual(`${deployBoardMockData.completion}%`);
}); });
it('should render percentage with completion value provided', (done) => { it('should render all instances', () => {
setTimeout(() => { const instances = component.$el.querySelectorAll('.deploy-board-instances-container div');
expect(
component.$el.querySelector('.deploy-board-information .percentage').textContent,
).toEqual(`${deployBoardMockData.completion}%`);
done();
}, 0);
});
it('should render all instances', (done) => { expect(instances.length).toEqual(deployBoardMockData.instances.length);
setTimeout(() => {
const instances = component.$el.querySelectorAll('.deploy-board-instances-container div');
expect(instances.length).toEqual(deployBoardMockData.instances.length); expect(
instances[2].classList.contains(`deploy-board-instance-${deployBoardMockData.instances[2].status}`),
expect( ).toBe(true);
instances[2].classList.contains(`deploy-board-instance-${deployBoardMockData.instances[2].status}`),
).toBe(true);
done();
}, 0);
}); });
it('should render an abort and a rollback button with the provided url', (done) => { it('should render an abort and a rollback button with the provided url', () => {
setTimeout(() => { const buttons = component.$el.querySelectorAll('.deploy-board-actions a');
const buttons = component.$el.querySelectorAll('.deploy-board-actions a');
expect(buttons[0].getAttribute('href')).toEqual(deployBoardMockData.rollback_url); expect(buttons[0].getAttribute('href')).toEqual(deployBoardMockData.rollback_url);
expect(buttons[1].getAttribute('href')).toEqual(deployBoardMockData.abort_url); expect(buttons[1].getAttribute('href')).toEqual(deployBoardMockData.abort_url);
done();
}, 0);
}); });
}); });
describe('successfull request without valid data', () => { describe('without valid data', () => {
const deployBoardInterceptorInvalidData = (request, next) => {
next(request.respondWith(JSON.stringify(invalidDeployBoardMockData), {
status: 200,
}));
};
let component; let component;
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(deployBoardInterceptorInvalidData);
this.service = new Service('environments');
component = new DeployBoardComponent({ component = new DeployBoardComponent({
propsData: { propsData: {
store: {},
service: new Service('environments'),
deployBoardData: invalidDeployBoardMockData, deployBoardData: invalidDeployBoardMockData,
environmentID: 1, isLoading: false,
endpoint: 'endpoint', hasError: false,
}, },
}).$mount(); }).$mount();
}); });
afterEach(() => { it('should render the empty state', () => {
Vue.http.interceptors = _.without( expect(component.$el.querySelector('.deploy-board-empty-state-svg svg')).toBeDefined();
Vue.http.interceptors, deployBoardInterceptorInvalidData, expect(component.$el.querySelector('.deploy-board-empty-state-text .title').textContent).toContain('Kubernetes deployment not found');
);
});
it('should render the empty state', (done) => {
setTimeout(() => {
expect(component.$el.querySelector('.deploy-board-empty-state-svg svg')).toBeDefined();
expect(component.$el.querySelector('.deploy-board-empty-state-text .title').textContent).toContain('Kubernetes deployment not found');
done();
}, 0);
}); });
}); });
describe('unsuccessfull request', () => { describe('with error', () => {
const deployBoardErrorInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify({}), {
status: 500,
}));
};
let component; let component;
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(deployBoardErrorInterceptor);
this.service = new Service('environments');
component = new DeployBoardComponent({ component = new DeployBoardComponent({
propsData: { propsData: {
store: {},
service: this.service,
deployBoardData: {}, deployBoardData: {},
environmentID: 1, isLoading: false,
endpoint: 'endpoint', hasError: true,
}, },
}).$mount(); }).$mount();
}); });
afterEach(() => { it('should render empty state', () => {
Vue.http.interceptors = _.without(Vue.http.interceptors, deployBoardErrorInterceptor); expect(component.$el.children.length).toEqual(1);
});
it('should render empty state', (done) => {
setTimeout(() => {
expect(component.$el.children.length).toEqual(1);
done();
}, 0);
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import environmentTableComp from '~/environments/components/environments_table.vue'; import environmentTableComp from '~/environments/components/environments_table.vue';
import eventHub from '~/environments/event_hub';
import { deployBoardMockData } from './mock_data'; import { deployBoardMockData } from './mock_data';
describe('Environment item', () => { describe('Environment item', () => {
...@@ -24,9 +25,6 @@ describe('Environment item', () => { ...@@ -24,9 +25,6 @@ describe('Environment item', () => {
environments: [mockItem], environments: [mockItem],
canCreateDeployment: false, canCreateDeployment: false,
canReadEnvironment: true, canReadEnvironment: true,
toggleDeployBoard: () => {},
store: {},
service: {},
}, },
}).$mount(); }).$mount();
...@@ -43,6 +41,8 @@ describe('Environment item', () => { ...@@ -43,6 +41,8 @@ describe('Environment item', () => {
hasDeployBoard: true, hasDeployBoard: true,
deployBoardData: deployBoardMockData, deployBoardData: deployBoardMockData,
isDeployBoardVisible: true, isDeployBoardVisible: true,
isLoadingDeployBoard: false,
hasErrorDeployBoard: false,
}; };
const component = new EnvironmentTable({ const component = new EnvironmentTable({
...@@ -51,11 +51,6 @@ describe('Environment item', () => { ...@@ -51,11 +51,6 @@ describe('Environment item', () => {
environments: [mockItem], environments: [mockItem],
canCreateDeployment: true, canCreateDeployment: true,
canReadEnvironment: true, canReadEnvironment: true,
toggleDeployBoard: () => {},
store: {},
service: {
getDeployBoard: () => Promise.resolve(),
},
}, },
}).$mount(); }).$mount();
...@@ -85,7 +80,9 @@ describe('Environment item', () => { ...@@ -85,7 +80,9 @@ describe('Environment item', () => {
isDeployBoardVisible: false, isDeployBoardVisible: false,
}; };
const spy = jasmine.createSpy('spy'); eventHub.$on('toggleDeployBoard', (env) => {
expect(env.id).toEqual(mockItem.id);
});
const component = new EnvironmentTable({ const component = new EnvironmentTable({
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
...@@ -93,16 +90,9 @@ describe('Environment item', () => { ...@@ -93,16 +90,9 @@ describe('Environment item', () => {
environments: [mockItem], environments: [mockItem],
canCreateDeployment: true, canCreateDeployment: true,
canReadEnvironment: true, canReadEnvironment: true,
toggleDeployBoard: spy,
store: {},
service: {
getDeployBoard: () => Promise.resolve(),
},
}, },
}).$mount(); }).$mount();
component.$el.querySelector('.deploy-board-icon').click(); component.$el.querySelector('.deploy-board-icon').click();
expect(spy).toHaveBeenCalled();
}); });
}); });
...@@ -33,6 +33,8 @@ describe('Store', () => { ...@@ -33,6 +33,8 @@ describe('Store', () => {
hasDeployBoard: true, hasDeployBoard: true,
isDeployBoardVisible: true, isDeployBoardVisible: true,
deployBoardData: {}, deployBoardData: {},
isLoadingDeployBoard: false,
hasErrorDeployBoard: false,
}; };
store.storeEnvironments(serverData); store.storeEnvironments(serverData);
...@@ -60,8 +62,10 @@ describe('Store', () => { ...@@ -60,8 +62,10 @@ describe('Store', () => {
const environment = { const environment = {
name: 'foo', name: 'foo',
size: 1, size: 1,
id: 1, latest: {
rollout_status_path: 'url', id: 1,
rollout_status_path: 'url',
},
}; };
store.storeEnvironments([environment]); store.storeEnvironments([environment]);
...@@ -74,7 +78,9 @@ describe('Store', () => { ...@@ -74,7 +78,9 @@ describe('Store', () => {
const environment = { const environment = {
name: 'bar', name: 'bar',
size: 3, size: 3,
id: 2, latest: {
id: 2,
},
}; };
store.storeEnvironments([environment]); store.storeEnvironments([environment]);
...@@ -119,6 +125,16 @@ describe('Store', () => { ...@@ -119,6 +125,16 @@ describe('Store', () => {
store.toggleFolder(store.state.environments[1]); store.toggleFolder(store.state.environments[1]);
expect(store.state.environments[1].isOpen).toEqual(false); expect(store.state.environments[1].isOpen).toEqual(false);
}); });
it('should keep folder open when environments are updated', () => {
store.storeEnvironments(serverData);
store.toggleFolder(store.state.environments[1]);
expect(store.state.environments[1].isOpen).toEqual(true);
store.storeEnvironments(serverData);
expect(store.state.environments[1].isOpen).toEqual(true);
});
}); });
describe('setfolderContent', () => { describe('setfolderContent', () => {
...@@ -130,6 +146,17 @@ describe('Store', () => { ...@@ -130,6 +146,17 @@ describe('Store', () => {
expect(store.state.environments[1].children.length).toEqual(serverData.length); expect(store.state.environments[1].children.length).toEqual(serverData.length);
expect(store.state.environments[1].children[0].isChildren).toEqual(true); expect(store.state.environments[1].children[0].isChildren).toEqual(true);
}); });
it('should keep folder content when environments are updated', () => {
store.storeEnvironments(serverData);
store.setfolderContent(store.state.environments[1], serverData);
expect(store.state.environments[1].children.length).toEqual(serverData.length);
// poll
store.storeEnvironments(serverData);
expect(store.state.environments[1].children.length).toEqual(serverData.length);
});
}); });
describe('store pagination', () => { describe('store pagination', () => {
...@@ -162,7 +189,10 @@ describe('Store', () => { ...@@ -162,7 +189,10 @@ describe('Store', () => {
const environment = { const environment = {
name: 'foo', name: 'foo',
size: 1, size: 1,
id: 1, latest: {
id: 1,
},
rollout_status_path: 'path',
}; };
store.storeEnvironments([environment]); store.storeEnvironments([environment]);
...@@ -170,13 +200,30 @@ describe('Store', () => { ...@@ -170,13 +200,30 @@ describe('Store', () => {
it('should toggle deploy board property for given environment id', () => { it('should toggle deploy board property for given environment id', () => {
store.toggleDeployBoard(1); store.toggleDeployBoard(1);
expect(store.state.environments[0].isDeployBoardVisible).toEqual(true);
expect(store.state.environments[0].isDeployBoardVisible).toEqual(false);
}); });
it('should store deploy board data for given environment id', () => { it('should store deploy board data for given environment id', () => {
store.storeDeployBoard(1, deployBoardMockData); store.storeDeployBoard(1, deployBoardMockData);
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData); expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
}); });
it('should keep deploy board data when updating environments', () => {
store.storeDeployBoard(1, deployBoardMockData);
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
const environment = {
name: 'foo',
size: 1,
latest: {
id: 1,
},
rollout_status_path: 'path',
};
store.storeEnvironments([environment]);
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
});
}); });
describe('getOpenFolders', () => { describe('getOpenFolders', () => {
...@@ -187,4 +234,22 @@ describe('Store', () => { ...@@ -187,4 +234,22 @@ describe('Store', () => {
expect(store.getOpenFolders()[0]).toEqual(store.state.environments[1]); expect(store.getOpenFolders()[0]).toEqual(store.state.environments[1]);
}); });
}); });
describe('getOpenDeployBoards', () => {
it('should return open deploy boards', () => {
const environment = {
name: 'foo',
size: 1,
latest: {
id: 1,
},
rollout_status_path: 'path',
};
store.storeEnvironments([environment]);
expect(store.getOpenDeployBoards().length).toEqual(1);
expect(store.getOpenDeployBoards()[0].id).toEqual(environment.latest.id);
});
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment