Commit 2f0dba92 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2017-10-11

# Conflicts:
#	app/assets/javascripts/label_manager.js
#	app/assets/javascripts/main.js
#	app/assets/javascripts/star.js
#	app/controllers/projects_controller.rb
#	app/views/projects/project_members/index.html.haml
#	doc/development/README.md
#	doc/development/testing_guide/testing_levels.md
#	doc/user/permissions.md
#	spec/helpers/groups_helper_spec.rb
#	spec/mailers/notify_spec.rb
[ci skip]
parents 5ba5711f 5a2acfe0
...@@ -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() {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global Sortable */ /* global Sortable */
import Flash from './flash'; import Flash from './flash';
<<<<<<< HEAD
((global) => { ((global) => {
class LabelManager { class LabelManager {
...@@ -20,106 +21,122 @@ import Flash from './flash'; ...@@ -20,106 +21,122 @@ import Flash from './flash';
}); });
this.bindEvents(); this.bindEvents();
} }
=======
>>>>>>> upstream/master
bindEvents() { export default class LabelManager {
this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick); constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority');
} this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
filter: '.empty-message',
forceFallback: true,
fallbackClass: 'is-dragging',
dataIdAttr: 'data-id',
onUpdate: this.onPrioritySortUpdate.bind(this),
});
this.bindEvents();
}
onTogglePriorityClick(e) { bindEvents() {
e.preventDefault(); this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick);
const _this = e.data; return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
const $btn = $(e.currentTarget); }
const $label = $(`#${$btn.data('domId')}`);
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
$tooltip.tooltip('destroy');
_this.toggleLabelPriority($label, action);
_this.toggleEmptyState($label, $btn, action);
}
onButtonActionClick(e) { onTogglePriorityClick(e) {
e.stopPropagation(); e.preventDefault();
$(e.currentTarget).tooltip('hide'); const _this = e.data;
} const $btn = $(e.currentTarget);
const $label = $(`#${$btn.data('domId')}`);
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
$tooltip.tooltip('destroy');
_this.toggleLabelPriority($label, action);
_this.toggleEmptyState($label, $btn, action);
}
toggleEmptyState($label, $btn, action) { onButtonActionClick(e) {
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); e.stopPropagation();
} $(e.currentTarget).tooltip('hide');
}
toggleLabelPriority($label, action, persistState) { toggleEmptyState($label, $btn, action) {
if (persistState == null) { this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
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));
}
onPrioritySortUpdate() { toggleLabelPriority($label, action, persistState) {
const xhr = this.savePrioritySort(); if (persistState == null) {
return xhr.fail(function() { persistState = true;
return new Flash(this.errorMessage, 'alert');
});
} }
let xhr;
savePrioritySort() { const _this = this;
return $.post({ const url = $label.find('.js-toggle-priority').data('url');
url: this.prioritizedLabels.data('url'), let $target = this.prioritizedLabels;
data: { let $from = this.otherLabels;
label_ids: this.getSortedLabelsIds() 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));
}
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,19 @@ import './lib/utils/url_utility'; ...@@ -46,26 +46,19 @@ 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';
<<<<<<< HEAD
import './api'; import './api';
import './ajax_loading_spinner'; import './ajax_loading_spinner';
=======
>>>>>>> upstream/master
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 +84,6 @@ import './issuable_context'; ...@@ -91,8 +84,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 +121,6 @@ import './right_sidebar'; ...@@ -130,7 +121,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';
......
...@@ -29,6 +29,12 @@ const bindEvents = () => { ...@@ -29,6 +29,12 @@ const bindEvents = () => {
const $newProjectForm = $('#new_project'); const $newProjectForm = $('#new_project');
const $projectImportUrl = $('#project_import_url'); const $projectImportUrl = $('#project_import_url');
const $projectPath = $('#project_path'); const $projectPath = $('#project_path');
const $useTemplateBtn = $('.template-button > input');
const $projectFieldsForm = $('.project-fields-form');
const $selectedTemplateText = $('.selected-template');
const $changeTemplateBtn = $('.change-template');
const $selectedIcon = $('.selected-icon svg');
const $templateProjectNameInput = $('#template-project-name #project_path');
if ($newProjectForm.length !== 1) { if ($newProjectForm.length !== 1) {
return; return;
...@@ -48,6 +54,40 @@ const bindEvents = () => { ...@@ -48,6 +54,40 @@ const bindEvents = () => {
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`); $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
}); });
function chooseTemplate() {
$('.template-option').hide();
$projectFieldsForm.addClass('selected');
$selectedIcon.removeClass('active');
const value = $(this).val();
const templates = {
rails: {
text: 'Ruby on Rails',
icon: '.selected-icon .icon-rails',
},
express: {
text: 'NodeJS Express',
icon: '.selected-icon .icon-node-express',
},
spring: {
text: 'Spring',
icon: '.selected-icon .icon-java-spring',
},
};
const selectedTemplate = templates[value];
$selectedTemplateText.text(selectedTemplate.text);
$(selectedTemplate.icon).addClass('active');
$templateProjectNameInput.focus();
}
$useTemplateBtn.on('change', chooseTemplate);
$changeTemplateBtn.on('click', () => {
$('.template-option').show();
$projectFieldsForm.removeClass('selected');
$useTemplateBtn.prop('checked', false);
});
$newProjectForm.on('submit', () => { $newProjectForm.on('submit', () => {
$projectPath.val($projectPath.val().trim()); $projectPath.val($projectPath.val().trim());
}); });
......
<<<<<<< HEAD
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
=======
>>>>>>> upstream/master
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);
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
@import "framework/new-sidebar"; @import "framework/new-sidebar";
@import "framework/tables"; @import "framework/tables";
@import "framework/notes"; @import "framework/notes";
@import "framework/tabs";
@import "framework/timeline"; @import "framework/timeline";
@import "framework/tooltips"; @import "framework/tooltips";
@import "framework/typography"; @import "framework/typography";
......
...@@ -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 {
......
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
border: 1px solid $dropdown-border-color; border: 1px solid $dropdown-border-color;
min-width: 175px; min-width: 175px;
color: $gl-text-color; color: $gl-text-color;
z-index: 999;
} }
.select2-drop.select2-drop-above.select2-drop-active { .select2-drop.select2-drop-above.select2-drop-active {
......
.gitlab-tabs {
background: $gray-light;
border: 1px solid $border-color;
li {
width: 50%;
&:not(:last-child) {
border-right: 1px solid $border-color;
}
&.active {
background: $white-light;
}
a {
width: 100%;
text-align: center;
}
}
}
.gitlab-tab-content {
border: 1px solid $border-color;
border-top: 0;
margin-bottom: $gl-padding;
.tab-pane {
padding: $gl-padding;
&.no-padding {
padding: 0;
}
}
}
...@@ -721,14 +721,6 @@ $perf-bar-bucket-color: #ccc; ...@@ -721,14 +721,6 @@ $perf-bar-bucket-color: #ccc;
$perf-bar-bucket-box-shadow-from: rgba($white-light, .2); $perf-bar-bucket-box-shadow-from: rgba($white-light, .2);
$perf-bar-bucket-box-shadow-to: rgba($black, .25); $perf-bar-bucket-box-shadow-to: rgba($black, .25);
/*
Project Templates Icons
*/
$rails: #c00;
$node: #353535;
$java: #70ad51;
/* /*
Issuable warning Issuable warning
*/ */
......
...@@ -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;
} }
......
...@@ -3,41 +3,12 @@ ...@@ -3,41 +3,12 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
} }
.project-member-tabs {
background: $gray-light;
border: 1px solid $border-color;
li {
width: 50%;
&.active {
background: $white-light;
}
&:first-child {
border-right: 1px solid $border-color;
}
a {
width: 100%;
text-align: center;
}
}
}
.users-project-form { .users-project-form {
.btn-create { .btn-create {
margin-right: 10px; margin-right: 10px;
} }
} }
.project-member-tab-content {
padding: $gl-padding;
border: 1px solid $border-color;
border-top: 0;
margin-bottom: $gl-padding;
}
.member { .member {
&.is-overriden { &.is-overriden {
.btn-ldap-override { .btn-ldap-override {
......
...@@ -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 {
......
...@@ -56,7 +56,8 @@ ...@@ -56,7 +56,8 @@
border: 1px solid $border-color; border: 1px solid $border-color;
} }
+ .select2 a { + .select2 a,
+ .btn-default {
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
...@@ -557,10 +558,92 @@ a.deploy-project-label { ...@@ -557,10 +558,92 @@ a.deploy-project-label {
} }
} }
.project-template, .project-template {
> .form-group {
margin-bottom: 0;
}
.template-option {
padding: $gl-padding $gl-padding $gl-padding ($gl-padding * 4);
position: relative;
&:not(:first-child) {
border-top: 1px solid $border-color;
}
}
.template-title {
font-size: 16px;
}
.template-description {
margin: 6px 0 12px;
}
.template-button {
input {
position: absolute;
clip: rect(0, 0, 0, 0);
}
}
svg {
position: absolute;
left: $gl-padding;
top: $gl-padding;
}
.project-fields-form {
display: none;
&.selected {
display: block;
padding: $gl-padding;
}
}
.template-input-group {
position: relative;
@media (min-width: $screen-sm-min) {
display: flex;
}
.input-group-addon {
flex: 1;
text-align: left;
padding-left: ($gl-padding * 3);
background-color: $white-light;
}
.selected-template {
line-height: 20px;
}
.selected-icon {
svg {
display: none;
top: 7px;
height: 20px;
width: 20px;
&.active {
display: block;
}
}
}
}
}
.gitlab-tab-content {
.import-project-pane {
padding-bottom: 6px;
}
}
.project-import { .project-import {
.form-group { .form-group {
margin-bottom: 5px; margin-bottom: 0;
} }
.import-buttons { .import-buttons {
...@@ -575,10 +658,6 @@ a.deploy-project-label { ...@@ -575,10 +658,6 @@ a.deploy-project-label {
margin-right: 10px; margin-right: 10px;
} }
.blank-option {
min-width: 70px;
}
.btn-template-icon { .btn-template-icon {
height: 24px; height: 24px;
width: inherit; width: inherit;
...@@ -600,18 +679,6 @@ a.deploy-project-label { ...@@ -600,18 +679,6 @@ a.deploy-project-label {
} }
} }
.icon-rails path {
fill: $rails;
}
.icon-node-express path {
fill: $node;
}
.icon-java-spring path {
fill: $java;
}
> div { > div {
margin-bottom: 10px; margin-bottom: 10px;
padding-left: 0; padding-left: 0;
...@@ -619,10 +686,6 @@ a.deploy-project-label { ...@@ -619,10 +686,6 @@ a.deploy-project-label {
} }
} }
.project-templates-buttons .btn:last-child {
margin-right: 0;
}
.create-project-options { .create-project-options {
display: flex; display: flex;
...@@ -1099,6 +1162,12 @@ a.allowed-to-push { ...@@ -1099,6 +1162,12 @@ a.allowed-to-push {
min-width: 100px; min-width: 100px;
} }
&.form-group {
@media (min-width: $screen-sm-min) {
margin-bottom: 0;
}
}
.select2-choice { .select2-choice {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 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
<<<<<<< HEAD
prepend EE::ProjectsController prepend EE::ProjectsController
=======
include PreviewMarkdown
>>>>>>> upstream/master
before_action :authenticate_user!, except: [:index, :show, :activity, :refs] before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :project, except: [:index, :new, :create] before_action :project, except: [:index, :new, :create]
...@@ -261,18 +265,6 @@ class ProjectsController < Projects::ApplicationController ...@@ -261,18 +265,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
......
- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
.row{ id: project_name_id }
.form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-light' do
%span
Project path
.input-group
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1}
- else
.input-group-addon.static-namespace
#{user_url(current_user.username)}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
.form-group.project-path.col-sm-6
= f.label :path, class: 'label-light' do
%span
Project name
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if current_user.can_create_group?
.help-block
Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path
.form-group
= f.label :description, class: 'label-light' do
Project description
%span (optional)
= f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250
.form-group.visibility-level-setting
= f.label :visibility_level, class: 'label-light' do
Visibility Level
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
.project-templates-buttons.import-buttons{ data: { toggle: "buttons" } } .project-templates-buttons.import-buttons
.btn.blank-option.active
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: "blank", checked: "true", value: "" }
= icon('file-o', class: 'btn-template-icon')
Blank
- Gitlab::ProjectTemplate.all.each do |template| - Gitlab::ProjectTemplate.all.each do |template|
.btn .template-option
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name }
= custom_icon(template.logo) = custom_icon(template.logo)
= template.title .template-title= template.title
.template-description= template.description
%label.btn.btn-success.template-button.choose-template.append-right-10{ for: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name }
%span Use template
%a.btn.btn-default{ href: template.preview, rel: 'noopener noreferrer', target: '_blank' } Preview
.project-fields-form
.form-group
%label.label-light
Template
.input-group.template-input-group
.input-group-addon
.selected-icon
- Gitlab::ProjectTemplate.all.each do |template|
= custom_icon(template.logo)
.selected-template
%button.btn.btn-default.change-template{ type: "button" } Change template
= render 'new_project_fields', f: f, project_name_id: "template-project-name"
...@@ -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|
......
...@@ -14,24 +14,40 @@ ...@@ -14,24 +14,40 @@
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
New project New project
- if import_sources_enabled? %p
%p A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}.
A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}. %p
%p All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings.
All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings.
.col-lg-9.js-toggle-container .col-lg-9.js-toggle-container
= form_for @project, html: { class: 'new_project' } do |f| %ul.nav-links.gitlab-tabs{ role: 'tablist' }
.create-project-options %li.active{ role: 'presentation' }
.first-column %a{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.hidden-xs Blank project
%span.visible-xs Blank
%li{ role: 'presentation' }
%a{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.hidden-xs Create from template
%span.visible-xs Template
%li{ role: 'presentation' }
%a{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.hidden-xs Import project
%span.visible-xs Import
.tab-content.gitlab-tab-content
.tab-pane.active{ id: 'blank-project-pane', role: 'tabpanel' }
= form_for @project, html: { class: 'new_project' } do |f|
= render 'new_project_fields', f: f, project_name_id: "blank-project-name"
.tab-pane.no-padding{ id: 'create-from-template-pane', role: 'tabpanel' }
= form_for @project, html: { class: 'new_project' } do |f|
.project-template .project-template
.form-group .form-group
= f.label :template_project, class: 'label-light' do
Create from template
= link_to icon('question-circle'), help_page_path("gitlab-basics/create-project"), target: '_blank', aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'}
%div %div
= render 'project_templates', f: f = render 'project_templates', f: f
- if import_sources_enabled?
.second-column .tab-pane.import-project-pane{ id: 'import-project-pane', role: 'tabpanel' }
= form_for @project, html: { class: 'new_project' } do |f|
- if import_sources_enabled?
.project-import .project-import
.form-group.clearfix .form-group.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong = f.label :visibility_level, class: 'label-light' do #the label here seems wrong
...@@ -74,54 +90,10 @@ ...@@ -74,54 +90,10 @@
.import_gitlab_project.has-tooltip{ data: { container: 'body' } } .import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export') = icon('gitlab', text: 'GitLab export')
.col-lg-12
.row .js-toggle-content.hide
.col-lg-12 %hr
.js-toggle-content.hide = render "shared/import_form", f: f
%hr
= render "shared/import_form", f: f
%hr
.row
.form-group.col-xs-12.col-sm-6
= f.label :namespace_id, class: 'label-light' do
%span
Project path
.form-group
.input-group
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1}
- else
.input-group-addon.static-namespace
#{root_url}#{current_user.username}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
.form-group.col-xs-12.col-sm-6.project-path
= f.label :path, class: 'label-light' do
%span
Project name
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if current_user.can_create_group?
.help-block
Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path
.form-group
= f.label :description, class: 'label-light' do
Project description
%span.light (optional)
= f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250
.form-group.visibility-level-setting
= f.label :visibility_level, class: 'label-light' do
Visibility Level
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
.save-project-loader.hide .save-project-loader.hide
.center .center
......
...@@ -17,19 +17,32 @@ ...@@ -17,19 +17,32 @@
%i Owners %i Owners
.light .light
- if can?(current_user, :admin_project_member, @project) - if can?(current_user, :admin_project_member, @project)
<<<<<<< HEAD
%ul.nav-links.project-member-tabs{ role: 'tablist' } %ul.nav-links.project-member-tabs{ role: 'tablist' }
- if !membership_locked? - if !membership_locked?
%li.active{ role: 'presentation' } %li.active{ role: 'presentation' }
%a{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member %a{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member
=======
%ul.nav-links.gitlab-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' }
%a{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member
>>>>>>> upstream/master
- if @project.allowed_to_share_with_group? - if @project.allowed_to_share_with_group?
%li{ role: 'presentation', class: ('active' if membership_locked?) } %li{ role: 'presentation', class: ('active' if membership_locked?) }
%a{ href: '#share-with-group-pane', id: 'share-with-group-tab', data: { toggle: 'tab' }, role: 'tab' } Share with group %a{ href: '#share-with-group-pane', id: 'share-with-group-tab', data: { toggle: 'tab' }, role: 'tab' } Share with group
<<<<<<< HEAD
.tab-content.project-member-tab-content .tab-content.project-member-tab-content
- if !membership_locked? - if !membership_locked?
.tab-pane.active{ id: 'add-member-pane', role: 'tabpanel' } .tab-pane.active{ id: 'add-member-pane', role: 'tabpanel' }
= render 'projects/project_members/new_project_member', tab_title: 'Add member' = render 'projects/project_members/new_project_member', tab_title: 'Add member'
.tab-pane{ id: 'share-with-group-pane', role: 'tabpanel', class: ('active' if membership_locked?) } .tab-pane{ id: 'share-with-group-pane', role: 'tabpanel', class: ('active' if membership_locked?) }
=======
.tab-content.gitlab-tab-content
.tab-pane.active{ id: 'add-member-pane', role: 'tabpanel' }
= render 'projects/project_members/new_project_member', tab_title: 'Add member'
.tab-pane{ id: 'share-with-group-pane', role: 'tabpanel' }
>>>>>>> upstream/master
= render 'projects/project_members/new_shared_group', tab_title: 'Share with group' = render 'projects/project_members/new_shared_group', tab_title: 'Share with group'
= render 'shared/members/requests', membership_source: @project, requesters: @requesters = render 'shared/members/requests', membership_source: @project, requesters: @requesters
......
- 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'
......
<svg xmlns="http://www.w3.org/2000/svg" width="27" height="32" viewBox="0 0 27 32" class="btn-template-icon icon-node-express"> <svg xmlns="http://www.w3.org/2000/svg" width="27" height="32" viewBox="0 0 27 32" class="btn-template-icon icon-node-express"><g fill="none" fill-rule="evenodd"><path d="M-3 0h32v32H-3z"/><path fill="#353535" d="M1.192 16.267c.04 2.065.288 3.982.745 5.75.456 1.767 1.16 3.307 2.115 4.618.953 1.31 2.185 2.343 3.694 3.098 1.51.755 3.357 1.132 5.54 1.132 3.22 0 5.89-.844 8.016-2.532 2.125-1.69 3.446-4.22 3.962-7.597h1.192c-.437 3.575-1.847 6.345-4.23 8.312-2.384 1.966-5.324 2.95-8.82 2.95-2.383.04-4.42-.338-6.107-1.133-1.69-.794-3.07-1.917-4.142-3.367-1.073-1.45-1.867-3.158-2.383-5.124C.258 20.408 0 18.294 0 16.028c0-2.542.377-4.806 1.132-6.792C1.887 7.25 2.88 5.57 4.112 4.2 5.34 2.83 6.77 1.79 8.4 1.074 10.03.358 11.698 0 13.406 0c2.383 0 4.44.457 6.167 1.37 1.728.914 3.138 2.126 4.23 3.635 1.093 1.51 1.887 3.238 2.384 5.184.496 1.945.705 3.97.625 6.077H1.193zm24.43-1.192c0-1.867-.26-3.645-.775-5.333-.516-1.688-1.28-3.168-2.294-4.44-1.013-1.27-2.274-2.273-3.784-3.008-1.51-.735-3.258-1.102-5.244-1.102-1.67 0-3.228.317-4.678.953-1.45.636-2.72 1.56-3.813 2.77-1.092 1.212-1.976 2.672-2.652 4.38-.675 1.708-1.072 3.635-1.19 5.78h24.43z"/></g></svg>
<g fill="none" fill-rule="evenodd" transform="translate(-3)">
<rect width="32" height="32"/>
<path fill="#353535" d="M4.19170065,16.2667139 C4.23142421,18.3323387 4.47969269,20.2489714 4.93651356,22.0166696 C5.39333443,23.7843677 6.09841693,25.3236323 7.05178222,26.6345096 C8.00514751,27.9453869 9.23655921,28.9781838 10.7460543,29.7329313 C12.2555493,30.4876788 14.1026668,30.8650469 16.2874623,30.8650469 C19.5050701,30.8650469 22.1764391,30.0209341 24.3016492,28.3326831 C26.4268593,26.644432 27.7476477,24.1120935 28.2640539,20.7355914 L29.4557545,20.7355914 C29.0187954,24.3107112 27.6086304,27.0813875 25.2252172,29.0477034 C22.841804,31.0140194 19.9023051,31.9971626 16.4066324,31.9971626 C14.0232191,32.0368861 11.9874175,31.659518 10.2991665,30.8650469 C8.61091547,30.0705759 7.23054269,28.9484023 6.15800673,27.4984926 C5.08547078,26.0485829 4.29101162,24.3404957 3.77460543,22.3741798 C3.25819923,20.4078639 3,18.2926164 3,16.0283738 C3,13.4860664 3.3773681,11.2218578 4.13211562,9.23568007 C4.88686314,7.24950238 5.87993709,5.57120741 7.11136726,4.20074481 C8.34279742,2.8302822 9.77282391,1.78755456 11.4014896,1.07253059 C13.0301553,0.357506621 14.6985195,0 16.4066324,0 C18.7900456,0 20.8457087,0.456814016 22.5736832,1.37045575 C24.3016578,2.28409749 25.7118228,3.4956477 26.8042206,5.00514275 C27.8966183,6.51463779 28.6910775,8.24258646 29.1876219,10.1890406 C29.6841663,12.1354947 29.8927118,14.1613656 29.8132647,16.2667139 L4.19170065,16.2667139 Z M28.6215641,15.0750133 C28.6215641,13.2080062 28.3633648,11.4304039 27.8469586,9.74215285 C27.3305524,8.05390181 26.5658855,6.57422163 25.5529349,5.30306791 C24.5399843,4.03191419 23.2787803,3.0289095 21.7692853,2.29402376 C20.2597903,1.55913801 18.5119801,1.19170065 16.5258024,1.19170065 C14.8574132,1.19170065 13.2982871,1.50948432 11.8483774,2.14506118 C10.3984676,2.78063804 9.12733299,3.70419681 8.03493526,4.9157652 C6.94253754,6.12733359 6.05870172,7.58715229 5.38340131,9.2952651 C4.70810089,11.0033779 4.31087132,12.9299414 4.19170065,15.0750133 L28.6215641,15.0750133 Z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="20" viewBox="0 0 32 20" class="btn-template-icon icon-rails"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="20" viewBox="0 0 32 20" class="btn-template-icon icon-rails"><g fill="none" fill-rule="evenodd"><path d="M0-6h32v32H0z"/><path fill="#c00" fill-rule="nonzero" d="M.985 19.636s.422-4.163 3.375-9.087c2.954-4.924 7.99-8.65 12.083-9.017 8.144-.816 15.46 6.485 15.46 6.485s-.24.168-.494.38C23.42 2.49 18.54 5.274 17.005 6.02c-7.033 3.925-4.91 13.616-4.91 13.616H.987zM24.137 2.32c-.45-.182-.9-.35-1.364-.505l.056-.93c.885.254 1.237.423 1.363.493l-.056.943zM22.8 5.304c.45.028.915.084 1.393.183l-.056.872c-.464-.1-.928-.155-1.392-.17l.056-.885zM17.597.913c-.407 0-.815.015-1.223.058l-.268-.83c.465-.056.915-.084 1.35-.084l.282.858h-.14zm.676 5.178c.35-.154.76-.31 1.237-.45l.31.93c-.41.125-.817.294-1.225.49l-.323-.97zm-6.386-3.7c-.366.184-.718.395-1.083.62l-.647-.985c.38-.225.745-.42 1.097-.604l.633.97zm2.883 6.33c.252-.323.548-.646.87-.942l.634.957c-.31.323-.59.647-.83 1L14.77 8.72zm-2.04 4.53c.112-.506.24-1.027.422-1.547l1.012.802c-.14.548-.24 1.097-.295 1.645l-1.14-.9zM6.57 6.57c-.34.35-.662.73-.958 1.11L4.53 6.752c.323-.352.674-.704 1.04-1.055l1 .872zm-4.25 6.286c-.224.52-.52 1.21-.702 1.688L0 13.954c.14-.38.436-1.084.703-1.69l1.618.592zm10.2 3.967l1.518.548c.084.663.21 1.28.337 1.83l-1.688-.605c-.07-.422-.14-1.027-.168-1.772z"/></g></svg>
<g fill="none" fill-rule="evenodd" transform="translate(0 -6)">
<rect width="32" height="32"/>
<path fill="#C00" fill-rule="nonzero" d="M0.984615385,25.636044 C0.984615385,25.636044 1.40659341,21.4725275 4.36043956,16.5494505 C7.31428571,11.6263736 12.3498901,7.8989011 16.4430769,7.53318681 C24.5872527,6.71736264 31.9015385,14.0175824 31.9015385,14.0175824 C31.9015385,14.0175824 31.6624176,14.1863736 31.4092308,14.3973626 C23.4197802,8.48967033 18.5389011,11.2747253 17.0057143,12.0202198 C9.97274725,15.9446154 12.0967033,25.636044 12.0967033,25.636044 L0.984615385,25.636044 Z M24.1371429,8.32087912 C23.687033,8.13802198 23.2369231,7.96923077 22.7727473,7.81450549 L22.829011,6.88615385 C23.7151648,7.13934066 24.0668132,7.30813187 24.1934066,7.37846154 L24.1371429,8.32087912 Z M22.8008791,11.3028571 C23.250989,11.330989 23.7151648,11.3872527 24.1934066,11.4857143 L24.1371429,12.3578022 C23.672967,12.2593407 23.2087912,12.2030769 22.7446154,12.189011 L22.8008791,11.3028571 Z M17.5964835,6.91428571 C17.1885714,6.91428571 16.7806593,6.92835165 16.3727473,6.97054945 L16.1054945,6.14065934 C16.5696703,6.0843956 17.0197802,6.05626374 17.4558242,6.05626374 L17.7371429,6.91428571 C17.6949451,6.91428571 17.6386813,6.91428571 17.5964835,6.91428571 Z M18.2716484,12.0905495 C18.6232967,11.9358242 19.0312088,11.7810989 19.5094505,11.6404396 L19.8189011,12.5687912 C19.410989,12.6953846 19.0030769,12.8641758 18.5951648,13.0610989 L18.2716484,12.0905495 Z M11.8857143,8.39120879 C11.52,8.57406593 11.1683516,8.78505495 10.8026374,9.01010989 L10.1556044,8.02549451 C10.5353846,7.80043956 10.9010989,7.60351648 11.2527473,7.42065934 L11.8857143,8.39120879 Z M14.7692308,14.7208791 C15.0224176,14.3973626 15.3178022,14.0738462 15.6413187,13.7784615 L16.2742857,14.7349451 C15.9648352,15.0584615 15.6835165,15.381978 15.4443956,15.7336264 L14.7692308,14.7208791 Z M12.7296703,19.2501099 C12.8421978,18.7437363 12.9687912,18.2232967 13.1516484,17.7028571 L14.1643956,18.5046154 C14.0237363,19.0531868 13.9252747,19.6017582 13.869011,20.1503297 L12.7296703,19.2501099 Z M6.56879121,12.5687912 C6.23120879,12.9204396 5.90769231,13.3002198 5.61230769,13.68 L4.52923077,12.7516484 C4.85274725,12.4 5.2043956,12.0483516 5.57010989,11.6967033 L6.56879121,12.5687912 Z M2.32087912,18.8562637 C2.09582418,19.3767033 1.80043956,20.0659341 1.61758242,20.5441758 L0,19.9534066 C0.140659341,19.5736264 0.436043956,18.8703297 0.703296703,18.2654945 L2.32087912,18.8562637 Z M12.5186813,22.8228571 L14.0378022,23.3714286 C14.1221978,24.0325275 14.2487912,24.6514286 14.3753846,25.2 L12.6874725,24.5951648 C12.6171429,24.1731868 12.5468132,23.5683516 12.5186813,22.8228571 Z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" class="btn-template-icon icon-java-spring"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" class="btn-template-icon icon-java-spring"><g fill="none" fill-rule="evenodd"><path d="M0 0h32v32H0z"/><path fill="#70AD51" d="M5.466 27.993c.586.473 1.446.385 1.918-.202.475-.585.386-1.445-.2-1.92-.585-.474-1.444-.383-1.92.202-.45.555-.392 1.356.115 1.844l-.266-.234C1.972 24.762 0 20.597 0 15.978 0 7.168 7.168 0 15.98 0c4.48 0 8.53 1.857 11.435 4.836.66-.898 1.232-1.902 1.7-3.015 2.036 6.118 3.233 11.26 2.795 15.31-.592 8.274-7.508 14.83-15.93 14.83-3.912 0-7.496-1.416-10.276-3.757l-.238-.21zm23.58-4.982c4.01-5.336 1.775-13.965-.085-19.48-1.657 3.453-5.738 6.094-9.262 6.93-3.303.788-6.226.142-9.283 1.318-6.97 2.68-6.86 10.992-3.02 12.86.002 0 .23.124.227.12 0-.002 5.644-1.122 8.764-2.274 4.56-1.684 9.566-5.835 11.213-10.657-.877 5.015-5.182 9.84-9.507 12.056-2.302 1.182-4.092 1.445-7.88 2.756-.464.158-.828.314-.828.314.96-.16 1.917-.212 1.917-.212 5.393-.255 13.807 1.516 17.745-3.73z"/></g></svg>
<g fill="none" fill-rule="evenodd">
<rect width="32" height="32"/>
<path fill="#70AD51" d="M5.46647617,27.9932117 C6.0517027,28.4658996 6.91159892,28.3777063 7.38425926,27.7914452 C7.85922261,27.2048452 7.76991326,26.3449044 7.18398981,25.8699411 C6.59874295,25.3956543 5.74015536,25.4869934 5.26383884,26.0722403 C4.81393367,26.6267596 4.87238621,27.4284565 5.37913494,27.9159868 L5.11431334,27.6818383 C1.97157151,24.7616933 0,20.5966301 0,15.9782542 C0,7.16842834 7.16775175,0 15.9796074,0 C20.4586065,0 24.5113565,1.8565519 27.4145869,4.8362365 C28.0749348,3.93840692 28.6466499,2.93435335 29.115524,1.82069284 C31.1513712,7.93770658 32.3482517,13.0811131 31.909824,17.1311567 C31.3178113,25.4044499 24.4017495,31.9585382 15.9796074,31.9585382 C12.0682639,31.9585382 8.48438805,30.5444735 5.7042963,28.2034861 L5.46647617,27.9932117 Z M29.0471888,23.0106888 C33.0546075,17.6737787 30.8211972,9.04527781 28.9612624,3.529749 C27.3029502,6.98304378 23.2217836,9.62375882 19.6981239,10.4613722 C16.3950312,11.2482417 13.4715032,10.6021021 10.4153644,11.7780085 C3.44517575,14.457289 3.55613585,22.7698242 7.39373146,24.6365249 C7.39711439,24.6392312 7.62444728,24.7616933 7.62174094,24.7576338 C7.62309411,24.7562806 13.2658211,23.6358542 16.3862356,22.4843049 C20.9450718,20.7996058 25.9524846,16.6494275 27.5986182,11.8273993 C26.723116,16.8415779 22.4179995,21.6669891 18.093262,23.8828081 C15.7908399,25.0648038 14.0005934,25.3279957 10.2123886,26.6385428 C9.74892722,26.798217 9.38492397,26.9538318 9.38492397,26.9538318 C10.3463526,26.7948341 11.301692,26.7420604 11.301692,26.7420604 C16.6954354,26.4869875 25.1087819,28.2582896 29.0471888,23.0106888 Z"/>
</g>
</svg>
...@@ -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
scope(path: 'groups/*group_id', scope(path: 'groups/*group_id',
module: :groups, module: :groups,
......
# 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
# 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.
......
...@@ -13,7 +13,10 @@ ...@@ -13,7 +13,10 @@
- [Generate a changelog entry with `bin/changelog`](changelog.md) - [Generate a changelog entry with `bin/changelog`](changelog.md)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed. - [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
- [Limit conflicts with EE when developing on CE](limit_ee_conflicts.md) - [Limit conflicts with EE when developing on CE](limit_ee_conflicts.md)
<<<<<<< HEAD
- [Guidelines for implementing Enterprise Edition feature](ee_features.md) - [Guidelines for implementing Enterprise Edition feature](ee_features.md)
=======
>>>>>>> upstream/master
## UX and frontend guides ## UX and frontend guides
...@@ -58,18 +61,29 @@ ...@@ -58,18 +61,29 @@
- [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)
<<<<<<< HEAD
- [Post Deployment Migrations](post_deployment_migrations.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) - [Serializing data](serializing_data.md)
- [Polymorphic associations](polymorphic_associations.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) - [Background Migrations](background_migrations.md)
=======
- [Foreign keys & associations](foreign_keys.md)
- [Single table inheritance](single_table_inheritance.md)
- [Polymorphic associations](polymorphic_associations.md)
- [Serializing data](serializing_data.md)
- [Hash indexes](hash_indexes.md)
>>>>>>> upstream/master
- [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)
<<<<<<< HEAD
- [Hash indexes](hash_indexes.md) - [Hash indexes](hash_indexes.md)
- [Swapping Tables](swapping_tables.md) - [Swapping Tables](swapping_tables.md)
=======
>>>>>>> upstream/master
## Documentation guides ## Documentation 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,11 @@ run JavaScript tests, so you can either run unit tests (e.g. test a single ...@@ -67,7 +67,11 @@ 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).
<<<<<<< HEAD
## System tests or Feature tests ## System tests or Feature tests
=======
## System tests or feature tests
>>>>>>> upstream/master
Formal definition: https://en.wikipedia.org/wiki/System_testing. Formal definition: https://en.wikipedia.org/wiki/System_testing.
...@@ -108,7 +112,11 @@ The reasons why we should follow these best practices are as follows: ...@@ -108,7 +112,11 @@ 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
<<<<<<< HEAD
## Black-box tests or End-to-end tests ## Black-box tests or End-to-end tests
=======
## Black-box tests or end-to-end tests
>>>>>>> upstream/master
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
...@@ -131,10 +139,13 @@ always in-sync with the codebase. ...@@ -131,10 +139,13 @@ always in-sync with the codebase.
[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa [GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa
[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa [part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa
<<<<<<< HEAD
## EE-specific tests ## EE-specific tests
EE-specific tests follows the same organization, but under the `spec/ee` folder. EE-specific tests follows the same organization, but under the `spec/ee` folder.
=======
>>>>>>> upstream/master
## How to test at the correct level? ## How to test at the correct level?
As many things in life, deciding what to test at each level of testing is a As many things in life, deciding what to test at each level of testing is a
......
# 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:
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment