BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
6669bfd3
Commit
6669bfd3
authored
Jan 19, 2017
by
Fatih Acet
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '25507-handle-errors-environment-list' into 'master'
Resolve "Error handling in environments list" Closes #25507 See merge request !8461
parents
1c81452a
725b1654
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
218 additions
and
48 deletions
+218
-48
environment.js.es6
...ts/javascripts/environments/components/environment.js.es6
+11
-45
environments_store.js.es6
...javascripts/environments/stores/environments_store.js.es6
+61
-2
25507-handle-errors-environment-list.yml
...elogs/unreleased/25507-handle-errors-environment-list.yml
+4
-0
environment_spec.js.es6
spec/javascripts/environments/environment_spec.js.es6
+127
-0
mock_data.js.es6
spec/javascripts/environments/mock_data.js.es6
+14
-0
environments.html.haml
.../javascripts/fixtures/environments/environments.html.haml
+1
-1
No files found.
app/assets/javascripts/environments/components/environment.js.es6
View file @
6669bfd3
/* eslint-disable no-param-reassign */
/* eslint-disable no-param-reassign
, no-new
*/
/* global Vue */
/* global EnvironmentsService */
/* global Flash */
//= require vue
//= require vue-resource
...
...
@@ -10,41 +11,6 @@
(() => {
window.gl = window.gl || {};
/**
* 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 `filterEnvironmentsByState`
* 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.
*/
const filterState = state => environment => environment.state === state && environment;
/**
* Given the filter function and the array of environments will return only
* the environments that match the state provided to the filter function.
*
* @param {Function} fn
* @param {Array} array
* @return {Array}
*/
const filterEnvironmentsByState = (fn, arr) => arr.map((item) => {
if (item.children) {
const filteredChildren = filterEnvironmentsByState(fn, item.children).filter(Boolean);
if (filteredChildren.length) {
item.children = filteredChildren;
return item;
}
}
return fn(item);
}).filter(Boolean);
gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', {
props: {
store: {
...
...
@@ -81,10 +47,6 @@
},
computed: {
filteredEnvironments() {
return filterEnvironmentsByState(filterState(this.visibility), this.state.environments);
},
scope() {
return this.$options.getQueryParameter('scope');
},
...
...
@@ -111,7 +73,7 @@
const scope = this.$options.getQueryParameter('scope');
if (scope) {
this.
visibility = scope
;
this.
store.storeVisibility(scope)
;
}
this.isLoading = true;
...
...
@@ -121,6 +83,10 @@
.then((json) => {
this.store.storeEnvironments(json);
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the environments.', 'alert');
});
},
...
...
@@ -188,7 +154,7 @@
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.environments.length === 0">
<h2 class="blank-state-title">
<h2 class="blank-state-title
js-blank-state-title
">
You don't have any environments right now.
</h2>
<p class="blank-state-text">
...
...
@@ -202,13 +168,13 @@
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create">
class="btn btn-create
js-new-environment-button
">
New Environment
</a>
</div>
<div class="table-holder"
v-if="!isLoading && state.
e
nvironments.length > 0">
v-if="!isLoading && state.
filteredE
nvironments.length > 0">
<table class="table ci-table environments">
<thead>
<tr>
...
...
@@ -221,7 +187,7 @@
</tr>
</thead>
<tbody>
<template v-for="model in filteredEnvironments"
<template v-for="model in
state.
filteredEnvironments"
v-bind:model="model">
<tr
...
...
app/assets/javascripts/environments/stores/environments_store.js.es6
View file @
6669bfd3
...
...
@@ -10,6 +10,8 @@
this.state.environments = [];
this.state.stoppedCounter = 0;
this.state.availableCounter = 0;
this.state.visibility = 'available';
this.state.filteredEnvironments = [];
return this;
},
...
...
@@ -59,7 +61,7 @@
if (occurs.length) {
acc[acc.indexOf(occurs[0])].children.push(environment);
acc[acc.indexOf(occurs[0])].children.sort(this.sortByName);
acc[acc.indexOf(occurs[0])].children.s
lice().s
ort(this.sortByName);
} else {
acc.push({
name: environment.environment_type,
...
...
@@ -73,13 +75,70 @@
}
return acc;
}, []).sort(this.sortByName);
}, []).s
lice().s
ort(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.
*
...
...
changelogs/unreleased/25507-handle-errors-environment-list.yml
0 → 100644
View file @
6669bfd3
---
title
:
Handle HTTP errors in environment list
merge_request
:
author
:
spec/javascripts/environments/environment_spec.js.es6
0 → 100644
View file @
6669bfd3
/* global Vue, environment */
//= require vue
//= require vue-resource
//= require flash
//= require environments/stores/environments_store
//= require environments/components/environment
//= require ./mock_data
describe('Environment', () => {
preloadFixtures('environments/environments');
let component;
beforeEach(() => {
loadFixtures('environments/environments');
});
describe('successfull request', () => {
describe('without environments', () => {
const environmentsEmptyResponseInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify([]), {
status: 200,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, environmentsEmptyResponseInterceptor,
);
});
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(() => {
expect(
component.$el.querySelector('.js-new-environment-button').textContent,
).toContain('New Environment');
expect(
component.$el.querySelector('.js-blank-state-title').textContent,
).toContain('You don\'t have any environments right now.');
done();
}, 0);
});
});
describe('with environments', () => {
const environmentsResponseInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify([environment]), {
status: 200,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, environmentsResponseInterceptor,
);
});
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(() => {
expect(
component.$el.querySelectorAll('table tbody tr').length,
).toEqual(1);
done();
}, 0);
});
});
});
describe('unsuccessfull request', () => {
const environmentsErrorResponseInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify([]), {
status: 500,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(environmentsErrorResponseInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, environmentsErrorResponseInterceptor,
);
});
it('should render empty state', (done) => {
component = new gl.environmentsList.EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
propsData: {
store: gl.environmentsList.EnvironmentsStore.create(),
},
});
setTimeout(() => {
expect(
component.$el.querySelector('.js-blank-state-title').textContent,
).toContain('You don\'t have any environments right now.');
done();
}, 0);
});
});
});
spec/javascripts/environments/mock_data.js.es6
View file @
6669bfd3
...
...
@@ -133,3 +133,17 @@ const environmentsList = [
updated_at: '2016-11-07T11:11:16.525Z',
},
];
const environment = {
id: 4,
name: 'production',
state: 'available',
external_url: 'http://production.',
environment_type: null,
last_deployment: {},
'stoppable?': 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',
};
spec/javascripts/fixtures/environments/environments.html.haml
View file @
6669bfd3
%div
#environments-list-view
{
data:
{
environments_data:
"
https://gitlab.com/
foo/environments"
,
#environments-list-view
{
data:
{
environments_data:
"foo/environments"
,
"can-create-deployment"
=>
"true"
,
"can-read-environment"
=>
"true"
,
"can-create-environment"
=>
"true"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment