Merge branch 'paginate-environments-bundle' into 'master'
Paginate environments bundle Closes #25499 See merge request !9302
This commit is contained in:
commit
f15340e044
41 changed files with 1858 additions and 1352 deletions
|
@ -1,223 +1,192 @@
|
|||
/* eslint-disable no-param-reassign, no-new */
|
||||
/* global Vue */
|
||||
/* global EnvironmentsService */
|
||||
/* global Flash */
|
||||
|
||||
window.Vue = require('vue');
|
||||
window.Vue.use(require('vue-resource'));
|
||||
require('../services/environments_service');
|
||||
require('./environment_item');
|
||||
const Vue = require('vue');
|
||||
Vue.use(require('vue-resource'));
|
||||
const EnvironmentsService = require('../services/environments_service');
|
||||
const EnvironmentTable = require('./environments_table');
|
||||
const EnvironmentsStore = require('../stores/environments_store');
|
||||
require('../../vue_shared/components/table_pagination');
|
||||
require('../../lib/utils/common_utils');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
module.exports = Vue.component('environment-component', {
|
||||
|
||||
gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', {
|
||||
props: {
|
||||
store: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
components: {
|
||||
'environment-table': EnvironmentTable,
|
||||
'table-pagination': gl.VueGlPagination,
|
||||
},
|
||||
|
||||
data() {
|
||||
const environmentsData = document.querySelector('#environments-list-view').dataset;
|
||||
const store = new EnvironmentsStore();
|
||||
|
||||
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,
|
||||
|
||||
// Pagination Properties,
|
||||
paginationInformation: {},
|
||||
pageNumber: 1,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
scope() {
|
||||
return gl.utils.getParameterByName('scope');
|
||||
},
|
||||
|
||||
components: {
|
||||
'environment-item': gl.environmentsList.EnvironmentItem,
|
||||
canReadEnvironmentParsed() {
|
||||
return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
|
||||
},
|
||||
|
||||
data() {
|
||||
const environmentsData = document.querySelector('#environments-list-view').dataset;
|
||||
|
||||
return {
|
||||
state: this.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,
|
||||
};
|
||||
canCreateDeploymentParsed() {
|
||||
return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
|
||||
},
|
||||
|
||||
computed: {
|
||||
scope() {
|
||||
return this.$options.getQueryParameter('scope');
|
||||
},
|
||||
canCreateEnvironmentParsed() {
|
||||
return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment);
|
||||
},
|
||||
|
||||
canReadEnvironmentParsed() {
|
||||
return this.$options.convertPermissionToBoolean(this.canReadEnvironment);
|
||||
},
|
||||
},
|
||||
|
||||
canCreateDeploymentParsed() {
|
||||
return this.$options.convertPermissionToBoolean(this.canCreateDeployment);
|
||||
},
|
||||
/**
|
||||
* Fetches all the environments and stores them.
|
||||
* Toggles loading property.
|
||||
*/
|
||||
created() {
|
||||
const scope = gl.utils.getParameterByName('scope') || this.visibility;
|
||||
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
|
||||
|
||||
canCreateEnvironmentParsed() {
|
||||
return this.$options.convertPermissionToBoolean(this.canCreateEnvironment);
|
||||
},
|
||||
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
|
||||
|
||||
const service = new EnvironmentsService(endpoint);
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return service.all()
|
||||
.then(resp => ({
|
||||
headers: resp.headers,
|
||||
body: resp.json(),
|
||||
}))
|
||||
.then((response) => {
|
||||
this.store.storeAvailableCount(response.body.available_count);
|
||||
this.store.storeStoppedCount(response.body.stopped_count);
|
||||
this.store.storeEnvironments(response.body.environments);
|
||||
this.store.setPagination(response.headers);
|
||||
})
|
||||
.then(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.isLoading = false;
|
||||
new Flash('An error occurred while fetching the environments.', 'alert');
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleRow(model) {
|
||||
return this.store.toggleFolder(model.name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the environments and stores them.
|
||||
* Toggles loading property.
|
||||
*/
|
||||
created() {
|
||||
gl.environmentsService = new EnvironmentsService(this.endpoint);
|
||||
|
||||
const scope = this.$options.getQueryParameter('scope');
|
||||
if (scope) {
|
||||
this.store.storeVisibility(scope);
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return gl.environmentsService.all()
|
||||
.then(resp => resp.json())
|
||||
.then((json) => {
|
||||
this.store.storeEnvironments(json);
|
||||
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.
|
||||
* Will change the page number and update the URL.
|
||||
*
|
||||
* @param {String} param
|
||||
* @returns {String} The value of the requested parameter.
|
||||
* @param {Number} pageNumber desired page to go to.
|
||||
* @return {String}
|
||||
*/
|
||||
getQueryParameter(parameter) {
|
||||
return window.location.search.substring(1).split('&').reduce((acc, param) => {
|
||||
const paramSplited = param.split('=');
|
||||
acc[paramSplited[0]] = paramSplited[1];
|
||||
return acc;
|
||||
}, {})[parameter];
|
||||
},
|
||||
changePage(pageNumber) {
|
||||
const param = gl.utils.setParamInURL('page', pageNumber);
|
||||
|
||||
/**
|
||||
* Converts permission provided as strings to booleans.
|
||||
* @param {String} string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
convertPermissionToBoolean(string) {
|
||||
return string === 'true';
|
||||
gl.utils.visitUrl(param);
|
||||
return param;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleRow(model) {
|
||||
return this.store.toggleFolder(model.name);
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<div :class="cssContainerClass">
|
||||
<div class="top-area">
|
||||
<ul v-if="!isLoading" class="nav-links">
|
||||
<li v-bind:class="{ 'active': scope === undefined }">
|
||||
<a :href="projectEnvironmentsPath">
|
||||
Available
|
||||
<span class="badge js-available-environments-count">
|
||||
{{state.availableCounter}}
|
||||
</span>
|
||||
</a>
|
||||
</li><li v-bind:class="{ 'active' : scope === 'stopped' }">
|
||||
<a :href="projectStoppedEnvironmentsPath">
|
||||
Stopped
|
||||
<span class="badge js-stopped-environments-count">
|
||||
{{state.stoppedCounter}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls">
|
||||
<a :href="newEnvironmentPath" class="btn btn-create">
|
||||
New environment
|
||||
template: `
|
||||
<div :class="cssContainerClass">
|
||||
<div class="top-area">
|
||||
<ul v-if="!isLoading" class="nav-links">
|
||||
<li v-bind:class="{ 'active': scope === null || scope === 'available' }">
|
||||
<a :href="projectEnvironmentsPath">
|
||||
Available
|
||||
<span class="badge js-available-environments-count">
|
||||
{{state.availableCounter}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="environments-container">
|
||||
<div class="environments-list-loading text-center" v-if="isLoading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="blank-state blank-state-no-icon"
|
||||
v-if="!isLoading && state.environments.length === 0">
|
||||
<h2 class="blank-state-title js-blank-state-title">
|
||||
You don't have any environments right now.
|
||||
</h2>
|
||||
<p class="blank-state-text">
|
||||
Environments are places where code gets deployed, such as staging or production.
|
||||
<br />
|
||||
<a :href="helpPagePath">
|
||||
Read more about environments
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<a
|
||||
v-if="canCreateEnvironmentParsed"
|
||||
:href="newEnvironmentPath"
|
||||
class="btn btn-create js-new-environment-button">
|
||||
New Environment
|
||||
</li>
|
||||
<li v-bind:class="{ 'active' : scope === 'stopped' }">
|
||||
<a :href="projectStoppedEnvironmentsPath">
|
||||
Stopped
|
||||
<span class="badge js-stopped-environments-count">
|
||||
{{state.stoppedCounter}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-holder"
|
||||
v-if="!isLoading && state.filteredEnvironments.length > 0">
|
||||
<table class="table ci-table environments">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="environments-name">Environment</th>
|
||||
<th class="environments-deploy">Last deployment</th>
|
||||
<th class="environments-build">Job</th>
|
||||
<th class="environments-commit">Commit</th>
|
||||
<th class="environments-date">Updated</th>
|
||||
<th class="hidden-xs environments-actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="model in state.filteredEnvironments"
|
||||
v-bind:model="model">
|
||||
|
||||
<tr
|
||||
is="environment-item"
|
||||
:model="model"
|
||||
:toggleRow="toggleRow.bind(model)"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:terminal-icon-svg="terminalIconSvg"
|
||||
:commit-icon-svg="commitIconSvg"></tr>
|
||||
|
||||
<tr v-if="model.isOpen && model.children && model.children.length > 0"
|
||||
is="environment-item"
|
||||
v-for="children in model.children"
|
||||
:model="children"
|
||||
:toggleRow="toggleRow.bind(children)"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:terminal-icon-svg="terminalIconSvg"
|
||||
:commit-icon-svg="commitIconSvg">
|
||||
</tr>
|
||||
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls">
|
||||
<a :href="newEnvironmentPath" class="btn btn-create">
|
||||
New environment
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
|
||||
<div class="environments-container">
|
||||
<div class="environments-list-loading text-center" v-if="isLoading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="blank-state blank-state-no-icon"
|
||||
v-if="!isLoading && state.environments.length === 0">
|
||||
<h2 class="blank-state-title js-blank-state-title">
|
||||
You don't have any environments right now.
|
||||
</h2>
|
||||
<p class="blank-state-text">
|
||||
Environments are places where code gets deployed, such as staging or production.
|
||||
<br />
|
||||
<a :href="helpPagePath">
|
||||
Read more about environments
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<a v-if="canCreateEnvironmentParsed"
|
||||
:href="newEnvironmentPath"
|
||||
class="btn btn-create js-new-environment-button">
|
||||
New Environment
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-holder"
|
||||
v-if="!isLoading && state.environments.length > 0">
|
||||
|
||||
<environment-table
|
||||
:environments="state.environments"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:terminal-icon-svg="terminalIconSvg"
|
||||
:commit-icon-svg="commitIconSvg">
|
||||
</environment-table>
|
||||
|
||||
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
|
||||
:change="changePage"
|
||||
:pageInfo="state.paginationInformation">
|
||||
</table-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -1,50 +1,43 @@
|
|||
/* global Vue */
|
||||
const Vue = require('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,
|
||||
},
|
||||
module.exports = Vue.component('actions-component', {
|
||||
props: {
|
||||
actions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="inline">
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-new btn btn-default" data-toggle="dropdown">
|
||||
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</a>
|
||||
playIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-align-right">
|
||||
<li v-for="action in actions">
|
||||
<a :href="action.play_path"
|
||||
data-method="post"
|
||||
rel="nofollow"
|
||||
class="js-manual-action-link">
|
||||
template: `
|
||||
<div class="inline">
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-new btn btn-default" data-toggle="dropdown">
|
||||
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</a>
|
||||
|
||||
<span class="js-action-play-icon-container" v-html="playIconSvg"></span>
|
||||
<ul class="dropdown-menu dropdown-menu-align-right">
|
||||
<li v-for="action in actions">
|
||||
<a :href="action.play_path"
|
||||
data-method="post"
|
||||
rel="nofollow"
|
||||
class="js-manual-action-link">
|
||||
|
||||
<span>
|
||||
{{action.name}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="js-action-play-icon-container" v-html="playIconSvg"></span>
|
||||
|
||||
<span>
|
||||
{{action.name}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -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: `
|
||||
<a class="btn external_url" :href="externalUrl" target="_blank">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
template: `
|
||||
<a class="btn external_url" :href="externalUrl" target="_blank">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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: '',
|
||||
},
|
||||
|
||||
isLastDeployment: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
module.exports = Vue.component('rollback-component', {
|
||||
props: {
|
||||
retryUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
template: `
|
||||
<a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
|
||||
<span v-if="isLastDeployment">
|
||||
Re-deploy
|
||||
</span>
|
||||
<span v-else>
|
||||
Rollback
|
||||
</span>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
isLastDeployment: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
|
||||
<span v-if="isLastDeployment">
|
||||
Re-deploy
|
||||
</span>
|
||||
<span v-else>
|
||||
Rollback
|
||||
</span>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -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: `
|
||||
<a class="btn stop-env-link"
|
||||
:href="stopUrl"
|
||||
data-confirm="Are you sure you want to stop this environment?"
|
||||
data-method="post"
|
||||
rel="nofollow">
|
||||
<i class="fa fa-stop stop-env-icon"></i>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
template: `
|
||||
<a class="btn stop-env-link"
|
||||
:href="stopUrl"
|
||||
data-confirm="Are you sure you want to stop this environment?"
|
||||
data-method="post"
|
||||
rel="nofollow">
|
||||
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -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: `
|
||||
<a class="btn terminal-button"
|
||||
:href="terminalPath">
|
||||
<span class="js-terminal-icon-container" v-html="terminalIconSvg"></span>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
template: `
|
||||
<a class="btn terminal-button"
|
||||
:href="terminalPath">
|
||||
<span class="js-terminal-icon-container" v-html="terminalIconSvg"></span>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Render environments table.
|
||||
*/
|
||||
const Vue = require('vue');
|
||||
const EnvironmentItem = require('./environment_item');
|
||||
|
||||
module.exports = Vue.component('environment-table-component', {
|
||||
|
||||
components: {
|
||||
'environment-item': EnvironmentItem,
|
||||
},
|
||||
|
||||
props: {
|
||||
environments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => ([]),
|
||||
},
|
||||
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
commitIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
|
||||
playIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
|
||||
terminalIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<table class="table ci-table environments">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="environments-name">Environment</th>
|
||||
<th class="environments-deploy">Last deployment</th>
|
||||
<th class="environments-build">Job</th>
|
||||
<th class="environments-commit">Commit</th>
|
||||
<th class="environments-date">Updated</th>
|
||||
<th class="hidden-xs environments-actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="model in environments"
|
||||
v-bind:model="model">
|
||||
<tr is="environment-item"
|
||||
:model="model"
|
||||
:can-create-deployment="canCreateDeployment"
|
||||
:can-read-environment="canReadEnvironment"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:terminal-icon-svg="terminalIconSvg"
|
||||
:commit-icon-svg="commitIconSvg"></tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
});
|
|
@ -1,6 +1,4 @@
|
|||
window.Vue = require('vue');
|
||||
require('./stores/environments_store');
|
||||
require('./components/environment');
|
||||
const EnvironmentsComponent = require('./components/environment');
|
||||
require('../vue_shared/vue_resource_interceptor');
|
||||
|
||||
$(() => {
|
||||
|
@ -9,14 +7,8 @@ $(() => {
|
|||
if (gl.EnvironmentsListApp) {
|
||||
gl.EnvironmentsListApp.$destroy(true);
|
||||
}
|
||||
const Store = gl.environmentsList.EnvironmentsStore;
|
||||
|
||||
gl.EnvironmentsListApp = new gl.environmentsList.EnvironmentsComponent({
|
||||
gl.EnvironmentsListApp = new EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
|
||||
propsData: {
|
||||
store: Store.create(),
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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'),
|
||||
});
|
||||
});
|
|
@ -0,0 +1,181 @@
|
|||
/* eslint-disable no-param-reassign, no-new */
|
||||
/* global Flash */
|
||||
|
||||
const Vue = require('vue');
|
||||
Vue.use(require('vue-resource'));
|
||||
const EnvironmentsService = require('../services/environments_service');
|
||||
const EnvironmentTable = require('../components/environments_table');
|
||||
const EnvironmentsStore = require('../stores/environments_store');
|
||||
require('../../vue_shared/components/table_pagination');
|
||||
require('../../lib/utils/common_utils');
|
||||
|
||||
module.exports = Vue.component('environment-folder-view', {
|
||||
|
||||
components: {
|
||||
'environment-table': EnvironmentTable,
|
||||
'table-pagination': gl.VueGlPagination,
|
||||
},
|
||||
|
||||
data() {
|
||||
const environmentsData = document.querySelector('#environments-folder-list-view').dataset;
|
||||
const store = new EnvironmentsStore();
|
||||
const pathname = window.location.pathname;
|
||||
const endpoint = `${pathname}.json`;
|
||||
const folderName = pathname.substr(pathname.lastIndexOf('/') + 1);
|
||||
|
||||
return {
|
||||
store,
|
||||
folderName,
|
||||
endpoint,
|
||||
state: store.state,
|
||||
visibility: 'available',
|
||||
isLoading: false,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
canCreateDeployment: environmentsData.canCreateDeployment,
|
||||
canReadEnvironment: environmentsData.canReadEnvironment,
|
||||
|
||||
// svgs
|
||||
commitIconSvg: environmentsData.commitIconSvg,
|
||||
playIconSvg: environmentsData.playIconSvg,
|
||||
terminalIconSvg: environmentsData.terminalIconSvg,
|
||||
|
||||
// Pagination Properties,
|
||||
paginationInformation: {},
|
||||
pageNumber: 1,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
scope() {
|
||||
return gl.utils.getParameterByName('scope');
|
||||
},
|
||||
|
||||
canReadEnvironmentParsed() {
|
||||
return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
|
||||
},
|
||||
|
||||
canCreateDeploymentParsed() {
|
||||
return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
|
||||
},
|
||||
|
||||
/**
|
||||
* URL to link in the stopped tab.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
stoppedPath() {
|
||||
return `${window.location.pathname}?scope=stopped`;
|
||||
},
|
||||
|
||||
/**
|
||||
* URL to link in the available tab.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
availablePath() {
|
||||
return window.location.pathname;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the environments and stores them.
|
||||
* Toggles loading property.
|
||||
*/
|
||||
created() {
|
||||
const scope = gl.utils.getParameterByName('scope') || this.visibility;
|
||||
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
|
||||
|
||||
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
|
||||
|
||||
const service = new EnvironmentsService(endpoint);
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return service.all()
|
||||
.then(resp => ({
|
||||
headers: resp.headers,
|
||||
body: resp.json(),
|
||||
}))
|
||||
.then((response) => {
|
||||
this.store.storeAvailableCount(response.body.available_count);
|
||||
this.store.storeStoppedCount(response.body.stopped_count);
|
||||
this.store.storeEnvironments(response.body.environments);
|
||||
this.store.setPagination(response.headers);
|
||||
})
|
||||
.then(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.isLoading = false;
|
||||
new Flash('An error occurred while fetching the environments.', 'alert');
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Will change the page number and update the URL.
|
||||
*
|
||||
* @param {Number} pageNumber desired page to go to.
|
||||
*/
|
||||
changePage(pageNumber) {
|
||||
const param = gl.utils.setParamInURL('page', pageNumber);
|
||||
|
||||
gl.utils.visitUrl(param);
|
||||
return param;
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<div :class="cssContainerClass">
|
||||
<div class="top-area" v-if="!isLoading">
|
||||
|
||||
<h4 class="js-folder-name environments-folder-name">
|
||||
Environments / <b>{{folderName}}</b>
|
||||
</h4>
|
||||
|
||||
<ul class="nav-links">
|
||||
<li v-bind:class="{ 'active': scope === null || scope === 'available' }">
|
||||
<a :href="availablePath" class="js-available-environments-folder-tab">
|
||||
Available
|
||||
<span class="badge js-available-environments-count">
|
||||
{{state.availableCounter}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li v-bind:class="{ 'active' : scope === 'stopped' }">
|
||||
<a :href="stoppedPath" class="js-stopped-environments-folder-tab">
|
||||
Stopped
|
||||
<span class="badge js-stopped-environments-count">
|
||||
{{state.stoppedCounter}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="environments-container">
|
||||
<div class="environments-list-loading text-center" v-if="isLoading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="table-holder"
|
||||
v-if="!isLoading && state.environments.length > 0">
|
||||
|
||||
<environment-table
|
||||
:environments="state.environments"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:terminal-icon-svg="terminalIconSvg"
|
||||
:commit-icon-svg="commitIconSvg">
|
||||
</environment-table>
|
||||
|
||||
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
|
||||
:change="changePage"
|
||||
:pageInfo="state.paginationInformation">
|
||||
</table-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
|
@ -1,20 +1,8 @@
|
|||
/* globals Vue */
|
||||
/* eslint-disable no-unused-vars, no-param-reassign */
|
||||
const 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;
|
||||
|
|
|
@ -1,190 +1,90 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
require('~/lib/utils/common_utils');
|
||||
/**
|
||||
* 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.paginationInformation = {};
|
||||
|
||||
gl.environmentsList.EnvironmentsStore = {
|
||||
state: {},
|
||||
return this;
|
||||
}
|
||||
|
||||
create() {
|
||||
this.state.environments = [];
|
||||
this.state.stoppedCounter = 0;
|
||||
this.state.availableCounter = 0;
|
||||
this.state.visibility = 'available';
|
||||
this.state.filteredEnvironments = [];
|
||||
/**
|
||||
*
|
||||
* Stores the received environments.
|
||||
*
|
||||
* In the main environments endpoint, each environment has the following schema
|
||||
* { name: String, size: Number, latest: Object }
|
||||
* In the endpoint to retrieve environments from each folder, the environment does
|
||||
* not have the `latest` key and the data is all in the root level.
|
||||
* To avoid doing this check in the view, we store both cases the same by extracting
|
||||
* what is inside the `latest` key.
|
||||
*
|
||||
* If the `size` is bigger than 1, it means it should be rendered as a folder.
|
||||
* In those cases we add `isFolder` key in order to render it properly.
|
||||
*
|
||||
* @param {Array} environments
|
||||
* @returns {Array}
|
||||
*/
|
||||
storeEnvironments(environments = []) {
|
||||
const filteredEnvironments = environments.map((env) => {
|
||||
let filtered = {};
|
||||
|
||||
return this;
|
||||
},
|
||||
if (env.size > 1) {
|
||||
filtered = Object.assign({}, env, { isFolder: true, folderName: env.name });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* @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}
|
||||
* ]
|
||||
*
|
||||
*
|
||||
* @param {Array} environments List of environments.
|
||||
* @returns {Array} Tree structured array with the received environments.
|
||||
*/
|
||||
storeEnvironments(environments = []) {
|
||||
this.state.stoppedCounter = this.countByState(environments, 'stopped');
|
||||
this.state.availableCounter = this.countByState(environments, 'available');
|
||||
if (env.latest) {
|
||||
filtered = Object.assign(filtered, env, env.latest);
|
||||
delete filtered.latest;
|
||||
} else {
|
||||
filtered = Object.assign(filtered, env);
|
||||
}
|
||||
|
||||
const environmentsTree = environments.reduce((acc, environment) => {
|
||||
if (environment.environment_type !== null) {
|
||||
const occurs = acc.filter(element => element.children &&
|
||||
element.name === environment.environment_type);
|
||||
return filtered;
|
||||
});
|
||||
|
||||
environment['vue-isChildren'] = true;
|
||||
this.state.environments = filteredEnvironments;
|
||||
|
||||
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 filteredEnvironments;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []).slice().sort(this.sortByName);
|
||||
setPagination(pagination = {}) {
|
||||
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
|
||||
const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders);
|
||||
|
||||
this.state.environments = environmentsTree;
|
||||
this.state.paginationInformation = paginationInformation;
|
||||
return paginationInformation;
|
||||
}
|
||||
|
||||
this.filterEnvironmentsByVisibility(this.state.environments);
|
||||
/**
|
||||
* Stores the number of available environments.
|
||||
*
|
||||
* @param {Number} count = 0
|
||||
* @return {Number}
|
||||
*/
|
||||
storeAvailableCount(count = 0) {
|
||||
this.state.availableCounter = count;
|
||||
return count;
|
||||
}
|
||||
|
||||
return environmentsTree;
|
||||
},
|
||||
/**
|
||||
* Stores the number of closed environments.
|
||||
*
|
||||
* @param {Number} count = 0
|
||||
* @return {Number}
|
||||
*/
|
||||
storeStoppedCount(count = 0) {
|
||||
this.state.stoppedCounter = count;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return env;
|
||||
});
|
||||
|
||||
this.state.environments = environmentsCopy;
|
||||
|
||||
return environmentsCopy;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
},
|
||||
};
|
||||
})();
|
||||
module.exports = EnvironmentsStore;
|
||||
|
|
|
@ -231,6 +231,21 @@
|
|||
return upperCaseHeaders;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses pagination object string values into numbers.
|
||||
*
|
||||
* @param {Object} paginationInformation
|
||||
* @returns {Object}
|
||||
*/
|
||||
w.gl.utils.parseIntPagination = paginationInformation => ({
|
||||
perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
|
||||
page: parseInt(paginationInformation['X-PAGE'], 10),
|
||||
total: parseInt(paginationInformation['X-TOTAL'], 10),
|
||||
totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10),
|
||||
nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
|
||||
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
|
||||
});
|
||||
|
||||
/**
|
||||
* Transforms a DOMStringMap into a plain object.
|
||||
*
|
||||
|
@ -241,5 +256,45 @@
|
|||
acc[element] = DOMStringMapObject[element];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
/**
|
||||
* Updates the search parameter of a URL given the parameter and values provided.
|
||||
*
|
||||
* If no search params are present we'll add it.
|
||||
* If param for page is already present, we'll update it
|
||||
* If there are params but not for the given one, we'll add it at the end.
|
||||
* Returns the new search parameters.
|
||||
*
|
||||
* @param {String} param
|
||||
* @param {Number|String|Undefined|Null} value
|
||||
* @return {String}
|
||||
*/
|
||||
w.gl.utils.setParamInURL = (param, value) => {
|
||||
let search;
|
||||
const locationSearch = window.location.search;
|
||||
|
||||
if (locationSearch.length === 0) {
|
||||
search = `?${param}=${value}`;
|
||||
}
|
||||
|
||||
if (locationSearch.indexOf(param) !== -1) {
|
||||
const regex = new RegExp(param + '=\\d');
|
||||
search = locationSearch.replace(regex, `${param}=${value}`);
|
||||
}
|
||||
|
||||
if (locationSearch.length && locationSearch.indexOf(param) === -1) {
|
||||
search = `${locationSearch}&${param}=${value}`;
|
||||
}
|
||||
|
||||
return search;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts permission provided as strings to booleans.
|
||||
*
|
||||
* @param {String} string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
|
||||
})(window);
|
||||
}).call(this);
|
||||
|
|
|
@ -35,7 +35,16 @@ require('../vue_shared/components/pipelines_table');
|
|||
this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope);
|
||||
},
|
||||
methods: {
|
||||
change(pagenum, apiScope) {
|
||||
|
||||
/**
|
||||
* Changes the URL according to the pagination component.
|
||||
*
|
||||
* If no scope is provided, 'all' is assumed.
|
||||
*
|
||||
* @param {Number} pagenum
|
||||
* @param {String} apiScope = 'all'
|
||||
*/
|
||||
change(pagenum, apiScope = 'all') {
|
||||
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -5,16 +5,7 @@ require('../vue_realtime_listener');
|
|||
((gl) => {
|
||||
const pageValues = (headers) => {
|
||||
const normalized = gl.utils.normalizeHeaders(headers);
|
||||
|
||||
const paginationInfo = {
|
||||
perPage: +normalized['X-PER-PAGE'],
|
||||
page: +normalized['X-PAGE'],
|
||||
total: +normalized['X-TOTAL'],
|
||||
totalPages: +normalized['X-TOTAL-PAGES'],
|
||||
nextPage: +normalized['X-NEXT-PAGE'],
|
||||
previousPage: +normalized['X-PREV-PAGE'],
|
||||
};
|
||||
|
||||
const paginationInfo = gl.utils.normalizeHeaders(normalized);
|
||||
return paginationInfo;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global Vue */
|
||||
window.Vue = require('vue');
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
font-size: 34px;
|
||||
}
|
||||
|
||||
.environments-folder-name {
|
||||
font-weight: normal;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.environments-container {
|
||||
width: 100%;
|
||||
|
@ -110,17 +115,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
display: inline-block;
|
||||
|
||||
.fa:nth-child(1) {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.folder-name {
|
||||
cursor: pointer;
|
||||
color: $gl-text-color-secondary;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,4 +143,4 @@
|
|||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,15 +9,40 @@ 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
|
||||
.with_state(params[:scope] || :available)
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: EnvironmentSerializer
|
||||
.new(project: @project, user: current_user)
|
||||
.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
|
||||
|
||||
def folder
|
||||
folder_environments = project.environments.where(environment_type: params[:id])
|
||||
@environments = folder_environments.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),
|
||||
available_count: folder_environments.available.count,
|
||||
stopped_count: folder_environments.stopped.count
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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')
|
||||
|
||||
environments = resource.where(id: items.map(&:last)).index_by(&: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?
|
||||
|
||||
items.map do |name, size, id|
|
||||
Item.new(name, size, environments[id])
|
||||
environments = resource.where(id: items.map(&:last_id)).index_by(&:id)
|
||||
|
||||
items.map do |item|
|
||||
Item.new(item.folder_name, item.size, environments[item.last_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
13
app/views/projects/environments/folder.html.haml
Normal file
13
app/views/projects/environments/folder.html.haml
Normal file
|
@ -0,0 +1,13 @@
|
|||
- @no_container = true
|
||||
- page_title "Environments"
|
||||
= render "projects/pipelines/head"
|
||||
|
||||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_bundle_tag("environments_folder")
|
||||
|
||||
#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
|
||||
"can-read-environment" => can?(current_user, :read_environment, @project).to_s,
|
||||
"css-class" => container_class,
|
||||
"commit-icon-svg" => custom_icon("icon_commit"),
|
||||
"terminal-icon-svg" => custom_icon("icon_terminal"),
|
||||
"play-icon-svg" => custom_icon("icon_play") } }
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Adds paginationd and folders view to environments table
|
||||
merge_request:
|
||||
author:
|
|
@ -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]
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -3,9 +3,12 @@ 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 +25,58 @@ describe Projects::EnvironmentsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when requesting JSON response' do
|
||||
it 'responds with correct JSON' do
|
||||
get :index, environment_params(format: :json)
|
||||
context 'when requesting JSON response for folders' do
|
||||
before do
|
||||
create(:environment, project: project,
|
||||
name: 'staging/review-1',
|
||||
state: :available)
|
||||
|
||||
first_environment = json_response.first
|
||||
create(:environment, project: project,
|
||||
name: 'staging/review-2',
|
||||
state: :available)
|
||||
|
||||
expect(first_environment).not_to be_empty
|
||||
expect(first_environment['name']). to eq environment.name
|
||||
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
|
||||
|
||||
context 'when requesting stopped environments scope' do
|
||||
before do
|
||||
get :index, environment_params(format: :json, scope: :stopped)
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -275,7 +275,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')
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
window.timeago = require('timeago.js');
|
||||
require('~/environments/components/environment_item');
|
||||
const EnvironmentItem = require('~/environments/components/environment_item');
|
||||
|
||||
describe('Environment item', () => {
|
||||
preloadFixtures('static/environments/table.html.raw');
|
||||
|
@ -14,33 +14,16 @@ 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',
|
||||
},
|
||||
],
|
||||
folderName: 'review',
|
||||
size: 3,
|
||||
isFolder: true,
|
||||
environment_path: 'url',
|
||||
};
|
||||
|
||||
component = new window.gl.environmentsList.EnvironmentItem({
|
||||
component = new EnvironmentItem({
|
||||
el: document.querySelector('tr#environment-row'),
|
||||
propsData: {
|
||||
model: mockItem,
|
||||
toggleRow: () => {},
|
||||
canCreateDeployment: false,
|
||||
canReadEnvironment: true,
|
||||
},
|
||||
|
@ -53,7 +36,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,8 +46,8 @@ describe('Environment item', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
environment = {
|
||||
id: 31,
|
||||
name: 'production',
|
||||
size: 1,
|
||||
state: 'stopped',
|
||||
external_url: 'http://external.com',
|
||||
environment_type: null,
|
||||
|
@ -125,11 +108,10 @@ describe('Environment item', () => {
|
|||
updated_at: '2016-11-10T15:55:58.778Z',
|
||||
};
|
||||
|
||||
component = new window.gl.environmentsList.EnvironmentItem({
|
||||
component = new EnvironmentItem({
|
||||
el: document.querySelector('tr#environment-row'),
|
||||
propsData: {
|
||||
model: environment,
|
||||
toggleRow: () => {},
|
||||
canCreateDeployment: true,
|
||||
canReadEnvironment: true,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
/* global Vue, environment */
|
||||
|
||||
const Vue = require('vue');
|
||||
require('~/flash');
|
||||
require('~/environments/stores/environments_store');
|
||||
require('~/environments/components/environment');
|
||||
require('./mock_data');
|
||||
const EnvironmentsComponent = require('~/environments/components/environment');
|
||||
const { environment } = require('./mock_data');
|
||||
|
||||
describe('Environment', () => {
|
||||
preloadFixtures('static/environments/environments.html.raw');
|
||||
|
@ -33,11 +31,8 @@ describe('Environment', () => {
|
|||
});
|
||||
|
||||
it('should render the empty state', (done) => {
|
||||
component = new gl.environmentsList.EnvironmentsComponent({
|
||||
component = new EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
propsData: {
|
||||
store: gl.environmentsList.EnvironmentsStore.create(),
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -54,15 +49,30 @@ describe('Environment', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('with environments', () => {
|
||||
describe('with paginated 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,
|
||||
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(() => {
|
||||
|
@ -72,13 +82,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(() => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('table tbody tr').length,
|
||||
|
@ -86,6 +89,59 @@ describe('Environment', () => {
|
|||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
it('should render pagination', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('.gl-pagination li').length,
|
||||
).toEqual(5);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when no search params are present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page is already present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?page=1');
|
||||
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page and scope are already present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?scope=all&page=1');
|
||||
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page and scope are already present and page is first param', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?page=1&scope=all');
|
||||
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -107,11 +163,8 @@ describe('Environment', () => {
|
|||
});
|
||||
|
||||
it('should render empty state', (done) => {
|
||||
component = new gl.environmentsList.EnvironmentsComponent({
|
||||
component = new EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
propsData: {
|
||||
store: gl.environmentsList.EnvironmentsStore.create(),
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -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,
|
||||
|
|
30
spec/javascripts/environments/environment_table_spec.js.es6
Normal file
30
spec/javascripts/environments/environment_table_spec.js.es6
Normal file
|
@ -0,0 +1,30 @@
|
|||
const EnvironmentTable = require('~/environments/components/environments_table');
|
||||
|
||||
describe('Environment item', () => {
|
||||
preloadFixtures('static/environments/element.html.raw');
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/environments/element.html.raw');
|
||||
});
|
||||
|
||||
it('Should render a table', () => {
|
||||
const mockItem = {
|
||||
name: 'review',
|
||||
size: 3,
|
||||
isFolder: true,
|
||||
latest: {
|
||||
environment_path: 'url',
|
||||
},
|
||||
};
|
||||
|
||||
const component = new EnvironmentTable({
|
||||
el: document.querySelector('.test-dom-element'),
|
||||
propsData: {
|
||||
environments: [{ mockItem }],
|
||||
canCreateDeployment: false,
|
||||
canReadEnvironment: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.$el.tagName).toEqual('TABLE');
|
||||
});
|
||||
});
|
|
@ -1,70 +1,58 @@
|
|||
/* global environmentsList */
|
||||
|
||||
require('~/environments/stores/environments_store');
|
||||
require('./mock_data');
|
||||
const Store = require('~/environments/stores/environments_store');
|
||||
const { environmentsList, serverData } = 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).toEqual(0);
|
||||
expect(store.state.stoppedCounter).toEqual(0);
|
||||
expect(store.state.availableCounter).toEqual(0);
|
||||
expect(store.state.paginationInformation).toEqual({});
|
||||
});
|
||||
|
||||
describe('store environments', () => {
|
||||
beforeEach(() => {
|
||||
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');
|
||||
});
|
||||
it('should store environments', () => {
|
||||
store.storeEnvironments(serverData);
|
||||
expect(store.state.environments.length).toEqual(serverData.length);
|
||||
expect(store.state.environments[0]).toEqual(environmentsList[0]);
|
||||
});
|
||||
|
||||
describe('toggleFolder', () => {
|
||||
beforeEach(() => {
|
||||
gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList);
|
||||
});
|
||||
it('should store available count', () => {
|
||||
store.storeAvailableCount(2);
|
||||
expect(store.state.availableCounter).toEqual(2);
|
||||
});
|
||||
|
||||
it('should toggle the open property for the given environment', () => {
|
||||
gl.environmentsList.EnvironmentsStore.toggleFolder('review');
|
||||
it('should store stopped count', () => {
|
||||
store.storeStoppedCount(2);
|
||||
expect(store.state.stoppedCounter).toEqual(2);
|
||||
});
|
||||
|
||||
const { environments } = gl.environmentsList.EnvironmentsStore.state;
|
||||
const environment = environments.filter(env => env['vue-isChildren'] === true && env.name === 'review');
|
||||
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',
|
||||
};
|
||||
|
||||
expect(environment[0].isOpen).toBe(true);
|
||||
});
|
||||
const expectedResult = {
|
||||
perPage: 1,
|
||||
page: 1,
|
||||
total: 37,
|
||||
totalPages: 2,
|
||||
nextPage: 2,
|
||||
previousPage: 2,
|
||||
};
|
||||
|
||||
store.setPagination(pagination);
|
||||
expect(store.state.paginationInformation).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
const Vue = require('vue');
|
||||
require('~/flash');
|
||||
const EnvironmentsFolderViewComponent = require('~/environments/folder/environments_folder_view');
|
||||
const { environmentsList } = require('../mock_data');
|
||||
|
||||
describe('Environments Folder View', () => {
|
||||
preloadFixtures('static/environments/environments_folder_view.html.raw');
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/environments/environments_folder_view.html.raw');
|
||||
window.history.pushState({}, null, 'environments/folders/build');
|
||||
});
|
||||
|
||||
let component;
|
||||
|
||||
describe('successfull request', () => {
|
||||
const environmentsResponseInterceptor = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify({
|
||||
environments: environmentsList,
|
||||
stopped_count: 1,
|
||||
available_count: 0,
|
||||
}), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'X-nExt-pAge': '2',
|
||||
'x-page': '1',
|
||||
'X-Per-Page': '1',
|
||||
'X-Prev-Page': '',
|
||||
'X-TOTAL': '37',
|
||||
'X-Total-Pages': '2',
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(environmentsResponseInterceptor);
|
||||
component = new EnvironmentsFolderViewComponent({
|
||||
el: document.querySelector('#environments-folder-list-view'),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, environmentsResponseInterceptor,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a table with environments', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('table tbody tr').length,
|
||||
).toEqual(2);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render available tab with count', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab').textContent,
|
||||
).toContain('Available');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
|
||||
).toContain('0');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render stopped tab with count', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
|
||||
).toContain('Stopped');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
|
||||
).toContain('1');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render parent folder name', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-folder-name').textContent,
|
||||
).toContain('Environments / build');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
it('should render pagination', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('.gl-pagination li').length,
|
||||
).toEqual(5);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when no search params are present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page is already present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?page=1');
|
||||
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page and scope are already present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?scope=all&page=1');
|
||||
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page and scope are already present and page is first param', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?page=1&scope=all');
|
||||
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsuccessfull request', () => {
|
||||
const environmentsErrorResponseInterceptor = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify([]), {
|
||||
status: 500,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(environmentsErrorResponseInterceptor);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, environmentsErrorResponseInterceptor,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render a table', (done) => {
|
||||
component = new EnvironmentsFolderViewComponent({
|
||||
el: document.querySelector('#environments-folder-list-view'),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('table'),
|
||||
).toBe(null);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render available tab with count 0', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab').textContent,
|
||||
).toContain('Available');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
|
||||
).toContain('0');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should render stopped tab with count 0', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
|
||||
).toContain('Stopped');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
|
||||
).toContain('0');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,153 +1,92 @@
|
|||
|
||||
const environmentsList = [
|
||||
{
|
||||
id: 31,
|
||||
name: 'production',
|
||||
name: 'DEV',
|
||||
size: 1,
|
||||
id: 7,
|
||||
state: 'available',
|
||||
external_url: 'https://www.gitlab.com',
|
||||
external_url: null,
|
||||
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: [],
|
||||
},
|
||||
'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: [],
|
||||
},
|
||||
last_deployment: null,
|
||||
'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',
|
||||
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',
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
name: 'test-environment',
|
||||
folderName: 'build',
|
||||
size: 5,
|
||||
id: 12,
|
||||
name: 'build/update-README',
|
||||
state: 'available',
|
||||
environment_type: 'review',
|
||||
external_url: null,
|
||||
environment_type: 'build',
|
||||
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',
|
||||
'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',
|
||||
},
|
||||
];
|
||||
|
||||
window.environmentsList = environmentsList;
|
||||
const serverData = [
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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;
|
||||
module.exports = {
|
||||
environmentsList,
|
||||
environment,
|
||||
serverData,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
%div
|
||||
#environments-folder-list-view{ data: { "can-create-deployment" => "true",
|
||||
"can-read-environment" => "true",
|
||||
"css-class" => "",
|
||||
"commit-icon-svg" => custom_icon("icon_commit"),
|
||||
"terminal-icon-svg" => custom_icon("icon_terminal"),
|
||||
"play-icon-svg" => custom_icon("icon_play") } }
|
|
@ -108,6 +108,30 @@ require('~/lib/utils/common_utils');
|
|||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.parseIntPagination', () => {
|
||||
it('should parse to integers all string values and return pagination object', () => {
|
||||
const pagination = {
|
||||
'X-PER-PAGE': 10,
|
||||
'X-PAGE': 2,
|
||||
'X-TOTAL': 30,
|
||||
'X-TOTAL-PAGES': 3,
|
||||
'X-NEXT-PAGE': 3,
|
||||
'X-PREV-PAGE': 1,
|
||||
};
|
||||
|
||||
const expectedPagination = {
|
||||
perPage: 10,
|
||||
page: 2,
|
||||
total: 30,
|
||||
totalPages: 3,
|
||||
nextPage: 3,
|
||||
previousPage: 1,
|
||||
};
|
||||
|
||||
expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.isMetaClick', () => {
|
||||
it('should identify meta click on Windows/Linux', () => {
|
||||
const e = {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue