Commit 0e00db61 authored by Alejandro Rodríguez's avatar Alejandro Rodríguez

Merge branch 'ce-upstream' into 'master'

CE upstream

See merge request !867
parents eda67b59 1d1795fe
...@@ -147,6 +147,7 @@ gem 'acts-as-taggable-on', '~> 4.0' ...@@ -147,6 +147,7 @@ gem 'acts-as-taggable-on', '~> 4.0'
gem 'sidekiq', '~> 4.2' gem 'sidekiq', '~> 4.2'
gem 'sidekiq-cron', '~> 0.4.0' gem 'sidekiq-cron', '~> 0.4.0'
gem 'redis-namespace', '~> 1.5.2' gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4'
# HTTP requests # HTTP requests
gem 'httparty', '~> 0.13.3' gem 'httparty', '~> 0.13.3'
......
...@@ -709,6 +709,8 @@ GEM ...@@ -709,6 +709,8 @@ GEM
redis-namespace (>= 1.5.2) redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24) rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0) sidekiq (>= 4.0.0)
sidekiq-limit_fetch (3.4.0)
sidekiq (>= 4)
simplecov (0.12.0) simplecov (0.12.0)
docile (~> 1.1.0) docile (~> 1.1.0)
json (>= 1.8, < 3) json (>= 1.8, < 3)
...@@ -994,6 +996,7 @@ DEPENDENCIES ...@@ -994,6 +996,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.2) sidekiq (~> 4.2)
sidekiq-cron (~> 0.4.0) sidekiq-cron (~> 0.4.0)
sidekiq-limit_fetch (~> 3.4)
simplecov (= 0.12.0) simplecov (= 0.12.0)
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1) spinach-rails (~> 0.2.1)
......
...@@ -112,6 +112,10 @@ GitLab is a Ruby on Rails application that runs on the following software: ...@@ -112,6 +112,10 @@ GitLab is a Ruby on Rails application that runs on the following software:
For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html). For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html).
## UX design
Please adhere to the [UX Guide](doc/development/ux_guide/readme.md) when creating designs and implementing code.
## Third-party applications ## Third-party applications
There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages. There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages.
......
...@@ -59,11 +59,28 @@ ...@@ -59,11 +59,28 @@
document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch); document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
window.addEventListener('hashchange', gl.utils.shiftWindow); window.addEventListener('hashchange', gl.utils.shiftWindow);
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
window.adjustScroll = function() {
var navbar = document.querySelector('.navbar-gitlab');
var subnav = document.querySelector('.layout-nav');
var fixedTabs = document.querySelector('.js-tabs-affix');
adjustment = 0;
if (navbar) adjustment -= navbar.offsetHeight;
if (subnav) adjustment -= subnav.offsetHeight;
if (fixedTabs) adjustment -= fixedTabs.offsetHeight;
return scrollBy(0, adjustment);
};
window.addEventListener("hashchange", adjustScroll);
window.onload = function () { window.onload = function () {
// Scroll the window to avoid the topnav bar // Scroll the window to avoid the topnav bar
// https://github.com/twitter/bootstrap/issues/1768 // https://github.com/twitter/bootstrap/issues/1768
if (location.hash) { if (location.hash) {
return setTimeout(gl.utils.shiftWindow, 100); return setTimeout(adjustScroll, 100);
} }
}; };
......
...@@ -23,6 +23,8 @@ $(() => { ...@@ -23,6 +23,8 @@ $(() => {
gl.IssueBoardsApp.$destroy(true); gl.IssueBoardsApp.$destroy(true);
} }
Store.create();
gl.IssueBoardsApp = new Vue({ gl.IssueBoardsApp = new Vue({
el: $boardApp, el: $boardApp,
components: { components: {
...@@ -39,16 +41,15 @@ $(() => { ...@@ -39,16 +41,15 @@ $(() => {
issueLinkBase: $boardApp.dataset.issueLinkBase, issueLinkBase: $boardApp.dataset.issueLinkBase,
detailIssue: Store.detail detailIssue: Store.detail
}, },
init: Store.create.bind(Store),
computed: { computed: {
detailIssueVisible () { detailIssueVisible () {
return Object.keys(this.detailIssue.issue).length; return Object.keys(this.detailIssue.issue).length;
} },
}, },
created () { created () {
gl.boardService = new BoardService(this.endpoint, this.boardId); gl.boardService = new BoardService(this.endpoint, this.boardId);
}, },
ready () { mounted () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
gl.boardService.all() gl.boardService.all()
.then((resp) => { .then((resp) => {
...@@ -62,6 +63,8 @@ $(() => { ...@@ -62,6 +63,8 @@ $(() => {
} }
}); });
this.state.lists = _.sortBy(this.state.lists, 'position');
Store.addBlankState(); Store.addBlankState();
this.loading = false; this.loading = false;
}); });
...@@ -72,6 +75,9 @@ $(() => { ...@@ -72,6 +75,9 @@ $(() => {
el: '#js-boards-seach', el: '#js-boards-seach',
data: { data: {
filters: Store.state.filters filters: Store.state.filters
},
mounted () {
gl.issueBoards.newListDropdownInit();
} }
}); });
}); });
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.Board = Vue.extend({ gl.issueBoards.Board = Vue.extend({
template: '#js-board-template',
components: { components: {
'board-list': gl.issueBoards.BoardList, 'board-list': gl.issueBoards.BoardList,
'board-delete': gl.issueBoards.BoardDelete, 'board-delete': gl.issueBoards.BoardDelete,
...@@ -24,7 +25,6 @@ ...@@ -24,7 +25,6 @@
return { return {
detailIssue: Store.detail, detailIssue: Store.detail,
filters: Store.state.filters, filters: Store.state.filters,
showIssueForm: false
}; };
}, },
watch: { watch: {
...@@ -58,10 +58,10 @@ ...@@ -58,10 +58,10 @@
}, },
methods: { methods: {
showNewIssueForm() { showNewIssueForm() {
this.showIssueForm = !this.showIssueForm; this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
} }
}, },
ready () { mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
disabled: this.disabled, disabled: this.disabled,
group: 'boards', group: 'boards',
...@@ -72,13 +72,9 @@ ...@@ -72,13 +72,9 @@
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray(), const order = this.sortable.toArray(),
$board = this.$parent.$refs.board[e.oldIndex + 1], list = Store.findList('id', parseInt(e.item.dataset.id));
list = $board.list;
$board.$destroy(true);
this.$nextTick(() => { this.$nextTick(() => {
Store.state.lists.splice(e.newIndex, 0, list);
Store.moveList(list, order); Store.moveList(list, order);
}); });
} }
...@@ -87,8 +83,5 @@ ...@@ -87,8 +83,5 @@
this.sortable = Sortable.create(this.$el.parentNode, options); this.sortable = Sortable.create(this.$el.parentNode, options);
}, },
beforeDestroy () {
Store.state.lists.$remove(this.list);
}
}); });
})(); })();
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
}); });
}); });
Store.state.lists = _.sortBy(Store.state.lists, 'position');
// Save the labels // Save the labels
gl.boardService.generateDefaultLists() gl.boardService.generateDefaultLists()
.then((resp) => { .then((resp) => {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardCard = Vue.extend({ gl.issueBoards.BoardCard = Vue.extend({
template: '#js-board-list-card',
props: { props: {
list: Object, list: Object,
issue: Object, issue: Object,
...@@ -53,11 +54,6 @@ ...@@ -53,11 +54,6 @@
mouseDown () { mouseDown () {
this.showDetail = true; this.showDetail = true;
}, },
mouseMove () {
if (this.showDetail) {
this.showDetail = false;
}
},
showIssue (e) { showIssue (e) {
const targetTagName = e.target.tagName.toLowerCase(); const targetTagName = e.target.tagName.toLowerCase();
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardList = Vue.extend({ gl.issueBoards.BoardList = Vue.extend({
template: '#js-board-list-template',
components: { components: {
'board-card': gl.issueBoards.BoardCard, 'board-card': gl.issueBoards.BoardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue 'board-new-issue': gl.issueBoards.BoardNewIssue
...@@ -19,20 +20,20 @@ ...@@ -19,20 +20,20 @@
issues: Array, issues: Array,
loading: Boolean, loading: Boolean,
issueLinkBase: String, issueLinkBase: String,
showIssueForm: Boolean
}, },
data () { data () {
return { return {
scrollOffset: 250, scrollOffset: 250,
filters: Store.state.filters, filters: Store.state.filters,
showCount: false showCount: false,
showIssueForm: false
}; };
}, },
watch: { watch: {
filters: { filters: {
handler () { handler () {
this.list.loadingMore = false; this.list.loadingMore = false;
this.$els.list.scrollTop = 0; this.$refs.list.scrollTop = 0;
}, },
deep: true deep: true
}, },
...@@ -51,15 +52,20 @@ ...@@ -51,15 +52,20 @@
}); });
} }
}, },
computed: {
orderedIssues () {
return _.sortBy(this.issues, 'priority');
},
},
methods: { methods: {
listHeight () { listHeight () {
return this.$els.list.getBoundingClientRect().height; return this.$refs.list.getBoundingClientRect().height;
}, },
scrollHeight () { scrollHeight () {
return this.$els.list.scrollHeight; return this.$refs.list.scrollHeight;
}, },
scrollTop () { scrollTop () {
return this.$els.list.scrollTop + this.listHeight(); return this.$refs.list.scrollTop + this.listHeight();
}, },
loadNextPage () { loadNextPage () {
const getIssues = this.list.nextPage(); const getIssues = this.list.nextPage();
...@@ -72,7 +78,7 @@ ...@@ -72,7 +78,7 @@
} }
}, },
}, },
ready () { mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
group: 'issues', group: 'issues',
sort: false, sort: false,
...@@ -81,23 +87,27 @@ ...@@ -81,23 +87,27 @@
onStart: (e) => { onStart: (e) => {
const card = this.$refs.issue[e.oldIndex]; const card = this.$refs.issue[e.oldIndex];
card.showDetail = false;
Store.moving.issue = card.issue; Store.moving.issue = card.issue;
Store.moving.list = card.list; Store.moving.list = card.list;
gl.issueBoards.onStart(); gl.issueBoards.onStart();
}, },
onAdd: (e) => { onAdd: (e) => {
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); // Add the element back to original list to allow Vue to handle DOM updates
e.from.appendChild(e.item);
this.$nextTick(() => {
// Update the issues once we know the element has been moved
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
});
}, },
onRemove: (e) => {
this.$refs.issue[e.oldIndex].$destroy(true);
}
}); });
this.sortable = Sortable.create(this.$els.list, options); this.sortable = Sortable.create(this.$refs.list, options);
// Scroll event on list to load more // Scroll event on list to load more
this.$els.list.onscroll = () => { this.$refs.list.onscroll = () => {
if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) { if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) {
this.loadNextPage(); this.loadNextPage();
} }
......
...@@ -2,29 +2,27 @@ ...@@ -2,29 +2,27 @@
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
const Store = gl.issueBoards.BoardsStore;
gl.issueBoards.BoardSelectorForm = Vue.extend({ gl.issueBoards.BoardSelectorForm = Vue.extend({
props: {
type: String,
currentBoard: Object,
currentPage: String,
reload: Boolean,
},
data() { data() {
return { return {
board: { board: {
id: false, id: false,
name: '', name: '',
}, },
currentBoard: Store.state.currentBoard,
currentPage: Store.state.currentPage,
}; };
}, },
ready() { mounted() {
if (this.currentBoard && Object.keys(this.currentBoard).length) { if (this.currentBoard && Object.keys(this.currentBoard).length && this.currentPage === 'edit') {
this.board = Vue.util.extend({}, this.currentBoard); this.board = Vue.util.extend({}, this.currentBoard);
} }
}, },
computed: { computed: {
buttonText() { buttonText() {
if (this.type === 'new') { if (this.currentPage === 'new') {
return 'Create'; return 'Create';
} }
...@@ -35,16 +33,16 @@ ...@@ -35,16 +33,16 @@
submit() { submit() {
gl.boardService.createBoard(this.board) gl.boardService.createBoard(this.board)
.then(() => { .then(() => {
if (this.currentBoard) { if (this.currentBoard && this.currentPage === 'edit') {
this.currentBoard.name = this.board.name; this.currentBoard.name = this.board.name;
} }
// Enable the button thanks to our jQuery disabling it // Enable the button thanks to our jQuery disabling it
$(this.$els.submitBtn).enable(); $(this.$refs.submitBtn).enable();
// Reset the selectors current page // Reset the selectors current page
this.currentPage = ''; Store.state.currentPage = '';
this.reload = true; Store.state.reload = true;
}); });
}, },
}, },
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
gl.issueBoards.BoardNewIssue = Vue.extend({ gl.issueBoards.BoardNewIssue = Vue.extend({
props: { props: {
list: Object, list: Object,
showIssueForm: Boolean
}, },
data() { data() {
return { return {
...@@ -15,11 +14,6 @@ ...@@ -15,11 +14,6 @@
error: false error: false
}; };
}, },
watch: {
showIssueForm () {
this.$els.input.focus();
}
},
methods: { methods: {
submit(e) { submit(e) {
e.preventDefault(); e.preventDefault();
...@@ -37,28 +31,30 @@ ...@@ -37,28 +31,30 @@
this.list.newIssue(issue) this.list.newIssue(issue)
.then((data) => { .then((data) => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions // Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$els.submitButton).enable(); $(this.$refs.submitButton).enable();
Store.detail.issue = issue; Store.detail.issue = issue;
}) })
.catch(() => { .catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions // Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$els.submitButton).enable(); $(this.$refs.submitButton).enable();
// Remove the issue // Remove the issue
this.list.removeIssue(issue); this.list.removeIssue(issue);
// Show error message // Show error message
this.error = true; this.error = true;
this.showIssueForm = true;
}); });
this.cancel(); this.cancel();
}, },
cancel() { cancel() {
this.showIssueForm = false;
this.title = ''; this.title = '';
this.$parent.showIssueForm = false;
} }
} },
mounted() {
this.$refs.input.focus();
},
}); });
})(); })();
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
this.detail.issue = {}; this.detail.issue = {};
} }
}, },
ready () { mounted () {
new IssuableContext(this.currentUser); new IssuableContext(this.currentUser);
new MilestoneSelect(); new MilestoneSelect();
new gl.DueDateSelectors(); new gl.DueDateSelectors();
......
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
const Store = gl.issueBoards.BoardsStore;
Store.createNewListDropdownData();
gl.issueBoards.BoardsSelector = Vue.extend({ gl.issueBoards.BoardsSelector = Vue.extend({
components: { components: {
'board-selector-form': gl.issueBoards.BoardSelectorForm, 'board-selector-form': gl.issueBoards.BoardSelectorForm,
...@@ -17,8 +21,7 @@ ...@@ -17,8 +21,7 @@
open: false, open: false,
loading: true, loading: true,
boards: [], boards: [],
currentPage: '', state: Store.state,
reload: false,
}; };
}, },
watch: { watch: {
...@@ -33,6 +36,15 @@ ...@@ -33,6 +36,15 @@
}, },
}, },
computed: { computed: {
currentPage() {
return this.state.currentPage;
},
reload() {
return this.state.reload;
},
board() {
return this.state.currentBoard;
},
showDelete() { showDelete() {
return this.boards.length > 1; return this.boards.length > 1;
}, },
...@@ -50,7 +62,7 @@ ...@@ -50,7 +62,7 @@
}, },
methods: { methods: {
showPage(page) { showPage(page) {
this.currentPage = page; this.state.currentPage = page;
}, },
toggleDropdown() { toggleDropdown() {
this.open = !this.open; this.open = !this.open;
...@@ -68,5 +80,8 @@ ...@@ -68,5 +80,8 @@
} }
}, },
}, },
created() {
this.state.currentBoard = this.currentBoard;
},
}); });
})(); })();
/* eslint-disable */ /* eslint-disable */
$(() => { (() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
$(document).off('created.label').on('created.label', (e, label) => { $(document).off('created.label').on('created.label', (e, label) => {
...@@ -15,54 +18,58 @@ $(() => { ...@@ -15,54 +18,58 @@ $(() => {
}); });
}); });
$('.js-new-board-list').each(function () { gl.issueBoards.newListDropdownInit = () => {
const $this = $(this); $('.js-new-board-list').each(function () {
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path')); const $this = $(this);
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
$this.glDropdown({ $this.glDropdown({
data(term, callback) { data(term, callback) {
$.get($this.attr('data-labels')) $.get($this.attr('data-labels'))
.then((resp) => { .then((resp) => {
callback(resp); callback(resp);
}); });
}, },
renderRow (label) { renderRow (label) {
const active = Store.findList('title', label.title), const active = Store.findList('title', label.title),
$li = $('<li />'), $li = $('<li />'),
$a = $('<a />', { $a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''), class: (active ? `is-active js-board-list-${active.id}` : ''),
text: label.title, text: label.title,
href: '#' href: '#'
}), }),
$labelColor = $('<span />', { $labelColor = $('<span />', {
class: 'dropdown-label-box', class: 'dropdown-label-box',
style: `background-color: ${label.color}` style: `background-color: ${label.color}`
}); });
return $li.append($a.prepend($labelColor)); return $li.append($a.prepend($labelColor));
}, },
search: { search: {
fields: ['title'] fields: ['title']
}, },
filterable: true, filterable: true,
selectable: true, selectable: true,
multiSelect: true, multiSelect: true,
clicked (label, $el, e) { clicked (label, $el, e) {
e.preventDefault(); e.preventDefault();
if (!Store.findList('title', label.title)) { if (!Store.findList('title', label.title)) {
Store.new({ Store.new({
title: label.title,
position: Store.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
title: label.title, title: label.title,
color: label.color position: Store.state.lists.length - 2,
} list_type: 'label',
}); label: {
id: label.id,
title: label.title,
color: label.color
}
});
Store.state.lists = _.sortBy(Store.state.lists, 'position');
}
} }
} });
}); });
}); };
}); })();
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
fallbackOnBody: true, fallbackOnBody: true,
ghostClass: 'is-ghost', ghostClass: 'is-ghost',
filter: '.board-delete, .btn', filter: '.board-delete, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 50, delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20, scrollSpeed: 20,
onStart: gl.issueBoards.onStart, onStart: gl.issueBoards.onStart,
......
...@@ -42,7 +42,8 @@ class List { ...@@ -42,7 +42,8 @@ class List {
} }
destroy () { destroy () {
gl.issueBoards.BoardsStore.state.lists.$remove(this); const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this);
gl.issueBoards.BoardsStore.state.lists.splice(index, 1);
gl.issueBoards.BoardsStore.updateNewListDropdown(this.id); gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
gl.boardService.destroyList(this.id); gl.boardService.destroyList(this.id);
......
...@@ -23,6 +23,11 @@ ...@@ -23,6 +23,11 @@
search: '' search: ''
}; };
}, },
createNewListDropdownData() {
this.state.currentBoard = {};
this.state.currentPage = '';
this.state.reload = false;
},
addList (listObj) { addList (listObj) {
const list = new List(listObj); const list = new List(listObj);
this.state.lists.push(list); this.state.lists.push(list);
...@@ -39,6 +44,8 @@ ...@@ -39,6 +44,8 @@
// Remove any new issues from the backlog // Remove any new issues from the backlog
// as they will be visible in the new list // as they will be visible in the new list
list.issues.forEach(backlogList.removeIssue.bind(backlogList)); list.issues.forEach(backlogList.removeIssue.bind(backlogList));
this.state.lists = _.sortBy(this.state.lists, 'position');
}); });
this.removeBlankState(); this.removeBlankState();
}, },
...@@ -58,6 +65,8 @@ ...@@ -58,6 +65,8 @@
title: 'Welcome to your Issue Board!', title: 'Welcome to your Issue Board!',
position: 0 position: 0
}); });
this.state.lists = _.sortBy(this.state.lists, 'position');
}, },
removeBlankState () { removeBlankState () {
this.removeList('blank'); this.removeList('blank');
......
/* eslint-disable */ /* eslint-disable */
((w) => { (() => {
w.CommentAndResolveBtn = Vue.extend({ const CommentAndResolveBtn = Vue.extend({
props: { props: {
discussionId: String, discussionId: String,
textareaIsEmpty: Boolean },
data() {
return {
textareaIsEmpty: true
}
}, },
computed: { computed: {
discussion: function () { discussion: function () {
...@@ -35,7 +39,7 @@ ...@@ -35,7 +39,7 @@
} }
} }
}, },
ready: function () { mounted: function () {
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`); const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`);
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';
...@@ -47,4 +51,6 @@ ...@@ -47,4 +51,6 @@
$(`#new-discussion-note-form-${this.discussionId} .note-textarea`).off('input.comment-and-resolve-btn'); $(`#new-discussion-note-form-${this.discussionId} .note-textarea`).off('input.comment-and-resolve-btn');
} }
}); });
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
})(window); })(window);
/* eslint-disable */ /* eslint-disable */
((w) => { (() => {
w.ResolveBtn = Vue.extend({ const ResolveBtn = Vue.extend({
props: { props: {
noteId: Number, noteId: Number,
discussionId: String, discussionId: String,
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
}, },
methods: { methods: {
updateTooltip: function () { updateTooltip: function () {
$(this.$els.button) $(this.$refs.button)
.tooltip('hide') .tooltip('hide')
.tooltip('fixTitle'); .tooltip('fixTitle');
}, },
...@@ -89,8 +89,8 @@ ...@@ -89,8 +89,8 @@
}); });
} }
}, },
compiled: function () { mounted: function () {
$(this.$els.button).tooltip({ $(this.$refs.button).tooltip({
container: 'body' container: 'body'
}); });
}, },
...@@ -101,4 +101,6 @@ ...@@ -101,4 +101,6 @@
CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy); CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
} }
}); });
})(window);
Vue.component('resolve-btn', ResolveBtn);
})();
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
computed: { computed: {
allResolved: function () { allResolved: function () {
return this.resolvedDiscussionCount === this.discussionCount; return this.resolvedDiscussionCount === this.discussionCount;
},
resolvedCountText() {
return this.discussionCount === 1 ? 'discussion' : 'discussions';
} }
} }
}); });
......
/* eslint-disable */ /* eslint-disable */
((w) => { (() => {
w.ResolveDiscussionBtn = Vue.extend({ const ResolveDiscussionBtn = Vue.extend({
props: { props: {
discussionId: String, discussionId: String,
mergeRequestId: Number, mergeRequestId: Number,
...@@ -54,4 +54,6 @@ ...@@ -54,4 +54,6 @@
CommentsStore.createDiscussion(this.discussionId, this.canResolve); CommentsStore.createDiscussion(this.discussionId, this.canResolve);
} }
}); });
})(window);
Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
})();
...@@ -8,24 +8,35 @@ ...@@ -8,24 +8,35 @@
//= require_directory ./components //= require_directory ./components
$(() => { $(() => {
window.DiffNotesApp = new Vue({ const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
el: '#diff-notes-app',
components: { window.gl = window.gl || {};
'resolve-btn': ResolveBtn, window.gl.diffNoteApps = {};
'resolve-discussion-btn': ResolveDiscussionBtn,
'comment-and-resolve-btn': CommentAndResolveBtn gl.diffNotesCompileComponents = () => {
}, const $components = $(COMPONENT_SELECTOR).filter(function () {
methods: { return $(this).closest('resolve-count').length !== 1;
compileComponents: function () { });
const $components = $('resolve-btn, resolve-discussion-btn, jump-to-discussion');
if ($components.length) { if ($components) {
$components.each(function () { $components.each(function () {
DiffNotesApp.$compile($(this).get(0)); const $this = $(this);
}); const noteId = $this.attr(':note-id');
const tmp = Vue.extend({
template: $this.get(0).outerHTML
});
const tmpApp = new tmp().$mount();
if (noteId) {
gl.diffNoteApps[`note_${noteId}`] = tmpApp;
} }
}
$this.replaceWith(tmpApp.$el);
});
} }
}); };
gl.diffNotesCompileComponents();
new Vue({ new Vue({
el: '#resolve-count-app', el: '#resolve-count-app',
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
this.loadEditor(); this.loadEditor();
} }
}, },
ready() { mounted() {
if (this.file.loadEditor) { if (this.file.loadEditor) {
this.loadEditor(); this.loadEditor();
} }
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.parallelConflictLine = Vue.extend({
props: {
file: Object,
line: Object
},
mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
template: '#parallel-conflict-line'
});
})(window.gl || (window.gl = {}));
...@@ -7,10 +7,22 @@ ...@@ -7,10 +7,22 @@
props: { props: {
file: Object file: Object
}, },
mixins: [global.mergeConflicts.utils], mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
components: { template: `
'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine <table>
} <tr class="line_holder parallel" v-for="section in file.parallelLines">
<template v-for="line in section">
<td class="diff-line-num header" :class="lineCssClass(line)" v-if="line.isHeader"></td>
<td class="line_content header" :class="lineCssClass(line)" v-if="line.isHeader">
<strong>{{line.richText}}</strong>
<button class="btn" @click="handleSelected(file, line.id, line.section)">{{line.buttonTitle}}</button>
</td>
<td class="diff-line-num old_line" :class="lineCssClass(line)" v-if="!line.isHeader">{{line.lineNumber}}</td>
<td class="line_content parallel" :class="lineCssClass(line)" v-if="!line.isHeader" v-html="line.richText"></td>
</template>
</tr>
</table>
`,
}); });
})(window.gl || (window.gl = {})); })(window.gl || (window.gl = {}));
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
//= require ./mixins/line_conflict_actions //= require ./mixins/line_conflict_actions
//= require ./components/diff_file_editor //= require ./components/diff_file_editor
//= require ./components/inline_conflict_lines //= require ./components/inline_conflict_lines
//= require ./components/parallel_conflict_line
//= require ./components/parallel_conflict_lines //= require ./components/parallel_conflict_lines
$(() => { $(() => {
...@@ -49,7 +48,7 @@ $(() => { ...@@ -49,7 +48,7 @@ $(() => {
mergeConflictsStore.setLoadingState(false); mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => { this.$nextTick(() => {
$(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight(); $('.js-syntax-highlight').syntaxHighlight();
}); });
}); });
}, },
......
...@@ -130,7 +130,7 @@ ...@@ -130,7 +130,7 @@
MergeRequestTabs.prototype.scrollToElement = function(container) { MergeRequestTabs.prototype.scrollToElement = function(container) {
var $el, navBarHeight; var $el, navBarHeight;
if (window.location.hash) { if (window.location.hash) {
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight(); navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + document.querySelector('.js-tabs-affix').offsetHeight;
$el = $(container + " " + window.location.hash + ":not(.match)"); $el = $(container + " " + window.location.hash + ":not(.match)");
if ($el.length) { if ($el.length) {
return $.scrollTo(container + " " + window.location.hash + ":not(.match)", { return $.scrollTo(container + " " + window.location.hash + ":not(.match)", {
...@@ -227,8 +227,8 @@ ...@@ -227,8 +227,8 @@
return function(data) { return function(data) {
$('#diffs').html(data.html); $('#diffs').html(data.html);
if (typeof DiffNotesApp !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
DiffNotesApp.compileComponents(); gl.diffNotesCompileComponents();
} }
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
......
...@@ -325,8 +325,8 @@ ...@@ -325,8 +325,8 @@
discussionContainer.append(note_html); discussionContainer.append(note_html);
} }
if (typeof DiffNotesApp !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
DiffNotesApp.compileComponents(); gl.diffNotesCompileComponents();
} }
gl.utils.localTimeAgo($('.js-timeago', note_html), false); gl.utils.localTimeAgo($('.js-timeago', note_html), false);
...@@ -466,8 +466,8 @@ ...@@ -466,8 +466,8 @@
$note_li.replaceWith($html); $note_li.replaceWith($html);
if (typeof DiffNotesApp !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
DiffNotesApp.compileComponents(); gl.diffNotesCompileComponents();
} }
}; };
...@@ -559,11 +559,9 @@ ...@@ -559,11 +559,9 @@
note = $(el); note = $(el);
notes = note.closest(".notes"); notes = note.closest(".notes");
if (typeof DiffNotesApp !== "undefined" && DiffNotesApp !== null) { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
ref = DiffNotesApp.$refs[noteId]; if (gl.diffNoteApps[noteId]) {
gl.diffNoteApps[noteId].$destroy();
if (ref) {
ref.$destroy(true);
} }
} }
...@@ -643,11 +641,12 @@ ...@@ -643,11 +641,12 @@
form.find('.js-note-target-close').remove(); form.find('.js-note-target-close').remove();
this.setupNoteForm(form); this.setupNoteForm(form);
if (typeof DiffNotesApp !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
var $commentBtn = form.find('comment-and-resolve-btn'); var $commentBtn = form.find('comment-and-resolve-btn');
$commentBtn $commentBtn
.attr(':discussion-id', "'" + dataHolder.data('discussionId') + "'"); .attr(':discussion-id', "'" + dataHolder.data('discussionId') + "'");
DiffNotesApp.$compile($commentBtn.get(0));
gl.diffNotesCompileComponents();
} }
form.find(".js-note-text").focus(); form.find(".js-note-text").focus();
......
...@@ -45,15 +45,15 @@ ...@@ -45,15 +45,15 @@
this.content.hide(); this.content.hide();
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down'); this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
this.collapsedContent.show(); this.collapsedContent.show();
if (typeof DiffNotesApp !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
DiffNotesApp.compileComponents(); gl.diffNotesCompileComponents();
} }
} else if (this.content) { } else if (this.content) {
this.collapsedContent.hide(); this.collapsedContent.hide();
this.content.show(); this.content.show();
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
if (typeof DiffNotesApp !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
DiffNotesApp.compileComponents(); gl.diffNotesCompileComponents();
} }
} else { } else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
...@@ -76,8 +76,8 @@ ...@@ -76,8 +76,8 @@
} }
_this.collapsedContent.after(_this.content); _this.collapsedContent.after(_this.content);
if (typeof DiffNotesApp !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
DiffNotesApp.compileComponents(); gl.diffNotesCompileComponents();
} }
if (cb) cb(); if (cb) cb();
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
display: none; display: none;
} }
.group-right-buttons { .group-buttons {
display: none; display: none;
} }
......
...@@ -165,8 +165,12 @@ ...@@ -165,8 +165,12 @@
} }
} }
.board-list { .board-list-component {
height: calc(100% - 49px); height: calc(100% - 49px);
}
.board-list {
height: 100%;
margin-bottom: 0; margin-bottom: 0;
padding: 5px; padding: 5px;
list-style: none; list-style: none;
...@@ -174,7 +178,7 @@ ...@@ -174,7 +178,7 @@
overflow-x: hidden; overflow-x: hidden;
&.is-smaller { &.is-smaller {
height: calc(100% - 185px); height: calc(100% - 136px);
} }
} }
......
...@@ -62,6 +62,8 @@ ...@@ -62,6 +62,8 @@
.ci-status-link { .ci-status-link {
display: inline-block; display: inline-block;
position: relative;
top: 1px;
} }
.btn-clipboard, .btn-clipboard,
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
} }
.group-row { .group-row {
.stats { .stats {
float: right; float: right;
line-height: $list-text-height; line-height: $list-text-height;
...@@ -28,31 +27,14 @@ ...@@ -28,31 +27,14 @@
} }
.ldap-group-links { .ldap-group-links {
.form-actions { .form-actions {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
} }
} }
.groups-cover-block { .group-buttons {
.notification-dropdown {
.container-fluid { display: inline-block;
position: relative;
}
.group-right-buttons {
position: absolute;
right: 16px;
.btn {
@include btn-gray;
padding: 3px 10px;
background-color: $background-color;
}
}
.group-avatar {
border: 0;
} }
} }
...@@ -63,7 +45,6 @@ ...@@ -63,7 +45,6 @@
} }
.groups-header { .groups-header {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.nav-links { .nav-links {
width: 35%; width: 35%;
......
...@@ -267,20 +267,6 @@ ...@@ -267,20 +267,6 @@
} }
} }
.issuable-header-btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
&.btn-primary {
@extend .btn-primary;
}
}
a { a {
&:hover { &:hover {
color: $md-link-color; color: $md-link-color;
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
&.right { &.right {
float: right; float: right;
padding-right: 0;
a { a {
color: $gl-gray; color: $gl-gray;
......
...@@ -86,7 +86,8 @@ ...@@ -86,7 +86,8 @@
} }
} }
.project-home-panel { .project-home-panel,
.group-home-panel {
padding-top: 24px; padding-top: 24px;
padding-bottom: 24px; padding-bottom: 24px;
...@@ -94,7 +95,8 @@ ...@@ -94,7 +95,8 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
} }
.project-avatar { .project-avatar,
.group-avatar {
float: none; float: none;
margin: 0 auto; margin: 0 auto;
border: none; border: none;
...@@ -104,7 +106,8 @@ ...@@ -104,7 +106,8 @@
} }
} }
.project-title { .project-title,
.group-title {
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 24px; font-size: 24px;
...@@ -118,10 +121,11 @@ ...@@ -118,10 +121,11 @@
} }
} }
.project-home-desc { .project-home-desc,
.group-home-desc {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 15px; margin-bottom: 0;
max-width: 700px; max-width: 700px;
> p { > p {
...@@ -141,13 +145,18 @@ ...@@ -141,13 +145,18 @@
} }
} }
.project-repo-buttons { .project-repo-buttons,
font-size: 0; .group-buttons {
margin-top: 15px;
.btn { .btn {
@include btn-gray; @include btn-gray;
padding: 3px 10px; padding: 3px 10px;
&:last-child {
margin-left: 0;
}
.fa { .fa {
color: $layout-link-gray; color: $layout-link-gray;
} }
...@@ -168,7 +177,8 @@ ...@@ -168,7 +177,8 @@
} }
} }
.project-repo-btn-group, .download-button,
.dropdown-toggle,
.notification-dropdown, .notification-dropdown,
.project-dropdown { .project-dropdown {
margin-left: 10px; margin-left: 10px;
...@@ -474,9 +484,7 @@ a.deploy-project-label { ...@@ -474,9 +484,7 @@ a.deploy-project-label {
margin-right: $gl-padding; margin-right: $gl-padding;
} }
&.project-repo-buttons-right { &.right {
margin-top: 10px;
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
float: right; float: right;
margin-top: 0; margin-top: 0;
......
...@@ -134,6 +134,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -134,6 +134,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:usage_ping_enabled, :usage_ping_enabled,
:enabled_git_access_protocol, :enabled_git_access_protocol,
:repository_size_limit, :repository_size_limit,
:sidekiq_throttling_enabled,
:sidekiq_throttling_factor,
:housekeeping_enabled, :housekeeping_enabled,
:housekeeping_bitmaps_enabled, :housekeeping_bitmaps_enabled,
:housekeeping_incremental_repack_period, :housekeeping_incremental_repack_period,
...@@ -142,7 +144,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -142,7 +144,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
repository_storages: [], repository_storages: [],
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [], import_sources: [],
disabled_oauth_sign_in_sources: [] disabled_oauth_sign_in_sources: [],
sidekiq_throttling_queues: []
) )
end end
end end
...@@ -3,7 +3,7 @@ module DiffForPath ...@@ -3,7 +3,7 @@ module DiffForPath
def render_diff_for_path(diffs) def render_diff_for_path(diffs)
diff_file = diffs.diff_files.find do |diff| diff_file = diffs.diff_files.find do |diff|
diff.old_path == params[:old_path] && diff.new_path == params[:new_path] diff.file_identifier == params[:file_identifier]
end end
return render_404 unless diff_file return render_404 unless diff_file
......
...@@ -58,7 +58,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -58,7 +58,7 @@ class Groups::MilestonesController < Groups::ApplicationController
def render_new_with_error(empty_project_ids) def render_new_with_error(empty_project_ids)
@milestone = Milestone.new(milestone_params) @milestone = Milestone.new(milestone_params)
@milestone.errors.add(:project_id, "Please select at least one project.") if empty_project_ids @milestone.errors.add(:base, "Please select at least one project.") if empty_project_ids
render :new render :new
end end
......
...@@ -546,6 +546,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -546,6 +546,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.close @merge_request.close
end end
labels
define_pipelines_vars define_pipelines_vars
end end
......
...@@ -18,7 +18,9 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -18,7 +18,9 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def create def create
@pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute(ignore_skip_ci: true, save_on_errors: false) @pipeline = Ci::CreatePipelineService
.new(project, current_user, create_params)
.execute(ignore_skip_ci: true, save_on_errors: false)
unless @pipeline.persisted? unless @pipeline.persisted?
render 'new' render 'new'
return return
......
...@@ -100,4 +100,8 @@ module ApplicationSettingsHelper ...@@ -100,4 +100,8 @@ module ApplicationSettingsHelper
options_for_select(options, @application_setting.repository_storages) options_for_select(options, @application_setting.repository_storages)
end end
def sidekiq_queue_options_for_select
options_for_select(Sidekiq::Queue.all.map(&:name), @application_setting.sidekiq_throttling_queues)
end
end end
...@@ -49,7 +49,7 @@ module ProjectsHelper ...@@ -49,7 +49,7 @@ module ProjectsHelper
end end
end end
def project_title(project, name = nil, url = nil) def project_title(project)
namespace_link = namespace_link =
if project.group if project.group
link_to(simple_sanitize(project.group.name), group_path(project.group)) link_to(simple_sanitize(project.group.name), group_path(project.group))
...@@ -66,10 +66,7 @@ module ProjectsHelper ...@@ -66,10 +66,7 @@ module ProjectsHelper
end end
end end
full_title = "#{namespace_link} / #{project_link}".html_safe "#{namespace_link} / #{project_link}".html_safe
full_title << ' &middot; '.html_safe << link_to(simple_sanitize(name), url) if name
full_title
end end
def remove_project_message(project) def remove_project_message(project)
......
...@@ -19,6 +19,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -19,6 +19,7 @@ class ApplicationSetting < ActiveRecord::Base
serialize :domain_whitelist, Array serialize :domain_whitelist, Array
serialize :domain_blacklist, Array serialize :domain_blacklist, Array
serialize :repository_storages serialize :repository_storages
serialize :sidekiq_throttling_queues, Array
cache_markdown_field :sign_in_text cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text cache_markdown_field :help_page_text
...@@ -97,6 +98,15 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -97,6 +98,15 @@ class ApplicationSetting < ActiveRecord::Base
presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' }, presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' },
if: :domain_blacklist_enabled? if: :domain_blacklist_enabled?
validates :sidekiq_throttling_factor,
numericality: { greater_than: 0, less_than: 1 },
presence: { message: 'Throttling factor cannot be empty if Sidekiq Throttling is enabled.' },
if: :sidekiq_throttling_enabled?
validates :sidekiq_throttling_queues,
presence: { message: 'Queues to throttle cannot be empty if Sidekiq Throttling is enabled.' },
if: :sidekiq_throttling_enabled?
validates :housekeeping_incremental_repack_period, validates :housekeeping_incremental_repack_period,
presence: true, presence: true,
numericality: { only_integer: true, greater_than: 0 } numericality: { only_integer: true, greater_than: 0 }
...@@ -195,6 +205,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -195,6 +205,7 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: true, usage_ping_enabled: true,
repository_storages: ['default'], repository_storages: ['default'],
user_default_external: false, user_default_external: false,
sidekiq_throttling_enabled: false,
housekeeping_enabled: true, housekeeping_enabled: true,
housekeeping_bitmaps_enabled: true, housekeeping_bitmaps_enabled: true,
housekeeping_incremental_repack_period: 10, housekeeping_incremental_repack_period: 10,
......
...@@ -280,7 +280,7 @@ class Issue < ActiveRecord::Base ...@@ -280,7 +280,7 @@ class Issue < ActiveRecord::Base
def as_json(options = {}) def as_json(options = {})
super(options).tap do |json| super(options).tap do |json|
json[:subscribed] = subscribed?(options[:user]) if options.has_key?(:user) json[:subscribed] = subscribed?(options[:user]) if options.has_key?(:user) && options[:user]
if options.has_key?(:labels) if options.has_key?(:labels)
json[:labels] = labels.as_json( json[:labels] = labels.as_json(
......
...@@ -1608,6 +1608,10 @@ class Project < ActiveRecord::Base ...@@ -1608,6 +1608,10 @@ class Project < ActiveRecord::Base
end end
end end
def only_allow_merge_if_all_discussions_are_resolved
super || false
end
private private
def pushes_since_gc_redis_key def pushes_since_gc_redis_key
......
...@@ -95,15 +95,17 @@ class Repository ...@@ -95,15 +95,17 @@ class Repository
def commit(ref = 'HEAD') def commit(ref = 'HEAD')
return nil unless exists? return nil unless exists?
commit = commit =
if ref.is_a?(Gitlab::Git::Commit) if ref.is_a?(Gitlab::Git::Commit)
ref ref
else else
Gitlab::Git::Commit.find(raw_repository, ref) Gitlab::Git::Commit.find(raw_repository, ref)
end end
commit = ::Commit.new(commit, @project) if commit commit = ::Commit.new(commit, @project) if commit
commit commit
rescue Rugged::OdbError rescue Rugged::OdbError, Rugged::TreeError
nil nil
end end
...@@ -289,6 +291,8 @@ class Repository ...@@ -289,6 +291,8 @@ class Repository
def ref_exists?(ref) def ref_exists?(ref)
rugged.references.exist?(ref) rugged.references.exist?(ref)
rescue Rugged::ReferenceError
false
end end
def update_ref!(name, newrev, oldrev) def update_ref!(name, newrev, oldrev)
...@@ -296,7 +300,7 @@ class Repository ...@@ -296,7 +300,7 @@ class Repository
# offer 'compare and swap' ref updates. Without compare-and-swap we can # offer 'compare and swap' ref updates. Without compare-and-swap we can
# (and have!) accidentally reset the ref to an earlier state, clobbering # (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534. # commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %w[git update-ref --stdin -z] command = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
_, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin| _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00") stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
end end
...@@ -327,11 +331,7 @@ class Repository ...@@ -327,11 +331,7 @@ class Repository
end end
def kept_around?(sha) def kept_around?(sha)
begin ref_exists?(keep_around_ref_name(sha))
ref_exists?(keep_around_ref_name(sha))
rescue Rugged::ReferenceError
false
end
end end
def tag_names def tag_names
......
...@@ -304,6 +304,31 @@ ...@@ -304,6 +304,31 @@
The amount of points to store in a single UDP packet. More points The amount of points to store in a single UDP packet. More points
results in fewer but larger UDP packets being sent. results in fewer but larger UDP packets being sent.
%fieldset
%legend Background Jobs
%p
These settings require a restart to take effect.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :sidekiq_throttling_enabled do
= f.check_box :sidekiq_throttling_enabled
Enable Sidekiq Job Throttling
.help-block
Limit the amount of resources slow running jobs are assigned.
.form-group
= f.label :sidekiq_throttling_queues, 'Sidekiq queues to throttle', class: 'control-label col-sm-2'
.col-sm-10
= f.select :sidekiq_throttling_queues, sidekiq_queue_options_for_select, { include_hidden: false }, multiple: true, class: 'select2 select-wide', data: { field: 'sidekiq_throttling_queues' }
.help-block
Choose which queues you wish to throttle.
.form-group
= f.label :sidekiq_throttling_factor, 'Throttling Factor', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :sidekiq_throttling_factor, class: 'form-control', min: '0.01', max: '0.99', step: '0.01'
.help-block
The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.
%fieldset %fieldset
%legend Spam and Anti-bot Protection %legend Spam and Anti-bot Protection
.form-group .form-group
......
...@@ -11,3 +11,6 @@ ...@@ -11,3 +11,6 @@
- if signin_enabled? - if signin_enabled?
%li %li
= link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab' = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
- if signin_enabled? && signup_enabled?
%li
= link_to 'Register', '#register-pane', 'data-toggle' => 'tab'
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
= f.label :projects, "Projects", class: "control-label" = f.label :projects, "Projects", class: "control-label"
.col-sm-10 .col-sm-10
= f.collection_select :project_ids, @group.projects.non_archived, :id, :name, = f.collection_select :project_ids, @group.projects.non_archived, :id, :name,
{ selected: @group.projects.non_archived.pluck(:id) }, multiple: true, class: 'select2' { selected: @group.projects.non_archived.pluck(:id) }, required: true, multiple: true, class: 'select2'
.col-md-6 .col-md-6
.form-group .form-group
......
...@@ -4,25 +4,23 @@ ...@@ -4,25 +4,23 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
.cover-block.groups-cover-block .group-home-panel.text-center
%div{ class: container_class } %div{ class: container_class }
.avatar-container.s70.group-avatar .avatar-container.s70.group-avatar
= image_tag group_icon(@group), class: "avatar s70 avatar-tile" = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
.group-info %h1.group-title
.cover-title @#{@group.path}
%h1 %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
@#{@group.path} = visibility_level_icon(@group.visibility_level, fw: false)
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, fw: false)
.group-right-buttons.btn-group - if @group.description.present?
- if current_user .group-home-desc
.pull-left.append-right-10= render 'shared/members/access_request_buttons', source: @group = markdown_field(@group, :description)
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @group.description.present? - if current_user
.cover-desc.description .group-buttons
= markdown_field(@group, :description) = render 'shared/members/access_request_buttons', source: @group
= render 'shared/notifications/button', notification_setting: @notification_setting
%div.groups-header{ class: container_class } %div.groups-header{ class: container_class }
.top-area .top-area
......
...@@ -14,26 +14,26 @@ ...@@ -14,26 +14,26 @@
- if can_admin_group - if can_admin_group
= nav_link(path: 'groups#projects') do = nav_link(path: 'groups#projects') do
= link_to 'Projects', projects_group_path(@group), title: 'Projects' = link_to 'Projects', projects_group_path(@group), title: 'Projects'
- if can_edit || can_leave - if (can_edit || can_leave) && can_admin_group
%li.divider %li.divider
- if can_edit - if can_edit
- if ldap_enabled? - if ldap_enabled?
= nav_link(controller: :ldap_group_links) do = nav_link(controller: :ldap_group_links) do
= link_to group_ldap_group_links_path(@group), title: "LDAP Groups" do = link_to group_ldap_group_links_path(@group), title: "LDAP Groups" do
%span
LDAP Groups
= nav_link(controller: :hooks) do
= link_to group_hooks_path(@group), title: "Webhooks" do
%span %span
Webhooks LDAP Groups
= nav_link(controller: :audit_events) do = nav_link(controller: :hooks) do
= link_to group_audit_events_path(@group), title: "Audit Events" do = link_to group_hooks_path(@group), title: "Webhooks" do
%span %span
Audit Events Webhooks
%li = nav_link(controller: :audit_events) do
= link_to 'Edit Group', edit_group_path(@group) = link_to group_audit_events_path(@group), title: "Audit Events" do
- if can_leave %span
%li Audit Events
= link_to polymorphic_path([:leave, @group, :members]), %li
data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do = link_to 'Edit Group', edit_group_path(@group)
Leave Group - if can_leave
%li
= link_to polymorphic_path([:leave, @group, :members]),
data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
Leave Group
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds' - page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
- header_title project_title(@project, "Builds", project_builds_path(@project))
.top-block.row-content-block.clearfix .top-block.row-content-block.clearfix
.pull-right .pull-right
......
- board = local_assigns.fetch(:board, nil)
- @no_container = true
- @content_class = "issue-boards-content"
- page_title "Boards"
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('boards/boards_bundle.js')
= page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test?
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
%script#js-board-list-card{ type: "text/x-template" }= render "projects/boards/components/card"
= render "projects/issues/head"
= render 'shared/issuable/filter', type: :boards
#board-app.boards-app{ "v-cloak" => true, data: board_data }
= render "title", board: board
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
%board{ "v-cloak" => true,
"v-for" => "list in state.lists",
"ref" => "board",
":list" => "list",
":disabled" => "disabled",
":issue-link-base" => "issueLinkBase",
":key" => "_uid" }
= render "projects/boards/components/sidebar"
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.boards-title-holder.dropdown .boards-title-holder.dropdown
%button.boards-switcher{ "@click" => "loadBoards", %button.boards-switcher{ "@click" => "loadBoards",
data: { toggle: "dropdown" } } data: { toggle: "dropdown" } }
{{ currentBoard.name }} {{ board.name }}
= icon("caret-down") = icon("caret-down")
.dropdown-menu{ ":class" => "{ 'is-loading': loading }" } .dropdown-menu{ ":class" => "{ 'is-loading': loading }" }
.dropdown-title .dropdown-title
...@@ -25,26 +25,15 @@ ...@@ -25,26 +25,15 @@
= icon("spin spinner") = icon("spin spinner")
- if can?(current_user, :admin_board, @project) - if can?(current_user, :admin_board, @project)
%board-selector-form{ "inline-template" => true, %board-selector-form{ "inline-template" => true,
"v-if" => "currentPage === 'edit'", "v-if" => "currentPage === 'new' || currentPage === 'edit'" }
"type" => "edit",
":current-board.sync" => "currentBoard",
":current-page.sync" => "currentPage",
":reload.sync" => "reload" }
= render "projects/boards/components/form"
%board-selector-form{ "inline-template" => true,
"v-if" => "currentPage === 'new'",
"type" => "new",
":current-page.sync" => "currentPage",
":reload.sync" => "reload" }
= render "projects/boards/components/form" = render "projects/boards/components/form"
.dropdown-content.board-selector-page-two{ "v-if" => "currentPage === 'delete'" } .dropdown-content.board-selector-page-two{ "v-if" => "currentPage === 'delete'" }
%p %p
Are you sure you want to delete this board? Are you sure you want to delete this board?
.board-delete-btns.clearfix .board-delete-btns.clearfix
= link_to "", = link_to namespace_project_board_path(@project.namespace, @project, board),
class: "btn btn-danger pull-left", class: "btn btn-danger pull-left",
method: :delete, method: :delete do
":href" => "'#{namespace_project_boards_path(@project.namespace, @project)}/' + currentBoard.id" do
Delete Delete
%button.btn.btn-default.pull-right{ type: "button", %button.btn.btn-default.pull-right{ type: "button",
"@click.stop.prevent" => "currentPage = ''" } "@click.stop.prevent" => "currentPage = ''" }
......
%board-blank-state{ "inline-template" => true, %board-blank-state{ "inline-template" => true,
"v-if" => "list.id == 'blank'" } "v-if" => 'list.id == "blank"' }
.board-blank-state .board-blank-state
%p %p
Add the following default lists to your Issue Board with one click: Add the following default lists to your Issue Board with one click:
......
%board{ "inline-template" => true, .board{ ":class" => '{ "is-draggable": !list.preset }',
"v-cloak" => true, ":data-id" => "list.id" }
"v-for" => "list in state.lists | orderBy 'position'", .board-inner
"v-ref:board" => true, %header.board-header{ ":class" => '{ "has-border": list.label }', ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
":list" => "list", %h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' }
":disabled" => "disabled", %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")',
":issue-link-base" => "issueLinkBase", data: { container: "body", placement: "bottom" } }
"track-by" => "_uid" } {{ list.title }}
.board{ ":class" => "{ 'is-draggable': !list.preset }", .board-issue-count-holder.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
":data-id" => "list.id" } %span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "done" }' }
.board-inner {{ list.issuesSize }}
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } - if can?(current_user, :admin_issue, @project)
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button",
%span.has-tooltip{ ":title" => "(list.label ? list.label.description : '')", "@click" => "showNewIssueForm",
data: { container: "body", placement: "bottom" } } "v-if" => 'list.type !== "done"',
{{ list.title }} "aria-label" => "Add an issue",
.board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" } "title" => "Add an issue",
%span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" } data: { placement: "top", container: "body" } }
{{ list.issuesSize }} = icon("plus")
- if can?(current_user, :admin_issue, @project) - if can?(current_user, :admin_list, @project)
%button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", %board-delete{ "inline-template" => true,
"@click" => "showNewIssueForm",
"v-if" => "list.type !== 'done'",
"aria-label" => "Add an issue",
"title" => "Add an issue",
data: { placement: "top", container: "body" } }
= icon("plus")
- if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true,
":list" => "list",
"v-if" => "!list.preset && list.id" }
%button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash")
%board-list{ "inline-template" => true,
"v-if" => "list.type !== 'blank'",
":list" => "list",
":issues" => "list.issues",
":loading" => "list.loading",
":disabled" => "disabled",
":show-issue-form.sync" => "showIssueForm",
":issue-link-base" => "issueLinkBase" }
.board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
- if can? current_user, :create_issue, @project
%board-new-issue{ "inline-template" => true,
":list" => "list", ":list" => "list",
":show-issue-form.sync" => "showIssueForm", "v-if" => "!list.preset && list.id" }
"v-show" => "list.type !== 'done' && showIssueForm" } %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
.card.board-new-issue-form = icon("trash")
%form{ "@submit" => "submit($event)" } %board-list{ "v-if" => 'list.type !== "blank"',
.flash-container{ "v-if" => "error" } ":list" => "list",
.flash-alert ":issues" => "list.issues",
An error occured. Please try again. ":loading" => "list.loading",
%label.label-light{ ":for" => "list.id + '-title'" } ":disabled" => "disabled",
Title ":issue-link-base" => "issueLinkBase",
%input.form-control{ type: "text", "ref" => "board-list" }
"v-model" => "title", - if can?(current_user, :admin_list, @project)
"v-el:input" => true, = render "projects/boards/components/blank_state"
":id" => "list.id + '-title'" }
.clearfix.prepend-top-10
%button.btn.btn-success.pull-left{ type: "submit",
":disabled" => "title === ''",
"v-el:submit-button" => true }
Submit issue
%button.btn.btn-default.pull-right{ type: "button",
"@click" => "cancel" }
Cancel
%ul.board-list{ "v-el:list" => true,
"v-show" => "!loading",
":data-board" => "list.id",
":class" => "{ 'is-smaller': showIssueForm }" }
= render "projects/boards/components/card"
%li.board-list-count.text-center{ "v-if" => "showCount" }
= icon("spinner spin", "v-show" => "list.loadingMore" )
%span{ "v-if" => "list.issues.length === list.issuesSize" }
Showing all issues
%span{ "v-else" => true }
Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
- if can?(current_user, :admin_list, @project)
= render "projects/boards/components/blank_state"
.board-list-component
.board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
- if can? current_user, :create_issue, @project
%board-new-issue{ "inline-template" => true,
":list" => "list",
"v-if" => 'list.type !== "done" && showIssueForm' }
.card.board-new-issue-form
%form{ "@submit" => "submit($event)" }
.flash-container{ "v-if" => "error" }
.flash-alert
An error occured. Please try again.
%label.label-light{ ":for" => 'list.id + "-title"' }
Title
%input.form-control{ type: "text",
"v-model" => "title",
"ref" => "input",
":id" => 'list.id + "-title"' }
.clearfix.prepend-top-10
%button.btn.btn-success.pull-left{ type: "submit",
":disabled" => 'title === ""',
"ref" => "submit-button" }
Submit issue
%button.btn.btn-default.pull-right{ type: "button",
"@click" => "cancel" }
Cancel
%ul.board-list{ "ref" => "list",
"v-show" => "!loading",
":data-board" => "list.id",
":class" => '{ "is-smaller": showIssueForm }' }
%board-card{ "v-for" => "(issue, index) in orderedIssues",
"ref" => "issue",
":index" => "index",
":list" => "list",
":issue" => "issue",
":issue-link-base" => "issueLinkBase",
":disabled" => "disabled",
"key" => "id" }
%li.board-list-count.text-center{ "v-if" => "showCount" }
= icon("spinner spin", "v-show" => "list.loadingMore" )
%span{ "v-if" => "list.issues.length === list.issuesSize" }
Showing all issues
%span{ "v-else" => true }
Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
%board-card{ "inline-template" => true, %li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }',
"v-for" => "issue in issues | orderBy 'priority'", ":index" => "index",
"v-ref:issue" => true, "@mousedown" => "mouseDown",
":index" => "$index", "@mouseup" => "showIssue($event)" }
":list" => "list", %h4.card-title
":issue" => "issue", = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
":issue-link-base" => "issueLinkBase", %a{ ":href" => 'issueLinkBase + "/" + issue.id',
":disabled" => "disabled", ":title" => "issue.title" }
"track-by" => "id" } {{ issue.title }}
%li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }", .card-footer
":index" => "index", %span.card-number{ "v-if" => "issue.id" }
"@mousedown" => "mouseDown", = precede '#' do
"@mouseMove" => "mouseMove", {{ issue.id }}
"@mouseup" => "showIssue($event)" } %a.has-tooltip{ ":href" => "\"#{root_path}\" + issue.assignee.username",
%h4.card-title ":title" => '"Assigned to " + issue.assignee.name',
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") "v-if" => "issue.assignee",
%a{ ":href" => "issueLinkBase + '/' + issue.id", data: { container: 'body' } }
":title" => "issue.title" } %img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 }
{{ issue.title }} %button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
.card-footer type: "button",
%span.card-number{ "v-if" => "issue.id" } "v-if" => "(!list.label || label.id !== list.label.id)",
= precede '#' do "@click" => "filterByLabel(label, $event)",
{{ issue.id }} ":style" => "{ backgroundColor: label.color, color: label.textColor }",
%a.has-tooltip{ ":href" => "'#{root_path}' + issue.assignee.username", ":title" => "label.description",
":title" => "'Assigned to ' + issue.assignee.name", data: { container: 'body' } }
"v-if" => "issue.assignee", {{ label.title }}
data: { container: 'body' } }
%img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 }
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
type: "button",
"v-if" => "(!list.label || label.id !== list.label.id)",
"@click" => "filterByLabel(label, $event)",
":style" => "{ backgroundColor: label.color, color: label.textColor }",
":title" => "label.description",
data: { container: 'body' } }
{{ label.title }}
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.clearfix.prepend-top-10 .clearfix.prepend-top-10
%button.btn.btn-primary.pull-left{ type: "submit", %button.btn.btn-primary.pull-left{ type: "submit",
":disabled" => "board.name === ''", ":disabled" => "board.name === ''",
"v-el:submit-btn" => true } "ref" => "'submit-btn'" }
{{ buttonText }} {{ buttonText }}
%button.btn.btn-default.pull-right{ type: "button", %button.btn.btn-default.pull-right{ type: "button",
"@click.stop.prevent" => "currentPage = ''" } "@click.stop.prevent" => "currentPage = ''" }
......
%board-sidebar{ "inline-template" => true, %board-sidebar{ "inline-template" => true,
":current-user" => "#{current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) if current_user}" } ":current-user" => "#{current_user ? current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) : {}}" }
%aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" } %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" }
.issuable-sidebar .issuable-sidebar
.block.issuable-sidebar-header .block.issuable-sidebar-header
......
- @no_container = true = render "show", board: @boards.first
- @content_class = "issue-boards-content"
- page_title "Boards"
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('boards/boards_bundle.js')
= page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test?
= render "projects/issues/head"
= render 'shared/issuable/filter', type: :boards
#board-app.boards-app{ "v-cloak" => true, data: board_data }
= render "title", board: @boards.first
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
= render "projects/boards/components/board"
= render "projects/boards/components/sidebar"
- @no_container = true = render "show", board: @board
- @content_class = "issue-boards-content"
- page_title "Boards"
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('boards/boards_bundle.js')
= page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test?
= render "projects/issues/head"
= render 'shared/issuable/filter', type: :boards
#board-app.boards-app{ "v-cloak" => true, data: board_data }
= render "title", board: @board
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
= render "projects/boards/components/board"
= render "projects/boards/components/sidebar"
- @no_container = true - @no_container = true
- page_title "#{@build.name} (##{@build.id})", "Builds" - page_title "#{@build.name} (##{@build.id})", "Builds"
- header_title project_title(@project, "Builds", project_builds_path(@project)) - trace_with_state = @build.trace_with_state
= render "projects/pipelines/head", build_subnav: true = render "projects/pipelines/head", build_subnav: true
%div{ class: container_class } %div{ class: container_class }
......
- if current_user - if current_user
.dropdown.inline.project-dropdown .dropdown.inline
%a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
= icon('plus') = icon('plus')
= icon("caret-down") = icon("caret-down")
......
.pipeline-graph-container .pipeline-graph-container
.row-content-block.build-content.middle-block.pipeline-actions .row-content-block.build-content.middle-block.pipeline-actions
.pull-right .pull-right
.btn.btn-grouped.btn-white.toggle-pipeline-btn %button.btn.btn-grouped.btn-white.toggle-pipeline-btn
%span.toggle-btn-text Hide %span.toggle-btn-text Hide
%span pipeline graph %span pipeline graph
%span.caret %span.caret
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- note_count = notes.user.count - note_count = notes.user.count
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(commit.status) if commit.status - cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do = cache(cache_key, expires_in: 1.day) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- if !project.repository.diffable?(blob) - if !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry. .nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif diff_file.collapsed? - elsif diff_file.collapsed?
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path)) - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
.nothing-here-block.diff-collapsed{data: { diff_for_path: url } } .nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
This diff is collapsed. This diff is collapsed.
%a.click-to-expand %a.click-to-expand
......
- header_title project_title(@project, "Environments", project_environments_path(@project))
- page_title "Import in progress" - page_title @project.forked? ? "Forking in progress" : "Import in progress"
.save-project-loader .save-project-loader
.center .center
%h2 %h2
......
...@@ -74,14 +74,15 @@ ...@@ -74,14 +74,15 @@
%span.badge= @merge_request.diff_size %span.badge= @merge_request.diff_size
%li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true } %li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true }
%resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" } %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
.line-resolve-all{ "v-show" => "discussionCount > 0", %div
":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } .line-resolve-all{ "v-show" => "discussionCount > 0",
%span.line-resolve-btn.is-disabled{ type: "button", ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } %span.line-resolve-btn.is-disabled{ type: "button",
= render "shared/icons/icon_status_success.svg" ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
%span.line-resolve-text = render "shared/icons/icon_status_success.svg"
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ discussionCount | pluralize 'discussion' }} resolved %span.line-resolve-text
= render "discussions/jump_to_next" {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
= render "discussions/jump_to_next"
.tab-content#diff-notes-app .tab-content#diff-notes-app
#notes.notes.tab-pane.voting_notes #notes.notes.tab-pane.voting_notes
......
...@@ -30,11 +30,8 @@ ...@@ -30,11 +30,8 @@
.diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
= render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
.diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
= render partial: "projects/merge_requests/conflicts/components/parallel_conflict_lines" %parallel-conflict-lines{ ":file" => "file" }
%div{"v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'"} %div{"v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'"}
= render partial: "projects/merge_requests/conflicts/components/diff_file_editor" = render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
= render partial: "projects/merge_requests/conflicts/submit_form" = render partial: "projects/merge_requests/conflicts/submit_form"
-# Components
= render partial: 'projects/merge_requests/conflicts/components/parallel_conflict_line'
...@@ -5,11 +5,10 @@ ...@@ -5,11 +5,10 @@
%a {{line.new_line}} %a {{line.new_line}}
%td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
%a {{line.old_line}} %a {{line.old_line}}
%td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader", "v-html" => "line.richText"}
{{{line.richText}}}
%td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%strong {{{line.richText}}} %strong{"v-html" => "line.richText"}
%button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" }
{{line.buttonTitle}} {{line.buttonTitle}}
%script{"id" => 'parallel-conflict-line', "type" => "text/x-template"}
%td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%strong {{line.richText}}
%button.btn{"@click" => "handleSelected(file, line.id, line.section)"}
{{line.buttonTitle}}
%td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
{{line.lineNumber}}
%td.line_content.parallel{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
{{{line.richText}}}
%parallel-conflict-lines{"inline-template" => "true", ":file" => "file"}
%table
%tr.line_holder.parallel{"v-for" => "section in file.parallelLines"}
%td{"is"=>"parallel-conflict-line", "v-for" => "line in section", ":line" => "line", ":file" => "file"}
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
"resolved-by" => "#{note.resolved_by.try(:name)}", "resolved-by" => "#{note.resolved_by.try(:name)}",
"v-show" => "#{can_resolve || note.resolved?}", "v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true, "inline-template" => true,
"v-ref:note_#{note.id}" => true } "ref" => "note_#{note.id}" }
.note-action-button .note-action-button
= icon("spin spinner", "v-show" => "loading") = icon("spin spinner", "v-show" => "loading")
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
"@click" => "resolve", "@click" => "resolve",
":title" => "buttonText", ":title" => "buttonText",
"v-show" => "!loading", "v-show" => "!loading",
"v-el:button" => true } ":ref" => "'button'" }
= render "shared/icons/icon_status_success.svg" = render "shared/icons/icon_status_success.svg"
......
...@@ -68,8 +68,8 @@ ...@@ -68,8 +68,8 @@
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI Set Up CI
%li.project-repo-buttons-right %li.project-repo-buttons.right
.project-repo-buttons.project-right-buttons .project-right-buttons
- if current_user - if current_user
= render 'shared/members/access_request_buttons', source: @project = render 'shared/members/access_request_buttons', source: @project
= render "projects/buttons/koding" = render "projects/buttons/koding"
...@@ -78,7 +78,8 @@ ...@@ -78,7 +78,8 @@
= render 'projects/buttons/download', project: @project, ref: @ref = render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown' = render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting .pull-right
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit - if @repository.commit
.project-last-commit{ class: container_class } .project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
......
<svg width="20" height="20" class="ci-status-icon-skipped" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Group Copy 31</title><g fill="#5C5C5C" fill-rule="evenodd"><path d="M10 17.857c4.286 0 7.857-3.571 7.857-7.857S14.286 2.143 10 2.143 2.143 5.714 2.143 10 5.714 17.857 10 17.857M10 0c5.571 0 10 4.429 10 10s-4.429 10-10 10S0 15.571 0 10 4.429 0 10 0"/><path d="M10.986 11l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.644-2.644a1.505 1.505 0 0 0 0-2.126l-2.644-2.644a1 1 0 0 0-1.414 1.414L10.986 9H6.4a1 1 0 0 0 0 2h4.586z"/></g></svg> <svg width="14" height="14" class="ci-status-icon-skipped" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Group Copy 31</title><g fill="#5C5C5C" fill-rule="evenodd"><path d="M10 17.857c4.286 0 7.857-3.571 7.857-7.857S14.286 2.143 10 2.143 2.143 5.714 2.143 10 5.714 17.857 10 17.857M10 0c5.571 0 10 4.429 10 10s-4.429 10-10 10S0 15.571 0 10 4.429 0 10 0"/><path d="M10.986 11l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.644-2.644a1.505 1.505 0 0 0 0-2.126l-2.644-2.644a1 1 0 0 0-1.414 1.414L10.986 9H6.4a1 1 0 0 0 0 2h4.586z"/></g></svg>
- left_align = local_assigns[:left_align] - left_align = local_assigns[:left_align]
- if notification_setting - if notification_setting
.dropdown.notification-dropdown.pull-right .dropdown.notification-dropdown
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting) = hidden_setting_source_input(notification_setting)
= f.hidden_field :level, class: "notification_setting_level" = f.hidden_field :level, class: "notification_setting_level"
......
---
title: Fix project records with invalid visibility_level values
merge_request: 7391
author:
---
title: Fix no "Register" tab if ldap auth is enabled (#24038)
merge_request: 7274
author: Luc Didry
---
title: "[Fix] Extra divider issue in dropdown"
merge_request: 7398
author:
---
title: Removed gray button styling from todo buttons in sidebars
merge_request: 7387
author:
---
title: Remove additional padding on right-aligned items in MR widget.
merge_request: 7411
author: Didem Acet
---
title: Fix issue causing Labels not to appear in sidebar on MR page
merge_request: 7416
author: Alex Sanford
---
title: Fix expanding a collapsed diff when converting a symlink to a regular file
merge_request: 6953
author:
---
title: Add api endpoint `/groups/owned`
merge_request: 7103
author: Borja Aparicio
---
title: Fix cache for commit status in commits list to respect branches
merge_request: 7372
author:
---
title: Fix error when using invalid branch name when creating a new pipeline
merge_request: 7324
author:
---
title: Use 'Forking in progress' title when appropriate
merge_request: 7394
author: Philip Karpiak
---
title: Require projects before creating milestone.
merge_request: 7301
author: gfyoung
---
title: Added ability to throttle Sidekiq Jobs
merge_request: 7292
author:
...@@ -32,6 +32,8 @@ Sidekiq.configure_server do |config| ...@@ -32,6 +32,8 @@ Sidekiq.configure_server do |config|
# Gitlab Geo: enable bulk notify job only on primary node # Gitlab Geo: enable bulk notify job only on primary node
Gitlab::Geo.bulk_notify_job.disable! unless Gitlab::Geo.primary? Gitlab::Geo.bulk_notify_job.disable! unless Gitlab::Geo.primary?
Gitlab::SidekiqThrottler.execute!
# Database pool should be at least `sidekiq_concurrency` + 2 # Database pool should be at least `sidekiq_concurrency` + 2
# For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md # For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
config = ActiveRecord::Base.configurations[Rails.env] || config = ActiveRecord::Base.configurations[Rails.env] ||
......
...@@ -5,10 +5,7 @@ class OnlyAllowMergeIfAllDiscussionsAreResolved < ActiveRecord::Migration ...@@ -5,10 +5,7 @@ class OnlyAllowMergeIfAllDiscussionsAreResolved < ActiveRecord::Migration
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:projects, add_column :projects, :only_allow_merge_if_all_discussions_are_resolved, :boolean
:only_allow_merge_if_all_discussions_are_resolved,
:boolean,
default: false)
end end
def down def down
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddSidekiqThrottlingToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
add_column :application_settings, :sidekiq_throttling_enabled, :boolean, default: false
add_column :application_settings, :sidekiq_throttling_queues, :string
add_column :application_settings, :sidekiq_throttling_factor, :decimal
end
end
class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
BATCH_SIZE = 1000
DOWNTIME = false
# This migration is idempotent and there's no sense in throwing away the
# partial result if it's interrupted
disable_ddl_transaction!
def up
projects = Arel::Table.new(:projects)
namespaces = Arel::Table.new(:namespaces)
finder =
projects.
join(namespaces, Arel::Nodes::InnerJoin).
on(projects[:namespace_id].eq(namespaces[:id])).
where(projects[:visibility_level].gt(namespaces[:visibility_level])).
project(projects[:id]).
take(BATCH_SIZE)
# MySQL requires a derived table to perform this query
nested_finder =
projects.
from(finder.as("AS projects_inner")).
project(projects[:id])
valuer =
namespaces.
where(namespaces[:id].eq(projects[:namespace_id])).
project(namespaces[:visibility_level])
# Update matching rows until none remain. The finder contains a limit.
loop do
updater = Arel::UpdateManager.new(ActiveRecord::Base).
table(projects).
set(projects[:visibility_level] => Arel::Nodes::SqlLiteral.new("(#{valuer.to_sql})")).
where(projects[:id].in(nested_finder))
num_updated = connection.exec_update(updater.to_sql, self.class.name, [])
break if num_updated == 0
end
end
def down
# no-op
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161106185620) do ActiveRecord::Schema.define(version: 20161109150329) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -107,6 +107,9 @@ ActiveRecord::Schema.define(version: 20161106185620) do ...@@ -107,6 +107,9 @@ ActiveRecord::Schema.define(version: 20161106185620) do
t.text "help_page_text_html" t.text "help_page_text_html"
t.text "shared_runners_text_html" t.text "shared_runners_text_html"
t.text "after_sign_up_text_html" t.text "after_sign_up_text_html"
t.boolean "sidekiq_throttling_enabled", default: false
t.string "sidekiq_throttling_queues"
t.decimal "sidekiq_throttling_factor"
t.boolean "housekeeping_enabled", default: true, null: false t.boolean "housekeeping_enabled", default: true, null: false
t.boolean "housekeeping_bitmaps_enabled", default: true, null: false t.boolean "housekeeping_bitmaps_enabled", default: true, null: false
t.integer "housekeeping_incremental_repack_period", default: 10, null: false t.integer "housekeeping_incremental_repack_period", default: 10, null: false
...@@ -1049,7 +1052,7 @@ ActiveRecord::Schema.define(version: 20161106185620) do ...@@ -1049,7 +1052,7 @@ ActiveRecord::Schema.define(version: 20161106185620) do
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
t.integer "repository_size_limit" t.integer "repository_size_limit"
t.boolean "only_allow_merge_if_all_discussions_are_resolved", default: false, null: false t.boolean "only_allow_merge_if_all_discussions_are_resolved"
end end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
# GitLab operations # GitLab operations
- [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md) - [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md)
- [Sidekiq Job throttling](operations/sidekiq_job_throttling.md)
- [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md) - [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md)
- [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) - [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md)
- [Moving repositories to a new location](operations/moving_repositories.md) - [Moving repositories to a new location](operations/moving_repositories.md)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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