Merge remote-tracking branch 'upstream/master' into 32815--Add-Custom-CI-Config-Path
* upstream/master: (149 commits) Revert change to design. Go back to scrollable page Fixes the column widths for the new navigation options in settings Migrate #submodule_url_for to Gitaly Add test example for external commit status retries Fix invalid Rails.logger call in lib/gitlab/health_checks/fs_shards_check.rb Fix build for !12300. Log rescued exceptions to Sentry Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths Revert "Merge branch 'revert-12499' into 'master'" Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion Improve the overall UX for the new monitoring dashboard Document that GitLab 9.3 requires the TRIGGER permission on MySQL Instrument Unicorn with Ruby exporter Remove group modal like remove project modal. Closes #33130 Update prometheus client gem Enables the option in user preferences to turn on the new navigation Add Jasmine tests for `OAuthRememberMe` Simplify authentication logic in the v4 users API for !12445. Use stub_application_setting when testing ApplicationHelper#support_url wait_for_requests is not needed when AJAX is not in play ...
This commit is contained in:
commit
33a5157ad4
|
@ -474,8 +474,6 @@ codeclimate:
|
|||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- docker pull stedolan/jq
|
||||
- docker pull codeclimate/codeclimate
|
||||
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
|
||||
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
|
||||
artifacts:
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 9.3.4 (2017-07-03)
|
||||
|
||||
- No changes.
|
||||
|
||||
## 9.3.3 (2017-06-30)
|
||||
|
||||
- Fix head pipeline stored in merge request for external pipelines. !12478
|
||||
|
|
|
@ -1 +1 @@
|
|||
5.0.6
|
||||
5.1.1
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -255,7 +255,7 @@ gem 'net-ssh', '~> 3.0.1'
|
|||
gem 'base32', '~> 0.3.0'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven', '~> 2.4.0'
|
||||
gem 'sentry-raven', '~> 2.5.3'
|
||||
|
||||
gem 'premailer-rails', '~> 1.9.7'
|
||||
|
||||
|
@ -285,6 +285,7 @@ group :metrics do
|
|||
|
||||
# Prometheus
|
||||
gem 'prometheus-client-mmap', '~>0.7.0.beta5'
|
||||
gem 'raindrops', '~> 0.18'
|
||||
end
|
||||
|
||||
group :development do
|
||||
|
|
11
Gemfile.lock
11
Gemfile.lock
|
@ -599,8 +599,8 @@ GEM
|
|||
premailer-rails (1.9.7)
|
||||
actionmailer (>= 3, < 6)
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
prometheus-client-mmap (0.7.0.beta5)
|
||||
mmap2 (~> 2.2.6)
|
||||
prometheus-client-mmap (0.7.0.beta8)
|
||||
mmap2 (~> 2.2, >= 2.2.7)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
|
@ -658,7 +658,7 @@ GEM
|
|||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
raindrops (0.17.0)
|
||||
raindrops (0.18.0)
|
||||
rake (10.5.0)
|
||||
rblineprof (0.3.6)
|
||||
debugger-ruby_core_source (~> 1.3)
|
||||
|
@ -775,7 +775,7 @@ GEM
|
|||
activesupport (>= 3.1)
|
||||
select2-rails (3.5.9.3)
|
||||
thor (~> 0.14)
|
||||
sentry-raven (2.4.0)
|
||||
sentry-raven (2.5.3)
|
||||
faraday (>= 0.7.6, < 1.0)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.9.0)
|
||||
|
@ -1062,6 +1062,7 @@ DEPENDENCIES
|
|||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rails-i18n (~> 4.0.9)
|
||||
rainbow (~> 2.2)
|
||||
raindrops (~> 0.18)
|
||||
rblineprof (~> 0.3.6)
|
||||
rdoc (~> 4.2)
|
||||
recaptcha (~> 3.0)
|
||||
|
@ -1089,7 +1090,7 @@ DEPENDENCIES
|
|||
scss_lint (~> 0.47.0)
|
||||
seed-fu (~> 2.3.5)
|
||||
select2-rails (~> 3.5.9)
|
||||
sentry-raven (~> 2.4.0)
|
||||
sentry-raven (~> 2.5.3)
|
||||
settingslogic (~> 2.0.9)
|
||||
sham_rack (~> 1.3.6)
|
||||
shoulda-matchers (~> 2.8.0)
|
||||
|
|
|
@ -1,23 +1,8 @@
|
|||
import autosize from 'vendor/autosize';
|
||||
|
||||
$(() => {
|
||||
const $fields = $('.js-autosize');
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const autosizeEls = document.querySelectorAll('.js-autosize');
|
||||
|
||||
$fields.on('autosize:resized', function resized() {
|
||||
const $field = $(this);
|
||||
$field.data('height', $field.outerHeight());
|
||||
});
|
||||
|
||||
$fields.on('resize.autosize', function resize() {
|
||||
const $field = $(this);
|
||||
if ($field.data('height') !== $field.outerHeight()) {
|
||||
$field.data('height', $field.outerHeight());
|
||||
autosize.destroy($field);
|
||||
$field.css('max-height', window.outerHeight);
|
||||
}
|
||||
});
|
||||
|
||||
autosize($fields);
|
||||
autosize.update($fields);
|
||||
$fields.css('resize', 'vertical');
|
||||
autosize(autosizeEls);
|
||||
autosize.update(autosizeEls);
|
||||
});
|
||||
|
|
|
@ -34,7 +34,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
|
|||
},
|
||||
milestoneTitle() {
|
||||
return this.issue.milestone ? this.issue.milestone.title : 'No Milestone';
|
||||
}
|
||||
},
|
||||
canRemove() {
|
||||
return !this.list.preset;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
detail: {
|
||||
|
|
|
@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
|||
},
|
||||
template: `
|
||||
<div
|
||||
class="block list"
|
||||
v-if="list.type !== 'closed'">
|
||||
class="block list">
|
||||
<button
|
||||
class="btn btn-default btn-block"
|
||||
type="button"
|
||||
|
|
|
@ -13,25 +13,21 @@ window.Build = (function () {
|
|||
this.options = options || $('.js-build-options').data();
|
||||
|
||||
this.pageUrl = this.options.pageUrl;
|
||||
this.buildUrl = this.options.buildUrl;
|
||||
this.buildStatus = this.options.buildStatus;
|
||||
this.state = this.options.logState;
|
||||
this.buildStage = this.options.buildStage;
|
||||
this.$document = $(document);
|
||||
this.logBytes = 0;
|
||||
this.scrollOffsetPadding = 30;
|
||||
this.hasBeenScrolled = false;
|
||||
|
||||
this.updateDropdown = this.updateDropdown.bind(this);
|
||||
this.getBuildTrace = this.getBuildTrace.bind(this);
|
||||
this.scrollToBottom = this.scrollToBottom.bind(this);
|
||||
|
||||
this.$body = $('body');
|
||||
this.$buildTrace = $('#build-trace');
|
||||
this.$buildRefreshAnimation = $('.js-build-refresh');
|
||||
this.$truncatedInfo = $('.js-truncated-info');
|
||||
this.$buildTraceOutput = $('.js-build-output');
|
||||
this.$scrollContainer = $('.js-scroll-container');
|
||||
this.$topBar = $('.js-top-bar');
|
||||
|
||||
// Scroll controllers
|
||||
this.$scrollTopBtn = $('.js-scroll-up');
|
||||
|
@ -63,13 +59,22 @@ window.Build = (function () {
|
|||
.off('click')
|
||||
.on('click', this.scrollToBottom.bind(this));
|
||||
|
||||
const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
|
||||
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
|
||||
|
||||
this.$scrollContainer
|
||||
$(window)
|
||||
.off('scroll')
|
||||
.on('scroll', () => {
|
||||
this.hasBeenScrolled = true;
|
||||
scrollThrottled();
|
||||
const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
|
||||
if (contentHeight > this.windowSize) {
|
||||
// means the user did not scroll, the content was updated.
|
||||
this.windowSize = contentHeight;
|
||||
} else {
|
||||
// User scrolled
|
||||
this.hasBeenScrolled = true;
|
||||
this.toggleScrollAnimation(false);
|
||||
}
|
||||
|
||||
this.scrollThrottled();
|
||||
});
|
||||
|
||||
$(window)
|
||||
|
@ -77,59 +82,73 @@ window.Build = (function () {
|
|||
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
|
||||
|
||||
this.updateArtifactRemoveDate();
|
||||
this.initAffixTopArea();
|
||||
|
||||
// eslint-disable-next-line
|
||||
this.getBuildTrace()
|
||||
.then(() => this.toggleScroll())
|
||||
.then(() => {
|
||||
if (!this.hasBeenScrolled) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
})
|
||||
.then(() => this.verifyTopPosition());
|
||||
this.getBuildTrace();
|
||||
}
|
||||
|
||||
Build.prototype.canScroll = function () {
|
||||
return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
|
||||
Build.prototype.initAffixTopArea = function () {
|
||||
/**
|
||||
If the browser does not support position sticky, it returns the position as static.
|
||||
If the browser does support sticky, then we allow the browser to handle it, if not
|
||||
then we default back to Bootstraps affix
|
||||
**/
|
||||
if (this.$topBar.css('position') !== 'static') return;
|
||||
|
||||
const offsetTop = this.$buildTrace.offset().top;
|
||||
|
||||
this.$topBar.affix({
|
||||
offset: {
|
||||
top: offsetTop,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.canScroll = function () {
|
||||
return document.body.scrollHeight > window.innerHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* | | Up | Down |
|
||||
* |--------------------------|----------|----------|
|
||||
* | on scroll bottom | active | disabled |
|
||||
* | on scroll top | disabled | active |
|
||||
* | no scroll | disabled | disabled |
|
||||
* | on.('scroll') is on top | disabled | active |
|
||||
* | on('scroll) is on bottom | active | disabled |
|
||||
*
|
||||
*/
|
||||
Build.prototype.toggleScroll = function () {
|
||||
const currentPosition = this.$scrollContainer.scrollTop();
|
||||
const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
|
||||
const currentPosition = document.body.scrollTop;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
if (this.canScroll()) {
|
||||
if (currentPosition === 0) {
|
||||
if (currentPosition > 0 &&
|
||||
(document.body.scrollHeight - currentPosition !== windowHeight)) {
|
||||
// User is in the middle of the log
|
||||
|
||||
this.toggleDisableButton(this.$scrollTopBtn, false);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, false);
|
||||
} else if (currentPosition === 0) {
|
||||
// User is at Top of Build Log
|
||||
|
||||
this.toggleDisableButton(this.$scrollTopBtn, true);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, false);
|
||||
} else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
|
||||
} else if (document.body.scrollHeight - currentPosition === windowHeight) {
|
||||
// User is at the bottom of the build log.
|
||||
|
||||
this.toggleDisableButton(this.$scrollTopBtn, false);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, true);
|
||||
} else {
|
||||
this.toggleDisableButton(this.$scrollTopBtn, false);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, false);
|
||||
}
|
||||
} else {
|
||||
this.toggleDisableButton(this.$scrollTopBtn, true);
|
||||
this.toggleDisableButton(this.$scrollBottomBtn, true);
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.scrollToTop = function () {
|
||||
this.hasBeenScrolled = true;
|
||||
this.$scrollContainer.scrollTop(0);
|
||||
this.toggleScroll();
|
||||
Build.prototype.scrollDown = function () {
|
||||
document.body.scrollTop = document.body.scrollHeight;
|
||||
};
|
||||
|
||||
Build.prototype.scrollToBottom = function () {
|
||||
this.scrollDown();
|
||||
this.hasBeenScrolled = true;
|
||||
this.toggleScroll();
|
||||
};
|
||||
|
||||
Build.prototype.scrollToTop = function () {
|
||||
document.body.scrollTop = 0;
|
||||
this.hasBeenScrolled = true;
|
||||
this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
|
||||
this.toggleScroll();
|
||||
};
|
||||
|
||||
|
@ -142,47 +161,6 @@ window.Build = (function () {
|
|||
this.$scrollBottomBtn.toggleClass('animate', toggle);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build trace top position depends on the space ocupied by the elments rendered before
|
||||
*/
|
||||
Build.prototype.verifyTopPosition = function () {
|
||||
const $buildPage = $('.build-page');
|
||||
|
||||
const $flashError = $('.alert-wrapper');
|
||||
const $header = $('.build-header', $buildPage);
|
||||
const $runnersStuck = $('.js-build-stuck', $buildPage);
|
||||
const $startsEnvironment = $('.js-environment-container', $buildPage);
|
||||
const $erased = $('.js-build-erased', $buildPage);
|
||||
const prependTopDefault = 20;
|
||||
|
||||
// header + navigation + margin
|
||||
let topPostion = 168;
|
||||
|
||||
if ($header.length) {
|
||||
topPostion += $header.outerHeight();
|
||||
}
|
||||
|
||||
if ($runnersStuck.length) {
|
||||
topPostion += $runnersStuck.outerHeight();
|
||||
}
|
||||
|
||||
if ($startsEnvironment.length) {
|
||||
topPostion += $startsEnvironment.outerHeight() + prependTopDefault;
|
||||
}
|
||||
|
||||
if ($erased.length) {
|
||||
topPostion += $erased.outerHeight() + prependTopDefault;
|
||||
}
|
||||
|
||||
if ($flashError.length) {
|
||||
topPostion += $flashError.outerHeight() + prependTopDefault;
|
||||
}
|
||||
|
||||
this.$buildTrace.css({
|
||||
top: topPostion,
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.initSidebar = function () {
|
||||
this.$sidebar = $('.js-build-sidebar');
|
||||
this.$sidebar.niceScroll();
|
||||
|
@ -200,6 +178,8 @@ window.Build = (function () {
|
|||
this.state = log.state;
|
||||
}
|
||||
|
||||
this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
|
||||
|
||||
if (log.append) {
|
||||
this.$buildTraceOutput.append(log.html);
|
||||
this.logBytes += log.size;
|
||||
|
@ -227,14 +207,7 @@ window.Build = (function () {
|
|||
}
|
||||
|
||||
Build.timeout = setTimeout(() => {
|
||||
//eslint-disable-next-line
|
||||
this.getBuildTrace()
|
||||
.then(() => {
|
||||
if (!this.hasBeenScrolled) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
})
|
||||
.then(() => this.verifyTopPosition());
|
||||
this.getBuildTrace();
|
||||
}, 4000);
|
||||
} else {
|
||||
this.$buildRefreshAnimation.remove();
|
||||
|
@ -247,7 +220,13 @@ window.Build = (function () {
|
|||
})
|
||||
.fail(() => {
|
||||
this.$buildRefreshAnimation.remove();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
if (!this.hasBeenScrolled) {
|
||||
this.scrollDown();
|
||||
}
|
||||
})
|
||||
.then(() => this.toggleScroll());
|
||||
};
|
||||
|
||||
Build.prototype.shouldHideSidebarForViewport = function () {
|
||||
|
@ -259,14 +238,11 @@ window.Build = (function () {
|
|||
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
|
||||
const $toggleButton = $('.js-sidebar-build-toggle-header');
|
||||
|
||||
this.$buildTrace
|
||||
.toggleClass('sidebar-expanded', shouldShow)
|
||||
.toggleClass('sidebar-collapsed', shouldHide);
|
||||
this.$sidebar
|
||||
.toggleClass('right-sidebar-expanded', shouldShow)
|
||||
.toggleClass('right-sidebar-collapsed', shouldHide);
|
||||
|
||||
$('.js-build-page')
|
||||
this.$topBar
|
||||
.toggleClass('sidebar-expanded', shouldShow)
|
||||
.toggleClass('sidebar-collapsed', shouldHide);
|
||||
|
||||
|
@ -279,17 +255,10 @@ window.Build = (function () {
|
|||
|
||||
Build.prototype.sidebarOnResize = function () {
|
||||
this.toggleSidebar(this.shouldHideSidebarForViewport());
|
||||
|
||||
this.verifyTopPosition();
|
||||
|
||||
if (this.canScroll()) {
|
||||
this.toggleScroll();
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.sidebarOnClick = function () {
|
||||
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
|
||||
this.verifyTopPosition();
|
||||
};
|
||||
|
||||
Build.prototype.updateArtifactRemoveDate = function () {
|
||||
|
|
|
@ -56,6 +56,7 @@ import GfmAutoComplete from './gfm_auto_complete';
|
|||
import ShortcutsBlob from './shortcuts_blob';
|
||||
import initSettingsPanels from './settings_panels';
|
||||
import initExperimentalFlags from './experimental_flags';
|
||||
import OAuthRememberMe from './oauth_remember_me';
|
||||
|
||||
(function() {
|
||||
var Dispatcher;
|
||||
|
@ -127,6 +128,7 @@ import initExperimentalFlags from './experimental_flags';
|
|||
case 'sessions:new':
|
||||
new UsernameValidator();
|
||||
new ActiveTabMemoizer();
|
||||
new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
|
||||
break;
|
||||
case 'projects:boards:show':
|
||||
case 'projects:boards:index':
|
||||
|
|
|
@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
mounted() {
|
||||
this.mediator.initBuildClass();
|
||||
},
|
||||
updated() {
|
||||
// Wait for flash message to be appended
|
||||
Vue.nextTick(() => {
|
||||
if (this.mediator.build) {
|
||||
this.mediator.build.verifyTopPosition();
|
||||
}
|
||||
});
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('job-header', {
|
||||
props: {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
graphHeight: 500,
|
||||
graphHeight: 450,
|
||||
graphWidth: 600,
|
||||
graphHeightOffset: 120,
|
||||
xScale: {},
|
||||
|
@ -88,7 +88,9 @@
|
|||
},
|
||||
|
||||
paddingBottomRootSvg() {
|
||||
return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0;
|
||||
return {
|
||||
paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -104,7 +106,7 @@
|
|||
}
|
||||
this.data = query.result[0].values;
|
||||
this.unitOfDisplay = query.unit || 'N/A';
|
||||
this.yAxisLabel = this.columnData.y_axis || 'Values';
|
||||
this.yAxisLabel = this.columnData.y_label || 'Values';
|
||||
this.legendTitle = query.legend || 'Average';
|
||||
this.graphWidth = this.$refs.baseSvg.clientWidth -
|
||||
this.margin.left - this.margin.right;
|
||||
|
@ -198,7 +200,7 @@
|
|||
watch: {
|
||||
updateAspectRatio() {
|
||||
if (this.updateAspectRatio) {
|
||||
this.graphHeight = 500;
|
||||
this.graphHeight = 450;
|
||||
this.graphWidth = 600;
|
||||
this.measurements = measurements.large;
|
||||
this.draw();
|
||||
|
@ -216,14 +218,14 @@
|
|||
<div
|
||||
:class="classType">
|
||||
<h5
|
||||
class="text-center">
|
||||
class="text-center graph-title">
|
||||
{{columnData.title}}
|
||||
</h5>
|
||||
<div
|
||||
class="prometheus-svg-container">
|
||||
<div
|
||||
class="prometheus-svg-container"
|
||||
:style="paddingBottomRootSvg">
|
||||
<svg
|
||||
:viewBox="outterViewBox"
|
||||
:style="{ 'padding-bottom': paddingBottomRootSvg }"
|
||||
ref="baseSvg">
|
||||
<g
|
||||
class="x-axis"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* OAuth-based login buttons have a separate "remember me" checkbox.
|
||||
*
|
||||
* Toggling this checkbox adds/removes a `remember_me` parameter to the
|
||||
* login buttons' href, which is passed on to the omniauth callback.
|
||||
**/
|
||||
|
||||
export default class OAuthRememberMe {
|
||||
constructor(opts = {}) {
|
||||
this.container = opts.container || '';
|
||||
this.loginLinkSelector = '.oauth-login';
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
$('#remember_me', this.container).on('click', this.toggleRememberMe);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
toggleRememberMe(event) {
|
||||
const rememberMe = $(event.target).is(':checked');
|
||||
|
||||
$('.oauth-login', this.container).each((i, element) => {
|
||||
const href = $(element).attr('href');
|
||||
|
||||
if (rememberMe) {
|
||||
$(element).attr('href', `${href}?remember_me=1`);
|
||||
} else {
|
||||
$(element).attr('href', href.replace('?remember_me=1', ''));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
|
|||
return $dropdown.glDropdown({
|
||||
showMenuAbove: showMenuAbove,
|
||||
data: function(term, callback) {
|
||||
var isAuthorFilter;
|
||||
isAuthorFilter = $('.js-author-search');
|
||||
return _this.users(term, options, function(users) {
|
||||
// GitLabDropdownFilter returns this.instance
|
||||
// GitLabDropdownRemote returns this.options.instance
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
max-width: $limited-layout-width-sm;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 64px;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,65 +37,77 @@
|
|||
}
|
||||
|
||||
.build-page {
|
||||
.sticky {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
.build-trace-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.build-trace-container {
|
||||
position: absolute;
|
||||
top: 225px;
|
||||
left: 15px;
|
||||
bottom: 10px;
|
||||
.build-trace {
|
||||
background: $black;
|
||||
color: $gray-darkest;
|
||||
font-family: $monospace_font;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
|
||||
&.sidebar-expanded {
|
||||
right: 305px;
|
||||
.bash {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
background: $gray-light;
|
||||
border: 1px solid $border-color;
|
||||
color: $gl-text-color;
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 50px;
|
||||
|
||||
&.affix {
|
||||
top: 50px;
|
||||
}
|
||||
|
||||
&.sidebar-collapsed {
|
||||
// with sidebar
|
||||
&.affix.sidebar-expanded {
|
||||
right: 306px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
// without sidebar
|
||||
&.affix.sidebar-collapsed {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
code {
|
||||
background: $black;
|
||||
color: $gray-darkest;
|
||||
&.affix-top {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
top: 0;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
background: $gray-light;
|
||||
border: 1px solid $border-color;
|
||||
color: $gl-text-color;
|
||||
.truncated-info {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
|
||||
.truncated-info {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
.truncated-info-size {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.truncated-info-size {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.raw-link {
|
||||
color: $gl-text-color;
|
||||
margin-left: 5px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.raw-link {
|
||||
color: $gl-text-color;
|
||||
margin-left: 5px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.controllers {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
font-size: 15px;
|
||||
margin-bottom: 4px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
height: 15px;
|
||||
|
@ -103,17 +115,9 @@
|
|||
fill: $gl-text-color;
|
||||
}
|
||||
|
||||
.controllers-buttons,
|
||||
.btn-scroll {
|
||||
color: $gl-text-color;
|
||||
height: 15px;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.controllers-buttons {
|
||||
margin: 1px 10px;
|
||||
color: $gl-text-color;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.btn-scroll.animate {
|
||||
|
@ -143,15 +147,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.bash {
|
||||
top: 35px;
|
||||
left: 10px;
|
||||
bottom: 0;
|
||||
padding: 10px 20px 20px 5px;
|
||||
white-space: pre-wrap;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.environment-information {
|
||||
border: 1px solid $border-color;
|
||||
padding: 8px $gl-padding 12px;
|
||||
|
|
|
@ -254,7 +254,7 @@
|
|||
.text-metric-usage {
|
||||
fill: $black;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legend-axis-text {
|
||||
|
@ -262,7 +262,11 @@
|
|||
}
|
||||
|
||||
.tick > text {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.text-metric-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-sm-max) {
|
||||
|
@ -277,3 +281,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.prometheus-row {
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -483,11 +483,12 @@ a.deploy-project-label {
|
|||
.project-stats {
|
||||
font-size: 0;
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
.nav {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.nav > li {
|
||||
|
|
|
@ -33,3 +33,20 @@
|
|||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-runner-btn-group-cell {
|
||||
min-width: 150px;
|
||||
|
||||
.btn-sm {
|
||||
padding: 4px 9px;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
.fa-pause,
|
||||
.fa-play {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,6 +110,8 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def log_exception(exception)
|
||||
Raven.capture_exception(exception) if sentry_enabled?
|
||||
|
||||
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
|
||||
application_trace.map!{ |t| " #{t}\n" }
|
||||
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
|
||||
|
|
|
@ -11,6 +11,9 @@ class Groups::MilestonesController < Groups::ApplicationController
|
|||
@milestone_states = GlobalMilestone.states_count(@projects)
|
||||
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
|
||||
end
|
||||
format.json do
|
||||
render json: milestones.map { |m| m.for_display.slice(:title, :name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
include AuthenticatesWithTwoFactor
|
||||
include Devise::Controllers::Rememberable
|
||||
|
||||
protect_from_forgery except: [:kerberos, :saml, :cas3]
|
||||
|
||||
|
@ -115,8 +116,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
if @user.persisted? && @user.valid?
|
||||
log_audit_event(@user, with: oauth['provider'])
|
||||
if @user.two_factor_enabled?
|
||||
params[:remember_me] = '1' if remember_me?
|
||||
prompt_for_two_factor(@user)
|
||||
else
|
||||
remember_me(@user) if remember_me?
|
||||
sign_in_and_redirect(@user)
|
||||
end
|
||||
else
|
||||
|
@ -147,4 +150,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
AuditEventService.new(user, user, options)
|
||||
.for_authentication.security_event
|
||||
end
|
||||
|
||||
def remember_me?
|
||||
request_params = request.env['omniauth.params']
|
||||
(request_params['remember_me'] == '1') if request_params.present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
def issue
|
||||
return @issue if defined?(@issue)
|
||||
# The Sortable default scope causes performance issues when used with find_by
|
||||
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
|
||||
@noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
|
||||
|
||||
return render_404 unless can?(current_user, :read_issue, @issue)
|
||||
|
||||
|
|
|
@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder
|
|||
def projects
|
||||
return @projects if defined?(@projects)
|
||||
|
||||
@projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute
|
||||
@projects = if skip_authorization
|
||||
Project.all
|
||||
else
|
||||
ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute
|
||||
end
|
||||
|
||||
@projects = @projects.in_namespace(params[:group_id]) if group?
|
||||
@projects = @projects.where(id: params[:project_ids]) if projects?
|
||||
@projects = @projects.reorder(nil)
|
||||
|
|
|
@ -60,13 +60,13 @@ class UsersFinder
|
|||
end
|
||||
|
||||
def by_external_identity(users)
|
||||
return users unless current_user.admin? && params[:extern_uid] && params[:provider]
|
||||
return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
|
||||
|
||||
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
|
||||
end
|
||||
|
||||
def by_external(users)
|
||||
return users = users.where.not(external: true) unless current_user.admin?
|
||||
return users = users.where.not(external: true) unless current_user&.admin?
|
||||
return users unless params[:external]
|
||||
|
||||
users.external
|
||||
|
|
|
@ -298,10 +298,6 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def can_toggle_new_nav?
|
||||
Rails.env.development?
|
||||
end
|
||||
|
||||
def show_new_nav?
|
||||
cookies["new_nav"] == "true"
|
||||
end
|
||||
|
|
|
@ -16,8 +16,8 @@ module FormHelper
|
|||
end
|
||||
end
|
||||
|
||||
def issue_dropdown_options(issuable, has_multiple_assignees = true)
|
||||
options = {
|
||||
def issue_assignees_dropdown_options
|
||||
{
|
||||
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
|
||||
title: 'Select assignee',
|
||||
filter: true,
|
||||
|
@ -27,8 +27,8 @@ module FormHelper
|
|||
first_user: current_user&.username,
|
||||
null_user: true,
|
||||
current_user: true,
|
||||
project_id: issuable.project.try(:id),
|
||||
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]",
|
||||
project_id: @project.id,
|
||||
field_name: 'issue[assignee_ids][]',
|
||||
default_label: 'Unassigned',
|
||||
'max-select': 1,
|
||||
'dropdown-header': 'Assignee',
|
||||
|
@ -38,13 +38,5 @@ module FormHelper
|
|||
current_user_info: current_user.to_json(only: [:id, :name])
|
||||
}
|
||||
}
|
||||
|
||||
if has_multiple_assignees
|
||||
options[:title] = 'Select assignee(s)'
|
||||
options[:data][:'dropdown-header'] = 'Assignee(s)'
|
||||
options[:data].delete(:'max-select')
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,6 +58,11 @@ module GroupsHelper
|
|||
IssuesFinder.new(current_user, group_id: group.id).execute
|
||||
end
|
||||
|
||||
def remove_group_message(group)
|
||||
_("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
|
||||
{ group_name: group.name }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_title_link(group, hidable: false)
|
||||
|
|
|
@ -74,6 +74,8 @@ module MilestonesHelper
|
|||
project = @target_project || @project
|
||||
if project
|
||||
namespace_project_milestones_path(project.namespace, project, :json)
|
||||
elsif @group
|
||||
group_milestones_path(@group, :json)
|
||||
else
|
||||
dashboard_milestones_path(:json)
|
||||
end
|
||||
|
|
|
@ -126,6 +126,18 @@ module SearchHelper
|
|||
search_path(options)
|
||||
end
|
||||
|
||||
def search_filter_input_options(type)
|
||||
{
|
||||
id: "filtered-search-#{type}",
|
||||
placeholder: 'Search or filter results...',
|
||||
data: {
|
||||
'project-id' => @project.id,
|
||||
'username-params' => @users.to_json(only: [:id, :username]),
|
||||
'base-endpoint' => namespace_project_path(@project.namespace, @project)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# Sanitize a HTML field for search display. Most tags are stripped out and the
|
||||
# maximum length is set to 200 characters.
|
||||
def search_md_sanitize(object, field)
|
||||
|
|
|
@ -176,9 +176,12 @@ module Ci
|
|||
# * Lowercased
|
||||
# * Anything not matching [a-z0-9-] is replaced with a -
|
||||
# * Maximum length is 63 bytes
|
||||
# * First/Last Character is not a hyphen
|
||||
def ref_slug
|
||||
slugified = ref.to_s.downcase
|
||||
slugified.gsub(/[^a-z0-9]/, '-')[0..62]
|
||||
ref.to_s
|
||||
.downcase
|
||||
.gsub(/[^a-z0-9]/, '-')[0..62]
|
||||
.gsub(/(\A-+|-+\z)/, '')
|
||||
end
|
||||
|
||||
# Variables whose value does not depend on environment
|
||||
|
|
|
@ -5,7 +5,7 @@ module Ci
|
|||
|
||||
belongs_to :project
|
||||
|
||||
validates :key, uniqueness: { scope: :project_id }
|
||||
validates :key, uniqueness: { scope: [:project_id, :environment_scope] }
|
||||
|
||||
scope :unprotected, -> { where(protected: false) }
|
||||
end
|
||||
|
|
|
@ -102,6 +102,14 @@ module Issuable
|
|||
def locking_enabled?
|
||||
title_changed? || description_changed?
|
||||
end
|
||||
|
||||
def allows_multiple_assignees?
|
||||
false
|
||||
end
|
||||
|
||||
def has_multiple_assignees?
|
||||
assignees.count > 1
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
|
|
@ -103,8 +103,12 @@ module Routable
|
|||
def full_path
|
||||
return uncached_full_path unless RequestStore.active?
|
||||
|
||||
key = "routable/full_path/#{self.class.name}/#{self.id}"
|
||||
RequestStore[key] ||= uncached_full_path
|
||||
RequestStore[full_path_key] ||= uncached_full_path
|
||||
end
|
||||
|
||||
def expires_full_path_cache
|
||||
RequestStore.delete(full_path_key) if RequestStore.active?
|
||||
@full_path = nil
|
||||
end
|
||||
|
||||
def build_full_path
|
||||
|
@ -135,6 +139,10 @@ module Routable
|
|||
path_changed? || parent_changed?
|
||||
end
|
||||
|
||||
def full_path_key
|
||||
@full_path_key ||= "routable/full_path/#{self.class.name}/#{self.id}"
|
||||
end
|
||||
|
||||
def build_full_name
|
||||
if parent && name
|
||||
parent.human_name + ' / ' + name
|
||||
|
|
|
@ -5,6 +5,25 @@
|
|||
module Sortable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module DropDefaultScopeOnFinders
|
||||
# Override these methods to drop the `ORDER BY id DESC` default scope.
|
||||
# See http://dba.stackexchange.com/a/110919 for why we do this.
|
||||
%i[find find_by find_by!].each do |meth|
|
||||
define_method meth do |*args, &block|
|
||||
return super(*args, &block) if block
|
||||
|
||||
unordered_relation = unscope(:order)
|
||||
|
||||
# We cannot simply call `meth` on `unscope(:order)`, since that is also
|
||||
# an instance of the same relation class this module is included into,
|
||||
# which means we'd get infinite recursion.
|
||||
# We explicitly use the original implementation to prevent this.
|
||||
original_impl = method(__method__).super_method.unbind
|
||||
original_impl.bind(unordered_relation).call(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
# By default all models should be ordered
|
||||
# by created_at field starting from newest
|
||||
|
@ -18,6 +37,10 @@ module Sortable
|
|||
scope :order_updated_asc, -> { reorder(updated_at: :asc) }
|
||||
scope :order_name_asc, -> { reorder(name: :asc) }
|
||||
scope :order_name_desc, -> { reorder(name: :desc) }
|
||||
|
||||
# All queries (relations) on this model are instances of this `relation_klass`.
|
||||
relation_klass = relation_delegate_class(ActiveRecord::Relation)
|
||||
relation_klass.prepend DropDefaultScopeOnFinders
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class ForkedProjectLink < ActiveRecord::Base
|
||||
belongs_to :forked_to_project, class_name: 'Project'
|
||||
belongs_to :forked_from_project, class_name: 'Project'
|
||||
belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
|
||||
belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
|
||||
end
|
||||
|
|
|
@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base
|
|||
}
|
||||
end
|
||||
|
||||
# This method is needed for compatibility with issues to not mess view and other code
|
||||
# These method are needed for compatibility with issues to not mess view and other code
|
||||
def assignees
|
||||
Array(assignee)
|
||||
end
|
||||
|
||||
def assignee_ids
|
||||
Array(assignee_id)
|
||||
end
|
||||
|
||||
def assignee_ids=(ids)
|
||||
write_attribute(:assignee_id, ids.last)
|
||||
end
|
||||
|
||||
def assignee_or_author?(user)
|
||||
author_id == user.id || assignee_id == user.id
|
||||
end
|
||||
|
|
|
@ -825,7 +825,7 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def ci_service
|
||||
@ci_service ||= ci_services.reorder(nil).find_by(active: true)
|
||||
@ci_service ||= ci_services.find_by(active: true)
|
||||
end
|
||||
|
||||
def deployment_services
|
||||
|
@ -833,7 +833,7 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def deployment_service
|
||||
@deployment_service ||= deployment_services.reorder(nil).find_by(active: true)
|
||||
@deployment_service ||= deployment_services.find_by(active: true)
|
||||
end
|
||||
|
||||
def monitoring_services
|
||||
|
@ -841,7 +841,7 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def monitoring_service
|
||||
@monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
|
||||
@monitoring_service ||= monitoring_services.find_by(active: true)
|
||||
end
|
||||
|
||||
def jira_tracker?
|
||||
|
@ -973,6 +973,7 @@ class Project < ActiveRecord::Base
|
|||
begin
|
||||
gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
|
||||
send_move_instructions(old_path_with_namespace)
|
||||
expires_full_path_cache
|
||||
|
||||
@old_path_with_namespace = old_path_with_namespace
|
||||
|
||||
|
@ -1084,21 +1085,21 @@ class Project < ActiveRecord::Base
|
|||
merge_requests.where(source_project_id: self.id)
|
||||
end
|
||||
|
||||
def create_repository
|
||||
def create_repository(force: false)
|
||||
# Forked import is handled asynchronously
|
||||
unless forked?
|
||||
if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
|
||||
repository.after_create
|
||||
true
|
||||
else
|
||||
errors.add(:base, 'Failed to create repository via gitlab-shell')
|
||||
false
|
||||
end
|
||||
return if forked? && !force
|
||||
|
||||
if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
|
||||
repository.after_create
|
||||
true
|
||||
else
|
||||
errors.add(:base, 'Failed to create repository via gitlab-shell')
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_repository
|
||||
create_repository unless repository_exists?
|
||||
create_repository(force: true) unless repository_exists?
|
||||
end
|
||||
|
||||
def repository_exists?
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require_dependency 'declarative_policy'
|
||||
|
||||
class BasePolicy < DeclarativePolicy::Base
|
||||
include Gitlab::CurrentSettings
|
||||
|
||||
desc "User is an instance admin"
|
||||
with_options scope: :user, score: 0
|
||||
condition(:admin) { @user&.admin? }
|
||||
|
@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base
|
|||
|
||||
with_options scope: :user, score: 0
|
||||
condition(:can_create_group) { @user&.can_create_group }
|
||||
|
||||
desc "The application is restricted from public visibility"
|
||||
condition(:restricted_public_level, scope: :global) do
|
||||
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
|
|||
with_options scope: :user, score: 0
|
||||
condition(:access_locked) { @user.access_locked? }
|
||||
|
||||
rule { anonymous }.prevent_all
|
||||
rule { anonymous }.policy do
|
||||
prevent :log_in
|
||||
prevent :access_api
|
||||
prevent :access_git
|
||||
prevent :receive_notifications
|
||||
prevent :use_quick_actions
|
||||
prevent :create_group
|
||||
end
|
||||
|
||||
rule { default }.policy do
|
||||
enable :read_users_list
|
||||
enable :log_in
|
||||
enable :access_api
|
||||
enable :access_git
|
||||
|
@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
|
|||
rule { access_locked }.policy do
|
||||
prevent :log_in
|
||||
end
|
||||
|
||||
rule { ~restricted_public_level }.policy do
|
||||
enable :read_users_list
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
class UserPolicy < BasePolicy
|
||||
include Gitlab::CurrentSettings
|
||||
|
||||
desc "The application is restricted from public visibility"
|
||||
condition(:restricted_public_level, scope: :global) do
|
||||
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
|
||||
end
|
||||
|
||||
desc "The current user is the user in question"
|
||||
condition(:user_is_self, score: 0) { @subject == @user }
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@ class AccessTokenValidationService
|
|||
REVOKED = :revoked
|
||||
INSUFFICIENT_SCOPE = :insufficient_scope
|
||||
|
||||
attr_reader :token
|
||||
attr_reader :token, :request
|
||||
|
||||
def initialize(token)
|
||||
def initialize(token, request: nil)
|
||||
@token = token
|
||||
@request = request
|
||||
end
|
||||
|
||||
def validate(scopes: [])
|
||||
|
@ -27,12 +28,23 @@ class AccessTokenValidationService
|
|||
end
|
||||
|
||||
# True if the token's scope contains any of the passed scopes.
|
||||
def include_any_scope?(scopes)
|
||||
if scopes.blank?
|
||||
def include_any_scope?(required_scopes)
|
||||
if required_scopes.blank?
|
||||
true
|
||||
else
|
||||
# Check whether the token is allowed access to any of the required scopes.
|
||||
Set.new(scopes).intersection(Set.new(token.scopes)).present?
|
||||
# We're comparing each required_scope against all token scopes, which would
|
||||
# take quadratic time. This consideration is irrelevant here because of the
|
||||
# small number of records involved.
|
||||
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12300/#note_33689006
|
||||
token_scopes = token.scopes.map(&:to_sym)
|
||||
|
||||
required_scopes.any? do |scope|
|
||||
if scope.respond_to?(:sufficient?)
|
||||
scope.sufficient?(token_scopes, request)
|
||||
else
|
||||
API::Scope.new(scope).sufficient?(token_scopes, request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService
|
|||
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
|
||||
# Prevent deletion of branches relevant to open merge requests
|
||||
branches -= merge_request_branch_names
|
||||
# Prevent deletion of protected branches
|
||||
branches -= project.protected_branches.pluck(:name)
|
||||
|
||||
branches.each do |branch|
|
||||
DeleteBranchService.new(project, current_user).execute(branch)
|
||||
|
|
|
@ -61,8 +61,12 @@ module MergeRequests
|
|||
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
|
||||
|
||||
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
|
||||
DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
|
||||
.execute(merge_request.source_branch)
|
||||
# Verify again that the source branch can be removed, since branch may be protected,
|
||||
# or the source branch may have been updated.
|
||||
if @merge_request.can_remove_source_branch?(branch_deletion_user)
|
||||
DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
|
||||
.execute(merge_request.source_branch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ module Projects
|
|||
Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
|
||||
|
||||
project.old_path_with_namespace = @old_path
|
||||
project.expires_full_path_cache
|
||||
|
||||
execute_system_hooks
|
||||
end
|
||||
|
|
|
@ -92,9 +92,12 @@ module QuickActions
|
|||
|
||||
desc 'Assign'
|
||||
explanation do |users|
|
||||
"Assigns #{users.first.to_reference}." if users.any?
|
||||
users = issuable.allows_multiple_assignees? ? users : users.take(1)
|
||||
"Assigns #{users.map(&:to_reference).to_sentence}."
|
||||
end
|
||||
params do
|
||||
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
|
||||
end
|
||||
params '@user'
|
||||
condition do
|
||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||
end
|
||||
|
@ -104,28 +107,69 @@ module QuickActions
|
|||
command :assign do |users|
|
||||
next if users.empty?
|
||||
|
||||
if issuable.is_a?(Issue)
|
||||
@updates[:assignee_ids] = [users.last.id]
|
||||
else
|
||||
@updates[:assignee_id] = users.last.id
|
||||
end
|
||||
@updates[:assignee_ids] =
|
||||
if issuable.allows_multiple_assignees?
|
||||
issuable.assignees.pluck(:id) + users.map(&:id)
|
||||
else
|
||||
[users.last.id]
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Remove assignee'
|
||||
desc do
|
||||
if issuable.allows_multiple_assignees?
|
||||
'Remove all or specific assignee(s)'
|
||||
else
|
||||
'Remove assignee'
|
||||
end
|
||||
end
|
||||
explanation do
|
||||
"Removes assignee #{issuable.assignees.first.to_reference}."
|
||||
"Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
|
||||
end
|
||||
params do
|
||||
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
|
||||
end
|
||||
condition do
|
||||
issuable.persisted? &&
|
||||
issuable.assignees.any? &&
|
||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||
end
|
||||
command :unassign do
|
||||
if issuable.is_a?(Issue)
|
||||
@updates[:assignee_ids] = []
|
||||
else
|
||||
@updates[:assignee_id] = nil
|
||||
end
|
||||
parse_params do |unassign_param|
|
||||
# When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
|
||||
extract_users(unassign_param) if issuable.allows_multiple_assignees?
|
||||
end
|
||||
command :unassign do |users = nil|
|
||||
@updates[:assignee_ids] =
|
||||
if users&.any?
|
||||
issuable.assignees.pluck(:id) - users.map(&:id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
desc do
|
||||
"Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
|
||||
end
|
||||
explanation do |users|
|
||||
users = issuable.allows_multiple_assignees? ? users : users.take(1)
|
||||
"Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
|
||||
end
|
||||
params do
|
||||
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
|
||||
end
|
||||
condition do
|
||||
issuable.persisted? &&
|
||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
|
||||
end
|
||||
parse_params do |assignee_param|
|
||||
extract_users(assignee_param)
|
||||
end
|
||||
command :reassign do |users|
|
||||
@updates[:assignee_ids] =
|
||||
if issuable.allows_multiple_assignees?
|
||||
users.map(&:id)
|
||||
else
|
||||
[users.last.id]
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Set milestone'
|
||||
|
|
|
@ -32,13 +32,16 @@
|
|||
#{time_ago_in_words(runner.contacted_at)} ago
|
||||
- else
|
||||
Never
|
||||
%td
|
||||
.pull-right
|
||||
= link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm'
|
||||
%td.admin-runner-btn-group-cell
|
||||
.pull-right.btn-group
|
||||
= link_to admin_runner_path(runner), class: 'btn btn-sm btn-default has-tooltip', title: 'Edit', ref: 'tooltip', aria: { label: 'Edit' }, data: { placement: 'top', container: 'body'} do
|
||||
= icon('pencil')
|
||||
|
||||
- if runner.active?
|
||||
= link_to 'Pause', [:pause, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
|
||||
= link_to [:pause, :admin, runner], method: :get, class: 'btn btn-sm btn-default has-tooltip', title: 'Pause', ref: 'tooltip', aria: { label: 'Pause' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
|
||||
= icon('pause')
|
||||
- else
|
||||
= link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
|
||||
= link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
|
||||
|
||||
= link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default btn-sm has-tooltip', title: 'Resume', ref: 'tooltip', aria: { label: 'Resume' }, data: { placement: 'top', container: 'body'} do
|
||||
= icon('play')
|
||||
= link_to [:admin, runner], method: :delete, class: 'btn btn-danger btn-sm has-tooltip', title: 'Remove', ref: 'tooltip', aria: { label: 'Remove' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
|
||||
= icon('remove')
|
||||
|
|
|
@ -6,4 +6,7 @@
|
|||
- providers.each do |provider|
|
||||
%span.light
|
||||
- has_icon = provider_has_icon?(provider)
|
||||
= link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn')
|
||||
= link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
|
||||
%fieldset
|
||||
= check_box_tag :remember_me
|
||||
= label_tag :remember_me, 'Remember Me'
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
%div
|
||||
- if Gitlab::Recaptcha.enabled?
|
||||
= recaptcha_tags
|
||||
%div
|
||||
.submit-container
|
||||
= f.submit "Register", class: "btn-register btn"
|
||||
.clearfix.submit-container
|
||||
%p
|
||||
|
|
|
@ -45,10 +45,13 @@
|
|||
.panel.panel-danger
|
||||
.panel-heading Remove group
|
||||
.panel-body
|
||||
%p
|
||||
Removing group will cause all child projects and resources to be removed.
|
||||
%br
|
||||
%strong Removed group can not be restored!
|
||||
= form_tag(@group, method: :delete) do
|
||||
%p
|
||||
Removing group will cause all child projects and resources to be removed.
|
||||
%br
|
||||
%strong Removed group can not be restored!
|
||||
|
||||
.form-actions
|
||||
= link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
|
||||
.form-actions
|
||||
= button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) }
|
||||
|
||||
= render 'shared/confirm_modal', phrase: @group.path
|
||||
|
|
|
@ -74,9 +74,8 @@
|
|||
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
|
||||
%li
|
||||
= link_to "Settings", profile_path
|
||||
- if can_toggle_new_nav?
|
||||
%li
|
||||
= link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
|
||||
%li
|
||||
= link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
|
||||
%li.divider
|
||||
%li
|
||||
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
|
||||
|
|
|
@ -16,25 +16,22 @@
|
|||
.preview= image_tag "#{scheme.css_class}-scheme-preview.png"
|
||||
= f.radio_button :color_scheme_id, scheme.id
|
||||
= scheme.name
|
||||
- if can_toggle_new_nav?
|
||||
.col-sm-12
|
||||
%hr
|
||||
.col-lg-3.profile-settings-sidebar#new-navigation
|
||||
%h4.prepend-top-0
|
||||
New Navigation
|
||||
%p
|
||||
This setting allows you to turn on or off the new upcoming navigation concept.
|
||||
= succeed '.' do
|
||||
= link_to 'Learn more', '', target: '_blank'
|
||||
.col-lg-9.syntax-theme
|
||||
= label_tag do
|
||||
.preview= image_tag "old_nav.png"
|
||||
%input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
|
||||
Old
|
||||
= label_tag do
|
||||
.preview= image_tag "new_nav.png"
|
||||
%input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
|
||||
New
|
||||
.col-sm-12
|
||||
%hr
|
||||
.col-lg-4.profile-settings-sidebar#new-navigation
|
||||
%h4.prepend-top-0
|
||||
New Navigation
|
||||
%p
|
||||
This setting allows you to turn on or off the new upcoming navigation concept.
|
||||
.col-lg-8.syntax-theme
|
||||
= label_tag do
|
||||
.preview= image_tag "old_nav.png"
|
||||
%input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
|
||||
Old
|
||||
= label_tag do
|
||||
.preview= image_tag "new_nav.png"
|
||||
%input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
|
||||
New
|
||||
.col-sm-12
|
||||
%hr
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
|
|
|
@ -23,4 +23,5 @@
|
|||
= render "projects/boards/components/sidebar/labels"
|
||||
= render "projects/boards/components/sidebar/notifications"
|
||||
%remove-btn{ ":issue" => "issue",
|
||||
":list" => "list" }
|
||||
":list" => "list",
|
||||
"v-if" => "canRemove" }
|
||||
|
|
|
@ -19,10 +19,11 @@
|
|||
":data-name" => "assignee.name",
|
||||
":data-username" => "assignee.username" }
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } },
|
||||
- dropdown_options = issue_assignees_dropdown_options
|
||||
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
|
||||
":data-issuable-id" => "issue.id",
|
||||
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
|
||||
Select assignee
|
||||
= dropdown_options[:title]
|
||||
= icon("chevron-down")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
|
||||
= dropdown_title("Assign to")
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
.top-area
|
||||
.row
|
||||
.col-sm-6
|
||||
%h3.page-title
|
||||
%h3
|
||||
Environment:
|
||||
= link_to @environment.name, environment_path(@environment)
|
||||
|
||||
|
|
|
@ -54,13 +54,14 @@
|
|||
- else
|
||||
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
|
||||
|
||||
.build-trace-container#build-trace
|
||||
.top-bar.sticky
|
||||
.build-trace-container.prepend-top-default
|
||||
.top-bar.js-top-bar
|
||||
.js-truncated-info.truncated-info.hidden<
|
||||
Showing last
|
||||
%span.js-truncated-info-size.truncated-info-size><
|
||||
KiB of log -
|
||||
%a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw
|
||||
|
||||
.controllers
|
||||
- if @build.has_trace?
|
||||
= link_to raw_namespace_project_job_path(@project.namespace, @project, @build),
|
||||
|
@ -82,10 +83,12 @@
|
|||
.has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
|
||||
%button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
|
||||
= custom_icon('scroll_down')
|
||||
.bash.sticky.js-scroll-container
|
||||
%code.js-build-output
|
||||
|
||||
%pre.build-trace#build-trace
|
||||
%code.bash.js-build-output
|
||||
.build-loader-animation.js-build-refresh
|
||||
|
||||
|
||||
= render "sidebar"
|
||||
|
||||
.js-build-options{ data: javascript_build_options }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- @no_container = true
|
||||
- page_title "Charts", "Pipelines"
|
||||
- page_title _("Charts"), _("Pipelines")
|
||||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_bundle_tag('common_d3')
|
||||
= page_specific_javascript_bundle_tag('graphs')
|
||||
|
@ -8,7 +8,7 @@
|
|||
%div{ class: container_class }
|
||||
.sub-header-block
|
||||
.oneline
|
||||
A collection of graphs for Continuous Integration
|
||||
= _("A collection of graphs regarding Continuous Integration")
|
||||
|
||||
#charts.ci-charts
|
||||
.row
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
%h4 Overall stats
|
||||
%h4= s_("PipelineCharts|Overall statistics")
|
||||
%ul
|
||||
%li
|
||||
Total:
|
||||
%strong= pluralize @counts[:total], 'pipeline'
|
||||
= s_("PipelineCharts|Total:")
|
||||
%strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
|
||||
%li
|
||||
Successful:
|
||||
%strong= pluralize @counts[:success], 'pipeline'
|
||||
= s_("PipelineCharts|Successful:")
|
||||
%strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
|
||||
%li
|
||||
Failed:
|
||||
%strong= pluralize @counts[:failed], 'pipeline'
|
||||
= s_("PipelineCharts|Failed:")
|
||||
%strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
|
||||
%li
|
||||
Success ratio:
|
||||
= s_("PipelineCharts|Success ratio:")
|
||||
%strong
|
||||
#{success_ratio(@counts)}%
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
%div
|
||||
%p.light
|
||||
Commit duration in minutes for last 30 commits
|
||||
= _("Commit duration in minutes for last 30 commits")
|
||||
|
||||
%canvas#build_timesChart{ height: 200 }
|
||||
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
%h4 Pipelines charts
|
||||
%h4= _("Pipelines charts")
|
||||
%p
|
||||
|
||||
%span.cgreen
|
||||
= icon("circle")
|
||||
success
|
||||
= s_("Pipeline|success")
|
||||
|
||||
%span.cgray
|
||||
= icon("circle")
|
||||
all
|
||||
= s_("Pipeline|all")
|
||||
|
||||
.prepend-top-default
|
||||
%p.light
|
||||
Jobs for last week
|
||||
= _("Jobs for last week")
|
||||
(#{date_from_to(Date.today - 7.days, Date.today)})
|
||||
%canvas#weekChart{ height: 200 }
|
||||
|
||||
.prepend-top-default
|
||||
%p.light
|
||||
Jobs for last month
|
||||
= _("Jobs for last month")
|
||||
(#{date_from_to(Date.today - 30.days, Date.today)})
|
||||
%canvas#monthChart{ height: 200 }
|
||||
|
||||
.prepend-top-default
|
||||
%p.light
|
||||
Jobs for last year
|
||||
= _("Jobs for last year")
|
||||
%canvas#yearChart.padded{ height: 250 }
|
||||
|
||||
- [:week, :month, :year].each do |scope|
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', branch_name: 'auto-deploy', context: 'autodeploy') do
|
||||
#{ _('Set up auto deploy') }
|
||||
|
||||
%div{ class: container_class }
|
||||
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
|
||||
- if @project.archived?
|
||||
.text-warning.center.prepend-top-20
|
||||
%p
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
.scroll-container
|
||||
%ul.tokens-container.list-unstyled
|
||||
%li.input-token
|
||||
%input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
|
||||
%input.form-control.filtered-search{ search_filter_input_options(type) }
|
||||
= icon('filter')
|
||||
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
||||
%ul{ data: { dropdown: true } }
|
||||
|
|
|
@ -37,19 +37,20 @@
|
|||
- issuable.assignees.each do |assignee|
|
||||
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
|
||||
|
||||
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
|
||||
|
||||
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
|
||||
- title = 'Select assignee'
|
||||
|
||||
- if issuable.is_a?(Issue)
|
||||
- unless issuable.assignees.any?
|
||||
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
|
||||
- dropdown_options = issue_assignees_dropdown_options
|
||||
- title = dropdown_options[:title]
|
||||
- options[:toggle_class] += ' js-multiselect js-save-user-data'
|
||||
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
|
||||
- data[:multi_select] = true
|
||||
- data['dropdown-title'] = title
|
||||
- data['dropdown-header'] = 'Assignee'
|
||||
- data['max-select'] = 1
|
||||
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
|
||||
- data['max-select'] = dropdown_options[:data][:'max-select']
|
||||
- options[:data].merge!(data)
|
||||
|
||||
= dropdown_tag(title, options: options)
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
.col-sm-10.col-sm-offset-2
|
||||
- if issuable.can_remove_source_branch?(current_user)
|
||||
.checkbox
|
||||
- initial_checkbox_value = issuable.merge_params.key?('force_remove_source_branch') ? issuable.force_remove_source_branch? : true
|
||||
= label_tag 'merge_request[force_remove_source_branch]' do
|
||||
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
|
||||
= check_box_tag 'merge_request[force_remove_source_branch]', '1', initial_checkbox_value
|
||||
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?
|
||||
Remove source branch when merge request is accepted.
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
- if issuable.assignees.length === 0
|
||||
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
|
||||
|
||||
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false))
|
||||
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
|
||||
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
- model_name = source.model_name.to_s.downcase
|
||||
|
||||
.project-action-button.inline
|
||||
- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
|
||||
- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
|
||||
.project-action-button.inline
|
||||
- link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
|
||||
= link_to link_text, polymorphic_path([:leave, source, :members]),
|
||||
method: :delete,
|
||||
data: { confirm: leave_confirmation_message(source) },
|
||||
class: 'btn'
|
||||
- elsif requester = source.requesters.find_by(user_id: current_user.id)
|
||||
- elsif requester = source.requesters.find_by(user_id: current_user.id)
|
||||
.project-action-button.inline
|
||||
= link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
|
||||
method: :delete,
|
||||
data: { confirm: remove_member_message(requester) },
|
||||
class: 'btn'
|
||||
- elsif source.request_access_enabled && can?(current_user, :request_access, source)
|
||||
- elsif source.request_access_enabled && can?(current_user, :request_access, source)
|
||||
.project-action-button.inline
|
||||
= link_to _('Request Access'), polymorphic_path([:request_access, source, :members]),
|
||||
method: :post,
|
||||
class: 'btn'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Honor the "Remember me" parameter for OAuth-based login
|
||||
merge_request: 11963
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Replace 'dashboard/new-project.feature' spinach with rspec
|
||||
merge_request: 12550
|
||||
author: Alexander Randa (@randaalex)
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix spacing on runner buttons.
|
||||
merge_request: !12535
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Set default for Remove source branch to false.
|
||||
merge_request: !12576
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: "Remove group modal like remove project modal (requires typing + confirmation)"
|
||||
merge_request: 12569
|
||||
author: Diego Souza
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix API Scoping
|
||||
merge_request: 12300
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Remove "Remove from board" button from backlog and closed list
|
||||
merge_request: 12430
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Change milestone endpoint for groups
|
||||
merge_request: 12374
|
||||
author: Takuya Noguchi
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Allow unauthenticated access to the /api/v4/users API
|
||||
merge_request: 12445
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Update jobs page output to have a scrollable page
|
||||
merge_request: 12587
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page
|
||||
merge_request: 12578
|
||||
author: Huang Tao
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Rename duplicated variables with the same key for projects. Add environment_scope
|
||||
column to variables and add unique constraint to make sure that no variables could
|
||||
be created with the same key within a project
|
||||
merge_request: 12363
|
||||
author:
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Prevent accidental deletion of protected MR source branch by repeating checks
|
||||
before actual deletion
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix issues with non-UTF8 filenames by always fixing the encoding of tree and
|
||||
blob paths
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname
|
||||
merge_request: 11218
|
||||
author: Stefan Hanreich
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Expires full_path cache after a repository is renamed/transferred
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Hide archived project labels from group issue tracker
|
||||
merge_request: 12547
|
||||
author: Horacio Bertorello
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Improve the overall UX for the new monitoring dashboard
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Make Project#ensure_repository force create a repo
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Log rescued exceptions to Sentry
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
|
||||
merge_request:
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Do not delete protected branches when deleting all merged branches
|
||||
merge_request: 12624
|
||||
author:
|
|
@ -543,6 +543,10 @@ production: &base
|
|||
# enabled: true
|
||||
# host: localhost
|
||||
# port: 3808
|
||||
prometheus:
|
||||
# Time between sampling of unicorn socket metrics, in seconds
|
||||
# unicorn_sampler_interval: 10
|
||||
|
||||
|
||||
#
|
||||
# 5. Extra customization
|
||||
|
@ -615,6 +619,53 @@ test:
|
|||
title: "JIRA"
|
||||
url: https://sample_company.atlassian.net
|
||||
project_key: PROJECT
|
||||
|
||||
omniauth:
|
||||
enabled: true
|
||||
allow_single_sign_on: true
|
||||
external_providers: []
|
||||
|
||||
providers:
|
||||
- { name: 'cas3',
|
||||
label: 'cas3',
|
||||
args: { url: 'https://sso.example.com',
|
||||
disable_ssl_verification: false,
|
||||
login_url: '/cas/login',
|
||||
service_validate_url: '/cas/p3/serviceValidate',
|
||||
logout_url: '/cas/logout'} }
|
||||
- { name: 'authentiq',
|
||||
app_id: 'YOUR_CLIENT_ID',
|
||||
app_secret: 'YOUR_CLIENT_SECRET',
|
||||
args: { scope: 'aq:name email~rs address aq:push' } }
|
||||
- { name: 'github',
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET',
|
||||
url: "https://github.com/",
|
||||
verify_ssl: false,
|
||||
args: { scope: 'user:email' } }
|
||||
- { name: 'bitbucket',
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET' }
|
||||
- { name: 'gitlab',
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET',
|
||||
args: { scope: 'api' } }
|
||||
- { name: 'google_oauth2',
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET',
|
||||
args: { access_type: 'offline', approval_prompt: '' } }
|
||||
- { name: 'facebook',
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET' }
|
||||
- { name: 'twitter',
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET' }
|
||||
- { name: 'auth0',
|
||||
args: {
|
||||
client_id: 'YOUR_AUTH0_CLIENT_ID',
|
||||
client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
|
||||
namespace: 'YOUR_AUTH0_DOMAIN' } }
|
||||
|
||||
ldap:
|
||||
enabled: false
|
||||
servers:
|
||||
|
|
|
@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false
|
|||
Settings.webpack.dev_server['host'] ||= 'localhost'
|
||||
Settings.webpack.dev_server['port'] ||= 3808
|
||||
|
||||
#
|
||||
# Prometheus metrics settings
|
||||
#
|
||||
Settings['prometheus'] ||= Settingslogic.new({})
|
||||
Settings.prometheus['unicorn_sampler_interval'] ||= 10
|
||||
|
||||
#
|
||||
# Testing settings
|
||||
#
|
||||
|
|
|
@ -119,6 +119,13 @@ def instrument_classes(instrumentation)
|
|||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start
|
||||
|
||||
Gitlab::Application.configure do |config|
|
||||
# 0 should be Sentry to catch errors in this middleware
|
||||
config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware)
|
||||
end
|
||||
|
||||
if Gitlab::Metrics.enabled?
|
||||
require 'pathname'
|
||||
require 'influxdb'
|
||||
|
@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled?
|
|||
|
||||
GC::Profiler.enable
|
||||
|
||||
Gitlab::Metrics::Sampler.new.start
|
||||
Gitlab::Metrics::InfluxSampler.initialize_instance.start
|
||||
|
||||
module TrackNewRedisConnections
|
||||
def connect(*args)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
class RenameDuplicatedVariableKey < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
execute(<<~SQL)
|
||||
UPDATE ci_variables
|
||||
SET #{key} = CONCAT(#{key}, #{underscore}, id)
|
||||
WHERE id IN (
|
||||
SELECT *
|
||||
FROM ( -- MySQL requires an extra layer
|
||||
SELECT dup.id
|
||||
FROM ci_variables dup
|
||||
INNER JOIN (SELECT max(id) AS id, #{key}, project_id
|
||||
FROM ci_variables tmp
|
||||
GROUP BY #{key}, project_id) var
|
||||
USING (#{key}, project_id) where dup.id <> var.id
|
||||
) dummy
|
||||
)
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
# noop
|
||||
end
|
||||
|
||||
def key
|
||||
# key needs to be quoted in MySQL
|
||||
quote_column_name('key')
|
||||
end
|
||||
|
||||
def underscore
|
||||
quote('_')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
class AddEnvironmentScopeToCiVariables < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_column_with_default(:ci_variables, :environment_scope, :string, default: '*')
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column(:ci_variables, :environment_scope)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
class AddUniqueConstraintToCiVariables < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
unless this_index_exists?
|
||||
add_concurrent_index(:ci_variables, columns, name: index_name, unique: true)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
if this_index_exists?
|
||||
if Gitlab::Database.mysql? && !index_exists?(:ci_variables, :project_id)
|
||||
# Need to add this index for MySQL project_id foreign key constraint
|
||||
add_concurrent_index(:ci_variables, :project_id)
|
||||
end
|
||||
|
||||
remove_concurrent_index(:ci_variables, columns, name: index_name)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def this_index_exists?
|
||||
index_exists?(:ci_variables, columns, name: index_name)
|
||||
end
|
||||
|
||||
def columns
|
||||
@columns ||= [:project_id, :key, :environment_scope]
|
||||
end
|
||||
|
||||
def index_name
|
||||
'index_ci_variables_on_project_id_and_key_and_environment_scope'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
class RemoveCiVariablesProjectIdIndex < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
if index_exists?(:ci_variables, :project_id)
|
||||
remove_concurrent_index(:ci_variables, :project_id)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
unless index_exists?(:ci_variables, :project_id)
|
||||
add_concurrent_index(:ci_variables, :project_id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
unless index_exists?(:ci_builds, :stage_id)
|
||||
add_concurrent_index(:ci_builds, :stage_id)
|
||||
end
|
||||
|
||||
unless foreign_key_exists?(:ci_builds, :stage_id)
|
||||
add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
if foreign_key_exists?(:ci_builds, :stage_id)
|
||||
remove_foreign_key(:ci_builds, column: :stage_id)
|
||||
end
|
||||
|
||||
if index_exists?(:ci_builds, :stage_id)
|
||||
remove_concurrent_index(:ci_builds, :stage_id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def foreign_key_exists?(table, column)
|
||||
foreign_keys(:ci_builds).any? do |key|
|
||||
key.options[:column] == column.to_s
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,19 +3,15 @@ class AddStageIdIndexToBuilds < ActiveRecord::Migration
|
|||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
##
|
||||
# Improved in 20170703102400_add_stage_id_foreign_key_to_builds.rb
|
||||
#
|
||||
|
||||
def up
|
||||
unless index_exists?(:ci_builds, :stage_id)
|
||||
add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
|
||||
add_concurrent_index(:ci_builds, :stage_id)
|
||||
end
|
||||
# noop
|
||||
end
|
||||
|
||||
def down
|
||||
if index_exists?(:ci_builds, :stage_id)
|
||||
remove_foreign_key(:ci_builds, column: :stage_id)
|
||||
remove_concurrent_index(:ci_builds, :stage_id)
|
||||
end
|
||||
# noop
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20170622162730) do
|
||||
ActiveRecord::Schema.define(version: 20170703102400) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -374,9 +374,10 @@ ActiveRecord::Schema.define(version: 20170622162730) do
|
|||
t.string "encrypted_value_iv"
|
||||
t.integer "project_id", null: false
|
||||
t.boolean "protected", default: false, null: false
|
||||
t.string "environment_scope", default: "*", null: false
|
||||
end
|
||||
|
||||
add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree
|
||||
add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
|
||||
|
||||
create_table "container_repositories", force: :cascade do |t|
|
||||
t.integer "project_id", null: false
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue