Commit d02ba2fe authored by Nick Thomas's avatar Nick Thomas

Merge remote-tracking branch 'ce/master' into ce-to-ee-2017-07-06

parents 8b0ec861 9274c3c1
...@@ -61,3 +61,4 @@ eslint-report.html ...@@ -61,3 +61,4 @@ eslint-report.html
/.gitlab_workhorse_secret /.gitlab_workhorse_secret
/webpack-report/ /webpack-report/
/locale/**/LC_MESSAGES /locale/**/LC_MESSAGES
/.rspec
--color
--format Fuubar
...@@ -265,7 +265,7 @@ gem 'base32', '~> 0.3.0' ...@@ -265,7 +265,7 @@ gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.0" gem "gitlab-license", "~> 1.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'
...@@ -295,6 +295,7 @@ group :metrics do ...@@ -295,6 +295,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
......
...@@ -627,8 +627,8 @@ GEM ...@@ -627,8 +627,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)
...@@ -686,7 +686,7 @@ GEM ...@@ -686,7 +686,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)
...@@ -803,7 +803,7 @@ GEM ...@@ -803,7 +803,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)
...@@ -1098,6 +1098,7 @@ DEPENDENCIES ...@@ -1098,6 +1098,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)
...@@ -1125,7 +1126,7 @@ DEPENDENCIES ...@@ -1125,7 +1126,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)
......
...@@ -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 () {
......
...@@ -60,10 +60,13 @@ import ShortcutsBlob from './shortcuts_blob'; ...@@ -60,10 +60,13 @@ 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'; import OAuthRememberMe from './oauth_remember_me';
<<<<<<< HEAD
// EE-only // EE-only
import ApproversSelect from './approvers_select'; import ApproversSelect from './approvers_select';
import AuditLogs from './audit_logs'; import AuditLogs from './audit_logs';
=======
>>>>>>> ce/master
(function() { (function() {
var Dispatcher; var Dispatcher;
......
...@@ -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;
...@@ -157,12 +159,12 @@ ...@@ -157,12 +159,12 @@
const xAxis = d3.svg.axis() const xAxis = d3.svg.axis()
.scale(axisXScale) .scale(axisXScale)
.ticks(measurements.ticks) .ticks(measurements.xTicks)
.orient('bottom'); .orient('bottom');
const yAxis = d3.svg.axis() const yAxis = d3.svg.axis()
.scale(this.yScale) .scale(this.yScale)
.ticks(measurements.ticks) .ticks(measurements.yTicks)
.orient('left'); .orient('left');
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis); d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
...@@ -170,8 +172,12 @@ ...@@ -170,8 +172,12 @@
const width = this.graphWidth; const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis) d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
.selectAll('.tick') .selectAll('.tick')
.each(function createTickLines() { .each(function createTickLines(d, i) {
d3.select(this).select('line').attr('x2', width); if (i > 0) {
d3.select(this).select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
} // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered }); // This will select all of the ticks once they're rendered
this.xScale = d3.time.scale() this.xScale = d3.time.scale()
...@@ -198,7 +204,7 @@ ...@@ -198,7 +204,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 +222,14 @@ ...@@ -216,14 +222,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"
......
...@@ -87,14 +87,14 @@ ...@@ -87,14 +87,14 @@
</rect> </rect>
<text <text
class="text-metric text-metric-bold" class="text-metric text-metric-bold"
x="8" x="16"
y="35" y="35"
transform="translate(-5, 20)"> transform="translate(-5, 20)">
{{formatTime}} {{formatTime}}
</text> </text>
<text <text
class="text-metric-date" class="text-metric"
x="8" x="16"
y="15" y="15"
transform="translate(-5, 20)"> transform="translate(-5, 20)">
{{formatDate}} {{formatDate}}
......
...@@ -109,13 +109,13 @@ ...@@ -109,13 +109,13 @@
</text> </text>
<rect <rect
class="rect-axis-text" class="rect-axis-text"
:x="xPosition + 50" :x="xPosition + 60"
:y="graphHeight - 80" :y="graphHeight - 80"
width="50" width="35"
height="50"> height="50">
</rect> </rect>
<text <text
class="label-axis-text" class="label-axis-text x-label-text"
:x="xPosition + 60" :x="xPosition + 60"
:y="yPosition" :y="yPosition"
dy=".35em"> dy=".35em">
...@@ -131,13 +131,13 @@ ...@@ -131,13 +131,13 @@
<text <text
class="text-metric-title" class="text-metric-title"
x="50" x="50"
:y="graphHeight - 40"> :y="graphHeight - 25">
{{legendTitle}} {{legendTitle}}
</text> </text>
<text <text
class="text-metric-usage" class="text-metric-usage"
x="50" x="50"
:y="graphHeight - 25"> :y="graphHeight - 10">
{{metricUsage}} {{metricUsage}}
</text> </text>
</g> </g>
......
...@@ -8,14 +8,14 @@ export default { ...@@ -8,14 +8,14 @@ export default {
}, },
legends: { legends: {
width: 15, width: 15,
height: 30, height: 25,
}, },
backgroundLegend: { backgroundLegend: {
width: 30, width: 30,
height: 50, height: 50,
}, },
axisLabelLineOffset: -20, axisLabelLineOffset: -20,
legendOffset: 52, legendOffset: 35,
}, },
large: { // This covers both md and lg screen sizes large: { // This covers both md and lg screen sizes
margin: { margin: {
...@@ -26,14 +26,15 @@ export default { ...@@ -26,14 +26,15 @@ export default {
}, },
legends: { legends: {
width: 20, width: 20,
height: 35, height: 30,
}, },
backgroundLegend: { backgroundLegend: {
width: 30, width: 30,
height: 150, height: 150,
}, },
axisLabelLineOffset: 20, axisLabelLineOffset: 20,
legendOffset: 55, legendOffset: 38,
}, },
ticks: 3, xTicks: 8,
yTicks: 3,
}; };
...@@ -2,56 +2,54 @@ ...@@ -2,56 +2,54 @@
/* eslint no-new: "off" */ /* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor'; import AccessorUtilities from './lib/utils/accessor';
((global) => { /**
/** * Memorize the last selected tab after reloading a page.
* Memorize the last selected tab after reloading a page. * Does that setting the current selected tab in the localStorage
* Does that setting the current selected tab in the localStorage */
*/ class ActiveTabMemoizer {
class ActiveTabMemoizer { constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { this.currentTabKey = currentTabKey;
this.currentTabKey = currentTabKey; this.tabSelector = tabSelector;
this.tabSelector = tabSelector; this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.bootstrap();
this.bootstrap(); }
}
bootstrap() {
const tabs = document.querySelectorAll(this.tabSelector);
if (tabs.length > 0) {
tabs[0].addEventListener('click', (e) => {
if (e.target && e.target.nodeName === 'A') {
const anchorName = e.target.getAttribute('href');
this.saveData(anchorName);
}
});
}
this.showTab(); bootstrap() {
const tabs = document.querySelectorAll(this.tabSelector);
if (tabs.length > 0) {
tabs[0].addEventListener('click', (e) => {
if (e.target && e.target.nodeName === 'A') {
const anchorName = e.target.getAttribute('href');
this.saveData(anchorName);
}
});
} }
showTab() { this.showTab();
const anchorName = this.readData(); }
if (anchorName) {
const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`); showTab() {
if (tab) { const anchorName = this.readData();
tab.click(); if (anchorName) {
} const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
if (tab) {
tab.click();
} }
} }
}
saveData(val) { saveData(val) {
if (!this.isLocalStorageAvailable) return undefined; if (!this.isLocalStorageAvailable) return undefined;
return window.localStorage.setItem(this.currentTabKey, val); return window.localStorage.setItem(this.currentTabKey, val);
} }
readData() { readData() {
if (!this.isLocalStorageAvailable) return null; if (!this.isLocalStorageAvailable) return null;
return window.localStorage.getItem(this.currentTabKey); return window.localStorage.getItem(this.currentTabKey);
}
} }
}
global.ActiveTabMemoizer = ActiveTabMemoizer; window.ActiveTabMemoizer = ActiveTabMemoizer;
})(window);
...@@ -2,99 +2,97 @@ ...@@ -2,99 +2,97 @@
import FilesCommentButton from './files_comment_button'; import FilesCommentButton from './files_comment_button';
(function() { window.SingleFileDiff = (function() {
window.SingleFileDiff = (function() { var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
WRAPPER = '<div class="diff-content"></div>'; WRAPPER = '<div class="diff-content"></div>';
LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file) { function SingleFileDiff(file) {
this.file = file; this.file = file;
this.toggleDiff = this.toggleDiff.bind(this); this.toggleDiff = this.toggleDiff.bind(this);
this.content = $('.diff-content', this.file); this.content = $('.diff-content', this.file);
this.$toggleIcon = $('.diff-toggle-caret', this.file); this.$toggleIcon = $('.diff-toggle-caret', this.file);
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
this.isOpen = !this.diffForPath; this.isOpen = !this.diffForPath;
if (this.diffForPath) { if (this.diffForPath) {
this.collapsedContent = this.content; this.collapsedContent = this.content;
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
this.content = null; this.content = null;
this.collapsedContent.after(this.loadingContent); this.collapsedContent.after(this.loadingContent);
this.$toggleIcon.addClass('fa-caret-right'); this.$toggleIcon.addClass('fa-caret-right');
} else { } else {
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
this.content.after(this.collapsedContent); this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down'); this.$toggleIcon.addClass('fa-caret-down');
} }
$('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
this.toggleDiff($(e.target));
}).bind(this));
}
$('.js-file-title, .click-to-expand', this.file).on('click', (function (e) { SingleFileDiff.prototype.toggleDiff = function($target, cb) {
this.toggleDiff($(e.target)); if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
}).bind(this)); this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) {
this.content.hide();
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
this.collapsedContent.show();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
} else if (this.content) {
this.collapsedContent.hide();
this.content.show();
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
} else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML(cb);
} }
};
SingleFileDiff.prototype.toggleDiff = function($target, cb) { SingleFileDiff.prototype.getContentHTML = function(cb) {
if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; this.collapsedContent.hide();
this.isOpen = !this.isOpen; this.loadingContent.show();
if (!this.isOpen && !this.hasError) { $.get(this.diffForPath, (function(_this) {
this.content.hide(); return function(data) {
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down'); _this.loadingContent.hide();
this.collapsedContent.show(); if (data.html) {
if (typeof gl.diffNotesCompileComponents !== 'undefined') { _this.content = $(data.html);
gl.diffNotesCompileComponents(); _this.content.syntaxHighlight();
} else {
_this.hasError = true;
_this.content = $(ERROR_HTML);
} }
} else if (this.content) { _this.collapsedContent.after(_this.content);
this.collapsedContent.hide();
this.content.show();
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
if (typeof gl.diffNotesCompileComponents !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
} }
} else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML(cb);
}
};
SingleFileDiff.prototype.getContentHTML = function(cb) {
this.collapsedContent.hide();
this.loadingContent.show();
$.get(this.diffForPath, (function(_this) {
return function(data) {
_this.loadingContent.hide();
if (data.html) {
_this.content = $(data.html);
_this.content.syntaxHighlight();
} else {
_this.hasError = true;
_this.content = $(ERROR_HTML);
}
_this.collapsedContent.after(_this.content);
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
FilesCommentButton.init($(_this.file)); FilesCommentButton.init($(_this.file));
if (cb) cb(); if (cb) cb();
}; };
})(this)); })(this));
}; };
return SingleFileDiff; return SingleFileDiff;
})(); })();
$.fn.singleFileDiff = function() { $.fn.singleFileDiff = function() {
return this.each(function() { return this.each(function() {
if (!$.data(this, 'singleFileDiff')) { if (!$.data(this, 'singleFileDiff')) {
return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this)); return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
} }
}); });
}; };
}).call(window);
/* /**
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable * Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API. * and controllable by a public API.
* */
* */
class SmartInterval {
(() => { /**
class SmartInterval { * @param { function } opts.callback Function to be called on each iteration (required)
/** * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
* @param { function } opts.callback Function to be called on each iteration (required) * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
* @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this * when the page is hidden
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
* when the page is hidden * @param { boolean } opts.lazyStart Configure if timer is initialized on
* @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor * instantiation or lazily
* @param { boolean } opts.lazyStart Configure if timer is initialized on * @param { boolean } opts.immediateExecution Configure if callback should
* instantiation or lazily * be executed before the first interval.
* @param { boolean } opts.immediateExecution Configure if callback should */
* be executed before the first interval. constructor(opts = {}) {
*/ this.cfg = {
constructor(opts = {}) { callback: opts.callback,
this.cfg = { startingInterval: opts.startingInterval,
callback: opts.callback, maxInterval: opts.maxInterval,
startingInterval: opts.startingInterval, hiddenInterval: opts.hiddenInterval,
maxInterval: opts.maxInterval, incrementByFactorOf: opts.incrementByFactorOf,
hiddenInterval: opts.hiddenInterval, lazyStart: opts.lazyStart,
incrementByFactorOf: opts.incrementByFactorOf, immediateExecution: opts.immediateExecution,
lazyStart: opts.lazyStart, };
immediateExecution: opts.immediateExecution,
}; this.state = {
intervalId: null,
this.state = { currentInterval: this.cfg.startingInterval,
intervalId: null, pageVisibility: 'visible',
currentInterval: this.cfg.startingInterval, };
pageVisibility: 'visible',
}; this.initInterval();
}
this.initInterval();
}
/* public */
start() {
const cfg = this.cfg;
const state = this.state;
if (cfg.immediateExecution) {
cfg.immediateExecution = false;
cfg.callback();
}
state.intervalId = window.setInterval(() => { /* public */
cfg.callback();
if (this.getCurrentInterval() === cfg.maxInterval) { start() {
return; const cfg = this.cfg;
} const state = this.state;
this.incrementInterval(); if (cfg.immediateExecution) {
this.resume(); cfg.immediateExecution = false;
}, this.getCurrentInterval()); cfg.callback();
} }
// cancel the existing timer, setting the currentInterval back to startingInterval state.intervalId = window.setInterval(() => {
cancel() { cfg.callback();
this.setCurrentInterval(this.cfg.startingInterval);
this.stopTimer();
}
onVisibilityHidden() { if (this.getCurrentInterval() === cfg.maxInterval) {
if (this.cfg.hiddenInterval) { return;
this.setCurrentInterval(this.cfg.hiddenInterval);
this.resume();
} else {
this.cancel();
} }
}
// start a timer, using the existing interval this.incrementInterval();
resume() { this.resume();
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped }, this.getCurrentInterval());
this.start(); }
}
onVisibilityVisible() { // cancel the existing timer, setting the currentInterval back to startingInterval
this.cancel(); cancel() {
this.start(); this.setCurrentInterval(this.cfg.startingInterval);
} this.stopTimer();
}
destroy() { onVisibilityHidden() {
if (this.cfg.hiddenInterval) {
this.setCurrentInterval(this.cfg.hiddenInterval);
this.resume();
} else {
this.cancel(); this.cancel();
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
$(document).off('visibilitychange').off('beforeunload');
} }
}
/* private */ // start a timer, using the existing interval
resume() {
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
this.start();
}
initInterval() { onVisibilityVisible() {
const cfg = this.cfg; this.cancel();
this.start();
}
if (!cfg.lazyStart) { destroy() {
this.start(); this.cancel();
} document.removeEventListener('visibilitychange', this.handleVisibilityChange);
$(document).off('visibilitychange').off('beforeunload');
}
this.initVisibilityChangeHandling(); /* private */
this.initPageUnloadHandling();
}
initVisibilityChangeHandling() { initInterval() {
// cancel interval when tab no longer shown (prevents cached pages from polling) const cfg = this.cfg;
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
}
initPageUnloadHandling() { if (!cfg.lazyStart) {
// TODO: Consider refactoring in light of turbolinks removal. this.start();
// prevent interval continuing after page change, when kept in cache by Turbolinks
$(document).on('beforeunload', () => this.cancel());
} }
handleVisibilityChange(e) { this.initVisibilityChangeHandling();
this.state.pageVisibility = e.target.visibilityState; this.initPageUnloadHandling();
const intervalAction = this.isPageVisible() ? }
this.onVisibilityVisible :
this.onVisibilityHidden;
intervalAction.apply(this); initVisibilityChangeHandling() {
} // cancel interval when tab no longer shown (prevents cached pages from polling)
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
}
getCurrentInterval() { initPageUnloadHandling() {
return this.state.currentInterval; // TODO: Consider refactoring in light of turbolinks removal.
} // prevent interval continuing after page change, when kept in cache by Turbolinks
$(document).on('beforeunload', () => this.cancel());
}
setCurrentInterval(newInterval) { handleVisibilityChange(e) {
this.state.currentInterval = newInterval; this.state.pageVisibility = e.target.visibilityState;
} const intervalAction = this.isPageVisible() ?
this.onVisibilityVisible :
this.onVisibilityHidden;
incrementInterval() { intervalAction.apply(this);
const cfg = this.cfg; }
const currentInterval = this.getCurrentInterval();
if (cfg.hiddenInterval && !this.isPageVisible()) return;
let nextInterval = currentInterval * cfg.incrementByFactorOf;
if (nextInterval > cfg.maxInterval) { getCurrentInterval() {
nextInterval = cfg.maxInterval; return this.state.currentInterval;
} }
setCurrentInterval(newInterval) {
this.state.currentInterval = newInterval;
}
incrementInterval() {
const cfg = this.cfg;
const currentInterval = this.getCurrentInterval();
if (cfg.hiddenInterval && !this.isPageVisible()) return;
let nextInterval = currentInterval * cfg.incrementByFactorOf;
this.setCurrentInterval(nextInterval); if (nextInterval > cfg.maxInterval) {
nextInterval = cfg.maxInterval;
} }
isPageVisible() { return this.state.pageVisibility === 'visible'; } this.setCurrentInterval(nextInterval);
}
stopTimer() { isPageVisible() { return this.state.pageVisibility === 'visible'; }
const state = this.state;
state.intervalId = window.clearInterval(state.intervalId); stopTimer() {
} const state = this.state;
state.intervalId = window.clearInterval(state.intervalId);
} }
gl.SmartInterval = SmartInterval; }
})(window.gl || (window.gl = {}));
window.gl.SmartInterval = SmartInterval;
/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */ /* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
(global => { window.gl.SnippetsList = function() {
global.gl = global.gl || {}; var $holder = $('.snippets-list-holder');
gl.SnippetsList = function() { $holder.find('.pagination').on('ajax:success', (e, data) => {
var $holder = $('.snippets-list-holder'); $holder.replaceWith(data.html);
});
$holder.find('.pagination').on('ajax:success', (e, data) => { };
$holder.replaceWith(data.html);
});
};
})(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */ /* global Flash */
(function() { window.Star = (function() {
this.Star = (function() { function Star() {
function Star() { $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) { var $starIcon, $starSpan, $this, toggleStar;
var $starIcon, $starSpan, $this, toggleStar; $this = $(this);
$this = $(this); $starSpan = $this.find('span');
$starSpan = $this.find('span'); $starIcon = $this.find('i');
$starIcon = $this.find('i'); toggleStar = function(isStarred) {
toggleStar = function(isStarred) { $this.parent().find('.star-count').text(data.star_count);
$this.parent().find('.star-count').text(data.star_count); if (isStarred) {
if (isStarred) { $starSpan.removeClass('starred').text('Star');
$starSpan.removeClass('starred').text('Star'); $starIcon.removeClass('fa-star').addClass('fa-star-o');
$starIcon.removeClass('fa-star').addClass('fa-star-o'); } else {
} else { $starSpan.addClass('starred').text('Unstar');
$starSpan.addClass('starred').text('Unstar'); $starIcon.removeClass('fa-star-o').addClass('fa-star');
$starIcon.removeClass('fa-star-o').addClass('fa-star'); }
} };
}; toggleStar($starSpan.hasClass('starred'));
toggleStar($starSpan.hasClass('starred')); }).on('ajax:error', function(e, xhr, status, error) {
}).on('ajax:error', function(e, xhr, status, error) { new Flash('Star toggle failed. Try again later.', 'alert');
new Flash('Star toggle failed. Try again later.', 'alert'); });
}); }
}
return Star; return Star;
})(); })();
}).call(window);
(() => { class Subscription {
class Subscription { constructor(containerElm) {
constructor(containerElm) { this.containerElm = containerElm;
this.containerElm = containerElm;
const subscribeButton = containerElm.querySelector('.js-subscribe-button'); const subscribeButton = containerElm.querySelector('.js-subscribe-button');
if (subscribeButton) { if (subscribeButton) {
// remove class so we don't bind twice // remove class so we don't bind twice
subscribeButton.classList.remove('js-subscribe-button'); subscribeButton.classList.remove('js-subscribe-button');
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this)); subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
} }
}
toggleSubscription(event) { toggleSubscription(event) {
const button = event.currentTarget; const button = event.currentTarget;
const buttonSpan = button.querySelector('span'); const buttonSpan = button.querySelector('span');
if (!buttonSpan || button.classList.contains('disabled')) { if (!buttonSpan || button.classList.contains('disabled')) {
return; return;
} }
button.classList.add('disabled'); button.classList.add('disabled');
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe'; const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
const toggleActionUrl = this.containerElm.dataset.url; const toggleActionUrl = this.containerElm.dataset.url;
$.post(toggleActionUrl, () => { $.post(toggleActionUrl, () => {
button.classList.remove('disabled'); button.classList.remove('disabled');
// hack to allow this to work with the issue boards Vue object // hack to allow this to work with the issue boards Vue object
if (document.querySelector('html').classList.contains('issue-boards-page')) { if (document.querySelector('html').classList.contains('issue-boards-page')) {
gl.issueBoards.boardStoreIssueSet( gl.issueBoards.boardStoreIssueSet(
'subscribed', 'subscribed',
!gl.issueBoards.BoardsStore.detail.issue.subscribed, !gl.issueBoards.BoardsStore.detail.issue.subscribed,
); );
} else { } else {
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe'; buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
} }
}); });
} }
static bindAll(selector) { static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm)); [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
} }
}
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.Subscription = Subscription; window.gl.Subscription = Subscription;
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
(function() {
this.SubscriptionSelect = (function() { window.SubscriptionSelect = (function() {
function SubscriptionSelect() { function SubscriptionSelect() {
$('.js-subscription-event').each(function(i, el) { $('.js-subscription-event').each(function(i, el) {
var fieldName; var fieldName;
fieldName = $(el).data("field-name"); fieldName = $(el).data("field-name");
return $(el).glDropdown({ return $(el).glDropdown({
selectable: true, selectable: true,
fieldName: fieldName, fieldName: fieldName,
toggleLabel: (function(_this) { toggleLabel: (function(_this) {
return function(selected, el, instance) { return function(selected, el, instance) {
var $item, label; var $item, label;
label = 'Subscription'; label = 'Subscription';
$item = instance.dropdown.find('.is-active'); $item = instance.dropdown.find('.is-active');
if ($item.length) { if ($item.length) {
label = $item.text(); label = $item.text();
} }
return label; return label;
}; };
})(this), })(this),
clicked: function(options) { clicked: function(options) {
return options.e.preventDefault(); return options.e.preventDefault();
}, },
id: function(obj, el) { id: function(obj, el) {
return $(el).data("id"); return $(el).data("id");
} }
});
}); });
} });
}
return SubscriptionSelect; return SubscriptionSelect;
})(); })();
}).call(window);
...@@ -9,19 +9,18 @@ ...@@ -9,19 +9,18 @@
// //
// <div class="js-syntax-highlight"></div> // <div class="js-syntax-highlight"></div>
// //
(function() {
$.fn.syntaxHighlight = function() {
var $children;
if ($(this).hasClass('js-syntax-highlight')) { $.fn.syntaxHighlight = function() {
// Given the element itself, apply highlighting var $children;
return $(this).addClass(gon.user_color_scheme);
} else { if ($(this).hasClass('js-syntax-highlight')) {
// Given a parent element, recurse to any of its applicable children // Given the element itself, apply highlighting
$children = $(this).find('.js-syntax-highlight'); return $(this).addClass(gon.user_color_scheme);
if ($children.length) { } else {
return $children.syntaxHighlight(); // Given a parent element, recurse to any of its applicable children
} $children = $(this).find('.js-syntax-highlight');
if ($children.length) {
return $children.syntaxHighlight();
} }
}; }
}).call(window); };
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
(function() { window.TreeView = (function() {
this.TreeView = (function() { function TreeView() {
function TreeView() { this.initKeyNav();
this.initKeyNav(); // Code browser tree slider
// Code browser tree slider // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
// Make the entire tree-item row clickable, but not if clicking another link (like a commit message) $(".tree-content-holder .tree-item").on('click', function(e) {
$(".tree-content-holder .tree-item").on('click', function(e) { var $clickedEl, path;
var $clickedEl, path; $clickedEl = $(e.target);
$clickedEl = $(e.target); path = $('.tree-item-file-name a', this).attr('href');
path = $('.tree-item-file-name a', this).attr('href'); if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) { if (e.metaKey || e.which === 2) {
if (e.metaKey || e.which === 2) { e.preventDefault();
e.preventDefault(); return window.open(path, '_blank');
return window.open(path, '_blank'); } else {
} else { return gl.utils.visitUrl(path);
return gl.utils.visitUrl(path);
}
} }
}); }
// Show the "Loading commit data" for only the first element });
$('span.log_loading:first').removeClass('hide'); // Show the "Loading commit data" for only the first element
} $('span.log_loading:first').removeClass('hide');
}
TreeView.prototype.initKeyNav = function() { TreeView.prototype.initKeyNav = function() {
var li, liSelected; var li, liSelected;
li = $("tr.tree-item"); li = $("tr.tree-item");
liSelected = null; liSelected = null;
return $('body').keydown(function(e) { return $('body').keydown(function(e) {
var next, path; var next, path;
if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) { if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
return false; return false;
} }
if (e.which === 40) { if (e.which === 40) {
if (liSelected) { if (liSelected) {
next = liSelected.next(); next = liSelected.next();
if (next.length > 0) { if (next.length > 0) {
liSelected.removeClass("selected"); liSelected.removeClass("selected");
liSelected = next.addClass("selected"); liSelected = next.addClass("selected");
}
} else {
liSelected = li.eq(0).addClass("selected");
}
return $(liSelected).focus();
} else if (e.which === 38) {
if (liSelected) {
next = liSelected.prev();
if (next.length > 0) {
liSelected.removeClass("selected");
liSelected = next.addClass("selected");
}
} else {
liSelected = li.last().addClass("selected");
} }
return $(liSelected).focus(); } else {
} else if (e.which === 13) { liSelected = li.eq(0).addClass("selected");
path = $('.tree-item.selected .tree-item-file-name a').attr('href'); }
if (path) { return $(liSelected).focus();
return gl.utils.visitUrl(path); } else if (e.which === 38) {
if (liSelected) {
next = liSelected.prev();
if (next.length > 0) {
liSelected.removeClass("selected");
liSelected = next.addClass("selected");
} }
} else {
liSelected = li.last().addClass("selected");
}
return $(liSelected).focus();
} else if (e.which === 13) {
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
if (path) {
return gl.utils.visitUrl(path);
} }
}); }
}; });
};
return TreeView; return TreeView;
})(); })();
}).call(window);
...@@ -2,34 +2,35 @@ ...@@ -2,34 +2,35 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
((global) => { class User {
global.User = class { constructor({ action }) {
constructor({ action }) { this.action = action;
this.action = action; this.placeProfileAvatarsToTop();
this.placeProfileAvatarsToTop(); this.initTabs();
this.initTabs(); this.hideProjectLimitMessage();
this.hideProjectLimitMessage(); }
}
placeProfileAvatarsToTop() { placeProfileAvatarsToTop() {
$('.profile-groups-avatars').tooltip({ $('.profile-groups-avatars').tooltip({
placement: 'top' placement: 'top'
}); });
} }
initTabs() { initTabs() {
return new global.UserTabs({ return new window.gl.UserTabs({
parentEl: '.user-profile', parentEl: '.user-profile',
action: this.action action: this.action
}); });
} }
hideProjectLimitMessage() { hideProjectLimitMessage() {
$('.hide-project-limit-message').on('click', e => { $('.hide-project-limit-message').on('click', e => {
e.preventDefault(); e.preventDefault();
Cookies.set('hide_project_limit_message', 'false'); Cookies.set('hide_project_limit_message', 'false');
$(this).parents('.project-limit-message').remove(); $(this).parents('.project-limit-message').remove();
}); });
} }
}; }
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.User = User;
...@@ -59,117 +59,118 @@ content on the Users#show page. ...@@ -59,117 +59,118 @@ content on the Users#show page.
</div> </div>
</div> </div>
*/ */
((global) => {
class UserTabs {
constructor ({ defaultAction, action, parentEl }) {
this.loaded = {};
this.defaultAction = defaultAction || 'activity';
this.action = action || this.defaultAction;
this.$parentEl = $(parentEl) || $(document);
this._location = window.location;
this.$parentEl.find('.nav-links a')
.each((i, navLink) => {
this.loaded[$(navLink).attr('data-action')] = false;
});
this.actions = Object.keys(this.loaded);
this.bindEvents();
if (this.action === 'show') {
this.action = this.defaultAction;
}
this.activateTab(this.action); class UserTabs {
constructor ({ defaultAction, action, parentEl }) {
this.loaded = {};
this.defaultAction = defaultAction || 'activity';
this.action = action || this.defaultAction;
this.$parentEl = $(parentEl) || $(document);
this._location = window.location;
this.$parentEl.find('.nav-links a')
.each((i, navLink) => {
this.loaded[$(navLink).attr('data-action')] = false;
});
this.actions = Object.keys(this.loaded);
this.bindEvents();
if (this.action === 'show') {
this.action = this.defaultAction;
} }
bindEvents() { this.activateTab(this.action);
this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this); }
this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') bindEvents() {
.on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper); this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
} .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
changeProjectsPage(e) { this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
e.preventDefault(); }
$('.tab-pane.active').empty(); changeProjectsPage(e) {
const endpoint = $(e.target).attr('href'); e.preventDefault();
this.loadTab(this.getCurrentAction(), endpoint);
}
tabShown(event) { $('.tab-pane.active').empty();
const $target = $(event.target); const endpoint = $(e.target).attr('href');
const action = $target.data('action'); this.loadTab(this.getCurrentAction(), endpoint);
const source = $target.attr('href'); }
const endpoint = $target.data('endpoint');
this.setTab(action, endpoint);
return this.setCurrentAction(source);
}
activateTab(action) { tabShown(event) {
return this.$parentEl.find(`.nav-links .js-${action}-tab a`) const $target = $(event.target);
.tab('show'); const action = $target.data('action');
} const source = $target.attr('href');
const endpoint = $target.data('endpoint');
this.setTab(action, endpoint);
return this.setCurrentAction(source);
}
setTab(action, endpoint) { activateTab(action) {
if (this.loaded[action]) { return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
return; .tab('show');
} }
if (action === 'activity') {
this.loadActivities();
}
const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; setTab(action, endpoint) {
if (loadableActions.indexOf(action) > -1) { if (this.loaded[action]) {
return this.loadTab(action, endpoint); return;
} }
if (action === 'activity') {
this.loadActivities();
} }
loadTab(action, endpoint) { const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
return $.ajax({ if (loadableActions.indexOf(action) > -1) {
beforeSend: () => this.toggleLoading(true), return this.loadTab(action, endpoint);
complete: () => this.toggleLoading(false),
dataType: 'json',
type: 'GET',
url: endpoint,
success: (data) => {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
}
});
} }
}
loadActivities() { loadTab(action, endpoint) {
if (this.loaded['activity']) { return $.ajax({
return; beforeSend: () => this.toggleLoading(true),
complete: () => this.toggleLoading(false),
dataType: 'json',
type: 'GET',
url: endpoint,
success: (data) => {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
} }
const $calendarWrap = this.$parentEl.find('.user-calendar'); });
$calendarWrap.load($calendarWrap.data('href')); }
new gl.Activities();
return this.loaded['activity'] = true;
}
toggleLoading(status) { loadActivities() {
return this.$parentEl.find('.loading-status .loading') if (this.loaded['activity']) {
.toggle(status); return;
} }
const $calendarWrap = this.$parentEl.find('.user-calendar');
$calendarWrap.load($calendarWrap.data('href'));
new gl.Activities();
return this.loaded['activity'] = true;
}
setCurrentAction(source) { toggleLoading(status) {
let new_state = source; return this.$parentEl.find('.loading-status .loading')
new_state = new_state.replace(/\/+$/, ''); .toggle(status);
new_state += this._location.search + this._location.hash; }
history.replaceState({
url: new_state
}, document.title, new_state);
return new_state;
}
getCurrentAction() { setCurrentAction(source) {
return this.$parentEl.find('.nav-links .active a').data('action'); let new_state = source;
} new_state = new_state.replace(/\/+$/, '');
new_state += this._location.search + this._location.hash;
history.replaceState({
url: new_state
}, document.title, new_state);
return new_state;
} }
global.UserTabs = UserTabs;
})(window.gl || (window.gl = {})); getCurrentAction() {
return this.$parentEl.find('.nav-links .active a').data('action');
}
}
window.gl = window.gl || {};
window.gl.UserTabs = UserTabs;
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */ /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
((global) => { const debounceTimeoutDuration = 1000;
const debounceTimeoutDuration = 1000; const invalidInputClass = 'gl-field-error-outline';
const invalidInputClass = 'gl-field-error-outline'; const successInputClass = 'gl-field-success-outline';
const successInputClass = 'gl-field-success-outline'; const unavailableMessageSelector = '.username .validation-error';
const unavailableMessageSelector = '.username .validation-error'; const successMessageSelector = '.username .validation-success';
const successMessageSelector = '.username .validation-success'; const pendingMessageSelector = '.username .validation-pending';
const pendingMessageSelector = '.username .validation-pending'; const invalidMessageSelector = '.username .gl-field-error';
const invalidMessageSelector = '.username .gl-field-error';
class UsernameValidator {
class UsernameValidator { constructor() {
constructor() { this.inputElement = $('#new_user_username');
this.inputElement = $('#new_user_username'); this.inputDomElement = this.inputElement.get(0);
this.inputDomElement = this.inputElement.get(0); this.state = {
this.state = { available: false,
available: false, valid: false,
valid: false, pending: false,
pending: false, empty: true
empty: true };
};
const debounceTimeout = _.debounce((username) => {
const debounceTimeout = _.debounce((username) => { this.validateUsername(username);
this.validateUsername(username); }, debounceTimeoutDuration);
}, debounceTimeoutDuration);
this.inputElement.on('keyup.username_check', () => {
this.inputElement.on('keyup.username_check', () => { const username = this.inputElement.val();
const username = this.inputElement.val();
this.state.valid = this.inputDomElement.validity.valid;
this.state.valid = this.inputDomElement.validity.valid; this.state.empty = !username.length;
this.state.empty = !username.length;
if (this.state.valid) {
return debounceTimeout(username);
}
this.renderState();
});
// Override generic field validation
this.inputElement.on('invalid', this.interceptInvalid.bind(this));
}
renderState() { if (this.state.valid) {
// Clear all state return debounceTimeout(username);
this.clearFieldValidationState();
if (this.state.valid && this.state.available) {
return this.setSuccessState();
} }
if (this.state.empty) { this.renderState();
return this.clearFieldValidationState(); });
}
if (this.state.pending) { // Override generic field validation
return this.setPendingState(); this.inputElement.on('invalid', this.interceptInvalid.bind(this));
} }
if (!this.state.available) { renderState() {
return this.setUnavailableState(); // Clear all state
} this.clearFieldValidationState();
if (!this.state.valid) { if (this.state.valid && this.state.available) {
return this.setInvalidState(); return this.setSuccessState();
}
} }
interceptInvalid(event) { if (this.state.empty) {
event.preventDefault(); return this.clearFieldValidationState();
event.stopPropagation();
} }
validateUsername(username) { if (this.state.pending) {
if (this.state.valid) { return this.setPendingState();
this.state.pending = true;
this.state.available = false;
this.renderState();
return $.ajax({
type: 'GET',
url: `${gon.relative_url_root}/users/${username}/exists`,
dataType: 'json',
success: (res) => this.setAvailabilityState(res.exists)
});
}
} }
setAvailabilityState(usernameTaken) { if (!this.state.available) {
if (usernameTaken) { return this.setUnavailableState();
this.state.valid = false;
this.state.available = false;
} else {
this.state.available = true;
}
this.state.pending = false;
this.renderState();
} }
clearFieldValidationState() { if (!this.state.valid) {
this.inputElement.siblings('p').hide(); return this.setInvalidState();
this.inputElement.removeClass(invalidInputClass)
.removeClass(successInputClass);
} }
}
setUnavailableState() { interceptInvalid(event) {
const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); event.preventDefault();
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); event.stopPropagation();
$usernameUnavailableMessage.show(); }
}
setSuccessState() { validateUsername(username) {
const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); if (this.state.valid) {
this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); this.state.pending = true;
$usernameSuccessMessage.show(); this.state.available = false;
this.renderState();
return $.ajax({
type: 'GET',
url: `${gon.relative_url_root}/users/${username}/exists`,
dataType: 'json',
success: (res) => this.setAvailabilityState(res.exists)
});
} }
}
setPendingState() { setAvailabilityState(usernameTaken) {
const $usernamePendingMessage = $(pendingMessageSelector); if (usernameTaken) {
if (this.state.pending) { this.state.valid = false;
$usernamePendingMessage.show(); this.state.available = false;
} else { } else {
$usernamePendingMessage.hide(); this.state.available = true;
}
} }
this.state.pending = false;
this.renderState();
}
clearFieldValidationState() {
this.inputElement.siblings('p').hide();
setInvalidState() { this.inputElement.removeClass(invalidInputClass)
const $inputErrorMessage = $(invalidMessageSelector); .removeClass(successInputClass);
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); }
$inputErrorMessage.show();
setUnavailableState() {
const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
$usernameUnavailableMessage.show();
}
setSuccessState() {
const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
$usernameSuccessMessage.show();
}
setPendingState() {
const $usernamePendingMessage = $(pendingMessageSelector);
if (this.state.pending) {
$usernamePendingMessage.show();
} else {
$usernamePendingMessage.hide();
} }
} }
global.UsernameValidator = UsernameValidator; setInvalidState() {
})(window); const $inputErrorMessage = $(invalidMessageSelector);
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
$inputErrorMessage.show();
}
}
window.UsernameValidator = UsernameValidator;
...@@ -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
......
(() => { class VisibilitySelect {
const gl = window.gl || (window.gl = {}); constructor(container) {
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
class VisibilitySelect { this.container = container;
constructor(container) { this.helpBlock = this.container.querySelector('.help-block');
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1'); this.select = this.container.querySelector('select');
this.container = container; }
this.helpBlock = this.container.querySelector('.help-block');
this.select = this.container.querySelector('select');
}
init() { init() {
if (this.select) { if (this.select) {
this.updateHelpText(); this.updateHelpText();
this.select.addEventListener('change', this.updateHelpText.bind(this)); this.select.addEventListener('change', this.updateHelpText.bind(this));
} else { } else {
this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock; this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
}
} }
}
updateHelpText() { updateHelpText() {
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description; this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
}
} }
}
gl.VisibilitySelect = VisibilitySelect; window.gl = window.gl || {};
})(); window.gl.VisibilitySelect = VisibilitySelect;
...@@ -19,9 +19,6 @@ export default { ...@@ -19,9 +19,6 @@ export default {
return hasCI && !ciStatus; return hasCI && !ciStatus;
}, },
hasPipeline() {
return Object.keys(this.mr.pipeline || {}).length > 0;
},
svg() { svg() {
return statusIconEntityMap.icon_status_failed; return statusIconEntityMap.icon_status_failed;
}, },
...@@ -48,11 +45,7 @@ export default { ...@@ -48,11 +45,7 @@ export default {
template: ` template: `
<div class="mr-widget-heading"> <div class="mr-widget-heading">
<div class="ci-widget"> <div class="ci-widget">
<template v-if="!hasPipeline"> <template v-if="hasCIError">
<i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
Waiting for pipeline...
</template>
<template v-else-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
<span class="js-icon-link icon-link"> <span class="js-icon-link icon-link">
<span <span
......
...@@ -4,66 +4,65 @@ ...@@ -4,66 +4,65 @@
import 'vendor/jquery.nicescroll'; import 'vendor/jquery.nicescroll';
import './breakpoints'; import './breakpoints';
((global) => { class Wikis {
class Wikis { constructor() {
constructor() { this.bp = Breakpoints.get();
this.bp = Breakpoints.get(); this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarEl = document.querySelector('.js-wiki-sidebar'); this.sidebarExpanded = false;
this.sidebarExpanded = false; $(this.sidebarEl).niceScroll();
$(this.sidebarEl).niceScroll();
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle'); const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
for (let i = 0; i < sidebarToggles.length; i += 1) { for (let i = 0; i < sidebarToggles.length; i += 1) {
sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e)); sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
} }
this.newWikiForm = document.querySelector('form.new-wiki-page');
if (this.newWikiForm) {
this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
}
window.addEventListener('resize', () => this.renderSidebar()); this.newWikiForm = document.querySelector('form.new-wiki-page');
this.renderSidebar(); if (this.newWikiForm) {
this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
} }
handleNewWikiSubmit(e) { window.addEventListener('resize', () => this.renderSidebar());
if (!this.newWikiForm) return; this.renderSidebar();
}
const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); handleNewWikiSubmit(e) {
const slug = gl.text.slugify(slugInput.value); if (!this.newWikiForm) return;
if (slug.length > 0) { const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
const wikisPath = slugInput.getAttribute('data-wikis-path'); const slug = gl.text.slugify(slugInput.value);
window.location.href = `${wikisPath}/${slug}`;
e.preventDefault();
}
}
handleToggleSidebar(e) { if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path');
window.location.href = `${wikisPath}/${slug}`;
e.preventDefault(); e.preventDefault();
this.sidebarExpanded = !this.sidebarExpanded;
this.renderSidebar();
} }
}
sidebarCanCollapse() { handleToggleSidebar(e) {
const bootstrapBreakpoint = this.bp.getBreakpointSize(); e.preventDefault();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm'; this.sidebarExpanded = !this.sidebarExpanded;
} this.renderSidebar();
}
sidebarCanCollapse() {
const bootstrapBreakpoint = this.bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}
renderSidebar() { renderSidebar() {
if (!this.sidebarEl) return; if (!this.sidebarEl) return;
const { classList } = this.sidebarEl; const { classList } = this.sidebarEl;
if (this.sidebarExpanded || !this.sidebarCanCollapse()) { if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
if (!classList.contains('right-sidebar-expanded')) { if (!classList.contains('right-sidebar-expanded')) {
classList.remove('right-sidebar-collapsed'); classList.remove('right-sidebar-collapsed');
classList.add('right-sidebar-expanded'); classList.add('right-sidebar-expanded');
}
} else if (classList.contains('right-sidebar-expanded')) {
classList.add('right-sidebar-collapsed');
classList.remove('right-sidebar-expanded');
} }
} else if (classList.contains('right-sidebar-expanded')) {
classList.add('right-sidebar-collapsed');
classList.remove('right-sidebar-expanded');
} }
} }
}
global.Wikis = Wikis; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); window.gl.Wikis = Wikis;
...@@ -34,65 +34,64 @@ window.Dropzone = Dropzone; ...@@ -34,65 +34,64 @@ window.Dropzone = Dropzone;
// **Cancelable** No // **Cancelable** No
// **Target** a.js-zen-leave // **Target** a.js-zen-leave
// //
(function() {
this.ZenMode = (function() { window.ZenMode = (function() {
function ZenMode() { function ZenMode() {
this.active_backdrop = null; this.active_backdrop = null;
this.active_textarea = null; this.active_textarea = null;
$(document).on('click', '.js-zen-enter', function(e) { $(document).on('click', '.js-zen-enter', function(e) {
e.preventDefault(); e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:enter'); return $(e.currentTarget).trigger('zen_mode:enter');
}); });
$(document).on('click', '.js-zen-leave', function(e) { $(document).on('click', '.js-zen-leave', function(e) {
e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:leave');
});
$(document).on('zen_mode:enter', (function(_this) {
return function(e) {
return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
};
})(this));
$(document).on('zen_mode:leave', (function(_this) {
return function(e) {
return _this.exit();
};
})(this));
$(document).on('keydown', function(e) {
// Esc
if (e.keyCode === 27) {
e.preventDefault(); e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:leave'); return $(document).trigger('zen_mode:leave');
}); }
$(document).on('zen_mode:enter', (function(_this) { });
return function(e) { }
return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
};
})(this));
$(document).on('zen_mode:leave', (function(_this) {
return function(e) {
return _this.exit();
};
})(this));
$(document).on('keydown', function(e) {
// Esc
if (e.keyCode === 27) {
e.preventDefault();
return $(document).trigger('zen_mode:leave');
}
});
}
ZenMode.prototype.enter = function(backdrop) { ZenMode.prototype.enter = function(backdrop) {
Mousetrap.pause(); Mousetrap.pause();
this.active_backdrop = $(backdrop); this.active_backdrop = $(backdrop);
this.active_backdrop.addClass('fullscreen'); this.active_backdrop.addClass('fullscreen');
this.active_textarea = this.active_backdrop.find('textarea'); this.active_textarea = this.active_backdrop.find('textarea');
// Prevent a user-resized textarea from persisting to fullscreen // Prevent a user-resized textarea from persisting to fullscreen
this.active_textarea.removeAttr('style'); this.active_textarea.removeAttr('style');
return this.active_textarea.focus(); return this.active_textarea.focus();
}; };
ZenMode.prototype.exit = function() { ZenMode.prototype.exit = function() {
if (this.active_textarea) { if (this.active_textarea) {
Mousetrap.unpause(); Mousetrap.unpause();
this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen'); this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
this.scrollTo(this.active_textarea); this.scrollTo(this.active_textarea);
this.active_textarea = null; this.active_textarea = null;
this.active_backdrop = null; this.active_backdrop = null;
return Dropzone.forElement('.div-dropzone').enable(); return Dropzone.forElement('.div-dropzone').enable();
} }
}; };
ZenMode.prototype.scrollTo = function(zen_area) { ZenMode.prototype.scrollTo = function(zen_area) {
return $.scrollTo(zen_area, 0, { return $.scrollTo(zen_area, 0, {
offset: -150 offset: -150
}); });
}; };
return ZenMode; return ZenMode;
})(); })();
}).call(window);
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
@mixin maintain-sidebar-dimensions { @mixin maintain-sidebar-dimensions {
display: block; display: block;
width: $gutter-width; width: $gutter-width;
padding: 10px 20px; padding: 10px 0;
} }
.issues-bulk-update.right-sidebar { .issues-bulk-update.right-sidebar {
......
...@@ -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;
......
...@@ -337,8 +337,7 @@ ...@@ -337,8 +337,7 @@
} }
.text-metric { .text-metric {
font-weight: 600; font-size: 12px;
font-size: 14px;
} }
.selected-metric-line { .selected-metric-line {
...@@ -382,10 +381,6 @@ ...@@ -382,10 +381,6 @@
width: 100%; width: 100%;
padding: 0; padding: 0;
padding-bottom: 100%; padding-bottom: 100%;
.text-metric-bold {
font-weight: 600;
}
} }
.prometheus-svg-container > svg { .prometheus-svg-container > svg {
...@@ -400,11 +395,15 @@ ...@@ -400,11 +395,15 @@
stroke-width: 0; stroke-width: 0;
} }
.text-metric-bold {
font-weight: 600;
}
.label-axis-text, .label-axis-text,
.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 {
...@@ -412,7 +411,20 @@ ...@@ -412,7 +411,20 @@
} }
.tick > text { .tick > text {
font-size: 14px; font-size: 12px;
}
.text-metric-title {
font-size: 12px;
}
.y-label-text,
.x-label-text {
fill: $gray-darkest;
}
.axis-tick {
stroke: $gray-darker;
} }
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
...@@ -427,3 +439,9 @@ ...@@ -427,3 +439,9 @@
} }
} }
} }
.prometheus-row {
h5 {
font-size: 16px;
}
}
...@@ -200,7 +200,6 @@ ...@@ -200,7 +200,6 @@
right: 0; right: 0;
transition: width .3s; transition: width .3s;
background: $gray-light; background: $gray-light;
padding: 0 20px;
z-index: 200; z-index: 200;
overflow: hidden; overflow: hidden;
...@@ -224,6 +223,10 @@ ...@@ -224,6 +223,10 @@
} }
} }
.issuable-sidebar {
padding: 0 20px;
}
.issuable-sidebar-header { .issuable-sidebar-header {
padding-top: 10px; padding-top: 10px;
} }
......
...@@ -72,6 +72,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -72,6 +72,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params[:application_setting][:disabled_oauth_sign_in_sources] = params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) - AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources) Array(enabled_oauth_sign_in_sources)
params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit( params.require(:application_setting).permit(
......
...@@ -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}"
......
...@@ -16,6 +16,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -16,6 +16,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: { render json: {
environments: EnvironmentSerializer environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user) .new(project: @project, current_user: @current_user)
......
...@@ -230,7 +230,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -230,7 +230,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)
......
...@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params def update_params
params.require(:project).permit( params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :auto_cancel_pending_pipelines :public_builds, :auto_cancel_pending_pipelines, :ci_config_path
) )
end end
end end
...@@ -91,8 +91,12 @@ class ProjectsController < Projects::ApplicationController ...@@ -91,8 +91,12 @@ class ProjectsController < Projects::ApplicationController
end end
def show def show
<<<<<<< HEAD
# If we're importing while we do have a repository, we're simply updating the mirror. # If we're importing while we do have a repository, we're simply updating the mirror.
if @project.import_in_progress? && !@project.updating_mirror? if @project.import_in_progress? && !@project.updating_mirror?
=======
if @project.import_in_progress?
>>>>>>> ce/master
redirect_to project_import_path(@project) redirect_to project_import_path(@project)
return return
end end
......
...@@ -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)
......
...@@ -28,7 +28,14 @@ class ProjectsFinder < UnionFinder ...@@ -28,7 +28,14 @@ class ProjectsFinder < UnionFinder
end end
def execute def execute
collection = init_collection user = params.delete(:user)
collection =
if user
PersonalProjectsFinder.new(user).execute(current_user)
else
init_collection
end
collection = by_ids(collection) collection = by_ids(collection)
collection = by_personal(collection) collection = by_personal(collection)
collection = by_starred(collection) collection = by_starred(collection)
......
...@@ -302,10 +302,6 @@ module ApplicationHelper ...@@ -302,10 +302,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
......
...@@ -34,17 +34,17 @@ module ApplicationSettingsHelper ...@@ -34,17 +34,17 @@ module ApplicationSettingsHelper
# Return a group of checkboxes that use Bootstrap's button plugin for a # Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect. # toggle button effect.
def restricted_level_checkboxes(help_block_id) def restricted_level_checkboxes(help_block_id, checkbox_name)
Gitlab::VisibilityLevel.options.map do |name, level| Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level) checked = restricted_visibility_levels(true).include?(level)
css_class = checked ? 'active' : '' css_class = checked ? 'active' : ''
checkbox_name = "application_setting[restricted_visibility_levels][]" tag_name = "application_setting_visibility_level_#{level}"
label_tag(name, class: css_class) do label_tag(tag_name, class: css_class) do
check_box_tag(checkbox_name, level, checked, check_box_tag(checkbox_name, level, checked,
autocomplete: 'off', autocomplete: 'off',
'aria-describedby' => help_block_id, 'aria-describedby' => help_block_id,
id: name) + visibility_level_icon(level) + name id: tag_name) + visibility_level_icon(level) + name
end end
end end
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
...@@ -64,6 +64,11 @@ module GroupsHelper ...@@ -64,6 +64,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)
......
...@@ -134,6 +134,18 @@ module SearchHelper ...@@ -134,6 +134,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' => project_path(@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)
......
...@@ -180,9 +180,12 @@ module Ci ...@@ -180,9 +180,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
......
...@@ -337,10 +337,24 @@ module Ci ...@@ -337,10 +337,24 @@ module Ci
end end
end end
def ci_yaml_file_path
if project.ci_config_path.blank?
'.gitlab-ci.yml'
else
project.ci_config_path
end
end
def ci_yaml_file def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file) return @ci_yaml_file if defined?(@ci_yaml_file)
@ci_yaml_file = project.repository.gitlab_ci_yml_for(sha) rescue nil @ci_yaml_file = begin
project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
rescue Rugged::ReferenceError, GRPC::NotFound, GRPC::Internal
self.yaml_errors =
"Failed to load CI/CD config file at #{ci_yaml_file_path}"
nil
end
end end
def has_yaml_errors? def has_yaml_errors?
...@@ -389,7 +403,11 @@ module Ci ...@@ -389,7 +403,11 @@ module Ci
def predefined_variables def predefined_variables
[ [
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true }, { key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
<<<<<<< HEAD
{ key: 'CI_PIPELINE_SOURCE', value: source.to_s, public: true } { key: 'CI_PIPELINE_SOURCE', value: source.to_s, public: true }
=======
{ key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true }
>>>>>>> ce/master
] ]
end end
......
...@@ -78,7 +78,7 @@ module CacheMarkdownField ...@@ -78,7 +78,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field) def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field) html_field = cached_markdown_fields.html_field(markdown_field)
cached = !cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present?
return false unless cached return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false markdown_changed = attribute_changed?(markdown_field) || false
......
...@@ -105,6 +105,14 @@ module Issuable ...@@ -105,6 +105,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
......
...@@ -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
......
...@@ -32,11 +32,19 @@ class Issue < ActiveRecord::Base ...@@ -32,11 +32,19 @@ class Issue < ActiveRecord::Base
belongs_to :moved_to, class_name: 'Issue' belongs_to :moved_to, class_name: 'Issue'
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
<<<<<<< HEAD
has_many :merge_requests_closing_issues, has_many :merge_requests_closing_issues,
class_name: 'MergeRequestsClosingIssues', class_name: 'MergeRequestsClosingIssues',
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
=======
has_many :merge_requests_closing_issues,
class_name: 'MergeRequestsClosingIssues',
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
>>>>>>> ce/master
has_many :issue_assignees has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees has_many :assignees, class_name: "User", through: :issue_assignees
......
...@@ -15,9 +15,12 @@ class MergeRequest < ActiveRecord::Base ...@@ -15,9 +15,12 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, class_name: "Project" belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User" belongs_to :merge_user, class_name: "User"
<<<<<<< HEAD
has_many :approvals, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :approvals, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :approvers, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :approvers, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :approver_groups, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :approver_groups, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
=======
>>>>>>> ce/master
has_many :merge_request_diffs has_many :merge_request_diffs
has_one :merge_request_diff, has_one :merge_request_diff,
-> { order('merge_request_diffs.id DESC') } -> { order('merge_request_diffs.id DESC') }
...@@ -209,11 +212,19 @@ class MergeRequest < ActiveRecord::Base ...@@ -209,11 +212,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
......
...@@ -186,8 +186,11 @@ class Project < ActiveRecord::Base ...@@ -186,8 +186,11 @@ class Project < ActiveRecord::Base
has_many :environments has_many :environments
has_many :deployments has_many :deployments
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule' has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
<<<<<<< HEAD
has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_project_id has_many :sourced_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :source_project_id
=======
>>>>>>> ce/master
has_many :source_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :project_id has_many :source_pipelines, class_name: Ci::Sources::Pipeline, foreign_key: :project_id
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
...@@ -205,6 +208,11 @@ class Project < ActiveRecord::Base ...@@ -205,6 +208,11 @@ class Project < ActiveRecord::Base
# Validations # Validations
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path,
format: { without: /\.{2}/,
message: 'cannot include directory traversal.' },
length: { maximum: 255 },
allow_blank: true
validates :name, validates :name,
presence: true, presence: true,
length: { maximum: 255 }, length: { maximum: 255 },
...@@ -535,6 +543,11 @@ class Project < ActiveRecord::Base ...@@ -535,6 +543,11 @@ class Project < ActiveRecord::Base
import_data&.destroy import_data&.destroy
end end
def ci_config_path=(value)
# Strip all leading slashes so that //foo -> foo
super(value&.sub(%r{\A/+}, '')&.delete("\0"))
end
def import_url=(value) def import_url=(value)
return super(value) unless Gitlab::UrlSanitizer.valid?(value) return super(value) unless Gitlab::UrlSanitizer.valid?(value)
...@@ -829,7 +842,7 @@ class Project < ActiveRecord::Base ...@@ -829,7 +842,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
...@@ -837,7 +850,7 @@ class Project < ActiveRecord::Base ...@@ -837,7 +850,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
...@@ -845,7 +858,7 @@ class Project < ActiveRecord::Base ...@@ -845,7 +858,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?
...@@ -1029,7 +1042,8 @@ class Project < ActiveRecord::Base ...@@ -1029,7 +1042,8 @@ class Project < ActiveRecord::Base
namespace: namespace.name, namespace: namespace.name,
visibility_level: visibility_level, visibility_level: visibility_level,
path_with_namespace: path_with_namespace, path_with_namespace: path_with_namespace,
default_branch: default_branch default_branch: default_branch,
ci_config_path: ci_config_path
} }
# Backward compatibility # Backward compatibility
......
...@@ -98,10 +98,13 @@ class KubernetesService < DeploymentService ...@@ -98,10 +98,13 @@ class KubernetesService < DeploymentService
end end
def predefined_variables def predefined_variables
config = YAML.dump(kubeconfig)
variables = [ variables = [
{ key: 'KUBE_URL', value: api_url, public: true }, { key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false }, { key: 'KUBE_TOKEN', value: token, public: false },
{ key: 'KUBE_NAMESPACE', value: actual_namespace, public: true } { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
{ key: 'KUBECONFIG', value: config, public: false, file: true }
] ]
if ca_pem.present? if ca_pem.present?
...@@ -137,6 +140,14 @@ class KubernetesService < DeploymentService ...@@ -137,6 +140,14 @@ class KubernetesService < DeploymentService
private private
def kubeconfig
to_kubeconfig(
url: api_url,
namespace: actual_namespace,
token: token,
ca_pem: ca_pem)
end
def namespace_placeholder def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER default_namespace || TEMPLATE_PLACEHOLDER
end end
......
...@@ -1005,7 +1005,7 @@ class Repository ...@@ -1005,7 +1005,7 @@ class Repository
def is_ancestor?(ancestor_id, descendant_id) def is_ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil? return false if ancestor_id.nil? || descendant_id.nil?
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled| Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled if is_enabled
raw_repository.is_ancestor?(ancestor_id, descendant_id) raw_repository.is_ancestor?(ancestor_id, descendant_id)
...@@ -1158,8 +1158,8 @@ class Repository ...@@ -1158,8 +1158,8 @@ class Repository
blob_data_at(sha, '.gitlab/route-map.yml') blob_data_at(sha, '.gitlab/route-map.yml')
end end
def gitlab_ci_yml_for(sha) def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
blob_data_at(sha, '.gitlab-ci.yml') blob_data_at(sha, path)
end end
private private
......
...@@ -51,6 +51,14 @@ class Service < ActiveRecord::Base ...@@ -51,6 +51,14 @@ class Service < ActiveRecord::Base
active active
end end
def show_active_box?
true
end
def editable?
true
end
def template? def template?
template template
end end
......
...@@ -38,9 +38,7 @@ class Snippet < ActiveRecord::Base ...@@ -38,9 +38,7 @@ class Snippet < ActiveRecord::Base
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 } validates :title, presence: true, length: { maximum: 255 }
validates :file_name, validates :file_name,
length: { maximum: 255 }, length: { maximum: 255 }
format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message }
validates :content, presence: true validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
......
...@@ -119,6 +119,7 @@ class User < ActiveRecord::Base ...@@ -119,6 +119,7 @@ class User < ActiveRecord::Base
has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
<<<<<<< HEAD
has_many :path_locks, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent has_many :path_locks, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent
has_many :approvals, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent has_many :approvals, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent
...@@ -127,6 +128,8 @@ class User < ActiveRecord::Base ...@@ -127,6 +128,8 @@ class User < ActiveRecord::Base
# Protected Branch Access # Protected Branch Access
has_many :protected_branch_merge_access_levels, dependent: :destroy, class_name: ProtectedBranch::MergeAccessLevel # rubocop:disable Cop/ActiveRecordDependent has_many :protected_branch_merge_access_levels, dependent: :destroy, class_name: ProtectedBranch::MergeAccessLevel # rubocop:disable Cop/ActiveRecordDependent
has_many :protected_branch_push_access_levels, dependent: :destroy, class_name: ProtectedBranch::PushAccessLevel # rubocop:disable Cop/ActiveRecordDependent has_many :protected_branch_push_access_levels, dependent: :destroy, class_name: ProtectedBranch::PushAccessLevel # rubocop:disable Cop/ActiveRecordDependent
=======
>>>>>>> ce/master
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
has_many :issue_assignees has_many :issue_assignees
......
...@@ -17,6 +17,7 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -17,6 +17,7 @@ class BasePolicy < DeclarativePolicy::Base
condition(:restricted_public_level, scope: :global) do condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end end
<<<<<<< HEAD
# EE Extensions # EE Extensions
with_scope :user with_scope :user
...@@ -27,4 +28,6 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -27,4 +28,6 @@ class BasePolicy < DeclarativePolicy::Base
with_scope :global with_scope :global
condition(:license_block) { License.block_changes? } condition(:license_block) { License.block_changes? }
=======
>>>>>>> ce/master
end end
...@@ -73,12 +73,15 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -73,12 +73,15 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def conflict_resolution_path def conflict_resolution_path
if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user) if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user)
conflicts_project_merge_request_path(project, merge_request) conflicts_project_merge_request_path(project, merge_request)
<<<<<<< HEAD
end end
end end
def rebase_path def rebase_path
if !rebase_in_progress? && should_be_rebased? && user_can_push_to_source_branch? if !rebase_in_progress? && should_be_rebased? && user_can_push_to_source_branch?
rebase_project_merge_request_path(project, merge_request) rebase_project_merge_request_path(project, merge_request)
=======
>>>>>>> ce/master
end end
end end
...@@ -91,12 +94,15 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -91,12 +94,15 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def source_branch_path def source_branch_path
if source_branch_exists? if source_branch_exists?
project_branch_path(source_project, source_branch) project_branch_path(source_project, source_branch)
<<<<<<< HEAD
end end
end end
def approvals_path def approvals_path
if requires_approve? if requires_approve?
approvals_project_merge_request_path(project, merge_request) approvals_project_merge_request_path(project, merge_request)
=======
>>>>>>> ce/master
end end
end end
......
...@@ -24,11 +24,14 @@ class EnvironmentEntity < Grape::Entity ...@@ -24,11 +24,14 @@ class EnvironmentEntity < Grape::Entity
expose :terminal_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment| expose :terminal_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment|
can?(request.current_user, :admin_environment, environment.project) && can?(request.current_user, :admin_environment, environment.project) &&
terminal_project_environment_path(environment.project, environment) terminal_project_environment_path(environment.project, environment)
<<<<<<< HEAD
end end
expose :rollout_status_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment| expose :rollout_status_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment|
can?(request.current_user, :read_deploy_board, environment.project) && can?(request.current_user, :read_deploy_board, environment.project) &&
status_project_environment_path(environment.project, environment, format: :json) status_project_environment_path(environment.project, environment, format: :json)
=======
>>>>>>> ce/master
end end
expose :created_at, :updated_at expose :created_at, :updated_at
......
...@@ -180,6 +180,7 @@ class MergeRequestEntity < IssuableEntity ...@@ -180,6 +180,7 @@ class MergeRequestEntity < IssuableEntity
expose :commit_change_content_path do |merge_request| expose :commit_change_content_path do |merge_request|
commit_change_content_project_merge_request_path(merge_request.project, merge_request) commit_change_content_project_merge_request_path(merge_request.project, merge_request)
<<<<<<< HEAD
end end
# EE-specific # EE-specific
...@@ -195,6 +196,8 @@ class MergeRequestEntity < IssuableEntity ...@@ -195,6 +196,8 @@ class MergeRequestEntity < IssuableEntity
merge_request.base_codeclimate_artifact, merge_request.base_codeclimate_artifact,
path: 'codeclimate.json') path: 'codeclimate.json')
end end
=======
>>>>>>> ce/master
end end
private private
......
...@@ -37,7 +37,7 @@ module Ci ...@@ -37,7 +37,7 @@ module Ci
unless pipeline.config_processor unless pipeline.config_processor
unless pipeline.ci_yaml_file unless pipeline.ci_yaml_file
return error('Missing .gitlab-ci.yml file') return error("Missing #{pipeline.ci_yaml_file_path} file")
end end
return error(pipeline.yaml_errors, save: save_on_errors) return error(pipeline.yaml_errors, save: save_on_errors)
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)
......
...@@ -90,8 +90,12 @@ module MergeRequests ...@@ -90,8 +90,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
......
...@@ -92,6 +92,7 @@ module QuickActions ...@@ -92,6 +92,7 @@ module QuickActions
desc 'Assign' desc 'Assign'
explanation do |users| explanation do |users|
<<<<<<< HEAD
## EE-specific ## EE-specific
users = issuable.is_a?(Issue) ? users : users.take(1) users = issuable.is_a?(Issue) ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}." "Assigns #{users.map(&:to_reference).to_sentence}."
...@@ -99,6 +100,13 @@ module QuickActions ...@@ -99,6 +100,13 @@ module QuickActions
params do params do
## EE-specific ## EE-specific
issuable.is_a?(Issue) ? '@user1 @user2' : '@user' issuable.is_a?(Issue) ? '@user1 @user2' : '@user'
=======
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'
>>>>>>> ce/master
end end
condition do condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
...@@ -109,13 +117,28 @@ module QuickActions ...@@ -109,13 +117,28 @@ module QuickActions
command :assign do |users| command :assign do |users|
next if users.empty? next if users.empty?
<<<<<<< HEAD
if issuable.is_a?(Issue) if issuable.is_a?(Issue)
# EE specific. In CE we should replace one assignee with another # EE specific. In CE we should replace one assignee with another
@updates[:assignee_ids] = issuable.assignees.pluck(:id) + users.map(&:id) @updates[:assignee_ids] = issuable.assignees.pluck(:id) + users.map(&:id)
=======
@updates[:assignee_ids] =
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)'
>>>>>>> ce/master
else else
@updates[:assignee_id] = users.last.id 'Remove assignee'
end end
end end
<<<<<<< HEAD
desc do desc do
if issuable.is_a?(Issue) if issuable.is_a?(Issue)
...@@ -129,12 +152,20 @@ module QuickActions ...@@ -129,12 +152,20 @@ module QuickActions
end end
params do params do
issuable.is_a?(Issue) ? '@user1 @user2' : '' issuable.is_a?(Issue) ? '@user1 @user2' : ''
=======
explanation do
"Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
>>>>>>> ce/master
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
<<<<<<< HEAD
command :unassign do |unassign_param = nil| command :unassign do |unassign_param = nil|
users = extract_users(unassign_param) users = extract_users(unassign_param)
...@@ -148,6 +179,45 @@ module QuickActions ...@@ -148,6 +179,45 @@ module QuickActions
else else
@updates[:assignee_id] = nil @updates[:assignee_id] = nil
end 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
>>>>>>> ce/master
end end
desc 'Change assignee(s)' desc 'Change assignee(s)'
......
...@@ -22,7 +22,9 @@ ...@@ -22,7 +22,9 @@
.form-group .form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2' = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
- restricted_level_checkboxes('restricted-visibility-help').each do |level| - checkbox_name = 'application_setting[restricted_visibility_levels][]'
= hidden_field_tag(checkbox_name)
- restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level|
.checkbox .checkbox
= level = level
%span.help-block#restricted-visibility-help %span.help-block#restricted-visibility-help
......
...@@ -72,6 +72,7 @@ ...@@ -72,6 +72,7 @@
= reply_email = reply_email
%span.light.pull-right %span.light.pull-right
= boolean_to_icon Gitlab::IncomingEmail.enabled? = boolean_to_icon Gitlab::IncomingEmail.enabled?
<<<<<<< HEAD
- elastic = "Elasticsearch" - elastic = "Elasticsearch"
%p{ "aria-label" => "#{elastic}: status " + (current_application_settings.elasticsearch_search? ? "on" : "off") } %p{ "aria-label" => "#{elastic}: status " + (current_application_settings.elasticsearch_search? ? "on" : "off") }
= elastic = elastic
...@@ -82,6 +83,8 @@ ...@@ -82,6 +83,8 @@
= geo = geo
%span.light.pull-right %span.light.pull-right
= boolean_to_icon Gitlab::Geo.enabled? = boolean_to_icon Gitlab::Geo.enabled?
=======
>>>>>>> ce/master
- container_reg = "Container Registry" - container_reg = "Container Registry"
%p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") } %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
= container_reg = container_reg
...@@ -123,6 +126,7 @@ ...@@ -123,6 +126,7 @@
GitLab API GitLab API
%span.pull-right %span.pull-right
= API::API::version = API::API::version
<<<<<<< HEAD
- if Gitlab::Geo.enabled? - if Gitlab::Geo.enabled?
%p %p
Geo Geo
...@@ -131,6 +135,8 @@ ...@@ -131,6 +135,8 @@
= Gitlab::Geo.current_node.primary ? 'Primary node' : 'Secondary node' = Gitlab::Geo.current_node.primary ? 'Primary node' : 'Secondary node'
- else - else
Undefined Undefined
=======
>>>>>>> ce/master
%p %p
Git Git
%span.pull-right %span.pull-right
......
...@@ -56,10 +56,13 @@ ...@@ -56,10 +56,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
...@@ -86,9 +86,8 @@ ...@@ -86,9 +86,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"
......
...@@ -11,7 +11,11 @@ ...@@ -11,7 +11,11 @@
Project Project
- if project_nav_tab? :files - if project_nav_tab? :files
<<<<<<< HEAD
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network path_locks)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network path_locks)) do
=======
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
>>>>>>> ce/master
= link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span %span
Repository Repository
......
...@@ -12,7 +12,10 @@ ...@@ -12,7 +12,10 @@
- if current_user - if current_user
:javascript :javascript
window.uploads_path = "#{project_uploads_path(project)}"; window.uploads_path = "#{project_uploads_path(project)}";
<<<<<<< HEAD
window.preview_markdown_path = "#{preview_markdown_path(project)}"; window.preview_markdown_path = "#{preview_markdown_path(project)}";
=======
>>>>>>> ce/master
- content_for :header_content do - content_for :header_content do
.js-dropdown-menu-projects .js-dropdown-menu-projects
......
...@@ -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
......
<<<<<<< HEAD
- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path) if @build.downloadable_single_artifacts_file? - path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path) if @build.downloadable_single_artifacts_file?
=======
- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path)
>>>>>>> ce/master
%tr.tree-item{ 'data-link' => path_to_file } %tr.tree-item{ 'data-link' => path_to_file }
- blob = file.blob - blob = file.blob
......
...@@ -19,10 +19,18 @@ ...@@ -19,10 +19,18 @@
":data-name" => "assignee.name", ":data-name" => "assignee.name",
":data-username" => "assignee.username" } ":data-username" => "assignee.username" }
.dropdown .dropdown
<<<<<<< HEAD
%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", dropdown: { header: 'Assignee(s)'} }, %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", dropdown: { header: 'Assignee(s)'} },
":data-issuable-id" => "issue.id", ":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" } ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
Select assignee(s) Select assignee(s)
=======
- 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" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
= dropdown_options[:title]
>>>>>>> ce/master
= 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")
......
...@@ -12,4 +12,4 @@ ...@@ -12,4 +12,4 @@
- if hidden > 0 - if hidden > 0
%li.alert.alert-warning %li.alert.alert-warning
= n_('%d additional commit has been omitted to prevent performance issues.', '%d additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden) = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
<<<<<<< HEAD
- if environment.deployment_service_ready? && can?(current_user, :admin_environment, @project) - if environment.deployment_service_ready? && can?(current_user, :admin_environment, @project)
=======
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
>>>>>>> ce/master
= link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do = link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
= icon('terminal') = icon('terminal')
...@@ -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)
......
...@@ -10,8 +10,12 @@ ...@@ -10,8 +10,12 @@
List List
= nav_link(controller: :boards) do = nav_link(controller: :boards) do
<<<<<<< HEAD
-# EE should use plural "Boards" -# EE should use plural "Boards"
= link_to project_boards_path(@project), title: 'Boards' do = link_to project_boards_path(@project), title: 'Boards' do
=======
= link_to project_boards_path(@project), title: 'Board' do
>>>>>>> ce/master
%span %span
Boards Boards
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago') = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
<<<<<<< HEAD
- if can?(current_user, :read_issue_link, @project) - if can?(current_user, :read_issue_link, @project)
.js-related-issues-root{ data: { endpoint: project_issue_links_path(@project, @issue), .js-related-issues-root{ data: { endpoint: project_issue_links_path(@project, @issue),
can_add_related_issues: "#{can?(current_user, :update_issue, @issue)}", can_add_related_issues: "#{can?(current_user, :update_issue, @issue)}",
...@@ -78,6 +79,8 @@ ...@@ -78,6 +79,8 @@
%h3.panel-title %h3.panel-title
Related issues Related issues
=======
>>>>>>> ce/master
#merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } } #merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
......
...@@ -32,7 +32,11 @@ ...@@ -32,7 +32,11 @@
= link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download Download
<<<<<<< HEAD
- if @build.browsable_artifacts? - if @build.browsable_artifacts?
=======
- if @build.artifacts_metadata?
>>>>>>> ce/master
= link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
Browse Browse
......
...@@ -56,13 +56,17 @@ ...@@ -56,13 +56,17 @@
- 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_project_job_path(@project, @build) }>< Complete Raw %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
<<<<<<< HEAD
=======
>>>>>>> ce/master
.controllers .controllers
- if @build.has_trace? - if @build.has_trace?
= link_to raw_project_job_path(@project, @build), = link_to raw_project_job_path(@project, @build),
...@@ -84,10 +88,12 @@ ...@@ -84,10 +88,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|
......
...@@ -45,6 +45,14 @@ ...@@ -45,6 +45,14 @@
Per job in minutes. If a job passes this threshold, it will be marked as failed Per job in minutes. If a job passes this threshold, it will be marked as failed
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank' = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank'
%hr
.form-group
= f.label :ci_config_path, 'Custom CI config path', class: 'label-light'
= f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.help-block
The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
%hr %hr
.form-group .form-group
.checkbox .checkbox
......
...@@ -18,5 +18,9 @@ ...@@ -18,5 +18,9 @@
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input %i.clear-icon.js-clear-input
= f.submit "Add to project", class: "btn btn-create" = f.submit "Add to project", class: "btn btn-create"
<<<<<<< HEAD
- if can?(current_user, :admin_project_member, @project) && !membership_locked? - if can?(current_user, :admin_project_member, @project) && !membership_locked?
= link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project" = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
=======
= link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
>>>>>>> ce/master
...@@ -11,16 +11,17 @@ ...@@ -11,16 +11,17 @@
.col-lg-9 .col-lg-9
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service = render 'shared/service_settings', form: form, subject: @service
.footer-block.row-content-block - if @service.editable?
%button.btn.btn-save{ type: 'submit' } .footer-block.row-content-block
= icon('spinner spin', class: 'hidden js-btn-spinner') %button.btn.btn-save{ type: 'submit' }
%span.js-btn-label = icon('spinner spin', class: 'hidden js-btn-spinner')
Save changes %span.js-btn-label
&nbsp; Save changes
- if @service.valid? && @service.activated? &nbsp;
- unless @service.can_test? - if @service.valid? && @service.activated?
- disabled_class = 'disabled' - unless @service.can_test?
- disabled_title = @service.disabled_title - disabled_class = 'disabled'
- disabled_title = @service.disabled_title
= link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
......
...@@ -75,7 +75,12 @@ ...@@ -75,7 +75,12 @@
.tree-controls .tree-controls
= render 'projects/find_file_link' = render 'projects/find_file_link'
<<<<<<< HEAD
= lock_file_link(html_options: { class: 'btn btn-grouped path-lock' }) = lock_file_link(html_options: { class: 'btn btn-grouped path-lock' })
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn btn-grouped' = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn btn-grouped'
=======
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
>>>>>>> ce/master
= render 'projects/buttons/download', project: @project, ref: @ref = render 'projects/buttons/download', project: @project, ref: @ref
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= render "projects/commits/head" = render "projects/commits/head"
= render 'projects/last_push' %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render 'projects/last_push'
%div{ class: container_class }
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref = render 'projects/files', commit: @last_commit, project: @project, ref: @ref
...@@ -10,6 +10,11 @@ ...@@ -10,6 +10,11 @@
.blob-result .blob-result
.file-holder .file-holder
.js-file-title.file-title .js-file-title.file-title
<<<<<<< HEAD
=======
- ref = @search_results.repository_ref
- blob_link = project_blob_path(@project, tree_join(ref, file_name))
>>>>>>> ce/master
= link_to blob_link do = link_to blob_link do
= icon('fa-file') = icon('fa-file')
%strong %strong
......
...@@ -4,7 +4,11 @@ ...@@ -4,7 +4,11 @@
.blob-result .blob-result
.file-holder .file-holder
.js-file-title.file-title .js-file-title.file-title
<<<<<<< HEAD
= link_to project_wiki_path(project, wiki_blob.basename) do = link_to project_wiki_path(project, wiki_blob.basename) do
=======
= link_to project_wiki_path(@project, wiki_blob.basename) do
>>>>>>> ce/master
%i.fa.fa-file %i.fa.fa-file
%strong %strong
- if @project - if @project
......
...@@ -7,10 +7,11 @@ ...@@ -7,10 +7,11 @@
= markdown @service.help = markdown @service.help
.service-settings .service-settings
.form-group - if @service.show_active_box?
= form.label :active, "Active", class: "control-label" .form-group
.col-sm-10 = form.label :active, "Active", class: "control-label"
= form.check_box :active .col-sm-10
= form.check_box :active
- if @service.supported_events.present? - if @service.supported_events.present?
.form-group .form-group
......
...@@ -27,7 +27,11 @@ ...@@ -27,7 +27,11 @@
.scroll-container .scroll-container
%ul.tokens-container.list-unstyled %ul.tokens-container.list-unstyled
%li.input-token %li.input-token
<<<<<<< HEAD
%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' => project_path(@project) } } %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' => project_path(@project) } }
=======
%input.form-control.filtered-search{ search_filter_input_options(type) }
>>>>>>> ce/master
= 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,18 +37,24 @@ ...@@ -37,18 +37,24 @@
- 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
<<<<<<< HEAD
- data['dropdown-header'] = 'Assignee' - data['dropdown-header'] = 'Assignee'
=======
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
- data['max-select'] = dropdown_options[:data][:'max-select']
>>>>>>> ce/master
- options[:data].merge!(data) - options[:data].merge!(data)
= dropdown_tag(title, options: options) = dropdown_tag(title, options: options)
...@@ -7,5 +7,9 @@ ...@@ -7,5 +7,9 @@
- 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: '' }
<<<<<<< HEAD
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,true)) = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,true))
=======
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
>>>>>>> ce/master
= 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)}"
...@@ -32,8 +32,13 @@ class ExpirePipelineCacheWorker ...@@ -32,8 +32,13 @@ class ExpirePipelineCacheWorker
Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json) Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json)
end end
<<<<<<< HEAD
def project_pipeline_path(pipeline) def project_pipeline_path(pipeline)
Gitlab::Routing.url_helpers.project_pipeline_path(pipeline.project, pipeline, format: :json) Gitlab::Routing.url_helpers.project_pipeline_path(pipeline.project, pipeline, format: :json)
=======
def project_pipeline_path(project, pipeline)
Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
>>>>>>> ce/master
end end
def commit_pipelines_path(project, commit) def commit_pipelines_path(project, commit)
......
---
title: Replace 'snippets/snippets.feature' spinach with rspec
merge_request: 12385
author: Alexander Randa @randaalex
---
title: Allow creation of files and directories with spaces through Web UI
merge_request: 12608
author:
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.
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