Merge branch 'master' into add-yarn-documentation
* master: (73 commits) fix typo in node section move "Install node modules" step before "Migrate DB" within update process Renders pagination again for pipelines table update migration docs for 8.17 to include minimum node version Add CHANGELOG file Fix positioning of top scroll button add space between ci text and commit sha in Merge Request widget Do not use single quote in headings as it breaks docs.gitlab.com Fix broken test Update services templates docs Simplify Pages admin source docs Simplify Pages admin Omnibus docs Fix error in MR widget after /merge slash command Remove arrow icon from folders Create util to handle pagination transformation Wrap long Project and Group titles Changes after review Changes after review Rename storePagination to setPagination Transforms startTimeAgoLoops into a static method so we can reuse it instead of have 2 ...
This commit is contained in:
commit
744580fd64
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -95,7 +95,7 @@ $(() => {
|
|||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
return Store.shouldAddBlankState();
|
||||
return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*
|
||||
* Used to store the Pipelines rendered in the commit view in the pipelines table.
|
||||
*/
|
||||
require('../../vue_realtime_listener');
|
||||
|
||||
class PipelinesStore {
|
||||
constructor() {
|
||||
|
@ -24,7 +25,7 @@ class PipelinesStore {
|
|||
* update the time to show how long as passed.
|
||||
*
|
||||
*/
|
||||
startTimeAgoLoops() {
|
||||
static startTimeAgoLoops() {
|
||||
const startTimeLoops = () => {
|
||||
this.timeLoopInterval = setInterval(() => {
|
||||
this.$children[0].$children.reduce((acc, component) => {
|
||||
|
@ -44,7 +45,4 @@ class PipelinesStore {
|
|||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
gl.commits = gl.commits || {};
|
||||
gl.commits.pipelines = gl.commits.pipelines || {};
|
||||
gl.commits.pipelines.PipelinesStore = PipelinesStore;
|
||||
module.exports = PipelinesStore;
|
||||
|
|
|
@ -6,9 +6,8 @@ window.Vue.use(require('vue-resource'));
|
|||
require('../../lib/utils/common_utils');
|
||||
require('../../vue_shared/vue_resource_interceptor');
|
||||
require('../../vue_shared/components/pipelines_table');
|
||||
require('../../vue_realtime_listener/index');
|
||||
require('./pipelines_service');
|
||||
require('./pipelines_store');
|
||||
const PipelineStore = require('./pipelines_store');
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -41,7 +40,7 @@ require('./pipelines_store');
|
|||
data() {
|
||||
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
|
||||
const svgsData = document.querySelector('.pipeline-svgs').dataset;
|
||||
const store = new gl.commits.pipelines.PipelinesStore();
|
||||
const store = new PipelineStore();
|
||||
|
||||
// Transform svgs DOMStringMap to a plain Object.
|
||||
const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
|
||||
|
@ -71,7 +70,6 @@ require('./pipelines_store');
|
|||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
this.store.storePipelines(json);
|
||||
this.store.startTimeAgoLoops.call(this, Vue);
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -80,6 +78,12 @@ require('./pipelines_store');
|
|||
});
|
||||
},
|
||||
|
||||
beforeUpdate() {
|
||||
if (this.state.pipelines.length && this.$children) {
|
||||
PipelineStore.startTimeAgoLoops.call(this, Vue);
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="pipelines">
|
||||
<div class="realtime-loading" v-if="isLoading">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -54,16 +54,19 @@ require('vendor/task_list');
|
|||
success: function(data, textStatus, jqXHR) {
|
||||
if ('id' in data) {
|
||||
$(document).trigger('issuable:change');
|
||||
const currentTotal = Number($('.issue_counter').text());
|
||||
if (isClose) {
|
||||
$('a.btn-close').addClass('hidden');
|
||||
$('a.btn-reopen').removeClass('hidden');
|
||||
$('div.status-box-closed').removeClass('hidden');
|
||||
$('div.status-box-open').addClass('hidden');
|
||||
$('.issue_counter').text(currentTotal - 1);
|
||||
} else {
|
||||
$('a.btn-reopen').addClass('hidden');
|
||||
$('a.btn-close').removeClass('hidden');
|
||||
$('div.status-box-closed').addClass('hidden');
|
||||
$('div.status-box-open').removeClass('hidden');
|
||||
$('.issue_counter').text(currentTotal + 1);
|
||||
}
|
||||
} else {
|
||||
new Flash(issueFailMessage, 'alert');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -110,7 +110,7 @@ require('./smart_interval');
|
|||
urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : '';
|
||||
return window.location.href = window.location.pathname + urlSuffix;
|
||||
} else if (data.merge_error) {
|
||||
return _this.$widgetBody.html("<h4>" + data.merge_error + "</h4>");
|
||||
return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>");
|
||||
} else {
|
||||
callback = function() {
|
||||
return merge_request_widget.mergeInProgress(deleteSourceBranch);
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
<li v-for='artifact in pipeline.details.artifacts'>
|
||||
<a
|
||||
rel="nofollow"
|
||||
download
|
||||
:href='artifact.path'
|
||||
>
|
||||
<i class="fa fa-download" aria-hidden="true"></i>
|
||||
|
|
|
@ -5,6 +5,7 @@ window.Vue = require('vue');
|
|||
require('../vue_shared/components/table_pagination');
|
||||
require('./store');
|
||||
require('../vue_shared/components/pipelines_table');
|
||||
const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_store');
|
||||
|
||||
((gl) => {
|
||||
gl.VuePipelines = Vue.extend({
|
||||
|
@ -32,10 +33,30 @@ require('../vue_shared/components/pipelines_table');
|
|||
const scope = gl.utils.getParameterByName('scope');
|
||||
if (pagenum) this.pagenum = pagenum;
|
||||
if (scope) this.apiScope = scope;
|
||||
|
||||
this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope);
|
||||
},
|
||||
|
||||
beforeUpdate() {
|
||||
if (this.pipelines.length && this.$children) {
|
||||
CommitPipelinesStoreWithTimeAgo.startTimeAgoLoops.call(this, Vue);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* Changes the URL according to the pagination component.
|
||||
*
|
||||
* If no scope is provided, 'all' is assumed.
|
||||
*
|
||||
* Pagination component sends "null" when no scope is provided.
|
||||
*
|
||||
* @param {Number} pagenum
|
||||
* @param {String} apiScope = 'all'
|
||||
*/
|
||||
change(pagenum, apiScope) {
|
||||
if (!apiScope) apiScope = 'all';
|
||||
gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,68 +1,31 @@
|
|||
/* global gl, Flash */
|
||||
/* eslint-disable no-param-reassign, no-underscore-dangle */
|
||||
require('../vue_realtime_listener');
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
((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.parseIntPagination(normalized);
|
||||
return paginationInfo;
|
||||
};
|
||||
|
||||
gl.PipelineStore = class {
|
||||
fetchDataLoop(Vue, pageNum, url, apiScope) {
|
||||
this.pageRequest = true;
|
||||
const updatePipelineNums = (count) => {
|
||||
const { all } = count;
|
||||
const running = count.running_or_pending;
|
||||
document.querySelector('.js-totalbuilds-count').innerHTML = all;
|
||||
document.querySelector('.js-running-count').innerHTML = running;
|
||||
};
|
||||
|
||||
const goFetch = () =>
|
||||
this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`)
|
||||
.then((response) => {
|
||||
const pageInfo = pageValues(response.headers);
|
||||
this.pageInfo = Object.assign({}, this.pageInfo, pageInfo);
|
||||
return this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`)
|
||||
.then((response) => {
|
||||
const pageInfo = pageValues(response.headers);
|
||||
this.pageInfo = Object.assign({}, this.pageInfo, pageInfo);
|
||||
|
||||
const res = JSON.parse(response.body);
|
||||
this.count = Object.assign({}, this.count, res.count);
|
||||
this.pipelines = Object.assign([], this.pipelines, res.pipelines);
|
||||
const res = JSON.parse(response.body);
|
||||
this.count = Object.assign({}, this.count, res.count);
|
||||
this.pipelines = Object.assign([], this.pipelines, res.pipelines);
|
||||
|
||||
updatePipelineNums(this.count);
|
||||
this.pageRequest = false;
|
||||
}, () => {
|
||||
this.pageRequest = false;
|
||||
return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
|
||||
});
|
||||
|
||||
goFetch();
|
||||
|
||||
const startTimeLoops = () => {
|
||||
this.timeLoopInterval = setInterval(() => {
|
||||
this.$children[0].$children.reduce((acc, component) => {
|
||||
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
|
||||
acc.push(timeAgoComponent);
|
||||
return acc;
|
||||
}, []).forEach(e => e.changeTime());
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
startTimeLoops();
|
||||
|
||||
const removeIntervals = () => clearInterval(this.timeLoopInterval);
|
||||
const startIntervals = () => startTimeLoops();
|
||||
|
||||
gl.VueRealtimeListener(removeIntervals, startIntervals);
|
||||
this.pageRequest = false;
|
||||
}, () => {
|
||||
this.pageRequest = false;
|
||||
return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
|
||||
});
|
||||
}
|
||||
};
|
||||
})(window.gl || (window.gl = {}));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
}
|
||||
|
||||
&.scroll-top {
|
||||
top: 110px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
&.scroll-bottom {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
word-wrap: break-word;
|
||||
|
||||
.fa {
|
||||
margin-left: 2px;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -369,10 +369,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def merge_widget_refresh
|
||||
if merge_request.in_progress_merge_commit_sha || merge_request.state == 'merged'
|
||||
@status = :success
|
||||
elsif merge_request.merge_when_build_succeeds
|
||||
if merge_request.merge_when_build_succeeds
|
||||
@status = :merge_when_build_succeeds
|
||||
else
|
||||
# Only MRs that can be merged end in this action
|
||||
# MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
|
||||
# in last case it does not have any special status. Possible error is handled inside widget js function
|
||||
@status = :success
|
||||
end
|
||||
|
||||
render 'merge'
|
||||
|
|
|
@ -296,4 +296,13 @@ module ApplicationHelper
|
|||
def page_class
|
||||
"issue-boards-page" if current_controller?(:boards)
|
||||
end
|
||||
|
||||
# Returns active css class when condition returns true
|
||||
# otherwise returns nil.
|
||||
#
|
||||
# Example:
|
||||
# %li{ class: active_when(params[:filter] == '1') }
|
||||
def active_when(condition)
|
||||
'active' if condition
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,6 +34,10 @@ module PageLayoutHelper
|
|||
end
|
||||
end
|
||||
|
||||
def favicon
|
||||
Rails.env.development? ? 'favicon-blue.ico' : 'favicon.ico'
|
||||
end
|
||||
|
||||
def page_image
|
||||
default = image_url('gitlab_logo.png')
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class Namespace < ActiveRecord::Base
|
|||
after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') }
|
||||
|
||||
# Save the storage paths before the projects are destroyed to use them on after destroy
|
||||
before_destroy(prepend: true) { @old_repository_storage_paths = repository_storage_paths }
|
||||
before_destroy(prepend: true) { prepare_for_destroy }
|
||||
after_destroy :rm_dir
|
||||
|
||||
scope :root, -> { where('type IS NULL') }
|
||||
|
@ -211,6 +211,14 @@ class Namespace < ActiveRecord::Base
|
|||
parent_id_changed?
|
||||
end
|
||||
|
||||
def prepare_for_destroy
|
||||
old_repository_storage_paths
|
||||
end
|
||||
|
||||
def old_repository_storage_paths
|
||||
@old_repository_storage_paths ||= repository_storage_paths
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_storage_paths
|
||||
|
@ -224,7 +232,7 @@ class Namespace < ActiveRecord::Base
|
|||
|
||||
def rm_dir
|
||||
# Remove the namespace directory in all storages paths used by member projects
|
||||
@old_repository_storage_paths.each do |repository_storage_path|
|
||||
old_repository_storage_paths.each do |repository_storage_path|
|
||||
# Move namespace directory into trash.
|
||||
# We will remove it later async
|
||||
new_path = "#{path}+#{id}+deleted"
|
||||
|
|
|
@ -214,6 +214,8 @@ class Project < ActiveRecord::Base
|
|||
# Scopes
|
||||
default_scope { where(pending_delete: false) }
|
||||
|
||||
scope :with_deleted, -> { unscope(where: :pending_delete) }
|
||||
|
||||
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
|
||||
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
|
||||
|
||||
|
|
|
@ -160,6 +160,10 @@ class ProjectWiki
|
|||
}
|
||||
end
|
||||
|
||||
def repository_storage_path
|
||||
project.repository_storage_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_repo(path_with_namespace)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,7 +8,9 @@ module Groups
|
|||
end
|
||||
|
||||
def execute
|
||||
group.projects.each do |project|
|
||||
group.prepare_for_destroy
|
||||
|
||||
group.projects.with_deleted.each do |project|
|
||||
# Execute the destruction of the models immediately to ensure atomic cleanup.
|
||||
# Skip repository removal because we remove directory with namespace
|
||||
# that contain all these repositories
|
||||
|
|
|
@ -11,18 +11,20 @@ module MergeRequests
|
|||
def execute(merge_request)
|
||||
@merge_request = merge_request
|
||||
|
||||
return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable?
|
||||
unless @merge_request.mergeable?
|
||||
return log_merge_error('Merge request is not mergeable', save_message_on_model: true)
|
||||
end
|
||||
|
||||
@source = find_merge_source
|
||||
|
||||
return log_merge_error('No source for merge', true) unless @source
|
||||
unless @source
|
||||
log_merge_error('No source for merge', save_message_on_model: true)
|
||||
end
|
||||
|
||||
merge_request.in_locked_state do
|
||||
if commit
|
||||
after_merge
|
||||
success
|
||||
else
|
||||
log_merge_error('Can not merge changes', true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -43,11 +45,11 @@ module MergeRequests
|
|||
if commit_id
|
||||
merge_request.update(merge_commit_sha: commit_id)
|
||||
else
|
||||
merge_request.update(merge_error: 'Conflicts detected during merge')
|
||||
log_merge_error('Conflicts detected during merge', save_message_on_model: true)
|
||||
false
|
||||
end
|
||||
rescue GitHooksService::PreReceiveError => e
|
||||
merge_request.update(merge_error: e.message)
|
||||
log_merge_error(e.message, save_message_on_model: true)
|
||||
false
|
||||
rescue StandardError => e
|
||||
merge_request.update(merge_error: "Something went wrong during merge: #{e.message}")
|
||||
|
@ -70,10 +72,10 @@ module MergeRequests
|
|||
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
|
||||
end
|
||||
|
||||
def log_merge_error(message, http_error = false)
|
||||
def log_merge_error(message, save_message_on_model: false)
|
||||
Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}")
|
||||
|
||||
error(message) if http_error
|
||||
@merge_request.update(merge_error: message) if save_message_on_model
|
||||
end
|
||||
|
||||
def merge_request_info
|
||||
|
|
|
@ -8,15 +8,14 @@
|
|||
%div{ class: container_class }
|
||||
%ul.nav-links.log-tabs
|
||||
- loggers.each do |klass|
|
||||
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }>
|
||||
%li{ class: active_when(klass == Gitlab::GitLogger) }>
|
||||
= link_to klass::file_name, "##{klass::file_name_noext}",
|
||||
'data-toggle' => 'tab'
|
||||
.row-content-block
|
||||
To prevent performance issues admin logs output the last 2000 lines
|
||||
.tab-content
|
||||
- loggers.each do |klass|
|
||||
.tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
|
||||
id: klass::file_name_noext }
|
||||
.tab-pane{ class: active_when(klass == Gitlab::GitLogger), id: klass::file_name_noext }
|
||||
.file-holder#README
|
||||
.js-file-title.file-title
|
||||
%i.fa.fa-file
|
||||
|
|
|
@ -48,13 +48,13 @@
|
|||
= link_to admin_projects_path do
|
||||
All
|
||||
|
||||
= nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s ? 'active' : '' }) do
|
||||
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do
|
||||
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
|
||||
Private
|
||||
= nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s ? 'active' : '' }) do
|
||||
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do
|
||||
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
|
||||
Internal
|
||||
= nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s ? 'active' : '' }) do
|
||||
= nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do
|
||||
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
|
||||
Public
|
||||
|
||||
|
|
|
@ -38,31 +38,31 @@
|
|||
.nav-block
|
||||
%ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs
|
||||
.fade-left
|
||||
= nav_link(html_options: { class: ('active' unless params[:filter]) }) do
|
||||
= nav_link(html_options: { class: active_when(params[:filter].nil?) }) do
|
||||
= link_to admin_users_path do
|
||||
Active
|
||||
%small.badge= number_with_delimiter(User.active.count)
|
||||
= nav_link(html_options: { class: ('active' if params[:filter] == 'admins') }) do
|
||||
= nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do
|
||||
= link_to admin_users_path(filter: "admins") do
|
||||
Admins
|
||||
%small.badge= number_with_delimiter(User.admins.count)
|
||||
= nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_enabled'} filter-two-factor-enabled" }) do
|
||||
= nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do
|
||||
= link_to admin_users_path(filter: 'two_factor_enabled') do
|
||||
2FA Enabled
|
||||
%small.badge= number_with_delimiter(User.with_two_factor.count)
|
||||
= nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_disabled'} filter-two-factor-disabled" }) do
|
||||
= nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do
|
||||
= link_to admin_users_path(filter: 'two_factor_disabled') do
|
||||
2FA Disabled
|
||||
%small.badge= number_with_delimiter(User.without_two_factor.count)
|
||||
= nav_link(html_options: { class: ('active' if params[:filter] == 'external') }) do
|
||||
= nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do
|
||||
= link_to admin_users_path(filter: 'external') do
|
||||
External
|
||||
%small.badge= number_with_delimiter(User.external.count)
|
||||
= nav_link(html_options: { class: ('active' if params[:filter] == 'blocked') }) do
|
||||
= nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do
|
||||
= link_to admin_users_path(filter: "blocked") do
|
||||
Blocked
|
||||
%small.badge= number_with_delimiter(User.blocked.count)
|
||||
= nav_link(html_options: { class: ('active' if params[:filter] == 'wop') }) do
|
||||
= nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do
|
||||
= link_to admin_users_path(filter: "wop") do
|
||||
Without projects
|
||||
%small.badge= number_with_delimiter(User.without_projects.count)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
.top-area
|
||||
%ul.nav-links
|
||||
%li{ class: ("active" unless params[:filter]) }>
|
||||
%li{ class: active_when(params[:filter].nil?) }>
|
||||
= link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
|
||||
Your Projects
|
||||
%li{ class: ("active" if params[:filter] == 'starred') }>
|
||||
%li{ class: active_when(params[:filter] == 'starred') }>
|
||||
= link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
|
||||
Starred Projects
|
||||
|
|
|
@ -4,15 +4,13 @@
|
|||
- if current_user.todos.any?
|
||||
.top-area
|
||||
%ul.nav-links
|
||||
- todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
|
||||
%li{ class: "todos-pending #{todo_pending_active}" }>
|
||||
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
|
||||
= link_to todos_filter_path(state: 'pending') do
|
||||
%span
|
||||
To do
|
||||
%span.badge
|
||||
= number_with_delimiter(todos_pending_count)
|
||||
- todo_done_active = ('active' if params[:state] == 'done')
|
||||
%li{ class: "todos-done #{todo_done_active}" }>
|
||||
%li.todos-done{ class: active_when(params[:state] == 'done') }>
|
||||
= link_to todos_filter_path(state: 'done') do
|
||||
%span
|
||||
Done
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
.login-body
|
||||
= render 'devise/sessions/new_crowd'
|
||||
- @ldap_servers.each_with_index do |server, i|
|
||||
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: (:active if i.zero? && !crowd_enabled?) }
|
||||
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) }
|
||||
.login-body
|
||||
= render 'devise/sessions/new_ldap', server: server
|
||||
- if signin_enabled?
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%li.active
|
||||
= link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
|
||||
- @ldap_servers.each_with_index do |server, i|
|
||||
%li{ class: (:active if i.zero? && !crowd_enabled?) }
|
||||
%li{ class: active_when(i.zero? && !crowd_enabled?) }
|
||||
= link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab'
|
||||
- if signin_enabled?
|
||||
%li
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
= link_to filter_projects_path(visibility_level: nil) do
|
||||
Any
|
||||
- Gitlab::VisibilityLevel.values.each do |level|
|
||||
%li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
|
||||
%li{ class: active_when(level.to_s == params[:visibility_level]) || 'light' }
|
||||
= link_to filter_projects_path(visibility_level: level) do
|
||||
= visibility_level_icon(level)
|
||||
= visibility_level_label(level)
|
||||
|
@ -34,7 +34,7 @@
|
|||
Any
|
||||
|
||||
- @tags.each do |tag|
|
||||
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
|
||||
%li{ class: active_when(tag.name == params[:tag]) || 'light' }
|
||||
= link_to filter_projects_path(tag: tag.name) do
|
||||
= icon('tag')
|
||||
= tag.name
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
-# total_pages: total number of pages
|
||||
-# per_page: number of items to fetch per page
|
||||
-# remote: data-remote
|
||||
%li{ class: "page#{' active' if page.current?}#{' sibling' if page.next? || page.prev?}" }
|
||||
%li.page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?)] }
|
||||
= link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil }
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
%title= page_title(site_name)
|
||||
%meta{ name: "description", content: page_description }
|
||||
|
||||
= favicon_link_tag 'favicon.ico'
|
||||
= favicon_link_tag favicon
|
||||
|
||||
= stylesheet_link_tag "application", media: "all"
|
||||
= stylesheet_link_tag "print", media: "print"
|
||||
|
|
|
@ -8,19 +8,19 @@
|
|||
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
|
||||
%li.dropdown-header Source code
|
||||
%li
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
|
||||
%i.fa.fa-download
|
||||
%span Download zip
|
||||
%li
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
|
||||
%i.fa.fa-download
|
||||
%span Download tar.gz
|
||||
%li
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
|
||||
%i.fa.fa-download
|
||||
%span Download tar.bz2
|
||||
%li
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
|
||||
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
|
||||
%i.fa.fa-download
|
||||
%span Download tar
|
||||
|
||||
|
@ -36,6 +36,6 @@
|
|||
%li.dropdown-header Previous Artifacts
|
||||
- artifacts.each do |job|
|
||||
%li
|
||||
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do
|
||||
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
|
||||
%i.fa.fa-download
|
||||
%span Download '#{job.name}'
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- artifacts.each do |build|
|
||||
%li
|
||||
= link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow' do
|
||||
= link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow', download: '' do
|
||||
= icon("download")
|
||||
%span Download '#{build.name}' artifacts
|
||||
|
||||
|
|
|
@ -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") } }
|
|
@ -28,7 +28,7 @@
|
|||
%span
|
||||
CI job
|
||||
= ci_label_for_status(status)
|
||||
for
|
||||
for
|
||||
- commit = @merge_request.diff_head_commit
|
||||
= succeed "." do
|
||||
= link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
|
||||
|
|
|
@ -5,23 +5,23 @@
|
|||
%div{ class: container_class }
|
||||
.top-area
|
||||
%ul.nav-links
|
||||
%li{ class: ('active' if @scope.nil?) }>
|
||||
%li{ class: active_when(@scope.nil?) }>
|
||||
= link_to project_pipelines_path(@project) do
|
||||
All
|
||||
%span.badge.js-totalbuilds-count
|
||||
= number_with_delimiter(@pipelines_count)
|
||||
|
||||
%li{ class: ('active' if @scope == 'running') }>
|
||||
%li{ class: active_when(@scope == 'running') }>
|
||||
= link_to project_pipelines_path(@project, scope: :running) do
|
||||
Running
|
||||
%span.badge.js-running-count
|
||||
= number_with_delimiter(@running_or_pending_count)
|
||||
|
||||
%li{ class: ('active' if @scope == 'branches') }>
|
||||
%li{ class: active_when(@scope == 'branches') }>
|
||||
= link_to project_pipelines_path(@project, scope: :branches) do
|
||||
Branches
|
||||
|
||||
%li{ class: ('active' if @scope == 'tags') }>
|
||||
%li{ class: active_when(@scope == 'tags') }>
|
||||
= link_to project_pipelines_path(@project, scope: :tags) do
|
||||
Tags
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
%li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
|
||||
%li{ class: active_when(params[:id] == wiki_page.slug) }
|
||||
= link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
|
||||
= wiki_page.title.capitalize
|
||||
|
|
|
@ -1,70 +1,70 @@
|
|||
%ul.nav-links.search-filter
|
||||
- if @project
|
||||
%li{ class: ("active" if @scope == 'blobs') }
|
||||
%li{ class: active_when(@scope == 'blobs') }
|
||||
= link_to search_filter_path(scope: 'blobs') do
|
||||
Code
|
||||
%span.badge
|
||||
= @search_results.blobs_count
|
||||
%li{ class: ("active" if @scope == 'issues') }
|
||||
%li{ class: active_when(@scope == 'issues') }
|
||||
= link_to search_filter_path(scope: 'issues') do
|
||||
Issues
|
||||
%span.badge
|
||||
= @search_results.issues_count
|
||||
%li{ class: ("active" if @scope == 'merge_requests') }
|
||||
%li{ class: active_when(@scope == 'merge_requests') }
|
||||
= link_to search_filter_path(scope: 'merge_requests') do
|
||||
Merge requests
|
||||
%span.badge
|
||||
= @search_results.merge_requests_count
|
||||
%li{ class: ("active" if @scope == 'milestones') }
|
||||
%li{ class: active_when(@scope == 'milestones') }
|
||||
= link_to search_filter_path(scope: 'milestones') do
|
||||
Milestones
|
||||
%span.badge
|
||||
= @search_results.milestones_count
|
||||
%li{ class: ("active" if @scope == 'notes') }
|
||||
%li{ class: active_when(@scope == 'notes') }
|
||||
= link_to search_filter_path(scope: 'notes') do
|
||||
Comments
|
||||
%span.badge
|
||||
= @search_results.notes_count
|
||||
%li{ class: ("active" if @scope == 'wiki_blobs') }
|
||||
%li{ class: active_when(@scope == 'wiki_blobs') }
|
||||
= link_to search_filter_path(scope: 'wiki_blobs') do
|
||||
Wiki
|
||||
%span.badge
|
||||
= @search_results.wiki_blobs_count
|
||||
%li{ class: ("active" if @scope == 'commits') }
|
||||
%li{ class: active_when(@scope == 'commits') }
|
||||
= link_to search_filter_path(scope: 'commits') do
|
||||
Commits
|
||||
%span.badge
|
||||
= @search_results.commits_count
|
||||
|
||||
- elsif @show_snippets
|
||||
%li{ class: ("active" if @scope == 'snippet_blobs') }
|
||||
%li{ class: active_when(@scope == 'snippet_blobs') }
|
||||
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
|
||||
Snippet Contents
|
||||
%span.badge
|
||||
= @search_results.snippet_blobs_count
|
||||
%li{ class: ("active" if @scope == 'snippet_titles') }
|
||||
%li{ class: active_when(@scope == 'snippet_titles') }
|
||||
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
|
||||
Titles and Filenames
|
||||
%span.badge
|
||||
= @search_results.snippet_titles_count
|
||||
|
||||
- else
|
||||
%li{ class: ("active" if @scope == 'projects') }
|
||||
%li{ class: active_when(@scope == 'projects') }
|
||||
= link_to search_filter_path(scope: 'projects') do
|
||||
Projects
|
||||
%span.badge
|
||||
= @search_results.projects_count
|
||||
%li{ class: ("active" if @scope == 'issues') }
|
||||
%li{ class: active_when(@scope == 'issues') }
|
||||
= link_to search_filter_path(scope: 'issues') do
|
||||
Issues
|
||||
%span.badge
|
||||
= @search_results.issues_count
|
||||
%li{ class: ("active" if @scope == 'merge_requests') }
|
||||
%li{ class: active_when(@scope == 'merge_requests') }
|
||||
= link_to search_filter_path(scope: 'merge_requests') do
|
||||
Merge requests
|
||||
%span.badge
|
||||
= @search_results.merge_requests_count
|
||||
%li{ class: ("active" if @scope == 'milestones') }
|
||||
%li{ class: active_when(@scope == 'milestones') }
|
||||
= link_to search_filter_path(scope: 'milestones') do
|
||||
Milestones
|
||||
%span.badge
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
= snippet.title
|
||||
by
|
||||
= link_to user_snippets_path(snippet.author) do
|
||||
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
|
||||
= image_tag avatar_icon(snippet.author), class: "avatar avatar-inline s16", alt: ''
|
||||
= snippet.author_name
|
||||
%span.light= time_ago_with_tooltip(snippet.created_at)
|
||||
%h4.snippet-title
|
||||
|
|
|
@ -18,6 +18,6 @@
|
|||
%span
|
||||
by
|
||||
= link_to user_snippets_path(snippet_title.author) do
|
||||
= image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: ''
|
||||
= image_tag avatar_icon(snippet_title.author), class: "avatar avatar-inline s16", alt: ''
|
||||
= snippet_title.author_name
|
||||
%span.light= time_ago_with_tooltip(snippet_title.created_at)
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
%ul.nav-links
|
||||
%li{ class: ('active' if scope.nil?) }>
|
||||
%li{ class: active_when(scope.nil?) }>
|
||||
= link_to build_path_proc.call(nil) do
|
||||
All
|
||||
%span.badge.js-totalbuilds-count
|
||||
= number_with_delimiter(all_builds.count(:id))
|
||||
|
||||
%li{ class: ('active' if scope == 'pending') }>
|
||||
%li{ class: active_when(scope == 'pending') }>
|
||||
= link_to build_path_proc.call('pending') do
|
||||
Pending
|
||||
%span.badge
|
||||
= number_with_delimiter(all_builds.pending.count(:id))
|
||||
|
||||
%li{ class: ('active' if scope == 'running') }>
|
||||
%li{ class: active_when(scope == 'running') }>
|
||||
= link_to build_path_proc.call('running') do
|
||||
Running
|
||||
%span.badge
|
||||
= number_with_delimiter(all_builds.running.count(:id))
|
||||
|
||||
%li{ class: ('active' if scope == 'finished') }>
|
||||
%li{ class: active_when(scope == 'finished') }>
|
||||
= link_to build_path_proc.call('finished') do
|
||||
Finished
|
||||
%span.badge
|
||||
|
|
|
@ -3,23 +3,23 @@
|
|||
- issuables = @issues || @merge_requests
|
||||
|
||||
%ul.nav-links.issues-state-filters
|
||||
%li{ class: ("active" if params[:state] == 'opened') }>
|
||||
%li{ class: active_when(params[:state] == 'opened') }>
|
||||
= link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened." do
|
||||
#{issuables_state_counter_text(type, :opened)}
|
||||
|
||||
- if type == :merge_requests
|
||||
%li{ class: ("active" if params[:state] == 'merged') }>
|
||||
%li{ class: active_when(params[:state] == 'merged') }>
|
||||
= link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.' do
|
||||
#{issuables_state_counter_text(type, :merged)}
|
||||
|
||||
%li{ class: ("active" if params[:state] == 'closed') }>
|
||||
%li{ class: active_when(params[:state] == 'closed') }>
|
||||
= link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.' do
|
||||
#{issuables_state_counter_text(type, :closed)}
|
||||
- else
|
||||
%li{ class: ("active" if params[:state] == 'closed') }>
|
||||
%li{ class: active_when(params[:state] == 'closed') }>
|
||||
= link_to page_filter_path(state: 'closed', label: true), id: 'state-all', title: 'Filter by issues that are currently closed.' do
|
||||
#{issuables_state_counter_text(type, :closed)}
|
||||
|
||||
%li{ class: ("active" if params[:state] == 'all') }>
|
||||
%li{ class: active_when(params[:state] == 'all') }>
|
||||
= link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}." do
|
||||
#{issuables_state_counter_text(type, :all)}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- include_private = local_assigns.fetch(:include_private, false)
|
||||
|
||||
.nav-links.snippet-scope-menu
|
||||
%li{ class: ("active" unless params[:scope]) }
|
||||
%li{ class: active_when(params[:scope].nil?) }
|
||||
= link_to subject_snippets_path(subject) do
|
||||
All
|
||||
%span.badge
|
||||
|
@ -12,19 +12,19 @@
|
|||
= subject.snippets.public_and_internal.count
|
||||
|
||||
- if include_private
|
||||
%li{ class: ("active" if params[:scope] == "are_private") }
|
||||
%li{ class: active_when(params[:scope] == "are_private") }
|
||||
= link_to subject_snippets_path(subject, scope: 'are_private') do
|
||||
Private
|
||||
%span.badge
|
||||
= subject.snippets.are_private.count
|
||||
|
||||
%li{ class: ("active" if params[:scope] == "are_internal") }
|
||||
%li{ class: active_when(params[:scope] == "are_internal") }
|
||||
= link_to subject_snippets_path(subject, scope: 'are_internal') do
|
||||
Internal
|
||||
%span.badge
|
||||
= subject.snippets.are_internal.count
|
||||
|
||||
%li{ class: ("active" if params[:scope] == "are_public") }
|
||||
%li{ class: active_when(params[:scope] == "are_public") }
|
||||
= link_to subject_snippets_path(subject, scope: 'are_public') do
|
||||
Public
|
||||
%span.badge
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: don't animate logo when downloading files
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: update issue count when closing/reopening an issue
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Only return target project's comments for a commit
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Show merge errors in merge request widget
|
||||
merge_request: 9229
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix error in MR widget after /merge slash command
|
||||
merge_request: 9259
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Only run timeago loops after rendering timeago components
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix positioning of `Scroll to top` button
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Wrap long Project and Group titles
|
||||
merge_request: 9301
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Set Auto-Submitted header to mails
|
||||
merge_request:
|
||||
author: Semyon Pupkov
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Make Karma output look nicer for CI
|
||||
merge_request: 9165
|
||||
author: winniehell
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Adds paginationd and folders view to environments table
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Move babel config for instanbul to karma config
|
||||
merge_request: 9286
|
||||
author: winniehell
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Seed abuse reports for development
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Reduced query count for snippet search
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1 @@
|
|||
ActionMailer::Base.register_interceptor(AdditionalEmailHeadersInterceptor)
|
|
@ -2,8 +2,20 @@ var path = require('path');
|
|||
var webpackConfig = require('./webpack.config.js');
|
||||
var ROOT_PATH = path.resolve(__dirname, '..');
|
||||
|
||||
// add coverage instrumentation to babel config
|
||||
if (webpackConfig && webpackConfig.module && webpackConfig.module.rules) {
|
||||
var babelConfig = webpackConfig.module.rules.find(function (rule) {
|
||||
return rule.loader === 'babel-loader';
|
||||
});
|
||||
|
||||
babelConfig.options = babelConfig.options || {};
|
||||
babelConfig.options.plugins = babelConfig.options.plugins || [];
|
||||
babelConfig.options.plugins.push('istanbul');
|
||||
}
|
||||
|
||||
// Karma configuration
|
||||
module.exports = function(config) {
|
||||
var progressReporter = process.env.CI ? 'mocha' : 'progress';
|
||||
config.set({
|
||||
basePath: ROOT_PATH,
|
||||
browsers: ['PhantomJS'],
|
||||
|
@ -15,7 +27,7 @@ module.exports = function(config) {
|
|||
preprocessors: {
|
||||
'spec/javascripts/**/*.js?(.es6)': ['webpack', 'sourcemap'],
|
||||
},
|
||||
reporters: ['progress', 'coverage-istanbul'],
|
||||
reporters: [progressReporter, 'coverage-istanbul'],
|
||||
coverageIstanbulReporter: {
|
||||
reports: ['html', 'text-summary'],
|
||||
dir: 'coverage-javascript/',
|
||||
|
|
|
@ -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',
|
||||
|
@ -54,7 +55,6 @@ var config = {
|
|||
exclude: /(node_modules|vendor\/assets)/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: IS_PRODUCTION ? [] : ['istanbul'],
|
||||
presets: [
|
||||
["es2015", {"modules": false}],
|
||||
'stage-2'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
require 'factory_girl_rails'
|
||||
|
||||
(AbuseReport.default_per_page + 3).times do
|
||||
FactoryGirl.create(:abuse_report)
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
# GitLab Pages Administration
|
||||
# GitLab Pages administration
|
||||
|
||||
> **Notes:**
|
||||
- [Introduced][ee-80] in GitLab EE 8.3.
|
||||
|
@ -6,6 +6,7 @@
|
|||
- GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
|
||||
- This guide is for Omnibus GitLab installations. If you have installed
|
||||
GitLab from source, follow the [Pages source installation document](source.md).
|
||||
- To learn how to use GitLab Pages, read the [user documentation][pages-userguide].
|
||||
|
||||
---
|
||||
|
||||
|
@ -14,9 +15,6 @@ sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
|
|||
version as it may include new features and changes needed to be made in your
|
||||
configuration.
|
||||
|
||||
If you are looking for ways to upload your static content in GitLab Pages, you
|
||||
probably want to read the [user documentation][pages-userguide].
|
||||
|
||||
## Overview
|
||||
|
||||
GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server
|
||||
|
@ -32,7 +30,7 @@ In the case of custom domains, the Pages daemon needs to listen on ports `80`
|
|||
and/or `443`. For that reason, there is some flexibility in the way which you
|
||||
can set it up:
|
||||
|
||||
1. Run the pages daemon in the same server as GitLab, listening on a secondary IP
|
||||
1. Run the pages daemon in the same server as GitLab, listening on a secondary IP.
|
||||
1. Run the pages daemon in a separate server. In that case, the
|
||||
[Pages path](#change-storage-path) must also be present in the server that
|
||||
the pages daemon is installed, so you will have to share it via network.
|
||||
|
@ -64,11 +62,11 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
|
|||
host that GitLab runs. For example, an entry would look like this:
|
||||
|
||||
```
|
||||
*.example.io. 1800 IN A 1.2.3.4
|
||||
*.example.io. 1800 IN A 1.1.1.1
|
||||
```
|
||||
|
||||
where `example.io` is the domain under which GitLab Pages will be served
|
||||
and `1.2.3.4` is the IP address of your GitLab instance.
|
||||
and `1.1.1.1` is the IP address of your GitLab instance.
|
||||
|
||||
> **Note:**
|
||||
You should not use the GitLab domain to serve user pages. For more information
|
||||
|
@ -78,69 +76,44 @@ see the [security section](#security).
|
|||
|
||||
## Configuration
|
||||
|
||||
Depending on your needs, you can install GitLab Pages in four different ways.
|
||||
Depending on your needs, you can set up GitLab Pages in 4 different ways.
|
||||
The following options are listed from the easiest setup to the most
|
||||
advanced one. The absolute minimum requirement is to set up the wildcard DNS
|
||||
since that is needed in all configurations.
|
||||
|
||||
### Option 1. Custom domains with HTTPS support
|
||||
### Wildcard domains
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes |
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
>
|
||||
>---
|
||||
>
|
||||
URL scheme: `http://page.example.io`
|
||||
|
||||
Pages enabled, daemon is enabled AND pages has external IP support enabled.
|
||||
In that case, the pages daemon is running, NGINX still proxies requests to
|
||||
the daemon but the daemon is also able to receive requests from the outside
|
||||
world. Custom domains and TLS are supported.
|
||||
This is the minimum setup that you can use Pages with. It is the base for all
|
||||
other setups as described below. Nginx will proxy all requests to the daemon.
|
||||
The Pages daemon doesn't listen to the outside world.
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`:
|
||||
1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
pages_external_url "https://example.io"
|
||||
nginx['listen_addresses'] = ['1.1.1.1']
|
||||
pages_nginx['enable'] = false
|
||||
gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
|
||||
gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
|
||||
gitlab_pages['external_http'] = '1.1.1.2:80'
|
||||
gitlab_pages['external_https'] = '1.1.1.2:443'
|
||||
pages_external_url 'http://example.io'
|
||||
```
|
||||
|
||||
where `1.1.1.1` is the primary IP address that GitLab is listening to and
|
||||
`1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
|
||||
|
||||
1. [Reconfigure GitLab][reconfigure]
|
||||
|
||||
### Option 2. Custom domains without HTTPS support
|
||||
### Wildcard domains with TLS support
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `http://page.example.io` and `http://page.com` | no | yes | no | yes |
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
- Wildcard TLS certificate
|
||||
>
|
||||
>---
|
||||
>
|
||||
URL scheme: `https://page.example.io`
|
||||
|
||||
Pages enabled, daemon is enabled AND pages has external IP support enabled.
|
||||
In that case, the pages daemon is running, NGINX still proxies requests to
|
||||
the daemon but the daemon is also able to receive requests from the outside
|
||||
world. Custom domains and TLS are supported.
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
pages_external_url "http://example.io"
|
||||
nginx['listen_addresses'] = ['1.1.1.1']
|
||||
pages_nginx['enable'] = false
|
||||
gitlab_pages['external_http'] = '1.1.1.2:80'
|
||||
```
|
||||
|
||||
where `1.1.1.1` is the primary IP address that GitLab is listening to and
|
||||
`1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
|
||||
|
||||
1. [Reconfigure GitLab][reconfigure]
|
||||
|
||||
### Option 3. Wildcard HTTPS domain without custom domains
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `https://page.example.io` | yes | no | no | no |
|
||||
|
||||
Pages enabled, daemon is enabled and NGINX will proxy all requests to the
|
||||
daemon. Pages daemon doesn't listen to the outside world.
|
||||
Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the
|
||||
outside world.
|
||||
|
||||
1. Place the certificate and key inside `/etc/gitlab/ssl`
|
||||
1. In `/etc/gitlab/gitlab.rb` specify the following configuration:
|
||||
|
@ -158,21 +131,71 @@ daemon. Pages daemon doesn't listen to the outside world.
|
|||
|
||||
1. [Reconfigure GitLab][reconfigure]
|
||||
|
||||
### Option 4. Wildcard HTTP domain without custom domains
|
||||
## Advanced configuration
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `http://page.example.io` | no | no | no | no |
|
||||
In addition to the wildcard domains, you can also have the option to configure
|
||||
GitLab Pages to work with custom domains. Again, there are two options here:
|
||||
support custom domains with and without TLS certificates. The easiest setup is
|
||||
that without TLS certificates.
|
||||
|
||||
Pages enabled, daemon is enabled and NGINX will proxy all requests to the
|
||||
daemon. Pages daemon doesn't listen to the outside world.
|
||||
### Custom domains
|
||||
|
||||
1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
- Secondary IP
|
||||
>
|
||||
---
|
||||
>
|
||||
URL scheme: `http://page.example.io` and `http://domain.com`
|
||||
|
||||
In that case, the pages daemon is running, Nginx still proxies requests to
|
||||
the daemon but the daemon is also able to receive requests from the outside
|
||||
world. Custom domains are supported, but no TLS.
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
pages_external_url 'http://example.io'
|
||||
pages_external_url "http://example.io"
|
||||
nginx['listen_addresses'] = ['1.1.1.1']
|
||||
pages_nginx['enable'] = false
|
||||
gitlab_pages['external_http'] = '1.1.1.2:80'
|
||||
```
|
||||
|
||||
where `1.1.1.1` is the primary IP address that GitLab is listening to and
|
||||
`1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
|
||||
|
||||
1. [Reconfigure GitLab][reconfigure]
|
||||
|
||||
### Custom domains with TLS support
|
||||
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
- Wildcard TLS certificate
|
||||
- Secondary IP
|
||||
>
|
||||
---
|
||||
>
|
||||
URL scheme: `https://page.example.io` and `https://domain.com`
|
||||
|
||||
In that case, the pages daemon is running, Nginx still proxies requests to
|
||||
the daemon but the daemon is also able to receive requests from the outside
|
||||
world. Custom domains and TLS are supported.
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
pages_external_url "https://example.io"
|
||||
nginx['listen_addresses'] = ['1.1.1.1']
|
||||
pages_nginx['enable'] = false
|
||||
gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
|
||||
gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
|
||||
gitlab_pages['external_http'] = '1.1.1.2:80'
|
||||
gitlab_pages['external_https'] = '1.1.1.2:443'
|
||||
```
|
||||
|
||||
where `1.1.1.1` is the primary IP address that GitLab is listening to and
|
||||
`1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
|
||||
|
||||
1. [Reconfigure GitLab][reconfigure]
|
||||
|
||||
## Change storage path
|
||||
|
|
|
@ -17,20 +17,234 @@ Pages to the latest supported version.
|
|||
|
||||
## Prerequisites
|
||||
|
||||
[Read the Omnibus prerequisites section.](index.md#prerequisites)
|
||||
Before proceeding with the Pages configuration, you will need to:
|
||||
|
||||
1. Have a separate domain under which the GitLab Pages will be served. In this
|
||||
document we assume that to be `example.io`.
|
||||
1. Configure a **wildcard DNS record**.
|
||||
1. (Optional) Have a **wildcard certificate** for that domain if you decide to
|
||||
serve Pages under HTTPS.
|
||||
1. (Optional but recommended) Enable [Shared runners](../../ci/runners/README.md)
|
||||
so that your users don't have to bring their own.
|
||||
|
||||
### DNS configuration
|
||||
|
||||
GitLab Pages expect to run on their own virtual host. In your DNS server/provider
|
||||
you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
|
||||
host that GitLab runs. For example, an entry would look like this:
|
||||
|
||||
```
|
||||
*.example.io. 1800 IN A 1.1.1.1
|
||||
```
|
||||
|
||||
where `example.io` is the domain under which GitLab Pages will be served
|
||||
and `1.1.1.1` is the IP address of your GitLab instance.
|
||||
|
||||
> **Note:**
|
||||
You should not use the GitLab domain to serve user pages. For more information
|
||||
see the [security section](#security).
|
||||
|
||||
[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record
|
||||
|
||||
## Configuration
|
||||
|
||||
Depending on your needs, you can install GitLab Pages in four different ways.
|
||||
Depending on your needs, you can set up GitLab Pages in 4 different ways.
|
||||
The following options are listed from the easiest setup to the most
|
||||
advanced one. The absolute minimum requirement is to set up the wildcard DNS
|
||||
since that is needed in all configurations.
|
||||
|
||||
### Option 1. Custom domains with HTTPS support
|
||||
### Wildcard domains
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes |
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
>
|
||||
>---
|
||||
>
|
||||
URL scheme: `http://page.example.io`
|
||||
|
||||
Pages enabled, daemon is enabled AND pages has external IP support enabled.
|
||||
In that case, the pages daemon is running, NGINX still proxies requests to
|
||||
This is the minimum setup that you can use Pages with. It is the base for all
|
||||
other setups as described below. Nginx will proxy all requests to the daemon.
|
||||
The Pages daemon doesn't listen to the outside world.
|
||||
|
||||
1. Install the Pages daemon:
|
||||
|
||||
```
|
||||
cd /home/git
|
||||
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
|
||||
cd gitlab-pages
|
||||
sudo -u git -H git checkout v0.2.4
|
||||
sudo -u git -H make
|
||||
```
|
||||
|
||||
1. Go to the GitLab installation directory:
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
```
|
||||
|
||||
1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and
|
||||
the `host` to the FQDN under which GitLab Pages will be served:
|
||||
|
||||
```yaml
|
||||
## GitLab Pages
|
||||
pages:
|
||||
enabled: true
|
||||
# The location where pages are stored (default: shared/pages).
|
||||
# path: shared/pages
|
||||
|
||||
host: example.io
|
||||
port: 80
|
||||
https: false
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
|
||||
|
||||
```bash
|
||||
sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
|
||||
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
|
||||
```
|
||||
|
||||
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
|
||||
|
||||
1. Restart NGINX
|
||||
1. [Restart GitLab][restart]
|
||||
|
||||
### Wildcard domains with TLS support
|
||||
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
- Wildcard TLS certificate
|
||||
>
|
||||
>---
|
||||
>
|
||||
URL scheme: `https://page.example.io`
|
||||
|
||||
Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the
|
||||
outside world.
|
||||
|
||||
1. Install the Pages daemon:
|
||||
|
||||
```
|
||||
cd /home/git
|
||||
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
|
||||
cd gitlab-pages
|
||||
sudo -u git -H git checkout v0.2.4
|
||||
sudo -u git -H make
|
||||
```
|
||||
|
||||
1. In `gitlab.yml`, set the port to `443` and https to `true`:
|
||||
|
||||
```bash
|
||||
## GitLab Pages
|
||||
pages:
|
||||
enabled: true
|
||||
# The location where pages are stored (default: shared/pages).
|
||||
# path: shared/pages
|
||||
|
||||
host: example.io
|
||||
port: 443
|
||||
https: true
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
|
||||
|
||||
```bash
|
||||
sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
|
||||
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
|
||||
```
|
||||
|
||||
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
|
||||
|
||||
1. Restart NGINX
|
||||
1. [Restart GitLab][restart]
|
||||
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
In addition to the wildcard domains, you can also have the option to configure
|
||||
GitLab Pages to work with custom domains. Again, there are two options here:
|
||||
support custom domains with and without TLS certificates. The easiest setup is
|
||||
that without TLS certificates.
|
||||
|
||||
### Custom domains
|
||||
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
- Secondary IP
|
||||
>
|
||||
---
|
||||
>
|
||||
URL scheme: `http://page.example.io` and `http://domain.com`
|
||||
|
||||
In that case, the pages daemon is running, Nginx still proxies requests to
|
||||
the daemon but the daemon is also able to receive requests from the outside
|
||||
world. Custom domains are supported, but no TLS.
|
||||
|
||||
1. Install the Pages daemon:
|
||||
|
||||
```
|
||||
cd /home/git
|
||||
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
|
||||
cd gitlab-pages
|
||||
sudo -u git -H git checkout v0.2.4
|
||||
sudo -u git -H make
|
||||
```
|
||||
|
||||
1. Edit `gitlab.yml` to look like the example below. You need to change the
|
||||
`host` to the FQDN under which GitLab Pages will be served. Set
|
||||
`external_http` to the secondary IP on which the pages daemon will listen
|
||||
for connections:
|
||||
|
||||
```yaml
|
||||
pages:
|
||||
enabled: true
|
||||
# The location where pages are stored (default: shared/pages).
|
||||
# path: shared/pages
|
||||
|
||||
host: example.io
|
||||
port: 80
|
||||
https: false
|
||||
|
||||
external_http: 1.1.1.2:80
|
||||
```
|
||||
|
||||
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
|
||||
order to enable the pages daemon. In `gitlab_pages_options` the
|
||||
`-pages-domain` and `-listen-http` must match the `host` and `external_http`
|
||||
settings that you set above respectively:
|
||||
|
||||
```
|
||||
gitlab_pages_enabled=true
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80"
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
|
||||
|
||||
```bash
|
||||
sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
|
||||
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
|
||||
```
|
||||
|
||||
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
|
||||
|
||||
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
|
||||
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
|
||||
listens to.
|
||||
1. Restart NGINX
|
||||
1. [Restart GitLab][restart]
|
||||
|
||||
### Custom domains with TLS support
|
||||
|
||||
>**Requirements:**
|
||||
- [Wildcard DNS setup](#dns-configuration)
|
||||
- Wildcard TLS certificate
|
||||
- Secondary IP
|
||||
>
|
||||
---
|
||||
>
|
||||
URL scheme: `https://page.example.io` and `https://domain.com`
|
||||
|
||||
In that case, the pages daemon is running, Nginx still proxies requests to
|
||||
the daemon but the daemon is also able to receive requests from the outside
|
||||
world. Custom domains and TLS are supported.
|
||||
|
||||
|
@ -91,165 +305,20 @@ world. Custom domains and TLS are supported.
|
|||
1. Restart NGINX
|
||||
1. [Restart GitLab][restart]
|
||||
|
||||
### Option 2. Custom domains without HTTPS support
|
||||
## Change storage path
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `http://page.example.io` and `http://page.com` | no | yes | no | yes |
|
||||
Follow the steps below to change the default path where GitLab Pages' contents
|
||||
are stored.
|
||||
|
||||
Pages enabled, daemon is enabled AND pages has external IP support enabled.
|
||||
In that case, the pages daemon is running, NGINX still proxies requests to
|
||||
the daemon but the daemon is also able to receive requests from the outside
|
||||
world. Custom domains and TLS are supported.
|
||||
1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`.
|
||||
If you wish to store them in another location you must set it up in
|
||||
`/etc/gitlab/gitlab.rb`:
|
||||
|
||||
1. Install the Pages daemon:
|
||||
|
||||
```
|
||||
cd /home/git
|
||||
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
|
||||
cd gitlab-pages
|
||||
sudo -u git -H git checkout v0.2.4
|
||||
sudo -u git -H make
|
||||
```
|
||||
|
||||
1. Edit `gitlab.yml` to look like the example below. You need to change the
|
||||
`host` to the FQDN under which GitLab Pages will be served. Set
|
||||
`external_http` to the secondary IP on which the pages daemon will listen
|
||||
for connections:
|
||||
|
||||
```yaml
|
||||
pages:
|
||||
enabled: true
|
||||
# The location where pages are stored (default: shared/pages).
|
||||
# path: shared/pages
|
||||
|
||||
host: example.io
|
||||
port: 80
|
||||
https: false
|
||||
|
||||
external_http: 1.1.1.2:80
|
||||
```ruby
|
||||
gitlab_rails['pages_path'] = "/mnt/storage/pages"
|
||||
```
|
||||
|
||||
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
|
||||
order to enable the pages daemon. In `gitlab_pages_options` the
|
||||
`-pages-domain` and `-listen-http` must match the `host` and `external_http`
|
||||
settings that you set above respectively:
|
||||
|
||||
```
|
||||
gitlab_pages_enabled=true
|
||||
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80"
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
|
||||
|
||||
```bash
|
||||
sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
|
||||
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
|
||||
```
|
||||
|
||||
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
|
||||
|
||||
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
|
||||
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab
|
||||
listens to.
|
||||
1. Restart NGINX
|
||||
1. [Restart GitLab][restart]
|
||||
|
||||
### Option 3. Wildcard HTTPS domain without custom domains
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `https://page.example.io` | yes | no | no | no |
|
||||
|
||||
Pages enabled, daemon is enabled and NGINX will proxy all requests to the
|
||||
daemon. Pages daemon doesn't listen to the outside world.
|
||||
|
||||
1. Install the Pages daemon:
|
||||
|
||||
```
|
||||
cd /home/git
|
||||
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
|
||||
cd gitlab-pages
|
||||
sudo -u git -H git checkout v0.2.4
|
||||
sudo -u git -H make
|
||||
```
|
||||
1. In `gitlab.yml`, set the port to `443` and https to `true`:
|
||||
|
||||
```bash
|
||||
## GitLab Pages
|
||||
pages:
|
||||
enabled: true
|
||||
# The location where pages are stored (default: shared/pages).
|
||||
# path: shared/pages
|
||||
|
||||
host: example.io
|
||||
port: 443
|
||||
https: true
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
|
||||
|
||||
```bash
|
||||
sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
|
||||
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
|
||||
```
|
||||
|
||||
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
|
||||
|
||||
1. Restart NGINX
|
||||
1. [Restart GitLab][restart]
|
||||
|
||||
### Option 4. Wildcard HTTP domain without custom domains
|
||||
|
||||
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `http://page.example.io` | no | no | no | no |
|
||||
|
||||
Pages enabled, daemon is enabled and NGINX will proxy all requests to the
|
||||
daemon. Pages daemon doesn't listen to the outside world.
|
||||
|
||||
1. Install the Pages daemon:
|
||||
|
||||
```
|
||||
cd /home/git
|
||||
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
|
||||
cd gitlab-pages
|
||||
sudo -u git -H git checkout v0.2.4
|
||||
sudo -u git -H make
|
||||
```
|
||||
|
||||
1. Go to the GitLab installation directory:
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
```
|
||||
|
||||
1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and
|
||||
the `host` to the FQDN under which GitLab Pages will be served:
|
||||
|
||||
```yaml
|
||||
## GitLab Pages
|
||||
pages:
|
||||
enabled: true
|
||||
# The location where pages are stored (default: shared/pages).
|
||||
# path: shared/pages
|
||||
|
||||
host: example.io
|
||||
port: 80
|
||||
https: false
|
||||
```
|
||||
|
||||
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
|
||||
|
||||
```bash
|
||||
sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
|
||||
sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
|
||||
```
|
||||
|
||||
Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL.
|
||||
|
||||
1. Restart NGINX
|
||||
1. [Restart GitLab][restart]
|
||||
1. [Reconfigure GitLab][reconfigure]
|
||||
|
||||
## NGINX caveats
|
||||
|
||||
|
|
|
@ -535,6 +535,7 @@ deploy_review:
|
|||
- master
|
||||
|
||||
stop_review:
|
||||
stage: deploy
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
script:
|
||||
|
@ -555,7 +556,9 @@ when their associated branch is deleted.
|
|||
|
||||
When you have an environment that has a stop action defined (typically when
|
||||
the environment describes a review app), GitLab will automatically trigger a
|
||||
stop action when the associated branch is deleted.
|
||||
stop action when the associated branch is deleted. The `stop_review` job must
|
||||
be in the same `stage` as the `deploy_review` one in order for the environment
|
||||
to automatically stop.
|
||||
|
||||
You can read more in the [`.gitlab-ci.yml` reference][onstop].
|
||||
|
||||
|
|
|
@ -690,6 +690,8 @@ The `stop_review_app` job is **required** to have the following keywords defined
|
|||
- `when` - [reference](#when)
|
||||
- `environment:name`
|
||||
- `environment:action`
|
||||
- `stage` should be the same as the `review_app` in order for the environment
|
||||
to stop automatically when the branch is deleted
|
||||
|
||||
#### dynamic environments
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
The purpose of this guide is to document potential "gotchas" that contributors
|
||||
might encounter or should avoid during development of GitLab CE and EE.
|
||||
|
||||
## Don't `describe` symbols
|
||||
## Do not `describe` symbols
|
||||
|
||||
Consider the following model spec:
|
||||
|
||||
|
@ -32,7 +32,7 @@ spec/models/user_spec.rb|6 error| Failure/Error: u = described_class.new NoMeth
|
|||
Except for the top-level `describe` block, always provide a String argument to
|
||||
`describe`.
|
||||
|
||||
## Don't assert against the absolute value of a sequence-generated attribute
|
||||
## Do not assert against the absolute value of a sequence-generated attribute
|
||||
|
||||
Consider the following factory:
|
||||
|
||||
|
@ -121,7 +121,7 @@ describe API::Labels do
|
|||
end
|
||||
```
|
||||
|
||||
## Don't `rescue Exception`
|
||||
## Do not `rescue Exception`
|
||||
|
||||
See ["Why is it bad style to `rescue Exception => e` in Ruby?"][Exception].
|
||||
|
||||
|
@ -130,7 +130,7 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9
|
|||
|
||||
[Exception]: http://stackoverflow.com/q/10048173/223897
|
||||
|
||||
## Don't use inline JavaScript in views
|
||||
## Do not use inline JavaScript in views
|
||||
|
||||
Using the inline `:javascript` Haml filters comes with a
|
||||
performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided.
|
||||
|
|
|
@ -5,7 +5,7 @@ GitLab University is the best place to learn about **Version Control with Git an
|
|||
It doesn't replace, but accompanies our great [Documentation](https://docs.gitlab.com)
|
||||
and [Blog Articles](https://about.gitlab.com/blog/).
|
||||
|
||||
Would you like to contribute to GitLab University? Then please take a look at our contribution [process](process) for more information.
|
||||
Would you like to contribute to GitLab University? Then please take a look at our contribution [process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) for more information.
|
||||
|
||||
## Gitlab University Curriculum
|
||||
|
||||
|
|
|
@ -49,7 +49,19 @@ Install Bundler:
|
|||
sudo gem install bundler --no-ri --no-rdoc
|
||||
```
|
||||
|
||||
### 4. Get latest code
|
||||
### 4. Update Node
|
||||
|
||||
GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets and
|
||||
it has a minimum requirement of node v4.3.0.
|
||||
|
||||
You can check which version you are running with `node -v`. If you are running
|
||||
a version older than `v4.3.0` you will need to update to a newer version. You
|
||||
can find instructions to install from community maintained packages or compile
|
||||
from source at the nodejs.org website.
|
||||
|
||||
<https://nodejs.org/en/download/>
|
||||
|
||||
### 5. Get latest code
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
|
@ -76,7 +88,7 @@ cd /home/git/gitlab
|
|||
sudo -u git -H git checkout 8-17-stable-ee
|
||||
```
|
||||
|
||||
### 5. Install libs, migrations, etc.
|
||||
### 6. Install libs, migrations, etc.
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab
|
||||
|
@ -93,13 +105,16 @@ sudo -u git -H bundle clean
|
|||
# Run database migrations
|
||||
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
|
||||
|
||||
# Install/update frontend asset dependencies
|
||||
sudo -u git -H npm install --production
|
||||
|
||||
# Clean up assets and cache
|
||||
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
|
||||
sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production
|
||||
```
|
||||
|
||||
**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
|
||||
|
||||
### 6. Update gitlab-workhorse
|
||||
### 7. Update gitlab-workhorse
|
||||
|
||||
Install and compile gitlab-workhorse. This requires
|
||||
[Go 1.5](https://golang.org/dl) which should already be on your system from
|
||||
|
@ -111,7 +126,7 @@ cd /home/git/gitlab
|
|||
sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
|
||||
```
|
||||
|
||||
### 7. Update gitlab-shell
|
||||
### 8. Update gitlab-shell
|
||||
|
||||
```bash
|
||||
cd /home/git/gitlab-shell
|
||||
|
@ -120,7 +135,7 @@ sudo -u git -H git fetch --all --tags
|
|||
sudo -u git -H git checkout v4.1.1
|
||||
```
|
||||
|
||||
### 8. Update configuration files
|
||||
### 9. Update configuration files
|
||||
|
||||
#### New configuration options for `gitlab.yml`
|
||||
|
||||
|
@ -194,14 +209,14 @@ For Ubuntu 16.04.1 LTS:
|
|||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
### 9. Start application
|
||||
### 10. Start application
|
||||
|
||||
```bash
|
||||
sudo service gitlab start
|
||||
sudo service nginx restart
|
||||
```
|
||||
|
||||
### 10. Check application status
|
||||
### 11. Check application status
|
||||
|
||||
Check if GitLab and its environment are configured correctly:
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.4 KiB |
|
@ -1,25 +1,26 @@
|
|||
# Services Templates
|
||||
# Services templates
|
||||
|
||||
A GitLab administrator can add a service template that sets a default for each
|
||||
project. This makes it much easier to configure individual projects.
|
||||
project. After a service template is enabled, it will be applied to new
|
||||
projects only and its details will be pre-filled on the project's Service page.
|
||||
|
||||
After the template is created, the template details will be pre-filled on a
|
||||
project's Service page.
|
||||
|
||||
## Enable a Service template
|
||||
## Enable a service template
|
||||
|
||||
In GitLab's Admin area, navigate to **Service Templates** and choose the
|
||||
service template you wish to create.
|
||||
|
||||
For example, in the image below you can see Redmine.
|
||||
## Services for external issue trackers
|
||||
|
||||
In the image below you can see how a service template for Redmine would look
|
||||
like.
|
||||
|
||||
![Redmine service template](img/services_templates_redmine_example.png)
|
||||
|
||||
---
|
||||
|
||||
**NOTE:** For each project, you will still need to configure the issue tracking
|
||||
For each project, you will still need to configure the issue tracking
|
||||
URLs by replacing `:issues_tracker_id` in the above screenshot with the ID used
|
||||
by your external issue tracker. Prior to GitLab v7.8, this ID was configured in
|
||||
the project settings, and GitLab would automatically update the URL configured
|
||||
in `gitlab.yml`. This behavior is now deprecated and all issue tracker URLs
|
||||
must be configured directly within the project's **Services** settings.
|
||||
must be configured directly within the project's **Integrations** settings.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
class AdditionalEmailHeadersInterceptor
|
||||
def self.delivering_email(message)
|
||||
message.headers(
|
||||
'Auto-Submitted' => 'auto-generated',
|
||||
'X-Auto-Response-Suppress' => 'All'
|
||||
)
|
||||
end
|
||||
end
|
|
@ -114,7 +114,7 @@ module API
|
|||
commit = user_project.commit(params[:sha])
|
||||
|
||||
not_found! 'Commit' unless commit
|
||||
notes = Note.where(commit_id: commit.id).order(:created_at)
|
||||
notes = user_project.notes.where(commit_id: commit.id).order(:created_at)
|
||||
|
||||
present paginate(notes), with: Entities::CommitNote
|
||||
end
|
||||
|
|
|
@ -15,14 +15,6 @@ module Gitlab
|
|||
execute(%W(#{git_bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all))
|
||||
end
|
||||
|
||||
def git_unbundle(repo_path:, bundle_path:)
|
||||
execute(%W(#{git_bin_path} clone --bare #{bundle_path} #{repo_path}))
|
||||
end
|
||||
|
||||
def git_restore_hooks
|
||||
execute(%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
|
||||
end
|
||||
|
||||
def mkdir_p(path)
|
||||
FileUtils.mkdir_p(path, mode: DEFAULT_MODE)
|
||||
FileUtils.chmod(DEFAULT_MODE, path)
|
||||
|
@ -56,10 +48,6 @@ module Gitlab
|
|||
FileUtils.copy_entry(source, destination)
|
||||
true
|
||||
end
|
||||
|
||||
def repository_storage_paths_args
|
||||
Gitlab.config.repositories.storages.values
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ module Gitlab
|
|||
module ImportExport
|
||||
class RepoRestorer
|
||||
include Gitlab::ImportExport::CommandLineUtil
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
def initialize(project:, shared:, path_to_bundle:)
|
||||
@project = project
|
||||
|
@ -12,29 +13,11 @@ module Gitlab
|
|||
def restore
|
||||
return true unless File.exist?(@path_to_bundle)
|
||||
|
||||
mkdir_p(path_to_repo)
|
||||
|
||||
git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) && repo_restore_hooks
|
||||
gitlab_shell.import_repository(@project.repository_storage_path, @project.path_with_namespace, @path_to_bundle)
|
||||
rescue => e
|
||||
@shared.error(e)
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def path_to_repo
|
||||
@project.repository.path_to_repo
|
||||
end
|
||||
|
||||
def repo_restore_hooks
|
||||
return true if wiki?
|
||||
|
||||
git_restore_hooks
|
||||
end
|
||||
|
||||
def wiki?
|
||||
@project.class.name == 'ProjectWiki'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -80,8 +80,10 @@ module Gitlab
|
|||
# import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://github.com/randx/six.git")
|
||||
#
|
||||
def import_repository(storage, name, url)
|
||||
# Timeout should be less than 900 ideally, to prevent the memory killer
|
||||
# to silently kill the process without knowing we are timing out here.
|
||||
output, status = Popen::popen([gitlab_shell_projects_path, 'import-project',
|
||||
storage, "#{name}.git", url, '900'])
|
||||
storage, "#{name}.git", url, '800'])
|
||||
raise Error, output unless status.zero?
|
||||
true
|
||||
end
|
||||
|
|
|
@ -31,11 +31,11 @@ module Gitlab
|
|||
private
|
||||
|
||||
def snippet_titles
|
||||
limit_snippets.search(query).order('updated_at DESC')
|
||||
limit_snippets.search(query).order('updated_at DESC').includes(:author)
|
||||
end
|
||||
|
||||
def snippet_blobs
|
||||
limit_snippets.search_code(query).order('updated_at DESC')
|
||||
limit_snippets.search_code(query).order('updated_at DESC').includes(:author)
|
||||
end
|
||||
|
||||
def default_scope
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"jquery-ui": "github:jquery/jquery-ui#1.11.4",
|
||||
"jquery-ujs": "1.2.1",
|
||||
"js-cookie": "^2.1.3",
|
||||
"karma-mocha-reporter": "^2.2.2",
|
||||
"mousetrap": "1.4.6",
|
||||
"pikaday": "^1.5.1",
|
||||
"select2": "3.5.2-browserify",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1143,15 +1143,15 @@ describe Projects::MergeRequestsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when no special status for MR' do
|
||||
context 'when MR does not have special state' do
|
||||
let(:merge_request) { create(:merge_request, source_project: project) }
|
||||
|
||||
it 'returns an OK response' do
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'sets status to nil' do
|
||||
expect(assigns(:status)).to be_nil
|
||||
it 'sets status to success' do
|
||||
expect(assigns(:status)).to eq(:success)
|
||||
expect(response).to render_template('merge')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,6 +28,12 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
expect(page).to have_content('Welcome to your Issue Board!')
|
||||
end
|
||||
|
||||
it 'disables add issues button by default' do
|
||||
button = page.find('.issue-boards-search button', text: 'Add issues')
|
||||
|
||||
expect(button[:disabled]).to eq true
|
||||
end
|
||||
|
||||
it 'hides the blank state when clicking nevermind button' do
|
||||
page.within(find('.board-blank-state')) do
|
||||
click_button("Nevermind, I'll use my own")
|
||||
|
|
|
@ -52,4 +52,19 @@ describe 'Merge request', :feature, :js do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'merge error' do
|
||||
before do
|
||||
allow_any_instance_of(Repository).to receive(:merge).and_return(false)
|
||||
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
|
||||
click_button 'Accept Merge Request'
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'updates the MR widget' do
|
||||
page.within('.mr-widget-body') do
|
||||
expect(page).to have_content('Conflicts detected during merge')
|
||||
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')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue