diff --git a/CHANGELOG.md b/CHANGELOG.md index 20907eef17b..86a37d5bdb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ entry. - Fail gracefully when creating merge request with non-existing branch (alexsanford) - Fix mobile layout issues in admin user overview page !7087 - 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) - Cleaned up global namespace JS !19661 (Jose Ivan Vargas) - Refactor Jira service to use jira-ruby gem diff --git a/Gemfile b/Gemfile index cb2a8470126..f2291568d25 100644 --- a/Gemfile +++ b/Gemfile @@ -137,6 +137,7 @@ gem 'acts-as-taggable-on', '~> 4.0' gem 'sidekiq', '~> 4.2' gem 'sidekiq-cron', '~> 0.4.0' gem 'redis-namespace', '~> 1.5.2' +gem 'sidekiq-limit_fetch', '~> 3.4' # HTTP requests gem 'httparty', '~> 0.13.3' diff --git a/Gemfile.lock b/Gemfile.lock index 290e4c3e1b3..81b43f2238a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -685,6 +685,8 @@ GEM redis-namespace (>= 1.5.2) rufus-scheduler (>= 2.0.24) sidekiq (>= 4.0.0) + sidekiq-limit_fetch (3.4.0) + sidekiq (>= 4) simplecov (0.12.0) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -961,6 +963,7 @@ DEPENDENCIES shoulda-matchers (~> 2.8.0) sidekiq (~> 4.2) sidekiq-cron (~> 0.4.0) + sidekiq-limit_fetch (~> 3.4) simplecov (= 0.12.0) slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) diff --git a/README.md b/README.md index dbe6db3ebed..f63543ca39d 100644 --- a/README.md +++ b/README.md @@ -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). +## UX design + +Please adhere to the [UX Guide](doc/development/ux_guide/readme.md) when creating designs and implementing code. + ## 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. diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 33c1708e1a9..5c047dd4481 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -58,11 +58,28 @@ document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch); 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 () { // Scroll the window to avoid the topnav bar // https://github.com/twitter/bootstrap/issues/1768 if (location.hash) { - return setTimeout(gl.utils.shiftWindow, 100); + return setTimeout(adjustScroll, 100); } }; diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index efb22d38513..7ba918a05f8 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -22,6 +22,8 @@ $(() => { gl.IssueBoardsApp.$destroy(true); } + Store.create(); + gl.IssueBoardsApp = new Vue({ el: $boardApp, components: { @@ -37,16 +39,15 @@ $(() => { issueLinkBase: $boardApp.dataset.issueLinkBase, detailIssue: Store.detail }, - init: Store.create.bind(Store), computed: { detailIssueVisible () { return Object.keys(this.detailIssue.issue).length; - } + }, }, created () { gl.boardService = new BoardService(this.endpoint, this.boardId); }, - ready () { + mounted () { Store.disabled = this.disabled; gl.boardService.all() .then((resp) => { @@ -60,6 +61,8 @@ $(() => { } }); + this.state.lists = _.sortBy(this.state.lists, 'position'); + Store.addBlankState(); this.loading = false; }); @@ -70,6 +73,9 @@ $(() => { el: '#js-boards-seach', data: { filters: Store.state.filters + }, + mounted () { + gl.issueBoards.newListDropdownInit(); } }); }); diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index 0e03d43872b..31de3b25284 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -10,6 +10,7 @@ window.gl.issueBoards = window.gl.issueBoards || {}; gl.issueBoards.Board = Vue.extend({ + template: '#js-board-template', components: { 'board-list': gl.issueBoards.BoardList, 'board-delete': gl.issueBoards.BoardDelete, @@ -24,7 +25,6 @@ return { detailIssue: Store.detail, filters: Store.state.filters, - showIssueForm: false }; }, watch: { @@ -58,10 +58,10 @@ }, methods: { showNewIssueForm() { - this.showIssueForm = !this.showIssueForm; + this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm; } }, - ready () { + mounted () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ disabled: this.disabled, group: 'boards', @@ -72,13 +72,9 @@ if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { const order = this.sortable.toArray(), - $board = this.$parent.$refs.board[e.oldIndex + 1], - list = $board.list; - - $board.$destroy(true); + list = Store.findList('id', parseInt(e.item.dataset.id)); this.$nextTick(() => { - Store.state.lists.splice(e.newIndex, 0, list); Store.moveList(list, order); }); } @@ -87,8 +83,5 @@ this.sortable = Sortable.create(this.$el.parentNode, options); }, - beforeDestroy () { - Store.state.lists.$remove(this.list); - } }); })(); diff --git a/app/assets/javascripts/boards/components/board_blank_state.js.es6 b/app/assets/javascripts/boards/components/board_blank_state.js.es6 index 885553690d3..691487b272a 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js.es6 +++ b/app/assets/javascripts/boards/components/board_blank_state.js.es6 @@ -30,6 +30,8 @@ }); }); + Store.state.lists = _.sortBy(Store.state.lists, 'position'); + // Save the labels gl.boardService.generateDefaultLists() .then((resp) => { diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index 2f6c03e3538..b1afbe7d97e 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -6,6 +6,7 @@ window.gl.issueBoards = window.gl.issueBoards || {}; gl.issueBoards.BoardCard = Vue.extend({ + template: '#js-board-list-card', props: { list: Object, issue: Object, @@ -53,11 +54,6 @@ mouseDown () { this.showDetail = true; }, - mouseMove () { - if (this.showDetail) { - this.showDetail = false; - } - }, showIssue (e) { const targetTagName = e.target.tagName.toLowerCase(); diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 34fc7694241..379f4f0d72b 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -9,6 +9,7 @@ window.gl.issueBoards = window.gl.issueBoards || {}; gl.issueBoards.BoardList = Vue.extend({ + template: '#js-board-list-template', components: { 'board-card': gl.issueBoards.BoardCard, 'board-new-issue': gl.issueBoards.BoardNewIssue @@ -19,20 +20,20 @@ issues: Array, loading: Boolean, issueLinkBase: String, - showIssueForm: Boolean }, data () { return { scrollOffset: 250, filters: Store.state.filters, - showCount: false + showCount: false, + showIssueForm: false }; }, watch: { filters: { handler () { this.list.loadingMore = false; - this.$els.list.scrollTop = 0; + this.$refs.list.scrollTop = 0; }, deep: true }, @@ -51,15 +52,20 @@ }); } }, + computed: { + orderedIssues () { + return _.sortBy(this.issues, 'priority'); + }, + }, methods: { listHeight () { - return this.$els.list.getBoundingClientRect().height; + return this.$refs.list.getBoundingClientRect().height; }, scrollHeight () { - return this.$els.list.scrollHeight; + return this.$refs.list.scrollHeight; }, scrollTop () { - return this.$els.list.scrollTop + this.listHeight(); + return this.$refs.list.scrollTop + this.listHeight(); }, loadNextPage () { const getIssues = this.list.nextPage(); @@ -72,7 +78,7 @@ } }, }, - ready () { + mounted () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ group: 'issues', sort: false, @@ -81,23 +87,27 @@ onStart: (e) => { const card = this.$refs.issue[e.oldIndex]; + card.showDetail = false; Store.moving.issue = card.issue; Store.moving.list = card.list; gl.issueBoards.onStart(); }, 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 - this.$els.list.onscroll = () => { + this.$refs.list.onscroll = () => { if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) { this.loadNextPage(); } diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index 7fc0bfd56f3..a7989a2ff4c 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -7,7 +7,6 @@ gl.issueBoards.BoardNewIssue = Vue.extend({ props: { list: Object, - showIssueForm: Boolean }, data() { return { @@ -15,11 +14,6 @@ error: false }; }, - watch: { - showIssueForm () { - this.$els.input.focus(); - } - }, methods: { submit(e) { e.preventDefault(); @@ -37,28 +31,30 @@ this.list.newIssue(issue) .then((data) => { // 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; }) .catch(() => { // Need this because our jQuery very kindly disables buttons on ALL form submissions - $(this.$els.submitButton).enable(); + $(this.$refs.submitButton).enable(); // Remove the issue this.list.removeIssue(issue); // Show error message this.error = true; - this.showIssueForm = true; }); this.cancel(); }, cancel() { - this.showIssueForm = false; this.title = ''; + this.$parent.showIssueForm = false; } - } + }, + mounted() { + this.$refs.input.focus(); + }, }); })(); diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index 4928320d015..d5cb6164e0b 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -41,7 +41,7 @@ this.detail.issue = {}; } }, - ready () { + mounted () { new IssuableContext(this.currentUser); new MilestoneSelect(); new gl.DueDateSelectors(); diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 index 14f618fd5d5..10ce746deb5 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 @@ -1,5 +1,8 @@ /* eslint-disable */ -$(() => { +(() => { + window.gl = window.gl || {}; + window.gl.issueBoards = window.gl.issueBoards || {}; + const Store = gl.issueBoards.BoardsStore; $(document).off('created.label').on('created.label', (e, label) => { @@ -15,54 +18,58 @@ $(() => { }); }); - $('.js-new-board-list').each(function () { - const $this = $(this); - new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path')); + gl.issueBoards.newListDropdownInit = () => { + $('.js-new-board-list').each(function () { + const $this = $(this); + new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path')); - $this.glDropdown({ - data(term, callback) { - $.get($this.attr('data-labels')) - .then((resp) => { - callback(resp); - }); - }, - renderRow (label) { - const active = Store.findList('title', label.title), - $li = $('
  • '), - $a = $('', { - class: (active ? `is-active js-board-list-${active.id}` : ''), - text: label.title, - href: '#' - }), - $labelColor = $('', { - class: 'dropdown-label-box', - style: `background-color: ${label.color}` - }); + $this.glDropdown({ + data(term, callback) { + $.get($this.attr('data-labels')) + .then((resp) => { + callback(resp); + }); + }, + renderRow (label) { + const active = Store.findList('title', label.title), + $li = $('
  • '), + $a = $('', { + class: (active ? `is-active js-board-list-${active.id}` : ''), + text: label.title, + href: '#' + }), + $labelColor = $('', { + class: 'dropdown-label-box', + style: `background-color: ${label.color}` + }); - return $li.append($a.prepend($labelColor)); - }, - search: { - fields: ['title'] - }, - filterable: true, - selectable: true, - multiSelect: true, - clicked (label, $el, e) { - e.preventDefault(); + return $li.append($a.prepend($labelColor)); + }, + search: { + fields: ['title'] + }, + filterable: true, + selectable: true, + multiSelect: true, + clicked (label, $el, e) { + e.preventDefault(); - if (!Store.findList('title', label.title)) { - Store.new({ - title: label.title, - position: Store.state.lists.length - 2, - list_type: 'label', - label: { - id: label.id, + if (!Store.findList('title', label.title)) { + Store.new({ 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'); + } } - } + }); }); - }); -}); + }; +})(); diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 index db9a5a8e40a..5f99de39122 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -23,7 +23,7 @@ fallbackOnBody: true, ghostClass: 'is-ghost', filter: '.board-delete, .btn', - delay: gl.issueBoards.touchEnabled ? 100 : 50, + delay: gl.issueBoards.touchEnabled ? 100 : 0, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSpeed: 20, onStart: gl.issueBoards.onStart, diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index b331a26fed5..8a7dc67409e 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -42,7 +42,8 @@ class List { } 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.boardService.destroyList(this.id); diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index 175e034afed..6bc95aa60f2 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -39,6 +39,8 @@ // Remove any new issues from the backlog // as they will be visible in the new list list.issues.forEach(backlogList.removeIssue.bind(backlogList)); + + this.state.lists = _.sortBy(this.state.lists, 'position'); }); this.removeBlankState(); }, @@ -58,6 +60,8 @@ title: 'Welcome to your Issue Board!', position: 0 }); + + this.state.lists = _.sortBy(this.state.lists, 'position'); }, removeBlankState () { this.removeList('blank'); diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 index 29a12a2395b..52e2846d279 100644 --- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 @@ -1,9 +1,13 @@ /* eslint-disable */ -((w) => { - w.CommentAndResolveBtn = Vue.extend({ +(() => { + const CommentAndResolveBtn = Vue.extend({ props: { discussionId: String, - textareaIsEmpty: Boolean + }, + data() { + return { + textareaIsEmpty: true + } }, computed: { discussion: function () { @@ -35,7 +39,7 @@ } } }, - ready: function () { + mounted: function () { const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`); this.textareaIsEmpty = $textarea.val() === ''; @@ -47,4 +51,6 @@ $(`#new-discussion-note-form-${this.discussionId} .note-textarea`).off('input.comment-and-resolve-btn'); } }); + + Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); })(window); diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 index bcc052c7c8c..27af9fc96ad 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 @@ -1,6 +1,6 @@ /* eslint-disable */ -((w) => { - w.ResolveBtn = Vue.extend({ +(() => { + const ResolveBtn = Vue.extend({ props: { noteId: Number, discussionId: String, @@ -54,7 +54,7 @@ }, methods: { updateTooltip: function () { - $(this.$els.button) + $(this.$refs.button) .tooltip('hide') .tooltip('fixTitle'); }, @@ -89,8 +89,8 @@ }); } }, - compiled: function () { - $(this.$els.button).tooltip({ + mounted: function () { + $(this.$refs.button).tooltip({ container: 'body' }); }, @@ -101,4 +101,6 @@ CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy); } }); -})(window); + + Vue.component('resolve-btn', ResolveBtn); +})(); diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 index 24a99e23132..9522ccb49da 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 @@ -13,6 +13,9 @@ computed: { allResolved: function () { return this.resolvedDiscussionCount === this.discussionCount; + }, + resolvedCountText() { + return this.discussionCount === 1 ? 'discussion' : 'discussions'; } } }); diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 index 060034f049b..b945a09fcbe 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 @@ -1,6 +1,6 @@ /* eslint-disable */ -((w) => { - w.ResolveDiscussionBtn = Vue.extend({ +(() => { + const ResolveDiscussionBtn = Vue.extend({ props: { discussionId: String, mergeRequestId: Number, @@ -54,4 +54,6 @@ CommentsStore.createDiscussion(this.discussionId, this.canResolve); } }); -})(window); + + Vue.component('resolve-discussion-btn', ResolveDiscussionBtn); +})(); diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 index 6149bfd052a..bd4c20aed8b 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 @@ -8,24 +8,35 @@ //= require_directory ./components $(() => { - window.DiffNotesApp = new Vue({ - el: '#diff-notes-app', - components: { - 'resolve-btn': ResolveBtn, - 'resolve-discussion-btn': ResolveDiscussionBtn, - 'comment-and-resolve-btn': CommentAndResolveBtn - }, - methods: { - compileComponents: function () { - const $components = $('resolve-btn, resolve-discussion-btn, jump-to-discussion'); - if ($components.length) { - $components.each(function () { - DiffNotesApp.$compile($(this).get(0)); - }); + const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn'; + + window.gl = window.gl || {}; + window.gl.diffNoteApps = {}; + + gl.diffNotesCompileComponents = () => { + const $components = $(COMPONENT_SELECTOR).filter(function () { + return $(this).closest('resolve-count').length !== 1; + }); + + if ($components) { + $components.each(function () { + 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({ el: '#resolve-count-app', diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 6da3942ea52..9e4ffd07dbd 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -36,7 +36,7 @@ this.loadEditor(); } }, - ready() { + mounted() { if (this.file.loadEditor) { this.loadEditor(); } diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 deleted file mode 100644 index 797850262cc..00000000000 --- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 +++ /dev/null @@ -1,15 +0,0 @@ -/* 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 = {})); diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 index 1b3e9901f1e..4ccbdcd6daa 100644 --- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 @@ -7,10 +7,22 @@ props: { file: Object }, - mixins: [global.mergeConflicts.utils], - components: { - 'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine - } + mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions], + template: ` + + + + +
    + `, }); })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 222a5dcfc2e..815443fb54e 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -6,7 +6,6 @@ //= require ./mixins/line_conflict_actions //= require ./components/diff_file_editor //= require ./components/inline_conflict_lines -//= require ./components/parallel_conflict_line //= require ./components/parallel_conflict_lines $(() => { @@ -49,7 +48,7 @@ $(() => { mergeConflictsStore.setLoadingState(false); this.$nextTick(() => { - $(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight(); + $('.js-syntax-highlight').syntaxHighlight(); }); }); }, diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 860ee5df57e..5ca4b34909a 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -130,7 +130,7 @@ MergeRequestTabs.prototype.scrollToElement = function(container) { var $el, navBarHeight; 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)"); if ($el.length) { return $.scrollTo(container + " " + window.location.hash + ":not(.match)", { @@ -227,8 +227,8 @@ return function(data) { $('#diffs').html(data.html); - if (typeof DiffNotesApp !== 'undefined') { - DiffNotesApp.compileComponents(); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); } gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 4976eef2896..df7e316ca6c 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -325,8 +325,8 @@ discussionContainer.append(note_html); } - if (typeof DiffNotesApp !== 'undefined') { - DiffNotesApp.compileComponents(); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); } gl.utils.localTimeAgo($('.js-timeago', note_html), false); @@ -466,8 +466,8 @@ $note_li.replaceWith($html); - if (typeof DiffNotesApp !== 'undefined') { - DiffNotesApp.compileComponents(); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); } }; @@ -559,11 +559,9 @@ note = $(el); notes = note.closest(".notes"); - if (typeof DiffNotesApp !== "undefined" && DiffNotesApp !== null) { - ref = DiffNotesApp.$refs[noteId]; - - if (ref) { - ref.$destroy(true); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + if (gl.diffNoteApps[noteId]) { + gl.diffNoteApps[noteId].$destroy(); } } @@ -643,11 +641,12 @@ form.find('.js-note-target-close').remove(); this.setupNoteForm(form); - if (typeof DiffNotesApp !== 'undefined') { + if (typeof gl.diffNotesCompileComponents !== 'undefined') { var $commentBtn = form.find('comment-and-resolve-btn'); $commentBtn .attr(':discussion-id', "'" + dataHolder.data('discussionId') + "'"); - DiffNotesApp.$compile($commentBtn.get(0)); + + gl.diffNotesCompileComponents(); } form.find(".js-note-text").focus(); diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 8e54ca4f0dc..01ccbe5b987 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -45,15 +45,15 @@ this.content.hide(); this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down'); this.collapsedContent.show(); - if (typeof DiffNotesApp !== 'undefined') { - DiffNotesApp.compileComponents(); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); } } else if (this.content) { this.collapsedContent.hide(); this.content.show(); this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); - if (typeof DiffNotesApp !== 'undefined') { - DiffNotesApp.compileComponents(); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); } } else { this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); @@ -76,8 +76,8 @@ } _this.collapsedContent.after(_this.content); - if (typeof DiffNotesApp !== 'undefined') { - DiffNotesApp.compileComponents(); + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); } if (cb) cb(); diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index c1ed43bc20f..9391661a595 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -71,7 +71,7 @@ display: none; } - .group-right-buttons { + .group-buttons { display: none; } diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 47a7e84b5c6..4f5753f6fc6 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -166,8 +166,12 @@ } } -.board-list { +.board-list-component { height: calc(100% - 49px); +} + +.board-list { + height: 100%; margin-bottom: 0; padding: 5px; list-style: none; @@ -175,7 +179,7 @@ overflow-x: hidden; &.is-smaller { - height: calc(100% - 185px); + height: calc(100% - 136px); } } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 4375e29c8db..57d028cec8c 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -10,7 +10,6 @@ } .group-row { - .stats { float: right; line-height: $list-text-height; @@ -23,36 +22,18 @@ } .ldap-group-links { - .form-actions { margin-bottom: $gl-padding; } } -.groups-cover-block { - - .container-fluid { - position: relative; - } - - .group-right-buttons { - position: absolute; - right: 16px; - - .btn { - @include btn-gray; - padding: 3px 10px; - background-color: $background-color; - } - } - - .group-avatar { - border: 0; +.group-buttons { + .notification-dropdown { + display: inline-block; } } .groups-header { - @media (min-width: $screen-sm-min) { .nav-links { width: 35%; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f7d54564530..ad46a2a9128 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -86,7 +86,8 @@ } } -.project-home-panel { +.project-home-panel, +.group-home-panel { padding-top: 24px; padding-bottom: 24px; @@ -94,7 +95,8 @@ border-bottom: 1px solid $border-color; } - .project-avatar { + .project-avatar, + .group-avatar { float: none; margin: 0 auto; border: none; @@ -104,7 +106,8 @@ } } - .project-title { + .project-title, + .group-title { margin-top: 10px; margin-bottom: 10px; font-size: 24px; @@ -118,10 +121,11 @@ } } - .project-home-desc { + .project-home-desc, + .group-home-desc { margin-left: auto; margin-right: auto; - margin-bottom: 15px; + margin-bottom: 0; max-width: 700px; > p { @@ -141,13 +145,18 @@ } } -.project-repo-buttons { - font-size: 0; +.project-repo-buttons, +.group-buttons { + margin-top: 15px; .btn { @include btn-gray; padding: 3px 10px; + &:last-child { + margin-left: 0; + } + .fa { color: $layout-link-gray; } @@ -168,7 +177,8 @@ } } - .project-repo-btn-group, + .download-button, + .dropdown-toggle, .notification-dropdown, .project-dropdown { margin-left: 10px; @@ -474,9 +484,7 @@ a.deploy-project-label { margin-right: $gl-padding; } - &.project-repo-buttons-right { - margin-top: 10px; - + &.right { @media (min-width: $screen-md-min) { float: right; margin-top: 0; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 52e0256943a..b81842e319b 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -117,6 +117,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :send_user_confirmation_email, :container_registry_token_expire_delay, :enabled_git_access_protocol, + :sidekiq_throttling_enabled, + :sidekiq_throttling_factor, :housekeeping_enabled, :housekeeping_bitmaps_enabled, :housekeeping_incremental_repack_period, @@ -125,7 +127,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController repository_storages: [], restricted_visibility_levels: [], import_sources: [], - disabled_oauth_sign_in_sources: [] + disabled_oauth_sign_in_sources: [], + sidekiq_throttling_queues: [] ) end end diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb index aeec3009f15..1efa9fe060f 100644 --- a/app/controllers/concerns/diff_for_path.rb +++ b/app/controllers/concerns/diff_for_path.rb @@ -3,7 +3,7 @@ module DiffForPath def render_diff_for_path(diffs) 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 return render_404 unless diff_file diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 9d5a28e8d4d..506484932cc 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -58,7 +58,7 @@ class Groups::MilestonesController < Groups::ApplicationController def render_new_with_error(empty_project_ids) @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 end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 45a567a1eba..be5e0301a43 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -100,4 +100,8 @@ module ApplicationSettingsHelper options_for_select(options, @application_setting.repository_storages) end + + def sidekiq_queue_options_for_select + options_for_select(Sidekiq::Queue.all.map(&:name), @application_setting.sidekiq_throttling_queues) + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 42c00ec3cd5..17123b1eaee 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -49,7 +49,7 @@ module ProjectsHelper end end - def project_title(project, name = nil, url = nil) + def project_title(project) namespace_link = if project.group link_to(simple_sanitize(project.group.name), group_path(project.group)) @@ -66,10 +66,7 @@ module ProjectsHelper end end - full_title = "#{namespace_link} / #{project_link}".html_safe - full_title << ' · '.html_safe << link_to(simple_sanitize(name), url) if name - - full_title + "#{namespace_link} / #{project_link}".html_safe end def remove_project_message(project) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index bb60cc8736c..d1e1b45ab43 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -19,6 +19,7 @@ class ApplicationSetting < ActiveRecord::Base serialize :domain_whitelist, Array serialize :domain_blacklist, Array serialize :repository_storages + serialize :sidekiq_throttling_queues, Array cache_markdown_field :sign_in_text cache_markdown_field :help_page_text @@ -85,6 +86,15 @@ class ApplicationSetting < ActiveRecord::Base presence: { message: 'Domain blacklist cannot be empty if Blacklist is 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, presence: true, numericality: { only_integer: true, greater_than: 0 } @@ -180,6 +190,7 @@ class ApplicationSetting < ActiveRecord::Base container_registry_token_expire_delay: 5, repository_storages: ['default'], user_default_external: false, + sidekiq_throttling_enabled: false, housekeeping_enabled: true, housekeeping_bitmaps_enabled: true, housekeeping_incremental_repack_period: 10, diff --git a/app/models/repository.rb b/app/models/repository.rb index fe991904601..063dc74021d 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -243,7 +243,7 @@ class Repository # offer 'compare and swap' ref updates. Without compare-and-swap we can # (and have!) accidentally reset the ref to an earlier state, clobbering # 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| stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00") end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 450ec322f2c..a236335131a 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -283,6 +283,31 @@ The amount of points to store in a single UDP packet. More points 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 %legend Spam and Anti-bot Protection .form-group diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 23d438b2aa1..0dfaf743992 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -34,7 +34,7 @@ = f.label :projects, "Projects", class: "control-label" .col-sm-10 = 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 .form-group diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index b439b40a75a..52ce26a20b1 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -4,25 +4,23 @@ - if current_user = 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 } .avatar-container.s70.group-avatar = image_tag group_icon(@group), class: "avatar s70 avatar-tile" - .group-info - .cover-title - %h1 - @#{@group.path} - %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } - = visibility_level_icon(@group.visibility_level, fw: false) + %h1.group-title + @#{@group.path} + %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 current_user - .pull-left.append-right-10= render 'shared/members/access_request_buttons', source: @group - = render 'shared/notifications/button', notification_setting: @notification_setting + - if @group.description.present? + .group-home-desc + = markdown_field(@group, :description) - - if @group.description.present? - .cover-desc.description - = markdown_field(@group, :description) + - if current_user + .group-buttons + = render 'shared/members/access_request_buttons', source: @group + = render 'shared/notifications/button', notification_setting: @notification_setting %div.groups-header{ class: container_class } .top-area diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 539d07d634a..ede01dcc1aa 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -1,5 +1,4 @@ - page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds' -- header_title project_title(@project, "Builds", project_builds_path(@project)) .top-block.row-content-block.clearfix .pull-right diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml new file mode 100644 index 00000000000..356bd50f7f3 --- /dev/null +++ b/app/views/projects/boards/_show.html.haml @@ -0,0 +1,28 @@ +- @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" diff --git a/app/views/projects/boards/components/_blank_state.html.haml b/app/views/projects/boards/components/_blank_state.html.haml index 97eb952eff1..0af40ddf8fe 100644 --- a/app/views/projects/boards/components/_blank_state.html.haml +++ b/app/views/projects/boards/components/_blank_state.html.haml @@ -1,5 +1,5 @@ %board-blank-state{ "inline-template" => true, - "v-if" => "list.id == 'blank'" } + "v-if" => 'list.id == "blank"' } .board-blank-state %p Add the following default lists to your Issue Board with one click: diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index f7071051efc..47165c70097 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -1,80 +1,34 @@ -%board{ "inline-template" => true, - "v-cloak" => true, - "v-for" => "list in state.lists | orderBy 'position'", - "v-ref:board" => true, - ":list" => "list", - ":disabled" => "disabled", - ":issue-link-base" => "issueLinkBase", - "track-by" => "_uid" } - .board{ ":class" => "{ 'is-draggable': !list.preset }", - ":data-id" => "list.id" } - .board-inner - %header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } - %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } - %span.has-tooltip{ ":title" => "(list.label ? list.label.description : '')", - data: { container: "body", placement: "bottom" } } - {{ list.title }} - .board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" } - %span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" } - {{ list.issuesSize }} - - if can?(current_user, :admin_issue, @project) - %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", - "@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, +.board{ ":class" => '{ "is-draggable": !list.preset }', + ":data-id" => "list.id" } + .board-inner + %header.board-header{ ":class" => '{ "has-border": list.label }', ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } + %h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' } + %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")', + data: { container: "body", placement: "bottom" } } + {{ list.title }} + .board-issue-count-holder.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' } + %span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "done" }' } + {{ list.issuesSize }} + - if can?(current_user, :admin_issue, @project) + %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", + "@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", - ":show-issue-form.sync" => "showIssueForm", - "v-show" => "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", - "v-el:input" => true, - ":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" + "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{ "v-if" => 'list.type !== "blank"', + ":list" => "list", + ":issues" => "list.issues", + ":loading" => "list.loading", + ":disabled" => "disabled", + ":issue-link-base" => "issueLinkBase", + "ref" => "board-list" } + - if can?(current_user, :admin_list, @project) + = render "projects/boards/components/blank_state" diff --git a/app/views/projects/boards/components/_board_list.html.haml b/app/views/projects/boards/components/_board_list.html.haml new file mode 100644 index 00000000000..d86e0ed8540 --- /dev/null +++ b/app/views/projects/boards/components/_board_list.html.haml @@ -0,0 +1,44 @@ +.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 diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 8fce702314c..72b31b8cdae 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -1,36 +1,26 @@ -%board-card{ "inline-template" => true, - "v-for" => "issue in issues | orderBy 'priority'", - "v-ref:issue" => true, - ":index" => "$index", - ":list" => "list", - ":issue" => "issue", - ":issue-link-base" => "issueLinkBase", - ":disabled" => "disabled", - "track-by" => "id" } - %li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }", - ":index" => "index", - "@mousedown" => "mouseDown", - "@mouseMove" => "mouseMove", - "@mouseup" => "showIssue($event)" } - %h4.card-title - = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") - %a{ ":href" => "issueLinkBase + '/' + issue.id", - ":title" => "issue.title" } - {{ issue.title }} - .card-footer - %span.card-number{ "v-if" => "issue.id" } - = precede '#' do - {{ issue.id }} - %a.has-tooltip{ ":href" => "'#{root_path}' + issue.assignee.username", - ":title" => "'Assigned to ' + issue.assignee.name", - "v-if" => "issue.assignee", - 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 }} +%li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }', + ":index" => "index", + "@mousedown" => "mouseDown", + "@mouseup" => "showIssue($event)" } + %h4.card-title + = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") + %a{ ":href" => 'issueLinkBase + "/" + issue.id', + ":title" => "issue.title" } + {{ issue.title }} + .card-footer + %span.card-number{ "v-if" => "issue.id" } + = precede '#' do + {{ issue.id }} + %a.has-tooltip{ ":href" => "\"#{root_path}\" + issue.assignee.username", + ":title" => '"Assigned to " + issue.assignee.name', + "v-if" => "issue.assignee", + 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 }} diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 29c9a43a0c1..2a5b8b1441e 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -1,18 +1 @@ -- @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? - -= 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" += render "show" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 29c9a43a0c1..2a5b8b1441e 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -1,18 +1 @@ -- @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? - -= 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" += render "show" diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index ae7a7ecb392..f533eec642e 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,6 +1,6 @@ - @no_container = true - 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 %div{ class: container_class } diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 6cd9b98a706..d3ccebbe290 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,5 +1,5 @@ - if current_user - .dropdown.inline.project-dropdown + .dropdown.inline %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('plus') = icon("caret-down") diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index 779c8ea0104..c3d2f80544b 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -9,7 +9,7 @@ - if !project.repository.diffable?(blob) .nothing-here-block This diff was suppressed by a .gitattributes entry. - 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 } } This diff is collapsed. %a.click-to-expand diff --git a/app/views/projects/environments/_header_title.html.haml b/app/views/projects/environments/_header_title.html.haml deleted file mode 100644 index e056fccad5d..00000000000 --- a/app/views/projects/environments/_header_title.html.haml +++ /dev/null @@ -1 +0,0 @@ -- header_title project_title(@project, "Environments", project_environments_path(@project)) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index f57abe73977..a497f418c7c 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -74,14 +74,15 @@ %span.badge= @merge_request.diff_size %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?}" } - .line-resolve-all{ "v-show" => "discussionCount > 0", - ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } - %span.line-resolve-btn.is-disabled{ type: "button", - ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } - = render "shared/icons/icon_status_success.svg" - %span.line-resolve-text - {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ discussionCount | pluralize 'discussion' }} resolved - = render "discussions/jump_to_next" + %div + .line-resolve-all{ "v-show" => "discussionCount > 0", + ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } + %span.line-resolve-btn.is-disabled{ type: "button", + ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } + = render "shared/icons/icon_status_success.svg" + %span.line-resolve-text + {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved + = render "discussions/jump_to_next" .tab-content#diff-notes-app #notes.notes.tab-pane.voting_notes diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index d9f74d2cbfb..16789f68f70 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -30,11 +30,8 @@ .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" .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'"} = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" = render partial: "projects/merge_requests/conflicts/submit_form" - --# Components -= render partial: 'projects/merge_requests/conflicts/components/parallel_conflict_line' diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml index f094df7fcaa..d35c7bee163 100644 --- a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml @@ -5,11 +5,10 @@ %a {{line.new_line}} %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} %a {{line.old_line}} - %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} - {{{line.richText}}} + %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader", "v-html" => "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.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)" } {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml deleted file mode 100644 index 5690bf7419c..00000000000 --- a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -%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}}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml deleted file mode 100644 index a8ecdf59393..00000000000 --- a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%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"} diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index ab719e38904..afff15228c1 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -32,7 +32,7 @@ "resolved-by" => "#{note.resolved_by.try(:name)}", "v-show" => "#{can_resolve || note.resolved?}", "inline-template" => true, - "v-ref:note_#{note.id}" => true } + "ref" => "note_#{note.id}" } .note-action-button = icon("spin spinner", "v-show" => "loading") @@ -43,7 +43,7 @@ "@click" => "resolve", ":title" => "buttonText", "v-show" => "!loading", - "v-el:button" => true } + ":ref" => "'button'" } = render "shared/icons/icon_status_success.svg" diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 4de95036eef..c50093cf47c 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -66,8 +66,8 @@ = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do Set Up CI - %li.project-repo-buttons-right - .project-repo-buttons.project-right-buttons + %li.project-repo-buttons.right + .project-right-buttons - if current_user = render 'shared/members/access_request_buttons', source: @project = render "projects/buttons/koding" @@ -76,7 +76,8 @@ = render 'projects/buttons/download', project: @project, ref: @ref = 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 .project-last-commit{ class: container_class } = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index feaa5570c21..1f7df0bcd19 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,6 +1,6 @@ - left_align = local_assigns[:left_align] - if notification_setting - .dropdown.notification-dropdown.pull-right + .dropdown.notification-dropdown = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = hidden_setting_source_input(notification_setting) = f.hidden_field :level, class: "notification_setting_level" diff --git a/changelogs/unreleased/22699-group-permssion-background-migration.yml b/changelogs/unreleased/22699-group-permssion-background-migration.yml new file mode 100644 index 00000000000..e8c221b6c42 --- /dev/null +++ b/changelogs/unreleased/22699-group-permssion-background-migration.yml @@ -0,0 +1,4 @@ +--- +title: Fix project records with invalid visibility_level values +merge_request: 7391 +author: diff --git a/changelogs/unreleased/adam-fix-collapsed-diff-symlink-file-conversion.yml b/changelogs/unreleased/adam-fix-collapsed-diff-symlink-file-conversion.yml new file mode 100644 index 00000000000..c83558f33d1 --- /dev/null +++ b/changelogs/unreleased/adam-fix-collapsed-diff-symlink-file-conversion.yml @@ -0,0 +1,4 @@ +--- +title: Fix expanding a collapsed diff when converting a symlink to a regular file +merge_request: 6953 +author: diff --git a/changelogs/unreleased/milestone-project-require.yml b/changelogs/unreleased/milestone-project-require.yml new file mode 100644 index 00000000000..e43033541c7 --- /dev/null +++ b/changelogs/unreleased/milestone-project-require.yml @@ -0,0 +1,4 @@ +--- +title: Require projects before creating milestone. +merge_request: 7301 +author: gfyoung diff --git a/changelogs/unreleased/sidekiq-job-throttling.yml b/changelogs/unreleased/sidekiq-job-throttling.yml new file mode 100644 index 00000000000..ec4e2051c7e --- /dev/null +++ b/changelogs/unreleased/sidekiq-job-throttling.yml @@ -0,0 +1,4 @@ +--- +title: Added ability to throttle Sidekiq Jobs +merge_request: 7292 +author: diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 023af2af23c..b87b31d9697 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -29,6 +29,8 @@ Sidekiq.configure_server do |config| end Sidekiq::Cron::Job.load_from_hash! cron_jobs + Gitlab::SidekiqThrottler.execute! + # Database pool should be at least `sidekiq_concurrency` + 2 # For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md config = ActiveRecord::Base.configurations[Rails.env] || diff --git a/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb b/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb new file mode 100644 index 00000000000..e644a174964 --- /dev/null +++ b/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb @@ -0,0 +1,31 @@ +# 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 diff --git a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb new file mode 100644 index 00000000000..bea1cfa4c5d --- /dev/null +++ b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb @@ -0,0 +1,49 @@ +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 diff --git a/db/schema.rb b/db/schema.rb index 6b291724df6..4cb3f0b9ddb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # 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 enable_extension "plpgsql" @@ -98,6 +98,9 @@ ActiveRecord::Schema.define(version: 20161106185620) do t.text "help_page_text_html" t.text "shared_runners_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_bitmaps_enabled", default: true, null: false t.integer "housekeeping_incremental_repack_period", default: 10, null: false diff --git a/doc/administration/operations.md b/doc/administration/operations.md index 4b582d16b64..0daceb98d99 100644 --- a/doc/administration/operations.md +++ b/doc/administration/operations.md @@ -1,6 +1,7 @@ # GitLab operations - [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) - [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) - [Moving repositories to a new location](operations/moving_repositories.md) diff --git a/doc/administration/operations/img/sidekiq_job_throttling.png b/doc/administration/operations/img/sidekiq_job_throttling.png new file mode 100644 index 00000000000..7f29a4d3c46 Binary files /dev/null and b/doc/administration/operations/img/sidekiq_job_throttling.png differ diff --git a/doc/administration/operations/sidekiq_job_throttling.md b/doc/administration/operations/sidekiq_job_throttling.md new file mode 100644 index 00000000000..ddeaa22e288 --- /dev/null +++ b/doc/administration/operations/sidekiq_job_throttling.md @@ -0,0 +1,33 @@ +# 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 diff --git a/doc/development/README.md b/doc/development/README.md index bf1f054b7d5..87306b1501d 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -14,7 +14,7 @@ contributing to documentation. - [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations - [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) - [SQL guidelines](sql.md) for working with SQL queries - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers diff --git a/doc/development/ux_guide/basics.md b/doc/development/ux_guide/basics.md new file mode 100644 index 00000000000..62ac56a6bce --- /dev/null +++ b/doc/development/ux_guide/basics.md @@ -0,0 +1,94 @@ +# 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. diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md new file mode 100644 index 00000000000..764c3355714 --- /dev/null +++ b/doc/development/ux_guide/components.md @@ -0,0 +1,254 @@ +# Components + +## Contents +* [Tooltips](#tooltips) +* [Anchor links](#anchor-links) +* [Buttons](#buttons) +* [Dropdowns](#dropdowns) +* [Counts](#counts) +* [Lists](#lists) +* [Tables](#tables) +* [Blocks](#blocks) +* [Panels](#panels) +* [Alerts](#alerts) +* [Forms](#forms) +* [File holders](#file-holders) +* [Data formats](#data-formats) + +--- + +## Tooltips + +### Usage +A tooltip should only be added if additional information is required. + +![Tooltip usage](img/tooltip-usage.png) + +### Placement +By default, tooltips should be placed below the element that they refer to. However, if there is not enough space in the viewpoint, the tooltip should be moved to the side as needed. + +![Tooltip placement location](img/tooltip-placement.png) + +--- + +## Anchor links + +Anchor links are used for navigational actions and lone, secondary commands (such as 'Reset filters' on the Issues List) when deemed appropriate by the UX team. + +### States + +#### Rest + +Primary links are blue in their rest state. Secondary links (such as the time stamp on comments) are a neutral gray color in rest. Details on the main GitLab navigation links can be found on the [features](features.md#navigation) page. + +#### Hover + +An underline should always be added on hover. A gray link becomes blue on hover. + +#### Focus + +The focus state should match the hover state. + +![Anchor link states ](img/components-anchorlinks.png) + +--- + +## Buttons + +Buttons communicate the command that will occur when the user clicks on them. + +### Types + +#### Primary +Primary buttons communicate the main call to action. There should only be one call to action in any given experience. Visually, primary buttons are conveyed with a full background fill + +![Primary button example](img/button-primary.png) + +#### Secondary +Secondary buttons are for alternative commands. They should be conveyed by a button with an stroke, and no background fill. + +![Secondary button example](img/button-secondary.png) + +### Icon and text treatment +Text should be in sentence case, where only the first word is capitalized. "Create issue" is correct, not "Create Issue". Buttons should only contain an icon or a text, not both. + +>>> +TODO: Rationalize this. Ensure that we still believe this. +>>> + +### Colors +Follow the color guidance on the [basics](basics.md#color) page. The default color treatment is the white/grey button. + +--- + +## Dropdowns + +Dropdowns are used to allow users to choose one (or many) options from a list of options. If this list of options is more 20, there should generally be a way to search through and filter the options (see the complex filter dropdowns below.) + +>>> +TODO: Will update this section when the new filters UI is implemented. +>>> + +![Dropdown states](img/components-dropdown.png) + + + +--- + +## Counts + +A count element is used in navigation contexts where it is helpful to indicate the count, or number of items, in a list. Always use the [`number_with_delimiter`][number_with_delimiter] helper to display counts in the UI. + +![Counts example](img/components-counts.png) + +[number_with_delimiter]: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_delimiter + +--- + +## Lists + +Lists are used where ever there is a single column of information to display. Ths [issues list](https://gitlab.com/gitlab-org/gitlab-ce/issues) is an example of a important list in the GitLab UI. + +### Types + +Simple list using .content-list + +![Simple list](img/components-simplelist.png) + +List with avatar, title and description using .content-list + +![List with avatar](img/components-listwithavatar.png) + +List with hover effect .well-list + +![List with hover effect](img/components-listwithhover.png) + +List inside panel + +![List inside panel](img/components-listinsidepanel.png) + +--- + +## Tables + +When the information is too complex for a list, with multiple columns of information, a table can be used. For example, the [pipelines page](https://gitlab.com/gitlab-org/gitlab-ce/pipelines) uses a table. + +![Table](img/components-table.png) + +--- + +## Blocks + +Blocks are a way to group related information. + +### Types + +#### Content blocks + +Content blocks (`.content-block`) are the basic grouping of content. They are commonly used in [lists](#lists), and are separated by a botton border. + +![Content block](img/components-contentblock.png) + +#### Row content blocks + +A background color can be added to this blocks. For example, items in the [issue list](https://gitlab.com/gitlab-org/gitlab-ce/issues) have a green background if they were created recently. Below is an example of a gray content block with side padding using `.row-content-block`. + +![Row content block](img/components-rowcontentblock.png) + +#### Cover blocks +Cover blocks are generally used to create a heading element for a page, such as a new project, or a user profile page. Below is a cover block (`.cover-block`) for the profile page with an avatar, name and description. + +![Cover block](img/components-coverblock.png) + +--- + +## Panels + +>>> +TODO: Catalog how we are currently using panels and rationalize how they relate to alerts +>>> + +![Panels](img/components-panels.png) + +--- + +## Alerts + +>>> +TODO: Catalog how we are currently using alerts +>>> + +![Alerts](img/components-alerts.png) + +--- + +## Forms + +There are two options shown below regarding the positioning of labels in forms. Both are options to consider based on context and available size. However, it is important to have a consistent treatment of labels in the same form. + +### Types + +#### Labels stack vertically + +Form (`form`) with label rendered above input. + +![Vertical form](img/components-verticalform.png) + +#### Labels side-by-side + +Horizontal form (`form.horizontal-form`) with label rendered inline with input. + +![Horizontal form](img/components-horizontalform.png) + +--- + +## File holders +A file holder (`.file-holder`) is used to show the contents of a file inline on a page of GitLab. + +![File Holder component](img/components-fileholder.png) + +--- + +## Data formats + +### Dates + +#### Exact + +Format for exacts dates should be ‘Mon DD, YYYY’, such as the examples below. + +![Exact date](img/components-dateexact.png) + +#### Relative + +This format relates how long since an action has occurred. The exact date can be shown as a tooltip on hover. + +![Relative date](img/components-daterelative.png) + +### References + +Referencing GitLab items depends on a symbol for each type of item. Typing that symbol will invoke a dropdown that allows you to search for and autocomplete the item you were looking for. References are shown as [links](#links) in context, and hovering on them shows the full title or name of the item. + +![Hovering on a reference](img/components-referencehover.png) + +#### `%` Milestones + +![Milestone reference](img/components-referencemilestone.png) + +#### `#` Issues + +![Issue reference](img/components-referenceissues.png) + +#### `!` Merge Requests + +![Merge request reference](img/components-referencemrs.png) + +#### `~` Labels + +![Labels reference](img/components-referencelabels.png) + +#### `@` People + +![People reference](img/components-referencepeople.png) + +> TODO: Open issue: Some commit references use monospace fonts, but others don't. Need to standardize this. diff --git a/doc/development/ux_guide/features.md b/doc/development/ux_guide/features.md new file mode 100644 index 00000000000..9472995c68c --- /dev/null +++ b/doc/development/ux_guide/features.md @@ -0,0 +1,57 @@ +# Features + +## Contents +* [Navigation](#navigation) +* [Filtering](#filtering) +* [Search results](#search-results) +* [Conversations](#conversations) +* [Empty states](#empty-states) + +--- + +## Navigation + +### Global navigation + +The global navigation is accessible via the menu button on the top left of the screen, and can be pinned to keep it open. It contains a consistent list of pages that allow you to view content that is across GitLab. For example, you can view your todos, issues and merge requests across projects and groups. + +![Global nav](img/features-globalnav.png) + + +### Contextual navigation + +The navigation in the header is contextual to each page. These options change depending on if you are looking at a project, group, or settings page. There should be no more than 10 items on a level in the contextual navigation, allowing it to comfortably fit on a typical laptop screen. There can be up to too levels of navigation. Each sub nav group should be a self-contained group of functionality. For example, everything related to the issue tracker should be under the 'Issue' tab, while everything relating to the wiki will be grouped under the 'Wiki' tab. The names used for each section should be short and easy to remember, ideally 1-2 words in length. + +![Contextual nav](img/features-contextualnav.png) + +### Information architecture + +The [GitLab Product Map](https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png) shows a visual representation of the information architecture for GitLab. + +--- + +## Filtering + +Today, lists are filtered by a series of dropdowns. Some of these dropdowns allow multiselect (labels), while others allow you to filter to one option (milestones). However, we are currently implementing a [new model](https://gitlab.com/gitlab-org/gitlab-ce/issues/21747) for this, and will update the guide when it is ready. + +![Filters](img/features-filters.png) + +--- + +## Search results + +### Global search + +[Global search](https://gitlab.com/search?group_id=&project_id=13083&repository_ref=&scope=issues&search=mobile) allows you to search across items in a project, or even across multiple projects. You can switch tabs to filter on type of object, or filter by group. + +### List search + +There are several core lists in the GitLab experience, such as the Issue list and the Merge Request list. You are also able to [filter and search these lists](https://gitlab.com/gitlab-org/gitlab-ce/issues?utf8=%E2%9C%93&search=mobile). This UI will be updated with the [new filtering model](https://gitlab.com/gitlab-org/gitlab-ce/issues/21747). + +--- + +## Empty states + +Empty states need to be considered in the design of features. They are vital to helping onboard new users, making the experience feel more approachable and understandable. Empty states should feel inviting and provide just enough information to get people started. There should be a single call to action and a clear explanation of what to use the feature for. + +![Empty states](img/features-emptystates.png) diff --git a/doc/development/ux_guide/img/button-primary.png b/doc/development/ux_guide/img/button-primary.png new file mode 100644 index 00000000000..f4c673f5b88 Binary files /dev/null and b/doc/development/ux_guide/img/button-primary.png differ diff --git a/doc/development/ux_guide/img/button-secondary.png b/doc/development/ux_guide/img/button-secondary.png new file mode 100644 index 00000000000..57fa65b247c Binary files /dev/null and b/doc/development/ux_guide/img/button-secondary.png differ diff --git a/doc/development/ux_guide/img/color-blue.png b/doc/development/ux_guide/img/color-blue.png new file mode 100644 index 00000000000..6449613eb16 Binary files /dev/null and b/doc/development/ux_guide/img/color-blue.png differ diff --git a/doc/development/ux_guide/img/color-green.png b/doc/development/ux_guide/img/color-green.png new file mode 100644 index 00000000000..15475b36f02 Binary files /dev/null and b/doc/development/ux_guide/img/color-green.png differ diff --git a/doc/development/ux_guide/img/color-grey.png b/doc/development/ux_guide/img/color-grey.png new file mode 100644 index 00000000000..58c474d5ce9 Binary files /dev/null and b/doc/development/ux_guide/img/color-grey.png differ diff --git a/doc/development/ux_guide/img/color-orange.png b/doc/development/ux_guide/img/color-orange.png new file mode 100644 index 00000000000..f4fc09b2d9b Binary files /dev/null and b/doc/development/ux_guide/img/color-orange.png differ diff --git a/doc/development/ux_guide/img/color-red.png b/doc/development/ux_guide/img/color-red.png new file mode 100644 index 00000000000..6fbbf0a885d Binary files /dev/null and b/doc/development/ux_guide/img/color-red.png differ diff --git a/doc/development/ux_guide/img/components-alerts.png b/doc/development/ux_guide/img/components-alerts.png new file mode 100644 index 00000000000..0b2ecc16a5f Binary files /dev/null and b/doc/development/ux_guide/img/components-alerts.png differ diff --git a/doc/development/ux_guide/img/components-anchorlinks.png b/doc/development/ux_guide/img/components-anchorlinks.png new file mode 100644 index 00000000000..950f348277d Binary files /dev/null and b/doc/development/ux_guide/img/components-anchorlinks.png differ diff --git a/doc/development/ux_guide/img/components-contentblock.png b/doc/development/ux_guide/img/components-contentblock.png new file mode 100644 index 00000000000..31fc1eec9df Binary files /dev/null and b/doc/development/ux_guide/img/components-contentblock.png differ diff --git a/doc/development/ux_guide/img/components-counts.png b/doc/development/ux_guide/img/components-counts.png new file mode 100644 index 00000000000..19280e988a0 Binary files /dev/null and b/doc/development/ux_guide/img/components-counts.png differ diff --git a/doc/development/ux_guide/img/components-coverblock.png b/doc/development/ux_guide/img/components-coverblock.png new file mode 100644 index 00000000000..c8f1f87a108 Binary files /dev/null and b/doc/development/ux_guide/img/components-coverblock.png differ diff --git a/doc/development/ux_guide/img/components-dateexact.png b/doc/development/ux_guide/img/components-dateexact.png new file mode 100644 index 00000000000..8c0c5c1be40 Binary files /dev/null and b/doc/development/ux_guide/img/components-dateexact.png differ diff --git a/doc/development/ux_guide/img/components-daterelative.png b/doc/development/ux_guide/img/components-daterelative.png new file mode 100644 index 00000000000..1dc6d89e4ef Binary files /dev/null and b/doc/development/ux_guide/img/components-daterelative.png differ diff --git a/doc/development/ux_guide/img/components-dropdown.png b/doc/development/ux_guide/img/components-dropdown.png new file mode 100644 index 00000000000..5770a393b37 Binary files /dev/null and b/doc/development/ux_guide/img/components-dropdown.png differ diff --git a/doc/development/ux_guide/img/components-fileholder.png b/doc/development/ux_guide/img/components-fileholder.png new file mode 100644 index 00000000000..4b8962905d6 Binary files /dev/null and b/doc/development/ux_guide/img/components-fileholder.png differ diff --git a/doc/development/ux_guide/img/components-horizontalform.png b/doc/development/ux_guide/img/components-horizontalform.png new file mode 100644 index 00000000000..92e28cf9afc Binary files /dev/null and b/doc/development/ux_guide/img/components-horizontalform.png differ diff --git a/doc/development/ux_guide/img/components-listinsidepanel.png b/doc/development/ux_guide/img/components-listinsidepanel.png new file mode 100644 index 00000000000..30ceb3eaa08 Binary files /dev/null and b/doc/development/ux_guide/img/components-listinsidepanel.png differ diff --git a/doc/development/ux_guide/img/components-listwithavatar.png b/doc/development/ux_guide/img/components-listwithavatar.png new file mode 100644 index 00000000000..d3cb0ebc02b Binary files /dev/null and b/doc/development/ux_guide/img/components-listwithavatar.png differ diff --git a/doc/development/ux_guide/img/components-listwithhover.png b/doc/development/ux_guide/img/components-listwithhover.png new file mode 100644 index 00000000000..1484ecba6a0 Binary files /dev/null and b/doc/development/ux_guide/img/components-listwithhover.png differ diff --git a/doc/development/ux_guide/img/components-panels.png b/doc/development/ux_guide/img/components-panels.png new file mode 100644 index 00000000000..6e71d0ad9c9 Binary files /dev/null and b/doc/development/ux_guide/img/components-panels.png differ diff --git a/doc/development/ux_guide/img/components-referencehover.png b/doc/development/ux_guide/img/components-referencehover.png new file mode 100644 index 00000000000..e9fb27e2aa9 Binary files /dev/null and b/doc/development/ux_guide/img/components-referencehover.png differ diff --git a/doc/development/ux_guide/img/components-referenceissues.png b/doc/development/ux_guide/img/components-referenceissues.png new file mode 100644 index 00000000000..caf9477db38 Binary files /dev/null and b/doc/development/ux_guide/img/components-referenceissues.png differ diff --git a/doc/development/ux_guide/img/components-referencelabels.png b/doc/development/ux_guide/img/components-referencelabels.png new file mode 100644 index 00000000000..a122b45d1f1 Binary files /dev/null and b/doc/development/ux_guide/img/components-referencelabels.png differ diff --git a/doc/development/ux_guide/img/components-referencemilestone.png b/doc/development/ux_guide/img/components-referencemilestone.png new file mode 100644 index 00000000000..5aa9ecd1a78 Binary files /dev/null and b/doc/development/ux_guide/img/components-referencemilestone.png differ diff --git a/doc/development/ux_guide/img/components-referencemrs.png b/doc/development/ux_guide/img/components-referencemrs.png new file mode 100644 index 00000000000..6280243859a Binary files /dev/null and b/doc/development/ux_guide/img/components-referencemrs.png differ diff --git a/doc/development/ux_guide/img/components-referencepeople.png b/doc/development/ux_guide/img/components-referencepeople.png new file mode 100644 index 00000000000..99772a539cf Binary files /dev/null and b/doc/development/ux_guide/img/components-referencepeople.png differ diff --git a/doc/development/ux_guide/img/components-rowcontentblock.png b/doc/development/ux_guide/img/components-rowcontentblock.png new file mode 100644 index 00000000000..1c2d7096955 Binary files /dev/null and b/doc/development/ux_guide/img/components-rowcontentblock.png differ diff --git a/doc/development/ux_guide/img/components-simplelist.png b/doc/development/ux_guide/img/components-simplelist.png new file mode 100644 index 00000000000..892f507cfc2 Binary files /dev/null and b/doc/development/ux_guide/img/components-simplelist.png differ diff --git a/doc/development/ux_guide/img/components-table.png b/doc/development/ux_guide/img/components-table.png new file mode 100644 index 00000000000..7e964c885cf Binary files /dev/null and b/doc/development/ux_guide/img/components-table.png differ diff --git a/doc/development/ux_guide/img/components-verticalform.png b/doc/development/ux_guide/img/components-verticalform.png new file mode 100644 index 00000000000..38863ad3c1c Binary files /dev/null and b/doc/development/ux_guide/img/components-verticalform.png differ diff --git a/doc/development/ux_guide/img/features-contextualnav.png b/doc/development/ux_guide/img/features-contextualnav.png new file mode 100644 index 00000000000..df157f54c84 Binary files /dev/null and b/doc/development/ux_guide/img/features-contextualnav.png differ diff --git a/doc/development/ux_guide/img/features-emptystates.png b/doc/development/ux_guide/img/features-emptystates.png new file mode 100644 index 00000000000..3befc14588e Binary files /dev/null and b/doc/development/ux_guide/img/features-emptystates.png differ diff --git a/doc/development/ux_guide/img/features-filters.png b/doc/development/ux_guide/img/features-filters.png new file mode 100644 index 00000000000..281e55d590c Binary files /dev/null and b/doc/development/ux_guide/img/features-filters.png differ diff --git a/doc/development/ux_guide/img/features-globalnav.png b/doc/development/ux_guide/img/features-globalnav.png new file mode 100644 index 00000000000..3c0db2247ca Binary files /dev/null and b/doc/development/ux_guide/img/features-globalnav.png differ diff --git a/doc/development/ux_guide/img/icon-add.png b/doc/development/ux_guide/img/icon-add.png new file mode 100644 index 00000000000..0d4c1a7692a Binary files /dev/null and b/doc/development/ux_guide/img/icon-add.png differ diff --git a/doc/development/ux_guide/img/icon-close.png b/doc/development/ux_guide/img/icon-close.png new file mode 100644 index 00000000000..88d2b3b0c6d Binary files /dev/null and b/doc/development/ux_guide/img/icon-close.png differ diff --git a/doc/development/ux_guide/img/icon-edit.png b/doc/development/ux_guide/img/icon-edit.png new file mode 100644 index 00000000000..f73be7a10fb Binary files /dev/null and b/doc/development/ux_guide/img/icon-edit.png differ diff --git a/doc/development/ux_guide/img/icon-notification.png b/doc/development/ux_guide/img/icon-notification.png new file mode 100644 index 00000000000..4758632edd7 Binary files /dev/null and b/doc/development/ux_guide/img/icon-notification.png differ diff --git a/doc/development/ux_guide/img/icon-rss.png b/doc/development/ux_guide/img/icon-rss.png new file mode 100644 index 00000000000..c7ac9fb1349 Binary files /dev/null and b/doc/development/ux_guide/img/icon-rss.png differ diff --git a/doc/development/ux_guide/img/icon-subscribe.png b/doc/development/ux_guide/img/icon-subscribe.png new file mode 100644 index 00000000000..5cb277bfd5d Binary files /dev/null and b/doc/development/ux_guide/img/icon-subscribe.png differ diff --git a/doc/development/ux_guide/img/icon-trash.png b/doc/development/ux_guide/img/icon-trash.png new file mode 100644 index 00000000000..357289a6fff Binary files /dev/null and b/doc/development/ux_guide/img/icon-trash.png differ diff --git a/doc/development/ux_guide/img/monospacefont-sample.png b/doc/development/ux_guide/img/monospacefont-sample.png new file mode 100644 index 00000000000..1cd290b713c Binary files /dev/null and b/doc/development/ux_guide/img/monospacefont-sample.png differ diff --git a/doc/development/ux_guide/img/sourcesanspro-sample.png b/doc/development/ux_guide/img/sourcesanspro-sample.png new file mode 100644 index 00000000000..f7ecf0c7c66 Binary files /dev/null and b/doc/development/ux_guide/img/sourcesanspro-sample.png differ diff --git a/doc/development/ux_guide/img/surfaces-contentitemtitle.png b/doc/development/ux_guide/img/surfaces-contentitemtitle.png new file mode 100644 index 00000000000..2eb926c1c43 Binary files /dev/null and b/doc/development/ux_guide/img/surfaces-contentitemtitle.png differ diff --git a/doc/development/ux_guide/img/surfaces-header.png b/doc/development/ux_guide/img/surfaces-header.png new file mode 100644 index 00000000000..ab44d4de696 Binary files /dev/null and b/doc/development/ux_guide/img/surfaces-header.png differ diff --git a/doc/development/ux_guide/img/surfaces-systeminformationblock.png b/doc/development/ux_guide/img/surfaces-systeminformationblock.png new file mode 100644 index 00000000000..5d91e993e24 Binary files /dev/null and b/doc/development/ux_guide/img/surfaces-systeminformationblock.png differ diff --git a/doc/development/ux_guide/img/surfaces-ux.png b/doc/development/ux_guide/img/surfaces-ux.png new file mode 100644 index 00000000000..e692c51e8c0 Binary files /dev/null and b/doc/development/ux_guide/img/surfaces-ux.png differ diff --git a/doc/development/ux_guide/img/tooltip-placement.png b/doc/development/ux_guide/img/tooltip-placement.png new file mode 100644 index 00000000000..29a61c8400a Binary files /dev/null and b/doc/development/ux_guide/img/tooltip-placement.png differ diff --git a/doc/development/ux_guide/img/tooltip-usage.png b/doc/development/ux_guide/img/tooltip-usage.png new file mode 100644 index 00000000000..e8e4c6ded91 Binary files /dev/null and b/doc/development/ux_guide/img/tooltip-usage.png differ diff --git a/doc/development/ux_guide/index.md b/doc/development/ux_guide/index.md new file mode 100644 index 00000000000..1a61be4ed51 --- /dev/null +++ b/doc/development/ux_guide/index.md @@ -0,0 +1,53 @@ +# GitLab UX Guide + +The goal of this guide is to provide standards, principles and in-depth information to design beautiful and effective GitLab features. This will be a living document, and we welcome contributions, feedback and suggestions. + +## Design + +--- + +### [Principles](principles.md) +These guiding principles set a solid foundation for our design system, and should remain relatively stable over multiple releases. They should be referenced as new design patterns are created. + +--- + +### [Basics](basics.md) +The basic ingredients of our experience establish our personality and feel. This section includes details about typography, color, and motion. + +--- + +### [Components](components.md) +Components are the controls that make up the GitLab experience, including guidance around buttons, links, dropdowns, etc. + +--- + +### [Surfaces](surfaces.md) +The GitLab experience is broken apart into several surfaces. Each of these surfaces is designated for a specific scope or type of content. Examples include the header, global menu, side pane, etc. + +--- + +### [Features](features.md) +The previous building blocks are combined into complete features in the GitLab UX. Examples include our navigation, filters, search results, and empty states. + +--- + +## Research + +--- + +### [Users](users.md) +How we think about the variety of users of GitLab, from small to large teams, comparing opensource usage to enterprise, etc. + +--- + +## Other + +--- + +### [Tips for designers](tips.md) +Tips for exporting assets, and other guidance. + +--- + +### [Resources](resources.md) +Resources for GitLab UX diff --git a/doc/development/ux_guide/principles.md b/doc/development/ux_guide/principles.md new file mode 100644 index 00000000000..1a297cba2cc --- /dev/null +++ b/doc/development/ux_guide/principles.md @@ -0,0 +1,17 @@ +# Principles + +These are the guiding principles that we should strive for to establish a solid foundation for the GitLab experience. + +## Professional and productive +GitLab is a tool to support what people do, day in, day out. We need to respect the importance of their work, and avoid gimicky details. + +## Minimal and efficient +While work can get complicated, GitLab is about bringing a sharp focus, helping our customers know what matters now. + +## Immediately recognizable +When you look at any screen, you should know immediately that it is GitLab. Our personality is strong and consistent across product and marketing experiences. + +## Human and quirky +We need to build empathy with our users, understanding their state of mind, and connect with them at a human level. Quirkiness is part of our DNA, and we should embrace it in the right moments and contexts. + +> TODO: Ensure these principles align well with the goals of the Marketing team diff --git a/doc/development/ux_guide/resources.md b/doc/development/ux_guide/resources.md new file mode 100644 index 00000000000..2f760c94414 --- /dev/null +++ b/doc/development/ux_guide/resources.md @@ -0,0 +1,13 @@ +# Resources + +## GitLab UI development kit + +We created a page inside GitLab where you can check commonly used html and css elements. + +When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples +you can use during GitLab development. + +## Design repository + +All design files are stored in the [gitlab-design](https://gitlab.com/gitlab-org/gitlab-design) +repository and maintained by GitLab UX designers. \ No newline at end of file diff --git a/doc/development/ux_guide/surfaces.md b/doc/development/ux_guide/surfaces.md new file mode 100644 index 00000000000..881d6aa4cd6 --- /dev/null +++ b/doc/development/ux_guide/surfaces.md @@ -0,0 +1,47 @@ +# Surfaces + +## Contents +* [Header](#header) +* [Global menu](#global-menu) +* [Side pane](#side-pane) +* [Content area](#content-area) + +--- + +![Surfaces UX](img/surfaces-ux.png) + +## Global menu + +This menu is to navigate to pages that contain content global to GitLab. + +--- + +## Header + +The header contains 3 main elements: Project switching and searching, user account avatar and settings, and a contextual menu that changes based on the current page. + +![Surfaces Header](img/surfaces-header.png) + +--- + +## Side pane + +The side pane holds supporting information and meta data for the information in the content area. + +--- + +## Content area + +The main content of the page. The content area can include other surfaces. + +### Item title bar + +The item title bar contains the top level information to identify the item, such as the name, id and status. + +![Item title](img/surfaces-contentitemtitle.png) + +### Item system information + +The system information block contains relevant system controlled information. + +![Item system information](img/surfaces-systeminformationblock.png) diff --git a/doc/development/ux_guide/tips.md b/doc/development/ux_guide/tips.md new file mode 100644 index 00000000000..8348de4f8a2 --- /dev/null +++ b/doc/development/ux_guide/tips.md @@ -0,0 +1,44 @@ +# Tips + +## Contents +* [SVGs](#svgs) + +--- + +## SVGs + +When exporting SVGs, be sure to follow the following guidelines: + +1. Convert all strokes to outlines. +2. Use pathfinder tools to combine overlapping paths and create compound paths. +3. SVGs that are limited to one color should be exported without a fill color so the color can be set using CSS. +4. Ensure that exported SVGs have been run through an [SVG cleaner](https://github.com/RazrFalcon/SVGCleaner) to remove unused elements and attributes. + +You can open your svg in a text editor to ensure that it is clean. +Incorrect files will look like this: + +```xml + + + + Group + Created with Sketch. + + + + + + + + + + +``` + +Correct file will look like this: + +```xml + +``` + +> TODO: Checkout [https://github.com/svg/svgo](https://github.com/svg/svgo) diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md new file mode 100644 index 00000000000..717a902c424 --- /dev/null +++ b/doc/development/ux_guide/users.md @@ -0,0 +1,16 @@ +# Users + +> TODO: Create personas. Understand the similarities and differences across the below spectrums. + +## Users by organization + +- Enterprise +- Medium company +- Small company +- Open source communities + +## Users by role + +- Admin +- Manager +- Developer diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md index 5210ce0de9a..eb9bbb67e7d 100644 --- a/doc/integration/shibboleth.md +++ b/doc/integration/shibboleth.md @@ -10,7 +10,7 @@ To enable the Shibboleth OmniAuth provider you must: 1. Configure Apache shibboleth module. Installation and configuration of module it self is out of scope of this document. Check https://wiki.shibboleth.net/ for more info. -1. You can find Apache config in gitlab-recipes (https://github.com/gitlabhq/gitlab-recipes/blob/master/web-server/apache/gitlab-ssl.conf) +1. You can find Apache config in gitlab-recipes (https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache) Following changes are needed to enable shibboleth: diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index ef9160d6437..3a651ef318a 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -23,6 +23,10 @@ module Gitlab settings || fake_application_settings end + def sidekiq_throttling_enabled? + current_application_settings.sidekiq_throttling_enabled + end + def fake_application_settings OpenStruct.new( default_projects_limit: Settings.gitlab['default_projects_limit'], @@ -50,6 +54,7 @@ module Gitlab repository_checks_enabled: true, container_registry_token_expire_delay: 5, user_default_external: false, + sidekiq_throttling_enabled: false, ) end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index ce85e5e0123..5110bfbf898 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -126,7 +126,7 @@ module Gitlab repository.blob_at(commit.id, file_path) end - def cache_key + def file_identifier "#{file_path}-#{new_file}-#{deleted_file}-#{renamed_file}" end end diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index dc4d47c878b..fe7adb7bed6 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -39,7 +39,7 @@ module Gitlab # hashes that represent serialized diff lines. # def cache_highlight!(diff_file) - item_key = diff_file.cache_key + item_key = diff_file.file_identifier if highlight_cache[item_key] highlight_diff_file_from_cache!(diff_file, highlight_cache[item_key]) diff --git a/lib/gitlab/sidekiq_throttler.rb b/lib/gitlab/sidekiq_throttler.rb new file mode 100644 index 00000000000..d4d39a888e7 --- /dev/null +++ b/lib/gitlab/sidekiq_throttler.rb @@ -0,0 +1,23 @@ +module Gitlab + class SidekiqThrottler + class << self + def execute! + if Gitlab::CurrentSettings.sidekiq_throttling_enabled? + Gitlab::CurrentSettings.current_application_settings.sidekiq_throttling_queues.each do |queue| + Sidekiq::Queue[queue].limit = queue_limit + end + end + end + + private + + def queue_limit + @queue_limit ||= + begin + factor = Gitlab::CurrentSettings.current_application_settings.sidekiq_throttling_factor + (factor * Sidekiq.options[:concurrency]).ceil + end + end + end + end +end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 760a8967123..a03cd6fbf2d 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -46,7 +46,7 @@ describe 'Issue Boards new issue', feature: true, js: true do click_button 'Cancel' - expect(page).to have_selector('.board-new-issue-form', visible: false) + expect(page).not_to have_selector('.board-new-issue-form') end end diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 6c938bdead8..3934c936f20 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -182,6 +182,20 @@ feature 'Expand and collapse diffs', js: true, feature: true do end end end + + context 'expanding a diff when symlink was converted to a regular file' do + let(:branch) { 'symlink-expand-diff' } + + it 'shows the content of the regular file' do + expect(page).to have_content('This diff is collapsed') + expect(page).to have_no_content('No longer a symlink') + + find('.click-to-expand').click + wait_for_ajax + + expect(page).to have_content('No longer a symlink') + end + end end context 'visiting a commit without collapsed diffs' do diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 13bfe90302c..4b19886274e 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -80,7 +80,7 @@ feature 'Group', feature: true do visit path - expect(page).to have_css('.description > p > strong') + expect(page).to have_css('.group-home-desc > p > strong') end it 'passes through html-pipeline' do @@ -88,7 +88,7 @@ feature 'Group', feature: true do visit path - expect(page).to have_css('.description > p > img') + expect(page).to have_css('.group-home-desc > p > img') end it 'sanitizes unwanted tags' do @@ -96,7 +96,7 @@ feature 'Group', feature: true do visit path - expect(page).not_to have_css('.description h1') + expect(page).not_to have_css('.group-home-desc h1') end it 'permits `rel` attribute on links' do @@ -104,7 +104,7 @@ feature 'Group', feature: true do visit path - expect(page).to have_css('.description a[rel]') + expect(page).to have_css('.group-home-desc a[rel]') end end end diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index 5e6d8467217..d5e3d8e7eff 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -69,8 +69,6 @@ feature 'Diff notes resolve', feature: true, js: true do page.within '.diff-content .note' do expect(page).to have_selector('.line-resolve-btn.is-active') - - expect(find('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}") end page.within '.line-resolve-all-container' do diff --git a/spec/lib/gitlab/sidekiq_throttler_spec.rb b/spec/lib/gitlab/sidekiq_throttler_spec.rb new file mode 100644 index 00000000000..ff32e0e699d --- /dev/null +++ b/spec/lib/gitlab/sidekiq_throttler_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Gitlab::SidekiqThrottler do + before do + Sidekiq.options[:concurrency] = 35 + + stub_application_setting( + sidekiq_throttling_enabled: true, + sidekiq_throttling_factor: 0.1, + sidekiq_throttling_queues: %w[build project_cache] + ) + end + + describe '#execute!' do + it 'sets limits on the selected queues' do + Gitlab::SidekiqThrottler.execute! + + expect(Sidekiq::Queue['build'].limit).to eq 4 + expect(Sidekiq::Queue['project_cache'].limit).to eq 4 + end + + it 'does not set limits on other queues' do + Gitlab::SidekiqThrottler.execute! + + expect(Sidekiq::Queue['merge'].limit).to be_nil + end + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 778e665500d..103f7542286 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -23,6 +23,7 @@ module TestEnv 'binary-encoding' => '7b1cf43', 'gitattributes' => '5a62481', 'expand-collapse-diffs' => '4842455', + 'symlink-expand-diff' => '81e6355', 'expand-collapse-files' => '025db92', 'expand-collapse-lines' => '238e82d', 'video' => '8879059', diff --git a/vendor/assets/javascripts/vue.full.js b/vendor/assets/javascripts/vue.full.js index 07c4778449b..ea15bfac416 100644 --- a/vendor/assets/javascripts/vue.full.js +++ b/vendor/assets/javascripts/vue.full.js @@ -7512,4 +7512,4 @@ Vue$3.compile = compileToFunctions; return Vue$3; -}))); \ No newline at end of file +})));