Commit 14bdd022 authored by Nick Thomas's avatar Nick Thomas

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

parents dd8d40c3 1df518ff
......@@ -20,6 +20,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
gem 'doorkeeper-openid_connect', '~> 1.1.0'
gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
......@@ -30,6 +31,7 @@ gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
<<<<<<< HEAD
gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
......@@ -38,6 +40,15 @@ gem 'gssapi', group: :kerberos
gem 'omniauth-authentiq', '~> 0.3.0'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6'
=======
gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.0'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6'
>>>>>>> ce/master
# Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
......@@ -245,6 +256,7 @@ gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.0'
gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
<<<<<<< HEAD
gem 'jquery-rails', '~> 4.1.0'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
......@@ -252,6 +264,14 @@ gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.0"
=======
gem 'jquery-rails', '~> 4.1.0'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
>>>>>>> ce/master
# Sentry integration
gem 'sentry-raven', '~> 2.0.0'
......
......@@ -86,6 +86,7 @@ GEM
better_errors (1.0.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
bindata (2.3.5)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootstrap-sass (3.3.6)
......@@ -175,6 +176,9 @@ GEM
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.0)
railties (>= 4.2)
doorkeeper-openid_connect (1.1.2)
doorkeeper (~> 4.0)
json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
rails (> 3.1)
elasticsearch (5.0.3)
......@@ -412,6 +416,12 @@ GEM
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.6)
json-jwt (1.7.1)
activesupport
bindata
multi_json (>= 1.3)
securecompare
url_safe_base64
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.6)
......@@ -720,6 +730,7 @@ GEM
scss_lint (0.47.1)
rake (>= 0.9, < 11)
sass (~> 3.4.15)
securecompare (1.0.0)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
......@@ -825,6 +836,7 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
......@@ -903,6 +915,7 @@ DEPENDENCIES
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
doorkeeper-openid_connect (~> 1.1.0)
dropzonejs-rails (~> 0.7.1)
elasticsearch-api (= 5.0.3)
elasticsearch-model (~> 0.1.9)
......
/* global CommentsStore Cookies notes */
import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
(() => {
const DiffNoteAvatars = Vue.extend({
props: ['discussionId'],
data() {
return {
isVisible: false,
lineType: '',
storeState: CommentsStore.state,
shownAvatars: 3,
collapseIcon,
};
},
template: `
<div class="diff-comment-avatar-holders"
v-show="notesCount !== 0">
<div v-if="!isVisible">
<img v-for="note in notesSubset"
class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
width="19"
height="19"
role="button"
data-container="body"
data-placement="top"
:data-line-type="lineType"
:title="note.authorName + ': ' + note.noteTruncated"
:src="note.authorAvatar"
@click="clickedAvatar($event)" />
<span v-if="notesCount > shownAvatars"
class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
data-container="body"
data-placement="top"
ref="extraComments"
role="button"
:data-line-type="lineType"
:title="extraNotesTitle"
@click="clickedAvatar($event)">{{ moreText }}</span>
</div>
<button class="diff-notes-collapse js-diff-comment-avatar"
type="button"
aria-label="Show comments"
:data-line-type="lineType"
@click="clickedAvatar($event)"
v-if="isVisible"
v-html="collapseIcon">
</button>
</div>
`,
mounted() {
this.$nextTick(() => {
this.addNoCommentClass();
this.setDiscussionVisible();
this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
});
$(document).on('toggle.comments', () => {
this.$nextTick(() => {
this.setDiscussionVisible();
});
});
},
destroyed() {
$(document).off('toggle.comments');
},
watch: {
storeState: {
handler() {
this.$nextTick(() => {
$('.has-tooltip', this.$el).tooltip('fixTitle');
// We need to add/remove a class to an element that is outside the Vue instance
this.addNoCommentClass();
});
},
deep: true,
},
},
computed: {
notesSubset() {
let notes = [];
if (this.discussion) {
notes = Object.keys(this.discussion.notes)
.slice(0, this.shownAvatars)
.map(noteId => this.discussion.notes[noteId]);
}
return notes;
},
extraNotesTitle() {
if (this.discussion) {
const extra = this.discussion.notesCount() - this.shownAvatars;
return `${extra} more comment${extra > 1 ? 's' : ''}`;
}
return '';
},
discussion() {
return this.storeState[this.discussionId];
},
notesCount() {
if (this.discussion) {
return this.discussion.notesCount();
}
return 0;
},
moreText() {
const plusSign = this.notesCount < 100 ? '+' : '';
return `${plusSign}${this.notesCount - this.shownAvatars}`;
},
},
methods: {
clickedAvatar(e) {
notes.addDiffNote(e);
// Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState();
this.$nextTick(() => {
this.setDiscussionVisible();
$('.has-tooltip', this.$el).tooltip('fixTitle');
$('.has-tooltip', this.$el).tooltip('hide');
});
},
addNoCommentClass() {
const notesCount = this.notesCount;
$(this.$el).closest('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0);
},
toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
const $visibleNotesHolders = $notesHolders.filter(':visible');
const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
$toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
},
setDiscussionVisible() {
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
},
},
});
Vue.component('diff-note-avatars', DiffNoteAvatars);
})();
......@@ -11,7 +11,10 @@ const Vue = require('vue');
discussionId: String,
resolved: Boolean,
canResolve: Boolean,
resolvedBy: String
resolvedBy: String,
authorName: String,
authorAvatar: String,
noteTruncated: String,
},
data: function () {
return {
......@@ -98,7 +101,16 @@ const Vue = require('vue');
CommentsStore.delete(this.discussionId, this.noteId);
},
created: function () {
CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
CommentsStore.create({
discussionId: this.discussionId,
noteId: this.noteId,
canResolve: this.canResolve,
resolved: this.resolved,
resolvedBy: this.resolvedBy,
authorName: this.authorName,
authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated,
});
this.note = this.discussion.getNote(this.noteId);
}
......
......@@ -14,6 +14,7 @@ require('./components/jump_to_discussion');
require('./components/resolve_btn');
require('./components/resolve_count');
require('./components/resolve_discussion_btn');
require('./components/diff_note_avatars');
$(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath;
......@@ -25,6 +26,15 @@ $(() => {
window.ResolveService = new gl.DiffNotesResolveServiceClass(projectPath);
gl.diffNotesCompileComponents = () => {
$('diff-note-avatars').each(function () {
const tmp = Vue.extend({
template: $(this).get(0).outerHTML
});
const tmpApp = new tmp().$mount();
$(this).replaceWith(tmpApp.$el);
});
const $components = $(COMPONENT_SELECTOR).filter(function () {
return $(this).closest('resolve-count').length !== 1;
});
......
<svg width="11" height="11" viewBox="0 0 9 13"><path d="M2.57568253,6.49866948 C2.50548852,6.57199715 2.44637866,6.59708255 2.39835118,6.57392645 C2.3503237,6.55077034 2.32631032,6.48902165 2.32631032,6.38867852 L2.32631032,-2.13272614 C2.32631032,-2.23306927 2.3503237,-2.29481796 2.39835118,-2.31797406 C2.44637866,-2.34113017 2.50548852,-2.31604477 2.57568253,-2.24271709 L6.51022184,1.86747129 C6.53977721,1.8983461 6.56379059,1.93500939 6.5822627,1.97746225 L6.5822627,2.27849013 C6.56379059,2.31708364 6.53977721,2.35374693 6.51022184,2.38848109 L2.57568253,6.49866948 Z" transform="translate(4.454287, 2.127976) rotate(90.000000) translate(-4.454287, -2.127976) "></path><path d="M3.74312342,2.09553332 C3.74312342,1.99519019 3.77821989,1.9083561 3.8484139,1.83502843 C3.91860791,1.76170075 4.00173115,1.72503747 4.09778611,1.72503747 L4.80711151,1.72503747 C4.90316647,1.72503747 4.98628971,1.76170075 5.05648372,1.83502843 C5.12667773,1.9083561 5.16177421,1.99519019 5.16177421,2.09553332 L5.16177421,10.2464421 C5.16177421,10.3467853 5.12667773,10.4336194 5.05648372,10.506947 C4.98628971,10.5802747 4.90316647,10.616938 4.80711151,10.616938 L4.09778611,10.616938 C4.00173115,10.616938 3.91860791,10.5802747 3.8484139,10.506947 C3.77821989,10.4336194 3.74312342,10.3467853 3.74312342,10.2464421 L3.74312342,2.09553332 Z" transform="translate(4.452449, 6.170988) rotate(-90.000000) translate(-4.452449, -6.170988) "></path><path d="M2.57568253,14.6236695 C2.50548852,14.6969971 2.44637866,14.7220826 2.39835118,14.6989264 C2.3503237,14.6757703 2.32631032,14.6140216 2.32631032,14.5136785 L2.32631032,5.99227386 C2.32631032,5.89193073 2.3503237,5.83018204 2.39835118,5.80702594 C2.44637866,5.78386983 2.50548852,5.80895523 2.57568253,5.88228291 L6.51022184,9.99247129 C6.53977721,10.0233461 6.56379059,10.0600094 6.5822627,10.1024622 L6.5822627,10.4034901 C6.56379059,10.4420836 6.53977721,10.4787469 6.51022184,10.5134811 L2.57568253,14.6236695 Z" transform="translate(4.454287, 10.252976) scale(1, -1) rotate(90.000000) translate(-4.454287, -10.252976) "></path></svg>
......@@ -10,8 +10,8 @@ class DiscussionModel {
this.canResolve = false;
}
createNote (noteId, canResolve, resolved, resolved_by) {
Vue.set(this.notes, noteId, new NoteModel(this.id, noteId, canResolve, resolved, resolved_by));
createNote (noteObj) {
Vue.set(this.notes, noteObj.noteId, new NoteModel(this.id, noteObj));
}
deleteNote (noteId) {
......
/* eslint-disable camelcase, no-unused-vars */
class NoteModel {
constructor(discussionId, noteId, canResolve, resolved, resolved_by) {
constructor(discussionId, noteObj) {
this.discussionId = discussionId;
this.id = noteId;
this.canResolve = canResolve;
this.resolved = resolved;
this.resolved_by = resolved_by;
this.id = noteObj.noteId;
this.canResolve = noteObj.canResolve;
this.resolved = noteObj.resolved;
this.resolved_by = noteObj.resolvedBy;
this.authorName = noteObj.authorName;
this.authorAvatar = noteObj.authorAvatar;
this.noteTruncated = noteObj.noteTruncated;
}
}
......
......@@ -21,10 +21,10 @@
return discussion;
},
create: function (discussionId, noteId, canResolve, resolved, resolved_by) {
const discussion = this.createDiscussion(discussionId);
create: function (noteObj) {
const discussion = this.createDiscussion(noteObj.discussionId);
discussion.createNote(noteId, canResolve, resolved, resolved_by);
discussion.createNote(noteObj);
},
update: function (discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
......
......@@ -5,7 +5,6 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make
/* global ShortcutsNavigation */
/* global Build */
/* global Issuable */
/* global Issue */
/* global ShortcutsIssuable */
/* global ZenMode */
/* global Milestone */
......@@ -35,8 +34,12 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make
/* global ProjectShow */
/* global Labels */
/* global Shortcuts */
<<<<<<< HEAD
/* global WeightSelect */
/* global AdminEmailSelect */
=======
import Issue from './issue';
>>>>>>> ce/master
import BindInOut from './behaviors/bind_in_out';
import GroupsList from './groups_list';
......@@ -290,6 +293,7 @@ const UserCallout = require('./user_callout');
case 'search:show':
new Search();
break;
<<<<<<< HEAD
case 'projects:mirrors:show':
case 'projects:mirrors:update':
new UsersSelect();
......@@ -297,6 +301,8 @@ const UserCallout = require('./user_callout');
case 'admin:emails:show':
new AdminEmailSelect();
break;
=======
>>>>>>> ce/master
case 'projects:repository:show':
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
......
......@@ -38,6 +38,9 @@
FilesCommentButton.prototype.render = function(e) {
var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
$currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('js-no-comment-btn')) return;
lineContentElement = this.getLineContent($currentTarget);
buttonParentElement = this.getButtonParent($currentTarget);
......
......@@ -80,6 +80,7 @@
}
// Determines the full search query (visual tokens + input)
<<<<<<< HEAD
static getSearchQuery() {
const tokensContainer = document.querySelector('.tokens-container');
const values = [];
......@@ -104,6 +105,50 @@
const input = document.querySelector('.filtered-search');
values.push(input && input.value);
=======
static getSearchQuery(untilInput = false) {
const tokens = [].slice.call(document.querySelectorAll('.tokens-container li'));
const values = [];
if (untilInput) {
const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
// Add one to include input-token to the tokens array
tokens.splice(inputIndex + 1);
}
tokens.forEach((token) => {
if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
let valueText = '';
if (value && value.innerText) {
valueText = value.innerText;
}
if (token.className.indexOf('filtered-search-token') !== -1) {
values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
} else {
values.push(name.innerText);
}
} else if (token.classList.contains('input-token')) {
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = document.querySelector('.filtered-search');
const inputValue = input && input.value;
if (isLastVisualTokenValid) {
values.push(inputValue);
} else {
const previous = values.pop();
values.push(`${previous}${inputValue}`);
}
}
});
>>>>>>> ce/master
return values.join(' ');
}
......
......@@ -8,5 +8,8 @@ require('./filtered_search_manager');
require('./filtered_search_token_keys');
require('./filtered_search_tokenizer');
require('./filtered_search_visual_tokens');
<<<<<<< HEAD
require('./filtered_search_token_keys_with_weights');
=======
>>>>>>> ce/master
......@@ -60,6 +60,7 @@
element: document.querySelector('#js-dropdown-hint'),
},
};
<<<<<<< HEAD
if (this.page === 'issues') {
this.mapping.weight = {
......@@ -79,6 +80,19 @@
if (clicked) {
gl.FilteredSearchVisualTokens.moveInputToTheRight();
}
=======
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = document.querySelector('.filtered-search');
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
input.value = '';
if (clicked) {
gl.FilteredSearchVisualTokens.moveInputToTheRight();
}
>>>>>>> ce/master
}
updateCurrentDropdownOffset() {
......@@ -151,7 +165,11 @@
}
setDropdown() {
<<<<<<< HEAD
const query = gl.DropdownUtils.getSearchQuery();
=======
const query = gl.DropdownUtils.getSearchQuery(true);
>>>>>>> ce/master
const { lastToken, searchToken } = this.tokenizer.processTokens(query);
if (this.currentDropdown) {
......
......@@ -178,7 +178,10 @@
if (e.keyCode === 8 || e.keyCode === 46) {
gl.FilteredSearchVisualTokens.removeSelectedToken();
this.handleInputPlaceholder();
<<<<<<< HEAD
=======
>>>>>>> ce/master
this.toggleClearSearchButton();
}
}
......@@ -368,12 +371,15 @@
tokenChange() {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
if (dropdown) {
const currentDropdownRef = dropdown.reference;
this.setDropdownWrapper();
currentDropdownRef.dispatchInputEvent();
}
}
}
window.gl = window.gl || {};
gl.FilteredSearchManager = FilteredSearchManager;
......
......@@ -5,12 +5,8 @@ require('./flash');
require('vendor/jquery.waitforimages');
require('./task_list');
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.Issue = (function() {
function Issue() {
this.submitNoteForm = bind(this.submitNoteForm, this);
class Issue {
constructor() {
if ($('a.btn-close').length) {
this.taskList = new gl.TaskList({
dataType: 'issue',
......@@ -21,16 +17,15 @@ require('./task_list');
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
});
this.initIssueBtnEventListeners();
Issue.initIssueBtnEventListeners();
}
this.initMergeRequests();
this.initRelatedBranches();
this.initCanCreateBranch();
Issue.initMergeRequests();
Issue.initRelatedBranches();
Issue.initCanCreateBranch();
}
Issue.prototype.initIssueBtnEventListeners = function() {
var _this, issueFailMessage;
_this = this;
static initIssueBtnEventListeners() {
var issueFailMessage;
issueFailMessage = 'Unable to update this issue at this time.';
return $('a.btn-close, a.btn-reopen').on('click', function(e) {
var $this, isClose, shouldSubmit, url;
......@@ -40,7 +35,7 @@ require('./task_list');
isClose = $this.hasClass('btn-close');
shouldSubmit = $this.hasClass('btn-comment');
if (shouldSubmit) {
_this.submitNoteForm($this.closest('form'));
Issue.submitNoteForm($this.closest('form'));
}
$this.prop('disabled', true);
url = $this.attr('href');
......@@ -76,17 +71,17 @@ require('./task_list');
}
});
});
};
}
Issue.prototype.submitNoteForm = function(form) {
static submitNoteForm(form) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
if (noteText.trim().length > 0) {
return form.submit();
}
};
}
Issue.prototype.initMergeRequests = function() {
static initMergeRequests() {
var $container;
$container = $('#merge-requests');
return $.getJSON($container.data('url')).error(function() {
......@@ -96,9 +91,9 @@ require('./task_list');
return $container.html(data.html);
}
});
};
}
Issue.prototype.initRelatedBranches = function() {
static initRelatedBranches() {
var $container;
$container = $('#related-branches');
return $.getJSON($container.data('url')).error(function() {
......@@ -108,9 +103,9 @@ require('./task_list');
return $container.html(data.html);
}
});
};
}
Issue.prototype.initCanCreateBranch = function() {
static initCanCreateBranch() {
var $container;
$container = $('#new-branch');
// If the user doesn't have the required permissions the container isn't
......@@ -128,8 +123,7 @@ require('./task_list');
return $container.find('.unavailable').show();
}
});
};
}
}
return Issue;
})();
}).call(window);
export default Issue;
......@@ -350,11 +350,11 @@ require('./weight_select');
var notesHolders = $this.closest('.diff-file').find('.notes_holder');
$this.toggleClass('active');
if ($this.hasClass('active')) {
notesHolders.show().find('.hide').show();
notesHolders.show().find('.hide, .content').show();
} else {
notesHolders.hide();
notesHolders.hide().find('.content').hide();
}
$this.trigger('blur');
$(document).trigger('toggle.comments');
return e.preventDefault();
});
$document.off('click', '.js-confirm-danger');
......
......@@ -312,7 +312,7 @@ require('./task_list');
*/
Notes.prototype.renderDiscussionNote = function(note) {
var discussionContainer, form, note_html, row;
var discussionContainer, form, note_html, row, lineType, diffAvatarContainer;
if (!this.isNewNote(note)) {
return;
}
......@@ -322,6 +322,8 @@ require('./task_list');
form = $("#new-discussion-note-form-" + note.original_discussion_id);
}
row = form.closest("tr");
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
note_html = $(note.html);
note_html.renderGFM();
// is this the first note of discussion?
......@@ -330,10 +332,26 @@ require('./task_list');
discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
}
if (discussionContainer.length === 0) {
if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
// insert the note and the reply button after the temp row
row.after(note.diff_discussion_html);
// remove the note (will be added again below)
row.next().find(".note").remove();
} else {
// Merge new discussion HTML in
var $discussion = $(note.diff_discussion_html);
var $notes = $discussion.find('.notes[data-discussion-id="' + note.discussion_id + '"]');
var contentContainerClass = '.' + $notes.closest('.notes_content')
.attr('class')
.split(' ')
.join('.');
// remove the note (will be added again below)
$notes.find('.note').remove();
row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
}
// Before that, the container didn't exist
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
// Add note to 'Changes' page discussions
......@@ -347,14 +365,40 @@ require('./task_list');
discussionContainer.append(note_html);
}
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
if (typeof gl.diffNotesCompileComponents !== 'undefined' && note.discussion_id) {
gl.diffNotesCompileComponents();
this.renderDiscussionAvatar(diffAvatarContainer, note);
}
gl.utils.localTimeAgo($('.js-timeago'), false);
return this.updateNotesCount(1);
};
Notes.prototype.getLineHolder = function(changesDiscussionContainer) {
return $(changesDiscussionContainer).closest('.notes_holder')
.prevAll('.line_holder')
.first()
.get(0);
};
Notes.prototype.renderDiscussionAvatar = function(diffAvatarContainer, note) {
var commentButton = diffAvatarContainer.find('.js-add-diff-note-button');
var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders');
if (!avatarHolder.length) {
avatarHolder = document.createElement('diff-note-avatars');
avatarHolder.setAttribute('discussion-id', note.discussion_id);
diffAvatarContainer.append(avatarHolder);
gl.diffNotesCompileComponents();
}
if (commentButton.length) {
commentButton.remove();
}
};
/*
Called in response the main target form has been successfully submitted.
......@@ -592,9 +636,14 @@ require('./task_list');
*/
Notes.prototype.removeNote = function(e) {
var noteId;
noteId = $(e.currentTarget).closest(".note").attr("id");
$(".note[id='" + noteId + "']").each((function(_this) {
var noteElId, noteId, dataNoteId, $note, lineHolder;
$note = $(e.currentTarget).closest('.note');
noteElId = $note.attr('id');
noteId = $note.attr('data-note-id');
lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
.closest('.notes_holder')
.prev('.line_holder');
$(".note[id='" + noteElId + "']").each((function(_this) {
// A same note appears in the "Discussion" and in the "Changes" tab, we have
// to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
// where $("#noteId") would return only one.
......@@ -604,17 +653,26 @@ require('./task_list');
notes = note.closest(".notes");
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
if (gl.diffNoteApps[noteId]) {
gl.diffNoteApps[noteId].$destroy();
if (gl.diffNoteApps[noteElId]) {
gl.diffNoteApps[noteElId].$destroy();
}
}
note.remove();
// check if this is the last note for this line
if (notes.find(".note").length === 1) {
if (notes.find(".note").length === 0) {
var notesTr = notes.closest("tr");
// "Discussions" tab
notes.closest(".timeline-entry").remove();
if (!_this.isParallelView() || notesTr.find('.note').length === 0) {
// "Changes" tab / commit view
notes.closest("tr").remove();
notesTr.remove();
} else {
notes.closest('.content').empty();
}
}
return note.remove();
};
......@@ -707,15 +765,16 @@ require('./task_list');
*/
Notes.prototype.addDiffNote = function(e) {
var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, notesContentSelector, replyButton, row, rowCssToAdd, targetContent;
var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, notesContentSelector, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
e.preventDefault();
$link = $(e.currentTarget);
$link = $(e.currentTarget || e.target);
row = $link.closest("tr");
nextRow = row.next();
hasNotes = nextRow.is(".notes_holder");
addForm = false;
notesContentSelector = ".notes_content";
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
isDiffCommentAvatar = $link.hasClass('js-diff-comment-avatar');
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineType = $link.data("lineType");
......@@ -723,7 +782,9 @@ require('./task_list');
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line old\"></td><td class=\"notes_content parallel old\"><div class=\"content\"></div></td><td class=\"notes_line new\"></td><td class=\"notes_content parallel new\"><div class=\"content\"></div></td></tr>";
}
notesContentSelector += " .content";
if (hasNotes) {
notesContent = nextRow.find(notesContentSelector);
if (hasNotes && !isDiffCommentAvatar) {
nextRow.show();
notesContent = nextRow.find(notesContentSelector);
if (notesContent.length) {
......@@ -740,13 +801,21 @@ require('./task_list');
}
}
}
} else {
} else if (!isDiffCommentAvatar) {
// add a notes row and insert the form
row.after(rowCssToAdd);
nextRow = row.next();
notesContent = nextRow.find(notesContentSelector);
addForm = true;
} else {
nextRow.show();
notesContent.toggle(!notesContent.is(':visible'));
if (!nextRow.find('.content:not(:empty)').is(':visible')) {
nextRow.hide();
}
}
if (addForm) {
newForm = this.formClone.clone();
newForm.appendTo(notesContent);
......
......@@ -4,6 +4,21 @@
&.reset-filters {
padding: 7px;
}
&.update-issues-btn {
float: right;
margin-right: 0;
@media (max-width: $screen-xs-max) {
float: none;
}
}
}
.filters-section {
@media (max-width: $screen-xs-max) {
display: inline-block;
}
}
@media (min-width: $screen-sm-min) {
......@@ -34,6 +49,11 @@
display: block;
margin: 0 0 10px;
}
.dropdown-menu-toggle,
.update-issues-btn .btn {
width: 100%;
}
}
.filtered-search-container {
......@@ -208,7 +228,15 @@
overflow: auto;
}
@media (max-width: $screen-xs-min) {
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
.issues-details-filters {
.dropdown-menu-toggle {
width: 100px;
}
}
}
@media (max-width: $screen-xs-max) {
.issues-details-filters {
padding: 0 0 10px;
background-color: $white-light;
......
......@@ -235,44 +235,6 @@ ul.content-list {
}
}
// Table list
.table-list {
display: table;
width: 100%;
.table-list-row {
display: table-row;
}
.table-list-cell {
display: table-cell;
vertical-align: top;
padding: 10px 16px;
border-bottom: 1px solid $gray-darker;
&.avatar-cell {
width: 36px;
padding-right: 0;
img {
margin-right: 0;
}
}
}
&.table-wide {
.table-list-cell {
&:last-of-type {
padding-right: 0;
}
&:first-of-type {
padding-left: 0;
}
}
}
}
.panel > .content-list > li {
padding: $gl-padding-top $gl-padding;
}
......
......@@ -100,8 +100,7 @@
@media (max-width: $screen-sm-max) {
.issues-filters {
.milestone-filter,
.labels-filter {
.milestone-filter {
display: none;
}
}
......
......@@ -294,7 +294,7 @@
.nav-control {
@media (max-width: $screen-sm-max) {
margin-right: 75px;
margin-right: 2px;
}
}
}
......
......@@ -48,11 +48,3 @@
line-height: inherit;
}
}
.panel-default {
.table-list-row:last-child {
.table-list-cell {
border-bottom: 0;
}
}
}
......@@ -78,6 +78,7 @@
padding: 5px 10px;
background-color: $gray-light;
border-bottom: 1px solid $gray-darker;
border-top: 1px solid $gray-darker;
font-size: 14px;
&:first-child {
......@@ -117,10 +118,37 @@
}
}
.commit.flex-list {
display: flex;
}
.avatar-cell {
width: 46px;
padding-left: 10px;
img {
margin-right: 0;
}
}
.commit-detail {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-grow: 1;
padding-left: 10px;
.merge-request-branches & {
flex-direction: column;
}
}
.commit-content {
padding-right: 10px;
}
.commit-actions {
@media (min-width: $screen-sm-min) {
width: 300px;
text-align: right;
font-size: 0;
}
......
......@@ -113,6 +113,10 @@
td.line_content.parallel {
width: 46%;
}
.add-diff-note {
margin-left: -55px;
}
}
.old_line,
......@@ -490,3 +494,103 @@
}
}
}
.diff-comment-avatar-holders {
position: absolute;
height: 19px;
width: 19px;
margin-left: -15px;
&:hover {
.diff-comment-avatar,
.diff-comments-more-count {
@for $i from 1 through 4 {
$x-pos: 14px;
&:nth-child(#{$i}) {
@if $i == 4 {
$x-pos: 14.5px;
}
transform: translateX((($i * $x-pos) - $x-pos));
&:hover {
transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2);
}
}
}
}
.diff-comments-more-count {
padding-left: 2px;
padding-right: 2px;
width: auto;
}
}
}
.diff-comment-avatar,
.diff-comments-more-count {
position: absolute;
left: 0;
width: 19px;
height: 19px;
margin-right: 0;
border-color: $white-light;
cursor: pointer;
transition: all .1s ease-out;
@for $i from 1 through 4 {
&:nth-child(#{$i}) {
z-index: (4 - $i);
}
}
}
.diff-comments-more-count {
width: 19px;
min-width: 19px;
padding-left: 0;
padding-right: 0;
overflow: hidden;
}
.diff-comments-more-count,
.diff-notes-collapse {
background-color: $gray-darkest;
color: $white-light;
border: 1px solid $white-light;
border-radius: 1em;
font-family: $regular_font;
font-size: 9px;
line-height: 17px;
text-align: center;
}
.diff-notes-collapse {
position: relative;
width: 19px;
height: 19px;
padding: 0;
transition: transform .1s ease-out;
svg {
position: absolute;
left: 50%;
top: 50%;
margin-left: -5.5px;
margin-top: -5.5px;
}
path {
fill: $white-light;
}
&:hover {
transform: scale(1.2);
}
&:focus {
outline: 0;
}
}
......@@ -144,6 +144,7 @@
}
}
<<<<<<< HEAD
/**
* Deploy boards
*/
......@@ -279,6 +280,8 @@
}
}
=======
>>>>>>> ce/master
.prometheus-graph {
text {
fill: $stat-graph-axis-fill;
......
......@@ -240,8 +240,7 @@
.commit {
margin: 0;
padding-top: 2px;
padding-bottom: 2px;
padding: 10px 0;
list-style: none;
&:hover {
......@@ -409,7 +408,7 @@
}
.panel-footer {
padding: 5px 10px;
padding: 0;
.btn {
min-width: auto;
......
......@@ -331,6 +331,10 @@ ul.notes {
&:hover {
color: $gl-link-color;
}
&:focus,
&:hover {
text-decoration: none;
}
}
......
......@@ -115,7 +115,7 @@
.table.ci-table {
&.builds-page tr {
&.builds-page tbody tr {
height: 71px;
}
......
......@@ -759,6 +759,8 @@ a.allowed-to-push {
}
.protected-branches-list {
margin-bottom: 30px;
a {
color: $gl-text-color;
......
......@@ -24,3 +24,14 @@
.service-settings .control-label {
padding-top: 0;
}
.token-token-container {
#impersonation-token-token {
width: 80%;
display: inline;
}
.btn-clipboard {
margin-left: 5px;
}
}
.triggers-container {
.label-container {
display: inline-block;
margin-left: 10px;
}
}
.trigger-actions {
.btn {
margin-left: 10px;
}
}
......@@ -139,18 +139,10 @@
.blob-commit-info {
list-style: none;
background: $gray-light;
padding: 6px 0;
padding: 16px 16px 16px 6px;
border: 1px solid $border-color;
border-bottom: none;
margin: 0;
.table-list-cell {
border-bottom: none;
}
.commit-actions {
width: 260px;
}
}
#modal-remove-blob > .modal-dialog { width: 850px; }
......
......@@ -2,7 +2,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
include OauthApplications
before_action :set_application, only: [:show, :edit, :update, :destroy]
before_action :load_scopes, only: [:new, :edit]
before_action :load_scopes, only: [:new, :create, :edit, :update]
def index
@applications = Doorkeeper::Application.where("owner_id IS NULL")
......
class Admin::ImpersonationTokensController < Admin::ApplicationController
before_action :user
def index
set_index_vars
end
def create
@impersonation_token = finder.build(impersonation_token_params)
if @impersonation_token.save
flash[:impersonation_token] = @impersonation_token.token
redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
else
set_index_vars
render :index
end
end
def revoke
@impersonation_token = finder.find(params[:id])
if @impersonation_token.revoke!
flash[:notice] = "Revoked impersonation token #{@impersonation_token.name}!"
else
flash[:alert] = "Could not revoke impersonation token #{@impersonation_token.name}."
end
redirect_to admin_user_impersonation_tokens_path
end
private
def user
@user ||= User.find_by!(username: params[:user_id])
end
def finder(options = {})
PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
end
def impersonation_token_params
params.require(:personal_access_token).permit(:name, :expires_at, :impersonation, scopes: [])
end
def set_index_vars
@scopes = Gitlab::Auth::API_SCOPES
@impersonation_token ||= finder.build
@inactive_impersonation_tokens = finder(state: 'inactive').execute
@active_impersonation_tokens = finder(state: 'active').execute.order(:expires_at)
end
end
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_action :authenticate_resource_owner!
layout 'profile'
# Overriden from Doorkeeper::AuthorizationsController to
# include the call to session.delete
def new
if pre_auth.authorizable?
if skip_authorization? || matching_token?
......@@ -16,44 +16,4 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
render "doorkeeper/authorizations/error"
end
end
# TODO: Handle raise invalid authorization
def create
redirect_or_render authorization.authorize
end
def destroy
redirect_or_render authorization.deny
end
private
def matching_token?
Doorkeeper::AccessToken.matching_token_for(pre_auth.client,
current_resource_owner.id,
pre_auth.scopes)
end
def redirect_or_render(auth)
if auth.redirectable?
redirect_to auth.redirect_uri
else
render json: auth.body, status: auth.status
end
end
def pre_auth
@pre_auth ||=
Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration,
server.client_via_uid,
params)
end
def authorization
@authorization ||= strategy.request
end
def strategy
@strategy ||= server.authorization_request(pre_auth.response_type)
end
end
......@@ -4,7 +4,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def create
@personal_access_token = current_user.personal_access_tokens.generate(personal_access_token_params)
@personal_access_token = finder.build(personal_access_token_params)
if @personal_access_token.save
flash[:personal_access_token] = @personal_access_token.token
......@@ -16,7 +16,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def revoke
@personal_access_token = current_user.personal_access_tokens.find(params[:id])
@personal_access_token = finder.find(params[:id])
if @personal_access_token.revoke!
flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!"
......@@ -29,14 +29,19 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
private
def finder(options = {})
PersonalAccessTokensFinder.new({ user: current_user, impersonation: false }.merge(options))
end
def personal_access_token_params
params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
end
def set_index_vars
@personal_access_token ||= current_user.personal_access_tokens.build
@scopes = Gitlab::Auth::SCOPES
@active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at)
@inactive_personal_access_tokens = current_user.personal_access_tokens.inactive
@scopes = Gitlab::Auth::API_SCOPES
@personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
end
end
......@@ -8,7 +8,11 @@ module Projects
def index
issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
issues = issues.page(params[:page]).per(params[:per] || 20)
<<<<<<< HEAD
make_sure_position_is_set(issues) unless Gitlab::Geo.secondary?
=======
make_sure_position_is_set(issues)
>>>>>>> ce/master
render json: {
issues: serialize_as_json(issues),
......
......@@ -19,9 +19,13 @@ class Projects::DeployKeysController < Projects::ApplicationController
@key = DeployKey.new(deploy_key_params.merge(user: current_user))
unless @key.valid? && @project.deploy_keys << @key
<<<<<<< HEAD
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
else
log_audit_event(@key.title, action: :create)
=======
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
>>>>>>> ce/master
end
redirect_to_repository_settings(@project)
end
......@@ -46,6 +50,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def deploy_key_params
params.require(:deploy_key).permit(:key, :title, :can_push)
<<<<<<< HEAD
end
def log_audit_event(key_title, options = {})
......@@ -55,5 +60,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def load_key
@key ||= current_user.accessible_deploy_keys.find(params[:id])
=======
>>>>>>> ce/master
end
end
......@@ -5,7 +5,11 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :authorize_create_deployment!, only: [:stop]
before_action :authorize_update_environment!, only: [:edit, :update]
before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
<<<<<<< HEAD
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics, :status]
=======
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
>>>>>>> ce/master
before_action :verify_api_request!, only: :terminal_websocket_authorize
def index
......@@ -122,6 +126,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
<<<<<<< HEAD
# The rollout status of an enviroment
def status
unless @environment.deployment_service_ready?
......@@ -139,6 +144,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
=======
>>>>>>> ce/master
private
def verify_api_request!
......
......@@ -57,8 +57,11 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
merge_access_levels_attributes: [:access_level, :id, :user_id, :_destroy, :group_id],
push_access_levels_attributes: [:access_level, :id, :user_id, :_destroy, :group_id])
end
<<<<<<< HEAD
def load_protected_branches
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
end
=======
>>>>>>> ce/master
end
......@@ -2,15 +2,22 @@ module Projects
module Settings
class RepositoryController < Projects::ApplicationController
before_action :authorize_admin_project!
<<<<<<< HEAD
before_action :push_rule, only: [:show]
before_action :remote_mirror, only: [:show]
=======
>>>>>>> ce/master
def show
@deploy_keys = DeployKeysPresenter
.new(@project, current_user: current_user)
define_protected_branches
<<<<<<< HEAD
end
=======
end
>>>>>>> ce/master
private
......@@ -20,6 +27,7 @@ module Projects
load_gon_index
end
<<<<<<< HEAD
def push_rule
@push_rule ||= PushRule.find_or_create_by(is_sample: true)
end
......@@ -28,6 +36,8 @@ module Projects
@remote_mirror = @project.remote_mirrors.first_or_initialize
end
=======
>>>>>>> ce/master
def load_protected_branches
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
end
......@@ -35,6 +45,7 @@ module Projects
def access_levels_options
{
push_access_levels: {
<<<<<<< HEAD
roles: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
},
merge_access_levels: {
......@@ -45,15 +56,33 @@ module Projects
}
end
=======
roles: ProtectedBranch::PushAccessLevel.human_access_levels.map do |id, text|
{ id: id, text: text, before_divider: true }
end
},
merge_access_levels: {
roles: ProtectedBranch::MergeAccessLevel.human_access_levels.map do |id, text|
{ id: id, text: text, before_divider: true }
end
}
}
end
>>>>>>> ce/master
def open_branches
branches = @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } }
{ open_branches: branches }
end
def load_gon_index
<<<<<<< HEAD
params = open_branches
params[:current_project_id] = @project.id if @project
gon.push(params.merge(access_levels_options))
=======
gon.push(open_branches.merge(access_levels_options))
>>>>>>> ce/master
end
end
end
......
class Projects::TriggersController < Projects::ApplicationController
before_action :authorize_admin_build!
before_action :authorize_manage_trigger!, except: [:index, :create]
before_action :authorize_admin_trigger!, only: [:edit, :update]
before_action :trigger, only: [:take_ownership, :edit, :update, :destroy]
layout 'project_settings'
......@@ -8,27 +11,67 @@ class Projects::TriggersController < Projects::ApplicationController
end
def create
@trigger = project.triggers.new
@trigger.save
@trigger = project.triggers.create(create_params.merge(owner: current_user))
if @trigger.valid?
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Trigger was created successfully.'
flash[:notice] = 'Trigger was created successfully.'
else
@triggers = project.triggers.select(&:persisted?)
render action: "show"
flash[:alert] = 'You could not create a new trigger.'
end
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
def take_ownership
if trigger.update(owner: current_user)
flash[:notice] = 'Trigger was re-assigned.'
else
flash[:alert] = 'You could not take ownership of trigger.'
end
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
def edit
end
def update
if trigger.update(update_params)
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.'
else
render action: "edit"
end
end
def destroy
trigger.destroy
flash[:alert] = "Trigger removed"
if trigger.destroy
flash[:notice] = "Trigger removed."
else
flash[:alert] = "Could not remove the trigger."
end
redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
end
private
def authorize_manage_trigger!
access_denied! unless can?(current_user, :manage_trigger, trigger)
end
def authorize_admin_trigger!
access_denied! unless can?(current_user, :admin_trigger, trigger)
end
def trigger
@trigger ||= project.triggers.find(params[:id])
@trigger ||= project.triggers.find(params[:id]) || render_404
end
def create_params
params.require(:trigger).permit(:description)
end
def update_params
params.require(:trigger).permit(:description)
end
end
......@@ -14,6 +14,8 @@ class UploadsController < ApplicationController
end
disposition = uploader.image? ? 'inline' : 'attachment'
expires_in 0.seconds, must_revalidate: true, private: true
send_file uploader.file.path, disposition: disposition
end
......
class PersonalAccessTokensFinder
attr_accessor :params
delegate :build, :find, :find_by, to: :execute
def initialize(params = {})
@params = params
end
def execute
tokens = PersonalAccessToken.all
tokens = by_user(tokens)
tokens = by_impersonation(tokens)
by_state(tokens)
end
private
def by_user(tokens)
return tokens unless @params[:user]
tokens.where(user: @params[:user])
end
def by_impersonation(tokens)
case @params[:impersonation]
when true
tokens.with_impersonation
when false
tokens.without_impersonation
else
tokens
end
end
def by_state(tokens)
case @params[:state]
when 'active'
tokens.active
when 'inactive'
tokens.inactive
else
tokens
end
end
end
......@@ -81,8 +81,8 @@ module ApplicationSettingsHelper
end
def repository_storages_options_for_select
options = Gitlab.config.repositories.storages.map do |name, path|
["#{name} - #{path}", name]
options = Gitlab.config.repositories.storages.map do |name, storage|
["#{name} - #{storage['path']}", name]
end
options_for_select(options, @application_setting.repository_storages)
......
......@@ -12,7 +12,7 @@ module BuildsHelper
build_url: namespace_project_build_url(@project.namespace, @project, @build, :json),
build_status: @build.status,
build_stage: @build.stage,
log_state: @build.trace_with_state[:state].to_s
log_state: ''
}
end
......
......@@ -15,6 +15,8 @@ module CiStatusHelper
'passed'
when 'success_with_warnings'
'passed with warnings'
when 'manual'
'waiting for manual action'
else
status
end
......@@ -48,6 +50,8 @@ module CiStatusHelper
'icon_status_created'
when 'skipped'
'icon_status_skipped'
when 'manual'
'icon_status_manual'
else
'icon_status_canceled'
end
......
......@@ -162,7 +162,12 @@ module EventsHelper
def event_note(text, options = {})
text = first_line_in_markdown(text, 150, options)
sanitize(text, tags: %w(a img b pre code p span))
sanitize(
text,
tags: %w(a img b pre code p span),
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style']
)
end
def event_commit_title(message)
......
......@@ -36,7 +36,7 @@ module PreferencesHelper
def project_view_choices
[
['Files and Readme (default)', :files],
['Activity view', :activity]
['Activity', :activity]
]
end
......
......@@ -52,7 +52,7 @@ module SortingHelper
end
def sort_title_priority
'Priority'
'Label priority'
end
def sort_title_oldest_updated
......
class ChatTeam < ActiveRecord::Base
validates :team_id, presence: true
validates :namespace, uniqueness: true
belongs_to :namespace
end
......@@ -518,6 +518,27 @@ module Ci
]
end
def steps
[Gitlab::Ci::Build::Step.from_commands(self),
Gitlab::Ci::Build::Step.from_after_script(self)].compact
end
def image
Gitlab::Ci::Build::Image.from_image(self)
end
def services
Gitlab::Ci::Build::Image.from_services(self)
end
def artifacts
[options[:artifacts]]
end
def cache
[options[:cache]]
end
def credentials
Gitlab::Ci::Build::Credentials::Factory.new(self).create!
end
......@@ -544,10 +565,35 @@ module Ci
@unscoped_project ||= Project.unscoped.find_by(id: gl_project_id)
end
CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
def predefined_variables
variables = [
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
{ key: 'CI_JOB_ID', value: id.to_s, public: true },
{ key: 'CI_JOB_NAME', value: name, public: true },
{ key: 'CI_JOB_STAGE', value: stage, public: true },
{ key: 'CI_JOB_TOKEN', value: token, public: false },
{ key: 'CI_COMMIT_SHA', value: sha, public: true },
{ key: 'CI_COMMIT_REF_NAME', value: ref, public: true },
{ key: 'CI_COMMIT_REF_SLUG', value: ref_slug, public: true },
{ key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER, public: true },
{ key: 'CI_REGISTRY_PASSWORD', value: token, public: false },
{ key: 'CI_REPOSITORY_URL', value: repo_url, public: false }
]
variables << { key: "CI_COMMIT_TAG", value: ref, public: true } if tag?
variables << { key: "CI_PIPELINE_TRIGGERED", value: 'true', public: true } if trigger_request
variables << { key: "CI_JOB_MANUAL", value: 'true', public: true } if action?
variables.concat(legacy_variables)
end
def legacy_variables
variables = [
{ key: 'CI_BUILD_ID', value: id.to_s, public: true },
{ key: 'CI_BUILD_TOKEN', value: token, public: false },
{ key: 'CI_BUILD_REF', value: sha, public: true },
......@@ -555,14 +601,12 @@ module Ci
{ key: 'CI_BUILD_REF_NAME', value: ref, public: true },
{ key: 'CI_BUILD_REF_SLUG', value: ref_slug, public: true },
{ key: 'CI_BUILD_NAME', value: name, public: true },
{ key: 'CI_BUILD_STAGE', value: stage, public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }
{ key: 'CI_BUILD_STAGE', value: stage, public: true }
]
variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if action?
variables << { key: "CI_BUILD_TAG", value: ref, public: true } if tag?
variables << { key: "CI_BUILD_TRIGGERED", value: 'true', public: true } if trigger_request
variables << { key: "CI_BUILD_MANUAL", value: 'true', public: true } if action?
variables
end
......
......@@ -29,8 +29,12 @@ module Ci
token[0...4]
end
def can_show_token?(user)
owner.blank? || owner == user
def legacy?
self.owner_id.blank?
end
def can_access_project?
self.owner_id.blank? || Ability.allowed?(self.owner, :create_build, project)
end
end
end
......@@ -7,7 +7,7 @@ module HasStatus
STARTED_STATUSES = %w[running success failed skipped manual].freeze
ACTIVE_STATUSES = %w[pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
ORDERED_STATUSES = %w[manual failed pending running canceled success skipped].freeze
ORDERED_STATUSES = %w[failed pending running manual canceled success skipped created].freeze
class_methods do
def status_sql
......
......@@ -157,6 +157,14 @@ class Environment < ActiveRecord::Base
project.monitoring_service.metrics(self) if has_metrics?
end
def has_metrics?
project.monitoring_service.present? && available? && last_deployment.present?
end
def metrics
project.monitoring_service.metrics(self) if has_metrics?
end
# An environment name is not necessarily suitable for use in URLs, DNS
# or other third-party contexts, so provide a slugified version. A slug has
# the following properties:
......
......@@ -9,11 +9,14 @@ class Issue < ActiveRecord::Base
include Elastic::IssuesSearch
include FasterCacheKeys
include RelativePositioning
<<<<<<< HEAD
WEIGHT_RANGE = 1..9
WEIGHT_ALL = 'Everything'.freeze
WEIGHT_ANY = 'Any Weight'.freeze
WEIGHT_NONE = 'No Weight'.freeze
=======
>>>>>>> ce/master
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
class OauthAccessGrant < Doorkeeper::AccessGrant
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
end
class OauthAccessToken < ActiveRecord::Base
class OauthAccessToken < Doorkeeper::AccessToken
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
end
class PersonalAccessToken < ActiveRecord::Base
include Expirable
include TokenAuthenticatable
add_authentication_token_field :token
......@@ -6,17 +7,30 @@ class PersonalAccessToken < ActiveRecord::Base
belongs_to :user
scope :active, -> { where(revoked: false).where("expires_at >= NOW() OR expires_at IS NULL") }
before_save :ensure_token
scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") }
scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
scope :with_impersonation, -> { where(impersonation: true) }
scope :without_impersonation, -> { where(impersonation: false) }
def self.generate(params)
personal_access_token = self.new(params)
personal_access_token.ensure_token
personal_access_token
end
validates :scopes, presence: true
validate :validate_api_scopes
def revoke!
self.revoked = true
self.save
end
def active?
!revoked? && !expired?
end
protected
def validate_api_scopes
unless scopes.all? { |scope| Gitlab::Auth::API_SCOPES.include?(scope.to_sym) }
errors.add :scopes, "can only contain API scopes"
end
end
end
......@@ -115,7 +115,10 @@ class Project < ActiveRecord::Base
has_one :external_wiki_service, dependent: :destroy
has_one :kubernetes_service, dependent: :destroy, inverse_of: :project
has_one :prometheus_service, dependent: :destroy, inverse_of: :project
<<<<<<< HEAD
has_one :index_status, dependent: :destroy
=======
>>>>>>> ce/master
has_one :mock_ci_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
......@@ -435,7 +438,7 @@ class Project < ActiveRecord::Base
end
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]
Gitlab.config.repositories.storages[repository_storage]['path']
end
def team
......
......@@ -36,7 +36,7 @@ class KubernetesService < DeploymentService
def initialize_properties
if properties.nil?
self.properties = {}
self.namespace = project.path if project.present?
self.namespace = "#{project.path}-#{project.id}" if project.present?
end
end
......
......@@ -58,10 +58,6 @@ class Repository
end
end
def self.storages
Gitlab.config.repositories.storages
end
def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace
@project = project
......@@ -324,11 +320,13 @@ class Repository
if !branch_name || branch_name == root_ref
branches.each do |branch|
cache.expire(:"diverging_commit_counts_#{branch.name}")
cache.expire(:"commit_count_#{branch.name}")
end
# In case a commit is pushed to a non-root branch we only have to flush the
# cache for said branch.
else
cache.expire(:"diverging_commit_counts_#{branch_name}")
cache.expire(:"commit_count_#{branch_name}")
end
end
......@@ -508,6 +506,16 @@ class Repository
end
cache_method :commit_count, fallback: 0
def commit_count_for_ref(ref)
return 0 unless exists?
begin
cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
rescue Rugged::ReferenceError
0
end
end
def branch_names
branches.map(&:name)
end
......
......@@ -345,8 +345,7 @@ class User < ActiveRecord::Base
end
def find_by_personal_access_token(token_string)
personal_access_token = PersonalAccessToken.active.find_by_token(token_string) if token_string
personal_access_token&.user
PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user
end
# Returns a user for the given SSH key.
......
module Ci
class TriggerPolicy < BasePolicy
def rules
delegate! @subject.project
if can?(:admin_build)
can! :admin_trigger if @subject.owner.blank? ||
@subject.owner == @user
can! :manage_trigger
end
end
end
end
module Ci
# This class responsible for assigning
# proper pending build to runner on runner API request
class RegisterBuildService
class RegisterJobService
include Gitlab::CurrentSettings
prepend EE::Ci::RegisterBuildService
......
......@@ -103,7 +103,11 @@ class GitPushService < BaseService
UpdateMergeRequestsWorker
.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
<<<<<<< HEAD
mirror_update = @project.mirror? && @project.repository.up_to_date_with_upstream?(branch_name)
=======
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
>>>>>>> ce/master
EventCreateService.new.push(@project, current_user, build_push_data)
@project.execute_hooks(build_push_data.dup, :push_hooks)
......
......@@ -35,13 +35,22 @@ class NamespaceValidator < ActiveModel::EachValidator
users
].freeze
WILDCARD_ROUTES = %w[tree commits wikis new edit create update logs_tree
preview blob blame raw files create_dir find_file].freeze
STRICT_RESERVED = (RESERVED + WILDCARD_ROUTES).freeze
def self.valid?(value)
!reserved?(value) && follow_format?(value)
end
def self.reserved?(value)
def self.reserved?(value, strict: false)
if strict
STRICT_RESERVED.include?(value)
else
RESERVED.include?(value)
end
end
def self.follow_format?(value)
value =~ Gitlab::Regex.namespace_regex
......@@ -54,7 +63,9 @@ class NamespaceValidator < ActiveModel::EachValidator
record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
end
if reserved?(value)
strict = record.is_a?(Group) && record.parent_id
if reserved?(value, strict: strict)
record.errors.add(attribute, "#{value} is a reserved name")
end
end
......
......@@ -14,10 +14,8 @@ class ProjectPathValidator < ActiveModel::EachValidator
# without tree as reserved name routing can match 'group/project' as group name,
# 'tree' as project name and 'deploy_keys' as route.
#
RESERVED = (NamespaceValidator::RESERVED -
%w[dashboard help ci admin search notes services assets profile public] +
%w[tree commits wikis new edit create update logs_tree
preview blob blame raw files create_dir find_file]).freeze
RESERVED = (NamespaceValidator::STRICT_RESERVED -
%w[dashboard help ci admin search notes services assets profile public]).freeze
def self.valid?(value)
!reserved?(value)
......
- page_title "Impersonation Tokens", @user.name, "Users"
= render 'admin/users/head'
.row.prepend-top-default
.col-lg-12
= render "shared/personal_access_tokens_form", path: admin_user_impersonation_tokens_path, impersonation: true, token: @impersonation_token, scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: true, active_tokens: @active_impersonation_tokens, inactive_tokens: @inactive_impersonation_tokens
......@@ -23,4 +23,6 @@
= link_to "SSH keys", keys_admin_user_path(@user)
= nav_link(controller: :identities) do
= link_to "Identities", admin_user_identities_path(@user)
= nav_link(controller: :impersonation_tokens) do
= link_to "Impersonation Tokens", admin_user_impersonation_tokens_path(@user)
.append-bottom-default
......@@ -6,10 +6,8 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
= link_to params.merge(rss_url_options), class: 'btn' do
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
......
......@@ -6,7 +6,7 @@
- if @last_push
= render "events/event_last_push", event: @last_push
- if @projects.any?
- if @projects.any? || params[:filter_projects]
= render 'projects'
- else
%h3 You don't have starred projects yet
......
- if inject_u2f_api?
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('u2f.js')
= page_specific_javascript_bundle_tag('u2f')
%div
= render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication'
......
......@@ -2,5 +2,5 @@
%tr.notes_holder{ class: ('hide' unless expanded) }
%td.notes_line{ colspan: 2 }
%td.notes_content
.content
.content{ class: ('hide' unless expanded) }
= render "discussions/notes", discussion: discussion
......@@ -27,6 +27,7 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Authorize", class: "btn btn-success wide pull-left"
= form_tag oauth_authorization_path, method: :delete do
= hidden_field_tag :client_id, @pre_auth.client.uid
......@@ -34,4 +35,5 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Deny", class: "btn btn-danger prepend-left-10"
- if current_user
.controls
.dropdown.project-settings-dropdown
%a.dropdown-new.btn.btn-default#project-settings-button{ href: '#', 'data-toggle' => 'dropdown' }
= icon('cog')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- can_edit = can?(current_user, :admin_project, @project)
= render 'layouts/nav/project_settings', can_edit: can_edit
- if can_edit
%li.divider
%li
= link_to edit_project_path(@project) do
Edit Project
- can_edit = can?(current_user, :admin_project, @project)
.scrolling-tabs-container{ class: nav_control_class }
.fade-left
= icon('angle-left')
......@@ -71,6 +55,17 @@
%span
Snippets
- if project_nav_tab? :settings
= nav_link(path: %w[projects#edit members#show integrations#show repository#show ci_cd#show pages#show]) do
= link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
%span
Settings
- else
= nav_link(path: %w[members#show]) do
= link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do
%span
Settings
-# Shortcut to Project > Activity
%li.hidden
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
......
......@@ -24,80 +24,11 @@
%hr
%h5.prepend-top-0
Add a Personal Access Token
%p.profile-settings-content
Pick a name for the application, and we'll give you a unique token.
= render "form", personal_access_token: @personal_access_token, scopes: @scopes
%hr
%h5 Active Personal Access Tokens (#{@active_personal_access_tokens.length})
- if @active_personal_access_tokens.present?
.table-responsive
%table.table.active-personal-access-tokens
%thead
%tr
%th Name
%th Created
%th Expires
%th Scopes
%th
%tbody
- @active_personal_access_tokens.each do |token|
%tr
%td= token.name
%td= token.created_at.to_date.to_s(:medium)
%td
- if token.expires_at.present?
= token.expires_at.to_date.to_s(:medium)
- else
%span.personal-access-tokens-never-expires-label Never
%td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
%td= link_to "Revoke", revoke_profile_personal_access_token_path(token), method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this token? This action cannot be undone." }
- else
.settings-message.text-center
You don't have any active tokens yet.
%hr
%h5 Inactive Personal Access Tokens (#{@inactive_personal_access_tokens.length})
- if @inactive_personal_access_tokens.present?
.table-responsive
%table.table.inactive-personal-access-tokens
%thead
%tr
%th Name
%th Created
%tbody
- @inactive_personal_access_tokens.each do |token|
%tr
%td= token.name
%td= token.created_at.to_date.to_s(:medium)
- else
.settings-message.text-center
There are no inactive tokens.
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
:javascript
var $dateField = $('#personal_access_token_expires_at');
var date = $dateField.val();
new Pikaday({
field: $dateField.get(0),
theme: 'gitlab-theme',
format: 'yyyy-mm-dd',
minDate: new Date(),
onSelect: function(dateText) {
$dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
});
$("#created-personal-access-token").click(function() {
this.select();
});
......@@ -4,7 +4,7 @@
- if inject_u2f_api?
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('u2f.js')
= page_specific_javascript_bundle_tag('u2f')
.row.prepend-top-default
.col-lg-3
......@@ -96,4 +96,3 @@
:javascript
var button = "<a class='btn btn-xs btn-warning pull-right' data-method='patch' href='#{skip_profile_two_factor_auth_path}'>Configure it later</a>";
$(".flash-alert").append(button);
- @no_container = true
= render "projects/head"
%div{ class: container_class }
.nav-block.activity-filter-block
......
- page_title "Activity"
= render "projects/head"
= render 'projects/last_push'
......
......@@ -19,7 +19,7 @@
= link_to title, '#'
= render_lock_icon(part_path)
%ul.blob-commit-info.table-list.hidden-xs
%ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project, ref: @ref
......
- @no_container = true
- page_title "#{@build.name} (##{@build.id})", "Jobs"
- trace_with_state = @build.trace_with_state
= render "projects/pipelines/head", build_subnav: true
%div{ class: container_class }
......
......@@ -34,6 +34,7 @@
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
%li.clearfix
= cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
- if can_collaborate_with_project?
%li.clearfix
= link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
%li.divider
......
......@@ -9,12 +9,13 @@
- cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do
%li.commit.table-list-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
%li.commit.flex-list.js-toggle-container{ id: "commit-#{commit.short_id}" }
.table-list-cell.avatar-cell.hidden-xs
.avatar-cell.hidden-xs
= author_avatar(commit, size: 36)
.table-list-cell.commit-content
.commit-detail
.commit-content
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title"
%span.commit-row-message.visible-xs-inline
&middot;
......@@ -33,7 +34,7 @@
committed
#{time_ago_with_tooltip(commit.committed_date)}
.table-list-cell.commit-actions.hidden-xs
.commit-actions.flex-row.hidden-xs
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
= clipboard_button(clipboard_text: commit.id, title: "Copy commit SHA to clipboard")
......
......@@ -11,4 +11,4 @@
%li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else
%ul.content-list.table-list= render commits, project: @project, ref: @ref
%ul.content-list= render commits, project: @project, ref: @ref
......@@ -4,7 +4,7 @@
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header #{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}
%li.commits-row
%ul.content-list.commit-list.table-list.table-wide
%ul.content-list.commit-list
= render commits, project: project, ref: ref
- if hidden > 0
......
......@@ -26,7 +26,11 @@
= render partial: 'projects/deploy_keys/deploy_key', collection: @deploy_keys.available_project_keys, as: :deploy_key
- else
.settings-message.text-center
<<<<<<< HEAD
No deploy keys from your projects could be found. Create one with the form above
=======
No deploy keys from your projects could be found. Create one with the form above or add existing one below.
>>>>>>> ce/master
- if @deploy_keys.any_available_public_keys_enabled?
%h5.prepend-top-default
Public deploy keys available to any project (#{@deploy_keys.available_public_keys_size})
......
- if can?(current_user, :create_deployment, deployment)
- actions = deployment.manual_actions
- if actions.present?
.inline
.btn-group
.dropdown
%a.dropdown-new.btn.btn-default{ type: 'button', 'data-toggle' => 'dropdown' }
%button.dropdown.dropdown-new.btn.btn-default{ type: 'button', 'data-toggle' => 'dropdown' }
= custom_icon('icon_play')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
......@@ -12,4 +12,3 @@
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= custom_icon('icon_play')
%span= action.name.humanize
......@@ -17,6 +17,6 @@
#{time_ago_with_tooltip(deployment.created_at)}
%td.hidden-xs
.pull-right
.pull-right.btn-group
= render 'projects/deployments/actions', deployment: deployment
= render 'projects/deployments/rollback', deployment: deployment
- email = local_assigns.fetch(:email, false)
- plain = local_assigns.fetch(:plain, false)
- discussions = local_assigns.fetch(:discussions, nil)
- type = line.type
- line_code = diff_file.line_code(line)
%tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } }
- if discussions && !line.meta?
- discussion = discussions[line_code]
%tr.line_holder{ class: type, id: (line_code unless plain) }
- case type
- when 'match'
= diff_match_line line.old_pos, line.new_pos, text: line.text
......@@ -11,12 +14,14 @@
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
%td.old_line.diff-line-num{ class: type, data: { linenumber: line.old_pos } }
%td.old_line.diff-line-num.js-avatar-container{ class: type, data: { linenumber: line.old_pos } }
- link_text = type == "new" ? " " : line.old_pos
- if plain
= link_text
- else
%a{ href: "##{line_code}", data: { linenumber: link_text } }
- if discussion && !plain
%diff-note-avatars{ "discussion-id" => discussion.id }
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = type == "old" ? " " : line.new_pos
- if plain
......@@ -29,9 +34,6 @@
- else
= diff_line_content(line.text)
- discussions = local_assigns.fetch(:discussions, nil)
- if discussions && !line.meta?
- discussion = discussions[line_code]
- if discussion
- if discussion
- discussion_expanded = local_assigns.fetch(:discussion_expanded, discussion.expanded?)
= render "discussions/diff_discussion", discussion: discussion, expanded: discussion_expanded
......@@ -4,6 +4,9 @@
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
- last_line = right.new_pos if right
- unless @diff_notes_disabled
- discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
%tr.line_holder.parallel
- if left
- case left.type
......@@ -15,8 +18,10 @@
- else
- left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left)
%td.old_line.diff-line-num{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
%td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
- if discussion_left
%diff-note-avatars{ "discussion-id" => discussion_left.id }
%td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text)
- else
%td.old_line.diff-line-num.empty-cell
......@@ -32,15 +37,15 @@
- else
- right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right)
%td.new_line.diff-line-num{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
%td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
- if discussion_right
%diff-note-avatars{ "discussion-id" => discussion_right.id }
%td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel
- unless @diff_notes_disabled
- discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
- if discussion_left || discussion_right
= render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
- if !diff_file.new_file && !diff_file.deleted_file && diff_file.diff_lines.any?
......
= render "projects/settings/head"
.project-edit-container
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
......
......@@ -16,7 +16,7 @@
- if can?(current_user, :create_deployment, @environment) && @environment.can_stop?
= link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
.deployments-container
.environments-container
- if @deployments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
......
......@@ -21,7 +21,7 @@
selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
= dropdown_toggle local_assigns.fetch(f.object.source_branch, "Select source branch"), { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-branch
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
......@@ -30,7 +30,7 @@
branches: @merge_request.source_branches,
selected: f.object.source_branch
.panel-footer
= icon('spinner spin', class: 'js-source-loading')
.text-center= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
.col-md-6
......@@ -60,7 +60,7 @@
branches: @merge_request.target_branches,
selected: f.object.target_branch
.panel-footer
= icon('spinner spin', class: "js-target-loading")
.text-center= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
- if @merge_request.errors.any?
......
- if @pipeline
.mr-widget-heading
- %w[success success_with_warnings skipped canceled failed running pending].each do |status|
- %w[success success_with_warnings skipped manual canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) }
%div{ class: "ci-status-icon ci-status-icon-#{status}" }
= link_to namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'icon-link' do
......
......@@ -37,6 +37,8 @@
= render 'projects/merge_requests/widget/open/rebase'
- elsif !@merge_request.mergeable_discussions_state?
= render 'projects/merge_requests/widget/open/unresolved_discussions'
- elsif @pipeline&.blocked?
= render 'projects/merge_requests/widget/open/manual'
- elsif @merge_request.can_be_merged? || resolved_conflicts
= render 'projects/merge_requests/widget/open/accept'
......
%h4
Pipeline blocked
%p
The pipeline for this merge request requires a manual action to proceed.
......@@ -2,7 +2,7 @@
- return if note.cross_reference_not_visible_for?(current_user)
- note_editable = note_editable?(note)
%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
.timeline-entry-inner
.timeline-icon
%a{ href: user_path(note.author) }
......@@ -30,11 +30,15 @@
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
%resolve-btn{ "discussion-id" => "#{note.discussion_id}",
%resolve-btn{ "project-path" => project_path(note.project),
"discussion-id" => note.discussion_id,
":note-id" => note.id,
":resolved" => note.resolved?,
":can-resolve" => can_resolve,
"resolved-by" => "#{note.resolved_by.try(:name)}",
":author-name" => "'#{j(note.author.name)}'",
"author-avatar" => note.author.avatar_url,
":note-truncated" => "'#{truncate(note.note, length: 17)}'",
":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
"v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true,
"ref" => "note_#{note.id}" }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment