Improve environments performance
This commit is contained in:
parent
1d8ab59ebf
commit
4563156256
|
@ -0,0 +1,71 @@
|
|||
<script>
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import environmentTable from '../components/environments_table.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
environments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
environmentTable,
|
||||
loadingIcon,
|
||||
tablePagination,
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChangePage(page) {
|
||||
this.$emit('onChangePage', page);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="environments-container">
|
||||
|
||||
<loading-icon
|
||||
label="Loading environments"
|
||||
v-if="isLoading"
|
||||
size="3"
|
||||
/>
|
||||
|
||||
<slot name="emptyState"></slot>
|
||||
|
||||
<div
|
||||
class="table-holder"
|
||||
v-if="!isLoading && environments.length > 0">
|
||||
|
||||
<environment-table
|
||||
:environments="environments"
|
||||
:can-create-deployment="canCreateDeployment"
|
||||
:can-read-environment="canReadEnvironment"
|
||||
/>
|
||||
|
||||
<table-pagination
|
||||
v-if="pagination && pagination.totalPages > 1"
|
||||
:change="onChangePage"
|
||||
:pageInfo="pagination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,42 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'environmentsEmptyState',
|
||||
props: {
|
||||
newPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canCreateEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
helpPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="blank-state-row">
|
||||
<div class="blank-state-center">
|
||||
<h2 class="blank-state-title js-blank-state-title">
|
||||
{{s__("Environments|You don't have any environments right now.")}}
|
||||
</h2>
|
||||
<p class="blank-state-text">
|
||||
{{s__("Environments|Environments are places where code gets deployed, such as staging or production.")}}
|
||||
<br />
|
||||
<a :href="helpPath">
|
||||
{{s__("Environments|Read more about environments")}}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<a
|
||||
v-if="canCreateEnvironment"
|
||||
:href="newPath"
|
||||
class="btn btn-create js-new-environment-button">
|
||||
{{s__("Environments|New environment")}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,270 +0,0 @@
|
|||
<script>
|
||||
import Visibility from 'visibilityjs';
|
||||
import Flash from '../../flash';
|
||||
import EnvironmentsService from '../services/environments_service';
|
||||
import environmentTable from './environments_table.vue';
|
||||
import EnvironmentsStore from '../stores/environments_store';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
|
||||
import eventHub from '../event_hub';
|
||||
import Poll from '../../lib/utils/poll';
|
||||
import environmentsMixin from '../mixins/environments_mixin';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
environmentTable,
|
||||
tablePagination,
|
||||
loadingIcon,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
environmentsMixin,
|
||||
],
|
||||
|
||||
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,
|
||||
isMakingRequest: false,
|
||||
|
||||
// Pagination Properties,
|
||||
paginationInformation: {},
|
||||
pageNumber: 1,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
scope() {
|
||||
return getParameterByName('scope');
|
||||
},
|
||||
|
||||
canReadEnvironmentParsed() {
|
||||
return convertPermissionToBoolean(this.canReadEnvironment);
|
||||
},
|
||||
|
||||
canCreateDeploymentParsed() {
|
||||
return convertPermissionToBoolean(this.canCreateDeployment);
|
||||
},
|
||||
|
||||
canCreateEnvironmentParsed() {
|
||||
return convertPermissionToBoolean(this.canCreateEnvironment);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the environments and stores them.
|
||||
* Toggles loading property.
|
||||
*/
|
||||
created() {
|
||||
const scope = getParameterByName('scope') || this.visibility;
|
||||
const page = getParameterByName('page') || this.pageNumber;
|
||||
|
||||
this.service = new EnvironmentsService(this.endpoint);
|
||||
|
||||
const poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'get',
|
||||
data: { scope, page },
|
||||
successCallback: this.successCallback,
|
||||
errorCallback: this.errorCallback,
|
||||
notificationCallback: (isMakingRequest) => {
|
||||
this.isMakingRequest = isMakingRequest;
|
||||
},
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
this.isLoading = true;
|
||||
poll.makeRequest();
|
||||
}
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
poll.restart();
|
||||
} else {
|
||||
poll.stop();
|
||||
}
|
||||
});
|
||||
|
||||
eventHub.$on('toggleFolder', this.toggleFolder);
|
||||
eventHub.$on('postAction', this.postAction);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
eventHub.$off('toggleFolder');
|
||||
eventHub.$off('postAction');
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleFolder(folder) {
|
||||
this.store.toggleFolder(folder);
|
||||
|
||||
if (!folder.isOpen) {
|
||||
this.fetchChildEnvironments(folder, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Will change the page number and update the URL.
|
||||
*
|
||||
* @param {Number} pageNumber desired page to go to.
|
||||
* @return {String}
|
||||
*/
|
||||
changePage(pageNumber) {
|
||||
const param = setParamInURL('page', pageNumber);
|
||||
|
||||
gl.utils.visitUrl(param);
|
||||
return param;
|
||||
},
|
||||
|
||||
fetchEnvironments() {
|
||||
const scope = getParameterByName('scope') || this.visibility;
|
||||
const page = getParameterByName('page') || this.pageNumber;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return this.service.get({ scope, page })
|
||||
.then(this.successCallback)
|
||||
.catch(this.errorCallback);
|
||||
},
|
||||
|
||||
fetchChildEnvironments(folder, showLoader = false) {
|
||||
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
|
||||
|
||||
this.service.getFolderContent(folder.folder_path)
|
||||
.then(resp => resp.json())
|
||||
.then(response => this.store.setfolderContent(folder, response.environments))
|
||||
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
|
||||
.catch(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new Flash('An error occurred while fetching the environments.');
|
||||
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
|
||||
});
|
||||
},
|
||||
|
||||
postAction(endpoint) {
|
||||
if (!this.isMakingRequest) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.service.postAction(endpoint)
|
||||
.then(() => this.fetchEnvironments())
|
||||
.catch(() => new Flash('An error occurred while making the request.'));
|
||||
}
|
||||
},
|
||||
|
||||
successCallback(resp) {
|
||||
this.saveData(resp);
|
||||
|
||||
// We need to verify if any folder is open to also update it
|
||||
const openFolders = this.store.getOpenFolders();
|
||||
if (openFolders.length) {
|
||||
openFolders.forEach(folder => this.fetchChildEnvironments(folder));
|
||||
}
|
||||
},
|
||||
|
||||
errorCallback() {
|
||||
this.isLoading = false;
|
||||
// eslint-disable-next-line no-new
|
||||
new Flash('An error occurred while fetching the environments.');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div :class="cssContainerClass">
|
||||
<div class="top-area">
|
||||
<ul
|
||||
v-if="!isLoading"
|
||||
class="nav-links">
|
||||
<li :class="{ active: scope === null || scope === 'available' }">
|
||||
<a :href="projectEnvironmentsPath">
|
||||
Available
|
||||
<span class="badge js-available-environments-count">
|
||||
{{state.availableCounter}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li :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
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="environments-container">
|
||||
<loading-icon
|
||||
label="Loading environments"
|
||||
size="3"
|
||||
v-if="isLoading"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="blank-state-row"
|
||||
v-if="!isLoading && state.environments.length === 0">
|
||||
<div class="blank-state-center">
|
||||
<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>
|
||||
|
||||
<div
|
||||
class="table-holder"
|
||||
v-if="!isLoading && state.environments.length > 0">
|
||||
|
||||
<environment-table
|
||||
:environments="state.environments"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<table-pagination
|
||||
v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
|
||||
:change="changePage"
|
||||
:pageInfo="state.paginationInformation" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
import { s__ } from '../../locale';
|
||||
|
||||
/**
|
||||
* Renders the external url link in environments table.
|
||||
|
@ -18,7 +19,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
title() {
|
||||
return 'Open';
|
||||
return s__('Environments|Open');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -432,7 +432,7 @@ export default {
|
|||
v-if="!model.isFolder"
|
||||
class="table-mobile-header"
|
||||
role="rowheader">
|
||||
Environment
|
||||
{{s__("Environments|Environment")}}
|
||||
</div>
|
||||
<a
|
||||
v-if="!model.isFolder"
|
||||
|
@ -505,7 +505,7 @@ export default {
|
|||
<div
|
||||
role="rowheader"
|
||||
class="table-mobile-header">
|
||||
Commit
|
||||
{{s__("Environments|Commit")}}
|
||||
</div>
|
||||
<div
|
||||
v-if="hasLastDeploymentKey"
|
||||
|
@ -521,7 +521,7 @@ export default {
|
|||
<div
|
||||
v-if="!hasLastDeploymentKey"
|
||||
class="commit-title table-mobile-content">
|
||||
No deployments yet
|
||||
{{s__("Environments|No deployments yet")}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -531,7 +531,7 @@ export default {
|
|||
<div
|
||||
role="rowheader"
|
||||
class="table-mobile-header">
|
||||
Updated
|
||||
{{s__("Environments|Updated")}}
|
||||
</div>
|
||||
<span
|
||||
v-if="canShowDate"
|
||||
|
|
|
@ -34,6 +34,7 @@ export default {
|
|||
:aria-label="title">
|
||||
<i
|
||||
class="fa fa-area-chart"
|
||||
aria-hidden="true" />
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
@ -48,10 +48,10 @@ export default {
|
|||
:disabled="isLoading">
|
||||
|
||||
<span v-if="isLastDeployment">
|
||||
Re-deploy
|
||||
{{s__("Environments|Re-deploy")}}
|
||||
</span>
|
||||
<span v-else>
|
||||
Rollback
|
||||
{{s__("Environments|Rollback")}}
|
||||
</span>
|
||||
|
||||
<loading-icon v-if="isLoading" />
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<script>
|
||||
import Flash from '../../flash';
|
||||
import { s__ } from '../../locale';
|
||||
import emptyState from './empty_state.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import environmentsMixin from '../mixins/environments_mixin';
|
||||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canCreateEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
cssContainerClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newEnvironmentPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
helpPagePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
emptyState,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
CIPaginationMixin,
|
||||
environmentsMixin,
|
||||
],
|
||||
|
||||
created() {
|
||||
eventHub.$on('toggleFolder', this.toggleFolder);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
eventHub.$off('toggleFolder');
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleFolder(folder) {
|
||||
this.store.toggleFolder(folder);
|
||||
|
||||
if (!folder.isOpen) {
|
||||
this.fetchChildEnvironments(folder, true);
|
||||
}
|
||||
},
|
||||
|
||||
fetchChildEnvironments(folder, showLoader = false) {
|
||||
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
|
||||
|
||||
this.service.getFolderContent(folder.folder_path)
|
||||
.then(resp => resp.json())
|
||||
.then(response => this.store.setfolderContent(folder, response.environments))
|
||||
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
|
||||
.catch(() => {
|
||||
Flash(s__('Environments|An error occurred while fetching the environments.'));
|
||||
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
|
||||
});
|
||||
},
|
||||
|
||||
successCallback(resp) {
|
||||
this.saveData(resp);
|
||||
|
||||
// We need to verify if any folder is open to also update it
|
||||
const openFolders = this.store.getOpenFolders();
|
||||
if (openFolders.length) {
|
||||
openFolders.forEach(folder => this.fetchChildEnvironments(folder));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div :class="cssContainerClass">
|
||||
<div class="top-area">
|
||||
<tabs
|
||||
:tabs="tabs"
|
||||
@onChangeTab="onChangeTab"
|
||||
scope="environments"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="canCreateEnvironment && !isLoading"
|
||||
class="nav-controls">
|
||||
<a
|
||||
:href="newEnvironmentPath"
|
||||
class="btn btn-create">
|
||||
{{s__("Environments|New environment")}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<container
|
||||
:is-loading="isLoading"
|
||||
:environments="state.environments"
|
||||
:pagination="state.paginationInformation"
|
||||
:can-create-deployment="canCreateDeployment"
|
||||
:can-read-environment="canReadEnvironment"
|
||||
@onChangePage="onChangePage"
|
||||
>
|
||||
<empty-state
|
||||
slot="emptyState"
|
||||
v-if="!isLoading && state.environments.length === 0"
|
||||
:new-path="newEnvironmentPath"
|
||||
:help-path="helpPagePath"
|
||||
:can-create-environment="canCreateEnvironment"
|
||||
/>
|
||||
</container>
|
||||
</div>
|
||||
</template>
|
|
@ -2,12 +2,12 @@
|
|||
/**
|
||||
* Render environments table.
|
||||
*/
|
||||
import EnvironmentTableRowComponent from './environment_item.vue';
|
||||
import environmentItem from './environment_item.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'environment-item': EnvironmentTableRowComponent,
|
||||
environmentItem,
|
||||
loadingIcon,
|
||||
},
|
||||
|
||||
|
@ -42,19 +42,19 @@ export default {
|
|||
<div class="ci-table" role="grid">
|
||||
<div class="gl-responsive-table-row table-row-header" role="row">
|
||||
<div class="table-section section-10 environments-name" role="columnheader">
|
||||
Environment
|
||||
{{s__("Environments|Environment")}}
|
||||
</div>
|
||||
<div class="table-section section-10 environments-deploy" role="columnheader">
|
||||
Deployment
|
||||
{{s__("Environments|Deployment")}}
|
||||
</div>
|
||||
<div class="table-section section-15 environments-build" role="columnheader">
|
||||
Job
|
||||
{{s__("Environments|Job")}}
|
||||
</div>
|
||||
<div class="table-section section-25 environments-commit" role="columnheader">
|
||||
Commit
|
||||
{{s__("Environments|Commit")}}
|
||||
</div>
|
||||
<div class="table-section section-10 environments-date" role="columnheader">
|
||||
Updated
|
||||
{{s__("Environments|Updated")}}
|
||||
</div>
|
||||
</div>
|
||||
<template
|
||||
|
@ -86,7 +86,7 @@ export default {
|
|||
<a
|
||||
:href="folderUrl(model)"
|
||||
class="btn btn-default">
|
||||
Show all
|
||||
{{s__("Environments|Show all")}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,39 @@
|
|||
import Vue from 'vue';
|
||||
import EnvironmentsComponent from './components/environment.vue';
|
||||
import environmentsComponent from './components/environments_app.vue';
|
||||
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
|
||||
import Translate from '../vue_shared/translate';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||
el: '#environments-list-view',
|
||||
components: {
|
||||
'environments-table-app': EnvironmentsComponent,
|
||||
environmentsComponent,
|
||||
},
|
||||
data() {
|
||||
const environmentsData = document.querySelector(this.$options.el).dataset;
|
||||
|
||||
return {
|
||||
endpoint: environmentsData.environmentsDataEndpoint,
|
||||
newEnvironmentPath: environmentsData.newEnvironmentPath,
|
||||
helpPagePath: environmentsData.helpPagePath,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
|
||||
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
|
||||
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('environments-component', {
|
||||
props: {
|
||||
endpoint: this.endpoint,
|
||||
newEnvironmentPath: this.newEnvironmentPath,
|
||||
helpPagePath: this.helpPagePath,
|
||||
cssContainerClass: this.cssContainerClass,
|
||||
canCreateEnvironment: this.canCreateEnvironment,
|
||||
canCreateDeployment: this.canCreateDeployment,
|
||||
canReadEnvironment: this.canReadEnvironment,
|
||||
},
|
||||
});
|
||||
},
|
||||
render: createElement => createElement('environments-table-app'),
|
||||
}));
|
||||
|
|
|
@ -1,10 +1,35 @@
|
|||
import Vue from 'vue';
|
||||
import EnvironmentsFolderComponent from './environments_folder_view.vue';
|
||||
import environmentsFolderApp from './environments_folder_view.vue';
|
||||
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
|
||||
import Translate from '../../vue_shared/translate';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||
el: '#environments-folder-list-view',
|
||||
components: {
|
||||
'environments-folder-app': EnvironmentsFolderComponent,
|
||||
environmentsFolderApp,
|
||||
},
|
||||
data() {
|
||||
const environmentsData = document.querySelector(this.$options.el).dataset;
|
||||
|
||||
return {
|
||||
endpoint: environmentsData.endpoint,
|
||||
folderName: environmentsData.folderName,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
|
||||
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('environments-folder-app', {
|
||||
props: {
|
||||
endpoint: this.endpoint,
|
||||
folderName: this.folderName,
|
||||
cssContainerClass: this.cssContainerClass,
|
||||
canCreateDeployment: this.canCreateDeployment,
|
||||
canReadEnvironment: this.canReadEnvironment,
|
||||
},
|
||||
});
|
||||
},
|
||||
render: createElement => createElement('environments-folder-app'),
|
||||
}));
|
||||
|
|
|
@ -1,166 +1,38 @@
|
|||
<script>
|
||||
import Visibility from 'visibilityjs';
|
||||
import Flash from '../../flash';
|
||||
import EnvironmentsService from '../services/environments_service';
|
||||
import environmentTable from '../components/environments_table.vue';
|
||||
import EnvironmentsStore from '../stores/environments_store';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import Poll from '../../lib/utils/poll';
|
||||
import eventHub from '../event_hub';
|
||||
import environmentsMixin from '../mixins/environments_mixin';
|
||||
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
|
||||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
environmentTable,
|
||||
tablePagination,
|
||||
loadingIcon,
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
folderName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cssContainerClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [
|
||||
environmentsMixin,
|
||||
CIPaginationMixin,
|
||||
],
|
||||
|
||||
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,
|
||||
// Pagination Properties,
|
||||
paginationInformation: {},
|
||||
pageNumber: 1,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
scope() {
|
||||
return getParameterByName('scope');
|
||||
},
|
||||
|
||||
canReadEnvironmentParsed() {
|
||||
return convertPermissionToBoolean(this.canReadEnvironment);
|
||||
},
|
||||
|
||||
canCreateDeploymentParsed() {
|
||||
return 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 = getParameterByName('scope') || this.visibility;
|
||||
const page = getParameterByName('page') || this.pageNumber;
|
||||
|
||||
this.service = new EnvironmentsService(this.endpoint);
|
||||
|
||||
const poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'get',
|
||||
data: { scope, page },
|
||||
successCallback: this.successCallback,
|
||||
errorCallback: this.errorCallback,
|
||||
notificationCallback: (isMakingRequest) => {
|
||||
this.isMakingRequest = isMakingRequest;
|
||||
},
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
this.isLoading = true;
|
||||
poll.makeRequest();
|
||||
}
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
poll.restart();
|
||||
} else {
|
||||
poll.stop();
|
||||
}
|
||||
});
|
||||
|
||||
eventHub.$on('postAction', this.postAction);
|
||||
},
|
||||
|
||||
beforeDestroyed() {
|
||||
eventHub.$off('postAction');
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Will change the page number and update the URL.
|
||||
*
|
||||
* @param {Number} pageNumber desired page to go to.
|
||||
*/
|
||||
changePage(pageNumber) {
|
||||
const param = setParamInURL('page', pageNumber);
|
||||
|
||||
gl.utils.visitUrl(param);
|
||||
return param;
|
||||
},
|
||||
|
||||
fetchEnvironments() {
|
||||
const scope = getParameterByName('scope') || this.visibility;
|
||||
const page = getParameterByName('page') || this.pageNumber;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return this.service.get({ scope, page })
|
||||
.then(this.successCallback)
|
||||
.catch(this.errorCallback);
|
||||
},
|
||||
|
||||
successCallback(resp) {
|
||||
this.saveData(resp);
|
||||
},
|
||||
|
||||
errorCallback() {
|
||||
this.isLoading = false;
|
||||
// eslint-disable-next-line no-new
|
||||
new Flash('An error occurred while fetching the environments.');
|
||||
},
|
||||
|
||||
postAction(endpoint) {
|
||||
if (!this.isMakingRequest) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.service.postAction(endpoint)
|
||||
.then(() => this.fetchEnvironments())
|
||||
.catch(() => new Flash('An error occurred while making the request.'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -171,56 +43,23 @@ export default {
|
|||
v-if="!isLoading">
|
||||
|
||||
<h4 class="js-folder-name environments-folder-name">
|
||||
Environments / <b>{{folderName}}</b>
|
||||
{{s__("Environments|Environments")}} / <b>{{folderName}}</b>
|
||||
</h4>
|
||||
|
||||
<ul class="nav-links">
|
||||
<li :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 :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>
|
||||
<tabs
|
||||
:tabs="tabs"
|
||||
@onChangeTab="onChangeTab"
|
||||
scope="environments"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="environments-container">
|
||||
|
||||
<loading-icon
|
||||
label="Loading environments"
|
||||
v-if="isLoading"
|
||||
size="3"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="table-holder"
|
||||
v-if="!isLoading && state.environments.length > 0">
|
||||
|
||||
<environment-table
|
||||
<container
|
||||
:is-loading="isLoading"
|
||||
:environments="state.environments"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:pagination="state.paginationInformation"
|
||||
:can-create-deployment="canCreateDeployment"
|
||||
:can-read-environment="canReadEnvironment"
|
||||
@onChangePage="onChangePage"
|
||||
/>
|
||||
|
||||
<table-pagination
|
||||
v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
|
||||
:change="changePage"
|
||||
:pageInfo="state.paginationInformation"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,15 +1,174 @@
|
|||
/**
|
||||
* Common code between environmets app and folder view
|
||||
*/
|
||||
|
||||
import Visibility from 'visibilityjs';
|
||||
import Poll from '../../lib/utils/poll';
|
||||
import {
|
||||
getParameterByName,
|
||||
parseQueryStringIntoObject,
|
||||
} from '../../lib/utils/common_utils';
|
||||
import { s__ } from '../../locale';
|
||||
import Flash from '../../flash';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
import EnvironmentsStore from '../stores/environments_store';
|
||||
import EnvironmentsService from '../services/environments_service';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import environmentTable from '../components/environments_table.vue';
|
||||
import tabs from '../../vue_shared/components/navigation_tabs.vue';
|
||||
import container from '../components/container.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
environmentTable,
|
||||
container,
|
||||
loadingIcon,
|
||||
tabs,
|
||||
tablePagination,
|
||||
},
|
||||
|
||||
data() {
|
||||
const store = new EnvironmentsStore();
|
||||
|
||||
return {
|
||||
store,
|
||||
state: store.state,
|
||||
isLoading: false,
|
||||
isMakingRequest: false,
|
||||
scope: getParameterByName('scope') || 'available',
|
||||
page: getParameterByName('page') || '1',
|
||||
requestData: {},
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
saveData(resp) {
|
||||
const headers = resp.headers;
|
||||
return resp.json().then((response) => {
|
||||
this.isLoading = false;
|
||||
|
||||
if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) {
|
||||
this.store.storeAvailableCount(response.available_count);
|
||||
this.store.storeStoppedCount(response.stopped_count);
|
||||
this.store.storeEnvironments(response.environments);
|
||||
this.store.setPagination(headers);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles URL and query parameter changes.
|
||||
* When the user uses the pagination or the tabs,
|
||||
* - update URL
|
||||
* - Make API request to the server with new parameters
|
||||
* - Update the polling function
|
||||
* - Update the internal state
|
||||
*/
|
||||
updateContent(parameters) {
|
||||
this.updateInternalState(parameters);
|
||||
// fetch new data
|
||||
return this.service.get(this.requestData)
|
||||
.then(response => this.successCallback(response))
|
||||
.then(() => {
|
||||
// restart polling
|
||||
this.poll.restart({ data: this.requestData });
|
||||
})
|
||||
.catch(() => {
|
||||
this.errorCallback();
|
||||
|
||||
// restart polling
|
||||
this.poll.restart();
|
||||
});
|
||||
},
|
||||
|
||||
errorCallback() {
|
||||
this.isLoading = false;
|
||||
Flash(s__('Environments|An error occurred while fetching the environments.'));
|
||||
},
|
||||
|
||||
postAction(endpoint) {
|
||||
if (!this.isMakingRequest) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.service.postAction(endpoint)
|
||||
.then(() => this.fetchEnvironments())
|
||||
.catch(() => {
|
||||
this.isLoading = false;
|
||||
Flash(s__('Environments|An error occurred while making the request.'));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetchEnvironments() {
|
||||
this.isLoading = true;
|
||||
|
||||
return this.service.get(this.requestData)
|
||||
.then(this.successCallback)
|
||||
.catch(this.errorCallback);
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
tabs() {
|
||||
return [
|
||||
{
|
||||
name: s__('Available'),
|
||||
scope: 'available',
|
||||
count: this.state.availableCounter,
|
||||
isActive: this.scope === 'available',
|
||||
},
|
||||
{
|
||||
name: s__('Stopped'),
|
||||
scope: 'stopped',
|
||||
count: this.state.stoppedCounter,
|
||||
isActive: this.scope === 'stopped',
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the environments and stores them.
|
||||
* Toggles loading property.
|
||||
*/
|
||||
created() {
|
||||
this.service = new EnvironmentsService(this.endpoint);
|
||||
this.requestData = { page: this.page, scope: this.scope };
|
||||
|
||||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'get',
|
||||
data: this.requestData,
|
||||
successCallback: this.successCallback,
|
||||
errorCallback: this.errorCallback,
|
||||
notificationCallback: (isMakingRequest) => {
|
||||
this.isMakingRequest = isMakingRequest;
|
||||
},
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
this.isLoading = true;
|
||||
this.poll.makeRequest();
|
||||
} else {
|
||||
this.fetchEnvironments();
|
||||
}
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
this.poll.restart();
|
||||
} else {
|
||||
this.poll.stop();
|
||||
}
|
||||
});
|
||||
|
||||
eventHub.$on('postAction', this.postAction);
|
||||
},
|
||||
|
||||
beforeDestroyed() {
|
||||
eventHub.$off('postAction');
|
||||
},
|
||||
};
|
||||
|
|
|
@ -36,7 +36,12 @@ export default class EnvironmentsStore {
|
|||
storeEnvironments(environments = []) {
|
||||
const filteredEnvironments = environments.map((env) => {
|
||||
const oldEnvironmentState = this.state.environments
|
||||
.find(element => element.id === env.latest.id) || {};
|
||||
.find((element) => {
|
||||
if (env.latest) {
|
||||
return element.id === env.latest.id;
|
||||
}
|
||||
return element.id === env.id;
|
||||
}) || {};
|
||||
|
||||
let filtered = {};
|
||||
|
||||
|
|
|
@ -269,46 +269,6 @@ export const parseIntPagination = paginationInformation => ({
|
|||
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the search parameter of a URL given the parameter and value 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}
|
||||
*/
|
||||
export const setParamInURL = (param, value) => {
|
||||
let search;
|
||||
const locationSearch = window.location.search;
|
||||
|
||||
if (locationSearch.length) {
|
||||
const parameters = locationSearch.substring(1, locationSearch.length)
|
||||
.split('&')
|
||||
.reduce((acc, element) => {
|
||||
const val = element.split('=');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
acc[val[0]] = decodeURIComponent(val[1]);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
parameters[param] = value;
|
||||
|
||||
const toString = Object.keys(parameters)
|
||||
.map(val => `${val}=${encodeURIComponent(parameters[val])}`)
|
||||
.join('&');
|
||||
|
||||
search = `?${toString}`;
|
||||
} else {
|
||||
search = `?${param}=${value}`;
|
||||
}
|
||||
|
||||
return search;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a string of query parameters creates an object.
|
||||
*
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
import PipelinesService from '../services/pipelines_service';
|
||||
import pipelinesMixin from '../mixins/pipelines';
|
||||
import tablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import navigationTabs from './navigation_tabs.vue';
|
||||
import navigationTabs from '../../vue_shared/components/navigation_tabs.vue';
|
||||
import navigationControls from './nav_controls.vue';
|
||||
import {
|
||||
convertPermissionToBoolean,
|
||||
getParameterByName,
|
||||
historyPushState,
|
||||
buildUrlWithCurrentLocation,
|
||||
parseQueryStringIntoObject,
|
||||
} from '../../lib/utils/common_utils';
|
||||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -36,6 +35,7 @@
|
|||
},
|
||||
mixins: [
|
||||
pipelinesMixin,
|
||||
CIPaginationMixin,
|
||||
],
|
||||
data() {
|
||||
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
|
||||
|
@ -170,22 +170,8 @@
|
|||
* - Update the internal state
|
||||
*/
|
||||
updateContent(parameters) {
|
||||
// stop polling
|
||||
this.poll.stop();
|
||||
this.updateInternalState(parameters);
|
||||
|
||||
const queryString = Object.keys(parameters).map((parameter) => {
|
||||
const value = parameters[parameter];
|
||||
// update internal state for UI
|
||||
this[parameter] = value;
|
||||
return `${parameter}=${encodeURIComponent(value)}`;
|
||||
}).join('&');
|
||||
|
||||
// update polling parameters
|
||||
this.requestData = parameters;
|
||||
|
||||
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
|
||||
|
||||
this.isLoading = true;
|
||||
// fetch new data
|
||||
return this.service.getPipelines(this.requestData)
|
||||
.then((response) => {
|
||||
|
@ -203,14 +189,6 @@
|
|||
this.poll.restart();
|
||||
});
|
||||
},
|
||||
|
||||
onChangeTab(scope) {
|
||||
this.updateContent({ scope, page: '1' });
|
||||
},
|
||||
onChangePage(page) {
|
||||
/* URLS parameters are strings, we need to parse to match types */
|
||||
this.updateContent({ scope: this.scope, page: Number(page).toString() });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -235,6 +213,7 @@
|
|||
<navigation-tabs
|
||||
:tabs="tabs"
|
||||
@onChangeTab="onChangeTab"
|
||||
scope="pipelines"
|
||||
/>
|
||||
|
||||
<navigation-controls
|
||||
|
|
|
@ -1,11 +1,36 @@
|
|||
<script>
|
||||
/**
|
||||
* Given an array of tabs, renders non linked bootstrap tabs.
|
||||
* When a tab is clicked it will trigger an event and provide the clicked scope.
|
||||
*
|
||||
* This component is used in apps that handle the API call.
|
||||
* If you only need to change the URL this component should not be used.
|
||||
*
|
||||
* @example
|
||||
* <navigation-tabs
|
||||
* :tabs="[
|
||||
* {
|
||||
* name: String,
|
||||
* scope: String,
|
||||
* count: Number || Undefined,
|
||||
* isActive: Boolean,
|
||||
* },
|
||||
* ]"
|
||||
* @onChangeTab="onChangeTab"
|
||||
* />
|
||||
*/
|
||||
export default {
|
||||
name: 'PipelineNavigationTabs',
|
||||
name: 'NavigationTabs',
|
||||
props: {
|
||||
tabs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$(document).trigger('init.scrolling-tabs');
|
||||
|
@ -34,7 +59,7 @@
|
|||
<a
|
||||
role="button"
|
||||
@click="onTabClick(tab)"
|
||||
:class="`js-pipelines-tab-${tab.scope}`"
|
||||
:class="`js-${scope}-tab-${tab.scope}`"
|
||||
>
|
||||
{{ tab.name }}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* API callbacks for pagination and tabs
|
||||
* shared between Pipelines and Environments table.
|
||||
*
|
||||
* Components need to have `scope`, `page` and `requestData`
|
||||
*/
|
||||
import {
|
||||
historyPushState,
|
||||
buildUrlWithCurrentLocation,
|
||||
} from '../../lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
onChangeTab(scope) {
|
||||
this.updateContent({ scope, page: '1' });
|
||||
},
|
||||
|
||||
onChangePage(page) {
|
||||
/* URLS parameters are strings, we need to parse to match types */
|
||||
this.updateContent({ scope: this.scope, page: Number(page).toString() });
|
||||
},
|
||||
|
||||
updateInternalState(parameters) {
|
||||
// stop polling
|
||||
this.poll.stop();
|
||||
|
||||
const queryString = Object.keys(parameters).map((parameter) => {
|
||||
const value = parameters[parameter];
|
||||
// update internal state for UI
|
||||
this[parameter] = value;
|
||||
return `${parameter}=${encodeURIComponent(value)}`;
|
||||
}).join('&');
|
||||
|
||||
// update polling parameters
|
||||
this.requestData = parameters;
|
||||
|
||||
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
|
||||
|
||||
this.isLoading = true;
|
||||
},
|
||||
},
|
||||
};
|
|
@ -34,6 +34,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
folder_environments = project.environments.where(environment_type: params[:id])
|
||||
@environments = folder_environments.with_state(params[:scope] || :available)
|
||||
.order(:name)
|
||||
@folder = params[:id]
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
= page_specific_javascript_bundle_tag('common_vue')
|
||||
= page_specific_javascript_bundle_tag("environments_folder")
|
||||
|
||||
#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
|
||||
#environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json),
|
||||
"folder-name" => @folder,
|
||||
"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 } }
|
||||
|
|
|
@ -3,15 +3,13 @@
|
|||
- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
|
||||
|
||||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_bundle_tag('common_vue')
|
||||
= page_specific_javascript_bundle_tag("common_vue")
|
||||
= page_specific_javascript_bundle_tag("environments")
|
||||
|
||||
#environments-list-view{ data: { environments_data: environments_list_data,
|
||||
"can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
|
||||
"can-read-environment" => can?(current_user, :read_environment, @project).to_s,
|
||||
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
|
||||
"project-environments-path" => project_environments_path(@project),
|
||||
"project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
|
||||
"new-environment-path" => new_project_environment_path(@project),
|
||||
"help-page-path" => help_page_path("ci/environments"),
|
||||
"css-class" => container_class } }
|
||||
|
|
|
@ -14,8 +14,10 @@ feature 'Environments page', :js do
|
|||
it 'shows "Available" and "Stopped" tab with links' do
|
||||
visit_environments(project)
|
||||
|
||||
expect(page).to have_link('Available')
|
||||
expect(page).to have_link('Stopped')
|
||||
expect(page).to have_selector('.js-environments-tab-available')
|
||||
expect(page).to have_content('Available')
|
||||
expect(page).to have_selector('.js-environments-tab-stopped')
|
||||
expect(page).to have_content('Stopped')
|
||||
end
|
||||
|
||||
describe 'with one available environment' do
|
||||
|
@ -75,8 +77,8 @@ feature 'Environments page', :js do
|
|||
it 'does not show environments and counters are set to zero' do
|
||||
expect(page).to have_content('You don\'t have any environments right now.')
|
||||
|
||||
expect(page.find('.js-available-environments-count').text).to eq('0')
|
||||
expect(page.find('.js-stopped-environments-count').text).to eq('0')
|
||||
expect(page.find('.js-environments-tab-available .badge').text).to eq('0')
|
||||
expect(page.find('.js-environments-tab-stopped .badge').text).to eq('0')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -93,8 +95,8 @@ feature 'Environments page', :js do
|
|||
it 'shows environments names and counters' do
|
||||
expect(page).to have_link(environment.name)
|
||||
|
||||
expect(page.find('.js-available-environments-count').text).to eq('1')
|
||||
expect(page.find('.js-stopped-environments-count').text).to eq('0')
|
||||
expect(page.find('.js-environments-tab-available .badge').text).to eq('1')
|
||||
expect(page.find('.js-environments-tab-stopped .badge').text).to eq('0')
|
||||
end
|
||||
|
||||
it 'does not show deployments' do
|
||||
|
@ -294,11 +296,32 @@ feature 'Environments page', :js do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'environments folders view' do
|
||||
before do
|
||||
create(:environment, project: project,
|
||||
name: 'staging.review/review-1',
|
||||
state: :available)
|
||||
create(:environment, project: project,
|
||||
name: 'staging.review/review-2',
|
||||
state: :available)
|
||||
end
|
||||
|
||||
scenario 'user opens folder view' do
|
||||
visit folder_project_environments_path(project, 'staging.review')
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content('Environments / staging.review')
|
||||
expect(page).to have_content('review-1')
|
||||
expect(page).to have_content('review-2')
|
||||
end
|
||||
end
|
||||
|
||||
def have_terminal_button
|
||||
have_link(nil, href: terminal_project_environment_path(project, environment))
|
||||
end
|
||||
|
||||
def visit_environments(project, **opts)
|
||||
visit project_environments_path(project, **opts)
|
||||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
import emptyState from '~/environments/components/empty_state.vue';
|
||||
import mountComponent from '../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('environments empty state', () => {
|
||||
let vm;
|
||||
let Component;
|
||||
|
||||
beforeEach(() => {
|
||||
Component = Vue.extend(emptyState);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('With permissions', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
newPath: 'foo',
|
||||
canCreateEnvironment: true,
|
||||
helpPath: 'bar',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders empty state and new environment button', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.js-blank-state-title').textContent.trim(),
|
||||
).toEqual('You don\'t have any environments right now.');
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.js-new-environment-button').getAttribute('href'),
|
||||
).toEqual('foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Without permission', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
newPath: 'foo',
|
||||
canCreateEnvironment: false,
|
||||
helpPath: 'bar',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders empty state without new button', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.js-blank-state-title').textContent.trim(),
|
||||
).toEqual('You don\'t have any environments right now.');
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.js-new-environment-button'),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,10 +1,17 @@
|
|||
import Vue from 'vue';
|
||||
import environmentTableComp from '~/environments/components/environments_table.vue';
|
||||
import mountComponent from '../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Environment table', () => {
|
||||
let Component;
|
||||
let vm;
|
||||
|
||||
describe('Environment item', () => {
|
||||
preloadFixtures('static/environments/element.html.raw');
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/environments/element.html.raw');
|
||||
Component = Vue.extend(environmentTableComp);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('Should render a table', () => {
|
||||
|
@ -17,18 +24,12 @@ describe('Environment item', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const EnvironmentTable = Vue.extend(environmentTableComp);
|
||||
|
||||
const component = new EnvironmentTable({
|
||||
el: document.querySelector('.test-dom-element'),
|
||||
propsData: {
|
||||
environments: [{ mockItem }],
|
||||
vm = mountComponent(Component, {
|
||||
environments: [mockItem],
|
||||
canCreateDeployment: false,
|
||||
canReadEnvironment: true,
|
||||
service: {},
|
||||
},
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
expect(component.$el.getAttribute('class')).toContain('ci-table');
|
||||
expect(vm.$el.getAttribute('class')).toContain('ci-table');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
import Vue from 'vue';
|
||||
import '~/flash';
|
||||
import environmentsComponent from '~/environments/components/environment.vue';
|
||||
import environmentsComponent from '~/environments/components/environments_app.vue';
|
||||
import { environment, folder } from './mock_data';
|
||||
import { headersInterceptor } from '../helpers/vue_resource_helper';
|
||||
import mountComponent from '../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Environment', () => {
|
||||
preloadFixtures('static/environments/environments.html.raw');
|
||||
const mockData = {
|
||||
endpoint: 'environments.json',
|
||||
canCreateEnvironment: true,
|
||||
canCreateDeployment: true,
|
||||
canReadEnvironment: true,
|
||||
cssContainerClass: 'container',
|
||||
newEnvironmentPath: 'environments/new',
|
||||
helpPagePath: 'help',
|
||||
};
|
||||
|
||||
let EnvironmentsComponent;
|
||||
let component;
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/environments/environments.html.raw');
|
||||
|
||||
EnvironmentsComponent = Vue.extend(environmentsComponent);
|
||||
});
|
||||
|
||||
|
@ -37,9 +43,7 @@ describe('Environment', () => {
|
|||
});
|
||||
|
||||
it('should render the empty state', (done) => {
|
||||
component = new EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
});
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
|
@ -81,9 +85,7 @@ describe('Environment', () => {
|
|||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(environmentsResponseInterceptor);
|
||||
Vue.http.interceptors.push(headersInterceptor);
|
||||
component = new EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
});
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -95,7 +97,7 @@ describe('Environment', () => {
|
|||
|
||||
it('should render a table with environments', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(component.$el.querySelectorAll('table')).toBeDefined();
|
||||
expect(component.$el.querySelectorAll('table')).not.toBeNull();
|
||||
expect(
|
||||
component.$el.querySelector('.environment-name').textContent.trim(),
|
||||
).toEqual(environment.name);
|
||||
|
@ -104,10 +106,6 @@ describe('Environment', () => {
|
|||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
afterEach(() => {
|
||||
window.history.pushState({}, null, '');
|
||||
});
|
||||
|
||||
it('should render pagination', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
|
@ -117,46 +115,23 @@ describe('Environment', () => {
|
|||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when no search params are present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
it('should make an API request when page is clicked', (done) => {
|
||||
spyOn(component, 'updateContent');
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page is already present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?page=1');
|
||||
|
||||
it('should make an API request when using tabs', (done) => {
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
spyOn(component, 'updateContent');
|
||||
component.$el.querySelector('.js-environments-tab-stopped').click();
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -180,9 +155,7 @@ describe('Environment', () => {
|
|||
});
|
||||
|
||||
it('should render empty state', (done) => {
|
||||
component = new EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
});
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
|
@ -214,9 +187,7 @@ describe('Environment', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(environmentsResponseInterceptor);
|
||||
component = new EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
});
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -289,4 +260,59 @@ describe('Environment', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
const environmentsEmptyResponseInterceptor = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify([]), {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
|
||||
Vue.http.interceptors.push(headersInterceptor);
|
||||
|
||||
component = mountComponent(EnvironmentsComponent, mockData);
|
||||
spyOn(history, 'pushState').and.stub();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, environmentsEmptyResponseInterceptor,
|
||||
);
|
||||
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
|
||||
});
|
||||
|
||||
describe('updateContent', () => {
|
||||
it('should set given parameters', (done) => {
|
||||
component.updateContent({ scope: 'stopped', page: '3' })
|
||||
.then(() => {
|
||||
expect(component.page).toEqual('3');
|
||||
expect(component.scope).toEqual('stopped');
|
||||
expect(component.requestData.scope).toEqual('stopped');
|
||||
expect(component.requestData.page).toEqual('3');
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChangeTab', () => {
|
||||
it('should set page to 1', () => {
|
||||
spyOn(component, 'updateContent');
|
||||
component.onChangeTab('stopped');
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChangePage', () => {
|
||||
it('should update page and keep scope', () => {
|
||||
spyOn(component, 'updateContent');
|
||||
component.onChangePage(4);
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,25 +1,28 @@
|
|||
import Vue from 'vue';
|
||||
import '~/flash';
|
||||
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
|
||||
import { environmentsList } from '../mock_data';
|
||||
import { headersInterceptor } from '../../helpers/vue_resource_helper';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Environments Folder View', () => {
|
||||
preloadFixtures('static/environments/environments_folder_view.html.raw');
|
||||
let EnvironmentsFolderViewComponent;
|
||||
let Component;
|
||||
let component;
|
||||
const mockData = {
|
||||
endpoint: 'environments.json',
|
||||
folderName: 'review',
|
||||
canCreateDeployment: true,
|
||||
canReadEnvironment: true,
|
||||
cssContainerClass: 'container',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/environments/environments_folder_view.html.raw');
|
||||
EnvironmentsFolderViewComponent = Vue.extend(environmentsFolderViewComponent);
|
||||
window.history.pushState({}, null, 'environments/folders/build');
|
||||
Component = Vue.extend(environmentsFolderViewComponent);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.history.pushState({}, null, '/');
|
||||
component.$destroy();
|
||||
});
|
||||
|
||||
let component;
|
||||
|
||||
describe('successfull request', () => {
|
||||
const environmentsResponseInterceptor = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify({
|
||||
|
@ -31,10 +34,10 @@ describe('Environments Folder View', () => {
|
|||
headers: {
|
||||
'X-nExt-pAge': '2',
|
||||
'x-page': '1',
|
||||
'X-Per-Page': '1',
|
||||
'X-Per-Page': '2',
|
||||
'X-Prev-Page': '',
|
||||
'X-TOTAL': '37',
|
||||
'X-Total-Pages': '2',
|
||||
'X-TOTAL': '20',
|
||||
'X-Total-Pages': '10',
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
@ -43,9 +46,7 @@ describe('Environments Folder View', () => {
|
|||
Vue.http.interceptors.push(environmentsResponseInterceptor);
|
||||
Vue.http.interceptors.push(headersInterceptor);
|
||||
|
||||
component = new EnvironmentsFolderViewComponent({
|
||||
el: document.querySelector('#environments-folder-list-view'),
|
||||
});
|
||||
component = mountComponent(Component, mockData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -57,7 +58,7 @@ describe('Environments Folder View', () => {
|
|||
|
||||
it('should render a table with environments', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(component.$el.querySelectorAll('table')).toBeDefined();
|
||||
expect(component.$el.querySelectorAll('table')).not.toBeNull();
|
||||
expect(
|
||||
component.$el.querySelector('.environment-name').textContent.trim(),
|
||||
).toEqual(environmentsList[0].name);
|
||||
|
@ -68,11 +69,11 @@ describe('Environments Folder View', () => {
|
|||
it('should render available tab with count', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-available').textContent,
|
||||
).toContain('Available');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-available .badge').textContent,
|
||||
).toContain('0');
|
||||
done();
|
||||
}, 0);
|
||||
|
@ -81,11 +82,11 @@ describe('Environments Folder View', () => {
|
|||
it('should render stopped tab with count', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-stopped').textContent,
|
||||
).toContain('Stopped');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-stopped .badge').textContent,
|
||||
).toContain('1');
|
||||
done();
|
||||
}, 0);
|
||||
|
@ -94,8 +95,8 @@ describe('Environments Folder View', () => {
|
|||
it('should render parent folder name', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-folder-name').textContent,
|
||||
).toContain('Environments / build');
|
||||
component.$el.querySelector('.js-folder-name').textContent.trim(),
|
||||
).toContain('Environments / review');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
@ -104,52 +105,30 @@ describe('Environments Folder View', () => {
|
|||
it('should render pagination', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('.gl-pagination li').length,
|
||||
).toEqual(5);
|
||||
component.$el.querySelectorAll('.gl-pagination'),
|
||||
).not.toBeNull();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when no search params are present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
it('should make an API request when changing page', (done) => {
|
||||
spyOn(component, 'updateContent');
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
component.$el.querySelector('.gl-pagination .js-last-button a').click();
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '10' });
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should update url when page is already present', (done) => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
window.history.pushState({}, null, '?page=1');
|
||||
|
||||
it('should make an API request when using tabs', (done) => {
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
|
||||
spyOn(component, 'updateContent');
|
||||
component.$el.querySelector('.js-environments-tab-stopped').click();
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -172,9 +151,7 @@ describe('Environments Folder View', () => {
|
|||
});
|
||||
|
||||
it('should not render a table', (done) => {
|
||||
component = new EnvironmentsFolderViewComponent({
|
||||
el: document.querySelector('#environments-folder-list-view'),
|
||||
});
|
||||
component = mountComponent(Component, mockData);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
|
@ -187,11 +164,11 @@ describe('Environments Folder View', () => {
|
|||
it('should render available tab with count 0', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-available').textContent,
|
||||
).toContain('Available');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-available .badge').textContent,
|
||||
).toContain('0');
|
||||
done();
|
||||
}, 0);
|
||||
|
@ -200,14 +177,70 @@ describe('Environments Folder View', () => {
|
|||
it('should render stopped tab with count 0', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-stopped').textContent,
|
||||
).toContain('Stopped');
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
|
||||
component.$el.querySelector('.js-environments-tab-stopped .badge').textContent,
|
||||
).toContain('0');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
const environmentsEmptyResponseInterceptor = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify([]), {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
|
||||
Vue.http.interceptors.push(headersInterceptor);
|
||||
|
||||
component = mountComponent(Component, mockData);
|
||||
spyOn(history, 'pushState').and.stub();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, environmentsEmptyResponseInterceptor,
|
||||
);
|
||||
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
|
||||
});
|
||||
|
||||
describe('updateContent', () => {
|
||||
it('should set given parameters', (done) => {
|
||||
component.updateContent({ scope: 'stopped', page: '4' })
|
||||
.then(() => {
|
||||
expect(component.page).toEqual('4');
|
||||
expect(component.scope).toEqual('stopped');
|
||||
expect(component.requestData.scope).toEqual('stopped');
|
||||
expect(component.requestData.page).toEqual('4');
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChangeTab', () => {
|
||||
it('should set page to 1', () => {
|
||||
spyOn(component, 'updateContent');
|
||||
component.onChangeTab('stopped');
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChangePage', () => {
|
||||
it('should update page and keep scope', () => {
|
||||
spyOn(component, 'updateContent');
|
||||
|
||||
component.onChangePage(4);
|
||||
|
||||
expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
.test-dom-element
|
|
@ -1,9 +0,0 @@
|
|||
%div
|
||||
#environments-list-view{ data: { environments_data: "foo/environments",
|
||||
"can-create-deployment" => "true",
|
||||
"can-read-environment" => "true",
|
||||
"can-create-environment" => "true",
|
||||
"project-environments-path" => "https://gitlab.com/foo/environments",
|
||||
"project-stopped-environments-path" => "https://gitlab.com/foo/environments?scope=stopped",
|
||||
"new-environment-path" => "https://gitlab.com/foo/environments/new",
|
||||
"help-page-path" => "https://gitlab.com/help_page"}}
|
|
@ -1,7 +0,0 @@
|
|||
%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") } }
|
|
@ -142,47 +142,6 @@ describe('common_utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('setParamInURL', () => {
|
||||
afterEach(() => {
|
||||
window.history.pushState({}, null, '');
|
||||
});
|
||||
|
||||
it('should return the parameter', () => {
|
||||
window.history.replaceState({}, null, '');
|
||||
|
||||
expect(commonUtils.setParamInURL('page', 156)).toBe('?page=156');
|
||||
expect(commonUtils.setParamInURL('page', '156')).toBe('?page=156');
|
||||
});
|
||||
|
||||
it('should update the existing parameter when its a number', () => {
|
||||
window.history.pushState({}, null, '?page=15');
|
||||
|
||||
expect(commonUtils.setParamInURL('page', 16)).toBe('?page=16');
|
||||
expect(commonUtils.setParamInURL('page', '16')).toBe('?page=16');
|
||||
expect(commonUtils.setParamInURL('page', true)).toBe('?page=true');
|
||||
});
|
||||
|
||||
it('should update the existing parameter when its a string', () => {
|
||||
window.history.pushState({}, null, '?scope=all');
|
||||
|
||||
expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished');
|
||||
});
|
||||
|
||||
it('should update the existing parameter when more than one parameter exists', () => {
|
||||
window.history.pushState({}, null, '?scope=all&page=15');
|
||||
|
||||
expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15');
|
||||
});
|
||||
|
||||
it('should add a new parameter to the end of the existing ones', () => {
|
||||
window.history.pushState({}, null, '?scope=all');
|
||||
|
||||
expect(commonUtils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
|
||||
expect(commonUtils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
|
||||
expect(commonUtils.setParamInURL('page', true)).toBe('?scope=all&page=true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('historyPushState', () => {
|
||||
afterEach(() => {
|
||||
window.history.replaceState({}, null, null);
|
||||
|
|
|
@ -176,6 +176,11 @@ describe('Pipelines', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(history, 'pushState').and.stub();
|
||||
});
|
||||
|
||||
describe('updateContent', () => {
|
||||
it('should set given parameters', () => {
|
||||
component = mountComponent(PipelinesComponent, {
|
||||
|
@ -195,7 +200,6 @@ describe('Pipelines', () => {
|
|||
component = mountComponent(PipelinesComponent, {
|
||||
store: new Store(),
|
||||
});
|
||||
|
||||
spyOn(component, 'updateContent');
|
||||
|
||||
component.onChangeTab('running');
|
||||
|
@ -209,7 +213,6 @@ describe('Pipelines', () => {
|
|||
component = mountComponent(PipelinesComponent, {
|
||||
store: new Store(),
|
||||
});
|
||||
|
||||
spyOn(component, 'updateContent');
|
||||
|
||||
component.onChangePage(4);
|
||||
|
@ -218,3 +221,4 @@ describe('Pipelines', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Vue from 'vue';
|
||||
import navigationTabs from '~/pipelines/components/navigation_tabs.vue';
|
||||
import mountComponent from '../helpers/vue_mount_component_helper';
|
||||
import navigationTabs from '~/vue_shared/components/navigation_tabs.vue';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('navigation tabs pipeline component', () => {
|
||||
describe('navigation tabs component', () => {
|
||||
let vm;
|
||||
let Component;
|
||||
let data;
|
||||
|
@ -29,7 +29,7 @@ describe('navigation tabs pipeline component', () => {
|
|||
];
|
||||
|
||||
Component = Vue.extend(navigationTabs);
|
||||
vm = mountComponent(Component, { tabs: data });
|
||||
vm = mountComponent(Component, { tabs: data, scope: 'pipelines' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -52,4 +52,10 @@ describe('navigation tabs pipeline component', () => {
|
|||
it('should not render badge', () => {
|
||||
expect(vm.$el.querySelector('.js-pipelines-tab-running .badge')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should trigger onTabClick', () => {
|
||||
spyOn(vm, '$emit');
|
||||
vm.$el.querySelector('.js-pipelines-tab-pending').click();
|
||||
expect(vm.$emit).toHaveBeenCalledWith('onChangeTab', 'pending');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue