Commit 33a5157a authored by Lin Jen-Shin's avatar Lin Jen-Shin

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
  ...
parents 9f5ac179 98768953
...@@ -474,8 +474,6 @@ codeclimate: ...@@ -474,8 +474,6 @@ codeclimate:
services: services:
- docker:dind - docker:dind
script: 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 - 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 - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
artifacts: artifacts:
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 9.3.4 (2017-07-03)
- No changes.
## 9.3.3 (2017-06-30) ## 9.3.3 (2017-06-30)
- Fix head pipeline stored in merge request for external pipelines. !12478 - Fix head pipeline stored in merge request for external pipelines. !12478
......
...@@ -255,7 +255,7 @@ gem 'net-ssh', '~> 3.0.1' ...@@ -255,7 +255,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0' gem 'base32', '~> 0.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 2.4.0' gem 'sentry-raven', '~> 2.5.3'
gem 'premailer-rails', '~> 1.9.7' gem 'premailer-rails', '~> 1.9.7'
...@@ -285,6 +285,7 @@ group :metrics do ...@@ -285,6 +285,7 @@ group :metrics do
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta5' gem 'prometheus-client-mmap', '~>0.7.0.beta5'
gem 'raindrops', '~> 0.18'
end end
group :development do group :development do
......
...@@ -599,8 +599,8 @@ GEM ...@@ -599,8 +599,8 @@ GEM
premailer-rails (1.9.7) premailer-rails (1.9.7)
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta5) prometheus-client-mmap (0.7.0.beta8)
mmap2 (~> 2.2.6) mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -658,7 +658,7 @@ GEM ...@@ -658,7 +658,7 @@ GEM
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.17.0) raindrops (0.18.0)
rake (10.5.0) rake (10.5.0)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
...@@ -775,7 +775,7 @@ GEM ...@@ -775,7 +775,7 @@ GEM
activesupport (>= 3.1) activesupport (>= 3.1)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
sentry-raven (2.4.0) sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.9.0) sexp_processor (4.9.0)
...@@ -1062,6 +1062,7 @@ DEPENDENCIES ...@@ -1062,6 +1062,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9) rails-i18n (~> 4.0.9)
rainbow (~> 2.2) rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
rdoc (~> 4.2) rdoc (~> 4.2)
recaptcha (~> 3.0) recaptcha (~> 3.0)
...@@ -1089,7 +1090,7 @@ DEPENDENCIES ...@@ -1089,7 +1090,7 @@ DEPENDENCIES
scss_lint (~> 0.47.0) scss_lint (~> 0.47.0)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
sentry-raven (~> 2.4.0) sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
......
import autosize from 'vendor/autosize'; import autosize from 'vendor/autosize';
$(() => { document.addEventListener('DOMContentLoaded', () => {
const $fields = $('.js-autosize'); const autosizeEls = document.querySelectorAll('.js-autosize');
$fields.on('autosize:resized', function resized() { autosize(autosizeEls);
const $field = $(this); autosize.update(autosizeEls);
$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');
}); });
...@@ -34,7 +34,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({ ...@@ -34,7 +34,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
}, },
milestoneTitle() { milestoneTitle() {
return this.issue.milestone ? this.issue.milestone.title : 'No Milestone'; return this.issue.milestone ? this.issue.milestone.title : 'No Milestone';
} },
canRemove() {
return !this.list.preset;
},
}, },
watch: { watch: {
detail: { detail: {
......
...@@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ ...@@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
}, },
template: ` template: `
<div <div
class="block list" class="block list">
v-if="list.type !== 'closed'">
<button <button
class="btn btn-default btn-block" class="btn btn-default btn-block"
type="button" type="button"
......
...@@ -13,25 +13,21 @@ window.Build = (function () { ...@@ -13,25 +13,21 @@ window.Build = (function () {
this.options = options || $('.js-build-options').data(); this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl; this.pageUrl = this.options.pageUrl;
this.buildUrl = this.options.buildUrl;
this.buildStatus = this.options.buildStatus; this.buildStatus = this.options.buildStatus;
this.state = this.options.logState; this.state = this.options.logState;
this.buildStage = this.options.buildStage; this.buildStage = this.options.buildStage;
this.$document = $(document); this.$document = $(document);
this.logBytes = 0; this.logBytes = 0;
this.scrollOffsetPadding = 30;
this.hasBeenScrolled = false; this.hasBeenScrolled = false;
this.updateDropdown = this.updateDropdown.bind(this); this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this); this.getBuildTrace = this.getBuildTrace.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
this.$body = $('body');
this.$buildTrace = $('#build-trace'); this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh'); this.$buildRefreshAnimation = $('.js-build-refresh');
this.$truncatedInfo = $('.js-truncated-info'); this.$truncatedInfo = $('.js-truncated-info');
this.$buildTraceOutput = $('.js-build-output'); this.$buildTraceOutput = $('.js-build-output');
this.$scrollContainer = $('.js-scroll-container'); this.$topBar = $('.js-top-bar');
// Scroll controllers // Scroll controllers
this.$scrollTopBtn = $('.js-scroll-up'); this.$scrollTopBtn = $('.js-scroll-up');
...@@ -63,13 +59,22 @@ window.Build = (function () { ...@@ -63,13 +59,22 @@ window.Build = (function () {
.off('click') .off('click')
.on('click', this.scrollToBottom.bind(this)); .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') .off('scroll')
.on('scroll', () => { .on('scroll', () => {
this.hasBeenScrolled = true; const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
scrollThrottled(); 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) $(window)
...@@ -77,59 +82,73 @@ window.Build = (function () { ...@@ -77,59 +82,73 @@ window.Build = (function () {
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100)); .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
this.updateArtifactRemoveDate(); this.updateArtifactRemoveDate();
this.initAffixTopArea();
// eslint-disable-next-line this.getBuildTrace();
this.getBuildTrace()
.then(() => this.toggleScroll())
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
})
.then(() => this.verifyTopPosition());
} }
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 () { Build.prototype.canScroll = function () {
return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height(); 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 () { Build.prototype.toggleScroll = function () {
const currentPosition = this.$scrollContainer.scrollTop(); const currentPosition = document.body.scrollTop;
const bottomScroll = currentPosition + this.$scrollContainer.innerHeight(); const windowHeight = window.innerHeight;
if (this.canScroll()) { 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.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false); 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.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, true); 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 () { Build.prototype.scrollDown = function () {
document.body.scrollTop = document.body.scrollHeight;
};
Build.prototype.scrollToBottom = function () {
this.scrollDown();
this.hasBeenScrolled = true; this.hasBeenScrolled = true;
this.$scrollContainer.scrollTop(0);
this.toggleScroll(); this.toggleScroll();
}; };
Build.prototype.scrollToBottom = function () { Build.prototype.scrollToTop = function () {
document.body.scrollTop = 0;
this.hasBeenScrolled = true; this.hasBeenScrolled = true;
this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
this.toggleScroll(); this.toggleScroll();
}; };
...@@ -142,47 +161,6 @@ window.Build = (function () { ...@@ -142,47 +161,6 @@ window.Build = (function () {
this.$scrollBottomBtn.toggleClass('animate', toggle); 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 () { Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar'); this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll(); this.$sidebar.niceScroll();
...@@ -200,6 +178,8 @@ window.Build = (function () { ...@@ -200,6 +178,8 @@ window.Build = (function () {
this.state = log.state; this.state = log.state;
} }
this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
if (log.append) { if (log.append) {
this.$buildTraceOutput.append(log.html); this.$buildTraceOutput.append(log.html);
this.logBytes += log.size; this.logBytes += log.size;
...@@ -227,14 +207,7 @@ window.Build = (function () { ...@@ -227,14 +207,7 @@ window.Build = (function () {
} }
Build.timeout = setTimeout(() => { Build.timeout = setTimeout(() => {
//eslint-disable-next-line this.getBuildTrace();
this.getBuildTrace()
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
})
.then(() => this.verifyTopPosition());
}, 4000); }, 4000);
} else { } else {
this.$buildRefreshAnimation.remove(); this.$buildRefreshAnimation.remove();
...@@ -247,7 +220,13 @@ window.Build = (function () { ...@@ -247,7 +220,13 @@ window.Build = (function () {
}) })
.fail(() => { .fail(() => {
this.$buildRefreshAnimation.remove(); this.$buildRefreshAnimation.remove();
}); })
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollDown();
}
})
.then(() => this.toggleScroll());
}; };
Build.prototype.shouldHideSidebarForViewport = function () { Build.prototype.shouldHideSidebarForViewport = function () {
...@@ -259,14 +238,11 @@ window.Build = (function () { ...@@ -259,14 +238,11 @@ window.Build = (function () {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined; const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header'); const $toggleButton = $('.js-sidebar-build-toggle-header');
this.$buildTrace
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
this.$sidebar this.$sidebar
.toggleClass('right-sidebar-expanded', shouldShow) .toggleClass('right-sidebar-expanded', shouldShow)
.toggleClass('right-sidebar-collapsed', shouldHide); .toggleClass('right-sidebar-collapsed', shouldHide);
$('.js-build-page') this.$topBar
.toggleClass('sidebar-expanded', shouldShow) .toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide); .toggleClass('sidebar-collapsed', shouldHide);
...@@ -279,17 +255,10 @@ window.Build = (function () { ...@@ -279,17 +255,10 @@ window.Build = (function () {
Build.prototype.sidebarOnResize = function () { Build.prototype.sidebarOnResize = function () {
this.toggleSidebar(this.shouldHideSidebarForViewport()); this.toggleSidebar(this.shouldHideSidebarForViewport());
this.verifyTopPosition();
if (this.canScroll()) {
this.toggleScroll();
}
}; };
Build.prototype.sidebarOnClick = function () { Build.prototype.sidebarOnClick = function () {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
this.verifyTopPosition();
}; };
Build.prototype.updateArtifactRemoveDate = function () { Build.prototype.updateArtifactRemoveDate = function () {
......
...@@ -56,6 +56,7 @@ import GfmAutoComplete from './gfm_auto_complete'; ...@@ -56,6 +56,7 @@ import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob'; import ShortcutsBlob from './shortcuts_blob';
import initSettingsPanels from './settings_panels'; import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags'; import initExperimentalFlags from './experimental_flags';
import OAuthRememberMe from './oauth_remember_me';
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -127,6 +128,7 @@ import initExperimentalFlags from './experimental_flags'; ...@@ -127,6 +128,7 @@ import initExperimentalFlags from './experimental_flags';
case 'sessions:new': case 'sessions:new':
new UsernameValidator(); new UsernameValidator();
new ActiveTabMemoizer(); new ActiveTabMemoizer();
new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
break; break;
case 'projects:boards:show': case 'projects:boards:show':
case 'projects:boards:index': case 'projects:boards:index':
......
...@@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
mounted() { mounted() {
this.mediator.initBuildClass(); this.mediator.initBuildClass();
}, },
updated() {
// Wait for flash message to be appended
Vue.nextTick(() => {
if (this.mediator.build) {
this.mediator.build.verifyTopPosition();
}
});
},
render(createElement) { render(createElement) {
return createElement('job-header', { return createElement('job-header', {
props: { props: {
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
data() { data() {
return { return {
graphHeight: 500, graphHeight: 450,
graphWidth: 600, graphWidth: 600,
graphHeightOffset: 120, graphHeightOffset: 120,
xScale: {}, xScale: {},
...@@ -88,7 +88,9 @@ ...@@ -88,7 +88,9 @@
}, },
paddingBottomRootSvg() { paddingBottomRootSvg() {
return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0; return {
paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
};
}, },
}, },
...@@ -104,7 +106,7 @@ ...@@ -104,7 +106,7 @@
} }
this.data = query.result[0].values; this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A'; 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.legendTitle = query.legend || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right; this.margin.left - this.margin.right;
...@@ -198,7 +200,7 @@ ...@@ -198,7 +200,7 @@
watch: { watch: {
updateAspectRatio() { updateAspectRatio() {
if (this.updateAspectRatio) { if (this.updateAspectRatio) {
this.graphHeight = 500; this.graphHeight = 450;
this.graphWidth = 600; this.graphWidth = 600;
this.measurements = measurements.large; this.measurements = measurements.large;
this.draw(); this.draw();
...@@ -216,14 +218,14 @@ ...@@ -216,14 +218,14 @@
<div <div
:class="classType"> :class="classType">
<h5 <h5
class="text-center"> class="text-center graph-title">
{{columnData.title}} {{columnData.title}}
</h5> </h5>
<div <div
class="prometheus-svg-container"> class="prometheus-svg-container"
:style="paddingBottomRootSvg">
<svg <svg
:viewBox="outterViewBox" :viewBox="outterViewBox"
:style="{ 'padding-bottom': paddingBottomRootSvg }"
ref="baseSvg"> ref="baseSvg">
<g <g
class="x-axis" class="x-axis"
......
/**
* 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) { ...@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: function(term, callback) { data: function(term, callback) {
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) { return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance // GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance // GitLabDropdownRemote returns this.options.instance
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
max-width: $limited-layout-width-sm; max-width: $limited-layout-width-sm;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding-top: 64px;
padding-bottom: 64px;
} }
} }
......
...@@ -37,65 +37,77 @@ ...@@ -37,65 +37,77 @@
} }
.build-page { .build-page {
.sticky { .build-trace-container {
position: absolute; position: relative;
left: 0;
right: 0;
} }
.build-trace-container { .build-trace {
position: absolute;
top: 225px;
left: 15px;
bottom: 10px;
background: $black; background: $black;
color: $gray-darkest; color: $gray-darkest;
font-family: $monospace_font; white-space: pre;
overflow-x: auto;
font-size: 12px; font-size: 12px;
border-radius: 0;
border: none;
&.sidebar-expanded { .bash {
right: 305px; display: block;
} }
}
&.sidebar-collapsed { .top-bar {
right: 16px; 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;
} }
code { // with sidebar
background: $black; &.affix.sidebar-expanded {
color: $gray-darkest; right: 306px;
left: 16px;
} }
.top-bar { // without sidebar
top: 0; &.affix.sidebar-collapsed {
height: 35px; right: 16px;
display: flex; left: 16px;
justify-content: flex-end; }
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
.truncated-info { &.affix-top {
margin: 0 auto; position: absolute;
align-self: center; right: 0;
left: 0;
}
.truncated-info-size { .truncated-info {
margin: 0 5px; margin: 0 auto;
} align-self: center;
.raw-link { .truncated-info-size {
color: $gl-text-color; margin: 0 5px;
margin-left: 5px; }
text-decoration: underline;
} .raw-link {
color: $gl-text-color;
margin-left: 5px;
text-decoration: underline;
} }
} }
.controllers { .controllers {
display: flex; display: flex;
align-self: center;
font-size: 15px; font-size: 15px;
margin-bottom: 4px; justify-content: center;
align-items: center;
svg { svg {
height: 15px; height: 15px;
...@@ -103,17 +115,9 @@ ...@@ -103,17 +115,9 @@
fill: $gl-text-color; fill: $gl-text-color;
} }
.controllers-buttons,
.btn-scroll {
color: $gl-text-color;
height: 15px;
vertical-align: middle;
padding: 0;
width: 12px;
}
.controllers-buttons { .controllers-buttons {
margin: 1px 10px; color: $gl-text-color;
margin: 0 10px;
} }
.btn-scroll.animate { .btn-scroll.animate {
...@@ -143,15 +147,6 @@ ...@@ -143,15 +147,6 @@
} }
} }
.bash {
top: 35px;
left: 10px;
bottom: 0;
padding: 10px 20px 20px 5px;
white-space: pre-wrap;
overflow: auto;
}
.environment-information { .environment-information {
border: 1px solid $border-color; border: 1px solid $border-color;
padding: 8px $gl-padding 12px; padding: 8px $gl-padding 12px;
......
...@@ -254,7 +254,7 @@ ...@@ -254,7 +254,7 @@
.text-metric-usage { .text-metric-usage {
fill: $black; fill: $black;
font-weight: 500; font-weight: 500;
font-size: 14px; font-size: 12px;
} }
.legend-axis-text { .legend-axis-text {
...@@ -262,7 +262,11 @@ ...@@ -262,7 +262,11 @@
} }
.tick > text { .tick > text {
font-size: 14px; font-size: 12px;
}
.text-metric-title {
font-size: 12px;
} }
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
...@@ -277,3 +281,9 @@ ...@@ -277,3 +281,9 @@
} }
} }
} }
.prometheus-row {
h5 {
font-size: 16px;
}
}
...@@ -483,11 +483,12 @@ a.deploy-project-label { ...@@ -483,11 +483,12 @@ a.deploy-project-label {
.project-stats { .project-stats {
font-size: 0; font-size: 0;
text-align: center; text-align: center;
max-width: 100%;
border-bottom: 1px solid $border-color;
.nav { .nav {
padding-top: 12px; padding-top: 12px;
padding-bottom: 12px; padding-bottom: 12px;
border-bottom: 1px solid $border-color;
} }
.nav > li { .nav > li {
......
...@@ -33,3 +33,20 @@ ...@@ -33,3 +33,20 @@
font-weight: normal; 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 ...@@ -110,6 +110,8 @@ class ApplicationController < ActionController::Base
end end
def log_exception(exception) def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled?
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" } application_trace.map!{ |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}" logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
......
...@@ -11,6 +11,9 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -11,6 +11,9 @@ class Groups::MilestonesController < Groups::ApplicationController
@milestone_states = GlobalMilestone.states_count(@projects) @milestone_states = GlobalMilestone.states_count(@projects)
@milestones = Kaminari.paginate_array(milestones).page(params[:page]) @milestones = Kaminari.paginate_array(milestones).page(params[:page])
end end
format.json do
render json: milestones.map { |m| m.for_display.slice(:title, :name) }
end
end end
end end
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
protect_from_forgery except: [:kerberos, :saml, :cas3] protect_from_forgery except: [:kerberos, :saml, :cas3]
...@@ -115,8 +116,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -115,8 +116,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.persisted? && @user.valid? if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider']) log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled? if @user.two_factor_enabled?
params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user) prompt_for_two_factor(@user)
else else
remember_me(@user) if remember_me?
sign_in_and_redirect(@user) sign_in_and_redirect(@user)
end end
else else
...@@ -147,4 +150,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -147,4 +150,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
AuditEventService.new(user, user, options) AuditEventService.new(user, user, options)
.for_authentication.security_event .for_authentication.security_event
end end
def remember_me?
request_params = request.env['omniauth.params']
(request_params['remember_me'] == '1') if request_params.present?
end
end end
...@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue def issue
return @issue if defined?(@issue) return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by # 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) return render_404 unless can?(current_user, :read_issue, @issue)
......
...@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder ...@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder
def projects def projects
return @projects if defined?(@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.in_namespace(params[:group_id]) if group?
@projects = @projects.where(id: params[:project_ids]) if projects? @projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil) @projects = @projects.reorder(nil)
......
...@@ -60,13 +60,13 @@ class UsersFinder ...@@ -60,13 +60,13 @@ class UsersFinder
end end
def by_external_identity(users) 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])) users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end end
def by_external(users) 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] return users unless params[:external]
users.external users.external
......
...@@ -298,10 +298,6 @@ module ApplicationHelper ...@@ -298,10 +298,6 @@ module ApplicationHelper
end end
end end
def can_toggle_new_nav?
Rails.env.development?
end
def show_new_nav? def show_new_nav?
cookies["new_nav"] == "true" cookies["new_nav"] == "true"
end end
......
...@@ -16,8 +16,8 @@ module FormHelper ...@@ -16,8 +16,8 @@ module FormHelper
end end
end end
def issue_dropdown_options(issuable, has_multiple_assignees = true) def issue_assignees_dropdown_options
options = { {
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data', toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
title: 'Select assignee', title: 'Select assignee',
filter: true, filter: true,
...@@ -27,8 +27,8 @@ module FormHelper ...@@ -27,8 +27,8 @@ module FormHelper
first_user: current_user&.username, first_user: current_user&.username,
null_user: true, null_user: true,
current_user: true, current_user: true,
project_id: issuable.project.try(:id), project_id: @project.id,
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]", field_name: 'issue[assignee_ids][]',
default_label: 'Unassigned', default_label: 'Unassigned',
'max-select': 1, 'max-select': 1,
'dropdown-header': 'Assignee', 'dropdown-header': 'Assignee',
...@@ -38,13 +38,5 @@ module FormHelper ...@@ -38,13 +38,5 @@ module FormHelper
current_user_info: current_user.to_json(only: [:id, :name]) 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
end end
...@@ -58,6 +58,11 @@ module GroupsHelper ...@@ -58,6 +58,11 @@ module GroupsHelper
IssuesFinder.new(current_user, group_id: group.id).execute IssuesFinder.new(current_user, group_id: group.id).execute
end 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 private
def group_title_link(group, hidable: false) def group_title_link(group, hidable: false)
......
...@@ -74,6 +74,8 @@ module MilestonesHelper ...@@ -74,6 +74,8 @@ module MilestonesHelper
project = @target_project || @project project = @target_project || @project
if project if project
namespace_project_milestones_path(project.namespace, project, :json) namespace_project_milestones_path(project.namespace, project, :json)
elsif @group
group_milestones_path(@group, :json)
else else
dashboard_milestones_path(:json) dashboard_milestones_path(:json)
end end
......
...@@ -126,6 +126,18 @@ module SearchHelper ...@@ -126,6 +126,18 @@ module SearchHelper
search_path(options) search_path(options)
end 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 # Sanitize a HTML field for search display. Most tags are stripped out and the
# maximum length is set to 200 characters. # maximum length is set to 200 characters.
def search_md_sanitize(object, field) def search_md_sanitize(object, field)
......
...@@ -176,9 +176,12 @@ module Ci ...@@ -176,9 +176,12 @@ module Ci
# * Lowercased # * Lowercased
# * Anything not matching [a-z0-9-] is replaced with a - # * Anything not matching [a-z0-9-] is replaced with a -
# * Maximum length is 63 bytes # * Maximum length is 63 bytes
# * First/Last Character is not a hyphen
def ref_slug def ref_slug
slugified = ref.to_s.downcase ref.to_s
slugified.gsub(/[^a-z0-9]/, '-')[0..62] .downcase
.gsub(/[^a-z0-9]/, '-')[0..62]
.gsub(/(\A-+|-+\z)/, '')
end end
# Variables whose value does not depend on environment # Variables whose value does not depend on environment
......
...@@ -5,7 +5,7 @@ module Ci ...@@ -5,7 +5,7 @@ module Ci
belongs_to :project belongs_to :project
validates :key, uniqueness: { scope: :project_id } validates :key, uniqueness: { scope: [:project_id, :environment_scope] }
scope :unprotected, -> { where(protected: false) } scope :unprotected, -> { where(protected: false) }
end end
......
...@@ -102,6 +102,14 @@ module Issuable ...@@ -102,6 +102,14 @@ module Issuable
def locking_enabled? def locking_enabled?
title_changed? || description_changed? title_changed? || description_changed?
end end
def allows_multiple_assignees?
false
end
def has_multiple_assignees?
assignees.count > 1
end
end end
module ClassMethods module ClassMethods
......
...@@ -103,8 +103,12 @@ module Routable ...@@ -103,8 +103,12 @@ module Routable
def full_path def full_path
return uncached_full_path unless RequestStore.active? return uncached_full_path unless RequestStore.active?
key = "routable/full_path/#{self.class.name}/#{self.id}" RequestStore[full_path_key] ||= uncached_full_path
RequestStore[key] ||= uncached_full_path end
def expires_full_path_cache
RequestStore.delete(full_path_key) if RequestStore.active?
@full_path = nil
end end
def build_full_path def build_full_path
...@@ -135,6 +139,10 @@ module Routable ...@@ -135,6 +139,10 @@ module Routable
path_changed? || parent_changed? path_changed? || parent_changed?
end end
def full_path_key
@full_path_key ||= "routable/full_path/#{self.class.name}/#{self.id}"
end
def build_full_name def build_full_name
if parent && name if parent && name
parent.human_name + ' / ' + name parent.human_name + ' / ' + name
......
...@@ -5,6 +5,25 @@ ...@@ -5,6 +5,25 @@
module Sortable module Sortable
extend ActiveSupport::Concern 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 included do
# By default all models should be ordered # By default all models should be ordered
# by created_at field starting from newest # by created_at field starting from newest
...@@ -18,6 +37,10 @@ module Sortable ...@@ -18,6 +37,10 @@ module Sortable
scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_asc, -> { reorder(name: :asc) }
scope :order_name_desc, -> { reorder(name: :desc) } 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 end
module ClassMethods module ClassMethods
......
class ForkedProjectLink < ActiveRecord::Base class ForkedProjectLink < ActiveRecord::Base
belongs_to :forked_to_project, class_name: 'Project' belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
belongs_to :forked_from_project, class_name: 'Project' belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
end end
...@@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base ...@@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base
} }
end 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 def assignees
Array(assignee) Array(assignee)
end end
def assignee_ids
Array(assignee_id)
end
def assignee_ids=(ids)
write_attribute(:assignee_id, ids.last)
end
def assignee_or_author?(user) def assignee_or_author?(user)
author_id == user.id || assignee_id == user.id author_id == user.id || assignee_id == user.id
end end
......
...@@ -825,7 +825,7 @@ class Project < ActiveRecord::Base ...@@ -825,7 +825,7 @@ class Project < ActiveRecord::Base
end end
def ci_service def ci_service
@ci_service ||= ci_services.reorder(nil).find_by(active: true) @ci_service ||= ci_services.find_by(active: true)
end end
def deployment_services def deployment_services
...@@ -833,7 +833,7 @@ class Project < ActiveRecord::Base ...@@ -833,7 +833,7 @@ class Project < ActiveRecord::Base
end end
def deployment_service def deployment_service
@deployment_service ||= deployment_services.reorder(nil).find_by(active: true) @deployment_service ||= deployment_services.find_by(active: true)
end end
def monitoring_services def monitoring_services
...@@ -841,7 +841,7 @@ class Project < ActiveRecord::Base ...@@ -841,7 +841,7 @@ class Project < ActiveRecord::Base
end end
def monitoring_service def monitoring_service
@monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) @monitoring_service ||= monitoring_services.find_by(active: true)
end end
def jira_tracker? def jira_tracker?
...@@ -973,6 +973,7 @@ class Project < ActiveRecord::Base ...@@ -973,6 +973,7 @@ class Project < ActiveRecord::Base
begin begin
gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace) send_move_instructions(old_path_with_namespace)
expires_full_path_cache
@old_path_with_namespace = old_path_with_namespace @old_path_with_namespace = old_path_with_namespace
...@@ -1084,21 +1085,21 @@ class Project < ActiveRecord::Base ...@@ -1084,21 +1085,21 @@ class Project < ActiveRecord::Base
merge_requests.where(source_project_id: self.id) merge_requests.where(source_project_id: self.id)
end end
def create_repository def create_repository(force: false)
# Forked import is handled asynchronously # Forked import is handled asynchronously
unless forked? return if forked? && !force
if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
repository.after_create if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
true repository.after_create
else true
errors.add(:base, 'Failed to create repository via gitlab-shell') else
false errors.add(:base, 'Failed to create repository via gitlab-shell')
end false
end end
end end
def ensure_repository def ensure_repository
create_repository unless repository_exists? create_repository(force: true) unless repository_exists?
end end
def repository_exists? def repository_exists?
......
require_dependency 'declarative_policy' require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin" desc "User is an instance admin"
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:admin) { @user&.admin? } condition(:admin) { @user&.admin? }
...@@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group } 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 end
...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy ...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? } 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 rule { default }.policy do
enable :read_users_list
enable :log_in enable :log_in
enable :access_api enable :access_api
enable :access_git enable :access_git
...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy ...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do rule { access_locked }.policy do
prevent :log_in prevent :log_in
end end
rule { ~restricted_public_level }.policy do
enable :read_users_list
end
end end
class UserPolicy < BasePolicy 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" desc "The current user is the user in question"
condition(:user_is_self, score: 0) { @subject == @user } condition(:user_is_self, score: 0) { @subject == @user }
......
...@@ -5,10 +5,11 @@ class AccessTokenValidationService ...@@ -5,10 +5,11 @@ class AccessTokenValidationService
REVOKED = :revoked REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope INSUFFICIENT_SCOPE = :insufficient_scope
attr_reader :token attr_reader :token, :request
def initialize(token) def initialize(token, request: nil)
@token = token @token = token
@request = request
end end
def validate(scopes: []) def validate(scopes: [])
...@@ -27,12 +28,23 @@ class AccessTokenValidationService ...@@ -27,12 +28,23 @@ class AccessTokenValidationService
end end
# True if the token's scope contains any of the passed scopes. # True if the token's scope contains any of the passed scopes.
def include_any_scope?(scopes) def include_any_scope?(required_scopes)
if scopes.blank? if required_scopes.blank?
true true
else else
# Check whether the token is allowed access to any of the required scopes. # We're comparing each required_scope against all token scopes, which would
Set.new(scopes).intersection(Set.new(token.scopes)).present? # 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 end
end end
...@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService ...@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
# Prevent deletion of branches relevant to open merge requests # Prevent deletion of branches relevant to open merge requests
branches -= merge_request_branch_names branches -= merge_request_branch_names
# Prevent deletion of protected branches
branches -= project.protected_branches.pluck(:name)
branches.each do |branch| branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch) DeleteBranchService.new(project, current_user).execute(branch)
......
...@@ -61,8 +61,12 @@ module MergeRequests ...@@ -61,8 +61,12 @@ module MergeRequests
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch? if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
DeleteBranchService.new(@merge_request.source_project, branch_deletion_user) # Verify again that the source branch can be removed, since branch may be protected,
.execute(merge_request.source_branch) # 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
end end
......
...@@ -78,6 +78,7 @@ module Projects ...@@ -78,6 +78,7 @@ module Projects
Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path) Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
project.old_path_with_namespace = @old_path project.old_path_with_namespace = @old_path
project.expires_full_path_cache
execute_system_hooks execute_system_hooks
end end
......
...@@ -92,9 +92,12 @@ module QuickActions ...@@ -92,9 +92,12 @@ module QuickActions
desc 'Assign' desc 'Assign'
explanation do |users| 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 end
params '@user'
condition do condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
...@@ -104,28 +107,69 @@ module QuickActions ...@@ -104,28 +107,69 @@ module QuickActions
command :assign do |users| command :assign do |users|
next if users.empty? next if users.empty?
if issuable.is_a?(Issue) @updates[:assignee_ids] =
@updates[:assignee_ids] = [users.last.id] if issuable.allows_multiple_assignees?
issuable.assignees.pluck(:id) + users.map(&:id)
else
[users.last.id]
end
end
desc do
if issuable.allows_multiple_assignees?
'Remove all or specific assignee(s)'
else else
@updates[:assignee_id] = users.last.id 'Remove assignee'
end end
end end
desc 'Remove assignee'
explanation do 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 end
condition do condition do
issuable.persisted? && issuable.persisted? &&
issuable.assignees.any? && issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :unassign do parse_params do |unassign_param|
if issuable.is_a?(Issue) # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
@updates[:assignee_ids] = [] extract_users(unassign_param) if issuable.allows_multiple_assignees?
else end
@updates[:assignee_id] = nil command :unassign do |users = nil|
end @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 end
desc 'Set milestone' desc 'Set milestone'
......
...@@ -32,13 +32,16 @@ ...@@ -32,13 +32,16 @@
#{time_ago_in_words(runner.contacted_at)} ago #{time_ago_in_words(runner.contacted_at)} ago
- else - else
Never Never
%td %td.admin-runner-btn-group-cell
.pull-right .pull-right.btn-group
= link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm' = 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')
&nbsp; &nbsp;
- if runner.active? - 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 - else
= link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success 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
= link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' = 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 @@ ...@@ -6,4 +6,7 @@
- providers.each do |provider| - providers.each do |provider|
%span.light %span.light
- has_icon = provider_has_icon?(provider) - 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 @@ ...@@ -25,7 +25,7 @@
%div %div
- if Gitlab::Recaptcha.enabled? - if Gitlab::Recaptcha.enabled?
= recaptcha_tags = recaptcha_tags
%div .submit-container
= f.submit "Register", class: "btn-register btn" = f.submit "Register", class: "btn-register btn"
.clearfix.submit-container .clearfix.submit-container
%p %p
......
...@@ -45,10 +45,13 @@ ...@@ -45,10 +45,13 @@
.panel.panel-danger .panel.panel-danger
.panel-heading Remove group .panel-heading Remove group
.panel-body .panel-body
%p = form_tag(@group, method: :delete) do
Removing group will cause all child projects and resources to be removed. %p
%br Removing group will cause all child projects and resources to be removed.
%strong Removed group can not be restored! %br
%strong Removed group can not be restored!
.form-actions .form-actions
= link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" = 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 @@ ...@@ -74,9 +74,8 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
- if can_toggle_new_nav? %li
%li = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
= link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
%li.divider %li.divider
%li %li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
......
...@@ -16,25 +16,22 @@ ...@@ -16,25 +16,22 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png" .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id = f.radio_button :color_scheme_id, scheme.id
= scheme.name = scheme.name
- if can_toggle_new_nav? .col-sm-12
.col-sm-12 %hr
%hr .col-lg-4.profile-settings-sidebar#new-navigation
.col-lg-3.profile-settings-sidebar#new-navigation %h4.prepend-top-0
%h4.prepend-top-0 New Navigation
New Navigation %p
%p This setting allows you to turn on or off the new upcoming navigation concept.
This setting allows you to turn on or off the new upcoming navigation concept. .col-lg-8.syntax-theme
= succeed '.' do = label_tag do
= link_to 'Learn more', '', target: '_blank' .preview= image_tag "old_nav.png"
.col-lg-9.syntax-theme %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
= label_tag do Old
.preview= image_tag "old_nav.png" = label_tag do
%input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? } .preview= image_tag "new_nav.png"
Old %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
= label_tag do New
.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 .col-sm-12
%hr %hr
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
......
...@@ -23,4 +23,5 @@ ...@@ -23,4 +23,5 @@
= render "projects/boards/components/sidebar/labels" = render "projects/boards/components/sidebar/labels"
= render "projects/boards/components/sidebar/notifications" = render "projects/boards/components/sidebar/notifications"
%remove-btn{ ":issue" => "issue", %remove-btn{ ":issue" => "issue",
":list" => "list" } ":list" => "list",
"v-if" => "canRemove" }
...@@ -19,10 +19,11 @@ ...@@ -19,10 +19,11 @@
":data-name" => "assignee.name", ":data-name" => "assignee.name",
":data-username" => "assignee.username" } ":data-username" => "assignee.username" }
.dropdown .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-issuable-id" => "issue.id",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Select assignee = dropdown_options[:title]
= icon("chevron-down") = icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author .dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to") = dropdown_title("Assign to")
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.top-area .top-area
.row .row
.col-sm-6 .col-sm-6
%h3.page-title %h3
Environment: Environment:
= link_to @environment.name, environment_path(@environment) = link_to @environment.name, environment_path(@environment)
......
...@@ -54,13 +54,14 @@ ...@@ -54,13 +54,14 @@
- else - else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)} Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
.build-trace-container#build-trace .build-trace-container.prepend-top-default
.top-bar.sticky .top-bar.js-top-bar
.js-truncated-info.truncated-info.hidden< .js-truncated-info.truncated-info.hidden<
Showing last Showing last
%span.js-truncated-info-size.truncated-info-size>< %span.js-truncated-info-size.truncated-info-size><
KiB of log - KiB of log -
%a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw %a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw
.controllers .controllers
- if @build.has_trace? - if @build.has_trace?
= link_to raw_namespace_project_job_path(@project.namespace, @project, @build), = link_to raw_namespace_project_job_path(@project.namespace, @project, @build),
...@@ -82,10 +83,12 @@ ...@@ -82,10 +83,12 @@
.has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} } .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 } %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
= custom_icon('scroll_down') = 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 .build-loader-animation.js-build-refresh
= render "sidebar" = render "sidebar"
.js-build-options{ data: javascript_build_options } .js-build-options{ data: javascript_build_options }
......
- @no_container = true - @no_container = true
- page_title "Charts", "Pipelines" - page_title _("Charts"), _("Pipelines")
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs') = page_specific_javascript_bundle_tag('graphs')
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%div{ class: container_class } %div{ class: container_class }
.sub-header-block .sub-header-block
.oneline .oneline
A collection of graphs for Continuous Integration = _("A collection of graphs regarding Continuous Integration")
#charts.ci-charts #charts.ci-charts
.row .row
......
%h4 Overall stats %h4= s_("PipelineCharts|Overall statistics")
%ul %ul
%li %li
Total: = s_("PipelineCharts|Total:")
%strong= pluralize @counts[:total], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
%li %li
Successful: = s_("PipelineCharts|Successful:")
%strong= pluralize @counts[:success], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
%li %li
Failed: = s_("PipelineCharts|Failed:")
%strong= pluralize @counts[:failed], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
%li %li
Success ratio: = s_("PipelineCharts|Success ratio:")
%strong %strong
#{success_ratio(@counts)}% #{success_ratio(@counts)}%
%div %div
%p.light %p.light
Commit duration in minutes for last 30 commits = _("Commit duration in minutes for last 30 commits")
%canvas#build_timesChart{ height: 200 } %canvas#build_timesChart{ height: 200 }
......
%h4 Pipelines charts %h4= _("Pipelines charts")
%p %p
&nbsp; &nbsp;
%span.cgreen %span.cgreen
= icon("circle") = icon("circle")
success = s_("Pipeline|success")
&nbsp; &nbsp;
%span.cgray %span.cgray
= icon("circle") = icon("circle")
all = s_("Pipeline|all")
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last week = _("Jobs for last week")
(#{date_from_to(Date.today - 7.days, Date.today)}) (#{date_from_to(Date.today - 7.days, Date.today)})
%canvas#weekChart{ height: 200 } %canvas#weekChart{ height: 200 }
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last month = _("Jobs for last month")
(#{date_from_to(Date.today - 30.days, Date.today)}) (#{date_from_to(Date.today - 30.days, Date.today)})
%canvas#monthChart{ height: 200 } %canvas#monthChart{ height: 200 }
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last year = _("Jobs for last year")
%canvas#yearChart.padded{ height: 250 } %canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope| - [:week, :month, :year].each do |scope|
......
...@@ -73,7 +73,7 @@ ...@@ -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 = 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') } #{ _('Set up auto deploy') }
%div{ class: container_class } %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if @project.archived? - if @project.archived?
.text-warning.center.prepend-top-20 .text-warning.center.prepend-top-20
%p %p
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.scroll-container .scroll-container
%ul.tokens-container.list-unstyled %ul.tokens-container.list-unstyled
%li.input-token %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') = icon('filter')
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
......
...@@ -37,19 +37,20 @@ ...@@ -37,19 +37,20 @@
- issuable.assignees.each do |assignee| - 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 } = 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' - title = 'Select assignee'
- if issuable.is_a?(Issue) - if issuable.is_a?(Issue)
- unless issuable.assignees.any? - unless issuable.assignees.any?
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil = 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' - options[:toggle_class] += ' js-multiselect js-save-user-data'
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" } - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
- data[:multi_select] = true - data[:multi_select] = true
- data['dropdown-title'] = title - data['dropdown-title'] = title
- data['dropdown-header'] = 'Assignee' - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
- data['max-select'] = 1 - data['max-select'] = dropdown_options[:data][:'max-select']
- options[:data].merge!(data) - options[:data].merge!(data)
= dropdown_tag(title, options: options) = dropdown_tag(title, options: options)
...@@ -11,8 +11,7 @@ ...@@ -11,8 +11,7 @@
.col-sm-10.col-sm-offset-2 .col-sm-10.col-sm-offset-2
- if issuable.can_remove_source_branch?(current_user) - if issuable.can_remove_source_branch?(current_user)
.checkbox .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 = label_tag 'merge_request[force_remove_source_branch]' do
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil = 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. Remove source branch when merge request is accepted.
...@@ -7,5 +7,5 @@ ...@@ -7,5 +7,5 @@
- if issuable.assignees.length === 0 - if issuable.assignees.length === 0
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' } = 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)}" = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
- model_name = source.model_name.to_s.downcase - 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_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
= link_to link_text, polymorphic_path([:leave, source, :members]), = link_to link_text, polymorphic_path([:leave, source, :members]),
method: :delete, method: :delete,
data: { confirm: leave_confirmation_message(source) }, data: { confirm: leave_confirmation_message(source) },
class: 'btn' 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]), = link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
method: :delete, method: :delete,
data: { confirm: remove_member_message(requester) }, data: { confirm: remove_member_message(requester) },
class: 'btn' 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]), = link_to _('Request Access'), polymorphic_path([:request_access, source, :members]),
method: :post, method: :post,
class: 'btn' class: 'btn'
---
title: Honor the "Remember me" parameter for OAuth-based login
merge_request: 11963
author:
---
title: Replace 'dashboard/new-project.feature' spinach with rspec
merge_request: 12550
author: Alexander Randa (@randaalex)
---
title: Fix spacing on runner buttons.
merge_request: !12535
author:
---
title: Set default for Remove source branch to false.
merge_request: !12576
author:
---
title: "Remove group modal like remove project modal (requires typing + confirmation)"
merge_request: 12569
author: Diego Souza
---
title: Fix API Scoping
merge_request: 12300
author:
---
title: Remove "Remove from board" button from backlog and closed list
merge_request: 12430
author:
---
title: Change milestone endpoint for groups
merge_request: 12374
author: Takuya Noguchi
---
title: Allow unauthenticated access to the /api/v4/users API
merge_request: 12445
author:
---
title: Update jobs page output to have a scrollable page
merge_request: 12587
author:
---
title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page
merge_request: 12578
author: Huang Tao
---
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:
---
title: Prevent accidental deletion of protected MR source branch by repeating checks
before actual deletion
merge_request:
author:
---
title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause
merge_request:
author:
---
title: Fix issues with non-UTF8 filenames by always fixing the encoding of tree and
blob paths
merge_request:
author:
---
title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname
merge_request: 11218
author: Stefan Hanreich
---
title: Expires full_path cache after a repository is renamed/transferred
merge_request:
author:
---
title: Hide archived project labels from group issue tracker
merge_request: 12547
author: Horacio Bertorello
---
title: Improve the overall UX for the new monitoring dashboard
merge_request:
author:
---
title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
merge_request:
author:
---
title: Make Project#ensure_repository force create a repo
merge_request:
author:
---
title: Log rescued exceptions to Sentry
merge_request:
author:
---
title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
merge_request:
author:
---
title: Do not delete protected branches when deleting all merged branches
merge_request: 12624
author:
...@@ -543,6 +543,10 @@ production: &base ...@@ -543,6 +543,10 @@ production: &base
# enabled: true # enabled: true
# host: localhost # host: localhost
# port: 3808 # port: 3808
prometheus:
# Time between sampling of unicorn socket metrics, in seconds
# unicorn_sampler_interval: 10
# #
# 5. Extra customization # 5. Extra customization
...@@ -615,6 +619,53 @@ test: ...@@ -615,6 +619,53 @@ test:
title: "JIRA" title: "JIRA"
url: https://sample_company.atlassian.net url: https://sample_company.atlassian.net
project_key: PROJECT 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: ldap:
enabled: false enabled: false
servers: servers:
......
...@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false ...@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false
Settings.webpack.dev_server['host'] ||= 'localhost' Settings.webpack.dev_server['host'] ||= 'localhost'
Settings.webpack.dev_server['port'] ||= 3808 Settings.webpack.dev_server['port'] ||= 3808
#
# Prometheus metrics settings
#
Settings['prometheus'] ||= Settingslogic.new({})
Settings.prometheus['unicorn_sampler_interval'] ||= 10
# #
# Testing settings # Testing settings
# #
......
...@@ -119,6 +119,13 @@ def instrument_classes(instrumentation) ...@@ -119,6 +119,13 @@ def instrument_classes(instrumentation)
end end
# rubocop:enable Metrics/AbcSize # 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? if Gitlab::Metrics.enabled?
require 'pathname' require 'pathname'
require 'influxdb' require 'influxdb'
...@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled? ...@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled?
GC::Profiler.enable GC::Profiler.enable
Gitlab::Metrics::Sampler.new.start Gitlab::Metrics::InfluxSampler.initialize_instance.start
module TrackNewRedisConnections module TrackNewRedisConnections
def connect(*args) def connect(*args)
......
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
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
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
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
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 ...@@ -3,19 +3,15 @@ class AddStageIdIndexToBuilds < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
disable_ddl_transaction! ##
# Improved in 20170703102400_add_stage_id_foreign_key_to_builds.rb
#
def up def up
unless index_exists?(:ci_builds, :stage_id) # noop
add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
add_concurrent_index(:ci_builds, :stage_id)
end
end end
def down def down
if index_exists?(:ci_builds, :stage_id) # noop
remove_foreign_key(:ci_builds, column: :stage_id)
remove_concurrent_index(:ci_builds, :stage_id)
end
end end
end end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -374,9 +374,10 @@ ActiveRecord::Schema.define(version: 20170622162730) do ...@@ -374,9 +374,10 @@ ActiveRecord::Schema.define(version: 20170622162730) do
t.string "encrypted_value_iv" t.string "encrypted_value_iv"
t.integer "project_id", null: false t.integer "project_id", null: false
t.boolean "protected", default: false, null: false t.boolean "protected", default: false, null: false
t.string "environment_scope", default: "*", null: false
end 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| create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
......
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.
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