Merge branch 'master' into new-nav-fix-contextual-breadcrumbs

This commit is contained in:
Phil Hughes 2017-07-06 17:27:12 +01:00
commit e5183d907e
991 changed files with 9483 additions and 5229 deletions

1
.gitignore vendored
View File

@ -60,3 +60,4 @@ eslint-report.html
/.gitlab_workhorse_secret
/webpack-report/
/locale/**/LC_MESSAGES
/.rspec

View File

@ -474,8 +474,6 @@ codeclimate:
services:
- docker:dind
script:
- docker pull stedolan/jq
- docker pull codeclimate/codeclimate
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
artifacts:

2
.rspec
View File

@ -1,2 +0,0 @@
--color
--format Fuubar

View File

@ -965,6 +965,10 @@ RSpec/AnyInstance:
RSpec/BeEql:
Enabled: true
# We don't enforce this as we use this technique in a few places.
RSpec/BeforeAfterAll:
Enabled: false
# Check that the first argument to the top level describe is the tested class or
# module.
RSpec/DescribeClass:
@ -1024,6 +1028,12 @@ RSpec/FilePath:
RSpec/Focus:
Enabled: true
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: is_expected, should
RSpec/ImplicitExpect:
Enabled: true
EnforcedStyle: is_expected
# Checks for the usage of instance variables.
RSpec/InstanceVariable:
Enabled: false

View File

@ -6,10 +6,6 @@
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 54
RSpec/BeforeAfterAll:
Enabled: false
# Offense count: 233
RSpec/EmptyLineAfterFinalLet:
Enabled: false
@ -24,12 +20,6 @@ RSpec/EmptyLineAfterSubject:
RSpec/HookArgument:
Enabled: false
# Offense count: 12
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: is_expected, should
RSpec/ImplicitExpect:
Enabled: false
# Offense count: 11
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: it_behaves_like, it_should_behave_like

View File

@ -2,6 +2,18 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 9.3.5 (2017-07-05)
- Remove "Remove from board" button from backlog and closed list. !12430
- Do not delete protected branches when deleting all merged branches. !12624
- Set default for Remove source branch to false.
- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion.
- Expires full_path cache after a repository is renamed/transferred.
## 9.3.4 (2017-07-03)
- No changes.
## 9.3.3 (2017-06-30)
- Fix head pipeline stored in merge request for external pipelines. !12478

View File

@ -1 +1 @@
5.0.6
5.1.1

View File

@ -255,7 +255,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 2.4.0'
gem 'sentry-raven', '~> 2.5.3'
gem 'premailer-rails', '~> 1.9.7'
@ -285,6 +285,7 @@ group :metrics do
# Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta5'
gem 'raindrops', '~> 0.18'
end
group :development do

View File

@ -599,8 +599,8 @@ GEM
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta5)
mmap2 (~> 2.2.6)
prometheus-client-mmap (0.7.0.beta8)
mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@ -658,7 +658,7 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
raindrops (0.17.0)
raindrops (0.18.0)
rake (10.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
@ -775,7 +775,7 @@ GEM
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
sentry-raven (2.4.0)
sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.9.0)
@ -1062,6 +1062,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rdoc (~> 4.2)
recaptcha (~> 3.0)
@ -1089,7 +1090,7 @@ DEPENDENCIES
scss_lint (~> 0.47.0)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 2.4.0)
sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)

View File

@ -1,23 +1,8 @@
import autosize from 'vendor/autosize';
$(() => {
const $fields = $('.js-autosize');
document.addEventListener('DOMContentLoaded', () => {
const autosizeEls = document.querySelectorAll('.js-autosize');
$fields.on('autosize:resized', function resized() {
const $field = $(this);
$field.data('height', $field.outerHeight());
});
$fields.on('resize.autosize', function resize() {
const $field = $(this);
if ($field.data('height') !== $field.outerHeight()) {
$field.data('height', $field.outerHeight());
autosize.destroy($field);
$field.css('max-height', window.outerHeight);
}
});
autosize($fields);
autosize.update($fields);
$fields.css('resize', 'vertical');
autosize(autosizeEls);
autosize.update(autosizeEls);
});

View File

@ -13,25 +13,21 @@ window.Build = (function () {
this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl;
this.buildUrl = this.options.buildUrl;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
this.$document = $(document);
this.logBytes = 0;
this.scrollOffsetPadding = 30;
this.hasBeenScrolled = false;
this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
this.$body = $('body');
this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh');
this.$truncatedInfo = $('.js-truncated-info');
this.$buildTraceOutput = $('.js-build-output');
this.$scrollContainer = $('.js-scroll-container');
this.$topBar = $('.js-top-bar');
// Scroll controllers
this.$scrollTopBtn = $('.js-scroll-up');
@ -63,13 +59,22 @@ window.Build = (function () {
.off('click')
.on('click', this.scrollToBottom.bind(this));
const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$scrollContainer
$(window)
.off('scroll')
.on('scroll', () => {
this.hasBeenScrolled = true;
scrollThrottled();
const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
if (contentHeight > this.windowSize) {
// means the user did not scroll, the content was updated.
this.windowSize = contentHeight;
} else {
// User scrolled
this.hasBeenScrolled = true;
this.toggleScrollAnimation(false);
}
this.scrollThrottled();
});
$(window)
@ -77,59 +82,73 @@ window.Build = (function () {
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
this.updateArtifactRemoveDate();
this.initAffixTopArea();
// eslint-disable-next-line
this.getBuildTrace()
.then(() => this.toggleScroll())
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
})
.then(() => this.verifyTopPosition());
this.getBuildTrace();
}
Build.prototype.canScroll = function () {
return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
Build.prototype.initAffixTopArea = function () {
/**
If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not
then we default back to Bootstraps affix
**/
if (this.$topBar.css('position') !== 'static') return;
const offsetTop = this.$buildTrace.offset().top;
this.$topBar.affix({
offset: {
top: offsetTop,
},
});
};
Build.prototype.canScroll = function () {
return document.body.scrollHeight > window.innerHeight;
};
/**
* | | Up | Down |
* |--------------------------|----------|----------|
* | on scroll bottom | active | disabled |
* | on scroll top | disabled | active |
* | no scroll | disabled | disabled |
* | on.('scroll') is on top | disabled | active |
* | on('scroll) is on bottom | active | disabled |
*
*/
Build.prototype.toggleScroll = function () {
const currentPosition = this.$scrollContainer.scrollTop();
const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
const currentPosition = document.body.scrollTop;
const windowHeight = window.innerHeight;
if (this.canScroll()) {
if (currentPosition === 0) {
if (currentPosition > 0 &&
(document.body.scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (currentPosition === 0) {
// User is at Top of Build Log
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
} else if (document.body.scrollHeight - currentPosition === windowHeight) {
// User is at the bottom of the build log.
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, true);
} else {
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
}
} else {
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, true);
}
};
Build.prototype.scrollToTop = function () {
this.hasBeenScrolled = true;
this.$scrollContainer.scrollTop(0);
this.toggleScroll();
Build.prototype.scrollDown = function () {
document.body.scrollTop = document.body.scrollHeight;
};
Build.prototype.scrollToBottom = function () {
this.scrollDown();
this.hasBeenScrolled = true;
this.toggleScroll();
};
Build.prototype.scrollToTop = function () {
document.body.scrollTop = 0;
this.hasBeenScrolled = true;
this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
this.toggleScroll();
};
@ -142,47 +161,6 @@ window.Build = (function () {
this.$scrollBottomBtn.toggleClass('animate', toggle);
};
/**
* Build trace top position depends on the space ocupied by the elments rendered before
*/
Build.prototype.verifyTopPosition = function () {
const $buildPage = $('.build-page');
const $flashError = $('.alert-wrapper');
const $header = $('.build-header', $buildPage);
const $runnersStuck = $('.js-build-stuck', $buildPage);
const $startsEnvironment = $('.js-environment-container', $buildPage);
const $erased = $('.js-build-erased', $buildPage);
const prependTopDefault = 20;
// header + navigation + margin
let topPostion = 168;
if ($header.length) {
topPostion += $header.outerHeight();
}
if ($runnersStuck.length) {
topPostion += $runnersStuck.outerHeight();
}
if ($startsEnvironment.length) {
topPostion += $startsEnvironment.outerHeight() + prependTopDefault;
}
if ($erased.length) {
topPostion += $erased.outerHeight() + prependTopDefault;
}
if ($flashError.length) {
topPostion += $flashError.outerHeight() + prependTopDefault;
}
this.$buildTrace.css({
top: topPostion,
});
};
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll();
@ -200,6 +178,8 @@ window.Build = (function () {
this.state = log.state;
}
this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
if (log.append) {
this.$buildTraceOutput.append(log.html);
this.logBytes += log.size;
@ -227,14 +207,7 @@ window.Build = (function () {
}
Build.timeout = setTimeout(() => {
//eslint-disable-next-line
this.getBuildTrace()
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollToBottom();
}
})
.then(() => this.verifyTopPosition());
this.getBuildTrace();
}, 4000);
} else {
this.$buildRefreshAnimation.remove();
@ -247,7 +220,13 @@ window.Build = (function () {
})
.fail(() => {
this.$buildRefreshAnimation.remove();
});
})
.then(() => {
if (!this.hasBeenScrolled) {
this.scrollDown();
}
})
.then(() => this.toggleScroll());
};
Build.prototype.shouldHideSidebarForViewport = function () {
@ -259,14 +238,11 @@ window.Build = (function () {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header');
this.$buildTrace
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
this.$sidebar
.toggleClass('right-sidebar-expanded', shouldShow)
.toggleClass('right-sidebar-collapsed', shouldHide);
$('.js-build-page')
this.$topBar
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
@ -279,17 +255,10 @@ window.Build = (function () {
Build.prototype.sidebarOnResize = function () {
this.toggleSidebar(this.shouldHideSidebarForViewport());
this.verifyTopPosition();
if (this.canScroll()) {
this.toggleScroll();
}
};
Build.prototype.sidebarOnClick = function () {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
this.verifyTopPosition();
};
Build.prototype.updateArtifactRemoveDate = function () {

View File

@ -57,6 +57,7 @@ import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
import OAuthRememberMe from './oauth_remember_me';
(function() {
var Dispatcher;
@ -128,6 +129,7 @@ import initExperimentalFlags from './experimental_flags';
case 'sessions:new':
new UsernameValidator();
new ActiveTabMemoizer();
new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
break;
case 'projects:boards:show':
case 'projects:boards:index':

View File

@ -32,7 +32,6 @@ export default {
state: store.state,
visibility: 'available',
isLoading: false,
isLoadingFolderContent: false,
cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment,
@ -86,9 +85,6 @@ export default {
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
// We need to verify if any folder is open to also fecth it
this.openFolders = this.store.getOpenFolders();
},
});
@ -119,7 +115,7 @@ export default {
this.store.toggleFolder(folder);
if (!folder.isOpen) {
this.fetchChildEnvironments(folder, folderUrl);
this.fetchChildEnvironments(folder, folderUrl, true);
}
},
@ -147,19 +143,17 @@ export default {
.catch(this.errorCallback);
},
fetchChildEnvironments(folder, folderUrl) {
this.isLoadingFolderContent = true;
fetchChildEnvironments(folder, folderUrl, showLoader = false) {
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folderUrl)
.then(resp => resp.json())
.then((response) => {
this.store.setfolderContent(folder, response.environments);
this.isLoadingFolderContent = false;
})
.then(response => this.store.setfolderContent(folder, response.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
this.isLoadingFolderContent = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
},
@ -176,13 +170,13 @@ export default {
successCallback(resp) {
this.saveData(resp);
// If folders are open while polling we need to open them again
if (this.openFolders.length) {
this.openFolders.map((folder) => {
// We need to verify if any folder is open to also update it
const openFolders = this.store.getOpenFolders();
if (openFolders.length) {
openFolders.forEach((folder) => {
// TODO - Move this to the backend
const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
this.store.updateFolder(folder, 'isOpen', true);
return this.fetchChildEnvironments(folder, folderUrl);
});
}
@ -267,7 +261,7 @@ export default {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:is-loading-folder-content="isLoadingFolderContent" />
/>
</div>
<table-pagination

View File

@ -29,12 +29,6 @@ export default {
required: false,
default: false,
},
isLoadingFolderContent: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
@ -74,7 +68,7 @@ export default {
/>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<div v-if="isLoadingFolderContent">
<div v-if="model.isLoadingFolderContent">
<loading-icon size="2" />
</div>

View File

@ -35,14 +35,18 @@ export default class EnvironmentsStore {
*/
storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => {
const oldEnvironmentState = this.state.environments
.find(element => element.id === env.latest.id) || {};
let filtered = {};
if (env.size > 1) {
filtered = Object.assign({}, env, {
isFolder: true,
isLoadingFolderContent: oldEnvironmentState.isLoading || false,
folderName: env.name,
isOpen: false,
children: [],
isOpen: oldEnvironmentState.isOpen || false,
children: oldEnvironmentState.children || [],
});
}
@ -98,7 +102,7 @@ export default class EnvironmentsStore {
* @return {Array}
*/
toggleFolder(folder) {
return this.updateFolder(folder, 'isOpen', !folder.isOpen);
return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen);
}
/**
@ -125,23 +129,23 @@ export default class EnvironmentsStore {
return updated;
});
return this.updateFolder(folder, 'children', updatedEnvironments);
return this.updateEnvironmentProp(folder, 'children', updatedEnvironments);
}
/**
* Given a folder a prop and a new value updates the correct folder.
* Given a environment, a prop and a new value updates the correct environment.
*
* @param {Object} folder
* @param {Object} environment
* @param {String} prop
* @param {String|Boolean|Object|Array} newValue
* @return {Array}
*/
updateFolder(folder, prop, newValue) {
updateEnvironmentProp(environment, prop, newValue) {
const environments = this.state.environments;
const updatedEnvironments = environments.map((env) => {
const updateEnv = Object.assign({}, env);
if (env.isFolder && env.id === folder.id) {
if (env.id === environment.id) {
updateEnv[prop] = newValue;
}
@ -149,8 +153,6 @@ export default class EnvironmentsStore {
});
this.state.environments = updatedEnvironments;
return updatedEnvironments;
}
getOpenFolders() {

View File

@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
mounted() {
this.mediator.initBuildClass();
},
updated() {
// Wait for flash message to be appended
Vue.nextTick(() => {
if (this.mediator.build) {
this.mediator.build.verifyTopPosition();
}
});
},
render(createElement) {
return createElement('job-header', {
props: {

View File

@ -35,7 +35,7 @@
data() {
return {
graphHeight: 500,
graphHeight: 450,
graphWidth: 600,
graphHeightOffset: 120,
xScale: {},
@ -88,7 +88,9 @@
},
paddingBottomRootSvg() {
return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0;
return {
paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
};
},
},
@ -104,7 +106,7 @@
}
this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A';
this.yAxisLabel = this.columnData.y_axis || 'Values';
this.yAxisLabel = this.columnData.y_label || 'Values';
this.legendTitle = query.legend || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
@ -157,12 +159,12 @@
const xAxis = d3.svg.axis()
.scale(axisXScale)
.ticks(measurements.ticks)
.ticks(measurements.xTicks)
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(this.yScale)
.ticks(measurements.ticks)
.ticks(measurements.yTicks)
.orient('left');
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
@ -170,8 +172,12 @@
const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
.selectAll('.tick')
.each(function createTickLines() {
d3.select(this).select('line').attr('x2', width);
.each(function createTickLines(d, i) {
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.xScale = d3.time.scale()
@ -198,7 +204,7 @@
watch: {
updateAspectRatio() {
if (this.updateAspectRatio) {
this.graphHeight = 500;
this.graphHeight = 450;
this.graphWidth = 600;
this.measurements = measurements.large;
this.draw();
@ -216,14 +222,14 @@
<div
:class="classType">
<h5
class="text-center">
class="text-center graph-title">
{{columnData.title}}
</h5>
<div
class="prometheus-svg-container">
<div
class="prometheus-svg-container"
:style="paddingBottomRootSvg">
<svg
:viewBox="outterViewBox"
:style="{ 'padding-bottom': paddingBottomRootSvg }"
ref="baseSvg">
<g
class="x-axis"

View File

@ -87,14 +87,14 @@
</rect>
<text
class="text-metric text-metric-bold"
x="8"
x="16"
y="35"
transform="translate(-5, 20)">
{{formatTime}}
</text>
<text
class="text-metric-date"
x="8"
class="text-metric"
x="16"
y="15"
transform="translate(-5, 20)">
{{formatDate}}

View File

@ -109,13 +109,13 @@
</text>
<rect
class="rect-axis-text"
:x="xPosition + 50"
:x="xPosition + 60"
:y="graphHeight - 80"
width="50"
width="35"
height="50">
</rect>
<text
class="label-axis-text"
class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em">
@ -131,13 +131,13 @@
<text
class="text-metric-title"
x="50"
:y="graphHeight - 40">
:y="graphHeight - 25">
{{legendTitle}}
</text>
<text
class="text-metric-usage"
x="50"
:y="graphHeight - 25">
:y="graphHeight - 10">
{{metricUsage}}
</text>
</g>

View File

@ -8,14 +8,14 @@ export default {
},
legends: {
width: 15,
height: 30,
height: 25,
},
backgroundLegend: {
width: 30,
height: 50,
},
axisLabelLineOffset: -20,
legendOffset: 52,
legendOffset: 35,
},
large: { // This covers both md and lg screen sizes
margin: {
@ -26,14 +26,15 @@ export default {
},
legends: {
width: 20,
height: 35,
height: 30,
},
backgroundLegend: {
width: 30,
height: 150,
},
axisLabelLineOffset: 20,
legendOffset: 55,
legendOffset: 38,
},
ticks: 3,
xTicks: 8,
yTicks: 3,
};

View File

@ -0,0 +1,32 @@
/**
* OAuth-based login buttons have a separate "remember me" checkbox.
*
* Toggling this checkbox adds/removes a `remember_me` parameter to the
* login buttons' href, which is passed on to the omniauth callback.
**/
export default class OAuthRememberMe {
constructor(opts = {}) {
this.container = opts.container || '';
this.loginLinkSelector = '.oauth-login';
}
bindEvents() {
$('#remember_me', this.container).on('click', this.toggleRememberMe);
}
// eslint-disable-next-line class-methods-use-this
toggleRememberMe(event) {
const rememberMe = $(event.target).is(':checked');
$('.oauth-login', this.container).each((i, element) => {
const href = $(element).attr('href');
if (rememberMe) {
$(element).attr('href', `${href}?remember_me=1`);
} else {
$(element).attr('href', href.replace('?remember_me=1', ''));
}
});
}
}

View File

@ -2,56 +2,54 @@
/* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor';
((global) => {
/**
* Memorize the last selected tab after reloading a page.
* Does that setting the current selected tab in the localStorage
*/
class ActiveTabMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
/**
* Memorize the last selected tab after reloading a page.
* Does that setting the current selected tab in the localStorage
*/
class ActiveTabMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
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();
}
showTab() {
const anchorName = this.readData();
if (anchorName) {
const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
if (tab) {
tab.click();
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();
}
showTab() {
const anchorName = this.readData();
if (anchorName) {
const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
if (tab) {
tab.click();
}
}
saveData(val) {
if (!this.isLocalStorageAvailable) return undefined;
return window.localStorage.setItem(this.currentTabKey, val);
}
readData() {
if (!this.isLocalStorageAvailable) return null;
return window.localStorage.getItem(this.currentTabKey);
}
}
global.ActiveTabMemoizer = ActiveTabMemoizer;
})(window);
saveData(val) {
if (!this.isLocalStorageAvailable) return undefined;
return window.localStorage.setItem(this.currentTabKey, val);
}
readData() {
if (!this.isLocalStorageAvailable) return null;
return window.localStorage.getItem(this.currentTabKey);
}
}
window.ActiveTabMemoizer = ActiveTabMemoizer;

View File

@ -2,99 +2,97 @@
import FilesCommentButton from './files_comment_button';
(function() {
window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
window.SingleFileDiff = (function() {
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) {
this.file = file;
this.toggleDiff = this.toggleDiff.bind(this);
this.content = $('.diff-content', this.file);
this.$toggleIcon = $('.diff-toggle-caret', this.file);
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
this.isOpen = !this.diffForPath;
if (this.diffForPath) {
this.collapsedContent = this.content;
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
this.content = null;
this.collapsedContent.after(this.loadingContent);
this.$toggleIcon.addClass('fa-caret-right');
} else {
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down');
}
$('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
this.toggleDiff($(e.target));
}).bind(this));
function SingleFileDiff(file) {
this.file = file;
this.toggleDiff = this.toggleDiff.bind(this);
this.content = $('.diff-content', this.file);
this.$toggleIcon = $('.diff-toggle-caret', this.file);
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
this.isOpen = !this.diffForPath;
if (this.diffForPath) {
this.collapsedContent = this.content;
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
this.content = null;
this.collapsedContent.after(this.loadingContent);
this.$toggleIcon.addClass('fa-caret-right');
} else {
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down');
}
SingleFileDiff.prototype.toggleDiff = function($target, cb) {
if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
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);
}
};
$('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
this.toggleDiff($(e.target));
}).bind(this));
}
SingleFileDiff.prototype.getContentHTML = function(cb) {
SingleFileDiff.prototype.toggleDiff = function($target, cb) {
if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
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.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));
if (cb) cb();
};
})(this));
};
return SingleFileDiff;
})();
$.fn.singleFileDiff = function() {
return this.each(function() {
if (!$.data(this, 'singleFileDiff')) {
return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
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);
}
};
}).call(window);
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));
if (cb) cb();
};
})(this));
};
return SingleFileDiff;
})();
$.fn.singleFileDiff = function() {
return this.each(function() {
if (!$.data(this, 'singleFileDiff')) {
return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
}
});
};

View File

@ -1,158 +1,157 @@
/*
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*
* */
/**
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*/
(() => {
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 { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
* when the page is hidden
* @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
* @param { boolean } opts.lazyStart Configure if timer is initialized on
* instantiation or lazily
* @param { boolean } opts.immediateExecution Configure if callback should
* be executed before the first interval.
*/
constructor(opts = {}) {
this.cfg = {
callback: opts.callback,
startingInterval: opts.startingInterval,
maxInterval: opts.maxInterval,
hiddenInterval: opts.hiddenInterval,
incrementByFactorOf: opts.incrementByFactorOf,
lazyStart: opts.lazyStart,
immediateExecution: opts.immediateExecution,
};
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 { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
* when the page is hidden
* @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
* @param { boolean } opts.lazyStart Configure if timer is initialized on
* instantiation or lazily
* @param { boolean } opts.immediateExecution Configure if callback should
* be executed before the first interval.
*/
constructor(opts = {}) {
this.cfg = {
callback: opts.callback,
startingInterval: opts.startingInterval,
maxInterval: opts.maxInterval,
hiddenInterval: opts.hiddenInterval,
incrementByFactorOf: opts.incrementByFactorOf,
lazyStart: opts.lazyStart,
immediateExecution: opts.immediateExecution,
};
this.state = {
intervalId: null,
currentInterval: this.cfg.startingInterval,
pageVisibility: 'visible',
};
this.state = {
intervalId: null,
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();
}
/* public */
start() {
const cfg = this.cfg;
const state = this.state;
state.intervalId = window.setInterval(() => {
cfg.callback();
if (cfg.immediateExecution) {
cfg.immediateExecution = false;
cfg.callback();
if (this.getCurrentInterval() === cfg.maxInterval) {
return;
}
state.intervalId = window.setInterval(() => {
cfg.callback();
this.incrementInterval();
this.resume();
}, this.getCurrentInterval());
}
if (this.getCurrentInterval() === cfg.maxInterval) {
return;
}
// cancel the existing timer, setting the currentInterval back to startingInterval
cancel() {
this.setCurrentInterval(this.cfg.startingInterval);
this.stopTimer();
}
this.incrementInterval();
this.resume();
}, this.getCurrentInterval());
}
// cancel the existing timer, setting the currentInterval back to startingInterval
cancel() {
this.setCurrentInterval(this.cfg.startingInterval);
this.stopTimer();
}
onVisibilityHidden() {
if (this.cfg.hiddenInterval) {
this.setCurrentInterval(this.cfg.hiddenInterval);
this.resume();
} else {
this.cancel();
}
}
// start a timer, using the existing interval
resume() {
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
this.start();
}
onVisibilityVisible() {
onVisibilityHidden() {
if (this.cfg.hiddenInterval) {
this.setCurrentInterval(this.cfg.hiddenInterval);
this.resume();
} else {
this.cancel();
this.start();
}
destroy() {
this.cancel();
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
$(document).off('visibilitychange').off('beforeunload');
}
/* private */
initInterval() {
const cfg = this.cfg;
if (!cfg.lazyStart) {
this.start();
}
this.initVisibilityChangeHandling();
this.initPageUnloadHandling();
}
initVisibilityChangeHandling() {
// cancel interval when tab no longer shown (prevents cached pages from polling)
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
}
initPageUnloadHandling() {
// 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());
}
handleVisibilityChange(e) {
this.state.pageVisibility = e.target.visibilityState;
const intervalAction = this.isPageVisible() ?
this.onVisibilityVisible :
this.onVisibilityHidden;
intervalAction.apply(this);
}
getCurrentInterval() {
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;
if (nextInterval > cfg.maxInterval) {
nextInterval = cfg.maxInterval;
}
this.setCurrentInterval(nextInterval);
}
isPageVisible() { return this.state.pageVisibility === 'visible'; }
stopTimer() {
const state = this.state;
state.intervalId = window.clearInterval(state.intervalId);
}
}
gl.SmartInterval = SmartInterval;
})(window.gl || (window.gl = {}));
// start a timer, using the existing interval
resume() {
this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
this.start();
}
onVisibilityVisible() {
this.cancel();
this.start();
}
destroy() {
this.cancel();
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
$(document).off('visibilitychange').off('beforeunload');
}
/* private */
initInterval() {
const cfg = this.cfg;
if (!cfg.lazyStart) {
this.start();
}
this.initVisibilityChangeHandling();
this.initPageUnloadHandling();
}
initVisibilityChangeHandling() {
// cancel interval when tab no longer shown (prevents cached pages from polling)
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
}
initPageUnloadHandling() {
// 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());
}
handleVisibilityChange(e) {
this.state.pageVisibility = e.target.visibilityState;
const intervalAction = this.isPageVisible() ?
this.onVisibilityVisible :
this.onVisibilityHidden;
intervalAction.apply(this);
}
getCurrentInterval() {
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;
if (nextInterval > cfg.maxInterval) {
nextInterval = cfg.maxInterval;
}
this.setCurrentInterval(nextInterval);
}
isPageVisible() { return this.state.pageVisibility === 'visible'; }
stopTimer() {
const state = this.state;
state.intervalId = window.clearInterval(state.intervalId);
}
}
window.gl.SmartInterval = SmartInterval;

View File

@ -1,13 +1,9 @@
/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
(global => {
global.gl = global.gl || {};
window.gl.SnippetsList = function() {
var $holder = $('.snippets-list-holder');
gl.SnippetsList = function() {
var $holder = $('.snippets-list-holder');
$holder.find('.pagination').on('ajax:success', (e, data) => {
$holder.replaceWith(data.html);
});
};
})(window);
$holder.find('.pagination').on('ajax:success', (e, data) => {
$holder.replaceWith(data.html);
});
};

View File

@ -1,30 +1,28 @@
/* 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 */
(function() {
this.Star = (function() {
function Star() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
var $starIcon, $starSpan, $this, toggleStar;
$this = $(this);
$starSpan = $this.find('span');
$starIcon = $this.find('i');
toggleStar = function(isStarred) {
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
$starSpan.removeClass('starred').text('Star');
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
$starSpan.addClass('starred').text('Unstar');
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
};
toggleStar($starSpan.hasClass('starred'));
}).on('ajax:error', function(e, xhr, status, error) {
new Flash('Star toggle failed. Try again later.', 'alert');
});
}
window.Star = (function() {
function Star() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
var $starIcon, $starSpan, $this, toggleStar;
$this = $(this);
$starSpan = $this.find('span');
$starIcon = $this.find('i');
toggleStar = function(isStarred) {
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
$starSpan.removeClass('starred').text('Star');
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
$starSpan.addClass('starred').text('Unstar');
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
};
toggleStar($starSpan.hasClass('starred'));
}).on('ajax:error', function(e, xhr, status, error) {
new Flash('Star toggle failed. Try again later.', 'alert');
});
}
return Star;
})();
}).call(window);
return Star;
})();

View File

@ -1,47 +1,45 @@
(() => {
class Subscription {
constructor(containerElm) {
this.containerElm = containerElm;
class Subscription {
constructor(containerElm) {
this.containerElm = containerElm;
const subscribeButton = containerElm.querySelector('.js-subscribe-button');
if (subscribeButton) {
// remove class so we don't bind twice
subscribeButton.classList.remove('js-subscribe-button');
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
}
toggleSubscription(event) {
const button = event.currentTarget;
const buttonSpan = button.querySelector('span');
if (!buttonSpan || button.classList.contains('disabled')) {
return;
}
button.classList.add('disabled');
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
const toggleActionUrl = this.containerElm.dataset.url;
$.post(toggleActionUrl, () => {
button.classList.remove('disabled');
// hack to allow this to work with the issue boards Vue object
if (document.querySelector('html').classList.contains('issue-boards-page')) {
gl.issueBoards.boardStoreIssueSet(
'subscribed',
!gl.issueBoards.BoardsStore.detail.issue.subscribed,
);
} else {
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
}
});
}
static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
const subscribeButton = containerElm.querySelector('.js-subscribe-button');
if (subscribeButton) {
// remove class so we don't bind twice
subscribeButton.classList.remove('js-subscribe-button');
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
}
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
})();
toggleSubscription(event) {
const button = event.currentTarget;
const buttonSpan = button.querySelector('span');
if (!buttonSpan || button.classList.contains('disabled')) {
return;
}
button.classList.add('disabled');
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
const toggleActionUrl = this.containerElm.dataset.url;
$.post(toggleActionUrl, () => {
button.classList.remove('disabled');
// hack to allow this to work with the issue boards Vue object
if (document.querySelector('html').classList.contains('issue-boards-page')) {
gl.issueBoards.boardStoreIssueSet(
'subscribed',
!gl.issueBoards.BoardsStore.detail.issue.subscribed,
);
} else {
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
}
});
}
static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
}
window.gl = window.gl || {};
window.gl.Subscription = Subscription;

View File

@ -1,34 +1,33 @@
/* 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() {
function SubscriptionSelect() {
$('.js-subscription-event').each(function(i, el) {
var fieldName;
fieldName = $(el).data("field-name");
return $(el).glDropdown({
selectable: true,
fieldName: fieldName,
toggleLabel: (function(_this) {
return function(selected, el, instance) {
var $item, label;
label = 'Subscription';
$item = instance.dropdown.find('.is-active');
if ($item.length) {
label = $item.text();
}
return label;
};
})(this),
clicked: function(options) {
return options.e.preventDefault();
},
id: function(obj, el) {
return $(el).data("id");
}
});
});
}
return SubscriptionSelect;
})();
}).call(window);
window.SubscriptionSelect = (function() {
function SubscriptionSelect() {
$('.js-subscription-event').each(function(i, el) {
var fieldName;
fieldName = $(el).data("field-name");
return $(el).glDropdown({
selectable: true,
fieldName: fieldName,
toggleLabel: (function(_this) {
return function(selected, el, instance) {
var $item, label;
label = 'Subscription';
$item = instance.dropdown.find('.is-active');
if ($item.length) {
label = $item.text();
}
return label;
};
})(this),
clicked: function(options) {
return options.e.preventDefault();
},
id: function(obj, el) {
return $(el).data("id");
}
});
});
}
return SubscriptionSelect;
})();

View File

@ -9,19 +9,18 @@
//
// <div class="js-syntax-highlight"></div>
//
(function() {
$.fn.syntaxHighlight = function() {
var $children;
if ($(this).hasClass('js-syntax-highlight')) {
// Given the element itself, apply highlighting
return $(this).addClass(gon.user_color_scheme);
} else {
// Given a parent element, recurse to any of its applicable children
$children = $(this).find('.js-syntax-highlight');
if ($children.length) {
return $children.syntaxHighlight();
}
$.fn.syntaxHighlight = function() {
var $children;
if ($(this).hasClass('js-syntax-highlight')) {
// Given the element itself, apply highlighting
return $(this).addClass(gon.user_color_scheme);
} else {
// 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);
}
};

View File

@ -1,68 +1,66 @@
/* 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() {
this.TreeView = (function() {
function TreeView() {
this.initKeyNav();
// Code browser tree slider
// 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) {
var $clickedEl, path;
$clickedEl = $(e.target);
path = $('.tree-item-file-name a', this).attr('href');
if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
if (e.metaKey || e.which === 2) {
e.preventDefault();
return window.open(path, '_blank');
} else {
return gl.utils.visitUrl(path);
}
window.TreeView = (function() {
function TreeView() {
this.initKeyNav();
// Code browser tree slider
// 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) {
var $clickedEl, path;
$clickedEl = $(e.target);
path = $('.tree-item-file-name a', this).attr('href');
if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
if (e.metaKey || e.which === 2) {
e.preventDefault();
return window.open(path, '_blank');
} else {
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() {
var li, liSelected;
li = $("tr.tree-item");
liSelected = null;
return $('body').keydown(function(e) {
var next, path;
if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
return false;
TreeView.prototype.initKeyNav = function() {
var li, liSelected;
li = $("tr.tree-item");
liSelected = null;
return $('body').keydown(function(e) {
var next, path;
if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
return false;
}
if (e.which === 40) {
if (liSelected) {
next = liSelected.next();
if (next.length > 0) {
liSelected.removeClass("selected");
liSelected = next.addClass("selected");
}
} else {
liSelected = li.eq(0).addClass("selected");
}
if (e.which === 40) {
if (liSelected) {
next = liSelected.next();
if (next.length > 0) {
liSelected.removeClass("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 if (e.which === 13) {
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
if (path) {
return gl.utils.visitUrl(path);
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 if (e.which === 13) {
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
if (path) {
return gl.utils.visitUrl(path);
}
}
});
};
return TreeView;
})();
}).call(window);
return TreeView;
})();

View File

@ -2,34 +2,35 @@
import Cookies from 'js-cookie';
((global) => {
global.User = class {
constructor({ action }) {
this.action = action;
this.placeProfileAvatarsToTop();
this.initTabs();
this.hideProjectLimitMessage();
}
class User {
constructor({ action }) {
this.action = action;
this.placeProfileAvatarsToTop();
this.initTabs();
this.hideProjectLimitMessage();
}
placeProfileAvatarsToTop() {
$('.profile-groups-avatars').tooltip({
placement: 'top'
});
}
placeProfileAvatarsToTop() {
$('.profile-groups-avatars').tooltip({
placement: 'top'
});
}
initTabs() {
return new global.UserTabs({
parentEl: '.user-profile',
action: this.action
});
}
initTabs() {
return new window.gl.UserTabs({
parentEl: '.user-profile',
action: this.action
});
}
hideProjectLimitMessage() {
$('.hide-project-limit-message').on('click', e => {
e.preventDefault();
Cookies.set('hide_project_limit_message', 'false');
$(this).parents('.project-limit-message').remove();
});
}
};
})(window.gl || (window.gl = {}));
hideProjectLimitMessage() {
$('.hide-project-limit-message').on('click', e => {
e.preventDefault();
Cookies.set('hide_project_limit_message', 'false');
$(this).parents('.project-limit-message').remove();
});
}
}
window.gl = window.gl || {};
window.gl.User = User;

View File

@ -59,117 +59,118 @@ content on the Users#show page.
</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);
}
bindEvents() {
this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
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));
this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
}
changeProjectsPage(e) {
e.preventDefault();
$('.tab-pane.active').empty();
const endpoint = $(e.target).attr('href');
this.loadTab(this.getCurrentAction(), endpoint);
}
tabShown(event) {
const $target = $(event.target);
const action = $target.data('action');
const source = $target.attr('href');
const endpoint = $target.data('endpoint');
this.setTab(action, endpoint);
return this.setCurrentAction(source);
}
activateTab(action) {
return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
.tab('show');
}
setTab(action, endpoint) {
if (this.loaded[action]) {
return;
}
if (action === 'activity') {
this.loadActivities();
}
const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
if (loadableActions.indexOf(action) > -1) {
return this.loadTab(action, endpoint);
}
}
loadTab(action, endpoint) {
return $.ajax({
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));
}
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;
}
loadActivities() {
if (this.loaded['activity']) {
return;
}
const $calendarWrap = this.$parentEl.find('.user-calendar');
$calendarWrap.load($calendarWrap.data('href'));
new gl.Activities();
return this.loaded['activity'] = true;
this.activateTab(this.action);
}
bindEvents() {
this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
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));
this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
}
changeProjectsPage(e) {
e.preventDefault();
$('.tab-pane.active').empty();
const endpoint = $(e.target).attr('href');
this.loadTab(this.getCurrentAction(), endpoint);
}
tabShown(event) {
const $target = $(event.target);
const action = $target.data('action');
const source = $target.attr('href');
const endpoint = $target.data('endpoint');
this.setTab(action, endpoint);
return this.setCurrentAction(source);
}
activateTab(action) {
return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
.tab('show');
}
setTab(action, endpoint) {
if (this.loaded[action]) {
return;
}
if (action === 'activity') {
this.loadActivities();
}
toggleLoading(status) {
return this.$parentEl.find('.loading-status .loading')
.toggle(status);
}
setCurrentAction(source) {
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;
}
getCurrentAction() {
return this.$parentEl.find('.nav-links .active a').data('action');
const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
if (loadableActions.indexOf(action) > -1) {
return this.loadTab(action, endpoint);
}
}
global.UserTabs = UserTabs;
})(window.gl || (window.gl = {}));
loadTab(action, endpoint) {
return $.ajax({
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));
}
});
}
loadActivities() {
if (this.loaded['activity']) {
return;
}
const $calendarWrap = this.$parentEl.find('.user-calendar');
$calendarWrap.load($calendarWrap.data('href'));
new gl.Activities();
return this.loaded['activity'] = true;
}
toggleLoading(status) {
return this.$parentEl.find('.loading-status .loading')
.toggle(status);
}
setCurrentAction(source) {
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;
}
getCurrentAction() {
return this.$parentEl.find('.nav-links .active a').data('action');
}
}
window.gl = window.gl || {};
window.gl.UserTabs = UserTabs;

View File

@ -1,135 +1,133 @@
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
((global) => {
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
const unavailableMessageSelector = '.username .validation-error';
const successMessageSelector = '.username .validation-success';
const pendingMessageSelector = '.username .validation-pending';
const invalidMessageSelector = '.username .gl-field-error';
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
const unavailableMessageSelector = '.username .validation-error';
const successMessageSelector = '.username .validation-success';
const pendingMessageSelector = '.username .validation-pending';
const invalidMessageSelector = '.username .gl-field-error';
class UsernameValidator {
constructor() {
this.inputElement = $('#new_user_username');
this.inputDomElement = this.inputElement.get(0);
this.state = {
available: false,
valid: false,
pending: false,
empty: true
};
class UsernameValidator {
constructor() {
this.inputElement = $('#new_user_username');
this.inputDomElement = this.inputElement.get(0);
this.state = {
available: false,
valid: false,
pending: false,
empty: true
};
const debounceTimeout = _.debounce((username) => {
this.validateUsername(username);
}, debounceTimeoutDuration);
const debounceTimeout = _.debounce((username) => {
this.validateUsername(username);
}, debounceTimeoutDuration);
this.inputElement.on('keyup.username_check', () => {
const username = this.inputElement.val();
this.inputElement.on('keyup.username_check', () => {
const username = this.inputElement.val();
this.state.valid = this.inputDomElement.validity.valid;
this.state.empty = !username.length;
this.state.valid = this.inputDomElement.validity.valid;
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() {
// Clear all state
this.clearFieldValidationState();
if (this.state.valid && this.state.available) {
return this.setSuccessState();
}
if (this.state.empty) {
return this.clearFieldValidationState();
}
if (this.state.pending) {
return this.setPendingState();
}
if (!this.state.available) {
return this.setUnavailableState();
}
if (!this.state.valid) {
return this.setInvalidState();
}
}
interceptInvalid(event) {
event.preventDefault();
event.stopPropagation();
}
validateUsername(username) {
if (this.state.valid) {
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)
});
return debounceTimeout(username);
}
}
setAvailabilityState(usernameTaken) {
if (usernameTaken) {
this.state.valid = false;
this.state.available = false;
} else {
this.state.available = true;
}
this.state.pending = false;
this.renderState();
});
// Override generic field validation
this.inputElement.on('invalid', this.interceptInvalid.bind(this));
}
renderState() {
// Clear all state
this.clearFieldValidationState();
if (this.state.valid && this.state.available) {
return this.setSuccessState();
}
clearFieldValidationState() {
this.inputElement.siblings('p').hide();
this.inputElement.removeClass(invalidInputClass)
.removeClass(successInputClass);
if (this.state.empty) {
return this.clearFieldValidationState();
}
setUnavailableState() {
const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
$usernameUnavailableMessage.show();
if (this.state.pending) {
return this.setPendingState();
}
setSuccessState() {
const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
$usernameSuccessMessage.show();
if (!this.state.available) {
return this.setUnavailableState();
}
setPendingState() {
const $usernamePendingMessage = $(pendingMessageSelector);
if (this.state.pending) {
$usernamePendingMessage.show();
} else {
$usernamePendingMessage.hide();
}
}
setInvalidState() {
const $inputErrorMessage = $(invalidMessageSelector);
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
$inputErrorMessage.show();
if (!this.state.valid) {
return this.setInvalidState();
}
}
global.UsernameValidator = UsernameValidator;
})(window);
interceptInvalid(event) {
event.preventDefault();
event.stopPropagation();
}
validateUsername(username) {
if (this.state.valid) {
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 (usernameTaken) {
this.state.valid = false;
this.state.available = false;
} else {
this.state.available = true;
}
this.state.pending = false;
this.renderState();
}
clearFieldValidationState() {
this.inputElement.siblings('p').hide();
this.inputElement.removeClass(invalidInputClass)
.removeClass(successInputClass);
}
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();
}
}
setInvalidState() {
const $inputErrorMessage = $(invalidMessageSelector);
this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
$inputErrorMessage.show();
}
}
window.UsernameValidator = UsernameValidator;

View File

@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance

View File

@ -1,27 +1,24 @@
(() => {
const gl = window.gl || (window.gl = {});
class VisibilitySelect {
constructor(container) {
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
this.container = container;
this.helpBlock = this.container.querySelector('.help-block');
this.select = this.container.querySelector('select');
}
class VisibilitySelect {
constructor(container) {
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
this.container = container;
this.helpBlock = this.container.querySelector('.help-block');
this.select = this.container.querySelector('select');
}
init() {
if (this.select) {
this.updateHelpText();
this.select.addEventListener('change', this.updateHelpText.bind(this));
} else {
this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
}
}
updateHelpText() {
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
init() {
if (this.select) {
this.updateHelpText();
this.select.addEventListener('change', this.updateHelpText.bind(this));
} else {
this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
}
}
gl.VisibilitySelect = VisibilitySelect;
})();
updateHelpText() {
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
}
}
window.gl = window.gl || {};
window.gl.VisibilitySelect = VisibilitySelect;

View File

@ -17,9 +17,6 @@ export default {
return hasCI && !ciStatus;
},
hasPipeline() {
return Object.keys(this.mr.pipeline || {}).length > 0;
},
svg() {
return statusIconEntityMap.icon_status_failed;
},
@ -33,11 +30,7 @@ export default {
template: `
<div class="mr-widget-heading">
<div class="ci-widget">
<template v-if="!hasPipeline">
<i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
Waiting for pipeline...
</template>
<template v-else-if="hasCIError">
<template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
<span class="js-icon-link icon-link">
<span

View File

@ -4,66 +4,65 @@
import 'vendor/jquery.nicescroll';
import './breakpoints';
((global) => {
class Wikis {
constructor() {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarExpanded = false;
$(this.sidebarEl).niceScroll();
class Wikis {
constructor() {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarExpanded = false;
$(this.sidebarEl).niceScroll();
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
for (let i = 0; i < sidebarToggles.length; i += 1) {
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.renderSidebar();
const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
for (let i = 0; i < sidebarToggles.length; i += 1) {
sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
}
handleNewWikiSubmit(e) {
if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
const slug = gl.text.slugify(slugInput.value);
if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path');
window.location.href = `${wikisPath}/${slug}`;
e.preventDefault();
}
this.newWikiForm = document.querySelector('form.new-wiki-page');
if (this.newWikiForm) {
this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
}
handleToggleSidebar(e) {
window.addEventListener('resize', () => this.renderSidebar());
this.renderSidebar();
}
handleNewWikiSubmit(e) {
if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
const slug = gl.text.slugify(slugInput.value);
if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path');
window.location.href = `${wikisPath}/${slug}`;
e.preventDefault();
this.sidebarExpanded = !this.sidebarExpanded;
this.renderSidebar();
}
sidebarCanCollapse() {
const bootstrapBreakpoint = this.bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}
renderSidebar() {
if (!this.sidebarEl) return;
const { classList } = this.sidebarEl;
if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
if (!classList.contains('right-sidebar-expanded')) {
classList.remove('right-sidebar-collapsed');
classList.add('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 = {}));
handleToggleSidebar(e) {
e.preventDefault();
this.sidebarExpanded = !this.sidebarExpanded;
this.renderSidebar();
}
sidebarCanCollapse() {
const bootstrapBreakpoint = this.bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}
renderSidebar() {
if (!this.sidebarEl) return;
const { classList } = this.sidebarEl;
if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
if (!classList.contains('right-sidebar-expanded')) {
classList.remove('right-sidebar-collapsed');
classList.add('right-sidebar-expanded');
}
} else if (classList.contains('right-sidebar-expanded')) {
classList.add('right-sidebar-collapsed');
classList.remove('right-sidebar-expanded');
}
}
}
window.gl = window.gl || {};
window.gl.Wikis = Wikis;

View File

@ -34,65 +34,64 @@ window.Dropzone = Dropzone;
// **Cancelable** No
// **Target** a.js-zen-leave
//
(function() {
this.ZenMode = (function() {
function ZenMode() {
this.active_backdrop = null;
this.active_textarea = null;
$(document).on('click', '.js-zen-enter', function(e) {
e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:enter');
});
$(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();
return $(document).trigger('zen_mode:leave');
}
});
}
ZenMode.prototype.enter = function(backdrop) {
Mousetrap.pause();
this.active_backdrop = $(backdrop);
this.active_backdrop.addClass('fullscreen');
this.active_textarea = this.active_backdrop.find('textarea');
// Prevent a user-resized textarea from persisting to fullscreen
this.active_textarea.removeAttr('style');
return this.active_textarea.focus();
};
ZenMode.prototype.exit = function() {
if (this.active_textarea) {
Mousetrap.unpause();
this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
this.scrollTo(this.active_textarea);
this.active_textarea = null;
this.active_backdrop = null;
return Dropzone.forElement('.div-dropzone').enable();
window.ZenMode = (function() {
function ZenMode() {
this.active_backdrop = null;
this.active_textarea = null;
$(document).on('click', '.js-zen-enter', function(e) {
e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:enter');
});
$(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();
return $(document).trigger('zen_mode:leave');
}
};
});
}
ZenMode.prototype.scrollTo = function(zen_area) {
return $.scrollTo(zen_area, 0, {
offset: -150
});
};
ZenMode.prototype.enter = function(backdrop) {
Mousetrap.pause();
this.active_backdrop = $(backdrop);
this.active_backdrop.addClass('fullscreen');
this.active_textarea = this.active_backdrop.find('textarea');
// Prevent a user-resized textarea from persisting to fullscreen
this.active_textarea.removeAttr('style');
return this.active_textarea.focus();
};
return ZenMode;
})();
}).call(window);
ZenMode.prototype.exit = function() {
if (this.active_textarea) {
Mousetrap.unpause();
this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
this.scrollTo(this.active_textarea);
this.active_textarea = null;
this.active_backdrop = null;
return Dropzone.forElement('.div-dropzone').enable();
}
};
ZenMode.prototype.scrollTo = function(zen_area) {
return $.scrollTo(zen_area, 0, {
offset: -150
});
};
return ZenMode;
})();

View File

@ -17,6 +17,8 @@
max-width: $limited-layout-width-sm;
margin-left: auto;
margin-right: auto;
padding-top: 64px;
padding-bottom: 64px;
}
}

View File

@ -92,7 +92,7 @@
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
padding: 10px 20px;
padding: 10px 0;
}
.issues-bulk-update.right-sidebar {

View File

@ -3,6 +3,7 @@
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: $border-radius-default;
margin-bottom: $gl-padding;
.well-segment {
padding: $gl-padding;
@ -21,6 +22,11 @@
font-size: 12px;
}
}
&.admin-well h4 {
border-bottom: 1px solid $border-color;
padding-bottom: 8px;
}
}
.icon-container {
@ -53,6 +59,14 @@
padding: 15px;
}
.dark-well {
background-color: $gray-normal;
.btn {
width: 100%;
}
}
.well-centered {
h1 {
font-weight: normal;

View File

@ -37,65 +37,77 @@
}
.build-page {
.sticky {
position: absolute;
left: 0;
right: 0;
.build-trace-container {
position: relative;
}
.build-trace-container {
position: absolute;
top: 225px;
left: 15px;
bottom: 10px;
.build-trace {
background: $black;
color: $gray-darkest;
font-family: $monospace_font;
white-space: pre;
overflow-x: auto;
font-size: 12px;
border-radius: 0;
border: none;
&.sidebar-expanded {
right: 305px;
.bash {
display: block;
}
}
.top-bar {
height: 35px;
display: flex;
justify-content: flex-end;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
position: sticky;
position: -webkit-sticky;
top: 50px;
&.affix {
top: 50px;
}
&.sidebar-collapsed {
// with sidebar
&.affix.sidebar-expanded {
right: 306px;
left: 16px;
}
// without sidebar
&.affix.sidebar-collapsed {
right: 16px;
left: 16px;
}
code {
background: $black;
color: $gray-darkest;
&.affix-top {
position: absolute;
right: 0;
left: 0;
}
.top-bar {
top: 0;
height: 35px;
display: flex;
justify-content: flex-end;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
.truncated-info {
margin: 0 auto;
align-self: center;
.truncated-info {
margin: 0 auto;
align-self: center;
.truncated-info-size {
margin: 0 5px;
}
.truncated-info-size {
margin: 0 5px;
}
.raw-link {
color: $gl-text-color;
margin-left: 5px;
text-decoration: underline;
}
.raw-link {
color: $gl-text-color;
margin-left: 5px;
text-decoration: underline;
}
}
.controllers {
display: flex;
align-self: center;
font-size: 15px;
margin-bottom: 4px;
justify-content: center;
align-items: center;
svg {
height: 15px;
@ -103,17 +115,9 @@
fill: $gl-text-color;
}
.controllers-buttons,
.btn-scroll {
color: $gl-text-color;
height: 15px;
vertical-align: middle;
padding: 0;
width: 12px;
}
.controllers-buttons {
margin: 1px 10px;
color: $gl-text-color;
margin: 0 10px;
}
.btn-scroll.animate {
@ -143,15 +147,6 @@
}
}
.bash {
top: 35px;
left: 10px;
bottom: 0;
padding: 10px 20px 20px 5px;
white-space: pre-wrap;
overflow: auto;
}
.environment-information {
border: 1px solid $border-color;
padding: 8px $gl-padding 12px;

View File

@ -187,8 +187,7 @@
}
.text-metric {
font-weight: 600;
font-size: 14px;
font-size: 12px;
}
.selected-metric-line {
@ -232,10 +231,6 @@
width: 100%;
padding: 0;
padding-bottom: 100%;
.text-metric-bold {
font-weight: 600;
}
}
.prometheus-svg-container > svg {
@ -250,11 +245,15 @@
stroke-width: 0;
}
.text-metric-bold {
font-weight: 600;
}
.label-axis-text,
.text-metric-usage {
fill: $black;
font-weight: 500;
font-size: 14px;
font-size: 12px;
}
.legend-axis-text {
@ -262,7 +261,20 @@
}
.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) {
@ -277,3 +289,9 @@
}
}
}
.prometheus-row {
h5 {
font-size: 16px;
}
}

View File

@ -200,7 +200,6 @@
right: 0;
transition: width .3s;
background: $gray-light;
padding: 0 20px;
z-index: 200;
overflow: hidden;
@ -224,6 +223,10 @@
}
}
.issuable-sidebar {
padding: 0 20px;
}
.issuable-sidebar-header {
padding-top: 10px;
}

View File

@ -483,11 +483,12 @@ a.deploy-project-label {
.project-stats {
font-size: 0;
text-align: center;
max-width: 100%;
border-bottom: 1px solid $border-color;
.nav {
padding-top: 12px;
padding-bottom: 12px;
border-bottom: 1px solid $border-color;
}
.nav > li {

View File

@ -33,3 +33,20 @@
font-weight: normal;
}
}
.admin-runner-btn-group-cell {
min-width: 150px;
.btn-sm {
padding: 4px 9px;
}
.btn-default {
color: $gl-text-color-secondary;
}
.fa-pause,
.fa-play {
font-size: 11px;
}
}

View File

@ -71,6 +71,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit(

View File

@ -40,14 +40,14 @@ class Admin::ProjectsController < Admin::ApplicationController
::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
@project.reload
redirect_to admin_namespace_project_path(@project.namespace, @project)
redirect_to admin_project_path(@project)
end
def repository_check
RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
redirect_to(
admin_namespace_project_path(@project.namespace, @project),
admin_project_path(@project),
notice: 'Repository check was triggered.'
)
end

View File

@ -110,6 +110,8 @@ class ApplicationController < ActionController::Base
end
def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled?
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"

View File

@ -78,8 +78,7 @@ module CreatesCommit
end
def new_merge_request_path
namespace_project_new_merge_request_path(
@project_to_commit_into.namespace,
project_new_merge_request_path(
@project_to_commit_into,
merge_request: {
source_project_id: @project_to_commit_into.id,
@ -91,7 +90,7 @@ module CreatesCommit
end
def existing_merge_request_path
namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
project_merge_request_path(@project, @merge_request)
end
def merge_request_exists?

View File

@ -45,7 +45,7 @@ module MilestoneActions
def milestone_redirect_path
if @project
namespace_project_milestone_path(@project.namespace, @project, @milestone)
project_milestone_path(@project, @milestone)
elsif @group
group_milestone_path(@group, @milestone.safe_title, title: @milestone.title)
else

View File

@ -2,6 +2,6 @@ module RepositorySettingsRedirect
extend ActiveSupport::Concern
def redirect_to_repository_settings(project)
redirect_to namespace_project_settings_repository_path(project.namespace, project)
redirect_to project_settings_repository_path(project)
end
end

View File

@ -9,9 +9,9 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.'
end
end
@ -25,7 +25,7 @@ module SpammableActions
def recaptcha_check_with_fallback(&fallback)
if spammable.valid?
redirect_to spammable
redirect_to spammable_path
elsif render_recaptcha?
ensure_spam_config_loaded!
@ -56,6 +56,10 @@ module SpammableActions
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
def spammable_path
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
def authorize_submit_spammable!
access_denied! unless current_user.admin?
end

View File

@ -11,6 +11,9 @@ class Groups::MilestonesController < Groups::ApplicationController
@milestone_states = GlobalMilestone.states_count(@projects)
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
render json: milestones.map { |m| m.for_display.slice(:title, :name) }
end
end
end

View File

@ -63,7 +63,7 @@ class InvitesController < ApplicationController
when Project
project = member.source
label = "project #{project.name_with_namespace}"
path = namespace_project_path(project.namespace, project)
path = project_path(project)
when Group
group = member.source
label = "group #{group.name}"

View File

@ -1,5 +1,6 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
protect_from_forgery except: [:kerberos, :saml, :cas3]
@ -115,8 +116,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled?
params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user)
else
remember_me(@user) if remember_me?
sign_in_and_redirect(@user)
end
else
@ -147,4 +150,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
AuditEventService.new(user, user, options)
.for_authentication.security_event
end
def remember_me?
request_params = request.env['omniauth.params']
(request_params['remember_me'] == '1') if request_params.present?
end
end

View File

@ -76,13 +76,13 @@ class Projects::ApplicationController < ApplicationController
def require_non_empty_project
# Be sure to return status code 303 to avoid a double DELETE:
# http://api.rubyonrails.org/classes/ActionController/Redirecting.html
redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo?
redirect_to project_path(@project), status: 303 if @project.empty_repo?
end
def require_branch_head
unless @repository.branch_exists?(@ref)
redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref),
project_tree_path(@project, @ref),
notice: "This action is not allowed unless you are on a branch"
)
end

View File

@ -46,7 +46,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
def keep
build.keep_artifacts!
redirect_to namespace_project_job_path(project.namespace, project, build)
redirect_to project_job_path(project, build)
end
def latest_succeeded

View File

@ -27,9 +27,9 @@ class Projects::BlobController < Projects::ApplicationController
def create
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) },
success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) },
failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
failure_path: project_new_blob_path(@project, @ref))
end
def show
@ -63,7 +63,7 @@ class Projects::BlobController < Projects::ApplicationController
@path = params[:file_path] if params[:file_path].present?
create_commit(Files::UpdateService, success_path: -> { after_edit_path },
failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
failure_path: project_blob_path(@project, @id))
rescue Files::UpdateService::FileChangedError
@conflict = true
@ -83,9 +83,9 @@ class Projects::BlobController < Projects::ApplicationController
def destroy
create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) },
success_path: -> { project_tree_path(@project, @branch_name) },
failure_view: :show,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
failure_path: project_blob_path(@project, @id))
end
def diff
@ -118,7 +118,7 @@ class Projects::BlobController < Projects::ApplicationController
else
if tree = @repository.tree(@commit.id, @path)
if tree.entries.any?
return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path))
return redirect_to project_tree_path(@project, File.join(@ref, @path))
end
end
@ -143,10 +143,10 @@ class Projects::BlobController < Projects::ApplicationController
def after_edit_path
from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
if from_merge_request && @branch_name == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"##{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path))
project_blob_path(@project, File.join(@branch_name, @path))
end
end

View File

@ -52,7 +52,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to url_to_autodeploy_setup(project, branch_name),
notice: view_context.autodeploy_flash_notice(branch_name)
else
redirect_to namespace_project_tree_path(@project.namespace, @project, branch_name)
redirect_to project_tree_path(@project, branch_name)
end
else
@error = result[:message]
@ -62,7 +62,7 @@ class Projects::BranchesController < Projects::ApplicationController
format.json do
if result[:status] == :success
render json: { name: branch_name, url: namespace_project_tree_url(@project.namespace, @project, branch_name) }
render json: { name: branch_name, url: project_tree_url(@project, branch_name) }
else
render json: result[:messsage], status: :unprocessable_entity
end
@ -79,7 +79,7 @@ class Projects::BranchesController < Projects::ApplicationController
flash_type = result[:status] == :error ? :alert : :notice
flash[flash_type] = result[:message]
redirect_to namespace_project_branches_path(@project.namespace, @project), status: 303
redirect_to project_branches_path(@project), status: 303
end
format.js { render nothing: true, status: result[:return_code] }
@ -90,7 +90,7 @@ class Projects::BranchesController < Projects::ApplicationController
def destroy_all_merged
DeleteMergedBranchesService.new(@project, current_user).async_execute
redirect_to namespace_project_branches_path(@project.namespace, @project),
redirect_to project_branches_path(@project),
notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
end
@ -106,8 +106,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def url_to_autodeploy_setup(project, branch_name)
namespace_project_new_blob_path(
project.namespace,
project_new_blob_path(
project,
branch_name,
file_name: '.gitlab-ci.yml',

View File

@ -7,23 +7,23 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
before_action :validate_artifacts!
def download
redirect_to download_namespace_project_job_artifacts_path(project.namespace, project, job)
redirect_to download_project_job_artifacts_path(project, job)
end
def browse
redirect_to browse_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
redirect_to browse_project_job_artifacts_path(project, job, path: params[:path])
end
def file
redirect_to file_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
redirect_to file_project_job_artifacts_path(project, job, path: params[:path])
end
def raw
redirect_to raw_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
redirect_to raw_project_job_artifacts_path(project, job, path: params[:path])
end
def latest_succeeded
redirect_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
redirect_to latest_succeeded_project_artifacts_path(project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
end
private

View File

@ -2,15 +2,15 @@ class Projects::BuildsController < Projects::ApplicationController
before_action :authorize_read_build!
def index
redirect_to namespace_project_jobs_path(project.namespace, project)
redirect_to project_jobs_path(project)
end
def show
redirect_to namespace_project_job_path(project.namespace, project, job)
redirect_to project_job_path(project, job)
end
def raw
redirect_to raw_namespace_project_job_path(project.namespace, project, job)
redirect_to raw_project_job_path(project, job)
end
private

View File

@ -80,16 +80,16 @@ class Projects::CommitController < Projects::ApplicationController
end
def successful_change_path
referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name)
referenced_merge_request_url || project_commits_url(@project, @branch_name)
end
def failed_change_path
referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id])
referenced_merge_request_url || project_commit_url(@project, params[:id])
end
def referenced_merge_request_url
if merge_request = @commit.merged_merge_request(current_user)
namespace_project_merge_request_url(merge_request.target_project.namespace, merge_request.target_project, merge_request)
project_merge_request_url(merge_request.target_project, merge_request)
end
end

View File

@ -31,9 +31,9 @@ class Projects::CompareController < Projects::ApplicationController
from: params[:from].presence,
to: params[:to].presence
}
redirect_to namespace_project_compare_index_path(@project.namespace, @project, from_to_vars)
redirect_to project_compare_index_path(@project, from_to_vars)
else
redirect_to namespace_project_compare_path(@project.namespace, @project,
redirect_to project_compare_path(@project,
params[:from], params[:to])
end
end

View File

@ -15,6 +15,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: {
environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user)
@ -63,7 +65,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment = project.environments.create(environment_params)
if @environment.persisted?
redirect_to namespace_project_environment_path(project.namespace, project, @environment)
redirect_to project_environment_path(project, @environment)
else
render :new
end
@ -71,7 +73,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def update
if @environment.update(environment_params)
redirect_to namespace_project_environment_path(project.namespace, project, @environment)
redirect_to project_environment_path(project, @environment)
else
render :edit
end
@ -86,7 +88,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
if stop_action
polymorphic_url([project.namespace.becomes(Namespace), project, stop_action])
else
namespace_project_environment_url(project.namespace, project, @environment)
project_environment_url(project, @environment)
end
respond_to do |format|

View File

@ -44,12 +44,12 @@ class Projects::ForksController < Projects::ApplicationController
if @forked_project.saved? && @forked_project.forked?
if @forked_project.import_in_progress?
redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
redirect_to project_import_path(@forked_project, continue: continue_params)
else
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
end
end
else

View File

@ -29,7 +29,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def ci
redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project)
redirect_to charts_project_pipelines_path(@project)
end
private

View File

@ -22,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
flash[:alert] = 'Please select a group.'
end
redirect_to namespace_project_settings_members_path(project.namespace, project)
redirect_to project_settings_members_path(project)
end
def update
@ -36,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to namespace_project_settings_members_path(project.namespace, project), status: 302
redirect_to project_settings_members_path(project), status: 302
end
format.js { head :ok }
end

View File

@ -18,7 +18,7 @@ class Projects::HookLogsController < Projects::ApplicationController
set_hook_execution_notice(status, message)
redirect_to edit_namespace_project_hook_path(@project.namespace, @project, @hook)
redirect_to edit_project_hook_path(@project, @hook)
end
private

View File

@ -17,7 +17,7 @@ class Projects::HooksController < Projects::ApplicationController
@hooks = @project.hooks.select(&:persisted?)
flash[:alert] = @hook.errors.full_messages.join.html_safe
end
redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
redirect_to project_settings_integrations_path(@project)
end
def edit
@ -26,7 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
def update
if hook.update_attributes(hook_params)
flash[:notice] = 'Hook was successfully updated.'
redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
redirect_to project_settings_integrations_path(@project)
else
render 'edit'
end
@ -47,7 +47,7 @@ class Projects::HooksController < Projects::ApplicationController
def destroy
hook.destroy
redirect_to namespace_project_settings_integrations_path(@project.namespace, @project), status: 302
redirect_to project_settings_integrations_path(@project), status: 302
end
private

View File

@ -17,7 +17,7 @@ class Projects::ImportsController < Projects::ApplicationController
@project.reload.import_schedule
end
redirect_to namespace_project_import_path(@project.namespace, @project)
redirect_to project_import_path(@project)
end
def show
@ -25,10 +25,10 @@ class Projects::ImportsController < Projects::ApplicationController
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice
redirect_to project_path(@project), notice: finished_notice
end
elsif @project.import_failed?
redirect_to new_namespace_project_import_path(@project.namespace, @project)
redirect_to new_project_import_path(@project)
else
if continue_params && continue_params[:notice_now]
flash.now[:notice] = continue_params[:notice_now]
@ -50,19 +50,19 @@ class Projects::ImportsController < Projects::ApplicationController
def require_no_repo
if @project.repository_exists?
redirect_to namespace_project_path(@project.namespace, @project)
redirect_to project_path(@project)
end
end
def redirect_if_progress
if @project.import_in_progress?
redirect_to namespace_project_import_path(@project.namespace, @project)
redirect_to project_import_path(@project)
end
end
def redirect_if_no_import
if @project.repository_exists? && @project.no_import?
redirect_to namespace_project_path(@project.namespace, @project)
redirect_to project_path(@project)
end
end
end

View File

@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue
return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
@noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
return render_404 unless can?(current_user, :read_issue, @issue)
@ -238,6 +238,10 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :awardable, :issue
alias_method :spammable, :issue
def spammable_path
project_issue_path(@project, @issue)
end
def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue)
end

View File

@ -38,7 +38,7 @@ class Projects::JobsController < Projects::ApplicationController
build.cancel if can?(current_user, :update_build, build)
end
redirect_to namespace_project_jobs_path(project.namespace, project)
redirect_to project_jobs_path(project)
end
def show
@ -108,7 +108,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
redirect_to namespace_project_job_path(project.namespace, project, @build),
redirect_to project_job_path(project, @build),
notice: "Build has been successfully erased!"
else
respond_422
@ -137,6 +137,6 @@ class Projects::JobsController < Projects::ApplicationController
end
def build_path(build)
namespace_project_job_path(build.project.namespace, build.project, build)
project_job_path(build.project, build)
end
end

View File

@ -33,7 +33,7 @@ class Projects::LabelsController < Projects::ApplicationController
if @label.valid?
respond_to do |format|
format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) }
format.html { redirect_to project_labels_path(@project) }
format.json { render json: @label }
end
else
@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label = Labels::UpdateService.new(label_params).execute(@label)
if @label.valid?
redirect_to namespace_project_labels_path(@project.namespace, @project)
redirect_to project_labels_path(@project)
else
render :edit
end
@ -61,12 +61,11 @@ class Projects::LabelsController < Projects::ApplicationController
Gitlab::IssuesLabels.generate(@project)
if params[:redirect] == 'issues'
redirect_to namespace_project_issues_path(@project.namespace, @project)
redirect_to project_issues_path(@project)
elsif params[:redirect] == 'merge_requests'
redirect_to namespace_project_merge_requests_path(@project.namespace,
@project)
redirect_to project_merge_requests_path(@project)
else
redirect_to namespace_project_labels_path(@project.namespace, @project)
redirect_to project_labels_path(@project)
end
end
@ -74,7 +73,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label.destroy
@labels = find_labels
redirect_to namespace_project_labels_path(@project.namespace, @project),
redirect_to project_labels_path(@project),
status: 302,
notice: 'Label was removed'
end
@ -114,7 +113,7 @@ class Projects::LabelsController < Projects::ApplicationController
return render_404 unless promote_service.execute(@label)
respond_to do |format|
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
redirect_to(project_labels_path(@project),
notice: 'Label was promoted to a Group Label')
end
format.js
@ -125,7 +124,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to(namespace_project_labels_path(@project.namespace, @project),
redirect_to(project_labels_path(@project),
notice: 'Failed to promote label due to internal error. Please contact administrators.')
end
format.js

View File

@ -16,12 +16,10 @@ class Projects::MattermostsController < Projects::ApplicationController
if result
flash[:notice] = 'This service is now configured'
redirect_to edit_namespace_project_service_path(
@project.namespace, @project, service)
redirect_to edit_project_service_path(@project, service)
else
flash[:alert] = message || 'Failed to configure service'
redirect_to new_namespace_project_mattermost_path(
@project.namespace, @project)
redirect_to new_project_mattermost_path(@project)
end
end

View File

@ -52,7 +52,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) }
render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
rescue Gitlab::Conflict::ResolutionError => e
render status: :bad_request, json: { message: e.message }
end

View File

@ -211,21 +211,18 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
stop_url =
if environment.stop_action? && can?(current_user, :create_deployment, environment)
stop_namespace_project_environment_path(project.namespace, project, environment)
stop_project_environment_path(project, environment)
end
metrics_url =
if can?(current_user, :read_environment, environment) && environment.has_metrics?
metrics_namespace_project_environment_deployment_path(environment.project.namespace,
environment.project,
environment,
deployment)
metrics_project_environment_deployment_path(environment.project, environment, deployment)
end
{
id: environment.id,
name: environment.name,
url: namespace_project_environment_path(project.namespace, project, environment),
url: project_environment_path(project, environment),
metrics_url: metrics_url,
stop_url: stop_url,
external_url: environment.external_url,

View File

@ -51,8 +51,7 @@ class Projects::MilestonesController < Projects::ApplicationController
@milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute
if @milestone.save
redirect_to namespace_project_milestone_path(@project.namespace,
@project, @milestone)
redirect_to project_milestone_path(@project, @milestone)
else
render "new"
end
@ -65,8 +64,7 @@ class Projects::MilestonesController < Projects::ApplicationController
format.js
format.html do
if @milestone.valid?
redirect_to namespace_project_milestone_path(@project.namespace,
@project, @milestone)
redirect_to project_milestone_path(@project, @milestone)
else
render :edit
end

View File

@ -8,8 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :assign_commit
def show
@url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
@commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
@url = project_network_path(@project, @ref, @options.merge(format: :json))
@commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
respond_to do |format|
format.html do

View File

@ -15,7 +15,7 @@ class Projects::PagesController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to namespace_project_pages_path(@project.namespace, @project),
redirect_to project_pages_path(@project),
status: 302,
notice: 'Pages were removed'
end

View File

@ -16,7 +16,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
@domain = @project.pages_domains.create(pages_domain_params)
if @domain.valid?
redirect_to namespace_project_pages_path(@project.namespace, @project)
redirect_to project_pages_path(@project)
else
render 'new'
end
@ -27,7 +27,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to namespace_project_pages_path(@project.namespace, @project),
redirect_to project_pages_path(@project),
status: 302,
notice: 'Domain was removed'
end

View File

@ -34,7 +34,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
def update
if schedule.update(schedule_params)
redirect_to namespace_project_pipeline_schedules_path(@project.namespace.becomes(Namespace), @project)
redirect_to project_pipeline_schedules_path(@project)
else
render :edit
end

View File

@ -60,7 +60,7 @@ class Projects::PipelinesController < Projects::ApplicationController
.execute(:web, ignore_skip_ci: true, save_on_errors: false)
if @pipeline.persisted?
redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
redirect_to project_pipeline_path(project, @pipeline)
else
render 'new'
end
@ -111,7 +111,7 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
redirect_back_or_default default: project_pipelines_path(project)
end
format.json { head :no_content }
@ -123,7 +123,7 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
redirect_back_or_default default: project_pipelines_path(project)
end
format.json { head :no_content }

View File

@ -2,13 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params)
redirect_to project_settings_ci_cd_path(@project, params: params)
end
def update
if @project.update_attributes(update_params)
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
redirect_to project_settings_ci_cd_path(@project)
else
render 'show'
end
@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params
params.require(:project).permit(
: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

View File

@ -7,7 +7,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def index
sort = params[:sort].presence || sort_value_name
redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort)
redirect_to project_settings_members_path(@project, sort: sort)
end
def update
@ -19,7 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def resend_invite
redirect_path = namespace_project_settings_members_path(@project.namespace, @project)
redirect_path = project_settings_members_path(@project)
@project_member = @project.project_members.find(params[:id])
@ -42,7 +42,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
return render_404
end
redirect_to(namespace_project_settings_members_path(project.namespace, project),
redirect_to(project_settings_members_path(project),
notice: notice)
end

View File

@ -13,21 +13,21 @@ class Projects::RefsController < Projects::ApplicationController
new_path =
case params[:destination]
when "tree"
namespace_project_tree_path(@project.namespace, @project, @id)
project_tree_path(@project, @id)
when "blob"
namespace_project_blob_path(@project.namespace, @project, @id)
project_blob_path(@project, @id)
when "graph"
namespace_project_network_path(@project.namespace, @project, @id, @options)
project_network_path(@project, @id, @options)
when "graphs"
namespace_project_graph_path(@project.namespace, @project, @id)
project_graph_path(@project, @id)
when "find_file"
namespace_project_find_file_path(@project.namespace, @project, @id)
project_find_file_path(@project, @id)
when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id)
commits_project_graph_path(@project, @id)
when "badges"
namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id)
project_pipelines_settings_path(@project, ref: @id)
else
namespace_project_commits_path(@project.namespace, @project, @id)
project_commits_path(@project, @id)
end
redirect_to new_path
@ -62,7 +62,7 @@ class Projects::RefsController < Projects::ApplicationController
offset = (@offset + @limit)
if contents.size > offset
@more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset)
@more_log_url = logs_file_project_ref_path(@project, @ref, @path || '', offset: offset)
end
respond_to do |format|

View File

@ -10,11 +10,11 @@ module Projects
def destroy
if image.destroy
redirect_to project_container_registry_path(@project),
redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Image repository has been removed successfully!'
else
redirect_to project_container_registry_path(@project),
redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove image repository!'
end

View File

@ -5,11 +5,11 @@ module Projects
def destroy
if tag.delete
redirect_to project_container_registry_path(@project),
redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Registry tag has been removed successfully!'
else
redirect_to project_container_registry_path(@project),
redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove registry tag!'
end

View File

@ -19,7 +19,7 @@ class Projects::ReleasesController < Projects::ApplicationController
release.destroy
end
redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
redirect_to project_tag_path(@project, @tag.name)
end
private

View File

@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
redirect_to project_settings_ci_cd_path(@project)
end
def edit
@ -49,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
redirect_to project_settings_ci_cd_path(@project)
end
protected

View File

@ -15,7 +15,7 @@ class Projects::ServicesController < Projects::ApplicationController
def update
if @service.save(context: :manual_change)
redirect_to(namespace_project_settings_integrations_path(@project.namespace, @project), notice: success_message)
redirect_to(project_settings_integrations_path(@project), notice: success_message)
else
render 'edit'
end

View File

@ -30,7 +30,7 @@ class Projects::SnippetsController < Projects::ApplicationController
).execute
@snippets = @snippets.page(params[:page])
if @snippets.out_of_range? && @snippets.total_pages != 0
redirect_to namespace_project_snippets_path(page: @snippets.total_pages)
redirect_to project_snippets_path(@project, page: @snippets.total_pages)
end
end
@ -79,7 +79,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet.destroy
redirect_to namespace_project_snippets_path(@project.namespace, @project), status: 302
redirect_to project_snippets_path(@project), status: 302
end
protected
@ -90,6 +90,10 @@ class Projects::SnippetsController < Projects::ApplicationController
alias_method :awardable, :snippet
alias_method :spammable, :snippet
def spammable_path
project_snippet_path(@project, @snippet)
end
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
end

View File

@ -35,7 +35,7 @@ class Projects::TagsController < Projects::ApplicationController
if result[:status] == :success
@tag = result[:tag]
redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
redirect_to project_tag_path(@project, @tag.name)
else
@error = result[:message]
@message = params[:message]
@ -50,7 +50,7 @@ class Projects::TagsController < Projects::ApplicationController
respond_to do |format|
if result[:status] == :success
format.html do
redirect_to namespace_project_tags_path(@project.namespace, @project), status: 303
redirect_to project_tags_path(@project), status: 303
end
format.js
@ -58,7 +58,7 @@ class Projects::TagsController < Projects::ApplicationController
@error = result[:message]
format.html do
redirect_to namespace_project_tags_path(@project.namespace, @project),
redirect_to project_tags_path(@project),
alert: @error, status: 303
end

View File

@ -16,7 +16,7 @@ class Projects::TreeController < Projects::ApplicationController
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
return redirect_to(
namespace_project_blob_path(@project.namespace, @project,
project_blob_path(@project,
File.join(@ref, @path))
)
elsif @path.present?
@ -37,8 +37,8 @@ class Projects::TreeController < Projects::ApplicationController
return render_404 unless @commit_params.values.all?
create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)),
failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)),
failure_path: project_tree_path(@project, @ref))
end
private

View File

@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController
layout 'project_settings'
def index
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
redirect_to project_settings_ci_cd_path(@project)
end
def create
@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not create a new trigger.'
end
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
redirect_to project_settings_ci_cd_path(@project)
end
def take_ownership
@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not take ownership of trigger.'
end
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
redirect_to project_settings_ci_cd_path(@project)
end
def edit
@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.'
redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.'
else
render action: "edit"
end
@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = "Could not remove the trigger."
end
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), status: 302
redirect_to project_settings_ci_cd_path(@project), status: 302
end
private

View File

@ -4,7 +4,7 @@ class Projects::VariablesController < Projects::ApplicationController
layout 'project_settings'
def index
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
redirect_to project_settings_ci_cd_path(@project)
end
def show
@ -14,19 +14,19 @@ class Projects::VariablesController < Projects::ApplicationController
def update
@variable = @project.variables.find(params[:id])
if @variable.update_attributes(project_params)
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.'
if @variable.update_attributes(variable_params)
redirect_to project_variables_path(project), notice: 'Variable was successfully updated.'
else
render action: "show"
end
end
def create
@variable = Ci::Variable.new(project_params)
@variable = @project.variables.new(variable_params)
if @variable.valid? && @project.variables << @variable
if @variable.save
flash[:notice] = 'Variables were successfully updated.'
redirect_to namespace_project_settings_ci_cd_path(project.namespace, project)
redirect_to project_settings_ci_cd_path(project)
else
render "show"
end
@ -36,15 +36,18 @@ class Projects::VariablesController < Projects::ApplicationController
@key = @project.variables.find(params[:id])
@key.destroy
redirect_to namespace_project_settings_ci_cd_path(project.namespace, project),
redirect_to project_settings_ci_cd_path(project),
status: 302,
notice: 'Variable was successfully removed.'
end
private
def project_params
params.require(:variable)
.permit([:id, :key, :value, :protected, :_destroy])
def variable_params
params.require(:variable).permit(*variable_params_attributes)
end
def variable_params_attributes
%i[id key value protected _destroy]
end
end

View File

@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.valid?
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
project_wiki_path(@project, @page),
notice: 'Wiki was successfully updated.'
)
else
@ -62,7 +62,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.persisted?
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
project_wiki_path(@project, @page),
notice: 'Wiki was successfully updated.'
)
else
@ -75,7 +75,7 @@ class Projects::WikisController < Projects::ApplicationController
unless @page
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, :home),
project_wiki_path(@project, :home),
notice: "Page not found"
)
end
@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id])
WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to namespace_project_wiki_path(@project.namespace, @project, :home),
redirect_to project_wiki_path(@project, :home),
status: 302,
notice: "Page was successfully deleted"
end

View File

@ -92,7 +92,7 @@ class ProjectsController < Projects::ApplicationController
def show
if @project.import_in_progress?
redirect_to namespace_project_import_path(@project.namespace, @project)
redirect_to project_import_path(@project)
return
end

View File

@ -44,7 +44,7 @@ class SearchController < ApplicationController
query = params[:search].strip.downcase
found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query)
redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha
redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha
end
end
end

View File

@ -107,6 +107,10 @@ class SnippetsController < ApplicationController
alias_method :awardable, :snippet
alias_method :spammable, :snippet
def spammable_path
snippet_path(@snippet)
end
def authorize_read_snippet!
return if can?(current_user, :read_personal_snippet, @snippet)

Some files were not shown because too many files have changed in this diff Show More