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:
- chrome_debug.log
- coverage-javascript/
codeclimate:
codequality:
<<: *except-docs
<<: *pull-cache
before_script: []
......
......@@ -414,7 +414,7 @@ group :ed25519 do
end
# 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
......
......@@ -297,7 +297,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.39.0)
gitaly-proto (0.41.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -1062,7 +1062,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.39.0)
gitaly-proto (~> 0.41.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
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 { convertPermissionToBoolean } from './lib/utils/common_utils';
window.BuildArtifacts = (function() {
function BuildArtifacts() {
export default class BuildArtifacts {
constructor() {
this.disablePropagation();
this.setupEntryClick();
this.setupTooltips();
}
BuildArtifacts.prototype.disablePropagation = function() {
$('.top-block').on('click', '.download', function(e) {
// eslint-disable-next-line class-methods-use-this
disablePropagation() {
$('.top-block').on('click', '.download', function (e) {
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();
});
};
BuildArtifacts.prototype.setupEntryClick = function() {
return $('.tree-holder').on('click', 'tr[data-link]', function(e) {
}
// eslint-disable-next-line class-methods-use-this
setupEntryClick() {
return $('.tree-holder').on('click', 'tr[data-link]', function () {
visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink));
});
};
BuildArtifacts.prototype.setupTooltips = function() {
}
// eslint-disable-next-line class-methods-use-this
setupTooltips() {
$('.js-artifact-tree-tooltip').tooltip({
placement: 'bottom',
// Stop the tooltip from hiding when we stop hovering the element directly
......@@ -41,7 +41,5 @@ window.BuildArtifacts = (function() {
.on('mouseleave', (e) => {
$(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() {
$('.reveal-variables').off('click').on('click', function() {
$('.js-build-variables').toggle();
$(this).hide();
});
});
export default function handleRevealVariables() {
$('.js-reveal-variables')
.off('click')
.on('click', function () {
$('.js-build-variables').toggle();
$(this).hide();
});
}
window.gl = window.gl || {};
class CILintEditor {
export default class CILintEditor {
constructor() {
this.editor = window.ace.edit('ci-editor');
this.textarea = document.querySelector('#content');
......@@ -13,5 +10,3 @@ class CILintEditor {
});
}
}
gl.CILintEditor = CILintEditor;
......@@ -12,7 +12,8 @@
/* global NotificationsDropdown */
/* global GroupAvatar */
/* global LineHighlighter */
/* global BuildArtifacts */
import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
/* global GroupsSelect */
/* global Search */
/* global Admin */
......@@ -29,7 +30,8 @@
/* global ProjectNew */
/* global ProjectShow */
/* global ProjectImport */
/* global Labels */
import Labels from './labels';
import LabelManager from './label_manager';
/* global Shortcuts */
/* global ShortcutsFindFile */
/* global Sidebar */
......@@ -82,6 +84,7 @@ import initChangesDropdown from './init_changes_dropdown';
import AbuseReports from './abuse_reports';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
import AjaxLoadingSpinner from './ajax_loading_spinner';
import U2FAuthenticate from './u2f/authenticate';
// EE-only
import ApproversSelect from './approvers_select';
......@@ -100,8 +103,8 @@ import initGroupAnalytics from './init_group_analytics';
}
Dispatcher.prototype.initPageScripts = function() {
var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
page = $('body').attr('data-page');
var path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
const page = $('body').attr('data-page');
if (!page) {
return false;
}
......@@ -503,7 +506,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'groups:labels:index':
case 'projects:labels:index':
if ($('.prioritized-labels').length) {
new gl.LabelManager();
new LabelManager();
}
$('.label-subscription').each((i, el) => {
const $el = $(el);
......@@ -564,7 +567,7 @@ import initGroupAnalytics from './init_group_analytics';
break;
case 'ci:lints:create':
case 'ci:lints:show':
new gl.CILintEditor();
new CILintEditor();
break;
case 'users:show':
new UserCallout();
......@@ -611,14 +614,16 @@ import initGroupAnalytics from './init_group_analytics';
case 'sessions':
case 'omniauth_callbacks':
if (!gon.u2f) break;
gl.u2fAuthenticate = new gl.U2FAuthenticate(
const u2fAuthenticate = new U2FAuthenticate(
$('#js-authenticate-u2f'),
'#js-login-u2f-form',
gon.u2f,
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'),
);
gl.u2fAuthenticate.start();
u2fAuthenticate.start();
// needed in rspec
gl.u2fAuthenticate = u2fAuthenticate;
case 'admin':
new Admin();
switch (path[1]) {
......
......@@ -237,9 +237,12 @@ window.DropzoneInput = (function() {
};
const insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) {
const $child = $(child);
$child.val(function(index, val) {
return val.replace(`{{${filename}}}`, url);
});
$child.trigger('change');
};
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');
$todoPendingCount.text(gl.text.highCountTrim(count));
$todoPendingCount.toggleClass('hidden', count === 0);
/**
* Updates todo counter when todos are toggled.
* When count is 0, we hide the badge.
*
* @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 bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
window.Build = (function () {
Build.timeout = null;
Build.state = null;
function Build(options) {
export default class Job {
constructor(options) {
this.timeout = null;
this.state = null;
this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl;
......@@ -19,9 +16,7 @@ window.Build = (function () {
this.$document = $(document);
this.logBytes = 0;
this.hasBeenScrolled = false;
this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh');
......@@ -33,7 +28,7 @@ window.Build = (function () {
this.$scrollTopBtn = $('.js-scroll-up');
this.$scrollBottomBtn = $('.js-scroll-down');
clearTimeout(Build.timeout);
clearTimeout(this.timeout);
this.initSidebar();
this.populateJobs(this.buildStage);
......@@ -85,7 +80,7 @@ window.Build = (function () {
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 support sticky, then we allow the browser to handle it, if not
......@@ -100,13 +95,14 @@ window.Build = (function () {
top: offsetTop,
},
});
};
}
Build.prototype.canScroll = function () {
// eslint-disable-next-line class-methods-use-this
canScroll() {
return $(document).height() > $(window).height();
};
}
Build.prototype.toggleScroll = function () {
toggleScroll() {
const currentPosition = $(document).scrollTop();
const scrollHeight = $(document).height();
......@@ -119,7 +115,7 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} 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.$scrollBottomBtn, false);
......@@ -133,38 +129,40 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, true);
}
};
}
Build.prototype.scrollDown = function () {
// eslint-disable-next-line class-methods-use-this
scrollDown() {
$(document).scrollTop($(document).height());
};
}
Build.prototype.scrollToBottom = function () {
scrollToBottom() {
this.scrollDown();
this.hasBeenScrolled = true;
this.toggleScroll();
};
}
Build.prototype.scrollToTop = function () {
scrollToTop() {
$(document).scrollTop(0);
this.hasBeenScrolled = true;
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;
$button.prop('disabled', disable);
};
}
Build.prototype.toggleScrollAnimation = function (toggle) {
toggleScrollAnimation(toggle) {
this.$scrollBottomBtn.toggleClass('animate', toggle);
};
}
Build.prototype.initSidebar = function () {
initSidebar() {
this.$sidebar = $('.js-build-sidebar');
};
}
Build.prototype.getBuildTrace = function () {
getBuildTrace() {
return $.ajax({
url: `${this.pageUrl}/trace.json`,
data: { state: this.state },
......@@ -204,7 +202,7 @@ window.Build = (function () {
this.toggleScrollAnimation(false);
}
Build.timeout = setTimeout(() => {
this.timeout = setTimeout(() => {
this.getBuildTrace();
}, 4000);
} else {
......@@ -225,14 +223,14 @@ window.Build = (function () {
}
})
.then(() => this.toggleScroll());
};
Build.prototype.shouldHideSidebarForViewport = function () {
}
// eslint-disable-next-line class-methods-use-this
shouldHideSidebarForViewport() {
const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
};
}
Build.prototype.toggleSidebar = function (shouldHide) {
toggleSidebar(shouldHide) {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header');
......@@ -249,17 +247,17 @@ window.Build = (function () {
} else {
$toggleButton.removeClass('hidden');
}
};
}
Build.prototype.sidebarOnResize = function () {
sidebarOnResize() {
this.toggleSidebar(this.shouldHideSidebarForViewport());
};
}
Build.prototype.sidebarOnClick = function () {
sidebarOnClick() {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
};
Build.prototype.updateArtifactRemoveDate = function () {
}
// eslint-disable-next-line class-methods-use-this, consistent-return
updateArtifactRemoveDate() {
const $date = $('.js-artifacts-remove');
if ($date.length) {
const date = $date.text();
......@@ -267,23 +265,21 @@ window.Build = (function () {
gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
);
}
};
Build.prototype.populateJobs = function (stage) {
}
// eslint-disable-next-line class-methods-use-this
populateJobs(stage) {
$('.build-job').hide();
$(`.build-job[data-stage="${stage}"]`).show();
};
Build.prototype.updateStageDropdownText = function (stage) {
}
// eslint-disable-next-line class-methods-use-this
updateStageDropdownText(stage) {
$('.stage-selection').text(stage);
};
}
Build.prototype.updateDropdown = function (e) {
updateDropdown(e) {
e.preventDefault();
const stage = e.currentTarget.text;
this.updateStageDropdownText(stage);
this.populateJobs(stage);
};
return Build;
})();
}
}
......@@ -5,7 +5,8 @@ import Flash from '../flash';
import Poll from '../lib/utils/poll';
import JobStore from './stores/job_store';
import JobService from './services/job_service';
import '../build';
import Job from '../job';
import handleRevealVariables from '../build_variables';
export default class JobMediator {
constructor(options = {}) {
......@@ -20,7 +21,8 @@ export default class JobMediator {
}
initBuildClass() {
this.build = new Build();
this.build = new Job();
handleRevealVariables();
}
fetchJob() {
......
......@@ -2,6 +2,7 @@
/* global Sortable */
import Flash from './flash';
<<<<<<< HEAD
((global) => {
class LabelManager {
......@@ -20,106 +21,122 @@ import Flash from './flash';
});
this.bindEvents();
}
=======
>>>>>>> upstream/master
bindEvents() {
this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick);
return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
}
export default class LabelManager {
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
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) {
e.preventDefault();
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);
}
bindEvents() {
this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick);
return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
}
onButtonActionClick(e) {
e.stopPropagation();
$(e.currentTarget).tooltip('hide');
}
onTogglePriorityClick(e) {
e.preventDefault();
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) {
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
}
onButtonActionClick(e) {
e.stopPropagation();
$(e.currentTarget).tooltip('hide');
}
toggleLabelPriority($label, action, persistState) {
if (persistState == null) {
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));
}
toggleEmptyState($label, $btn, action) {
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
}
onPrioritySortUpdate() {
const xhr = this.savePrioritySort();
return xhr.fail(function() {
return new Flash(this.errorMessage, 'alert');
});
toggleLabelPriority($label, action, persistState) {
if (persistState == null) {
persistState = true;
}
savePrioritySort() {
return $.post({
url: this.prioritizedLabels.data('url'),
data: {
label_ids: this.getSortedLabelsIds()
}
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));
}
rollbackLabelPosition($label, originalAction) {
const action = originalAction === 'remove' ? 'add' : 'remove';
this.toggleLabelPriority($label, action, false);
onPrioritySortUpdate() {
const xhr = this.savePrioritySort();
return xhr.fail(function() {
return new Flash(this.errorMessage, 'alert');
}
});
}
getSortedLabelsIds() {
const sortedIds = [];
this.prioritizedLabels.find('> li').each(function() {
const id = $(this).data('id');
savePrioritySort() {
return $.post({
url: this.prioritizedLabels.data('url'),
data: {
label_ids: this.getSortedLabelsIds()
}
});
}
if (id) {
sortedIds.push(id);
}
});
return sortedIds;
}
rollbackLabelPosition($label, originalAction) {
const action = originalAction === 'remove' ? 'add' : 'remove';
this.toggleLabelPriority($label, action, false);
return new Flash(this.errorMessage, 'alert');
}
gl.LabelManager = LabelManager;
})(window.gl || (window.gl = {}));
getSortedLabelsIds() {
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 */
(function() {
this.Labels = (function() {
function Labels() {
this.setSuggestedColor = this.setSuggestedColor.bind(this);
this.updateColorPreview = this.updateColorPreview.bind(this);
var form;
form = $('.label-form');
this.cleanBinding();
this.addBinding();
this.updateColorPreview();
}
export default class Labels {
constructor() {
this.setSuggestedColor = this.setSuggestedColor.bind(this);
this.updateColorPreview = this.updateColorPreview.bind(this);
this.cleanBinding();
this.addBinding();
this.updateColorPreview();
}
Labels.prototype.addBinding = function() {
$(document).on('click', '.suggest-colors a', this.setSuggestedColor);
return $(document).on('input', 'input#label_color', this.updateColorPreview);
};
addBinding() {
$(document).on('click', '.suggest-colors a', this.setSuggestedColor);
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() {
$(document).off('click', '.suggest-colors a');
return $(document).off('input', 'input#label_color');
};
Labels.prototype.updateColorPreview = function() {
var previewColor;
previewColor = $('input#label_color').val();
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);
// Updates the preview color with a click on a suggested color
setSuggestedColor(e) {
const 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();
}
}
/* 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';
......@@ -13,9 +13,17 @@ if ((base = w.gl).text == null) {
gl.text.addDelimiter = function(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;
};
}
gl.text.randomString = function() {
return Math.random().toString(36).substring(7);
};
......
......@@ -46,26 +46,19 @@ import './lib/utils/url_utility';
// behaviors
import './behaviors/';
// u2f
import './u2f/authenticate';
import './u2f/error';
import './u2f/register';
import './u2f/util';
// everything else
import './activities';
import './admin';
<<<<<<< HEAD
import './api';
import './ajax_loading_spinner';
=======
>>>>>>> upstream/master
import './aside';
import './autosave';
import loadAwardsHandler from './awards_handler';
import bp from './breakpoints';
import './broadcast_message';
import './build';
import './build_artifacts';
import './build_variables';
import './ci_lint_editor';
import './commits';
import './compare';
import './compare_autocomplete';
......@@ -91,8 +84,6 @@ import './issuable_context';
import './issuable_form';
import './issue';
import './issue_status_select';
import './label_manager';
import './labels';
import './labels_select';
import './layout_nav';
import LazyLoader from './lazy_loader';
......@@ -130,7 +121,6 @@ import './right_sidebar';
import './search';
import './search_autocomplete';
import './smart_interval';
import './star';
import './subscription';
import './subscription_select';
import initBreadcrumbs from './breadcrumb';
......
......@@ -29,6 +29,12 @@ const bindEvents = () => {
const $newProjectForm = $('#new_project');
const $projectImportUrl = $('#project_import_url');
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) {
return;
......@@ -48,6 +54,40 @@ const bindEvents = () => {
$('.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', () => {
$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 */
=======
>>>>>>> upstream/master
import Flash from './flash';
import { __, s__ } from './locale';
export default class Star {
constructor() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
var $starIcon, $starSpan, $this, toggleStar;
$this = $(this);
$starSpan = $this.find('span');
$starIcon = $this.find('i');
toggleStar = function(isStarred) {
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star'));
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
$starSpan.addClass('starred').text(__('Unstar'));
$starIcon.removeClass('fa-star-o').addClass('fa-star');
$('.project-home-panel .toggle-star')
.on('ajax:success', function handleSuccess(e, data) {
const $this = $(this);
const $starSpan = $this.find('span');
const $starIcon = $this.find('i');
function toggleStar(isStarred) {
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star'));
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
$starSpan.addClass('starred').text(__('Unstar'));
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
}
};
toggleStar($starSpan.hasClass('starred'));
}).on('ajax:error', function(e, xhr, status, error) {
new Flash('Star toggle failed. Try again later.', 'alert');
});
toggleStar($starSpan.hasClass('starred'));
})
.on('ajax:error', () => {
Flash('Star toggle failed. Try again later.', 'alert');
});
}
}
/* global U2FRegister */
import U2FRegister from './u2f/register';
document.addEventListener('DOMContentLoaded', () => {
const twoFactorNode = document.querySelector('.js-two-factor-auth');
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 U2FError */
/* global U2FUtil */
import _ from 'underscore';
import isU2FSupported from './util';
import U2FError from './error';
// Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
//
// State Flow #1: setup -> in_progress -> authenticated -> POST to server
// State Flow #2: setup -> in_progress -> error -> setup
(function() {
const global = window.gl || (window.gl = {});
global.U2FAuthenticate = (function() {
function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) {
this.container = container;
this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderAuthenticated = this.renderAuthenticated.bind(this);
this.renderError = this.renderError.bind(this);
this.renderInProgress = this.renderInProgress.bind(this);
this.renderTemplate = this.renderTemplate.bind(this);
this.authenticate = this.authenticate.bind(this);
this.start = this.start.bind(this);
this.appId = u2fParams.app_id;
this.challenge = u2fParams.challenge;
this.form = form;
this.fallbackButton = fallbackButton;
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');
});
export default class U2FAuthenticate {
constructor(container, form, u2fParams, fallbackButton, fallbackUI) {
this.container = container;
this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderAuthenticated = this.renderAuthenticated.bind(this);
this.renderError = this.renderError.bind(this);
this.renderInProgress = this.renderInProgress.bind(this);
this.renderTemplate = this.renderTemplate.bind(this);
this.authenticate = this.authenticate.bind(this);
this.start = this.start.bind(this);
this.appId = u2fParams.app_id;
this.challenge = u2fParams.challenge;
this.form = form;
this.fallbackButton = fallbackButton;
this.fallbackUI = fallbackUI;
if (this.fallbackButton) {
this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this));
}
U2FAuthenticate.prototype.start = function() {
if (U2FUtil.isU2FSupported()) {
return this.renderInProgress();
} else {
return this.renderNotSupported();
}
};
// 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
this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge'));
U2FAuthenticate.prototype.authenticate = function() {
return u2f.sign(this.appId, this.challenge, this.signRequests, (function(_this) {
return function(response) {
var error;
if (response.errorCode) {
error = new U2FError(response.errorCode, 'authenticate');
return _this.renderError(error);
} else {
return _this.renderAuthenticated(JSON.stringify(response));
}
};
})(this), 10);
this.templates = {
notSupported: '#js-authenticate-u2f-not-supported',
setup: '#js-authenticate-u2f-setup',
inProgress: '#js-authenticate-u2f-in-progress',
error: '#js-authenticate-u2f-error',
authenticated: '#js-authenticate-u2f-authenticated',
};
}
// Rendering #
U2FAuthenticate.prototype.templates = {
"notSupported": "#js-authenticate-u2f-not-supported",
"setup": '#js-authenticate-u2f-setup',
"inProgress": '#js-authenticate-u2f-in-progress',
"error": '#js-authenticate-u2f-error',
"authenticated": '#js-authenticate-u2f-authenticated'
};
start() {
if (isU2FSupported()) {
return this.renderInProgress();
}
return this.renderNotSupported();
}
U2FAuthenticate.prototype.renderTemplate = function(name, params) {
var template, templateString;
templateString = $(this.templates[name]).html();
template = _.template(templateString);
return this.container.html(template(params));
};
authenticate() {
return u2f.sign(this.appId, this.challenge, this.signRequests, (function (_this) {
return function (response) {
if (response.errorCode) {
const error = new U2FError(response.errorCode, 'authenticate');
return _this.renderError(error);
}
return _this.renderAuthenticated(JSON.stringify(response));
};
})(this), 10);
}
U2FAuthenticate.prototype.renderInProgress = function() {
this.renderTemplate('inProgress');
return this.authenticate();
};
renderTemplate(name, params) {
const templateString = $(this.templates[name]).html();
const template = _.template(templateString);
return this.container.html(template(params));
}
U2FAuthenticate.prototype.renderError = function(error) {
this.renderTemplate('error', {
error_message: error.message(),
error_code: error.errorCode
});
return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress);
};
renderInProgress() {
this.renderTemplate('inProgress');
return this.authenticate();
}
U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) {
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');
};
renderError(error) {
this.renderTemplate('error', {
error_message: error.message(),
error_code: error.errorCode,
});
return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress);
}
U2FAuthenticate.prototype.renderNotSupported = function() {
return this.renderTemplate('notSupported');
};
renderAuthenticated(deviceResponse) {
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() {
this.fallbackButton.classList.add('hidden');
this.container[0].classList.add('hidden');
this.fallbackUI.classList.remove('hidden');
};
renderNotSupported() {
return this.renderTemplate('notSupported');
}
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 */
/* global u2f */
export default class U2FError {
constructor(errorCode, u2fFlowType) {
this.errorCode = errorCode;
this.message = this.message.bind(this);
this.httpsDisabled = window.location.protocol !== 'https:';
this.u2fFlowType = u2fFlowType;
}
(function() {
this.U2FError = (function() {
function U2FError(errorCode, u2fFlowType) {
this.errorCode = errorCode;
this.message = this.message.bind(this);
this.httpsDisabled = window.location.protocol !== 'https:';
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.';
message() {
if (this.errorCode === window.u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) {
return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.';
} else if (this.errorCode === window.u2f.ErrorCodes.DEVICE_INELIGIBLE) {
if (this.u2fFlowType === 'authenticate') {
return 'This device has not been registered with us.';
}
return "There was a problem communicating with your device.";
};
return U2FError;
})();
}).call(window);
if (this.u2fFlowType === 'register') {
return 'This device has already been registered with us.';
}
}
return 'There was a problem communicating with your device.';
}
}
/* 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 U2FError */
/* global U2FUtil */
import _ from 'underscore';
import isU2FSupported from './util';
import U2FError from './error';
// Register U2F (universal 2nd factor) devices for users to authenticate with.
//
// State Flow #1: setup -> in_progress -> registered -> POST to server
// State Flow #2: setup -> in_progress -> error -> setup
(function() {
this.U2FRegister = (function() {
function U2FRegister(container, u2fParams) {
this.container = container;
this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderRegistered = this.renderRegistered.bind(this);
this.renderError = this.renderError.bind(this);
this.renderInProgress = this.renderInProgress.bind(this);
this.renderSetup = this.renderSetup.bind(this);
this.renderTemplate = this.renderTemplate.bind(this);
this.register = this.register.bind(this);
this.start = this.start.bind(this);
this.appId = u2fParams.app_id;
this.registerRequests = u2fParams.register_requests;
this.signRequests = u2fParams.sign_requests;
}
export default class U2FRegister {
constructor(container, u2fParams) {
this.container = container;
this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderRegistered = this.renderRegistered.bind(this);
this.renderError = this.renderError.bind(this);
this.renderInProgress = this.renderInProgress.bind(this);
this.renderSetup = this.renderSetup.bind(this);
this.renderTemplate = this.renderTemplate.bind(this);
this.register = this.register.bind(this);
this.start = this.start.bind(this);
this.appId = u2fParams.app_id;
this.registerRequests = u2fParams.register_requests;
this.signRequests = u2fParams.sign_requests;
U2FRegister.prototype.start = function() {
if (U2FUtil.isU2FSupported()) {
return this.renderSetup();
} else {
return this.renderNotSupported();
}
this.templates = {
notSupported: '#js-register-u2f-not-supported',
setup: '#js-register-u2f-setup',
inProgress: '#js-register-u2f-in-progress',
error: '#js-register-u2f-error',
registered: '#js-register-u2f-registered',
};
}
U2FRegister.prototype.register = function() {
return u2f.register(this.appId, this.registerRequests, this.signRequests, (function(_this) {
return function(response) {
var error;
if (response.errorCode) {
error = new U2FError(response.errorCode, 'register');
return _this.renderError(error);
} else {
return _this.renderRegistered(JSON.stringify(response));
}
};
})(this), 10);
};
start() {
if (isU2FSupported()) {
return this.renderSetup();
}
return this.renderNotSupported();
}
// Rendering #
U2FRegister.prototype.templates = {
"notSupported": "#js-register-u2f-not-supported",
"setup": '#js-register-u2f-setup',
"inProgress": '#js-register-u2f-in-progress',
"error": '#js-register-u2f-error',
"registered": '#js-register-u2f-registered'
};
register() {
return u2f.register(this.appId, this.registerRequests, this.signRequests, (function (_this) {
return function (response) {
if (response.errorCode) {
const error = new U2FError(response.errorCode, 'register');
return _this.renderError(error);
}
return _this.renderRegistered(JSON.stringify(response));
};
})(this), 10);
}
U2FRegister.prototype.renderTemplate = function(name, params) {
var template, templateString;
templateString = $(this.templates[name]).html();
template = _.template(templateString);
return this.container.html(template(params));
};
renderTemplate(name, params) {
const templateString = $(this.templates[name]).html();
const template = _.template(templateString);
return this.container.html(template(params));
}
U2FRegister.prototype.renderSetup = function() {
this.renderTemplate('setup');
return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress);
};
renderSetup() {
this.renderTemplate('setup');
return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress);
}
U2FRegister.prototype.renderInProgress = function() {
this.renderTemplate('inProgress');
return this.register();
};
renderInProgress() {
this.renderTemplate('inProgress');
return this.register();
}
U2FRegister.prototype.renderError = function(error) {
this.renderTemplate('error', {
error_message: error.message(),
error_code: error.errorCode
});
return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
};
renderError(error) {
this.renderTemplate('error', {
error_message: error.message(),
error_code: error.errorCode,
});
return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
}
U2FRegister.prototype.renderRegistered = function(deviceResponse) {
this.renderTemplate('registered');
// Prefer to do this instead of interpolating using Underscore templates
// because of JSON escaping issues.
return this.container.find("#js-device-response").val(deviceResponse);
};
U2FRegister.prototype.renderNotSupported = function() {
return this.renderTemplate('notSupported');
};
renderRegistered(deviceResponse) {
this.renderTemplate('registered');
// Prefer to do this instead of interpolating using Underscore templates
// because of JSON escaping issues.
return this.container.find('#js-device-response').val(deviceResponse);
}
return U2FRegister;
})();
}).call(window);
renderNotSupported() {
return this.renderTemplate('notSupported');
}
}
/* eslint-disable func-names, space-before-function-paren, wrap-iife */
(function() {
this.U2FUtil = (function() {
function U2FUtil() {}
U2FUtil.isU2FSupported = function() {
return window.u2f;
};
return U2FUtil;
})();
}).call(window);
export default function isU2FSupported() {
return window.u2f;
}
......@@ -37,6 +37,7 @@
@import "framework/new-sidebar";
@import "framework/tables";
@import "framework/notes";
@import "framework/tabs";
@import "framework/timeline";
@import "framework/tooltips";
@import "framework/typography";
......
......@@ -749,7 +749,7 @@
margin-bottom: $dropdown-vertical-offset;
}
li:not(.dropdown-bold-header) {
li {
display: block;
padding: 0 1px;
......
......@@ -161,9 +161,10 @@
}
}
.dropdown-bold-header {
li.dropdown-bold-header {
color: $gl-text-color-secondary;
font-size: 12px;
padding: 0 16px;
}
.navbar-collapse {
......
......@@ -61,6 +61,7 @@
border: 1px solid $dropdown-border-color;
min-width: 175px;
color: $gl-text-color;
z-index: 999;
}
.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;
$perf-bar-bucket-box-shadow-from: rgba($white-light, .2);
$perf-bar-bucket-box-shadow-to: rgba($black, .25);
/*
Project Templates Icons
*/
$rails: #c00;
$node: #353535;
$java: #70ad51;
/*
Issuable warning
*/
......
......@@ -54,12 +54,15 @@
.mr-widget-pipeline-graph {
display: inline-block;
vertical-align: middle;
margin-right: 4px;
.stage-cell .stage-container {
margin: 3px 3px 3px 0;
}
.stage-container:last-child {
margin-right: 0;
}
.dropdown-menu {
margin-top: 11px;
}
......
......@@ -3,41 +3,12 @@
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 {
.btn-create {
margin-right: 10px;
}
}
.project-member-tab-content {
padding: $gl-padding;
border: 1px solid $border-color;
border-top: 0;
margin-bottom: $gl-padding;
}
.member {
&.is-overriden {
.btn-ldap-override {
......
......@@ -236,9 +236,11 @@
}
.stage-cell {
@media (min-width: $screen-md-min) {
min-width: 148px;
margin-right: -4px;
&.table-section {
@media (min-width: $screen-md-min) {
min-width: 148px;
margin-right: -4px;
}
}
.mini-pipeline-graph-dropdown-toggle svg {
......
......@@ -56,7 +56,8 @@
border: 1px solid $border-color;
}
+ .select2 a {
+ .select2 a,
+ .btn-default {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
......@@ -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 {
.form-group {
margin-bottom: 5px;
margin-bottom: 0;
}
.import-buttons {
......@@ -575,10 +658,6 @@ a.deploy-project-label {
margin-right: 10px;
}
.blank-option {
min-width: 70px;
}
.btn-template-icon {
height: 24px;
width: inherit;
......@@ -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 {
margin-bottom: 10px;
padding-left: 0;
......@@ -619,10 +686,6 @@ a.deploy-project-label {
}
}
.project-templates-buttons .btn:last-child {
margin-right: 0;
}
.create-project-options {
display: flex;
......@@ -1099,6 +1162,12 @@ a.allowed-to-push {
min-width: 100px;
}
&.form-group {
@media (min-width: $screen-sm-min) {
margin-bottom: 0;
}
}
.select2-choice {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
......
......@@ -2,7 +2,8 @@ class Admin::RunnersController < Admin::ApplicationController
before_action :runner, except: :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.page(params[:page]).per(30)
@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
def index
@sort = params[:sort] || 'id_desc'
@sort = params[:sort] || 'created_desc'
@groups =
if params[:parent_id] && Group.supports_nested_groups?
......
......@@ -2,6 +2,7 @@ class GroupsController < Groups::ApplicationController
include IssuesAction
include MergeRequestsAction
include ParamsBackwardCompatibility
include PreviewMarkdown
respond_to :html
......
class Projects::WikisController < Projects::ApplicationController
include PreviewMarkdown
before_action :authorize_read_wiki!
before_action :authorize_create_wiki!, only: [:edit, :create, :history]
before_action :authorize_admin_wiki!, only: :destroy
......@@ -93,17 +95,6 @@ class Projects::WikisController < Projects::ApplicationController
def git_access
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
def load_project_wiki
......
class ProjectsController < Projects::ApplicationController
include IssuableCollections
include ExtractsPath
<<<<<<< HEAD
prepend EE::ProjectsController
=======
include PreviewMarkdown
>>>>>>> upstream/master
before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :project, except: [:index, :new, :create]
......@@ -261,18 +265,6 @@ class ProjectsController < Projects::ApplicationController
render json: options.to_json
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
# Render project landing depending of which features are available
......
......@@ -4,6 +4,7 @@ class SnippetsController < ApplicationController
include SpammableActions
include SnippetsActions
include RendersBlob
include PreviewMarkdown
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
......@@ -87,17 +88,6 @@ class SnippetsController < ApplicationController
redirect_to snippets_path, status: 302
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
def snippet
......
......@@ -4,8 +4,8 @@ module CompareHelper
to.present? &&
from != to &&
can?(current_user, :create_merge_request, project) &&
project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to)
project.repository.branch_exists?(from) &&
project.repository.branch_exists?(to)
end
def create_mr_path(from = params[:from], to = params[:to], project = @project)
......
......@@ -7,7 +7,12 @@ module GroupsHelper
can?(current_user, :change_share_with_group_lock, group)
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)
group = Group.find_by_full_path(group)
end
......@@ -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
output =
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
""
end
......
......@@ -10,6 +10,7 @@ module LazyImageTagHelper
unless options.delete(:lazy) == false
options[:data] ||= {}
options[:data][:src] = path_to_image(source)
options[:class] ||= ""
options[:class] << " lazy"
......
......@@ -11,7 +11,7 @@ module Avatarable
# 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.
host = asset_host.present? ? asset_host : gitlab_host
host = (asset_host.present? && (!respond_to?(:public?) || public?)) ? asset_host : gitlab_host
[host, avatar.url].join
end
......
......@@ -266,23 +266,22 @@ module Issuable
participants(user).include?(user)
end
def to_hook_data(user)
hook_data = {
object_kind: self.class.name.underscore,
user: user.hook_attrs,
project: project.hook_attrs,
object_attributes: hook_attrs,
labels: labels.map(&:hook_attrs),
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
if self.is_a?(Issue)
hook_data[:assignees] = assignees.map(&:hook_attrs) if assignees.any?
else
hook_data[:assignee] = assignee.hook_attrs if assignee
def to_hook_data(user, old_labels: [], old_assignees: [])
changes = previous_changes
if old_labels != labels
changes[:labels] = [old_labels.map(&:hook_attrs), labels.map(&:hook_attrs)]
end
if old_assignees != assignees
if self.is_a?(Issue)
changes[:assignees] = [old_assignees.map(&:hook_attrs), assignees.map(&:hook_attrs)]
else
changes[:assignee] = [old_assignees&.first&.hook_attrs, assignee&.hook_attrs]
end
end
hook_data
Gitlab::HookData::IssuableBuilder.new(self).build(user: user, changes: changes)
end
def labels_array
......
......@@ -86,20 +86,6 @@ class Issue < ActiveRecord::Base
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
'#'
end
......@@ -146,6 +132,10 @@ class Issue < ActiveRecord::Base
"id DESC")
end
def hook_attrs
Gitlab::HookData::IssueBuilder.new(self).build
end
# Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes
{
......
......@@ -183,6 +183,10 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
def hook_attrs
Gitlab::HookData::MergeRequestBuilder.new(self).build
end
# Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes
{
......@@ -612,24 +616,6 @@ class MergeRequest < ActiveRecord::Base
!discussions_to_be_resolved?
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?
target_project != source_project
end
......@@ -714,13 +700,13 @@ class MergeRequest < ActiveRecord::Base
def source_branch_exists?
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
def target_branch_exists?
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
def merge_commit_message(include_description: false)
......
......@@ -53,13 +53,17 @@ class SentNotification < ActiveRecord::Base
end
def unsubscribable?
!for_commit?
!(for_commit? || for_snippet?)
end
def for_commit?
noteable_type == "Commit"
end
def for_snippet?
noteable_type.end_with?('Snippet')
end
def noteable
if for_commit?
project.commit(commit_id) rescue nil
......
......@@ -45,6 +45,6 @@ class GroupEntity < Grape::Entity
end
expose :avatar_url do |group|
group_icon(group)
group_icon_url(group)
end
end
......@@ -257,7 +257,7 @@ class IssuableBaseService < BaseService
invalidate_cache_counts(issuable, users: affected_assignees.compact)
after_update(issuable)
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
end
......
module Issues
class BaseService < ::IssuableBaseService
def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.build(issue)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue_data
def hook_data(issue, action, old_labels: [], old_assignees: [])
hook_data = issue.to_hook_data(current_user, old_labels: old_labels, old_assignees: old_assignees)
hook_data[:object_attributes][:action] = action
hook_data
end
def reopen_service
......@@ -22,8 +22,8 @@ module Issues
issue, issue.project, current_user, old_assignees)
end
def execute_hooks(issue, action = 'open')
issue_data = hook_data(issue, action)
def execute_hooks(issue, action = 'open', old_labels: [], old_assignees: [])
issue_data = hook_data(issue, action, old_labels: old_labels, old_assignees: old_assignees)
hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
issue.project.execute_hooks(issue_data, hooks_scope)
issue.project.execute_services(issue_data, hooks_scope)
......
......@@ -20,19 +20,19 @@ module MergeRequests
super if changed_title
end
def hook_data(merge_request, action, oldrev = nil)
hook_data = merge_request.to_hook_data(current_user)
hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
def hook_data(merge_request, action, old_rev: nil, old_labels: [], old_assignees: [])
hook_data = merge_request.to_hook_data(current_user, old_labels: old_labels, old_assignees: old_assignees)
hook_data[:object_attributes][:action] = action
if oldrev && !Gitlab::Git.blank_ref?(oldrev)
hook_data[:object_attributes][:oldrev] = oldrev
if old_rev && !Gitlab::Git.blank_ref?(old_rev)
hook_data[:object_attributes][:oldrev] = old_rev
end
hook_data
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
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_services(merge_data, :merge_request_hooks)
end
......
......@@ -183,7 +183,7 @@ module MergeRequests
# Call merge request webhook with update branches
def execute_mr_web_hooks
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
......
......@@ -459,7 +459,7 @@ module QuickActions
target_branch_param.strip
end
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
desc 'Move issue from one column of the board to another'
......
......@@ -647,7 +647,7 @@ module SystemNoteService
def discussion_lock(issuable, author)
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))
end
......
......@@ -22,7 +22,7 @@
= visibility_level_icon(group.visibility_level, fw: false)
.avatar-container.s40
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
= group_icon(group, class: "avatar s40 hidden-xs")
.title
= link_to [:admin, group], class: 'group-name' do
= group.full_name
......
......@@ -16,7 +16,7 @@
%ul.well-list
%li
.avatar-container.s60
= image_tag group_icon(@group), class: "avatar s60"
= group_icon(@group, class: "avatar s60")
%li
%span.light Name:
%strong= @group.name
......
......@@ -63,7 +63,7 @@
%th Projects
%th Jobs
%th Tags
%th Last contact
%th= link_to 'Last contact', admin_runners_path(params.slice(:search).merge(sort: 'contacted_asc'))
%th
- @runners.each do |runner|
......
......@@ -7,4 +7,4 @@
%td.notes_line{ colspan: 2 }
%td.notes_content
.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 @@
= render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false }
.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)
- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse
- disable_collapse_class = local_assigns.fetch(:disable_collapse_class, false)
- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse_class
- badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter]
- show_toggle = local_assigns.fetch(:show_toggle, true)
- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false)
......
......@@ -19,8 +19,7 @@
- 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
%li.commits-stat
- if event.commits_count > 2
%span ... and #{event.commits_count - 2} more commits.
%span ... and #{pluralize(event.commits_count - 1, 'more commit')}.
- if event.md_ref?
- from = event.commit_from
......
.group-home-panel.text-center
%div{ class: container_class }
.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
= @group.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
......
......@@ -12,7 +12,7 @@
.form-group
.col-sm-offset-2.col-sm-10
.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
- if @group.avatar?
You can change your group avatar here
......
......@@ -10,7 +10,7 @@
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.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...'
.clearfix
.error-alert
......
......@@ -10,7 +10,7 @@
.context-header
= link_to group_path(@group), title: @group.name do
.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
= @group.name
%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" } }
.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
.project-templates-buttons.import-buttons
- Gitlab::ProjectTemplate.all.each do |template|
.btn
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name }
.template-option
= 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 @@
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%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) }
.sub-section
%h4 Housekeeping
......
......@@ -48,7 +48,7 @@
- if @build.trigger_variables.any?
%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
- @build.trigger_variables.each do |trigger_variable|
......
......@@ -14,24 +14,40 @@
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
New project
- if import_sources_enabled?
%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'}.
%p
All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings.
%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'}.
%p
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
= form_for @project, html: { class: 'new_project' } do |f|
.create-project-options
.first-column
%ul.nav-links.gitlab-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' }
%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
.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
= 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
.form-group.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
......@@ -74,54 +90,10 @@
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
.row
.col-lg-12
.js-toggle-content.hide
%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'
.col-lg-12
.js-toggle-content.hide
%hr
= render "shared/import_form", f: f
.save-project-loader.hide
.center
......
......@@ -17,19 +17,32 @@
%i Owners
.light
- if can?(current_user, :admin_project_member, @project)
<<<<<<< HEAD
%ul.nav-links.project-member-tabs{ role: 'tablist' }
- if !membership_locked?
%li.active{ role: 'presentation' }
%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?
%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
<<<<<<< HEAD
.tab-content.project-member-tab-content
- if !membership_locked?
.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', 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 'shared/members/requests', membership_source: @project, requesters: @requesters
......
- page_title _("Wiki")
%h3.page-title= _("Wiki|Empty page")
%h3.page-title= s_("Wiki|Empty page")
%hr
.error_message
= s_("WikiEmptyPageError|You are not allowed to create wiki pages")
......@@ -28,7 +28,7 @@
.avatar-container.s40
= link_to group do
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
= group_icon(group, class: "avatar s40 hidden-xs")
.title
= 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">
<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="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>
<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" 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="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>
<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">
<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>
<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>
......@@ -4,7 +4,7 @@
- dom_id = "group_member_#{group_link.id}"
%li.member.group_member{ id: dom_id }
%span.list-item-name
= image_tag group_icon(group), class: "avatar s40", alt: ''
= group_icon(group, class: "avatar s40", alt: '')
%strong
= link_to group.full_name, group_path(group)
.cgray
......
......@@ -2,4 +2,4 @@
- groups.each do |group|
= link_to group, class: 'profile-groups-avatars inline', title: group.name do
.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
resource :auth, only: [], controller: :authorizations do
match :callback, via: [:get, :post]
scope '-' do
namespace :google_api do
resource :auth, only: [], controller: :authorizations do
match :callback, via: [:get, :post]
end
end
end
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',
module: :groups,
......
# rubocop:disable Migration/Datetime
class AddArtifactsExpireDateToCiBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :artifacts_expire_at, :timestamp
......
# rubocop:disable Migration/Datetime
class AddQueuedAtToCiBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# 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
# for more information on how to write migrations for GitLab.
......
......@@ -13,7 +13,10 @@
- [Generate a changelog entry with `bin/changelog`](changelog.md)
- [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)
<<<<<<< HEAD
- [Guidelines for implementing Enterprise Edition feature](ee_features.md)
=======
>>>>>>> upstream/master
## UX and frontend guides
......@@ -58,18 +61,29 @@
- [Merge Request checklist](database_merge_request_checklist.md)
- [Adding database indexes](adding_database_indexes.md)
<<<<<<< HEAD
- [Post Deployment Migrations](post_deployment_migrations.md)
- [Foreign keys & associations](foreign_keys.md)
- [Serializing data](serializing_data.md)
- [Polymorphic associations](polymorphic_associations.md)
- [Single table inheritance](single_table_inheritance.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)
- [Iterating tables in batches](iterating_tables_in_batches.md)
- [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md)
<<<<<<< HEAD
- [Hash indexes](hash_indexes.md)
- [Swapping Tables](swapping_tables.md)
=======
>>>>>>> upstream/master
## Documentation guides
......
......@@ -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
```javascript
// bad
require('foo');
const SomeClass = require('some_class');
// good
import Foo from 'foo';
import SomeClass from 'some_class';
// bad
module.exports = Foo;
module.exports = SomeClass;
// 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
......@@ -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. **Reference Naming**: Use camelCase for their instances:
```javascript
// bad
import CardBoard from 'cardBoard'
components: {
CardBoard:
};
// good
import cardBoard from 'cardBoard'
......
......@@ -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
of multiple components).
<<<<<<< HEAD
## System tests or Feature tests
=======
## System tests or feature tests
>>>>>>> upstream/master
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:
[Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist
[RackTest]: https://github.com/teamcapybara/capybara#racktest
<<<<<<< HEAD
## 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],
[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces
......@@ -131,10 +139,13 @@ always in-sync with the codebase.
[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa
[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa
<<<<<<< HEAD
## EE-specific tests
EE-specific tests follows the same organization, but under the `spec/ee` folder.
=======
>>>>>>> upstream/master
## How to test at the correct level?
As many things in life, deciding what to test at each level of testing is a
......
# GitLab Helm Chart
> **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).
......@@ -8,7 +8,9 @@ For more information on available GitLab Helm Charts, please see our [overview](
## 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 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