Commit d38949ee authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'ce/master' into ce-to-ee-2017-11-22

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents b49fab30 d22c8857
Remove this section and replace it with a description of your MR. Also follow the Add a description of your merge request here. Merge requests without an adequate
checklist below and check off any tasks that are done. If a certain task can not description will not be reviewed until one is added.
be done you should explain so in the MR body. You are free to remove any
sections that do not apply to your MR.
When gathering statistics (e.g. the output of `EXPLAIN ANALYZE`) you should make
sure your database has enough data. Having around 10 000 rows in the tables
being queries should provide a reasonable estimate of how a query will behave.
Also make sure that PostgreSQL uses the following settings:
* `random_page_cost`: `1`
* `work_mem`: `16MB`
* `maintenance_work_mem`: at least `64MB`
* `shared_buffers`: at least `256MB`
If you have access to GitLab.com's staging environment you should also run your
measurements there, and include the results in this MR.
## Database Checklist ## Database Checklist
...@@ -23,34 +8,23 @@ When adding migrations: ...@@ -23,34 +8,23 @@ When adding migrations:
- [ ] Updated `db/schema.rb` - [ ] Updated `db/schema.rb`
- [ ] Added a `down` method so the migration can be reverted - [ ] Added a `down` method so the migration can be reverted
- [ ] Added the output of the migration(s) to the MR body - [ ] Added the output of the migration(s) to the MR body
- [ ] Added the execution time of the migration(s) to the MR body - [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when migrating data)
- [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when
migrating data)
- [ ] Made sure the migration won't interfere with a running GitLab cluster,
for example by disabling transactions for long running migrations
When adding or modifying queries to improve performance: When adding or modifying queries to improve performance:
- [ ] Included the raw SQL queries of the relevant queries - [ ] Included data that shows the performance improvement, preferably in the form of a benchmark
- [ ] Included the output of `EXPLAIN ANALYZE` and execution timings of the - [ ] Included the output of `EXPLAIN (ANALYZE, BUFFERS)` of the relevant queries
relevant queries
- [ ] Added tests for the relevant changes
When adding indexes:
- [ ] Described the need for these indexes in the MR body
- [ ] Made sure existing indexes can not be reused instead
When adding foreign keys to existing tables: When adding foreign keys to existing tables:
- [ ] Included a migration to remove orphaned rows in the source table - [ ] Included a migration to remove orphaned rows in the source table before adding the foreign key
- [ ] Removed any instances of `dependent: ...` that may no longer be necessary - [ ] Removed any instances of `dependent: ...` that may no longer be necessary
When adding tables: When adding tables:
- [ ] Ordered columns based on their type sizes in descending order - [ ] Ordered columns based on the [Ordering Table Columns](https://docs.gitlab.com/ee/development/ordering_table_columns.html#ordering-table-columns) guidelines
- [ ] Added foreign keys if necessary - [ ] Added foreign keys to any columns pointing to data in other tables
- [ ] Added indexes if necessary - [ ] Added indexes for fields that are used in statements such as WHERE, ORDER BY, GROUP BY, and JOINs
When removing columns, tables, indexes or other structures: When removing columns, tables, indexes or other structures:
...@@ -64,8 +38,6 @@ When removing columns, tables, indexes or other structures: ...@@ -64,8 +38,6 @@ When removing columns, tables, indexes or other structures:
- [ ] API support added - [ ] API support added
- [ ] Tests added for this feature/bug - [ ] Tests added for this feature/bug
- Review - Review
- [ ] Has been reviewed by UX
- [ ] Has been reviewed by Frontend
- [ ] Has been reviewed by Backend - [ ] Has been reviewed by Backend
- [ ] Has been reviewed by Database - [ ] Has been reviewed by Database
- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html) - [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
......
<script>
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
export default {
props: {
isLoading: {
type: Boolean,
required: true,
},
environments: {
type: Array,
required: true,
},
pagination: {
type: Object,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
},
components: {
environmentTable,
loadingIcon,
tablePagination,
},
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
},
},
};
</script>
<template>
<div class="environments-container">
<loading-icon
label="Loading environments"
v-if="isLoading"
size="3"
/>
<slot name="emptyState"></slot>
<div
class="table-holder"
v-if="!isLoading && environments.length > 0">
<environment-table
:environments="environments"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
/>
<table-pagination
v-if="pagination && pagination.totalPages > 1"
:change="onChangePage"
:pageInfo="pagination"
/>
</div>
</div>
</template>
<script>
export default {
name: 'environmentsEmptyState',
props: {
newPath: {
type: String,
required: true,
},
canCreateEnvironment: {
type: Boolean,
required: true,
},
helpPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="blank-state-row">
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">
{{s__("Environments|You don't have any environments right now.")}}
</h2>
<p class="blank-state-text">
{{s__("Environments|Environments are places where code gets deployed, such as staging or production.")}}
<br />
<a :href="helpPath">
{{s__("Environments|Read more about environments")}}
</a>
</p>
<a
v-if="canCreateEnvironment"
:href="newPath"
class="btn btn-create js-new-environment-button">
{{s__("Environments|New environment")}}
</a>
</div>
</div>
</template>
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import { s__ } from '../../locale';
/** /**
* Renders the external url link in environments table. * Renders the external url link in environments table.
...@@ -18,7 +19,7 @@ export default { ...@@ -18,7 +19,7 @@ export default {
computed: { computed: {
title() { title() {
return 'Open'; return s__('Environments|Open');
}, },
}, },
}; };
......
...@@ -430,7 +430,7 @@ export default { ...@@ -430,7 +430,7 @@ export default {
v-if="!model.isFolder" v-if="!model.isFolder"
class="table-mobile-header" class="table-mobile-header"
role="rowheader"> role="rowheader">
Environment {{s__("Environments|Environment")}}
</div> </div>
<span <span
class="deploy-board-icon" class="deploy-board-icon"
...@@ -520,7 +520,7 @@ export default { ...@@ -520,7 +520,7 @@ export default {
<div <div
role="rowheader" role="rowheader"
class="table-mobile-header"> class="table-mobile-header">
Commit {{s__("Environments|Commit")}}
</div> </div>
<div <div
v-if="hasLastDeploymentKey" v-if="hasLastDeploymentKey"
...@@ -536,7 +536,7 @@ export default { ...@@ -536,7 +536,7 @@ export default {
<div <div
v-if="!hasLastDeploymentKey" v-if="!hasLastDeploymentKey"
class="commit-title table-mobile-content"> class="commit-title table-mobile-content">
No deployments yet {{s__("Environments|No deployments yet")}}
</div> </div>
</div> </div>
...@@ -546,7 +546,7 @@ export default { ...@@ -546,7 +546,7 @@ export default {
<div <div
role="rowheader" role="rowheader"
class="table-mobile-header"> class="table-mobile-header">
Updated {{s__("Environments|Updated")}}
</div> </div>
<span <span
v-if="canShowDate" v-if="canShowDate"
......
...@@ -34,6 +34,7 @@ export default { ...@@ -34,6 +34,7 @@ export default {
:aria-label="title"> :aria-label="title">
<i <i
class="fa fa-area-chart" class="fa fa-area-chart"
aria-hidden="true" /> aria-hidden="true"
/>
</a> </a>
</template> </template>
...@@ -48,10 +48,10 @@ export default { ...@@ -48,10 +48,10 @@ export default {
:disabled="isLoading"> :disabled="isLoading">
<span v-if="isLastDeployment"> <span v-if="isLastDeployment">
Re-deploy {{s__("Environments|Re-deploy")}}
</span> </span>
<span v-else> <span v-else>
Rollback {{s__("Environments|Rollback")}}
</span> </span>
<loading-icon v-if="isLoading" /> <loading-icon v-if="isLoading" />
......
<script>
import Flash from '../../flash';
import { s__ } from '../../locale';
import emptyState from './empty_state.vue';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
props: {
endpoint: {
type: String,
required: true,
},
canCreateEnvironment: {
type: Boolean,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
cssContainerClass: {
type: String,
required: true,
},
newEnvironmentPath: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
},
components: {
emptyState,
},
mixins: [
CIPaginationMixin,
environmentsMixin,
],
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
},
beforeDestroy() {
eventHub.$off('toggleFolder');
},
methods: {
toggleFolder(folder) {
this.store.toggleFolder(folder);
if (!folder.isOpen) {
this.fetchChildEnvironments(folder, true);
}
},
fetchChildEnvironments(folder, showLoader = false) {
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folder.folder_path)
.then(resp => resp.json())
.then(response => this.store.setfolderContent(folder, response.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
Flash(s__('Environments|An error occurred while fetching the environments.'));
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
},
successCallback(resp) {
this.saveData(resp);
// We need to verify if any folder is open to also update it
const openFolders = this.store.getOpenFolders();
if (openFolders.length) {
openFolders.forEach(folder => this.fetchChildEnvironments(folder));
}
},
},
};
</script>
<template>
<div :class="cssContainerClass">
<div class="top-area">
<tabs
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="environments"
/>
<div
v-if="canCreateEnvironment && !isLoading"
class="nav-controls">
<a
:href="newEnvironmentPath"
class="btn btn-create">
{{s__("Environments|New environment")}}
</a>
</div>
</div>
<container
:is-loading="isLoading"
:environments="state.environments"
:pagination="state.paginationInformation"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
>
<empty-state
slot="emptyState"
v-if="!isLoading && state.environments.length === 0"
:new-path="newEnvironmentPath"
:help-path="helpPagePath"
:can-create-environment="canCreateEnvironment"
/>
</container>
</div>
</template>
...@@ -2,14 +2,22 @@ ...@@ -2,14 +2,22 @@
/** /**
* Render environments table. * Render environments table.
*/ */
<<<<<<< HEAD
import EnvironmentTableRowComponent from './environment_item.vue'; import EnvironmentTableRowComponent from './environment_item.vue';
import DeployBoard from './deploy_board_component.vue'; import DeployBoard from './deploy_board_component.vue';
=======
import environmentItem from './environment_item.vue';
>>>>>>> ce/master
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
components: { components: {
<<<<<<< HEAD
'environment-item': EnvironmentTableRowComponent, 'environment-item': EnvironmentTableRowComponent,
DeployBoard, DeployBoard,
=======
environmentItem,
>>>>>>> ce/master
loadingIcon, loadingIcon,
}, },
...@@ -44,19 +52,19 @@ export default { ...@@ -44,19 +52,19 @@ export default {
<div class="ci-table" role="grid"> <div class="ci-table" role="grid">
<div class="gl-responsive-table-row table-row-header" role="row"> <div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-10 environments-name" role="columnheader"> <div class="table-section section-10 environments-name" role="columnheader">
Environment {{s__("Environments|Environment")}}
</div> </div>
<div class="table-section section-10 environments-deploy" role="columnheader"> <div class="table-section section-10 environments-deploy" role="columnheader">
Deployment {{s__("Environments|Deployment")}}
</div> </div>
<div class="table-section section-15 environments-build" role="columnheader"> <div class="table-section section-15 environments-build" role="columnheader">
Job {{s__("Environments|Job")}}
</div> </div>
<div class="table-section section-25 environments-commit" role="columnheader"> <div class="table-section section-25 environments-commit" role="columnheader">
Commit {{s__("Environments|Commit")}}
</div> </div>
<div class="table-section section-10 environments-date" role="columnheader"> <div class="table-section section-10 environments-date" role="columnheader">
Updated {{s__("Environments|Updated")}}
</div> </div>
</div> </div>
<template <template
...@@ -98,7 +106,7 @@ export default { ...@@ -98,7 +106,7 @@ export default {
<a <a
:href="folderUrl(model)" :href="folderUrl(model)"
class="btn btn-default"> class="btn btn-default">
Show all {{s__("Environments|Show all")}}
</a> </a>
</div> </div>
</div> </div>
......
import Vue from 'vue'; import Vue from 'vue';
import EnvironmentsComponent from './components/environment.vue'; import environmentsComponent from './components/environments_app.vue';
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#environments-list-view', el: '#environments-list-view',
components: { components: {
'environments-table-app': EnvironmentsComponent, environmentsComponent,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
endpoint: environmentsData.environmentsDataEndpoint,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
cssContainerClass: environmentsData.cssClass,
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-component', {
props: {
endpoint: this.endpoint,
newEnvironmentPath: this.newEnvironmentPath,
helpPagePath: this.helpPagePath,
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
}, },
render: createElement => createElement('environments-table-app'),
})); }));
import Vue from 'vue'; import Vue from 'vue';
import EnvironmentsFolderComponent from './environments_folder_view.vue'; import environmentsFolderApp from './environments_folder_view.vue';
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
Vue.use(Translate);
<<<<<<< HEAD
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
...@@ -11,3 +16,33 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -11,3 +16,33 @@ document.addEventListener('DOMContentLoaded', () => {
render: createElement => createElement('environments-folder-app'), render: createElement => createElement('environments-folder-app'),
}); });
}); });
=======
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#environments-folder-list-view',
components: {
environmentsFolderApp,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
endpoint: environmentsData.endpoint,
folderName: environmentsData.folderName,
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-folder-app', {
props: {
endpoint: this.endpoint,
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
},
}));
>>>>>>> ce/master
<script> <script>
<<<<<<< HEAD
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../../flash'; import Flash from '../../flash';
import EnvironmentsService from '../services/environments_service'; import EnvironmentsService from '../services/environments_service';
...@@ -162,19 +163,44 @@ export default { ...@@ -162,19 +163,44 @@ export default {
this.isLoading = false; this.isLoading = false;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.'); new Flash('An error occurred while fetching the environments.');
=======
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
props: {
endpoint: {
type: String,
required: true,
},
folderName: {
type: String,
required: true,
},
cssContainerClass: {
type: String,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
>>>>>>> ce/master
}, },
mixins: [
postAction(endpoint) { environmentsMixin,
if (!this.isMakingRequest) { CIPaginationMixin,
this.isLoading = true; ],
methods: {
this.service.postAction(endpoint) successCallback(resp) {
.then(() => this.fetchEnvironments()) this.saveData(resp);
.catch(() => new Flash('An error occurred while making the request.')); },
}
}, },
}, };
};
</script> </script>
<template> <template>
<div :class="cssContainerClass"> <div :class="cssContainerClass">
...@@ -183,9 +209,10 @@ export default { ...@@ -183,9 +209,10 @@ export default {
v-if="!isLoading"> v-if="!isLoading">
<h4 class="js-folder-name environments-folder-name"> <h4 class="js-folder-name environments-folder-name">
Environments / <b>{{folderName}}</b> {{s__("Environments|Environments")}} / <b>{{folderName}}</b>
</h4> </h4>
<<<<<<< HEAD
<ul class="nav-links"> <ul class="nav-links">
<li :class="{ 'active': scope === null || scope === 'available' }"> <li :class="{ 'active': scope === null || scope === 'available' }">
<a <a
...@@ -236,6 +263,22 @@ export default { ...@@ -236,6 +263,22 @@ export default {
:change="changePage" :change="changePage"
:pageInfo="state.paginationInformation"/> :pageInfo="state.paginationInformation"/>
</div> </div>
=======
<tabs
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="environments"
/>
>>>>>>> ce/master
</div> </div>
<container
:is-loading="isLoading"
:environments="state.environments"
:pagination="state.paginationInformation"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
/>
</div> </div>
</template> </template>
/**
* Common code between environmets app and folder view
*/
import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll';
import {
getParameterByName,
parseQueryStringIntoObject,
} from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
import EnvironmentsStore from '../stores/environments_store';
import EnvironmentsService from '../services/environments_service';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
import tabs from '../../vue_shared/components/navigation_tabs.vue';
import container from '../components/container.vue';
export default { export default {
components: {
environmentTable,
container,
loadingIcon,
tabs,
tablePagination,
},
data() {
const store = new EnvironmentsStore();
return {
store,
state: store.state,
isLoading: false,
isMakingRequest: false,
scope: getParameterByName('scope') || 'available',
page: getParameterByName('page') || '1',
requestData: {},
};
},
methods: { methods: {
saveData(resp) { saveData(resp) {
const headers = resp.headers; const headers = resp.headers;
return resp.json().then((response) => { return resp.json().then((response) => {
this.isLoading = false; this.isLoading = false;
this.store.storeAvailableCount(response.available_count); if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) {
this.store.storeStoppedCount(response.stopped_count); this.store.storeAvailableCount(response.available_count);
this.store.storeEnvironments(response.environments); this.store.storeStoppedCount(response.stopped_count);
this.store.setPagination(headers); this.store.storeEnvironments(response.environments);
this.store.setPagination(headers);
}
}); });
}, },
/**
* Handles URL and query parameter changes.
* When the user uses the pagination or the tabs,
* - update URL
* - Make API request to the server with new parameters
* - Update the polling function
* - Update the internal state
*/
updateContent(parameters) {
this.updateInternalState(parameters);
// fetch new data
return this.service.get(this.requestData)
.then(response => this.successCallback(response))
.then(() => {
// restart polling
this.poll.restart({ data: this.requestData });
})
.catch(() => {
this.errorCallback();
// restart polling
this.poll.restart();
});
},
errorCallback() {
this.isLoading = false;
Flash(s__('Environments|An error occurred while fetching the environments.'));
},
postAction(endpoint) {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => {
this.isLoading = false;
Flash(s__('Environments|An error occurred while making the request.'));
});
}
},
fetchEnvironments() {
this.isLoading = true;
return this.service.get(this.requestData)
.then(this.successCallback)
.catch(this.errorCallback);
},
},
computed: {
tabs() {
return [
{
name: s__('Available'),
scope: 'available',
count: this.state.availableCounter,
isActive: this.scope === 'available',
},
{
name: s__('Stopped'),
scope: 'stopped',
count: this.state.stoppedCounter,
isActive: this.scope === 'stopped',
},
];
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created() {
this.service = new EnvironmentsService(this.endpoint);
this.requestData = { page: this.page, scope: this.scope };
this.poll = new Poll({
resource: this.service,
method: 'get',
data: this.requestData,
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
},
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
} else {
this.fetchEnvironments();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('postAction', this.postAction);
},
beforeDestroyed() {
eventHub.$off('postAction');
}, },
}; };
...@@ -44,7 +44,12 @@ export default class EnvironmentsStore { ...@@ -44,7 +44,12 @@ export default class EnvironmentsStore {
storeEnvironments(environments = []) { storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => { const filteredEnvironments = environments.map((env) => {
const oldEnvironmentState = this.state.environments const oldEnvironmentState = this.state.environments
.find(element => element.id === env.latest.id) || {}; .find((element) => {
if (env.latest) {
return element.id === env.latest.id;
}
return element.id === env.id;
}) || {};
let filtered = {}; let filtered = {};
......
...@@ -4,9 +4,11 @@ import tooltip from '../../vue_shared/directives/tooltip'; ...@@ -4,9 +4,11 @@ import tooltip from '../../vue_shared/directives/tooltip';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue'; import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { COMMON_STR } from '../constants'; import { COMMON_STR } from '../constants';
import Icon from '../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
Icon,
PopupDialog, PopupDialog,
}, },
directives: { directives: {
...@@ -63,9 +65,9 @@ export default { ...@@ -63,9 +65,9 @@ export default {
:aria-label="editBtnTitle" :aria-label="editBtnTitle"
data-container="body" data-container="body"
class="edit-group btn no-expand"> class="edit-group btn no-expand">
<i <icon
class="fa fa-cogs" name="settings">
aria-hidden="true"/> </icon>
</a> </a>
<a <a
v-tooltip v-tooltip
......
...@@ -269,46 +269,6 @@ export const parseIntPagination = paginationInformation => ({ ...@@ -269,46 +269,6 @@ export const parseIntPagination = paginationInformation => ({
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10), previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
}); });
/**
* Updates the search parameter of a URL given the parameter and value provided.
*
* If no search params are present we'll add it.
* If param for page is already present, we'll update it
* If there are params but not for the given one, we'll add it at the end.
* Returns the new search parameters.
*
* @param {String} param
* @param {Number|String|Undefined|Null} value
* @return {String}
*/
export const setParamInURL = (param, value) => {
let search;
const locationSearch = window.location.search;
if (locationSearch.length) {
const parameters = locationSearch.substring(1, locationSearch.length)
.split('&')
.reduce((acc, element) => {
const val = element.split('=');
// eslint-disable-next-line no-param-reassign
acc[val[0]] = decodeURIComponent(val[1]);
return acc;
}, {});
parameters[param] = value;
const toString = Object.keys(parameters)
.map(val => `${val}=${encodeURIComponent(parameters[val])}`)
.join('&');
search = `?${toString}`;
} else {
search = `?${param}=${value}`;
}
return search;
};
/** /**
* Given a string of query parameters creates an object. * Given a string of query parameters creates an object.
* *
......
...@@ -71,8 +71,6 @@ import './project_import'; ...@@ -71,8 +71,6 @@ import './project_import';
import './projects_dropdown'; import './projects_dropdown';
import './projects_list'; import './projects_list';
import './syntax_highlight'; import './syntax_highlight';
import './render_math';
import './render_mermaid';
import './render_gfm'; import './render_gfm';
import './right_sidebar'; import './right_sidebar';
import './search'; import './search';
......
...@@ -3,15 +3,14 @@ ...@@ -3,15 +3,14 @@
import PipelinesService from '../services/pipelines_service'; import PipelinesService from '../services/pipelines_service';
import pipelinesMixin from '../mixins/pipelines'; import pipelinesMixin from '../mixins/pipelines';
import tablePagination from '../../vue_shared/components/table_pagination.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue';
import navigationTabs from './navigation_tabs.vue'; import navigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import navigationControls from './nav_controls.vue'; import navigationControls from './nav_controls.vue';
import { import {
convertPermissionToBoolean, convertPermissionToBoolean,
getParameterByName, getParameterByName,
historyPushState,
buildUrlWithCurrentLocation,
parseQueryStringIntoObject, parseQueryStringIntoObject,
} from '../../lib/utils/common_utils'; } from '../../lib/utils/common_utils';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default { export default {
props: { props: {
...@@ -36,6 +35,7 @@ ...@@ -36,6 +35,7 @@
}, },
mixins: [ mixins: [
pipelinesMixin, pipelinesMixin,
CIPaginationMixin,
], ],
data() { data() {
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
...@@ -170,22 +170,8 @@ ...@@ -170,22 +170,8 @@
* - Update the internal state * - Update the internal state
*/ */
updateContent(parameters) { updateContent(parameters) {
// stop polling this.updateInternalState(parameters);
this.poll.stop();
const queryString = Object.keys(parameters).map((parameter) => {
const value = parameters[parameter];
// update internal state for UI
this[parameter] = value;
return `${parameter}=${encodeURIComponent(value)}`;
}).join('&');
// update polling parameters
this.requestData = parameters;
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
this.isLoading = true;
// fetch new data // fetch new data
return this.service.getPipelines(this.requestData) return this.service.getPipelines(this.requestData)
.then((response) => { .then((response) => {
...@@ -203,14 +189,6 @@ ...@@ -203,14 +189,6 @@
this.poll.restart(); this.poll.restart();
}); });
}, },
onChangeTab(scope) {
this.updateContent({ scope, page: '1' });
},
onChangePage(page) {
/* URLS parameters are strings, we need to parse to match types */
this.updateContent({ scope: this.scope, page: Number(page).toString() });
},
}, },
}; };
</script> </script>
...@@ -235,6 +213,7 @@ ...@@ -235,6 +213,7 @@
<navigation-tabs <navigation-tabs
:tabs="tabs" :tabs="tabs"
@onChangeTab="onChangeTab" @onChangeTab="onChangeTab"
scope="pipelines"
/> />
<navigation-controls <navigation-controls
......
/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len */ import renderMath from './render_math';
import renderMermaid from './render_mermaid';
// Render Gitlab flavoured Markdown // Render Gitlab flavoured Markdown
// //
// Delegates to syntax highlight and render math & mermaid diagrams. // Delegates to syntax highlight and render math & mermaid diagrams.
// //
(function() { $.fn.renderGFM = function renderGFM() {
$.fn.renderGFM = function() { this.find('.js-syntax-highlight').syntaxHighlight();
this.find('.js-syntax-highlight').syntaxHighlight(); renderMath(this.find('.js-render-math'));
this.find('.js-render-math').renderMath(); renderMermaid(this.find('.js-render-mermaid'));
this.find('.js-render-mermaid').renderMermaid(); return this;
return this; };
};
$(() => $('body').renderGFM()); $(() => $('body').renderGFM());
}).call(window);
/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len, no-console */
/* global katex */ /* global katex */
// Renders math using KaTeX in any element with the // Renders math using KaTeX in any element with the
...@@ -8,49 +7,45 @@ ...@@ -8,49 +7,45 @@
// //
// <code class="js-render-math"></div> // <code class="js-render-math"></div>
// //
(function() {
// Only load once // Only load once
var katexLoaded = false; let katexLoaded = false;
// Loop over all math elements and render math // Loop over all math elements and render math
var renderWithKaTeX = function (elements) { function renderWithKaTeX(elements) {
elements.each(function () { elements.each(function katexElementsLoop() {
var mathNode = $('<span></span>'); const mathNode = $('<span></span>');
var $this = $(this); const $this = $(this);
var display = $this.attr('data-math-style') === 'display'; const display = $this.attr('data-math-style') === 'display';
try { try {
katex.render($this.text(), mathNode.get(0), { displayMode: display }); katex.render($this.text(), mathNode.get(0), { displayMode: display });
mathNode.insertAfter($this); mathNode.insertAfter($this);
$this.remove(); $this.remove();
} catch (err) { } catch (err) {
// What can we do?? throw err;
console.log(err.message); }
} });
}); }
};
$.fn.renderMath = function() { export default function renderMath($els) {
var $this = this; if (!$els.length) return;
if ($this.length === 0) return;
if (katexLoaded) renderWithKaTeX($this); if (katexLoaded) {
else { renderWithKaTeX($els);
// Request CSS file so it is in the cache } else {
$.get(gon.katex_css_url, function() { $.get(gon.katex_css_url, () => {
var css = $('<link>', const css = $('<link>', {
{ rel: 'stylesheet', rel: 'stylesheet',
type: 'text/css', type: 'text/css',
href: gon.katex_css_url, href: gon.katex_css_url,
}); });
css.appendTo('head'); css.appendTo('head');
// Load KaTeX js // Load KaTeX js
$.getScript(gon.katex_js_url, function() { $.getScript(gon.katex_js_url, () => {
katexLoaded = true; katexLoaded = true;
renderWithKaTeX($this); // Run KaTeX renderWithKaTeX($els); // Run KaTeX
});
}); });
} });
}; }
}).call(window); }
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
import Flash from './flash'; import Flash from './flash';
$.fn.renderMermaid = function renderMermaid() { export default function renderMermaid($els) {
if (this.length === 0) return; if (!$els.length) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => { import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
mermaid.initialize({ mermaid.initialize({
...@@ -23,8 +23,10 @@ $.fn.renderMermaid = function renderMermaid() { ...@@ -23,8 +23,10 @@ $.fn.renderMermaid = function renderMermaid() {
theme: 'neutral', theme: 'neutral',
}); });
mermaid.init(undefined, this); $els.each((i, el) => {
mermaid.init(undefined, el);
});
}).catch((err) => { }).catch((err) => {
Flash(`Can't load mermaid module: ${err}`); Flash(`Can't load mermaid module: ${err}`);
}); });
}; }
import Flash from './flash'; import Flash from './flash';
import { __, s__ } from './locale'; import { __, s__ } from './locale';
import { spriteIcon } from './lib/utils/common_utils';
export default class Star { export default class Star {
constructor() { constructor() {
...@@ -7,16 +8,18 @@ export default class Star { ...@@ -7,16 +8,18 @@ export default class Star {
.on('ajax:success', function handleSuccess(e, data) { .on('ajax:success', function handleSuccess(e, data) {
const $this = $(this); const $this = $(this);
const $starSpan = $this.find('span'); const $starSpan = $this.find('span');
const $starIcon = $this.find('i'); const $startIcon = $this.find('svg');
function toggleStar(isStarred) { function toggleStar(isStarred) {
$this.parent().find('.star-count').text(data.star_count); $this.parent().find('.star-count').text(data.star_count);
if (isStarred) { if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star')); $starSpan.removeClass('starred').text(s__('StarProject|Star'));
$starIcon.removeClass('fa-star').addClass('fa-star-o'); $startIcon.remove();
$this.prepend(spriteIcon('star-o'));
} else { } else {
$starSpan.addClass('starred').text(__('Unstar')); $starSpan.addClass('starred').text(__('Unstar'));
$starIcon.removeClass('fa-star-o').addClass('fa-star'); $startIcon.remove();
$this.prepend(spriteIcon('star'));
} }
} }
......
...@@ -14,7 +14,7 @@ export default { ...@@ -14,7 +14,7 @@ export default {
statusObj() { statusObj() {
return { return {
group: this.status, group: this.status,
icon: `icon_status_${this.status}`, icon: `status_${this.status}`,
}; };
}, },
}, },
......
<script> <script>
/**
* Given an array of tabs, renders non linked bootstrap tabs.
* When a tab is clicked it will trigger an event and provide the clicked scope.
*
* This component is used in apps that handle the API call.
* If you only need to change the URL this component should not be used.
*
* @example
* <navigation-tabs
* :tabs="[
* {
* name: String,
* scope: String,
* count: Number || Undefined,
* isActive: Boolean,
* },
* ]"
* @onChangeTab="onChangeTab"
* />
*/
export default { export default {
name: 'PipelineNavigationTabs', name: 'NavigationTabs',
props: { props: {
tabs: { tabs: {
type: Array, type: Array,
required: true, required: true,
}, },
scope: {
type: String,
required: false,
default: '',
},
}, },
mounted() { mounted() {
$(document).trigger('init.scrolling-tabs'); $(document).trigger('init.scrolling-tabs');
...@@ -34,7 +59,7 @@ ...@@ -34,7 +59,7 @@
<a <a
role="button" role="button"
@click="onTabClick(tab)" @click="onTabClick(tab)"
:class="`js-pipelines-tab-${tab.scope}`" :class="`js-${scope}-tab-${tab.scope}`"
> >
{{ tab.name }} {{ tab.name }}
......
/**
* API callbacks for pagination and tabs
* shared between Pipelines and Environments table.
*
* Components need to have `scope`, `page` and `requestData`
*/
import {
historyPushState,
buildUrlWithCurrentLocation,
} from '../../lib/utils/common_utils';
export default {
methods: {
onChangeTab(scope) {
this.updateContent({ scope, page: '1' });
},
onChangePage(page) {
/* URLS parameters are strings, we need to parse to match types */
this.updateContent({ scope: this.scope, page: Number(page).toString() });
},
updateInternalState(parameters) {
// stop polling
this.poll.stop();
const queryString = Object.keys(parameters).map((parameter) => {
const value = parameters[parameter];
// update internal state for UI
this[parameter] = value;
return `${parameter}=${encodeURIComponent(value)}`;
}).join('&');
// update polling parameters
this.requestData = parameters;
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
this.isLoading = true;
},
},
};
...@@ -364,6 +364,18 @@ span.idiff { ...@@ -364,6 +364,18 @@ span.idiff {
float: none; float: none;
} }
} }
@media (max-width: $screen-xs-max) {
display: block;
.file-actions {
white-space: normal;
.btn-group {
padding-top: 5px;
}
}
}
} }
.is-stl-loading { .is-stl-loading {
......
...@@ -204,7 +204,7 @@ class ApplicationController < ActionController::Base ...@@ -204,7 +204,7 @@ class ApplicationController < ActionController::Base
end end
def check_password_expiration def check_password_expiration
return if session[:impersonator_id] || current_user&.ldap_user? return if session[:impersonator_id] || !current_user&.allow_password_authentication?
password_expires_at = current_user&.password_expires_at password_expires_at = current_user&.password_expires_at
......
...@@ -152,7 +152,7 @@ module IssuableCollections ...@@ -152,7 +152,7 @@ module IssuableCollections
when 'MergeRequest' when 'MergeRequest'
[ [
:source_project, :target_project, :author, :assignee, :labels, :milestone, :source_project, :target_project, :author, :assignee, :labels, :milestone,
head_pipeline: :project, target_project: :namespace, merge_request_diff: :merge_request_diff_commits head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits
] ]
end end
end end
......
...@@ -8,6 +8,7 @@ module PreviewMarkdown ...@@ -8,6 +8,7 @@ module PreviewMarkdown
case controller_name case controller_name
when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] } when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] }
when 'snippets' then { skip_project_check: true } when 'snippets' then { skip_project_check: true }
when 'groups' then { group: group }
else {} else {}
end end
......
...@@ -51,7 +51,7 @@ class InvitesController < ApplicationController ...@@ -51,7 +51,7 @@ class InvitesController < ApplicationController
return if current_user return if current_user
notice = "To accept this invitation, sign in" notice = "To accept this invitation, sign in"
notice << " or create an account" if current_application_settings.signup_enabled? notice << " or create an account" if current_application_settings.allow_signup?
notice << "." notice << "."
store_location_for :user, request.fullpath store_location_for :user, request.fullpath
......
...@@ -154,7 +154,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -154,7 +154,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
label = Gitlab::OAuth::Provider.label_for(oauth['provider']) label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if current_application_settings.signup_enabled? if current_application_settings.allow_signup?
message << " Create a GitLab account first, and then connect it to your #{label} account." message << " Create a GitLab account first, and then connect it to your #{label} account."
end end
......
class PasswordsController < Devise::PasswordsController class PasswordsController < Devise::PasswordsController
include Gitlab::CurrentSettings
before_action :resource_from_email, only: [:create] before_action :resource_from_email, only: [:create]
before_action :prevent_ldap_reset, only: [:create] before_action :check_password_authentication_available, only: [:create]
before_action :throttle_reset, only: [:create] before_action :throttle_reset, only: [:create]
prepend EE::PasswordsController prepend EE::PasswordsController
...@@ -27,7 +29,7 @@ class PasswordsController < Devise::PasswordsController ...@@ -27,7 +29,7 @@ class PasswordsController < Devise::PasswordsController
def update def update
super do |resource| super do |resource|
if resource.valid? && resource.require_password_creation? if resource.valid? && resource.password_automatically_set?
resource.update_attribute(:password_automatically_set, false) resource.update_attribute(:password_automatically_set, false)
end end
end end
...@@ -40,11 +42,15 @@ class PasswordsController < Devise::PasswordsController ...@@ -40,11 +42,15 @@ class PasswordsController < Devise::PasswordsController
self.resource = resource_class.find_by_email(email) self.resource = resource_class.find_by_email(email)
end end
def prevent_ldap_reset def check_password_authentication_available
return unless resource&.ldap_user? if resource
return if resource.allow_password_authentication?
else
return if current_application_settings.password_authentication_enabled?
end
redirect_to after_sending_reset_password_instructions_path_for(resource_name), redirect_to after_sending_reset_password_instructions_path_for(resource_name),
alert: "Cannot reset password for LDAP user." alert: "Password authentication is unavailable."
end end
def throttle_reset def throttle_reset
......
...@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController ...@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
end end
def authorize_change_password! def authorize_change_password!
render_404 if @user.ldap_user? render_404 unless @user.allow_password_authentication?
end end
def user_params def user_params
......
...@@ -35,6 +35,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -35,6 +35,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
folder_environments = project.environments.where(environment_type: params[:id]) folder_environments = project.environments.where(environment_type: params[:id])
@environments = folder_environments.with_state(params[:scope] || :available) @environments = folder_environments.with_state(params[:scope] || :available)
.order(:name) .order(:name)
@folder = params[:id]
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -64,7 +64,7 @@ class SessionsController < Devise::SessionsController ...@@ -64,7 +64,7 @@ class SessionsController < Devise::SessionsController
user = User.admins.last user = User.admins.last
return unless user && user.require_password_creation? return unless user && user.require_password_creation_for_web?
Users::UpdateService.new(current_user, user: user).execute do |user| Users::UpdateService.new(current_user, user: user).execute do |user|
@token = user.generate_reset_token @token = user.generate_reset_token
......
# :nocov:
if Rails.env.test?
class UnicornTestController < ActionController::Base
def pid
render plain: Process.pid.to_s
end
def kill
Process.kill(params[:signal], Process.pid)
render plain: 'Bye!'
end
end
end
# :nocov:
...@@ -4,9 +4,9 @@ module ApplicationSettingsHelper ...@@ -4,9 +4,9 @@ module ApplicationSettingsHelper
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
delegate :gravatar_enabled?, delegate :allow_signup?,
:signup_enabled?, :gravatar_enabled?,
:password_authentication_enabled?, :password_authentication_enabled_for_web?,
:akismet_enabled?, :akismet_enabled?,
:koding_enabled?, :koding_enabled?,
to: :current_application_settings to: :current_application_settings
...@@ -204,7 +204,7 @@ module ApplicationSettingsHelper ...@@ -204,7 +204,7 @@ module ApplicationSettingsHelper
:metrics_port, :metrics_port,
:metrics_sample_interval, :metrics_sample_interval,
:metrics_timeout, :metrics_timeout,
:password_authentication_enabled, :password_authentication_enabled_for_web,
:performance_bar_allowed_group_id, :performance_bar_allowed_group_id,
:performance_bar_enabled, :performance_bar_enabled,
:plantuml_enabled, :plantuml_enabled,
......
...@@ -58,12 +58,12 @@ module ButtonHelper ...@@ -58,12 +58,12 @@ module ButtonHelper
def http_clone_button(project, placement = 'right', append_link: true) def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector' klass = 'http-selector'
klass << ' has-tooltip' if current_user.try(:require_password_creation?) || current_user.try(:require_personal_access_token_creation_for_git_auth?) klass << ' has-tooltip' if current_user.try(:require_extra_setup_for_git_auth?)
protocol = gitlab_config.protocol.upcase protocol = gitlab_config.protocol.upcase
tooltip_title = tooltip_title =
if current_user.try(:require_password_creation?) if current_user.try(:require_password_creation_for_git?)
_("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol } _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
else else
_("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol } _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
......
...@@ -236,11 +236,11 @@ module ProjectsHelper ...@@ -236,11 +236,11 @@ module ProjectsHelper
def show_no_password_message? def show_no_password_message?
cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
( current_user.require_password_creation? || current_user.require_personal_access_token_creation_for_git_auth? ) current_user.require_extra_setup_for_git_auth?
end end
def link_to_set_password def link_to_set_password
if current_user.require_password_creation? if current_user.require_password_creation_for_git?
link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
else else
link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
......
...@@ -289,7 +289,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -289,7 +289,8 @@ class ApplicationSetting < ActiveRecord::Base
koding_url: nil, koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'], max_attachment_size: Settings.gitlab['max_attachment_size'],
password_authentication_enabled: Settings.gitlab['password_authentication_enabled'], password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
password_authentication_enabled_for_git: true,
performance_bar_allowed_group_id: nil, performance_bar_allowed_group_id: nil,
rsa_key_restriction: 0, rsa_key_restriction: 0,
plantuml_enabled: false, plantuml_enabled: false,
...@@ -521,6 +522,14 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -521,6 +522,14 @@ class ApplicationSetting < ActiveRecord::Base
has_attribute?(attr_name) ? public_send(attr_name) : FORBIDDEN_KEY_VALUE # rubocop:disable GitlabSecurity/PublicSend has_attribute?(attr_name) ? public_send(attr_name) : FORBIDDEN_KEY_VALUE # rubocop:disable GitlabSecurity/PublicSend
end end
def allow_signup?
signup_enabled? && password_authentication_enabled_for_web?
end
def password_authentication_enabled?
password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
end
private private
def ensure_uuid! def ensure_uuid!
......
...@@ -247,7 +247,7 @@ module Ci ...@@ -247,7 +247,7 @@ module Ci
@merge_request ||= @merge_request ||=
begin begin
merge_requests = MergeRequest.includes(:merge_request_diff) merge_requests = MergeRequest.includes(:latest_merge_request_diff)
.where(source_branch: ref, .where(source_branch: ref,
source_project: pipeline.project) source_project: pipeline.project)
.reorder(iid: :desc) .reorder(iid: :desc)
......
...@@ -109,12 +109,12 @@ class Commit ...@@ -109,12 +109,12 @@ class Commit
@link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/) @link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/)
end end
def to_reference(from_project = nil, full: false) def to_reference(from = nil, full: false)
commit_reference(from_project, id, full: full) commit_reference(from, id, full: full)
end end
def reference_link_text(from_project = nil, full: false) def reference_link_text(from = nil, full: false)
commit_reference(from_project, short_id, full: full) commit_reference(from, short_id, full: full)
end end
def diff_line_count def diff_line_count
...@@ -381,8 +381,8 @@ class Commit ...@@ -381,8 +381,8 @@ class Commit
private private
def commit_reference(from_project, referable_commit_id, full: false) def commit_reference(from, referable_commit_id, full: false)
reference = project.to_reference(from_project, full: full) reference = project.to_reference(from, full: full)
if reference.present? if reference.present?
"#{reference}#{self.class.reference_prefix}#{referable_commit_id}" "#{reference}#{self.class.reference_prefix}#{referable_commit_id}"
......
...@@ -89,8 +89,8 @@ class CommitRange ...@@ -89,8 +89,8 @@ class CommitRange
alias_method :id, :to_s alias_method :id, :to_s
def to_reference(from_project = nil, full: false) def to_reference(from = nil, full: false)
project_reference = project.to_reference(from_project, full: full) project_reference = project.to_reference(from, full: full)
if project_reference.present? if project_reference.present?
project_reference + self.class.reference_prefix + self.id project_reference + self.class.reference_prefix + self.id
...@@ -99,8 +99,8 @@ class CommitRange ...@@ -99,8 +99,8 @@ class CommitRange
end end
end end
def reference_link_text(from_project = nil) def reference_link_text(from = nil)
project_reference = project.to_reference(from_project) project_reference = project.to_reference(from)
reference = ref_from + notation + ref_to reference = ref_from + notation + ref_to
if project_reference.present? if project_reference.present?
......
module ManualInverseAssociation
extend ActiveSupport::Concern
module ClassMethods
def manual_inverse_association(association, inverse)
define_method(association) do |*args|
super(*args).tap do |value|
next unless value
child_association = value.association(inverse)
child_association.set_inverse_instance(self)
child_association.target = self
end
end
end
end
end
...@@ -31,11 +31,11 @@ module Mentionable ...@@ -31,11 +31,11 @@ module Mentionable
# #
# By default this will be the class name and the result of calling # By default this will be the class name and the result of calling
# `to_reference` on the object. # `to_reference` on the object.
def gfm_reference(from_project = nil) def gfm_reference(from = nil)
# "MergeRequest" > "merge_request" > "Merge request" > "merge request" # "MergeRequest" > "merge_request" > "Merge request" > "merge request"
friendly_name = self.class.to_s.underscore.humanize.downcase friendly_name = self.class.to_s.underscore.humanize.downcase
"#{friendly_name} #{to_reference(from_project)}" "#{friendly_name} #{to_reference(from)}"
end end
# The GFM reference to this Mentionable, which shouldn't be included in its #references. # The GFM reference to this Mentionable, which shouldn't be included in its #references.
......
...@@ -7,7 +7,7 @@ module Referable ...@@ -7,7 +7,7 @@ module Referable
# Returns the String necessary to reference this object in Markdown # Returns the String necessary to reference this object in Markdown
# #
# from_project - Refering Project object # from - Referring parent object
# #
# This should be overridden by the including class. # This should be overridden by the including class.
# #
...@@ -17,12 +17,12 @@ module Referable ...@@ -17,12 +17,12 @@ module Referable
# Issue.last.to_reference(other_project) # => "cross-project#1" # Issue.last.to_reference(other_project) # => "cross-project#1"
# #
# Returns a String # Returns a String
def to_reference(_from_project = nil, full:) def to_reference(_from = nil, full:)
'' ''
end end
def reference_link_text(from_project = nil) def reference_link_text(from = nil)
to_reference(from_project) to_reference(from)
end end
included do included do
......
...@@ -38,11 +38,11 @@ class ExternalIssue ...@@ -38,11 +38,11 @@ class ExternalIssue
@project.id @project.id
end end
def to_reference(_from_project = nil, full: nil) def to_reference(_from = nil, full: nil)
id id
end end
def reference_link_text(from_project = nil) def reference_link_text(from = nil)
return "##{id}" if id =~ /^\d+$/ return "##{id}" if id =~ /^\d+$/
id id
......
...@@ -115,7 +115,7 @@ class Group < Namespace ...@@ -115,7 +115,7 @@ class Group < Namespace
end end
end end
def to_reference(_from_project = nil, full: nil) def to_reference(_from = nil, full: nil)
"#{self.class.reference_prefix}#{full_path}" "#{self.class.reference_prefix}#{full_path}"
end end
......
...@@ -168,12 +168,12 @@ class Label < ActiveRecord::Base ...@@ -168,12 +168,12 @@ class Label < ActiveRecord::Base
# #
# Returns a String # Returns a String
# #
def to_reference(from_project = nil, target_project: nil, format: :id, full: false) def to_reference(from = nil, target_project: nil, format: :id, full: false)
format_reference = label_format_reference(format) format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if from_project if from
"#{from_project.to_reference(target_project, full: full)}#{reference}" "#{from.to_reference(target_project, full: full)}#{reference}"
else else
reference reference
end end
......
...@@ -6,6 +6,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -6,6 +6,8 @@ class MergeRequest < ActiveRecord::Base
include Elastic::MergeRequestsSearch include Elastic::MergeRequestsSearch
include IgnorableColumn include IgnorableColumn
include TimeTrackable include TimeTrackable
include ManualInverseAssociation
include EachBatch
ignore_column :locked_at, ignore_column :locked_at,
:ref_fetched :ref_fetched
...@@ -18,9 +20,28 @@ class MergeRequest < ActiveRecord::Base ...@@ -18,9 +20,28 @@ class MergeRequest < ActiveRecord::Base
belongs_to :merge_user, class_name: "User" belongs_to :merge_user, class_name: "User"
has_many :merge_request_diffs has_many :merge_request_diffs
has_one :merge_request_diff, has_one :merge_request_diff,
-> { order('merge_request_diffs.id DESC') }, inverse_of: :merge_request -> { order('merge_request_diffs.id DESC') }, inverse_of: :merge_request
belongs_to :latest_merge_request_diff, class_name: 'MergeRequestDiff'
manual_inverse_association :latest_merge_request_diff, :merge_request
# This is the same as latest_merge_request_diff unless:
# 1. There are arguments - in which case we might be trying to force-reload.
# 2. This association is already loaded.
# 3. The latest diff does not exist.
#
# The second one in particular is important - MergeRequestDiff#merge_request
# is the inverse of MergeRequest#merge_request_diff, which means it may not be
# the latest diff, because we could have loaded any diff from this particular
# MR. If we haven't already loaded a diff, then it's fine to load the latest.
def merge_request_diff(*args)
fallback = latest_merge_request_diff if args.empty? && !association(:merge_request_diff).loaded?
fallback || super
end
belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline" belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
...@@ -172,6 +193,22 @@ class MergeRequest < ActiveRecord::Base ...@@ -172,6 +193,22 @@ class MergeRequest < ActiveRecord::Base
where("merge_requests.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection where("merge_requests.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end end
# This is used after project import, to reset the IDs to the correct
# values. It is not intended to be called without having already scoped the
# relation.
def self.set_latest_merge_request_diff_ids!
update = '
latest_merge_request_diff_id = (
SELECT MAX(id)
FROM merge_request_diffs
WHERE merge_requests.id = merge_request_diffs.merge_request_id
)'.squish
self.each_batch do |batch|
batch.update_all(update)
end
end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def self.work_in_progress?(title) def self.work_in_progress?(title)
......
...@@ -2,6 +2,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -2,6 +2,7 @@ class MergeRequestDiff < ActiveRecord::Base
include Sortable include Sortable
include Importable include Importable
include Gitlab::EncodingHelper include Gitlab::EncodingHelper
include ManualInverseAssociation
# Prevent store of diff if commits amount more then 500 # Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100 COMMITS_SAFE_SIZE = 100
...@@ -10,6 +11,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -10,6 +11,8 @@ class MergeRequestDiff < ActiveRecord::Base
VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze
belongs_to :merge_request belongs_to :merge_request
manual_inverse_association :merge_request, :merge_request_diff
has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) } has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) }
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) } has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
...@@ -194,7 +197,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -194,7 +197,7 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def latest? def latest?
self == merge_request.merge_request_diff self.id == merge_request.latest_merge_request_diff_id
end end
def compare_with(sha) def compare_with(sha)
......
...@@ -166,18 +166,18 @@ class Milestone < ActiveRecord::Base ...@@ -166,18 +166,18 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1" # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
# #
def to_reference(from_project = nil, format: :name, full: false) def to_reference(from = nil, format: :name, full: false)
format_reference = milestone_format_reference(format) format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if project if project
"#{project.to_reference(from_project, full: full)}#{reference}" "#{project.to_reference(from, full: full)}#{reference}"
else else
reference reference
end end
end end
def reference_link_text(from_project = nil) def reference_link_text(from = nil)
self.title self.title
end end
......
...@@ -761,10 +761,10 @@ class Project < ActiveRecord::Base ...@@ -761,10 +761,10 @@ class Project < ActiveRecord::Base
end end
end end
def to_human_reference(from_project = nil) def to_human_reference(from = nil)
if cross_namespace_reference?(from_project) if cross_namespace_reference?(from)
name_with_namespace name_with_namespace
elsif cross_project_reference?(from_project) elsif cross_project_reference?(from)
name name
end end
end end
......
...@@ -917,19 +917,13 @@ class Repository ...@@ -917,19 +917,13 @@ class Repository
end end
end end
def merged_to_root_ref?(branch_or_name, pre_loaded_merged_branches = nil) def merged_to_root_ref?(branch_or_name)
branch = Gitlab::Git::Branch.find(self, branch_or_name) branch = Gitlab::Git::Branch.find(self, branch_or_name)
if branch if branch
@root_ref_sha ||= commit(root_ref).sha @root_ref_sha ||= commit(root_ref).sha
same_head = branch.target == @root_ref_sha same_head = branch.target == @root_ref_sha
merged = merged = ancestor?(branch.target, @root_ref_sha)
if pre_loaded_merged_branches
pre_loaded_merged_branches.include?(branch.name)
else
ancestor?(branch.target, @root_ref_sha)
end
!same_head && merged !same_head && merged
else else
nil nil
......
...@@ -76,11 +76,11 @@ class Snippet < ActiveRecord::Base ...@@ -76,11 +76,11 @@ class Snippet < ActiveRecord::Base
@link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/) @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end end
def to_reference(from_project = nil, full: false) def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
if project.present? if project.present?
"#{project.to_reference(from_project, full: full)}#{reference}" "#{project.to_reference(from, full: full)}#{reference}"
else else
reference reference
end end
......
...@@ -456,7 +456,7 @@ class User < ActiveRecord::Base ...@@ -456,7 +456,7 @@ class User < ActiveRecord::Base
username username
end end
def to_reference(_from_project = nil, target_project: nil, full: nil) def to_reference(_from = nil, target_project: nil, full: nil)
"#{self.class.reference_prefix}#{username}" "#{self.class.reference_prefix}#{username}"
end end
...@@ -652,18 +652,34 @@ class User < ActiveRecord::Base ...@@ -652,18 +652,34 @@ class User < ActiveRecord::Base
count.zero? && Gitlab::ProtocolAccess.allowed?('ssh') count.zero? && Gitlab::ProtocolAccess.allowed?('ssh')
end end
def require_password_creation? def require_password_creation_for_web?
password_automatically_set? && allow_password_authentication? allow_password_authentication_for_web? && password_automatically_set?
end
def require_password_creation_for_git?
allow_password_authentication_for_git? && password_automatically_set?
end end
def require_personal_access_token_creation_for_git_auth? def require_personal_access_token_creation_for_git_auth?
return false if current_application_settings.password_authentication_enabled? || ldap_user? return false if allow_password_authentication_for_git? || ldap_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end end
def require_extra_setup_for_git_auth?
require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth?
end
def allow_password_authentication? def allow_password_authentication?
!ldap_user? && current_application_settings.password_authentication_enabled? allow_password_authentication_for_web? || allow_password_authentication_for_git?
end
def allow_password_authentication_for_web?
current_application_settings.password_authentication_enabled_for_web? && !ldap_user?
end
def allow_password_authentication_for_git?
current_application_settings.password_authentication_enabled_for_git? && !ldap_user?
end end
def can_change_username? def can_change_username?
......
...@@ -36,7 +36,7 @@ module MergeRequests ...@@ -36,7 +36,7 @@ module MergeRequests
# target branch manually # target branch manually
def close_merge_requests def close_merge_requests
commit_ids = @commits.map(&:id) commit_ids = @commits.map(&:id)
merge_requests = @project.merge_requests.preload(:merge_request_diff).opened.where(target_branch: @branch_name).to_a merge_requests = @project.merge_requests.preload(:latest_merge_request_diff).opened.where(target_branch: @branch_name).to_a
merge_requests = merge_requests.select(&:diff_head_commit) merge_requests = merge_requests.select(&:diff_head_commit)
merge_requests = merge_requests.select do |merge_request| merge_requests = merge_requests.select do |merge_request|
......
# The protected branches API still uses the `developers_can_push` and `developers_can_merge` # The branches#protect API still uses the `developers_can_push` and `developers_can_merge`
# flags for backward compatibility, and so performs translation between that format and the # flags for backward compatibility, and so performs translation between that format and the
# internal data model (separate access levels). The translation code is non-trivial, and so # internal data model (separate access levels). The translation code is non-trivial, and so
# lives in this service. # lives in this service.
module ProtectedBranches module ProtectedBranches
class ApiCreateService < BaseService class LegacyApiCreateService < BaseService
def execute def execute
push_access_level = push_access_level =
if params.delete(:developers_can_push) if params.delete(:developers_can_push)
......
# The protected branches API still uses the `developers_can_push` and `developers_can_merge` # The branches#protect API still uses the `developers_can_push` and `developers_can_merge`
# flags for backward compatibility, and so performs translation between that format and the # flags for backward compatibility, and so performs translation between that format and the
# internal data model (separate access levels). The translation code is non-trivial, and so # internal data model (separate access levels). The translation code is non-trivial, and so
# lives in this service. # lives in this service.
module ProtectedBranches module ProtectedBranches
class ApiUpdateService < BaseService class LegacyApiUpdateService < BaseService
def execute(protected_branch) def execute(protected_branch)
@developers_can_push = params.delete(:developers_can_push) @developers_can_push = params.delete(:developers_can_push)
@developers_can_merge = params.delete(:developers_can_merge) @developers_can_merge = params.delete(:developers_can_merge)
......
...@@ -35,7 +35,7 @@ module Users ...@@ -35,7 +35,7 @@ module Users
private private
def can_create_user? def can_create_user?
(current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin? (current_user.nil? && current_application_settings.allow_signup?) || current_user&.admin?
end end
# Allowed params for creating a user (admins only) # Allowed params for creating a user (admins only)
......
...@@ -190,9 +190,22 @@ ...@@ -190,9 +190,22 @@
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
= f.label :password_authentication_enabled do = f.label :password_authentication_enabled_for_web do
= f.check_box :password_authentication_enabled = f.check_box :password_authentication_enabled_for_web
Sign-in enabled Password authentication enabled for web interface
.help-block
When disabled, an external authentication provider must be used.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :password_authentication_enabled_for_git do
= f.check_box :password_authentication_enabled_for_git
Password authentication enabled for Git over HTTP(S)
.help-block
When disabled, a Personal Access Token
- if Gitlab::LDAP::Config.enabled?
or LDAP password
must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any? - if omniauth_enabled? && button_based_providers.any?
.form-group .form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
......
...@@ -48,10 +48,10 @@ ...@@ -48,10 +48,10 @@
.well-segment.admin-well.admin-well-features .well-segment.admin-well.admin-well-features
%h4 Features %h4 Features
- sign_up = "Sign up" - sign_up = "Sign up"
%p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") } %p{ "aria-label" => "#{sign_up}: status " + (allow_signup? ? "on" : "off") }
= sign_up = sign_up
%span.light.pull-right %span.light.pull-right
= boolean_to_icon signup_enabled? = boolean_to_icon allow_signup?
- ldap = "LDAP" - ldap = "LDAP"
%p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") } %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
= ldap = ldap
......
...@@ -6,15 +6,15 @@ ...@@ -6,15 +6,15 @@
- else - else
= render 'devise/shared/tabs_normal' = render 'devise/shared/tabs_normal'
.tab-content .tab-content
- if password_authentication_enabled? || ldap_enabled? || crowd_enabled? - if password_authentication_enabled_for_web? || ldap_enabled? || crowd_enabled?
= render 'devise/shared/signin_box' = render 'devise/shared/signin_box'
-# Signup only makes sense if you can also sign-in -# Signup only makes sense if you can also sign-in
- if password_authentication_enabled? && signup_enabled? - if allow_signup?
= render 'devise/shared/signup_box' = render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled -# Show a message if none of the mechanisms above are enabled
- if !password_authentication_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - if !password_authentication_enabled_for_web? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
%div %div
No authentication methods configured. No authentication methods configured.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br /> <%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br />
<% end -%> <% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' && gitlab_config.signup_enabled %> <%- if devise_mapping.registerable? && controller_name != 'registrations' && allow_signup? %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br /> <%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end -%> <% end -%>
......
...@@ -13,12 +13,12 @@ ...@@ -13,12 +13,12 @@
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) } .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) }
.login-body .login-body
= render 'devise/sessions/new_ldap', server: server = render 'devise/sessions/new_ldap', server: server
- if password_authentication_enabled? - if password_authentication_enabled_for_web?
.login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' } .login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' }
.login-body .login-body
= render 'devise/sessions/new_base' = render 'devise/sessions/new_base'
- elsif password_authentication_enabled? - elsif password_authentication_enabled_for_web?
.login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' } .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
.login-body .login-body
= render 'devise/sessions/new_base' = render 'devise/sessions/new_base'
...@@ -8,9 +8,9 @@ ...@@ -8,9 +8,9 @@
- @ldap_servers.each_with_index do |server, i| - @ldap_servers.each_with_index do |server, i|
%li{ class: active_when(i.zero? && !crowd_enabled?) } %li{ class: active_when(i.zero? && !crowd_enabled?) }
= link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab' = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab'
- if password_authentication_enabled? - if password_authentication_enabled_for_web?
%li %li
= link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab' = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
- if password_authentication_enabled? && signup_enabled? - if allow_signup?
%li %li
= link_to 'Register', '#register-pane', 'data-toggle' => 'tab' = link_to 'Register', '#register-pane', 'data-toggle' => 'tab'
%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' } %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' } %li.active{ role: 'presentation' }
%a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
- if password_authentication_enabled? && signup_enabled? - if allow_signup?
%li{ role: 'presentation' } %li{ role: 'presentation' }
%a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; } img { -ms-interpolation-mode: bicubic; }
.hidden {
display: none !important;
visibility: hidden !important;
}
/* iOS BLUE LINKS */ /* iOS BLUE LINKS */
a[x-apple-data-detectors] { a[x-apple-data-detectors] {
......
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
= link_to profile_emails_path do = link_to profile_emails_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Emails') } #{ _('Emails') }
- unless current_user.ldap_user? - if current_user.allow_password_authentication?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= link_to edit_profile_password_path do = link_to edit_profile_password_path do
.nav-icon-container .nav-icon-container
......
%p %p
Hi #{@user['name']}! Hi #{@user['name']}!
%p %p
- if Gitlab.config.gitlab.signup_enabled - if current_application_settings.allow_signup?
Your account has been created successfully. Your account has been created successfully.
- else - else
The Administrator created an account for you. Now you are a member of the company GitLab application. The Administrator created an account for you. Now you are a member of the company GitLab application.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.js-file-title.file-title-flex-parent .js-file-title.file-title-flex-parent
= render 'projects/blob/header_content', blob: blob = render 'projects/blob/header_content', blob: blob
.file-actions.hidden-xs .file-actions
= render 'projects/blob/viewer_switcher', blob: blob unless blame = render 'projects/blob/viewer_switcher', blob: blob unless blame
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
- if @branches.any? - if @branches.any?
%ul.content-list.all-branches %ul.content-list.all-branches
- @branches.each do |branch| - @branches.each do |branch|
= render "projects/branches/branch", branch: branch, merged: @repository.merged_to_root_ref?(branch, @merged_branch_names) = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name)
= paginate @branches, theme: 'gitlab' = paginate @branches, theme: 'gitlab'
- else - else
.nothing-here-block .nothing-here-block
......
- if current_user - if current_user
= link_to toggle_star_project_path(@project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do = link_to toggle_star_project_path(@project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
- if current_user.starred?(@project) - if current_user.starred?(@project)
= icon('star') = sprite_icon('star')
%span.starred= _('Unstar') %span.starred= _('Unstar')
- else - else
= icon('star-o') = sprite_icon('star-o')
%span= s_('StarProject|Star') %span= s_('StarProject|Star')
.count-with-arrow .count-with-arrow
%span.arrow %span.arrow
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
- else - else
= link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: _('You must sign in to star a project') do = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: _('You must sign in to star a project') do
= icon('star') = sprite_icon('star')
#{ s_('StarProject|Star') } #{ s_('StarProject|Star') }
.count-with-arrow .count-with-arrow
%span.arrow %span.arrow
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag("environments_folder") = page_specific_javascript_bundle_tag("environments_folder")
#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, #environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json),
"folder-name" => @folder,
"can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
"can-read-environment" => can?(current_user, :read_environment, @project).to_s, "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"css-class" => container_class } } "css-class" => container_class } }
...@@ -3,15 +3,13 @@ ...@@ -3,15 +3,13 @@
- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag("common_vue")
= page_specific_javascript_bundle_tag("environments") = page_specific_javascript_bundle_tag("environments")
#environments-list-view{ data: { environments_data: environments_list_data, #environments-list-view{ data: { environments_data: environments_list_data,
"can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
"can-read-environment" => can?(current_user, :read_environment, @project).to_s, "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"can-create-environment" => can?(current_user, :create_environment, @project).to_s, "can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"project-environments-path" => project_environments_path(@project),
"project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
"new-environment-path" => new_project_environment_path(@project), "new-environment-path" => new_project_environment_path(@project),
"help-page-path" => help_page_path("ci/environments"), "help-page-path" => help_page_path("ci/environments"),
"css-class" => container_class } } "css-class" => container_class } }
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
.controls.hidden-xs .controls.hidden-xs
- if can?(current_user, :admin_group, group) - if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn" do = link_to edit_group_path(group), class: "btn" do
= icon('cogs') = sprite_icon('settings')
= link_to leave_group_group_members_path(group), data: { confirm: leave_confirmation_message(group) }, method: :delete, class: "btn", title: s_("GroupsTree|Leave this group") do = link_to leave_group_group_members_path(group), data: { confirm: leave_confirmation_message(group) }, method: :delete, class: "btn", title: s_("GroupsTree|Leave this group") do
= icon('sign-out') = icon('sign-out')
......
...@@ -9,7 +9,7 @@ class PipelineScheduleWorker ...@@ -9,7 +9,7 @@ class PipelineScheduleWorker
pipeline = Ci::CreatePipelineService.new(schedule.project, pipeline = Ci::CreatePipelineService.new(schedule.project,
schedule.owner, schedule.owner,
ref: schedule.ref) ref: schedule.ref)
.execute(:schedule, save_on_errors: false, schedule: schedule) .execute(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)
schedule.deactivate! unless pipeline.persisted? schedule.deactivate! unless pipeline.persisted?
rescue => e rescue => e
......
---
title: Add edit button to mobile file view
merge_request: 15199
author: Travis Miller
type: added
---
title: Allow password authentication to be disabled entirely
merge_request: 15223
author: Markus Koller
type: changed
---
title: Avoid deactivation when pipeline schedules execute a branch includes `[ci skip]`
comment
merge_request: 15405
author:
type: fixed
---
title: Fix link text from group context
merge_request:
author:
type: fixed
---
title: Make finding most recent merge request diffs more efficient
merge_request:
author:
type: performance
...@@ -287,7 +287,7 @@ rescue ArgumentError # no user configured ...@@ -287,7 +287,7 @@ rescue ArgumentError # no user configured
end end
Settings.gitlab['time_zone'] ||= nil Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil? Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['password_authentication_enabled'] ||= true if Settings.gitlab['password_authentication_enabled'].nil? Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
......
...@@ -115,7 +115,5 @@ Rails.application.routes.draw do ...@@ -115,7 +115,5 @@ Rails.application.routes.draw do
root to: "root#index" root to: "root#index"
draw :test if Rails.env.test?
get '*unmatched_route', to: 'application#route_not_found' get '*unmatched_route', to: 'application#route_not_found'
end end
get '/unicorn_test/pid' => 'unicorn_test#pid'
post '/unicorn_test/kill' => 'unicorn_test#kill'
class RenameApplicationSettingsPasswordAuthenticationEnabledToPasswordAuthenticationEnabledForWeb < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
rename_column_concurrently :application_settings, :password_authentication_enabled, :password_authentication_enabled_for_web
end
def down
cleanup_concurrent_column_rename :application_settings, :password_authentication_enabled_for_web, :password_authentication_enabled
end
end
class AddPasswordAuthenticationEnabledForGitToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :application_settings, :password_authentication_enabled_for_git, :boolean, default: true, null: false
end
end
# This is identical to the stolen background migration, which already has specs.
class PopulateMergeRequestsLatestMergeRequestDiffIdTakeTwo < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 1_000
class MergeRequest < ActiveRecord::Base
self.table_name = 'merge_requests'
include ::EachBatch
end
disable_ddl_transaction!
def up
Gitlab::BackgroundMigration.steal('PopulateMergeRequestsLatestMergeRequestDiffId')
update = '
latest_merge_request_diff_id = (
SELECT MAX(id)
FROM merge_request_diffs
WHERE merge_requests.id = merge_request_diffs.merge_request_id
)'.squish
MergeRequest.where(latest_merge_request_diff_id: nil).each_batch(of: BATCH_SIZE) do |relation|
relation.update_all(update)
end
end
end
class AddEnvironmentScopeToClusters < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:clusters, :environment_scope, :string, default: '*')
end
def down
remove_column(:clusters, :environment_scope)
end
end
...@@ -74,7 +74,6 @@ class MigrateGcpClustersToNewClustersArchitectures < ActiveRecord::Migration ...@@ -74,7 +74,6 @@ class MigrateGcpClustersToNewClustersArchitectures < ActiveRecord::Migration
encrypted_access_token_iv: gcp_cluster.encrypted_gcp_token_iv encrypted_access_token_iv: gcp_cluster.encrypted_gcp_token_iv
}, },
platform_kubernetes_attributes: { platform_kubernetes_attributes: {
cluster_id: gcp_cluster.id,
api_url: api_url(gcp_cluster.endpoint), api_url: api_url(gcp_cluster.endpoint),
ca_cert: gcp_cluster.ca_cert, ca_cert: gcp_cluster.ca_cert,
namespace: gcp_cluster.project_namespace, namespace: gcp_cluster.project_namespace,
......
class CleanupApplicationSettingsPasswordAuthenticationEnabledRename < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :application_settings, :password_authentication_enabled, :password_authentication_enabled_for_web
end
def down
rename_column_concurrently :application_settings, :password_authentication_enabled_for_web, :password_authentication_enabled
end
end
...@@ -147,11 +147,14 @@ ActiveRecord::Schema.define(version: 20171121144800) do ...@@ -147,11 +147,14 @@ ActiveRecord::Schema.define(version: 20171121144800) do
t.boolean "authorized_keys_enabled", default: true, null: false t.boolean "authorized_keys_enabled", default: true, null: false
t.boolean "help_page_hide_commercial_content", default: false t.boolean "help_page_hide_commercial_content", default: false
t.string "help_page_support_url" t.string "help_page_support_url"
<<<<<<< HEAD
t.boolean "slack_app_enabled", default: false t.boolean "slack_app_enabled", default: false
t.string "slack_app_id" t.string "slack_app_id"
t.string "slack_app_secret" t.string "slack_app_secret"
t.string "slack_app_verification_token" t.string "slack_app_verification_token"
t.boolean "password_authentication_enabled" t.boolean "password_authentication_enabled"
=======
>>>>>>> ce/master
t.integer "performance_bar_allowed_group_id" t.integer "performance_bar_allowed_group_id"
t.boolean "allow_group_owners_to_manage_ldap", default: true, null: false t.boolean "allow_group_owners_to_manage_ldap", default: true, null: false
t.boolean "hashed_storage_enabled", default: false, null: false t.boolean "hashed_storage_enabled", default: false, null: false
...@@ -173,6 +176,8 @@ ActiveRecord::Schema.define(version: 20171121144800) do ...@@ -173,6 +176,8 @@ ActiveRecord::Schema.define(version: 20171121144800) do
t.boolean "throttle_authenticated_web_enabled", default: false, null: false t.boolean "throttle_authenticated_web_enabled", default: false, null: false
t.integer "throttle_authenticated_web_requests_per_period", default: 7200, null: false t.integer "throttle_authenticated_web_requests_per_period", default: 7200, null: false
t.integer "throttle_authenticated_web_period_in_seconds", default: 3600, null: false t.integer "throttle_authenticated_web_period_in_seconds", default: 3600, null: false
t.boolean "password_authentication_enabled_for_web"
t.boolean "password_authentication_enabled_for_git", default: true
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
...@@ -614,6 +619,7 @@ ActiveRecord::Schema.define(version: 20171121144800) do ...@@ -614,6 +619,7 @@ ActiveRecord::Schema.define(version: 20171121144800) do
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
t.boolean "enabled", default: true t.boolean "enabled", default: true
t.string "name", null: false t.string "name", null: false
t.string "environment_scope", default: "*", null: false
end end
add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree
......
...@@ -39,6 +39,12 @@ immediately block all access. ...@@ -39,6 +39,12 @@ immediately block all access.
>**Note**: GitLab EE supports a configurable sync time, with a default >**Note**: GitLab EE supports a configurable sync time, with a default
of one hour. of one hour.
## Git password authentication
LDAP-enabled users can always authenticate with Git using their GitLab username
or email and LDAP password, even if password authentication for Git is disabled
in the application settings.
## Configuration ## Configuration
To enable LDAP integration you need to add your LDAP server settings in To enable LDAP integration you need to add your LDAP server settings in
......
# Repository storages This document was moved to [another location](repository_storage_paths.md).
This document was moved to a [new location](repository_storage_paths.md).
...@@ -25,7 +25,7 @@ Example response: ...@@ -25,7 +25,7 @@ Example response:
"id" : 1, "id" : 1,
"default_branch_protection" : 2, "default_branch_protection" : 2,
"restricted_visibility_levels" : [], "restricted_visibility_levels" : [],
"password_authentication_enabled" : true, "password_authentication_enabled_for_web" : true,
"after_sign_out_path" : null, "after_sign_out_path" : null,
"max_attachment_size" : 10, "max_attachment_size" : 10,
"user_oauth_applications" : true, "user_oauth_applications" : true,
...@@ -130,7 +130,8 @@ PUT /application/settings ...@@ -130,7 +130,8 @@ PUT /application/settings
| `metrics_port` | integer | no | The UDP port to use for connecting to InfluxDB | | `metrics_port` | integer | no | The UDP port to use for connecting to InfluxDB |
| `metrics_sample_interval` | integer | yes (if `metrics_enabled` is `true`) | The sampling interval in seconds. | | `metrics_sample_interval` | integer | yes (if `metrics_enabled` is `true`) | The sampling interval in seconds. |
| `metrics_timeout` | integer | yes (if `metrics_enabled` is `true`) | The amount of seconds after which InfluxDB will time out. | | `metrics_timeout` | integer | yes (if `metrics_enabled` is `true`) | The amount of seconds after which InfluxDB will time out. |
| `password_authentication_enabled` | boolean | no | Enable authentication via a GitLab account password. Default is `true`. | | `password_authentication_enabled_for_web` | boolean | no | Enable authentication for the web interface via a GitLab account password. Default is `true`. |
| `password_authentication_enabled_for_git` | boolean | no | Enable authentication for Git over HTTP(S) via a GitLab account password. Default is `true`. |
| `performance_bar_allowed_group_id` | string | no | The group that is allowed to enable the performance bar | | `performance_bar_allowed_group_id` | string | no | The group that is allowed to enable the performance bar |
| `performance_bar_enabled` | boolean | no | Allow enabling the performance bar | | `performance_bar_enabled` | boolean | no | Allow enabling the performance bar |
| `plantuml_enabled` | boolean | no | Enable PlantUML integration. Default is `false`. | | `plantuml_enabled` | boolean | no | Enable PlantUML integration. Default is `false`. |
...@@ -182,7 +183,7 @@ Example response: ...@@ -182,7 +183,7 @@ Example response:
"id": 1, "id": 1,
"default_projects_limit": 100000, "default_projects_limit": 100000,
"signup_enabled": true, "signup_enabled": true,
"password_authentication_enabled": true, "password_authentication_enabled_for_web": true,
"gravatar_enabled": true, "gravatar_enabled": true,
"sign_in_text": "", "sign_in_text": "",
"created_at": "2015-06-12T15:51:55.432Z", "created_at": "2015-06-12T15:51:55.432Z",
......
...@@ -303,10 +303,10 @@ GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the ...@@ -303,10 +303,10 @@ GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the
Documentation team beforehand. Documentation team beforehand.
If you indeed need to change a document's location, do NOT remove the old If you indeed need to change a document's location, do NOT remove the old
document, but rather put a text in it that points to the new location, like: document, but rather replace all of its contents with a new line:
``` ```
This document was moved to [path/to/new_doc.md](path/to/new_doc.md). This document was moved to [another location](path/to/new_doc.md).
``` ```
where `path/to/new_doc.md` is the relative path to the root directory `doc/`. where `path/to/new_doc.md` is the relative path to the root directory `doc/`.
...@@ -320,7 +320,7 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to ...@@ -320,7 +320,7 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with: 1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
``` ```
This document was moved to [administration/lfs.md](../../administration/lfs.md). This document was moved to [another location](../../administration/lfs.md).
``` ```
1. Find and replace any occurrences of the old location with the new one. 1. Find and replace any occurrences of the old location with the new one.
......
...@@ -12,8 +12,9 @@ in the project's default branch. ...@@ -12,8 +12,9 @@ in the project's default branch.
If a commit message or merge request description contains a sentence matching If a commit message or merge request description contains a sentence matching
a certain regular expression, all issues referenced from the matched text will a certain regular expression, all issues referenced from the matched text will
be closed. This happens when the commit is pushed to a project's **default** be closed. This happens when the commit is pushed to a project's
branch, or when a commit or merge request is merged into it. [**default** branch](../repository/branches/index.md#default-branch), or when a
commit or merge request is merged into it.
## Default closing pattern value ## Default closing pattern value
......
This document was moved to a [new location](../../user/project/import/index.md). This document was moved to [another location](../../user/project/import/index.md).
This document was moved to a [new location](../../user/project/import/bitbucket.md). This document was moved to [another location](../../user/project/import/bitbucket.md).
This document was moved to a [new location](../../user/project/import/fogbugz.md). This document was moved to [another location](../../user/project/import/fogbugz.md).
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment