Commit b83e8a5a authored by Alejandro Rodríguez's avatar Alejandro Rodríguez

Merge branch 'master' into 8-14-stable

parents cb5f3368 6eeff67c
...@@ -213,11 +213,24 @@ rake downtime_check: *exec ...@@ -213,11 +213,24 @@ rake downtime_check: *exec
rake ee_compat_check: rake ee_compat_check:
<<: *exec <<: *exec
only: only:
- branches - branches@gitlab-org/gitlab-ce
- branches@gitlab/gitlabhq
except: except:
- master - master
- tags - tags
- /^[\d-]+-stable(-ee)?$/
allow_failure: yes allow_failure: yes
cache:
key: "ruby231-ee_compat_check_repo"
paths:
- ee_compat_check/repo/
- vendor/ruby
artifacts:
name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}"
when: on_failure
expire_in: 10d
paths:
- ee_compat_check/patches/*.patch
rake db:migrate:reset: rake db:migrate:reset:
stage: test stage: test
......
...@@ -51,6 +51,7 @@ entry. ...@@ -51,6 +51,7 @@ entry.
- Fail gracefully when creating merge request with non-existing branch (alexsanford) - Fail gracefully when creating merge request with non-existing branch (alexsanford)
- Fix mobile layout issues in admin user overview page !7087 - Fix mobile layout issues in admin user overview page !7087
- Fix HipChat notifications rendering (airatshigapov, eisnerd) - Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Removed unneeded "Builds" and "Environments" link from project titles
- Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato) - Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato)
- Cleaned up global namespace JS !19661 (Jose Ivan Vargas) - Cleaned up global namespace JS !19661 (Jose Ivan Vargas)
- Refactor Jira service to use jira-ruby gem - Refactor Jira service to use jira-ruby gem
......
...@@ -137,6 +137,7 @@ gem 'acts-as-taggable-on', '~> 4.0' ...@@ -137,6 +137,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'
......
...@@ -685,6 +685,8 @@ GEM ...@@ -685,6 +685,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)
...@@ -961,6 +963,7 @@ DEPENDENCIES ...@@ -961,6 +963,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)
......
...@@ -82,6 +82,10 @@ GitLab is a Ruby on Rails application that runs on the following software: ...@@ -82,6 +82,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.
......
...@@ -58,11 +58,28 @@ ...@@ -58,11 +58,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);
} }
}; };
......
...@@ -22,6 +22,8 @@ $(() => { ...@@ -22,6 +22,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: {
...@@ -37,16 +39,15 @@ $(() => { ...@@ -37,16 +39,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) => {
...@@ -60,6 +61,8 @@ $(() => { ...@@ -60,6 +61,8 @@ $(() => {
} }
}); });
this.state.lists = _.sortBy(this.state.lists, 'position');
Store.addBlankState(); Store.addBlankState();
this.loading = false; this.loading = false;
}); });
...@@ -70,6 +73,9 @@ $(() => { ...@@ -70,6 +73,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();
} }
......
...@@ -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();
......
/* 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);
......
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,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 +60,8 @@ ...@@ -58,6 +60,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;
} }
......
...@@ -166,8 +166,12 @@ ...@@ -166,8 +166,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;
...@@ -175,7 +179,7 @@ ...@@ -175,7 +179,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,
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
} }
.group-row { .group-row {
.stats { .stats {
float: right; float: right;
line-height: $list-text-height; line-height: $list-text-height;
...@@ -23,36 +22,18 @@ ...@@ -23,36 +22,18 @@
} }
.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;
} }
} }
.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;
......
...@@ -117,6 +117,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -117,6 +117,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:send_user_confirmation_email, :send_user_confirmation_email,
:container_registry_token_expire_delay, :container_registry_token_expire_delay,
:enabled_git_access_protocol, :enabled_git_access_protocol,
:sidekiq_throttling_enabled,
:sidekiq_throttling_factor,
:housekeeping_enabled, :housekeeping_enabled,
:housekeeping_bitmaps_enabled, :housekeeping_bitmaps_enabled,
:housekeeping_incremental_repack_period, :housekeeping_incremental_repack_period,
...@@ -125,7 +127,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -125,7 +127,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
......
...@@ -507,6 +507,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -507,6 +507,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
...@@ -85,6 +86,15 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -85,6 +86,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 }
...@@ -180,6 +190,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -180,6 +190,7 @@ class ApplicationSetting < ActiveRecord::Base
container_registry_token_expire_delay: 5, container_registry_token_expire_delay: 5,
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,
......
...@@ -1334,6 +1334,10 @@ class Project < ActiveRecord::Base ...@@ -1334,6 +1334,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
......
...@@ -84,15 +84,17 @@ class Repository ...@@ -84,15 +84,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
...@@ -232,6 +234,8 @@ class Repository ...@@ -232,6 +234,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)
...@@ -239,7 +243,7 @@ class Repository ...@@ -239,7 +243,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
...@@ -270,11 +274,7 @@ class Repository ...@@ -270,11 +274,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
......
...@@ -283,6 +283,31 @@ ...@@ -283,6 +283,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
......
...@@ -8,3 +8,6 @@ ...@@ -8,3 +8,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,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- 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
%li %li
......
- 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
......
- @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 }
.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"
%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 }}
- @no_container = true = render "show"
- @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 }
.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"
- @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 }
.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"
......
...@@ -66,8 +66,8 @@ ...@@ -66,8 +66,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"
...@@ -76,7 +76,8 @@ ...@@ -76,7 +76,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:
...@@ -29,6 +29,8 @@ Sidekiq.configure_server do |config| ...@@ -29,6 +29,8 @@ Sidekiq.configure_server do |config|
end end
Sidekiq::Cron::Job.load_from_hash! cron_jobs Sidekiq::Cron::Job.load_from_hash! cron_jobs
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"
...@@ -98,6 +98,9 @@ ActiveRecord::Schema.define(version: 20161106185620) do ...@@ -98,6 +98,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
...@@ -912,7 +915,7 @@ ActiveRecord::Schema.define(version: 20161106185620) do ...@@ -912,7 +915,7 @@ ActiveRecord::Schema.define(version: 20161106185620) do
t.boolean "has_external_wiki" t.boolean "has_external_wiki"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
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)
# Sidekiq Job throttling
> Note: Introduced with GitLab 8.14
When your GitLab installation needs to handle tens of thousands of background
jobs, it can be convenient to throttle queues that do not need to be executed
immediately, e.g. long running jobs like Pipelines, thus allowing jobs that do
need to be executed immediately to have access to more resources.
In order to accomplish this, you can limit the amount of workers that certain
slow running queues can have available. This is what we call Sidekiq Job
Throttling. Depending on your infrastructure, you might have different slow
running queues, which is why you can choose which queues you want to throttle
and by how much you want to throttle them.
These settings are available in the Application Settings of your GitLab
installation.
![Sidekiq Job Throttling](img/sidekiq_job_throttling.png)
The throttle factor determines the maximum number of workers a queue can run on.
This value gets multiplied by `:concurrency` value set in the Sidekiq settings
and rounded up to the closest full integer.
So, for example, you set the `:concurrency` to 25 and the `Throttling factor` to
0.1, the maximum workers assigned to the selected queues would be 3.
```ruby
queue_limit = (factor * Sidekiq.options[:concurrency]).ceil
```
After enabling the job throttling, you will need to restart your GitLab
instance, in order for the changes to take effect.
\ No newline at end of file
...@@ -26,6 +26,15 @@ GET /groups ...@@ -26,6 +26,15 @@ GET /groups
You can search for groups by name or path, see below. You can search for groups by name or path, see below.
=======
## List owned groups
Get a list of groups which are owned by the authenticated user.
```
GET /groups/owned
```
## List a group's projects ## List a group's projects
Get a list of projects in this group. Get a list of projects in this group.
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
contributing to documentation. contributing to documentation.
- [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations - [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations
- [Testing standards and style guidelines](testing.md) - [Testing standards and style guidelines](testing.md)
- [UI guide](ui_guide.md) for building GitLab with existing CSS styles and elements - [UX guide](ux_guide/README.md) for building GitLab with existing CSS styles and elements
- [Frontend guidelines](frontend.md) - [Frontend guidelines](frontend.md)
- [SQL guidelines](sql.md) for working with SQL queries - [SQL guidelines](sql.md) for working with SQL queries
- [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers
......
# Basics
## Contents
* [Responsive](#responsive)
* [Typography](#typography)
* [Icons](#icons)
* [Color](#color)
* [Motion](#motion)
* [Voice and tone](#voice-and-tone)
---
## Responsive
GitLab is a responsive experience that works well across all screen sizes, from mobile devices to large monitors. In order to provide a great user experience, the core functionality (browsing files, creating issues, writing comments, etc.) must be available at all resolutions. However, due to size limitations, some secondary functionality may be hidden on smaller screens. Please keep this functionality limited to rare actions that aren't expected to be needed on small devices.
---
## Typography
### Primary typeface
GitLab's main typeface used throughout the UI is **Source Sans Pro**. We support both the bold and regular weight.
![Source Sans Pro sample](img/sourcesanspro-sample.png)
### Monospace typeface
This is the typeface used for code blocks. GitLab uses the OS default font.
- **Menlo** (Mac)
- **Consolas** (Windows)
- **Liberation Mono** (Linux)
![Monospace font sample](img/monospacefont-sample.png)
---
## Icons
GitLab uses Font Awesome icons throughout our interface.
![Trash icon](img/icon-trash.png)
The trash icon is used for destructive actions that deletes information.
![Edit icon](img/icon-edit.png)
The pencil icon is used for editing content such as comments.
![Notification icon](img/icon-notification.png)
The bell icon is for notifications, such as Todos.
![Subscribe icon](img/icon-subscribe.png)
The eye icon is for subscribing to updates. For example, you can subscribe to a label and get updated on issues with that label.
![RSS icon](img/icon-rss.png)
The standard RSS icon is used for linking to RSS/atom feeds.
![Close icon](img/icon-close.png)
An 'x' is used for closing UI elements such as dropdowns.
![Add icon](img/icon-add.png)
A plus is used when creating new objects, such as issues, projects, etc.
> TODO: update this section, add more general guidance to icon usage and personality, etc.
---
## Color
![Blue](img/color-blue.png)
Blue is used to highlight primary active elements (such as current tab), as well as other organization and managing commands.
![Green](img/color-green.png)
Green is for actions that create new objects.
![Orange](img/color-orange.png)
Orange is used for warnings
![Red](img/color-red.png)
Red is reserved for delete and other destructive commands
![Grey](img/color-grey.png)
Grey, and white (depending on context) is used for netral, secondary elements
> TODO: Establish a perspective for color in terms of our personality and rationalize with Marketing usage.
---
## Motion
Motion is a tool to help convey important relationships, changes or transitions between elements. It should be used sparingly and intentionally, highlighting the right elements at the right moment.
> TODO: Determine a more concrete perspective on motion, create consistent easing/timing curves to follow.
---
## Voice and tone
The copy for GitLab is clear and direct. We strike a clear balance between professional and friendly. We can empathesize with users (such as celebrating completing all Todos), and remain respectful of the importance of the work. We are that trusted, friendly coworker that is helpful and understanding.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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