Commit 727af20c authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2017-10-11' into 'master'

CE upstream - Wednesday

Closes gitlab-com/support-forum#2476

See merge request gitlab-org/gitlab-ee!3121
parents bd7ba308 5049eb42
...@@ -588,7 +588,7 @@ karma: ...@@ -588,7 +588,7 @@ karma:
- chrome_debug.log - chrome_debug.log
- coverage-javascript/ - coverage-javascript/
codeclimate: codequality:
<<: *except-docs <<: *except-docs
<<: *pull-cache <<: *pull-cache
before_script: [] before_script: []
......
...@@ -414,7 +414,7 @@ group :ed25519 do ...@@ -414,7 +414,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.39.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.41.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -297,7 +297,7 @@ GEM ...@@ -297,7 +297,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.39.0) gitaly-proto (0.41.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -1062,7 +1062,7 @@ DEPENDENCIES ...@@ -1062,7 +1062,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.39.0) gitaly-proto (~> 0.41.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */ /* eslint-disable func-names, prefer-arrow-callback, no-return-assign */
import { visitUrl } from './lib/utils/url_utility'; import { visitUrl } from './lib/utils/url_utility';
import { convertPermissionToBoolean } from './lib/utils/common_utils'; import { convertPermissionToBoolean } from './lib/utils/common_utils';
window.BuildArtifacts = (function() { export default class BuildArtifacts {
function BuildArtifacts() { constructor() {
this.disablePropagation(); this.disablePropagation();
this.setupEntryClick(); this.setupEntryClick();
this.setupTooltips(); this.setupTooltips();
} }
// eslint-disable-next-line class-methods-use-this
BuildArtifacts.prototype.disablePropagation = function() { disablePropagation() {
$('.top-block').on('click', '.download', function(e) { $('.top-block').on('click', '.download', function (e) {
return e.stopPropagation(); return e.stopPropagation();
}); });
return $('.tree-holder').on('click', 'tr[data-link] a', function(e) { return $('.tree-holder').on('click', 'tr[data-link] a', function (e) {
return e.stopImmediatePropagation(); return e.stopImmediatePropagation();
}); });
}; }
// eslint-disable-next-line class-methods-use-this
BuildArtifacts.prototype.setupEntryClick = function() { setupEntryClick() {
return $('.tree-holder').on('click', 'tr[data-link]', function(e) { return $('.tree-holder').on('click', 'tr[data-link]', function () {
visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink)); visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink));
}); });
}; }
// eslint-disable-next-line class-methods-use-this
BuildArtifacts.prototype.setupTooltips = function() { setupTooltips() {
$('.js-artifact-tree-tooltip').tooltip({ $('.js-artifact-tree-tooltip').tooltip({
placement: 'bottom', placement: 'bottom',
// Stop the tooltip from hiding when we stop hovering the element directly // Stop the tooltip from hiding when we stop hovering the element directly
...@@ -41,7 +41,5 @@ window.BuildArtifacts = (function() { ...@@ -41,7 +41,5 @@ window.BuildArtifacts = (function() {
.on('mouseleave', (e) => { .on('mouseleave', (e) => {
$(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide'); $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide');
}); });
}; }
}
return BuildArtifacts;
})();
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren */ /* eslint-disable func-names*/
$(function() { export default function handleRevealVariables() {
$('.reveal-variables').off('click').on('click', function() { $('.js-reveal-variables')
$('.js-build-variables').toggle(); .off('click')
$(this).hide(); .on('click', function () {
}); $('.js-build-variables').toggle();
}); $(this).hide();
});
}
export default class CILintEditor {
window.gl = window.gl || {};
class CILintEditor {
constructor() { constructor() {
this.editor = window.ace.edit('ci-editor'); this.editor = window.ace.edit('ci-editor');
this.textarea = document.querySelector('#content'); this.textarea = document.querySelector('#content');
...@@ -13,5 +10,3 @@ class CILintEditor { ...@@ -13,5 +10,3 @@ class CILintEditor {
}); });
} }
} }
gl.CILintEditor = CILintEditor;
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
/* global NotificationsDropdown */ /* global NotificationsDropdown */
/* global GroupAvatar */ /* global GroupAvatar */
/* global LineHighlighter */ /* global LineHighlighter */
/* global BuildArtifacts */ import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
/* global GroupsSelect */ /* global GroupsSelect */
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
...@@ -29,7 +30,8 @@ ...@@ -29,7 +30,8 @@
/* global ProjectNew */ /* global ProjectNew */
/* global ProjectShow */ /* global ProjectShow */
/* global ProjectImport */ /* global ProjectImport */
/* global Labels */ import Labels from './labels';
import LabelManager from './label_manager';
/* global Shortcuts */ /* global Shortcuts */
/* global ShortcutsFindFile */ /* global ShortcutsFindFile */
/* global Sidebar */ /* global Sidebar */
...@@ -82,6 +84,7 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -82,6 +84,7 @@ import initChangesDropdown from './init_changes_dropdown';
import AbuseReports from './abuse_reports'; import AbuseReports from './abuse_reports';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
import AjaxLoadingSpinner from './ajax_loading_spinner'; import AjaxLoadingSpinner from './ajax_loading_spinner';
import U2FAuthenticate from './u2f/authenticate';
// EE-only // EE-only
import ApproversSelect from './approvers_select'; import ApproversSelect from './approvers_select';
...@@ -100,8 +103,8 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -100,8 +103,8 @@ import initGroupAnalytics from './init_group_analytics';
} }
Dispatcher.prototype.initPageScripts = function() { Dispatcher.prototype.initPageScripts = function() {
var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl; var path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
page = $('body').attr('data-page'); const page = $('body').attr('data-page');
if (!page) { if (!page) {
return false; return false;
} }
...@@ -503,7 +506,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -503,7 +506,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'groups:labels:index': case 'groups:labels:index':
case 'projects:labels:index': case 'projects:labels:index':
if ($('.prioritized-labels').length) { if ($('.prioritized-labels').length) {
new gl.LabelManager(); new LabelManager();
} }
$('.label-subscription').each((i, el) => { $('.label-subscription').each((i, el) => {
const $el = $(el); const $el = $(el);
...@@ -564,7 +567,7 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -564,7 +567,7 @@ import initGroupAnalytics from './init_group_analytics';
break; break;
case 'ci:lints:create': case 'ci:lints:create':
case 'ci:lints:show': case 'ci:lints:show':
new gl.CILintEditor(); new CILintEditor();
break; break;
case 'users:show': case 'users:show':
new UserCallout(); new UserCallout();
...@@ -611,14 +614,16 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -611,14 +614,16 @@ import initGroupAnalytics from './init_group_analytics';
case 'sessions': case 'sessions':
case 'omniauth_callbacks': case 'omniauth_callbacks':
if (!gon.u2f) break; if (!gon.u2f) break;
gl.u2fAuthenticate = new gl.U2FAuthenticate( const u2fAuthenticate = new U2FAuthenticate(
$('#js-authenticate-u2f'), $('#js-authenticate-u2f'),
'#js-login-u2f-form', '#js-login-u2f-form',
gon.u2f, gon.u2f,
document.querySelector('#js-login-2fa-device'), document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'), document.querySelector('.js-2fa-form'),
); );
gl.u2fAuthenticate.start(); u2fAuthenticate.start();
// needed in rspec
gl.u2fAuthenticate = u2fAuthenticate;
case 'admin': case 'admin':
new Admin(); new Admin();
switch (path[1]) { switch (path[1]) {
......
...@@ -237,9 +237,12 @@ window.DropzoneInput = (function() { ...@@ -237,9 +237,12 @@ window.DropzoneInput = (function() {
}; };
const insertToTextArea = function(filename, url) { const insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) { const $child = $(child);
$child.val(function(index, val) {
return val.replace(`{{${filename}}}`, url); return val.replace(`{{${filename}}}`, url);
}); });
$child.trigger('change');
}; };
const appendToTextArea = function(url) { const appendToTextArea = function(url) {
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var */ import { highCountTrim } from '~/lib/utils/text_utility';
$(document).on('todo:toggle', function(e, count) { /**
var $todoPendingCount = $('.todos-count'); * Updates todo counter when todos are toggled.
$todoPendingCount.text(gl.text.highCountTrim(count)); * When count is 0, we hide the badge.
$todoPendingCount.toggleClass('hidden', count === 0); *
* @param {jQuery.Event} e
* @param {String} count
*/
$(document).on('todo:toggle', (e, count) => {
const parsedCount = parseInt(count, 10);
const $todoPendingCount = $('.todos-count');
$todoPendingCount.text(highCountTrim(parsedCount));
$todoPendingCount.toggleClass('hidden', parsedCount === 0);
}); });
/* eslint-disable func-names, wrap-iife, no-use-before-define,
consistent-return, prefer-rest-params */
import _ from 'underscore'; import _ from 'underscore';
import bp from './breakpoints'; import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils'; import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils';
window.Build = (function () { export default class Job {
Build.timeout = null; constructor(options) {
Build.state = null; this.timeout = null;
this.state = null;
function Build(options) {
this.options = options || $('.js-build-options').data(); this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl; this.pageUrl = this.options.pageUrl;
...@@ -19,9 +16,7 @@ window.Build = (function () { ...@@ -19,9 +16,7 @@ window.Build = (function () {
this.$document = $(document); this.$document = $(document);
this.logBytes = 0; this.logBytes = 0;
this.hasBeenScrolled = false; this.hasBeenScrolled = false;
this.updateDropdown = this.updateDropdown.bind(this); this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
this.$buildTrace = $('#build-trace'); this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh'); this.$buildRefreshAnimation = $('.js-build-refresh');
...@@ -33,7 +28,7 @@ window.Build = (function () { ...@@ -33,7 +28,7 @@ window.Build = (function () {
this.$scrollTopBtn = $('.js-scroll-up'); this.$scrollTopBtn = $('.js-scroll-up');
this.$scrollBottomBtn = $('.js-scroll-down'); this.$scrollBottomBtn = $('.js-scroll-down');
clearTimeout(Build.timeout); clearTimeout(this.timeout);
this.initSidebar(); this.initSidebar();
this.populateJobs(this.buildStage); this.populateJobs(this.buildStage);
...@@ -85,7 +80,7 @@ window.Build = (function () { ...@@ -85,7 +80,7 @@ window.Build = (function () {
this.getBuildTrace(); this.getBuildTrace();
} }
Build.prototype.initAffixTopArea = function () { initAffixTopArea() {
/** /**
If the browser does not support position sticky, it returns the position as static. 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 If the browser does support sticky, then we allow the browser to handle it, if not
...@@ -100,13 +95,14 @@ window.Build = (function () { ...@@ -100,13 +95,14 @@ window.Build = (function () {
top: offsetTop, top: offsetTop,
}, },
}); });
}; }
Build.prototype.canScroll = function () { // eslint-disable-next-line class-methods-use-this
canScroll() {
return $(document).height() > $(window).height(); return $(document).height() > $(window).height();
}; }
Build.prototype.toggleScroll = function () { toggleScroll() {
const currentPosition = $(document).scrollTop(); const currentPosition = $(document).scrollTop();
const scrollHeight = $(document).height(); const scrollHeight = $(document).height();
...@@ -119,7 +115,7 @@ window.Build = (function () { ...@@ -119,7 +115,7 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false); this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (currentPosition === 0) { } else if (currentPosition === 0) {
// User is at Top of Build Log // User is at Top of Log
this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false); this.toggleDisableButton(this.$scrollBottomBtn, false);
...@@ -133,38 +129,40 @@ window.Build = (function () { ...@@ -133,38 +129,40 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, true);
} }
}; }
Build.prototype.scrollDown = function () { // eslint-disable-next-line class-methods-use-this
scrollDown() {
$(document).scrollTop($(document).height()); $(document).scrollTop($(document).height());
}; }
Build.prototype.scrollToBottom = function () { scrollToBottom() {
this.scrollDown(); this.scrollDown();
this.hasBeenScrolled = true; this.hasBeenScrolled = true;
this.toggleScroll(); this.toggleScroll();
}; }
Build.prototype.scrollToTop = function () { scrollToTop() {
$(document).scrollTop(0); $(document).scrollTop(0);
this.hasBeenScrolled = true; this.hasBeenScrolled = true;
this.toggleScroll(); this.toggleScroll();
}; }
Build.prototype.toggleDisableButton = function ($button, disable) { // eslint-disable-next-line class-methods-use-this
toggleDisableButton($button, disable) {
if (disable && $button.prop('disabled')) return; if (disable && $button.prop('disabled')) return;
$button.prop('disabled', disable); $button.prop('disabled', disable);
}; }
Build.prototype.toggleScrollAnimation = function (toggle) { toggleScrollAnimation(toggle) {
this.$scrollBottomBtn.toggleClass('animate', toggle); this.$scrollBottomBtn.toggleClass('animate', toggle);
}; }
Build.prototype.initSidebar = function () { initSidebar() {
this.$sidebar = $('.js-build-sidebar'); this.$sidebar = $('.js-build-sidebar');
}; }
Build.prototype.getBuildTrace = function () { getBuildTrace() {
return $.ajax({ return $.ajax({
url: `${this.pageUrl}/trace.json`, url: `${this.pageUrl}/trace.json`,
data: { state: this.state }, data: { state: this.state },
...@@ -204,7 +202,7 @@ window.Build = (function () { ...@@ -204,7 +202,7 @@ window.Build = (function () {
this.toggleScrollAnimation(false); this.toggleScrollAnimation(false);
} }
Build.timeout = setTimeout(() => { this.timeout = setTimeout(() => {
this.getBuildTrace(); this.getBuildTrace();
}, 4000); }, 4000);
} else { } else {
...@@ -225,14 +223,14 @@ window.Build = (function () { ...@@ -225,14 +223,14 @@ window.Build = (function () {
} }
}) })
.then(() => this.toggleScroll()); .then(() => this.toggleScroll());
}; }
// eslint-disable-next-line class-methods-use-this
Build.prototype.shouldHideSidebarForViewport = function () { shouldHideSidebarForViewport() {
const bootstrapBreakpoint = bp.getBreakpointSize(); const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm'; return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}; }
Build.prototype.toggleSidebar = function (shouldHide) { toggleSidebar(shouldHide) {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined; const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header'); const $toggleButton = $('.js-sidebar-build-toggle-header');
...@@ -249,17 +247,17 @@ window.Build = (function () { ...@@ -249,17 +247,17 @@ window.Build = (function () {
} else { } else {
$toggleButton.removeClass('hidden'); $toggleButton.removeClass('hidden');
} }
}; }
Build.prototype.sidebarOnResize = function () { sidebarOnResize() {
this.toggleSidebar(this.shouldHideSidebarForViewport()); this.toggleSidebar(this.shouldHideSidebarForViewport());
}; }
Build.prototype.sidebarOnClick = function () { sidebarOnClick() {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
}; }
// eslint-disable-next-line class-methods-use-this, consistent-return
Build.prototype.updateArtifactRemoveDate = function () { updateArtifactRemoveDate() {
const $date = $('.js-artifacts-remove'); const $date = $('.js-artifacts-remove');
if ($date.length) { if ($date.length) {
const date = $date.text(); const date = $date.text();
...@@ -267,23 +265,21 @@ window.Build = (function () { ...@@ -267,23 +265,21 @@ window.Build = (function () {
gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '), gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
); );
} }
}; }
// eslint-disable-next-line class-methods-use-this
Build.prototype.populateJobs = function (stage) { populateJobs(stage) {
$('.build-job').hide(); $('.build-job').hide();
$(`.build-job[data-stage="${stage}"]`).show(); $(`.build-job[data-stage="${stage}"]`).show();
}; }
// eslint-disable-next-line class-methods-use-this
Build.prototype.updateStageDropdownText = function (stage) { updateStageDropdownText(stage) {
$('.stage-selection').text(stage); $('.stage-selection').text(stage);
}; }
Build.prototype.updateDropdown = function (e) { updateDropdown(e) {
e.preventDefault(); e.preventDefault();
const stage = e.currentTarget.text; const stage = e.currentTarget.text;
this.updateStageDropdownText(stage); this.updateStageDropdownText(stage);
this.populateJobs(stage); this.populateJobs(stage);
}; }
}
return Build;
})();
...@@ -5,7 +5,8 @@ import Flash from '../flash'; ...@@ -5,7 +5,8 @@ import Flash from '../flash';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
import JobStore from './stores/job_store'; import JobStore from './stores/job_store';
import JobService from './services/job_service'; import JobService from './services/job_service';
import '../build'; import Job from '../job';
import handleRevealVariables from '../build_variables';
export default class JobMediator { export default class JobMediator {
constructor(options = {}) { constructor(options = {}) {
...@@ -20,7 +21,8 @@ export default class JobMediator { ...@@ -20,7 +21,8 @@ export default class JobMediator {
} }
initBuildClass() { initBuildClass() {
this.build = new Build(); this.build = new Job();
handleRevealVariables();
} }
fetchJob() { fetchJob() {
......
...@@ -3,123 +3,119 @@ ...@@ -3,123 +3,119 @@
import Flash from './flash'; import Flash from './flash';
((global) => { export default class LabelManager {
class LabelManager { constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority');
this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); this.otherLabels = otherLabels || $('.js-other-labels');
this.otherLabels = otherLabels || $('.js-other-labels'); this.errorMessage = 'Unable to update label prioritization at this time';
this.errorMessage = 'Unable to update label prioritization at this time'; this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.emptyState = document.querySelector('#js-priority-labels-empty-state'); this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
this.sortable = Sortable.create(this.prioritizedLabels.get(0), { filter: '.empty-message',
filter: '.empty-message', forceFallback: true,
forceFallback: true, fallbackClass: 'is-dragging',
fallbackClass: 'is-dragging', dataIdAttr: 'data-id',
dataIdAttr: 'data-id', onUpdate: this.onPrioritySortUpdate.bind(this),
onUpdate: this.onPrioritySortUpdate.bind(this), });
}); this.bindEvents();
this.bindEvents(); }
}
bindEvents() { bindEvents() {
this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick); this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick);
return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
} }
onTogglePriorityClick(e) { onTogglePriorityClick(e) {
e.preventDefault(); e.preventDefault();
const _this = e.data; const _this = e.data;
const $btn = $(e.currentTarget); const $btn = $(e.currentTarget);
const $label = $(`#${$btn.data('domId')}`); const $label = $(`#${$btn.data('domId')}`);
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
$tooltip.tooltip('destroy'); $tooltip.tooltip('destroy');
_this.toggleLabelPriority($label, action); _this.toggleLabelPriority($label, action);
_this.toggleEmptyState($label, $btn, action); _this.toggleEmptyState($label, $btn, action);
} }
onButtonActionClick(e) { onButtonActionClick(e) {
e.stopPropagation(); e.stopPropagation();
$(e.currentTarget).tooltip('hide'); $(e.currentTarget).tooltip('hide');
} }
toggleEmptyState($label, $btn, action) { toggleEmptyState($label, $btn, action) {
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
} }
toggleLabelPriority($label, action, persistState) { toggleLabelPriority($label, action, persistState) {
if (persistState == null) { if (persistState == null) {
persistState = true; persistState = true;
}
let xhr;
const _this = this;
const url = $label.find('.js-toggle-priority').data('url');
let $target = this.prioritizedLabels;
let $from = this.otherLabels;
if (action === 'remove') {
$target = this.otherLabels;
$from = this.prioritizedLabels;
}
$label.detach().appendTo($target);
if ($from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
if ($target.find('> li:not(.empty-message)').length) {
$target.find('.empty-message').addClass('hidden');
}
// Return if we are not persisting state
if (!persistState) {
return;
}
if (action === 'remove') {
xhr = $.ajax({
url,
type: 'DELETE'
});
// Restore empty message
if (!$from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
} else {
xhr = this.savePrioritySort($label, action);
}
return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
} }
let xhr;
onPrioritySortUpdate() { const _this = this;
const xhr = this.savePrioritySort(); const url = $label.find('.js-toggle-priority').data('url');
return xhr.fail(function() { let $target = this.prioritizedLabels;
return new Flash(this.errorMessage, 'alert'); let $from = this.otherLabels;
}); if (action === 'remove') {
$target = this.otherLabels;
$from = this.prioritizedLabels;
} }
$label.detach().appendTo($target);
savePrioritySort() { if ($from.find('li').length) {
return $.post({ $from.find('.empty-message').removeClass('hidden');
url: this.prioritizedLabels.data('url'), }
data: { if ($target.find('> li:not(.empty-message)').length) {
label_ids: this.getSortedLabelsIds() $target.find('.empty-message').addClass('hidden');
} }
// Return if we are not persisting state
if (!persistState) {
return;
}
if (action === 'remove') {
xhr = $.ajax({
url,
type: 'DELETE'
}); });
// Restore empty message
if (!$from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
} else {
xhr = this.savePrioritySort($label, action);
} }
return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
}
rollbackLabelPosition($label, originalAction) { onPrioritySortUpdate() {
const action = originalAction === 'remove' ? 'add' : 'remove'; const xhr = this.savePrioritySort();
this.toggleLabelPriority($label, action, false); return xhr.fail(function() {
return new Flash(this.errorMessage, 'alert'); return new Flash(this.errorMessage, 'alert');
} });
}
getSortedLabelsIds() { savePrioritySort() {
const sortedIds = []; return $.post({
this.prioritizedLabels.find('> li').each(function() { url: this.prioritizedLabels.data('url'),
const id = $(this).data('id'); data: {
label_ids: this.getSortedLabelsIds()
}
});
}
if (id) { rollbackLabelPosition($label, originalAction) {
sortedIds.push(id); const action = originalAction === 'remove' ? 'add' : 'remove';
} this.toggleLabelPriority($label, action, false);
}); return new Flash(this.errorMessage, 'alert');
return sortedIds;
}
} }
gl.LabelManager = LabelManager; getSortedLabelsIds() {
})(window.gl || (window.gl = {})); const sortedIds = [];
this.prioritizedLabels.find('> li').each(function() {
const id = $(this).data('id');
if (id) {
sortedIds.push(id);
}
});
return sortedIds;
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, max-len */ export default class Labels {
(function() { constructor() {
this.Labels = (function() { this.setSuggestedColor = this.setSuggestedColor.bind(this);
function Labels() { this.updateColorPreview = this.updateColorPreview.bind(this);
this.setSuggestedColor = this.setSuggestedColor.bind(this); this.cleanBinding();
this.updateColorPreview = this.updateColorPreview.bind(this); this.addBinding();
var form; this.updateColorPreview();
form = $('.label-form'); }
this.cleanBinding();
this.addBinding();
this.updateColorPreview();
}
Labels.prototype.addBinding = function() { addBinding() {
$(document).on('click', '.suggest-colors a', this.setSuggestedColor); $(document).on('click', '.suggest-colors a', this.setSuggestedColor);
return $(document).on('input', 'input#label_color', this.updateColorPreview); return $(document).on('input', 'input#label_color', this.updateColorPreview);
}; }
// eslint-disable-next-line class-methods-use-this
cleanBinding() {
$(document).off('click', '.suggest-colors a');
return $(document).off('input', 'input#label_color');
}
// eslint-disable-next-line class-methods-use-this
updateColorPreview() {
const previewColor = $('input#label_color').val();
return $('div.label-color-preview').css('background-color', previewColor);
// Updates the the preview color with the hex-color input
}
Labels.prototype.cleanBinding = function() { // Updates the preview color with a click on a suggested color
$(document).off('click', '.suggest-colors a'); setSuggestedColor(e) {
return $(document).off('input', 'input#label_color'); const color = $(e.currentTarget).data('color');
}; $('input#label_color').val(color);
this.updateColorPreview();
Labels.prototype.updateColorPreview = function() { // Notify the form, that color has changed
var previewColor; $('.label-form').trigger('keyup');
previewColor = $('input#label_color').val(); return e.preventDefault();
return $('div.label-color-preview').css('background-color', previewColor); }
// Updates the the preview color with the hex-color input }
};
// Updates the preview color with a click on a suggested color
Labels.prototype.setSuggestedColor = function(e) {
var color;
color = $(e.currentTarget).data('color');
$('input#label_color').val(color);
this.updateColorPreview();
// Notify the form, that color has changed
$('.label-form').trigger('keyup');
return e.preventDefault();
};
return Labels;
})();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */ /* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
import 'vendor/latinise'; import 'vendor/latinise';
...@@ -13,9 +13,17 @@ if ((base = w.gl).text == null) { ...@@ -13,9 +13,17 @@ if ((base = w.gl).text == null) {
gl.text.addDelimiter = function(text) { gl.text.addDelimiter = function(text) {
return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text; return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text;
}; };
gl.text.highCountTrim = function(count) {
/**
* Returns '99+' for numbers bigger than 99.
*
* @param {Number} count
* @return {Number|String}
*/
export function highCountTrim(count) {
return count > 99 ? '99+' : count; return count > 99 ? '99+' : count;
}; }
gl.text.randomString = function() { gl.text.randomString = function() {
return Math.random().toString(36).substring(7); return Math.random().toString(36).substring(7);
}; };
......
...@@ -46,26 +46,14 @@ import './lib/utils/url_utility'; ...@@ -46,26 +46,14 @@ import './lib/utils/url_utility';
// behaviors // behaviors
import './behaviors/'; import './behaviors/';
// u2f
import './u2f/authenticate';
import './u2f/error';
import './u2f/register';
import './u2f/util';
// everything else // everything else
import './activities'; import './activities';
import './admin'; import './admin';
import './api';
import './ajax_loading_spinner';
import './aside'; import './aside';
import './autosave'; import './autosave';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import bp from './breakpoints'; import bp from './breakpoints';
import './broadcast_message'; import './broadcast_message';
import './build';
import './build_artifacts';
import './build_variables';
import './ci_lint_editor';
import './commits'; import './commits';
import './compare'; import './compare';
import './compare_autocomplete'; import './compare_autocomplete';
...@@ -91,8 +79,6 @@ import './issuable_context'; ...@@ -91,8 +79,6 @@ import './issuable_context';
import './issuable_form'; import './issuable_form';
import './issue'; import './issue';
import './issue_status_select'; import './issue_status_select';
import './label_manager';
import './labels';
import './labels_select'; import './labels_select';
import './layout_nav'; import './layout_nav';
import LazyLoader from './lazy_loader'; import LazyLoader from './lazy_loader';
...@@ -130,7 +116,6 @@ import './right_sidebar'; ...@@ -130,7 +116,6 @@ import './right_sidebar';
import './search'; import './search';
import './search_autocomplete'; import './search_autocomplete';
import './smart_interval'; import './smart_interval';
import './star';
import './subscription'; import './subscription';
import './subscription_select'; import './subscription_select';
import initBreadcrumbs from './breadcrumb'; import initBreadcrumbs from './breadcrumb';
......
/* 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 */
import Flash from './flash'; import Flash from './flash';
import { __, s__ } from './locale'; import { __, s__ } from './locale';
export default class Star { export default class Star {
constructor() { constructor() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) { $('.project-home-panel .toggle-star')
var $starIcon, $starSpan, $this, toggleStar; .on('ajax:success', function handleSuccess(e, data) {
$this = $(this); const $this = $(this);
$starSpan = $this.find('span'); const $starSpan = $this.find('span');
$starIcon = $this.find('i'); const $starIcon = $this.find('i');
toggleStar = function(isStarred) {
$this.parent().find('.star-count').text(data.star_count); function toggleStar(isStarred) {
if (isStarred) { $this.parent().find('.star-count').text(data.star_count);
$starSpan.removeClass('starred').text(s__('StarProject|Star')); if (isStarred) {
$starIcon.removeClass('fa-star').addClass('fa-star-o'); $starSpan.removeClass('starred').text(s__('StarProject|Star'));
} else { $starIcon.removeClass('fa-star').addClass('fa-star-o');
$starSpan.addClass('starred').text(__('Unstar')); } else {
$starIcon.removeClass('fa-star-o').addClass('fa-star'); $starSpan.addClass('starred').text(__('Unstar'));
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
} }
};
toggleStar($starSpan.hasClass('starred')); toggleStar($starSpan.hasClass('starred'));
}).on('ajax:error', function(e, xhr, status, error) { })
new Flash('Star toggle failed. Try again later.', 'alert'); .on('ajax:error', () => {
}); Flash('Star toggle failed. Try again later.', 'alert');
});
} }
} }
/* global U2FRegister */ import U2FRegister from './u2f/register';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const twoFactorNode = document.querySelector('.js-two-factor-auth'); const twoFactorNode = document.querySelector('.js-two-factor-auth');
const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true'; const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true';
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, prefer-arrow-callback, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */ /* eslint-disable func-names, wrap-iife */
/* global u2f */ /* global u2f */
/* global U2FError */
/* global U2FUtil */
import _ from 'underscore'; import _ from 'underscore';
import isU2FSupported from './util';
import U2FError from './error';
// Authenticate U2F (universal 2nd factor) devices for users to authenticate with. // Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
// //
// State Flow #1: setup -> in_progress -> authenticated -> POST to server // State Flow #1: setup -> in_progress -> authenticated -> POST to server
// State Flow #2: setup -> in_progress -> error -> setup // State Flow #2: setup -> in_progress -> error -> setup
(function() { export default class U2FAuthenticate {
const global = window.gl || (window.gl = {}); constructor(container, form, u2fParams, fallbackButton, fallbackUI) {
this.container = container;
global.U2FAuthenticate = (function() { this.renderNotSupported = this.renderNotSupported.bind(this);
function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) { this.renderAuthenticated = this.renderAuthenticated.bind(this);
this.container = container; this.renderError = this.renderError.bind(this);
this.renderNotSupported = this.renderNotSupported.bind(this); this.renderInProgress = this.renderInProgress.bind(this);
this.renderAuthenticated = this.renderAuthenticated.bind(this); this.renderTemplate = this.renderTemplate.bind(this);
this.renderError = this.renderError.bind(this); this.authenticate = this.authenticate.bind(this);
this.renderInProgress = this.renderInProgress.bind(this); this.start = this.start.bind(this);
this.renderTemplate = this.renderTemplate.bind(this); this.appId = u2fParams.app_id;
this.authenticate = this.authenticate.bind(this); this.challenge = u2fParams.challenge;
this.start = this.start.bind(this); this.form = form;
this.appId = u2fParams.app_id; this.fallbackButton = fallbackButton;
this.challenge = u2fParams.challenge; this.fallbackUI = fallbackUI;
this.form = form; if (this.fallbackButton) {
this.fallbackButton = fallbackButton; this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this));
this.fallbackUI = fallbackUI;
if (this.fallbackButton) this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this));
this.signRequests = u2fParams.sign_requests.map(function(request) {
// The U2F Javascript API v1.1 requires a single challenge, with
// _no challenges per-request_. The U2F Javascript API v1.0 requires a
// challenge per-request, which is done by copying the single challenge
// into every request.
//
// In either case, we don't need the per-request challenges that the server
// has generated, so we can remove them.
//
// Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
// This can be removed once we upgrade.
// https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
return _(request).omit('challenge');
});
} }
U2FAuthenticate.prototype.start = function() { // The U2F Javascript API v1.1 requires a single challenge, with
if (U2FUtil.isU2FSupported()) { // _no challenges per-request_. The U2F Javascript API v1.0 requires a
return this.renderInProgress(); // challenge per-request, which is done by copying the single challenge
} else { // into every request.
return this.renderNotSupported(); //
} // In either case, we don't need the per-request challenges that the server
}; // has generated, so we can remove them.
//
// Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
// This can be removed once we upgrade.
// https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge'));
U2FAuthenticate.prototype.authenticate = function() { this.templates = {
return u2f.sign(this.appId, this.challenge, this.signRequests, (function(_this) { notSupported: '#js-authenticate-u2f-not-supported',
return function(response) { setup: '#js-authenticate-u2f-setup',
var error; inProgress: '#js-authenticate-u2f-in-progress',
if (response.errorCode) { error: '#js-authenticate-u2f-error',
error = new U2FError(response.errorCode, 'authenticate'); authenticated: '#js-authenticate-u2f-authenticated',
return _this.renderError(error);
} else {
return _this.renderAuthenticated(JSON.stringify(response));
}
};
})(this), 10);
}; };
}
// Rendering # start() {
U2FAuthenticate.prototype.templates = { if (isU2FSupported()) {
"notSupported": "#js-authenticate-u2f-not-supported", return this.renderInProgress();
"setup": '#js-authenticate-u2f-setup', }
"inProgress": '#js-authenticate-u2f-in-progress', return this.renderNotSupported();
"error": '#js-authenticate-u2f-error', }
"authenticated": '#js-authenticate-u2f-authenticated'
};
U2FAuthenticate.prototype.renderTemplate = function(name, params) { authenticate() {
var template, templateString; return u2f.sign(this.appId, this.challenge, this.signRequests, (function (_this) {
templateString = $(this.templates[name]).html(); return function (response) {
template = _.template(templateString); if (response.errorCode) {
return this.container.html(template(params)); const error = new U2FError(response.errorCode, 'authenticate');
}; return _this.renderError(error);
}
return _this.renderAuthenticated(JSON.stringify(response));
};
})(this), 10);
}
U2FAuthenticate.prototype.renderInProgress = function() { renderTemplate(name, params) {
this.renderTemplate('inProgress'); const templateString = $(this.templates[name]).html();
return this.authenticate(); const template = _.template(templateString);
}; return this.container.html(template(params));
}
U2FAuthenticate.prototype.renderError = function(error) { renderInProgress() {
this.renderTemplate('error', { this.renderTemplate('inProgress');
error_message: error.message(), return this.authenticate();
error_code: error.errorCode }
});
return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress);
};
U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) { renderError(error) {
this.renderTemplate('authenticated'); this.renderTemplate('error', {
const container = this.container[0]; error_message: error.message(),
container.querySelector('#js-device-response').value = deviceResponse; error_code: error.errorCode,
container.querySelector(this.form).submit(); });
this.fallbackButton.classList.add('hidden'); return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress);
}; }
U2FAuthenticate.prototype.renderNotSupported = function() { renderAuthenticated(deviceResponse) {
return this.renderTemplate('notSupported'); this.renderTemplate('authenticated');
}; const container = this.container[0];
container.querySelector('#js-device-response').value = deviceResponse;
container.querySelector(this.form).submit();
this.fallbackButton.classList.add('hidden');
}
U2FAuthenticate.prototype.switchToFallbackUI = function() { renderNotSupported() {
this.fallbackButton.classList.add('hidden'); return this.renderTemplate('notSupported');
this.container[0].classList.add('hidden'); }
this.fallbackUI.classList.remove('hidden');
}; switchToFallbackUI() {
this.fallbackButton.classList.add('hidden');
this.container[0].classList.add('hidden');
this.fallbackUI.classList.remove('hidden');
}
return U2FAuthenticate; }
})();
})();
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-console, quotes, prefer-template, max-len */ export default class U2FError {
/* global u2f */ constructor(errorCode, u2fFlowType) {
this.errorCode = errorCode;
this.message = this.message.bind(this);
this.httpsDisabled = window.location.protocol !== 'https:';
this.u2fFlowType = u2fFlowType;
}
(function() { message() {
this.U2FError = (function() { if (this.errorCode === window.u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) {
function U2FError(errorCode, u2fFlowType) { return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.';
this.errorCode = errorCode; } else if (this.errorCode === window.u2f.ErrorCodes.DEVICE_INELIGIBLE) {
this.message = this.message.bind(this); if (this.u2fFlowType === 'authenticate') {
this.httpsDisabled = window.location.protocol !== 'https:'; return 'This device has not been registered with us.';
this.u2fFlowType = u2fFlowType;
}
U2FError.prototype.message = function() {
if (this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) {
return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.';
} else if (this.errorCode === u2f.ErrorCodes.DEVICE_INELIGIBLE) {
if (this.u2fFlowType === 'authenticate') return 'This device has not been registered with us.';
if (this.u2fFlowType === 'register') return 'This device has already been registered with us.';
} }
return "There was a problem communicating with your device."; if (this.u2fFlowType === 'register') {
}; return 'This device has already been registered with us.';
}
return U2FError; }
})(); return 'There was a problem communicating with your device.';
}).call(window); }
}
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */ /* eslint-disable func-names, wrap-iife */
/* global u2f */ /* global u2f */
/* global U2FError */
/* global U2FUtil */
import _ from 'underscore'; import _ from 'underscore';
import isU2FSupported from './util';
import U2FError from './error';
// Register U2F (universal 2nd factor) devices for users to authenticate with. // Register U2F (universal 2nd factor) devices for users to authenticate with.
// //
// State Flow #1: setup -> in_progress -> registered -> POST to server // State Flow #1: setup -> in_progress -> registered -> POST to server
// State Flow #2: setup -> in_progress -> error -> setup // State Flow #2: setup -> in_progress -> error -> setup
(function() { export default class U2FRegister {
this.U2FRegister = (function() { constructor(container, u2fParams) {
function U2FRegister(container, u2fParams) { this.container = container;
this.container = container; this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderNotSupported = this.renderNotSupported.bind(this); this.renderRegistered = this.renderRegistered.bind(this);
this.renderRegistered = this.renderRegistered.bind(this); this.renderError = this.renderError.bind(this);
this.renderError = this.renderError.bind(this); this.renderInProgress = this.renderInProgress.bind(this);
this.renderInProgress = this.renderInProgress.bind(this); this.renderSetup = this.renderSetup.bind(this);
this.renderSetup = this.renderSetup.bind(this); this.renderTemplate = this.renderTemplate.bind(this);
this.renderTemplate = this.renderTemplate.bind(this); this.register = this.register.bind(this);
this.register = this.register.bind(this); this.start = this.start.bind(this);
this.start = this.start.bind(this); this.appId = u2fParams.app_id;
this.appId = u2fParams.app_id; this.registerRequests = u2fParams.register_requests;
this.registerRequests = u2fParams.register_requests; this.signRequests = u2fParams.sign_requests;
this.signRequests = u2fParams.sign_requests;
}
U2FRegister.prototype.start = function() { this.templates = {
if (U2FUtil.isU2FSupported()) { notSupported: '#js-register-u2f-not-supported',
return this.renderSetup(); setup: '#js-register-u2f-setup',
} else { inProgress: '#js-register-u2f-in-progress',
return this.renderNotSupported(); error: '#js-register-u2f-error',
} registered: '#js-register-u2f-registered',
}; };
}
U2FRegister.prototype.register = function() { start() {
return u2f.register(this.appId, this.registerRequests, this.signRequests, (function(_this) { if (isU2FSupported()) {
return function(response) { return this.renderSetup();
var error; }
if (response.errorCode) { return this.renderNotSupported();
error = new U2FError(response.errorCode, 'register'); }
return _this.renderError(error);
} else {
return _this.renderRegistered(JSON.stringify(response));
}
};
})(this), 10);
};
// Rendering # register() {
U2FRegister.prototype.templates = { return u2f.register(this.appId, this.registerRequests, this.signRequests, (function (_this) {
"notSupported": "#js-register-u2f-not-supported", return function (response) {
"setup": '#js-register-u2f-setup', if (response.errorCode) {
"inProgress": '#js-register-u2f-in-progress', const error = new U2FError(response.errorCode, 'register');
"error": '#js-register-u2f-error', return _this.renderError(error);
"registered": '#js-register-u2f-registered' }
}; return _this.renderRegistered(JSON.stringify(response));
};
})(this), 10);
}
U2FRegister.prototype.renderTemplate = function(name, params) { renderTemplate(name, params) {
var template, templateString; const templateString = $(this.templates[name]).html();
templateString = $(this.templates[name]).html(); const template = _.template(templateString);
template = _.template(templateString); return this.container.html(template(params));
return this.container.html(template(params)); }
};
U2FRegister.prototype.renderSetup = function() { renderSetup() {
this.renderTemplate('setup'); this.renderTemplate('setup');
return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress); return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress);
}; }
U2FRegister.prototype.renderInProgress = function() { renderInProgress() {
this.renderTemplate('inProgress'); this.renderTemplate('inProgress');
return this.register(); return this.register();
}; }
U2FRegister.prototype.renderError = function(error) { renderError(error) {
this.renderTemplate('error', { this.renderTemplate('error', {
error_message: error.message(), error_message: error.message(),
error_code: error.errorCode error_code: error.errorCode,
}); });
return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
}; }
U2FRegister.prototype.renderRegistered = function(deviceResponse) { renderRegistered(deviceResponse) {
this.renderTemplate('registered'); this.renderTemplate('registered');
// Prefer to do this instead of interpolating using Underscore templates // Prefer to do this instead of interpolating using Underscore templates
// because of JSON escaping issues. // because of JSON escaping issues.
return this.container.find("#js-device-response").val(deviceResponse); return this.container.find('#js-device-response').val(deviceResponse);
}; }
U2FRegister.prototype.renderNotSupported = function() {
return this.renderTemplate('notSupported');
};
return U2FRegister; renderNotSupported() {
})(); return this.renderTemplate('notSupported');
}).call(window); }
}
/* eslint-disable func-names, space-before-function-paren, wrap-iife */ export default function isU2FSupported() {
(function() { return window.u2f;
this.U2FUtil = (function() { }
function U2FUtil() {}
U2FUtil.isU2FSupported = function() {
return window.u2f;
};
return U2FUtil;
})();
}).call(window);
...@@ -749,7 +749,7 @@ ...@@ -749,7 +749,7 @@
margin-bottom: $dropdown-vertical-offset; margin-bottom: $dropdown-vertical-offset;
} }
li:not(.dropdown-bold-header) { li {
display: block; display: block;
padding: 0 1px; padding: 0 1px;
......
...@@ -161,9 +161,10 @@ ...@@ -161,9 +161,10 @@
} }
} }
.dropdown-bold-header { li.dropdown-bold-header {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
font-size: 12px; font-size: 12px;
padding: 0 16px;
} }
.navbar-collapse { .navbar-collapse {
......
...@@ -54,12 +54,15 @@ ...@@ -54,12 +54,15 @@
.mr-widget-pipeline-graph { .mr-widget-pipeline-graph {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
margin-right: 4px;
.stage-cell .stage-container { .stage-cell .stage-container {
margin: 3px 3px 3px 0; margin: 3px 3px 3px 0;
} }
.stage-container:last-child {
margin-right: 0;
}
.dropdown-menu { .dropdown-menu {
margin-top: 11px; margin-top: 11px;
} }
......
...@@ -236,9 +236,11 @@ ...@@ -236,9 +236,11 @@
} }
.stage-cell { .stage-cell {
@media (min-width: $screen-md-min) { &.table-section {
min-width: 148px; @media (min-width: $screen-md-min) {
margin-right: -4px; min-width: 148px;
margin-right: -4px;
}
} }
.mini-pipeline-graph-dropdown-toggle svg { .mini-pipeline-graph-dropdown-toggle svg {
......
...@@ -653,6 +653,10 @@ a.deploy-project-label { ...@@ -653,6 +653,10 @@ a.deploy-project-label {
} }
.project-import { .project-import {
.form-group {
margin-bottom: 0;
}
.import-btn-container { .import-btn-container {
margin-bottom: 0; margin-bottom: 0;
} }
......
...@@ -2,7 +2,8 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -2,7 +2,8 @@ class Admin::RunnersController < Admin::ApplicationController
before_action :runner, except: :index before_action :runner, except: :index
def index def index
@runners = Ci::Runner.order('id DESC') sort = params[:sort] == 'contacted_asc' ? { contacted_at: :asc } : { id: :desc }
@runners = Ci::Runner.order(sort)
@runners = @runners.search(params[:search]) if params[:search].present? @runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30) @runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.online.count @active_runners_cnt = Ci::Runner.online.count
......
module PreviewMarkdown
extend ActiveSupport::Concern
def preview_markdown
result = PreviewMarkdownService.new(@project, current_user, params).execute
markdown_params =
case controller_name
when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] }
when 'snippets' then { skip_project_check: true }
else {}
end
render json: {
body: view_context.markdown(result[:text], markdown_params),
references: {
users: result[:users],
commands: view_context.markdown(result[:commands])
}
}
end
end
class Dashboard::GroupsController < Dashboard::ApplicationController class Dashboard::GroupsController < Dashboard::ApplicationController
def index def index
@sort = params[:sort] || 'id_desc' @sort = params[:sort] || 'created_desc'
@groups = @groups =
if params[:parent_id] && Group.supports_nested_groups? if params[:parent_id] && Group.supports_nested_groups?
......
...@@ -2,6 +2,7 @@ class GroupsController < Groups::ApplicationController ...@@ -2,6 +2,7 @@ class GroupsController < Groups::ApplicationController
include IssuesAction include IssuesAction
include MergeRequestsAction include MergeRequestsAction
include ParamsBackwardCompatibility include ParamsBackwardCompatibility
include PreviewMarkdown
respond_to :html respond_to :html
......
class Projects::WikisController < Projects::ApplicationController class Projects::WikisController < Projects::ApplicationController
include PreviewMarkdown
before_action :authorize_read_wiki! before_action :authorize_read_wiki!
before_action :authorize_create_wiki!, only: [:edit, :create, :history] before_action :authorize_create_wiki!, only: [:edit, :create, :history]
before_action :authorize_admin_wiki!, only: :destroy before_action :authorize_admin_wiki!, only: :destroy
...@@ -93,17 +95,6 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -93,17 +95,6 @@ class Projects::WikisController < Projects::ApplicationController
def git_access def git_access
end end
def preview_markdown
result = PreviewMarkdownService.new(@project, current_user, params).execute
render json: {
body: view_context.markdown(result[:text], pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id]),
references: {
users: result[:users]
}
}
end
private private
def load_project_wiki def load_project_wiki
......
class ProjectsController < Projects::ApplicationController class ProjectsController < Projects::ApplicationController
include IssuableCollections include IssuableCollections
include ExtractsPath include ExtractsPath
include PreviewMarkdown
prepend EE::ProjectsController prepend EE::ProjectsController
before_action :authenticate_user!, except: [:index, :show, :activity, :refs] before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
...@@ -261,18 +262,6 @@ class ProjectsController < Projects::ApplicationController ...@@ -261,18 +262,6 @@ class ProjectsController < Projects::ApplicationController
render json: options.to_json render json: options.to_json
end end
def preview_markdown
result = PreviewMarkdownService.new(@project, current_user, params).execute
render json: {
body: view_context.markdown(result[:text]),
references: {
users: result[:users],
commands: view_context.markdown(result[:commands])
}
}
end
private private
# Render project landing depending of which features are available # Render project landing depending of which features are available
......
...@@ -4,6 +4,7 @@ class SnippetsController < ApplicationController ...@@ -4,6 +4,7 @@ class SnippetsController < ApplicationController
include SpammableActions include SpammableActions
include SnippetsActions include SnippetsActions
include RendersBlob include RendersBlob
include PreviewMarkdown
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
...@@ -87,17 +88,6 @@ class SnippetsController < ApplicationController ...@@ -87,17 +88,6 @@ class SnippetsController < ApplicationController
redirect_to snippets_path, status: 302 redirect_to snippets_path, status: 302
end end
def preview_markdown
result = PreviewMarkdownService.new(@project, current_user, params).execute
render json: {
body: view_context.markdown(result[:text], skip_project_check: true),
references: {
users: result[:users]
}
}
end
protected protected
def snippet def snippet
......
...@@ -4,8 +4,8 @@ module CompareHelper ...@@ -4,8 +4,8 @@ module CompareHelper
to.present? && to.present? &&
from != to && from != to &&
can?(current_user, :create_merge_request, project) && can?(current_user, :create_merge_request, project) &&
project.repository.branch_names.include?(from) && project.repository.branch_exists?(from) &&
project.repository.branch_names.include?(to) project.repository.branch_exists?(to)
end end
def create_mr_path(from = params[:from], to = params[:to], project = @project) def create_mr_path(from = params[:from], to = params[:to], project = @project)
......
...@@ -7,7 +7,12 @@ module GroupsHelper ...@@ -7,7 +7,12 @@ module GroupsHelper
can?(current_user, :change_share_with_group_lock, group) can?(current_user, :change_share_with_group_lock, group)
end end
def group_icon(group) def group_icon(group, options = {})
img_path = group_icon_url(group, options)
image_tag img_path, options
end
def group_icon_url(group, options = {})
if group.is_a?(String) if group.is_a?(String)
group = Group.find_by_full_path(group) group = Group.find_by_full_path(group)
end end
...@@ -95,7 +100,7 @@ module GroupsHelper ...@@ -95,7 +100,7 @@ module GroupsHelper
link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do
output = output =
if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) group_icon(group, class: "avatar-tile", width: 15, height: 15)
else else
"" ""
end end
......
...@@ -10,6 +10,7 @@ module LazyImageTagHelper ...@@ -10,6 +10,7 @@ module LazyImageTagHelper
unless options.delete(:lazy) == false unless options.delete(:lazy) == false
options[:data] ||= {} options[:data] ||= {}
options[:data][:src] = path_to_image(source) options[:data][:src] = path_to_image(source)
options[:class] ||= "" options[:class] ||= ""
options[:class] << " lazy" options[:class] << " lazy"
......
...@@ -11,7 +11,7 @@ module Avatarable ...@@ -11,7 +11,7 @@ module Avatarable
# If asset_host is set then it is expected that assets are handled by a standalone host. # If asset_host is set then it is expected that assets are handled by a standalone host.
# That means we do not want to get GitLab's relative_url_root option anymore. # That means we do not want to get GitLab's relative_url_root option anymore.
host = asset_host.present? ? asset_host : gitlab_host host = (asset_host.present? && (!respond_to?(:public?) || public?)) ? asset_host : gitlab_host
[host, avatar.url].join [host, avatar.url].join
end end
......
...@@ -266,23 +266,22 @@ module Issuable ...@@ -266,23 +266,22 @@ module Issuable
participants(user).include?(user) participants(user).include?(user)
end end
def to_hook_data(user) def to_hook_data(user, old_labels: [], old_assignees: [])
hook_data = { changes = previous_changes
object_kind: self.class.name.underscore,
user: user.hook_attrs, if old_labels != labels
project: project.hook_attrs, changes[:labels] = [old_labels.map(&:hook_attrs), labels.map(&:hook_attrs)]
object_attributes: hook_attrs, end
labels: labels.map(&:hook_attrs),
# DEPRECATED if old_assignees != assignees
repository: project.hook_attrs.slice(:name, :url, :description, :homepage) if self.is_a?(Issue)
} changes[:assignees] = [old_assignees.map(&:hook_attrs), assignees.map(&:hook_attrs)]
if self.is_a?(Issue) else
hook_data[:assignees] = assignees.map(&:hook_attrs) if assignees.any? changes[:assignee] = [old_assignees&.first&.hook_attrs, assignee&.hook_attrs]
else end
hook_data[:assignee] = assignee.hook_attrs if assignee
end end
hook_data Gitlab::HookData::IssuableBuilder.new(self).build(user: user, changes: changes)
end end
def labels_array def labels_array
......
...@@ -86,20 +86,6 @@ class Issue < ActiveRecord::Base ...@@ -86,20 +86,6 @@ class Issue < ActiveRecord::Base
end end
end end
def hook_attrs
assignee_ids = self.assignee_ids
attrs = {
total_time_spent: total_time_spent,
human_total_time_spent: human_total_time_spent,
human_time_estimate: human_time_estimate,
assignee_ids: assignee_ids,
assignee_id: assignee_ids.first # This key is deprecated
}
attributes.merge!(attrs)
end
def self.reference_prefix def self.reference_prefix
'#' '#'
end end
...@@ -146,6 +132,10 @@ class Issue < ActiveRecord::Base ...@@ -146,6 +132,10 @@ class Issue < ActiveRecord::Base
"id DESC") "id DESC")
end end
def hook_attrs
Gitlab::HookData::IssueBuilder.new(self).build
end
# Returns a Hash of attributes to be used for Twitter card metadata # Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes def card_attributes
{ {
......
...@@ -183,6 +183,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -183,6 +183,10 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}" work_in_progress?(title) ? title : "WIP: #{title}"
end end
def hook_attrs
Gitlab::HookData::MergeRequestBuilder.new(self).build
end
# Returns a Hash of attributes to be used for Twitter card metadata # Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes def card_attributes
{ {
...@@ -612,24 +616,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -612,24 +616,6 @@ class MergeRequest < ActiveRecord::Base
!discussions_to_be_resolved? !discussions_to_be_resolved?
end end
def hook_attrs
attrs = {
source: source_project.try(:hook_attrs),
target: target_project.hook_attrs,
last_commit: nil,
work_in_progress: work_in_progress?,
total_time_spent: total_time_spent,
human_total_time_spent: human_total_time_spent,
human_time_estimate: human_time_estimate
}
if diff_head_commit
attrs[:last_commit] = diff_head_commit.hook_attrs
end
attributes.merge!(attrs)
end
def for_fork? def for_fork?
target_project != source_project target_project != source_project
end end
...@@ -714,13 +700,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -714,13 +700,13 @@ class MergeRequest < ActiveRecord::Base
def source_branch_exists? def source_branch_exists?
return false unless self.source_project return false unless self.source_project
self.source_project.repository.branch_names.include?(self.source_branch) self.source_project.repository.branch_exists?(self.source_branch)
end end
def target_branch_exists? def target_branch_exists?
return false unless self.target_project return false unless self.target_project
self.target_project.repository.branch_names.include?(self.target_branch) self.target_project.repository.branch_exists?(self.target_branch)
end end
def merge_commit_message(include_description: false) def merge_commit_message(include_description: false)
......
...@@ -53,13 +53,17 @@ class SentNotification < ActiveRecord::Base ...@@ -53,13 +53,17 @@ class SentNotification < ActiveRecord::Base
end end
def unsubscribable? def unsubscribable?
!for_commit? !(for_commit? || for_snippet?)
end end
def for_commit? def for_commit?
noteable_type == "Commit" noteable_type == "Commit"
end end
def for_snippet?
noteable_type.end_with?('Snippet')
end
def noteable def noteable
if for_commit? if for_commit?
project.commit(commit_id) rescue nil project.commit(commit_id) rescue nil
......
...@@ -45,6 +45,6 @@ class GroupEntity < Grape::Entity ...@@ -45,6 +45,6 @@ class GroupEntity < Grape::Entity
end end
expose :avatar_url do |group| expose :avatar_url do |group|
group_icon(group) group_icon_url(group)
end end
end end
...@@ -257,7 +257,7 @@ class IssuableBaseService < BaseService ...@@ -257,7 +257,7 @@ class IssuableBaseService < BaseService
invalidate_cache_counts(issuable, users: affected_assignees.compact) invalidate_cache_counts(issuable, users: affected_assignees.compact)
after_update(issuable) after_update(issuable)
issuable.create_new_cross_references!(current_user) issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update') execute_hooks(issuable, 'update', old_labels: old_labels, old_assignees: old_assignees)
issuable.update_project_counter_caches if update_project_counters issuable.update_project_counter_caches if update_project_counters
end end
......
module Issues module Issues
class BaseService < ::IssuableBaseService class BaseService < ::IssuableBaseService
def hook_data(issue, action) def hook_data(issue, action, old_labels: [], old_assignees: [])
issue_data = issue.to_hook_data(current_user) hook_data = issue.to_hook_data(current_user, old_labels: old_labels, old_assignees: old_assignees)
issue_url = Gitlab::UrlBuilder.build(issue) hook_data[:object_attributes][:action] = action
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue_data hook_data
end end
def reopen_service def reopen_service
...@@ -22,8 +22,8 @@ module Issues ...@@ -22,8 +22,8 @@ module Issues
issue, issue.project, current_user, old_assignees) issue, issue.project, current_user, old_assignees)
end end
def execute_hooks(issue, action = 'open') def execute_hooks(issue, action = 'open', old_labels: [], old_assignees: [])
issue_data = hook_data(issue, action) issue_data = hook_data(issue, action, old_labels: old_labels, old_assignees: old_assignees)
hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
issue.project.execute_hooks(issue_data, hooks_scope) issue.project.execute_hooks(issue_data, hooks_scope)
issue.project.execute_services(issue_data, hooks_scope) issue.project.execute_services(issue_data, hooks_scope)
......
...@@ -20,19 +20,19 @@ module MergeRequests ...@@ -20,19 +20,19 @@ module MergeRequests
super if changed_title super if changed_title
end end
def hook_data(merge_request, action, oldrev = nil) def hook_data(merge_request, action, old_rev: nil, old_labels: [], old_assignees: [])
hook_data = merge_request.to_hook_data(current_user) hook_data = merge_request.to_hook_data(current_user, old_labels: old_labels, old_assignees: old_assignees)
hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
hook_data[:object_attributes][:action] = action hook_data[:object_attributes][:action] = action
if oldrev && !Gitlab::Git.blank_ref?(oldrev) if old_rev && !Gitlab::Git.blank_ref?(old_rev)
hook_data[:object_attributes][:oldrev] = oldrev hook_data[:object_attributes][:oldrev] = old_rev
end end
hook_data hook_data
end end
def execute_hooks(merge_request, action = 'open', oldrev = nil) def execute_hooks(merge_request, action = 'open', old_rev: nil, old_labels: [], old_assignees: [])
if merge_request.project if merge_request.project
merge_data = hook_data(merge_request, action, oldrev) merge_data = hook_data(merge_request, action, old_rev: old_rev, old_labels: old_labels, old_assignees: old_assignees)
merge_request.project.execute_hooks(merge_data, :merge_request_hooks) merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
merge_request.project.execute_services(merge_data, :merge_request_hooks) merge_request.project.execute_services(merge_data, :merge_request_hooks)
end end
......
...@@ -183,7 +183,7 @@ module MergeRequests ...@@ -183,7 +183,7 @@ module MergeRequests
# Call merge request webhook with update branches # Call merge request webhook with update branches
def execute_mr_web_hooks def execute_mr_web_hooks
merge_requests_for_source_branch.each do |merge_request| merge_requests_for_source_branch.each do |merge_request|
execute_hooks(merge_request, 'update', @oldrev) execute_hooks(merge_request, 'update', old_rev: @oldrev)
end end
end end
......
...@@ -459,7 +459,7 @@ module QuickActions ...@@ -459,7 +459,7 @@ module QuickActions
target_branch_param.strip target_branch_param.strip
end end
command :target_branch do |branch_name| command :target_branch do |branch_name|
@updates[:target_branch] = branch_name if project.repository.branch_names.include?(branch_name) @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
end end
desc 'Move issue from one column of the board to another' desc 'Move issue from one column of the board to another'
......
...@@ -647,7 +647,7 @@ module SystemNoteService ...@@ -647,7 +647,7 @@ module SystemNoteService
def discussion_lock(issuable, author) def discussion_lock(issuable, author)
action = issuable.discussion_locked? ? 'locked' : 'unlocked' action = issuable.discussion_locked? ? 'locked' : 'unlocked'
body = "#{action} this issue" body = "#{action} this #{issuable.class.to_s.titleize.downcase}"
create_note(NoteSummary.new(issuable, issuable.project, author, body, action: action)) create_note(NoteSummary.new(issuable, issuable.project, author, body, action: action))
end end
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
= visibility_level_icon(group.visibility_level, fw: false) = visibility_level_icon(group.visibility_level, fw: false)
.avatar-container.s40 .avatar-container.s40
= image_tag group_icon(group), class: "avatar s40 hidden-xs" = group_icon(group, class: "avatar s40 hidden-xs")
.title .title
= link_to [:admin, group], class: 'group-name' do = link_to [:admin, group], class: 'group-name' do
= group.full_name = group.full_name
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%ul.well-list %ul.well-list
%li %li
.avatar-container.s60 .avatar-container.s60
= image_tag group_icon(@group), class: "avatar s60" = group_icon(@group, class: "avatar s60")
%li %li
%span.light Name: %span.light Name:
%strong= @group.name %strong= @group.name
......
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
%th Projects %th Projects
%th Jobs %th Jobs
%th Tags %th Tags
%th Last contact %th= link_to 'Last contact', admin_runners_path(params.slice(:search).merge(sort: 'contacted_asc'))
%th %th
- @runners.each do |runner| - @runners.each do |runner|
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
%td.notes_line{ colspan: 2 } %td.notes_line{ colspan: 2 }
%td.notes_content %td.notes_content
.content{ class: ('hide' unless expanded) } .content{ class: ('hide' unless expanded) }
= render partial: "discussions/notes", collection: discussions, as: :discussion = render partial: "discussions/notes", collection: discussions, as: :discussion, locals: { disable_collapse_class: true }
...@@ -24,4 +24,4 @@ ...@@ -24,4 +24,4 @@
= render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false }
.note-container .note-container
= render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true } = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse_class: true }
- disable_collapse = local_assigns.fetch(:disable_collapse, false) - disable_collapse_class = local_assigns.fetch(:disable_collapse_class, false)
- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse - collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse_class
- badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter]
- show_toggle = local_assigns.fetch(:show_toggle, true) - show_toggle = local_assigns.fetch(:show_toggle, true)
- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false)
......
...@@ -19,8 +19,7 @@ ...@@ -19,8 +19,7 @@
- create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user) - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user)
- if event.commits_count > 1 - if event.commits_count > 1
%li.commits-stat %li.commits-stat
- if event.commits_count > 2 %span ... and #{pluralize(event.commits_count - 1, 'more commit')}.
%span ... and #{event.commits_count - 2} more commits.
- if event.md_ref? - if event.md_ref?
- from = event.commit_from - from = event.commit_from
......
.group-home-panel.text-center .group-home-panel.text-center
%div{ class: container_class } %div{ class: container_class }
.avatar-container.s70.group-avatar .avatar-container.s70.group-avatar
= image_tag group_icon(@group), class: "avatar s70 avatar-tile" = group_icon(@group, class: "avatar s70 avatar-tile")
%h1.group-title %h1.group-title
= @group.name = @group.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.avatar-container.s160 .avatar-container.s160
= image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' = group_icon(@group, alt: '', class: 'avatar group-avatar s160')
%p.light %p.light
- if @group.avatar? - if @group.avatar?
You can change your group avatar here You can change your group avatar here
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.form-group.milestone-description .form-group.milestone-description
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { url: '' } do = render layout: 'projects/md_preview', locals: { url: group_preview_markdown_path } do
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
.clearfix .clearfix
.error-alert .error-alert
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.context-header .context-header
= link_to group_path(@group), title: @group.name do = link_to group_path(@group), title: @group.name do
.avatar-container.s40.group-avatar .avatar-container.s40.group-avatar
= image_tag group_icon(@group), class: "avatar s40 avatar-tile" = group_icon(@group, class: "avatar s40 avatar-tile")
.sidebar-context-title .sidebar-context-title
= @group.name = @group.name
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
......
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Perform advanced options such as housekeeping, exporting, archiving, renaming, transferring, or removing your project. Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project.
.settings-content.no-animate{ class: ('expanded' if expanded) } .settings-content.no-animate{ class: ('expanded' if expanded) }
.sub-section .sub-section
%h4 Housekeeping %h4 Housekeeping
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
- if @build.trigger_variables.any? - if @build.trigger_variables.any?
%p %p
%button.btn.group.btn-group-justified.reveal-variables Reveal Variables %button.btn.group.btn-group-justified.js-reveal-variables Reveal Variables
%dl.js-build-variables.trigger-build-variables.hide %dl.js-build-variables.trigger-build-variables.hide
- @build.trigger_variables.each do |trigger_variable| - @build.trigger_variables.each do |trigger_variable|
......
- page_title _("Wiki") - page_title _("Wiki")
%h3.page-title= _("Wiki|Empty page") %h3.page-title= s_("Wiki|Empty page")
%hr %hr
.error_message .error_message
= s_("WikiEmptyPageError|You are not allowed to create wiki pages") = s_("WikiEmptyPageError|You are not allowed to create wiki pages")
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.avatar-container.s40 .avatar-container.s40
= link_to group do = link_to group do
= image_tag group_icon(group), class: "avatar s40 hidden-xs" = group_icon(group, class: "avatar s40 hidden-xs")
.title .title
= link_to group_name, group, class: 'group-name' = link_to group_name, group, class: 'group-name'
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- dom_id = "group_member_#{group_link.id}" - dom_id = "group_member_#{group_link.id}"
%li.member.group_member{ id: dom_id } %li.member.group_member{ id: dom_id }
%span.list-item-name %span.list-item-name
= image_tag group_icon(group), class: "avatar s40", alt: '' = group_icon(group, class: "avatar s40", alt: '')
%strong %strong
= link_to group.full_name, group_path(group) = link_to group.full_name, group_path(group)
.cgray .cgray
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
- groups.each do |group| - groups.each do |group|
= link_to group, class: 'profile-groups-avatars inline', title: group.name do = link_to group, class: 'profile-groups-avatars inline', title: group.name do
.avatar-container.s40 .avatar-container.s40
= image_tag group_icon(group), class: 'avatar group-avatar s40' = group_icon(group, class: 'avatar group-avatar s40')
---
title: Don't show an "Unsubscribe" link in snippet comment notifications
merge_request: 14764
author:
type: fixed
---
title: Include the changes in issuable webhook payloads
merge_request: 14308
author:
type: added
---
title: Fix bad type checking to prevent 0 count badge to be shown
merge_request:
author:
type: fixed
---
title: Fix the project import with issues and milestones
merge_request: 14657
author:
type: fixed
---
title: Decreases z-index of select2 to a lower number of our navigation bar
merge_request:
author:
type: fixed
---
title: Fix the number representing the amount of commits related to a push event
merge_request:
author:
type: fixed
---
title: Fixes mini pipeline graph in commit view
merge_request:
author:
type: fixed
---
title: Add sort runners on admin runners
merge_request: 14661
author: Takuya Noguchi
type: added
---
title: Avoid fetching all branches for branch existence checks
merge_request: 14778
author:
type: changed
---
title: Fixed group sort dropdown defaulting to empty
merge_request:
author:
type: fixed
---
title: Add support for markdown preview to group milestones
merge_request: 14806
author: Vitaliy @blackst0ne Klachkov
type: fixed
namespace :google_api do scope '-' do
resource :auth, only: [], controller: :authorizations do namespace :google_api do
match :callback, via: [:get, :post] resource :auth, only: [], controller: :authorizations do
match :callback, via: [:get, :post]
end
end end
end end
require 'constraints/group_url_constrainer' require 'constraints/group_url_constrainer'
resources :groups, only: [:index, :new, :create] resources :groups, only: [:index, :new, :create] do
post :preview_markdown
end
constraints(GroupUrlConstrainer.new) do constraints(GroupUrlConstrainer.new) do
scope(path: 'groups/*id', scope(path: 'groups/*id',
......
# rubocop:disable Migration/Datetime
class AddArtifactsExpireDateToCiBuilds < ActiveRecord::Migration class AddArtifactsExpireDateToCiBuilds < ActiveRecord::Migration
def change def change
add_column :ci_builds, :artifacts_expire_at, :timestamp add_column :ci_builds, :artifacts_expire_at, :timestamp
......
# rubocop:disable Migration/Datetime
class AddQueuedAtToCiBuilds < ActiveRecord::Migration class AddQueuedAtToCiBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class AddSharedRunnersSecondsToProjectStatistics < ActiveRecord::Migration class AddSharedRunnersSecondsToProjectStatistics < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
......
# rubocop:disable Migration/Datetime
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
......
...@@ -57,18 +57,15 @@ ...@@ -57,18 +57,15 @@
- [Merge Request checklist](database_merge_request_checklist.md) - [Merge Request checklist](database_merge_request_checklist.md)
- [Adding database indexes](adding_database_indexes.md) - [Adding database indexes](adding_database_indexes.md)
- [Post Deployment Migrations](post_deployment_migrations.md)
- [Foreign keys & associations](foreign_keys.md) - [Foreign keys & associations](foreign_keys.md)
- [Serializing data](serializing_data.md)
- [Polymorphic associations](polymorphic_associations.md)
- [Single table inheritance](single_table_inheritance.md) - [Single table inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md) - [Polymorphic associations](polymorphic_associations.md)
- [Serializing data](serializing_data.md)
- [Hash indexes](hash_indexes.md)
- [Storing SHA1 hashes as binary](sha1_as_binary.md) - [Storing SHA1 hashes as binary](sha1_as_binary.md)
- [Iterating tables in batches](iterating_tables_in_batches.md) - [Iterating tables in batches](iterating_tables_in_batches.md)
- [Ordering table columns](ordering_table_columns.md) - [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md) - [Verifying database capabilities](verifying_database_capabilities.md)
- [Hash indexes](hash_indexes.md)
- [Swapping Tables](swapping_tables.md)
## Testing guides ## Testing guides
......
...@@ -88,16 +88,31 @@ followed by any global declarations, then a blank newline prior to any imports o ...@@ -88,16 +88,31 @@ followed by any global declarations, then a blank newline prior to any imports o
1. Use ES module syntax to import modules 1. Use ES module syntax to import modules
```javascript ```javascript
// bad // bad
require('foo'); const SomeClass = require('some_class');
// good // good
import Foo from 'foo'; import SomeClass from 'some_class';
// bad // bad
module.exports = Foo; module.exports = SomeClass;
// good // good
export default Foo; export default SomeClass;
```
Import statements are following usual naming guidelines, for example object literals use camel case:
```javascript
// some_object file
export default {
key: 'value',
};
// bad
import ObjectLiteral from 'some_object';
// good
import objectLiteral from 'some_object';
``` ```
1. Relative paths: when importing a module in the same directory, a child 1. Relative paths: when importing a module in the same directory, a child
...@@ -285,6 +300,13 @@ A forEach will cause side effects, it will be mutating the array being iterated. ...@@ -285,6 +300,13 @@ A forEach will cause side effects, it will be mutating the array being iterated.
1. **Extensions**: Use `.vue` extension for Vue components. 1. **Extensions**: Use `.vue` extension for Vue components.
1. **Reference Naming**: Use camelCase for their instances: 1. **Reference Naming**: Use camelCase for their instances:
```javascript ```javascript
// bad
import CardBoard from 'cardBoard'
components: {
CardBoard:
};
// good // good
import cardBoard from 'cardBoard' import cardBoard from 'cardBoard'
......
...@@ -67,7 +67,7 @@ run JavaScript tests, so you can either run unit tests (e.g. test a single ...@@ -67,7 +67,7 @@ run JavaScript tests, so you can either run unit tests (e.g. test a single
JavaScript method), or integration tests (e.g. test a component that is composed JavaScript method), or integration tests (e.g. test a component that is composed
of multiple components). of multiple components).
## System tests or Feature tests ## System tests or feature tests
Formal definition: https://en.wikipedia.org/wiki/System_testing. Formal definition: https://en.wikipedia.org/wiki/System_testing.
...@@ -108,7 +108,7 @@ The reasons why we should follow these best practices are as follows: ...@@ -108,7 +108,7 @@ The reasons why we should follow these best practices are as follows:
[Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist [Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist
[RackTest]: https://github.com/teamcapybara/capybara#racktest [RackTest]: https://github.com/teamcapybara/capybara#racktest
## Black-box tests or End-to-end tests ## Black-box tests or end-to-end tests
GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse], GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse],
[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces [Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces
......
# GitLab Helm Chart # GitLab Helm Chart
> **Note**: > **Note**:
* This chart will be replaced by the [gitlab-omnibus](gitlab_omnibus.md) chart, once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68). For more information on available charts, please see our [overview](index.md#chart-overview). * This chart is deprecated, and is being replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). For more information on available charts, please see our [overview](index.md#chart-overview).
* These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues). * These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
...@@ -8,7 +8,9 @@ For more information on available GitLab Helm Charts, please see our [overview]( ...@@ -8,7 +8,9 @@ For more information on available GitLab Helm Charts, please see our [overview](
## Introduction ## Introduction
The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. For most deployments we **strongly recommended** the [gitlab-omnibus](gitlab_omnibus.md) chart, which will replace this chart once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68). Due to the difficulty in supporting upgrades to the `omnibus-gitlab` chart, migrating will require exporting data out of this instance and importing it into the new deployment. The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. We **strongly recommend** the [gitlab-omnibus](gitlab_omnibus.md) chart.
This chart is deprecated, and will be replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment.
This chart includes the following: This chart includes the following:
......
# GitLab-Omnibus Helm Chart # GitLab-Omnibus Helm Chart
> **Note:** > **Note:**
* This Helm chart is in beta, while [additional features](https://gitlab.com/charts/charts.gitlab.io/issues/68) are being worked on. * This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md).
* GitLab is working on a [cloud native set of Charts](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) which will eventually replace these.
* These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues). * These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work. This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
...@@ -12,6 +11,8 @@ For more information on available GitLab Helm Charts, please see our [overview]( ...@@ -12,6 +11,8 @@ For more information on available GitLab Helm Charts, please see our [overview](
This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned via [Let's Encrypt](https://letsencrypt.org/). This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned via [Let's Encrypt](https://letsencrypt.org/).
This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) once available. Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment.
The deployment includes: The deployment includes:
- A [GitLab Omnibus](https://docs.gitlab.com/omnibus/) Pod, including Mattermost, Container Registry, and Prometheus - A [GitLab Omnibus](https://docs.gitlab.com/omnibus/) Pod, including Mattermost, Container Registry, and Prometheus
...@@ -23,9 +24,8 @@ The deployment includes: ...@@ -23,9 +24,8 @@ The deployment includes:
### Limitations ### Limitations
* This chart is suited for small to medium size deployments, because [High Availability](https://docs.gitlab.com/ee/administration/high_availability/) and [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html) will not be supported. * This chart is in beta, and suited for small to medium size deployments. [High Availability](https://docs.gitlab.com/ee/administration/high_availability/) and [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html) are not supported.
* It is in beta. Additional features to support production deployments, like backups, are [in development](https://gitlab.com/charts/charts.gitlab.io/issues/68). Once completed, this chart will be generally available. * A new generation [cloud native GitLab chart](index.md#cloud-native-gitlab-chart) is in development, and will deprecate this chart. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. We plan to release the new chart in beta by the end of 2017.
* A new generation of [cloud native charts](index.md#upcoming-cloud-native-helm-charts) is in development, and will eventually deprecate these. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. We do not expect these to be production ready before the second half of 2018.
For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview). For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
......
...@@ -9,11 +9,11 @@ should be deployed, upgraded, and configured. ...@@ -9,11 +9,11 @@ should be deployed, upgraded, and configured.
## Chart Overview ## Chart Overview
* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today. It is suited for small to medium deployments, and is in beta while support for backups and other features are added. * **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small to medium deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart).
* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components. * **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components.
* Other Charts * Other Charts
* [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner. * [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner.
* [Advanced GitLab Installation](gitlab_chart.md): Provides additional deployment options, but provides less functionality out-of-the-box. It's beta, no longer actively developed, and will be deprecated by [gitlab-omnibus](#gitlab-omnibus-chart-recommended) once it supports these options. * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box.
* [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart. * [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart.
## GitLab-Omnibus Chart (Recommended) ## GitLab-Omnibus Chart (Recommended)
...@@ -21,13 +21,13 @@ should be deployed, upgraded, and configured. ...@@ -21,13 +21,13 @@ should be deployed, upgraded, and configured.
This chart is the best available way to operate GitLab on Kubernetes. It deploys and configures nearly all features of GitLab, including: a [Runner](https://docs.gitlab.com/runner/), [Container Registry](../../user/project/container_registry.html#gitlab-container-registry), [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/), [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego), and a [load balancer](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). It is based on our [GitLab Omnibus Docker Images](https://docs.gitlab.com/omnibus/docker/README.html). This chart is the best available way to operate GitLab on Kubernetes. It deploys and configures nearly all features of GitLab, including: a [Runner](https://docs.gitlab.com/runner/), [Container Registry](../../user/project/container_registry.html#gitlab-container-registry), [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/), [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego), and a [load balancer](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). It is based on our [GitLab Omnibus Docker Images](https://docs.gitlab.com/omnibus/docker/README.html).
Once the [cloud native charts](#upcoming-cloud-native-helm-charts) are ready for production use, this chart will be deprecated. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. Once the [cloud native GitLab chart](#cloud-native-gitlab-chart) is ready for production use, this chart will be deprecated. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment.
Learn more about the [gitlab-omnibus chart.](gitlab_omnibus.md) Learn more about the [gitlab-omnibus chart](gitlab_omnibus.md).
## Cloud Native GitLab Chart ## Cloud Native GitLab Chart
GitLab is working towards building a [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current charts](#official-gitlab-helm-charts-recommended). GitLab is working towards building a [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current chart](#gitlab-omnibus-chart-recommended).
By offering individual containers and charts, we will be able to provide a number of benefits: By offering individual containers and charts, we will be able to provide a number of benefits:
* Easier horizontal scaling of each service, * Easier horizontal scaling of each service,
...@@ -35,9 +35,9 @@ By offering individual containers and charts, we will be able to provide a numbe ...@@ -35,9 +35,9 @@ By offering individual containers and charts, we will be able to provide a numbe
* Potential for rolling updates and canaries within a service, * Potential for rolling updates and canaries within a service,
* and plenty more. * and plenty more.
This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We do not expect these to be production ready before the second half of 2018. This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We are planning to launch this chart in beta by the end of 2017.
Learn more about the [cloud native GitLab chart.](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) Learn more about the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md).
## Other Charts ## Other Charts
......
...@@ -194,38 +194,67 @@ load and will have a corresponding badge counter to match the counter on the ima ...@@ -194,38 +194,67 @@ load and will have a corresponding badge counter to match the counter on the ima
> [Introduced][ce-14531] in GitLab 10.1. > [Introduced][ce-14531] in GitLab 10.1.
There might be some cases where a discussion is better off if it's locked down. Sometimes a discussion is revolved around an image. With image discussions,
For example: you can easily target a specific coordinate of an image and start a discussion
around it. Image discussions are available in merge requests and commit detail views.
To start an image discussion, hover your mouse over the image. Your mouse pointer
should convert into an icon, indicating that the image is available for commenting.
Simply click anywhere on the image to create a new discussion.
![Start image discussion](img/start_image_discussion.gif)
After you click on the image, a comment form will be displayed that would be the start
of your discussion. Once you save your comment, you will see a new badge displayed on
top of your image. This badge represents your discussion.
>**Note:**
This discussion badge is typically associated with a number that is only used as a visual
reference for each discussion. In the merge request discussion tab,
this badge will be indicated with a comment icon since each discussion will render a new
image section.
Image discussions also work on diffs that replace an existing image. In this diff view
mode, you can toggle the different view modes and still see the discussion point badges.
| 2-up | Swipe | Onion Skin |
| :-----------: | :----------: | :----------: |
| ![2-up view](img/two_up_view.png) | ![swipe view](img/swipe_view.png) | ![onion skin view](img/onion_skin_view.png) |
- Discussions that are several years old and the issue/merge request is closed, Image discussions also work well with resolvable discussions. Resolved discussions
but people continue to try to resurrect the discussion. on diffs (not on the merge request discussion tab) will appear collapsed on page
- Discussions where someone or a group of people are trolling, are abusive, or load and will have a corresponding badge counter to match the counter on the image.
in-general are causing the discussion to be unproductive.
![Image resolved discussion](img/image_resolved_discussion.png)
## Lock discussions
> [Introduced][ce-14531] in GitLab 10.1.
In locked discussions, only team members can write new comments and edit the old For large projects with many contributors, it may be useful to stop discussions
ones. in issues or merge requests in these scenarios:
To lock or unlock a discussion, you need to have at least Master [permissions]: - The project maintainer has already resolved the discussion and it is not helpful
for continued feedback. The project maintainer has already directed new conversation
to newer issues or merge requests.
- The people participating in the discussion are trolling, abusive, or otherwise
being unproductive.
1. Find the "Lock" section in the sidebar and click **Edit** In these cases, a user with Master permissions or higher in the project can lock (and unlock)
1. In the dialog that will appear, you can choose to turn on or turn off the an issue or a merge request, using the "Lock" section in the sidebar:
discussion lock
1. Optionally, leave a comment to explain your reasoning behind that action
| Turn off discussion lock | Turn on discussion lock | | Unlock | Lock |
| :-----------: | :----------: | | :-----------: | :----------: |
| ![Turn off discussion lock](img/turn_off_lock.png) | ![Turn on discussion lock](img/turn_on_lock.png) | | ![Turn off discussion lock](img/turn_off_lock.png) | ![Turn on discussion lock](img/turn_on_lock.png) |
Every change is indicated by a system note in the issue's or merge request's System notes indicate locking and unlocking.
comments.
![Discussion lock system notes](img/discussion_lock_system_notes.png) ![Discussion lock system notes](img/discussion_lock_system_notes.png)
Once an issue or merge request is locked, project members can see the indicator In a locked issue or merge request, only team members can add new comments and
in the comment area, whereas non project members can only see the information edit existing comments. Non-team members are restricted from adding or editing comments.
that the discussion is locked.
| Team member | Not a member | | Team member | Non-team member |
| :-----------: | :----------: | | :-----------: | :----------: |
| ![Comment form member](img/lock_form_member.png) | ![Comment form non-member](img/lock_form_non_member.png) | | ![Comment form member](img/lock_form_member.png) | ![Comment form non-member](img/lock_form_non_member.png) |
......
...@@ -26,7 +26,7 @@ The following table depicts the various user permission levels in a project. ...@@ -26,7 +26,7 @@ The following table depicts the various user permission levels in a project.
| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ | | View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ | | See related issues | ✓ | ✓ | ✓ | ✓ | ✓ |
| Lock comments | | | | ✓ | ✓ | | Lock discussions (issues and merge requests) | | | | ✓ | ✓ |
| See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
...@@ -56,7 +56,7 @@ The following table depicts the various user permission levels in a project. ...@@ -56,7 +56,7 @@ The following table depicts the various user permission levels in a project.
| Create or update commit status | | | ✓ | ✓ | ✓ | | Create or update commit status | | | ✓ | ✓ | ✓ |
| Update a container registry | | | ✓ | ✓ | ✓ | | Update a container registry | | | ✓ | ✓ | ✓ |
| Remove a container registry image | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ | | Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ |
| Enable/disable branch protection | | | | ✓ | ✓ | | Enable/disable branch protection | | | | ✓ | ✓ |
...@@ -146,6 +146,7 @@ group. ...@@ -146,6 +146,7 @@ group.
| Manage group members | | | | | ✓ | | Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ | | Remove group | | | | | ✓ |
| Manage group labels | | ✓ | ✓ | ✓ | ✓ | | Manage group labels | | ✓ | ✓ | ✓ | ✓ |
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
### Subgroup permissions ### Subgroup permissions
......
...@@ -230,7 +230,7 @@ X-Gitlab-Event: Issue Hook ...@@ -230,7 +230,7 @@ X-Gitlab-Event: Issue Hook
"username": "root", "username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project":{ "project": {
"name":"Gitlab Test", "name":"Gitlab Test",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test", "web_url":"http://example.com/gitlabhq/gitlab-test",
...@@ -246,7 +246,7 @@ X-Gitlab-Event: Issue Hook ...@@ -246,7 +246,7 @@ X-Gitlab-Event: Issue Hook
"ssh_url":"git@example.com:gitlabhq/gitlab-test.git", "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"http_url":"http://example.com/gitlabhq/gitlab-test.git" "http_url":"http://example.com/gitlabhq/gitlab-test.git"
}, },
"repository":{ "repository": {
"name": "Gitlab Test", "name": "Gitlab Test",
"url": "http://example.com/gitlabhq/gitlab-test.git", "url": "http://example.com/gitlabhq/gitlab-test.git",
"description": "Aut reprehenderit ut est.", "description": "Aut reprehenderit ut est.",
...@@ -291,7 +291,37 @@ X-Gitlab-Event: Issue Hook ...@@ -291,7 +291,37 @@ X-Gitlab-Event: Issue Hook
"description": "API related issues", "description": "API related issues",
"type": "ProjectLabel", "type": "ProjectLabel",
"group_id": 41 "group_id": 41
}] }],
"changes": {
"updated_by_id": [null, 1],
"updated_at": ["2017-09-15 16:50:55 UTC", "2017-09-15 16:52:00 UTC"],
"labels": {
"previous": [{
"id": 206,
"title": "API",
"color": "#ffffff",
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"template": false,
"description": "API related issues",
"type": "ProjectLabel",
"group_id": 41
}],
"current": [{
"id": 205,
"title": "Platform",
"color": "#123123",
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"template": false,
"description": "Platform related issues",
"type": "ProjectLabel",
"group_id": 41
}]
}
}
} }
``` ```
...@@ -686,6 +716,28 @@ X-Gitlab-Event: Merge Request Hook ...@@ -686,6 +716,28 @@ X-Gitlab-Event: Merge Request Hook
"username": "root", "username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project": {
"name":"Gitlab Test",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test",
"avatar_url":null,
"git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"git_http_url":"http://example.com/gitlabhq/gitlab-test.git",
"namespace":"GitlabHQ",
"visibility_level":20,
"path_with_namespace":"gitlabhq/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlabhq/gitlab-test",
"url":"http://example.com/gitlabhq/gitlab-test.git",
"ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"http_url":"http://example.com/gitlabhq/gitlab-test.git"
},
"repository": {
"name": "Gitlab Test",
"url": "http://example.com/gitlabhq/gitlab-test.git",
"description": "Aut reprehenderit ut est.",
"homepage": "http://example.com/gitlabhq/gitlab-test"
},
"object_attributes": { "object_attributes": {
"id": 99, "id": 99,
"target_branch": "master", "target_branch": "master",
...@@ -704,7 +756,7 @@ X-Gitlab-Event: Merge Request Hook ...@@ -704,7 +756,7 @@ X-Gitlab-Event: Merge Request Hook
"target_project_id": 14, "target_project_id": 14,
"iid": 1, "iid": 1,
"description": "", "description": "",
"source":{ "source": {
"name":"Awesome Project", "name":"Awesome Project",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/awesome_space/awesome_project", "web_url":"http://example.com/awesome_space/awesome_project",
...@@ -754,6 +806,48 @@ X-Gitlab-Event: Merge Request Hook ...@@ -754,6 +806,48 @@ X-Gitlab-Event: Merge Request Hook
"username": "user1", "username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
} }
},
"labels": [{
"id": 206,
"title": "API",
"color": "#ffffff",
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"template": false,
"description": "API related issues",
"type": "ProjectLabel",
"group_id": 41
}],
"changes": {
"updated_by_id": [null, 1],
"updated_at": ["2017-09-15 16:50:55 UTC", "2017-09-15 16:52:00 UTC"],
"labels": {
"previous": [{
"id": 206,
"title": "API",
"color": "#ffffff",
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"template": false,
"description": "API related issues",
"type": "ProjectLabel",
"group_id": 41
}],
"current": [{
"id": 205,
"title": "Platform",
"color": "#123123",
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"template": false,
"description": "Platform related issues",
"type": "ProjectLabel",
"group_id": 41
}]
}
} }
} }
``` ```
...@@ -1040,7 +1134,7 @@ X-Gitlab-Event: Build Hook ...@@ -1040,7 +1134,7 @@ X-Gitlab-Event: Build Hook
## Testing webhooks ## Testing webhooks
You can trigger the webhook manually. Sample data from the project will be used.Sample data will take from the project. You can trigger the webhook manually. Sample data from the project will be used.Sample data will take from the project.
> For example: for triggering `Push Events` your project should have at least one commit. > For example: for triggering `Push Events` your project should have at least one commit.
![Webhook testing](img/webhook_testing.png) ![Webhook testing](img/webhook_testing.png)
......
...@@ -193,7 +193,7 @@ module Gitlab ...@@ -193,7 +193,7 @@ module Gitlab
def has_local_branches? def has_local_branches?
gitaly_migrate(:has_local_branches) do |is_enabled| gitaly_migrate(:has_local_branches) do |is_enabled|
if is_enabled if is_enabled
gitaly_ref_client.has_local_branches? gitaly_repository_client.has_local_branches?
else else
has_local_branches_rugged? has_local_branches_rugged?
end end
......
...@@ -57,14 +57,6 @@ module Gitlab ...@@ -57,14 +57,6 @@ module Gitlab
branch_names.count branch_names.count
end end
# TODO implement a more efficient RPC for this https://gitlab.com/gitlab-org/gitaly/issues/616
def has_local_branches?
request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request).first
response&.names.present?
end
def local_branches(sort_by: nil) def local_branches(sort_by: nil)
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
request.sort_by = sort_by_param(sort_by) if sort_by request.sort_by = sort_by_param(sort_by) if sort_by
......
...@@ -58,6 +58,13 @@ module Gitlab ...@@ -58,6 +58,13 @@ module Gitlab
request = Gitaly::CreateRepositoryRequest.new(repository: @gitaly_repo) request = Gitaly::CreateRepositoryRequest.new(repository: @gitaly_repo)
GitalyClient.call(@storage, :repository_service, :create_repository, request) GitalyClient.call(@storage, :repository_service, :create_repository, request)
end end
def has_local_branches?
request = Gitaly::HasLocalBranchesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :repository_service, :has_local_branches, request)
response.value
end
end end
end end
end end
module Gitlab
module HookData
class IssuableBuilder
CHANGES_KEYS = %i[previous current].freeze
attr_accessor :issuable
def initialize(issuable)
@issuable = issuable
end
def build(user: nil, changes: {})
hook_data = {
object_kind: issuable.class.name.underscore,
user: user.hook_attrs,
project: issuable.project.hook_attrs,
object_attributes: issuable.hook_attrs,
labels: issuable.labels.map(&:hook_attrs),
changes: final_changes(changes.slice(*safe_keys)),
# DEPRECATED
repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)
}
if issuable.is_a?(Issue)
hook_data[:assignees] = issuable.assignees.map(&:hook_attrs) if issuable.assignees.any?
else
hook_data[:assignee] = issuable.assignee.hook_attrs if issuable.assignee
end
hook_data
end
def safe_keys
issuable_builder::SAFE_HOOK_ATTRIBUTES + issuable_builder::SAFE_HOOK_RELATIONS
end
private
def issuable_builder
case issuable
when Issue
Gitlab::HookData::IssueBuilder
when MergeRequest
Gitlab::HookData::MergeRequestBuilder
end
end
def final_changes(changes_hash)
changes_hash.reduce({}) do |hash, (key, changes_array)|
hash[key] = Hash[CHANGES_KEYS.zip(changes_array)]
hash
end
end
end
end
end
module Gitlab
module HookData
class IssueBuilder
SAFE_HOOK_ATTRIBUTES = %i[
assignee_id
author_id
branch_name
closed_at
confidential
created_at
deleted_at
description
due_date
id
iid
last_edited_at
last_edited_by_id
milestone_id
moved_to_id
project_id
relative_position
state
time_estimate
title
updated_at
updated_by_id
].freeze
SAFE_HOOK_RELATIONS = %i[
assignees
labels
].freeze
attr_accessor :issue
def initialize(issue)
@issue = issue
end
def build
attrs = {
url: Gitlab::UrlBuilder.build(issue),
total_time_spent: issue.total_time_spent,
human_total_time_spent: issue.human_total_time_spent,
human_time_estimate: issue.human_time_estimate,
assignee_ids: issue.assignee_ids,
assignee_id: issue.assignee_ids.first # This key is deprecated
}
issue.attributes.with_indifferent_access.slice(*SAFE_HOOK_ATTRIBUTES)
.merge!(attrs)
end
end
end
end
module Gitlab
module HookData
class MergeRequestBuilder
SAFE_HOOK_ATTRIBUTES = %i[
assignee_id
author_id
created_at
deleted_at
description
head_pipeline_id
id
iid
last_edited_at
last_edited_by_id
merge_commit_sha
merge_error
merge_params
merge_status
merge_user_id
merge_when_pipeline_succeeds
milestone_id
ref_fetched
source_branch
source_project_id
state
target_branch
target_project_id
time_estimate
title
updated_at
updated_by_id
].freeze
SAFE_HOOK_RELATIONS = %i[
assignee
labels
].freeze
attr_accessor :merge_request
def initialize(merge_request)
@merge_request = merge_request
end
def build
attrs = {
url: Gitlab::UrlBuilder.build(merge_request),
source: merge_request.source_project.try(:hook_attrs),
target: merge_request.target_project.hook_attrs,
last_commit: merge_request.diff_head_commit&.hook_attrs,
work_in_progress: merge_request.work_in_progress?,
total_time_spent: merge_request.total_time_spent,
human_total_time_spent: merge_request.human_total_time_spent,
human_time_estimate: merge_request.human_time_estimate
}
merge_request.attributes.with_indifferent_access.slice(*SAFE_HOOK_ATTRIBUTES)
.merge!(attrs)
end
end
end
end
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module ImportExport module ImportExport
class ProjectTreeRestorer class ProjectTreeRestorer
# Relations which cannot have both group_id and project_id at the same time # Relations which cannot have both group_id and project_id at the same time
RESTRICT_PROJECT_AND_GROUP = %i(milestones).freeze RESTRICT_PROJECT_AND_GROUP = %i(milestone milestones).freeze
def initialize(user:, shared:, project:) def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json') @path = File.join(shared.export_path, 'project.json')
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment