diff options
Diffstat (limited to 'app')
336 files changed, 2220 insertions, 833 deletions
diff --git a/app/assets/javascripts/abuse_reports.js.es6 b/app/assets/javascripts/abuse_reports.js.es6 index 2fe46b9fd06..82e526ae0ef 100644 --- a/app/assets/javascripts/abuse_reports.js.es6 +++ b/app/assets/javascripts/abuse_reports.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { const MAX_MESSAGE_LENGTH = 500; const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index f4f8cf04184..59ac9b9cef5 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Activities = (function() { function Activities() { @@ -24,9 +25,7 @@ var filter = sender.attr("id").split("_")[0]; $('.event-filter .active').removeClass("active"); - $.cookie("event_filter", filter, { - path: gon.relative_url_root || '/' - }); + Cookies.set("event_filter", filter); sender.closest('li').toggleClass("active"); }; diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index f8460beb5d2..1ef340e4ca1 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Admin = (function() { function Admin() { diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 56ec1489f89..7ebe1599fca 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Api = { groupsPath: "/api/:version/groups.json", diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 17cbfd0e66f..7dd9adac736 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,5 +1,6 @@ +/* eslint-disable */ // This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// Add new JavaScript code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. @@ -11,13 +12,13 @@ /*= require jquery-ui/effect-highlight */ /*= require jquery-ui/sortable */ /*= require jquery_ujs */ -/*= require jquery.cookie */ /*= require jquery.endless-scroll */ /*= require jquery.highlight */ /*= require jquery.waitforimages */ /*= require jquery.atwho */ /*= require jquery.scrollTo */ /*= require jquery.turbolinks */ +/*= require js.cookie */ /*= require turbolinks */ /*= require autosave */ /*= require bootstrap/affix */ @@ -124,15 +125,11 @@ return str.replace(/<(?:.|\n)*?>/gm, ''); }; - window.unbindEvents = function() { - return $(document).off('scroll'); - }; - window.shiftWindow = function() { return scrollBy(0, -100); }; - document.addEventListener("page:fetch", unbindEvents); + document.addEventListener("page:fetch", gl.utils.cleanupBeforeFetch); window.addEventListener("hashchange", shiftWindow); @@ -149,6 +146,10 @@ $document = $(document); $window = $(window); $body = $('body'); + + // Set the default path for all cookies to GitLab's root directory + Cookies.defaults.path = gon.relative_url_root || '/'; + gl.utils.preventDisabledButtons(); bootstrapBreakpoint = bp.getBreakpointSize(); $(".nav-sidebar").niceScroll({ @@ -187,6 +188,7 @@ // Close select2 on escape }); // Initialize tooltips + $.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover'; $body.tooltip({ selector: '.has-tooltip, [data-toggle="tooltip"]', placement: function(_, el) { diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js index 7b546e79ee0..c7eff27f971 100644 --- a/app/assets/javascripts/aside.js +++ b/app/assets/javascripts/aside.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Aside = (function() { function Aside() { diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js index a9aec6e8ea4..ab09e4475e6 100644 --- a/app/assets/javascripts/autosave.js +++ b/app/assets/javascripts/autosave.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Autosave = (function() { function Autosave(field, key) { diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 44af1c135a0..8bdb0965f99 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.AwardsHandler = (function() { const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence @@ -91,7 +92,7 @@ css = { top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px" }; - if ((position != null) && position === 'right') { + if (position === 'right') { css.left = (($addBtn.offset().left - $menu.outerWidth()) + 20) + "px"; $menu.addClass('is-aligned-right'); } else { @@ -322,21 +323,18 @@ var frequentlyUsedEmojis; frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); frequentlyUsedEmojis.push(emoji); - return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { - path: gon.relative_url_root || '/', - expires: 365 - }); + Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }); }; AwardsHandler.prototype.getFrequentlyUsedEmojis = function() { var frequentlyUsedEmojis; - frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(','); + frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(','); return _.compact(_.uniq(frequentlyUsedEmojis)); }; AwardsHandler.prototype.renderFrequentlyUsedBlock = function() { var emoji, frequentlyUsedEmojis, i, len, ul; - if ($.cookie('frequently_used_emojis')) { + if (Cookies.get('frequently_used_emojis')) { frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>"); for (i = 0, len = frequentlyUsedEmojis.length; i < len; i++) { diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index dc8ae601961..074378b9e52 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require jquery.ba-resize */ /*= require autosize */ diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js index 1df681a4816..a64cefb62bd 100644 --- a/app/assets/javascripts/behaviors/details_behavior.js +++ b/app/assets/javascripts/behaviors/details_behavior.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { $(function() { $("body").on("click", ".js-details-target", function() { @@ -14,6 +15,11 @@ return $("body").on("click", ".js-details-expand", function(e) { $(this).next('.js-details-content').removeClass("hide"); $(this).hide(); + + var truncatedItem = $(this).siblings('.js-details-short'); + if (truncatedItem.length) { + truncatedItem.addClass("hide"); + } return e.preventDefault(); }); }); diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 54b7360ab41..7ff88ecdcaf 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Quick Submit behavior // // When a child field of a form with a `js-quick-submit` class receives a diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index 894034bdd54..4ac343f876c 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Requires Input behavior // // When called on a form with input fields with the `required` attribute, the diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index a6ce378d67a..05b213fe3fb 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function(w) { $(function() { // Toggle button. Show/hide content inside parent container. diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index d6ea4f84f57..37531aaec9b 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require blob/template_selector */ ((global) => { diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index 8cca1aa9232..33fb4f8185c 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.BlobFileDropzone = (function() { function BlobFileDropzone(form, method) { diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js index cd746b05cf6..344fe5dcd94 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selector.js +++ b/app/assets/javascripts/blob/blob_gitignore_selector.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require blob/template_selector */ diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js index 4e9500428b2..9e992f7913c 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selectors.js +++ b/app/assets/javascripts/blob/blob_gitignore_selectors.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.BlobGitignoreSelectors = (function() { function BlobGitignoreSelectors(opts) { diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js index 2701df3e6de..41a83a56146 100644 --- a/app/assets/javascripts/blob/blob_license_selector.js +++ b/app/assets/javascripts/blob/blob_license_selector.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require blob/template_selector */ diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 index 153ed457559..adeb8ba1318 100644 --- a/app/assets/javascripts/blob/blob_license_selectors.js.es6 +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { class BlobLicenseSelectors { constructor({ $dropdowns, editor }) { diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index 4e309e480b0..5434a19bcec 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { class TemplateSelector { constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) { @@ -68,14 +69,10 @@ // To be implemented on the extending class // e.g. // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - requestFileSuccess(file, { skipFocus, append } = {}) { + requestFileSuccess(file, { skipFocus } = {}) { const oldValue = this.editor.getValue(); let newValue = file.content; - if (append && oldValue.length && oldValue !== newValue) { - newValue = oldValue + '\n\n' + newValue; - } - this.editor.setValue(newValue, 1); if (!skipFocus) this.editor.focus(); @@ -99,4 +96,3 @@ global.TemplateSelector = TemplateSelector; })(window.gl || ( window.gl = {})); - diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js index 2afef43f3d6..b801c10f168 100644 --- a/app/assets/javascripts/blob_edit/blob_edit_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_edit_bundle.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require_tree . */ (function() { diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 8db4f6a3b28..60840560dd3 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index d4f8f4b9420..efb22d38513 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ //= require vue //= require vue-resource //= require Sortable @@ -5,7 +6,9 @@ //= require_tree ./stores //= require_tree ./services //= require_tree ./mixins +//= require_tree ./filters //= require ./components/board +//= require ./components/board_sidebar //= require ./components/new_list_dropdown //= require ./vue_resource_interceptor @@ -22,7 +25,8 @@ $(() => { gl.IssueBoardsApp = new Vue({ el: $boardApp, components: { - 'board': gl.issueBoards.Board + 'board': gl.issueBoards.Board, + 'board-sidebar': gl.issueBoards.BoardSidebar }, data: { state: Store.state, @@ -30,9 +34,15 @@ $(() => { endpoint: $boardApp.dataset.endpoint, boardId: $boardApp.dataset.boardId, disabled: $boardApp.dataset.disabled === 'true', - issueLinkBase: $boardApp.dataset.issueLinkBase + 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); }, diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index cacb36a897f..0e03d43872b 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ //= require ./board_blank_state //= require ./board_delete //= require ./board_list @@ -21,6 +22,7 @@ }, data () { return { + detailIssue: Store.detail, filters: Store.state.filters, showIssueForm: false }; @@ -32,6 +34,26 @@ this.list.getIssues(true); }, deep: true + }, + detailIssue: { + handler () { + if (!Object.keys(this.detailIssue.issue).length) return; + + const issue = this.list.findIssue(this.detailIssue.issue.id); + + if (issue) { + const boardsList = document.querySelectorAll('.boards-list')[0]; + const right = (this.$el.offsetLeft + this.$el.offsetWidth) - boardsList.offsetWidth; + const left = boardsList.scrollLeft - this.$el.offsetLeft; + + if (right - boardsList.scrollLeft > 0) { + boardsList.scrollLeft = right; + } else if (left > 0) { + boardsList.scrollLeft = this.$el.offsetLeft; + } + } + }, + deep: true } }, methods: { 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 ff90f2d6d75..885553690d3 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js.es6 +++ b/app/assets/javascripts/boards/components/board_blank_state.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (() => { const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6 index 4a7cfeaeab2..2f6c03e3538 100644 --- a/app/assets/javascripts/boards/components/board_card.js.es6 +++ b/app/assets/javascripts/boards/components/board_card.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (() => { const Store = gl.issueBoards.BoardsStore; @@ -12,6 +13,17 @@ disabled: Boolean, index: Number }, + data () { + return { + showDetail: false, + detailIssue: Store.detail + }; + }, + computed: { + issueDetailVisible () { + return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id; + } + }, methods: { filterByLabel (label, e) { let labelToggleText = label.title; @@ -37,6 +49,29 @@ $('.labels-filter .dropdown-toggle-text').text(labelToggleText); Store.updateFiltersUrl(); + }, + mouseDown () { + this.showDetail = true; + }, + mouseMove () { + if (this.showDetail) { + this.showDetail = false; + } + }, + showIssue (e) { + const targetTagName = e.target.tagName.toLowerCase(); + + if (targetTagName === 'a' || targetTagName === 'button') return; + + if (this.showDetail) { + this.showDetail = false; + + if (Store.detail.issue && Store.detail.issue.id === this.issue.id) { + Store.detail.issue = {}; + } else { + Store.detail.issue = this.issue; + } + } } } }); diff --git a/app/assets/javascripts/boards/components/board_delete.js.es6 b/app/assets/javascripts/boards/components/board_delete.js.es6 index 34653cd48ef..c45e1926c5c 100644 --- a/app/assets/javascripts/boards/components/board_delete.js.es6 +++ b/app/assets/javascripts/boards/components/board_delete.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (() => { window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 7022a29e818..34fc7694241 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ //= require ./board_card //= require ./board_new_issue 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 a4fad422eca..7fc0bfd56f3 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -1,4 +1,7 @@ +/* eslint-disable */ (() => { + const Store = gl.issueBoards.BoardsStore; + window.gl = window.gl || {}; gl.issueBoards.BoardNewIssue = Vue.extend({ @@ -27,13 +30,16 @@ const labels = this.list.label ? [this.list.label] : []; const issue = new ListIssue({ title: this.title, - labels + labels, + subscribed: true }); this.list.newIssue(issue) .then((data) => { // Need this because our jQuery very kindly disables buttons on ALL form submissions $(this.$els.submitButton).enable(); + + Store.detail.issue = issue; }) .catch(() => { // Need this because our jQuery very kindly disables buttons on ALL form submissions diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 new file mode 100644 index 00000000000..4928320d015 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -0,0 +1,53 @@ +/* eslint-disable */ +(() => { + const Store = gl.issueBoards.BoardsStore; + + window.gl = window.gl || {}; + window.gl.issueBoards = window.gl.issueBoards || {}; + + gl.issueBoards.BoardSidebar = Vue.extend({ + props: { + currentUser: Object + }, + data() { + return { + detail: Store.detail, + issue: {} + }; + }, + computed: { + showSidebar () { + return Object.keys(this.issue).length; + } + }, + watch: { + detail: { + handler () { + this.issue = this.detail.issue; + }, + deep: true + }, + issue () { + if (this.showSidebar) { + this.$nextTick(() => { + $('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0); + $('.right-sidebar').getNiceScroll().resize(); + }); + } + } + }, + methods: { + closeSidebar () { + this.detail.issue = {}; + } + }, + ready () { + new IssuableContext(this.currentUser); + new MilestoneSelect(); + new gl.DueDateSelectors(); + new LabelsSelect(); + new Sidebar(); + new Subscription('.subscription'); + } + }); +})(); 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 6ccd83e2d84..fe1a6dc7ea0 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ $(() => { const Store = gl.issueBoards.BoardsStore; @@ -32,6 +33,7 @@ $(() => { }, filterable: true, selectable: true, + multiSelect: true, clicked (label, $el, e) { e.preventDefault(); diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 new file mode 100644 index 00000000000..9eceac4eddd --- /dev/null +++ b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 @@ -0,0 +1,5 @@ +/* eslint-disable */ +Vue.filter('due-date', (value) => { + const date = new Date(value); + return $.datepicker.formatDate('M d, yy', date); +}); 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 f629d45c587..db9a5a8e40a 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((w) => { window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; @@ -21,8 +22,8 @@ fallbackClass: 'is-dragging', fallbackOnBody: true, ghostClass: 'is-ghost', - filter: '.has-tooltip, .btn', - delay: gl.issueBoards.touchEnabled ? 100 : 0, + filter: '.board-delete, .btn', + delay: gl.issueBoards.touchEnabled ? 100 : 50, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSpeed: 20, onStart: gl.issueBoards.onStart, diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index eb082103de9..21d735e8231 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -1,14 +1,21 @@ +/* eslint-disable */ class ListIssue { constructor (obj) { this.id = obj.iid; this.title = obj.title; this.confidential = obj.confidential; + this.dueDate = obj.due_date; + this.subscribed = obj.subscribed; this.labels = []; if (obj.assignee) { this.assignee = new ListUser(obj.assignee); } + if (obj.milestone) { + this.milestone = new ListMilestone(obj.milestone); + } + obj.labels.forEach((label) => { this.labels.push(new ListLabel(label)); }); @@ -41,4 +48,21 @@ class ListIssue { getLists () { return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) ); } + + update (url) { + const data = { + issue: { + milestone_id: this.milestone ? this.milestone.id : null, + due_date: this.dueDate, + assignee_id: this.assignee ? this.assignee.id : null, + label_ids: this.labels.map( (label) => label.id ) + } + }; + + if (!data.issue.label_ids.length) { + data.issue.label_ids = ['']; + } + + return Vue.http.patch(url, data); + } } diff --git a/app/assets/javascripts/boards/models/label.js.es6 b/app/assets/javascripts/boards/models/label.js.es6 index 583829552cd..0910fe9a854 100644 --- a/app/assets/javascripts/boards/models/label.js.es6 +++ b/app/assets/javascripts/boards/models/label.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ class ListLabel { constructor (obj) { this.id = obj.id; diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 5d0a561cdba..b331a26fed5 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ class List { constructor (obj) { this.id = obj.id; diff --git a/app/assets/javascripts/boards/models/milestone.js.es6 b/app/assets/javascripts/boards/models/milestone.js.es6 new file mode 100644 index 00000000000..a48969e19c9 --- /dev/null +++ b/app/assets/javascripts/boards/models/milestone.js.es6 @@ -0,0 +1,7 @@ +/* eslint-disable */ +class ListMilestone { + constructor (obj) { + this.id = obj.id; + this.title = obj.title; + } +} diff --git a/app/assets/javascripts/boards/models/user.js.es6 b/app/assets/javascripts/boards/models/user.js.es6 index 904b3a68507..583a973fc46 100644 --- a/app/assets/javascripts/boards/models/user.js.es6 +++ b/app/assets/javascripts/boards/models/user.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ class ListUser { constructor (user) { this.id = user.id; diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index b9c91cbf31e..f59a2ed7937 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -1,7 +1,6 @@ +/* eslint-disable */ class BoardService { constructor (root, boardId) { - Vue.http.options.root = root; - this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, { generate: { method: 'POST', diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index bd07ee0c161..175e034afed 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (() => { window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; @@ -5,6 +6,9 @@ gl.issueBoards.BoardsStore = { disabled: false, state: {}, + detail: { + issue: {} + }, moving: { issue: {}, list: {} @@ -58,12 +62,13 @@ removeBlankState () { this.removeList('blank'); - $.cookie('issue_board_welcome_hidden', 'true', { - expires: 365 * 10 + Cookies.set('issue_board_welcome_hidden', 'true', { + expires: 365 * 10, + path: '' }); }, welcomeIsHidden () { - return $.cookie('issue_board_welcome_hidden') === 'true'; + return Cookies.get('issue_board_welcome_hidden') === 'true'; }, removeList (id, type = 'blank') { const list = this.findList('id', id, type); diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/boards/test_utils/simulate_drag.js index 75f8b730195..039ca491cf5 100644 --- a/app/assets/javascripts/boards/test_utils/simulate_drag.js +++ b/app/assets/javascripts/boards/test_utils/simulate_drag.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function () { 'use strict'; diff --git a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 index b5ff3a81ed5..80f137ca12e 100644 --- a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 +++ b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ Vue.http.interceptors.push((request, next) => { Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js index 5fef9725178..5d4d23e26c6 100644 --- a/app/assets/javascripts/breakpoints.js +++ b/app/assets/javascripts/breakpoints.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Breakpoints = (function() { var BreakpointInstance, instance; diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js index fceeff36728..576f4c76c1e 100644 --- a/app/assets/javascripts/broadcast_message.js +++ b/app/assets/javascripts/broadcast_message.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { $(function() { var previewPath; diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index f4c387a1a05..12e653f4122 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js index f345ba0abe6..49f84581650 100644 --- a/app/assets/javascripts/build_artifacts.js +++ b/app/assets/javascripts/build_artifacts.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.BuildArtifacts = (function() { function BuildArtifacts() { diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js.es6 index 8d3e29794a1..0ecd20bc11e 100644 --- a/app/assets/javascripts/build_variables.js.es6 +++ b/app/assets/javascripts/build_variables.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ $(function(){ $('.reveal-variables').off('click').on('click',function(){ $('.js-build').toggle().niceScroll(); diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js index 23cf5b519f4..fac5b4f17da 100644 --- a/app/assets/javascripts/commit.js +++ b/app/assets/javascripts/commit.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Commit = (function() { function Commit() { diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js index be24ee56aad..16d63729d31 100644 --- a/app/assets/javascripts/commit/file.js +++ b/app/assets/javascripts/commit/file.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.CommitFile = (function() { function CommitFile(file) { diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index e893491b19b..ffddce1297b 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ImageFile = (function() { var prepareFrames; diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 9132089adcd..c765d233831 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.CommitsList = (function() { function CommitsList() {} diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js index 342ac0e8e69..b3f769d4129 100644 --- a/app/assets/javascripts/compare.js +++ b/app/assets/javascripts/compare.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Compare = (function() { function Compare(opts) { diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6 index 9a2082d97e0..bd980f87e72 100644 --- a/app/assets/javascripts/compare_autocomplete.js.es6 +++ b/app/assets/javascripts/compare_autocomplete.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.CompareAutocomplete = (function() { function CompareAutocomplete() { diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index 708ab08ffac..230a1b95c52 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ConfirmDangerModal = (function() { function ConfirmDangerModal(form, text) { diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index e23bda2fa4e..7808d7fe313 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require clipboard */ diff --git a/app/assets/javascripts/create_label.js.es6 b/app/assets/javascripts/create_label.js.es6 index c5f8c29242d..f20580b1279 100644 --- a/app/assets/javascripts/create_label.js.es6 +++ b/app/assets/javascripts/create_label.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (function (w) { class CreateLabelDropdown { constructor ($el, namespacePath, projectPath) { diff --git a/app/assets/javascripts/cycle_analytics.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 index 20791bab942..331f0209888 100644 --- a/app/assets/javascripts/cycle_analytics.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ //= require vue ((global) => { @@ -6,7 +7,7 @@ const store = gl.cycleAnalyticsStore = { isLoading: true, hasError: false, - isHelpDismissed: $.cookie(COOKIE_NAME), + isHelpDismissed: Cookies.get(COOKIE_NAME), analytics: {} }; @@ -75,9 +76,7 @@ dismissLanding() { store.isHelpDismissed = true; - $.cookie(COOKIE_NAME, true, { - path: gon.relative_url_root || '/' - }); + Cookies.set(COOKIE_NAME, true); } initDropdown() { diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 8086c10ad6b..4ddafff428f 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Diff = (function() { var UNFOLD_COUNT; 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 48bc7d77805..29a12a2395b 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,3 +1,4 @@ +/* eslint-disable */ ((w) => { w.CommentAndResolveBtn = Vue.extend({ props: { diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 index ad80d1118df..983e554b9c1 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (() => { JumpToDiscussion = Vue.extend({ mixins: [DiscussionMixins], 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 cdedfd1af15..bcc052c7c8c 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((w) => { w.ResolveBtn = Vue.extend({ props: { 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 9e383b14a3e..24a99e23132 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((w) => { w.ResolveCount = Vue.extend({ mixins: [DiscussionMixins], 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 0a617034502..060034f049b 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,3 +1,4 @@ +/* eslint-disable */ ((w) => { w.ResolveDiscussionBtn = Vue.extend({ props: { 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 22d9cf6c857..6149bfd052a 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ //= require vue //= require vue-resource //= require_directory ./models diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 b/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 index a05f885201d..7a929017f36 100644 --- a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((w) => { w.DiscussionMixins = { computed: { diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6 index 488714e4870..439f55520ef 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ class DiscussionModel { constructor (discussionId) { this.id = discussionId; diff --git a/app/assets/javascripts/diff_notes/models/note.js.es6 b/app/assets/javascripts/diff_notes/models/note.js.es6 index f2d2d389c38..d0541b02632 100644 --- a/app/assets/javascripts/diff_notes/models/note.js.es6 +++ b/app/assets/javascripts/diff_notes/models/note.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ class NoteModel { constructor (discussionId, noteId, canResolve, resolved, resolved_by) { this.discussionId = discussionId; diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js.es6 index 2a55f739b31..86953ce7ffb 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js.es6 +++ b/app/assets/javascripts/diff_notes/services/resolve.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((w) => { class ResolveServiceClass { constructor() { diff --git a/app/assets/javascripts/diff_notes/stores/comments.js.es6 b/app/assets/javascripts/diff_notes/stores/comments.js.es6 index 69522e1dac5..f42ca406bb1 100644 --- a/app/assets/javascripts/diff_notes/stores/comments.js.es6 +++ b/app/assets/javascripts/diff_notes/stores/comments.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((w) => { w.CommentsStore = { state: {}, diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index a1fe57562fa..ff8b8f6d0ae 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var Dispatcher; diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 4a6fea929c7..1a0aa9757ba 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require preview_markdown */ diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6 index 41925fcc8e3..fd7f961aab9 100644 --- a/app/assets/javascripts/due_date_select.js.es6 +++ b/app/assets/javascripts/due_date_select.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (function(global) { class DueDateSelect { constructor({ $dropdown, $loading } = {}) { @@ -41,7 +42,12 @@ defaultDate: $("input[name='" + this.fieldName + "']").val(), altField: "input[name='" + this.fieldName + "']", onSelect: () => { - return this.saveDueDate(true); + if (this.$dropdown.hasClass('js-issue-boards-due-date')) { + gl.issueBoards.BoardsStore.detail.issue.dueDate = $(`input[name='${this.fieldName}']`).val(); + this.updateIssueBoardIssue(); + } else { + return this.saveDueDate(true); + } } }); } @@ -49,8 +55,14 @@ initRemoveDueDate() { this.$block.on('click', '.js-remove-due-date', (e) => { e.preventDefault(); - $("input[name='" + this.fieldName + "']").val(''); - return this.saveDueDate(false); + + if (this.$dropdown.hasClass('js-issue-boards-due-date')) { + gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; + this.updateIssueBoardIssue(); + } else { + $("input[name='" + this.fieldName + "']").val(''); + return this.saveDueDate(false); + } }); } @@ -83,6 +95,18 @@ this.datePayload = datePayload; } + updateIssueBoardIssue () { + this.$loading.fadeIn(); + this.$dropdown.trigger('loading.gl.dropdown'); + this.$selectbox.hide(); + this.$value.css('display', ''); + + gl.issueBoards.BoardsStore.detail.issue.update(this.$dropdown.attr('data-issue-update')) + .then(() => { + this.$loading.fadeOut(); + }); + } + submitSelectedDate(isDropdown) { return $.ajax({ type: 'PUT', diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js index 24f9e00097c..4c9e219aa43 100644 --- a/app/assets/javascripts/extensions/array.js +++ b/app/assets/javascripts/extensions/array.js @@ -1,3 +1,4 @@ +/* eslint-disable */ Array.prototype.first = function() { return this[0]; } diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6 new file mode 100644 index 00000000000..c74fc9ad074 --- /dev/null +++ b/app/assets/javascripts/extensions/element.js.es6 @@ -0,0 +1,7 @@ +/* eslint-disable */ +Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatches; + +Element.prototype.closest = function closest(selector, selectedElement = this) { + if (!selectedElement) return; + return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement); +}; diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js index 4978e24949c..623a80b7053 100644 --- a/app/assets/javascripts/extensions/jquery.js +++ b/app/assets/javascripts/extensions/jquery.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Disable an element and add the 'disabled' Bootstrap class (function() { $.fn.extend({ diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index 3fb3b1a8b51..732136f1f2c 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index c8a02d6fa15..46e272c3311 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Flash = (function() { var hideFlash; diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 845313b6b38..31df51ac03a 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ // Creates the variables for setting up GFM auto-completion (function() { if (window.GitLab == null) { diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 53762f2965c..98e43c4d088 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, @@ -208,7 +209,7 @@ FILTER_INPUT = '.dropdown-input .dropdown-input-field'; function GitLabDropdown(el1, options) { - var ref, ref1, ref2, ref3, searchFields, selector, self; + var searchFields, selector, self; this.el = el1; this.options = options; this.updateLabel = bind(this.updateLabel, this); @@ -219,7 +220,11 @@ selector = $(this.el).data("target"); this.dropdown = selector != null ? $(selector) : $(this.el).parent(); // Set Defaults - ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true; + this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT); + this.highlight = !!this.options.highlight + this.filterInputBlur = this.options.filterInputBlur != null + ? this.options.filterInputBlur + : true; // If no input is passed create a default one self = this; // If selector was passed @@ -234,6 +239,7 @@ this.fullData = this.options.data; currentIndex = -1; this.parseData(this.options.data); + this.focusTextInput(); } else { this.remote = new GitLabDropdownRemote(this.options.data, { dataType: this.options.dataType, @@ -242,6 +248,7 @@ return function(data) { _this.fullData = data; _this.parseData(_this.fullData); + _this.focusTextInput(); if (_this.options.filterable && _this.filter && _this.filter.input) { return _this.filter.input.trigger('input'); } @@ -418,7 +425,9 @@ var $target; if (this.options.multiSelect) { $target = $(e.target); - if ($target && !$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) { + if ($target && !$target.hasClass('dropdown-menu-close') && + !$target.hasClass('dropdown-menu-close-icon') && + !$target.data('is-link')) { e.stopPropagation(); return false; } else { @@ -445,9 +454,8 @@ contentHtml = $('.dropdown-content', this.dropdown).html(); if (this.remote && contentHtml === "") { this.remote.execute(); - } - if (this.options.filterable) { - this.filterInput.focus(); + } else { + this.focusTextInput(); } if (this.options.showMenuAbove) { @@ -549,6 +557,8 @@ value = this.options.id ? this.options.id(data) : data.id; fieldName = this.options.fieldName; + if (value) { value = value.toString().replace(/'/g, '\\\'') }; + field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); if (field.length) { selected = true; @@ -620,8 +630,21 @@ selectedObject = this.renderedData[selectedIndex]; } } + + if (this.options.vue) { + if (el.hasClass(ACTIVE_CLASS)) { + el.removeClass(ACTIVE_CLASS); + } else { + el.addClass(ACTIVE_CLASS); + } + + return selectedObject; + } + field = []; - value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; + value = this.options.id + ? this.options.id(selectedObject, el) + : selectedObject.id; if (isInput) { field = $(this.el); } else if(value) { @@ -669,6 +692,10 @@ return selectedObject; }; + GitLabDropdown.prototype.focusTextInput = function() { + if (this.options.filterable) { this.filterInput.focus() } + } + GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { var $input; // Create hidden input for form diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 8e8f9f29ab3..be6c3ec274f 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { /* * This class overrides the browser's validation error bubbles, displaying custom diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index 2703adc0705..742807d93ad 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.GLForm = (function() { function GLForm(form) { diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js index 4886da9f21f..e103748d499 100644 --- a/app/assets/javascripts/graphs/graphs_bundle.js +++ b/app/assets/javascripts/graphs/graphs_bundle.js @@ -1,5 +1,6 @@ +/* eslint-disable */ // This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// Add new JavaScript code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js index f041980bc19..b796a9abb49 100644 --- a/app/assets/javascripts/graphs/stat_graph.js +++ b/app/assets/javascripts/graphs/stat_graph.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.StatGraph = (function() { function StatGraph() {} diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js index 927d241b357..818bff0c413 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require d3 */ diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index 7d9d4d7c679..dea26a3f1e1 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require d3 */ @@ -29,8 +30,7 @@ ContributorsGraph.set_y_domain = function(data) { return ContributorsGraph.prototype.y_domain = [ 0, d3.max(data, function(d) { - var ref, ref1; - return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions; + return d.commits = d.commits || d.additions || d.deletions; }) ]; }; @@ -44,8 +44,7 @@ ContributorsGraph.init_y_domain = function(data) { return ContributorsGraph.prototype.y_domain = [ 0, d3.max(data, function(d) { - var ref, ref1; - return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions; + return d.commits = d.commits || d.additions || d.deletions; }) ]; }; @@ -147,9 +146,8 @@ return this.area = d3.svg.area().x(function(d) { return x(d.date); }).y0(this.height).y1(function(d) { - var ref, ref1, xa; - xa = d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions; - return y(xa); + d.commits = d.commits || d.additions || d.deletions; + return y(d.commits); }).interpolate("basis"); }; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index 0d240bed8b6..362a77e868f 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { window.ContributorsStatGraphUtil = { parse_log: function(log) { diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js index c28ce86d7af..774477dc7a9 100644 --- a/app/assets/javascripts/group_avatar.js +++ b/app/assets/javascripts/group_avatar.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.GroupAvatar = (function() { function GroupAvatar() { diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index 5f06186504b..b275620c799 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var slice = [].slice; diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js new file mode 100644 index 00000000000..81fcaf06430 --- /dev/null +++ b/app/assets/javascripts/header.js @@ -0,0 +1,10 @@ +/* eslint-disable */ +(function() { + + $(document).on('todo:toggle', function(e, count) { + var $todoPendingCount = $('.todos-pending-count'); + $todoPendingCount.text(gl.text.addDelimiter(count)); + $todoPendingCount.toggleClass('hidden', count === 0); + }); + +})(); diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 4aced1e618f..c53f7c88aa2 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ImporterStatus = (function() { function ImporterStatus(jobs_url, import_url) { diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6 index 57f7e4ef230..8fc498be27d 100644 --- a/app/assets/javascripts/issuable.js.es6 +++ b/app/assets/javascripts/issuable.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var issuable_created; @@ -15,16 +16,61 @@ return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>'); }, initSearch: function() { + const $searchInput = $('#issuable_search'); + + Issuable.initSearchState($searchInput); + // `immediate` param set to false debounces on the `trailing` edge, lets user finish typing - const debouncedExecSearch = _.debounce(Issuable.executeSearch, 500, false); + const debouncedExecSearch = _.debounce(Issuable.executeSearch, 1000, false); - $('#issuable_search').off('keyup').on('keyup', debouncedExecSearch); + $searchInput.off('keyup').on('keyup', debouncedExecSearch); // ensures existing filters are preserved when manually submitted - $('#issue_search_form').on('submit', (e) => { + $('#issuable_search_form').on('submit', (e) => { e.preventDefault(); debouncedExecSearch(e); }); + + }, + initSearchState: function($searchInput) { + const currentSearchVal = $searchInput.val(); + + Issuable.searchState = { + elem: $searchInput, + current: currentSearchVal + }; + + Issuable.maybeFocusOnSearch(); + }, + accessSearchPristine: function(set) { + // store reference to previous value to prevent search on non-mutating keyup + const state = Issuable.searchState; + const currentSearchVal = state.elem.val(); + + if (set) { + state.current = currentSearchVal; + } else { + return state.current === currentSearchVal; + } + }, + maybeFocusOnSearch: function() { + const currentSearchVal = Issuable.searchState.current; + if (currentSearchVal && currentSearchVal !== '') { + const queryLength = currentSearchVal.length; + const $searchInput = Issuable.searchState.elem; + + /* The following ensures that the cursor is initially placed at + * the end of search input when focus is applied. It accounts + * for differences in browser implementations of `setSelectionRange` + * and cursor placement for elements in focus. + */ + $searchInput.focus(); + if ($searchInput.setSelectionRange) { + $searchInput.setSelectionRange(queryLength, queryLength); + } else { + $searchInput.val(currentSearchVal); + } + } }, executeSearch: function(e) { const $search = $('#issuable_search'); @@ -32,6 +78,11 @@ const $searchValue = $search.val(); const $filtersForm = $('.js-filter-form'); const $input = $(`input[name='${$searchName}']`, $filtersForm); + const isPristine = Issuable.accessSearchPristine(); + + if (isPristine) { + return; + } if (!$input.length) { $filtersForm.append(`<input type='hidden' name='${$searchName}' value='${_.escape($searchValue)}'/>`); diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index 8147e83ffe8..fae49ee6144 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.IssuableContext = (function() { function IssuableContext(currentUser) { diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index b7f92ae9883..849b45756ee 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -44,8 +45,8 @@ }; IssuableForm.prototype.handleSubmit = function() { - var ref, ref1; - if (((ref = parseInt((ref1 = this.issueMoveField) != null ? ref1.val() : void 0)) != null ? ref : 0) > 0) { + var fieldId = (this.issueMoveField != null) ? this.issueMoveField.val() : null; + if ((parseInt(fieldId) || 0) > 0) { if (!confirm(this.issueMoveConfirmMsg)) { return false; } diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 261bf6137c2..67ace697936 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require flash */ /*= require jquery.waitforimages */ @@ -94,7 +95,11 @@ return $.ajax({ type: 'PATCH', url: $('form.js-issuable-update').attr('action'), - data: patchData + data: patchData, + success: function(issue) { + document.querySelector('#task_status').innerText = issue.task_status; + document.querySelector('#task_status_short').innerText = issue.task_status_short; + } }); // TODO (rspeicher): Make the issue description inline-editable like a note so // that we can re-use its form here diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js index 076e3972944..d7262e5eb74 100644 --- a/app/assets/javascripts/issue_status_select.js +++ b/app/assets/javascripts/issue_status_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.IssueStatusSelect = (function() { function IssueStatusSelect() { diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6 index 0808f538f01..9697fb33566 100644 --- a/app/assets/javascripts/issues_bulk_assignment.js.es6 +++ b/app/assets/javascripts/issues_bulk_assignment.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { class IssuableBulkActions { diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6 index bc68e53504f..175623e7448 100644 --- a/app/assets/javascripts/label_manager.js.es6 +++ b/app/assets/javascripts/label_manager.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { class LabelManager { diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js index cb16e2ba814..3033e8ca5c2 100644 --- a/app/assets/javascripts/labels.js +++ b/app/assets/javascripts/labels.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index b4f6e70f694..c334e3e0c02 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.LabelsSelect = (function() { function LabelsSelect() { @@ -22,7 +23,7 @@ abilityName = $dropdown.data('ability-name'); $selectbox = $dropdown.closest('.selectbox'); $block = $selectbox.closest('.block'); - $form = $dropdown.closest('form'); + $form = $dropdown.closest('form, .js-issuable-update'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $value = $block.find('.value'); @@ -317,6 +318,7 @@ } }, multiSelect: $dropdown.hasClass('js-multiselect'), + vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(label, $el, e) { var isIssueIndex, isMRIndex, page; _this.enableBulkLabelDropdown(); @@ -334,7 +336,7 @@ page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = page === 'projects:merge_requests:index'; - if ($('html').hasClass('issue-boards-page')) { + if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; } @@ -362,6 +364,30 @@ else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); } + else if ($dropdown.hasClass('js-issue-board-sidebar')) { + if ($el.hasClass('is-active')) { + gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({ + id: label.id, + title: label.title, + color: label.color[0], + textColor: '#fff' + })); + } + else { + var labels = gl.issueBoards.BoardsStore.detail.issue.labels; + labels = labels.filter(function (selectedLabel) { + return selectedLabel.id !== label.id; + }); + gl.issueBoards.BoardsStore.detail.issue.labels = labels; + } + + $loading.fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + .then(function () { + $loading.fadeOut(); + }); + } else { if ($dropdown.hasClass('js-multiselect')) { diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 8e2fc0d1479..6b4edf02f4d 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var hideEndFade; diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js index 4cdf99cae72..b1718e89d3d 100644 --- a/app/assets/javascripts/lib/ace.js +++ b/app/assets/javascripts/lib/ace.js @@ -1,2 +1,3 @@ +/* eslint-disable */ /*= require ace-rails-ap */ /*= require ace/ext-searchbox */ diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js index d9b07c10a49..e1dfdae97de 100644 --- a/app/assets/javascripts/lib/chart.js +++ b/app/assets/javascripts/lib/chart.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require Chart */ diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js index a88e640f298..155e30cc462 100644 --- a/app/assets/javascripts/lib/cropper.js +++ b/app/assets/javascripts/lib/cropper.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require cropper */ diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js index ee1baf54803..0c9c2787077 100644 --- a/app/assets/javascripts/lib/d3.js +++ b/app/assets/javascripts/lib/d3.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require d3 */ diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js index 6df427bc2b1..cc445db274b 100644 --- a/app/assets/javascripts/lib/raphael.js +++ b/app/assets/javascripts/lib/raphael.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require raphael */ /*= require g.raphael */ diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js index d36efdabc93..a68edab2aad 100644 --- a/app/assets/javascripts/lib/utils/animate.js +++ b/app/assets/javascripts/lib/utils/animate.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { (function(w) { if (w.gl == null) { diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index b170e26eebf..21efe2d76dd 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { (function(w) { var base; @@ -43,6 +44,14 @@ parser.href = url; return parser; }; + + gl.utils.cleanupBeforeFetch = function() { + // Unbind scroll events + $(document).off('scroll'); + // Close any open tooltips + $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); + }; + return jQuery.timefor = function(time, suffix, expiredLabel) { var suffixFromNow, timefor; if (!time) { diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 8fdf4646cd8..59e526ed623 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { (function(w) { var base; diff --git a/app/assets/javascripts/lib/utils/jquery.timeago.js b/app/assets/javascripts/lib/utils/jquery.timeago.js index cc17aa7d3d1..de76cdd2ea7 100644 --- a/app/assets/javascripts/lib/utils/jquery.timeago.js +++ b/app/assets/javascripts/lib/utils/jquery.timeago.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /** * Timeago is a jQuery plugin that makes it easy to support automatically * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js index 5b338b00d76..dafc006d2e5 100644 --- a/app/assets/javascripts/lib/utils/notify.js +++ b/app/assets/javascripts/lib/utils/notify.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { (function(w) { var notificationGranted, notifyMe, notifyPermissions; diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index d761a844be9..98f9815ff05 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { (function(w) { var base; @@ -7,6 +8,9 @@ if ((base = w.gl).text == null) { base.text = {}; } + gl.text.addDelimiter = function(text) { + return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text; + } gl.text.randomString = function() { return Math.random().toString(36).substring(7); }; diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js index dc30babd645..4fd1e3fc1d3 100644 --- a/app/assets/javascripts/lib/utils/type_utility.js +++ b/app/assets/javascripts/lib/utils/type_utility.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { (function(w) { var base; diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index b8d52becb3f..44a66a915e3 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { (function(w) { var base; diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index 93daea1dce7..ea5a60bb78e 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // LineHighlighter // // Handles single- and multi-line selection and highlight for blob views. diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js index 7d8eef1b495..d4f86534f0c 100644 --- a/app/assets/javascripts/logo.js +++ b/app/assets/javascripts/logo.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { Turbolinks.enableProgressBar(); diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js index e1532fd9ec4..0bd90c57396 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { // Add datepickers to all `js-access-expiration-date` elements. If those elements are // children of an element with the `clearable-input` class, and have a sibling diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 2bdd0f7a637..371abd09e78 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((w) => { w.gl = w.gl || {}; 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 5012bdfe997..6da3942ea52 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 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 index b4be1c8988d..23c4618af70 100644 --- a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; 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 index 8b0a8ab2073..797850262cc 100644 --- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; 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 eb4cc6a9dac..1b3e9901f1e 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 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 index da2fb8b1323..8a7519b0786 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index 5c5c65f29d4..f94e51e783c 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -1,7 +1,8 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; - const diffViewType = $.cookie('diff_view'); + const diffViewType = Cookies.get('diff_view'); const HEAD_HEADER_TEXT = 'HEAD//our changes'; const ORIGIN_HEADER_TEXT = 'origin//their changes'; const HEAD_BUTTON_TITLE = 'Use ours'; @@ -180,9 +181,7 @@ this.state.diffView = viewType; this.state.isParallel = viewType === VIEW_TYPES.PARALLEL; - $.cookie('diff_view', viewType, { - path: gon.relative_url_root || '/' - }); + Cookies.set('diff_view', viewType); }, getHeadHeaderLine(id) { 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 7fd3749b3e2..222a5dcfc2e 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ //= require vue //= require ./merge_conflict_store //= require ./merge_conflict_service diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 index 114a2c5b305..c8de586aa21 100644 --- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 index b846a90ab2a..88c3a20ce13 100644 --- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 02ff5a382e2..d3bd1e846c1 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require jquery.waitforimages */ /*= require task_list */ @@ -96,7 +97,11 @@ return $.ajax({ type: 'PATCH', url: $('form.js-issuable-update').attr('action'), - data: patchData + data: patchData, + success: function(mergeRequest) { + document.querySelector('#task_status').innerText = mergeRequest.task_status; + document.querySelector('#task_status_short').innerText = mergeRequest.task_status_short; + } }); // TODO (rspeicher): Make the merge request description inline-editable like a // note so that we can re-use its form here diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 3dde979185b..860ee5df57e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -1,9 +1,10 @@ +/* eslint-disable */ // MergeRequestTabs // // Handles persisting and restoring the current tab selection and lazily-loading // content on the MergeRequests#show page. // -/*= require jquery.cookie */ +/*= require js.cookie */ // // ### Example Markup @@ -237,8 +238,11 @@ _this.expandViewContainer(); } _this.diffsLoaded = true; - _this.scrollToElement("#diffs"); - _this.highlighSelectedLine(); + var anchoredDiff = gl.utils.getLocationHash(); + if (anchoredDiff) _this.openAnchoredDiff(anchoredDiff, function() { + _this.scrollToElement("#diffs"); + _this.highlighSelectedLine(); + }); _this.filesCommentButton = $('.files .diff-file').filesCommentButton(); return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) { e.preventDefault(); @@ -251,6 +255,17 @@ }); }; + MergeRequestTabs.prototype.openAnchoredDiff = function(anchoredDiff, cb) { + var diffTitle = $('#file-path-' + anchoredDiff); + var diffFile = diffTitle.closest('.diff-file'); + var nothingHereBlock = $('.nothing-here-block:visible', diffFile); + if (nothingHereBlock.length) { + diffFile.singleFileDiff(true, cb); + } else { + cb(); + } + }; + MergeRequestTabs.prototype.highlighSelectedLine = function() { var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight; $('.hll').removeClass('hll'); @@ -368,7 +383,7 @@ MergeRequestTabs.prototype.expandView = function() { var $gutterIcon; - if ($.cookie('collapsed_gutter') === 'true') { + if (Cookies.get('collapsed_gutter') === 'true') { return; } $gutterIcon = $('.js-sidebar-toggle i:visible'); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 3ff6851d59b..3a2fe454b68 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js index 1fed38661a2..7ad86d8c084 100644 --- a/app/assets/javascripts/merged_buttons.js +++ b/app/assets/javascripts/merged_buttons.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index bc1a99057d9..9299c96e8ea 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Milestone = (function() { Milestone.updateIssue = function(li, issue_url, data) { diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index cee42633c79..c909b53dc21 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.MilestoneSelect = (function() { function MilestoneSelect(currentProject) { @@ -101,6 +102,7 @@ // display:block overrides the hide-collapse rule return $value.css('display', ''); }, + vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(selected, $el, e) { var data, isIssueIndex, isMRIndex, page; page = $('body').data('page'); @@ -110,7 +112,7 @@ e.preventDefault(); return; } - if ($('html').hasClass('issue-boards-page')) { + if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); @@ -123,6 +125,24 @@ return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); + } else if ($dropdown.hasClass('js-issue-board-sidebar')) { + if (selected.id !== -1) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({ + id: selected.id, + title: selected.name + })); + } else { + Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone'); + } + + $dropdown.trigger('loading.gl.dropdown'); + $loading.fadeIn(); + + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + .then(function () { + $dropdown.trigger('loaded.gl.dropdown'); + $loading.fadeOut(); + }); } else { selected = $selectbox.find('input[type="hidden"]').val(); data = {}; diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 10f4fd106d8..d1168227b77 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index 91132af273a..74dbeb94741 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js index 7baebcd100a..8898e7ace43 100644 --- a/app/assets/javascripts/network/network.js +++ b/app/assets/javascripts/network/network.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Network = (function() { function Network(opts) { diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js index 67c3e645364..42d6799c82f 100644 --- a/app/assets/javascripts/network/network_bundle.js +++ b/app/assets/javascripts/network/network_bundle.js @@ -1,5 +1,6 @@ +/* eslint-disable */ // This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// Add new JavaScript code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js index 20aa2fced27..0e643b0ff14 100644 --- a/app/assets/javascripts/new_branch_form.js +++ b/app/assets/javascripts/new_branch_form.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index 21bf8867f7b..acb529023fa 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 866a04d3e21..4976eef2896 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require autosave */ /*= require autosize */ diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index a41e9d3fabe..ef3f2c6ae73 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.NotificationsDropdown = (function() { function NotificationsDropdown() { diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js index 6b2ef17ef6b..6fbec8efe9b 100644 --- a/app/assets/javascripts/notifications_form.js +++ b/app/assets/javascripts/notifications_form.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js index b81ed50cb48..2e4dc62273e 100644 --- a/app/assets/javascripts/pager.js +++ b/app/assets/javascripts/pager.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Pager = { init: function(limit, preload, disable, callback) { diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index a7624de6089..e6fada5c84c 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -1,37 +1,41 @@ +/* eslint-disable */ ((global) => { class Pipelines { constructor() { - $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + this.initGraphToggle(); this.addMarginToBuildColumns(); } - toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - + initGraphToggle() { + this.pipelineGraph = document.querySelector('.pipeline-graph'); + this.toggleButton = document.querySelector('.toggle-pipeline-btn'); + this.toggleButtonText = this.toggleButton.querySelector('.toggle-btn-text'); + this.toggleButton.addEventListener('click', this.toggleGraph.bind(this)); + } - graphCollapsed ? $btnText.text('Hide') : $btnText.text('Expand') + toggleGraph() { + const graphCollapsed = this.pipelineGraph.classList.contains('graph-collapsed'); + this.toggleButton.classList.toggle('graph-collapsed'); + this.pipelineGraph.classList.toggle('graph-collapsed'); + this.toggleButtonText.textContent = graphCollapsed ? 'Hide' : 'Expand'; } addMarginToBuildColumns() { - const $secondChildBuildNode = $('.build:nth-child(2)'); - if ($secondChildBuildNode.length) { - const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); - const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); - const $previousColumn = $multiBuildColumn.prev('.stage-column'); - $multiBuildColumn.addClass('left-margin'); - $firstChildBuildNode.addClass('left-connector'); - $previousColumn.each(function() { - $this = $(this); - if ($('.build', $this).length === 1) $this.addClass('no-margin'); - }); + const secondChildBuildNodes = this.pipelineGraph.querySelectorAll('.build:nth-child(2)'); + for (buildNodeIndex in secondChildBuildNodes) { + const buildNode = secondChildBuildNodes[buildNodeIndex]; + const firstChildBuildNode = buildNode.previousElementSibling; + if (!firstChildBuildNode || !firstChildBuildNode.matches('.build')) continue; + const multiBuildColumn = buildNode.closest('.stage-column'); + const previousColumn = multiBuildColumn.previousElementSibling; + if (!previousColumn || !previousColumn.matches('.stage-column')) continue; + multiBuildColumn.classList.add('left-margin'); + firstChildBuildNode.classList.add('left-connector'); + const columnBuilds = previousColumn.querySelectorAll('.build'); + if (columnBuilds.length === 1) previousColumn.classList.add('no-margin'); } - $('.pipeline-graph').removeClass('hidden'); + this.pipelineGraph.classList.remove('hidden'); } } diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js index 5200487814f..f2a45a18bed 100644 --- a/app/assets/javascripts/preview_markdown.js +++ b/app/assets/javascripts/preview_markdown.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // MarkdownPreview // // Handles toggling the "Write" and "Preview" tab clicks, rendering the preview, diff --git a/app/assets/javascripts/profile/gl_crop.js.es6 b/app/assets/javascripts/profile/gl_crop.js.es6 index a1b0126e857..6da6c1d0295 100644 --- a/app/assets/javascripts/profile/gl_crop.js.es6 +++ b/app/assets/javascripts/profile/gl_crop.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { // Matches everything but the file name diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index b2307be73ad..73858388261 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { class Profile { diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js index d6e4d9f7ad8..22bee0f6187 100644 --- a/app/assets/javascripts/profile/profile_bundle.js +++ b/app/assets/javascripts/profile/profile_bundle.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require_tree . */ diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index a6c015299a0..2d0c6b16699 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Project = (function() { function Project() { @@ -23,16 +24,12 @@ return $(this).parents('form').submit(); }); $('.hide-no-ssh-message').on('click', function(e) { - $.cookie('hide_no_ssh_message', 'false', { - path: gon.relative_url_root || '/' - }); + Cookies.set('hide_no_ssh_message', 'false'); $(this).parents('.no-ssh-key-message').remove(); return e.preventDefault(); }); $('.hide-no-password-message').on('click', function(e) { - $.cookie('hide_no_password_message', 'false', { - path: gon.relative_url_root || '/' - }); + Cookies.set('hide_no_password_message', 'false'); $(this).parents('.no-password-message').remove(); return e.preventDefault(); }); @@ -82,7 +79,7 @@ if (ref.header != null) { return $('<li />').addClass('dropdown-header').text(ref.header); } else { - link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); + link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', ref); return $('<li />').append(link); } }, diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js index 277e71523d5..61877c6616d 100644 --- a/app/assets/javascripts/project_avatar.js +++ b/app/assets/javascripts/project_avatar.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ProjectAvatar = (function() { function ProjectAvatar() { diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index b8347367717..ddac5ed83e1 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js index d2261c51f35..fd95f8f2c19 100644 --- a/app/assets/javascripts/project_fork.js +++ b/app/assets/javascripts/project_fork.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ProjectFork = (function() { function ProjectFork() { diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js index c61b0cf2fde..f1c4a9fe542 100644 --- a/app/assets/javascripts/project_import.js +++ b/app/assets/javascripts/project_import.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ProjectImport = (function() { function ProjectImport() { diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 478e82aa14d..40575caa57f 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 4239ed2f889..b74b4ae68ff 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ProjectSelect = (function() { function ProjectSelect() { diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js index c8cfc9a9ba8..21650f5f67a 100644 --- a/app/assets/javascripts/project_show.js +++ b/app/assets/javascripts/project_show.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ProjectShow = (function() { function ProjectShow() {} diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js index 04fb49552e8..3458cd89ae2 100644 --- a/app/assets/javascripts/projects_list.js +++ b/app/assets/javascripts/projects_list.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.ProjectsList = { init: function() { diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 index 7aeb5f92514..2d60947a666 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (global => { global.gl = global.gl || {}; diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 index 46beca469b9..c45c9d8ff22 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (global => { global.gl = global.gl || {}; diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 index 983322cbecc..e3f226e9a2a 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ class ProtectedBranchDropdown { constructor(options) { this.onSelect = options.onSelect; diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 index 15a6dca2875..ac3142ffb07 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (global => { global.gl = global.gl || {}; diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 index 9ff0fd12c76..705378a364d 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (global => { global.gl = global.gl || {}; diff --git a/app/assets/javascripts/protected_branches/protected_branches_bundle.js b/app/assets/javascripts/protected_branches/protected_branches_bundle.js index 15b3affd469..17e34163831 100644 --- a/app/assets/javascripts/protected_branches/protected_branches_bundle.js +++ b/app/assets/javascripts/protected_branches/protected_branches_bundle.js @@ -1 +1,2 @@ +/* eslint-disable */ /*= require_tree . */ diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index e3d5f413c77..df38937858f 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -5,15 +6,24 @@ function Sidebar(currentUser) { this.toggleTodo = bind(this.toggleTodo, this); this.sidebar = $('aside'); + this.removeListeners(); this.addEventListeners(); } + Sidebar.prototype.removeListeners = function () { + this.sidebar.off('click', '.sidebar-collapsed-icon'); + $('.dropdown').off('hidden.gl.dropdown'); + $('.dropdown').off('loading.gl.dropdown'); + $('.dropdown').off('loaded.gl.dropdown'); + $(document).off('click', '.js-sidebar-toggle'); + } + Sidebar.prototype.addEventListeners = function() { this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden); $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); - $(document).off('click', '.js-sidebar-toggle').on('click', '.js-sidebar-toggle', function(e, triggered) { + $(document).on('click', '.js-sidebar-toggle', function(e, triggered) { var $allGutterToggleIcons, $this, $thisIcon; e.preventDefault(); $this = $(this); @@ -29,9 +39,7 @@ $('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); } if (!triggered) { - return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), { - path: gon.relative_url_root || '/' - }); + return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed')); } }); return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo); @@ -74,16 +82,11 @@ }; Sidebar.prototype.todoUpdateDone = function(data, $btn, $btnText, $todoLoading) { - var $todoPendingCount; - $todoPendingCount = $('.todos-pending-count'); - $todoPendingCount.text(data.count); + $(document).trigger('todo:toggle', data.count); + $btn.enable(); $todoLoading.addClass('hidden'); - if (data.count === 0) { - $todoPendingCount.addClass('hidden'); - } else { - $todoPendingCount.removeClass('hidden'); - } + if (data.delete_path != null) { $btn.attr('aria-label', $btn.data('mark-text')).attr('data-delete-path', data.delete_path); return $btnText.text($btn.data('mark-text')); diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index 8074a94f33e..6c2389f202f 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Search = (function() { function Search() { diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index b4c6226dc68..5fa94556501 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { const KEYCODE = { diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index 3aa8536d40a..8d8ab6dda5e 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js index b931eab638f..704a8bd3a57 100644 --- a/app/assets/javascripts/shortcuts_blob.js +++ b/app/assets/javascripts/shortcuts_blob.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require shortcuts */ diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js index f7492a2aa5c..befe4eccdba 100644 --- a/app/assets/javascripts/shortcuts_dashboard_navigation.js +++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require shortcuts */ diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js index 92ce31969e3..90ed4267661 100644 --- a/app/assets/javascripts/shortcuts_find_file.js +++ b/app/assets/javascripts/shortcuts_find_file.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require shortcuts_navigation */ diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index 235bf4f95ec..25ec7dbc067 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require mousetrap */ /*= require shortcuts_navigation */ diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index b04159420d1..19c6b7d30ab 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require shortcuts */ diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js index fb2b39e757e..002e979a2c6 100644 --- a/app/assets/javascripts/shortcuts_network.js +++ b/app/assets/javascripts/shortcuts_network.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require shortcuts_navigation */ diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 index 755fac8107b..a23ca449c4b 100644 --- a/app/assets/javascripts/sidebar.js.es6 +++ b/app/assets/javascripts/sidebar.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { let singleton; @@ -28,7 +29,7 @@ } init() { - this.isPinned = $.cookie(pinnedStateCookie) === 'true'; + this.isPinned = Cookies.get(pinnedStateCookie) === 'true'; this.isExpanded = ( window.innerWidth >= sidebarBreakpoint && $(pageSelector).hasClass(expandedPageClass) @@ -37,7 +38,8 @@ .on('click', sidebarToggleSelector, () => this.toggleSidebar()) .on('click', pinnedToggleSelector, () => this.togglePinnedState()) .on('click', 'html, body', (e) => this.handleClickEvent(e)) - .on('page:change', () => this.renderState()); + .on('page:change', () => this.renderState()) + .on('todo:toggle', (e, count) => this.updateTodoCount(count)); this.renderState(); } @@ -52,6 +54,10 @@ } } + updateTodoCount(count) { + $('.js-todos-count').text(gl.text.addDelimiter(count)); + } + toggleSidebar() { this.isExpanded = !this.isExpanded; this.renderState(); @@ -62,10 +68,7 @@ if (!this.isPinned) { this.isExpanded = false; } - $.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', { - path: gon.relative_url_root || '/', - expires: 3650 - }); + Cookies.set(pinnedStateCookie, this.isPinned ? 'true' : 'false', { expires: 3650 }); this.renderState(); } diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index ee6af123268..8e54ca4f0dc 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -12,7 +13,7 @@ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; - function SingleFileDiff(file) { + function SingleFileDiff(file, forceLoad, cb) { this.file = file; this.toggleDiff = bind(this.toggleDiff, this); this.content = $('.diff-content', this.file); @@ -31,9 +32,12 @@ this.$toggleIcon.addClass('fa-caret-down'); } $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); + if (forceLoad) { + this.toggleDiff(null, cb); + } } - SingleFileDiff.prototype.toggleDiff = function(e) { + SingleFileDiff.prototype.toggleDiff = function(e, cb) { var $target = $(e.target); if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; this.isOpen = !this.isOpen; @@ -53,11 +57,11 @@ } } else { this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); - return this.getContentHTML(); + return this.getContentHTML(cb); } }; - SingleFileDiff.prototype.getContentHTML = function() { + SingleFileDiff.prototype.getContentHTML = function(cb) { this.collapsedContent.hide(); this.loadingContent.show(); $.get(this.diffForPath, (function(_this) { @@ -75,6 +79,8 @@ if (typeof DiffNotesApp !== 'undefined') { DiffNotesApp.compileComponents(); } + + if (cb) cb(); }; })(this)); }; @@ -83,10 +89,10 @@ })(); - $.fn.singleFileDiff = function() { + $.fn.singleFileDiff = function(forceLoad, cb) { return this.each(function() { - if (!$.data(this, 'singleFileDiff')) { - return $.data(this, 'singleFileDiff', new SingleFileDiff(this)); + if (!$.data(this, 'singleFileDiff') || forceLoad) { + return $.data(this, 'singleFileDiff', new SingleFileDiff(this, forceLoad, cb)); } }); }; diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js index 855e97eb301..083dc23c796 100644 --- a/app/assets/javascripts/snippet/snippet_bundle.js +++ b/app/assets/javascripts/snippet/snippet_bundle.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require_tree . */ (function() { diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js.es6 index 6f0996c0d2a..c3afc3f2246 100644 --- a/app/assets/javascripts/snippets_list.js.es6 +++ b/app/assets/javascripts/snippets_list.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ (global => { global.gl = global.gl || {}; diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index 10509313c12..cfd1e2204d5 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.Star = (function() { function Star() { @@ -10,11 +11,9 @@ $this.parent().find('.star-count').text(data.star_count); if (isStarred) { $starSpan.removeClass('starred').text('Star'); - gl.utils.updateTooltipTitle($this, 'Star project'); $starIcon.removeClass('fa-star').addClass('fa-star-o'); } else { $starSpan.addClass('starred').text('Unstar'); - gl.utils.updateTooltipTitle($this, 'Unstar project'); $starIcon.removeClass('fa-star-o').addClass('fa-star'); } }; diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js index 5e3c5983d75..f9915593657 100644 --- a/app/assets/javascripts/subscription.js +++ b/app/assets/javascripts/subscription.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -5,10 +6,10 @@ function Subscription(container) { this.toggleSubscription = bind(this.toggleSubscription, this); var $container; - $container = $(container); - this.url = $container.attr('data-url'); - this.subscribe_button = $container.find('.js-subscribe-button'); - this.subscription_status = $container.find('.subscription-status'); + this.$container = $(container); + this.url = this.$container.attr('data-url'); + this.subscribe_button = this.$container.find('.js-subscribe-button'); + this.subscription_status = this.$container.find('.subscription-status'); this.subscribe_button.unbind('click').click(this.toggleSubscription); } @@ -18,17 +19,27 @@ action = btn.find('span').text(); current_status = this.subscription_status.attr('data-status'); btn.addClass('disabled'); + + if ($('html').hasClass('issue-boards-page')) { + this.url = this.$container.attr('data-url'); + } + return $.post(this.url, (function(_this) { return function() { var status; btn.removeClass('disabled'); - status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed'; - _this.subscription_status.attr('data-status', status); - action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe'; - btn.find('span').text(action); - _this.subscription_status.find('>div').toggleClass('hidden'); - if (btn.attr('data-original-title')) { - return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle'); + + if ($('html').hasClass('issue-boards-page')) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed); + } else { + status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed'; + _this.subscription_status.attr('data-status', status); + action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe'; + btn.find('span').text(action); + _this.subscription_status.find('>div').toggleClass('hidden'); + if (btn.attr('data-original-title')) { + return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle'); + } } }; })(this)); diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js index d6c219603d1..2ca65cb762d 100644 --- a/app/assets/javascripts/subscription_select.js +++ b/app/assets/javascripts/subscription_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.SubscriptionSelect = (function() { function SubscriptionSelect() { diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js index 2ae7bf5fc15..77ad4f30b7a 100644 --- a/app/assets/javascripts/syntax_highlight.js +++ b/app/assets/javascripts/syntax_highlight.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Syntax Highlighter // // Applies a syntax highlighting color scheme CSS class to any element with the diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index bd4e3c3d00d..93a3d67ee9f 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require ../blob/template_selector */ ((global) => { @@ -32,24 +33,22 @@ this.currentTemplate = currentTemplate; if (err) return; // Error handled by global AJAX error handler this.stopLoadingSpinner(); - this.setInputValueToTemplateContent(true); + this.setInputValueToTemplateContent(); }); return; } - setInputValueToTemplateContent(append) { + setInputValueToTemplateContent() { // `this.requestFileSuccess` sets the value of the description input field - // to the content of the template selected. If `append` is true, the - // template content will be appended to the previous value of the field, - // separated by a blank line if the previous value is non-empty. + // to the content of the template selected. if (this.titleInput.val() === '') { // If the title has not yet been set, focus the title input and // skip focusing the description input by setting `true` as the // `skipFocus` option to `requestFileSuccess`. - this.requestFileSuccess(this.currentTemplate, {skipFocus: true, append}); + this.requestFileSuccess(this.currentTemplate, {skipFocus: true}); this.titleInput.focus(); } else { - this.requestFileSuccess(this.currentTemplate, {skipFocus: false, append}); + this.requestFileSuccess(this.currentTemplate, {skipFocus: false}); } return; } diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 index 4e8247b89e1..0a3890e85fe 100644 --- a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { class IssuableTemplateSelectors { constructor({ $dropdowns, editor } = {}) { diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index 055228c5df8..213e80825b7 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { class Todos { @@ -97,7 +98,8 @@ } updateBadges(data) { - $('.todos-pending .badge, .todos-pending-count').text(data.count); + $(document).trigger('todo:toggle', data.count); + $('.todos-pending .badge').text(data.count); return $('.todos-done .badge').text(data.done_count); } diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 9b7be17c4fe..70aff4b9a2f 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.TreeView = (function() { function TreeView() { diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index ce2930c7fc7..35f2b1e2b25 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Authenticate U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> authenticated -> POST to server diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index bc48c67c4f2..aff605169e4 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 926912fa988..22fbf9f3a91 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Register U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> registered -> POST to server diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js index 907e640161a..2eab2d5ae23 100644 --- a/app/assets/javascripts/u2f/util.js +++ b/app/assets/javascripts/u2f/util.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { this.U2FUtil = (function() { function U2FUtil() {} diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index 0f97924d94e..5e869e99fdb 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { global.User = class { constructor({ action }) { @@ -23,10 +24,7 @@ hideProjectLimitMessage() { $('.hide-project-limit-message').on('click', e => { e.preventDefault(); - const path = gon.relative_url_root || '/'; - $.cookie('hide_project_limit_message', 'false', { - path: path - }); + Cookies.set('hide_project_limit_message', 'false'); $(this).parents('.project-limit-message').remove(); }); } diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index dfdfa1e7f75..2b310da319c 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ /* UserTabs diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index bf4b2e320cd..c4dde575c6e 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable */ ((global) => { const debounceTimeoutDuration = 1000; const invalidInputClass = 'gl-field-error-outline'; diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 3bd4c3c066f..0ec878e7e60 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js index d6e4d9f7ad8..22bee0f6187 100644 --- a/app/assets/javascripts/users/users_bundle.js +++ b/app/assets/javascripts/users/users_bundle.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require_tree . */ diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 3020b7cc239..3847278e80a 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,3 +1,4 @@ +/* eslint-disable */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; @@ -9,7 +10,11 @@ this.usersPath = "/autocomplete/users.json"; this.userPath = "/autocomplete/users/:id.json"; if (currentUser != null) { - this.currentUser = JSON.parse(currentUser); + if (typeof currentUser === 'object') { + this.currentUser = currentUser; + } else { + this.currentUser = JSON.parse(currentUser); + } } $('.js-user-search').each((function(_this) { return function(i, dropdown) { @@ -32,9 +37,30 @@ $value = $block.find('.value'); $collapsedSidebar = $block.find('.sidebar-collapsed-user'); $loading = $block.find('.block-loading').fadeOut(); + + var updateIssueBoardsIssue = function () { + $loading.fadeIn(); + gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + .then(function () { + $loading.fadeOut(); + }); + }; + $block.on('click', '.js-assign-yourself', function(e) { e.preventDefault(); - return assignTo(_this.currentUser.id); + + if ($dropdown.hasClass('js-issue-board-sidebar')) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ + id: _this.currentUser.id, + username: _this.currentUser.username, + name: _this.currentUser.name, + avatar_url: _this.currentUser.avatar_url + })); + + updateIssueBoardsIssue(); + } else { + return assignTo(_this.currentUser.id); + } }); assignTo = function(selected) { var data; @@ -150,6 +176,7 @@ // display:block overrides the hide-collapse rule return $value.css('display', ''); }, + vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(user, $el, e) { var isIssueIndex, isMRIndex, page, selected; page = $('body').data('page'); @@ -160,7 +187,7 @@ selectedId = user.id; return; } - if ($('html').hasClass('issue-boards-page')) { + if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { selectedId = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.updateFiltersUrl(); @@ -170,6 +197,19 @@ return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); + } else if ($dropdown.hasClass('js-issue-board-sidebar')) { + if (user.id) { + Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ + id: user.id, + username: user.username, + name: user.name, + avatar_url: user.avatar_url + })); + } else { + Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'assignee'); + } + + updateIssueBoardsIssue(); } else { selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val(); return assignTo(selected); diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js index 35401231fbf..ad9b842db3c 100644 --- a/app/assets/javascripts/wikis.js +++ b/app/assets/javascripts/wikis.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /*= require latinise */ diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index 777b32b41c9..fa124e7052d 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Zen Mode (full screen) textarea // /*= provides zen_mode:enter */ diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 98e301d3799..ce117c3fba5 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -1,11 +1,36 @@ -.avatar { +@mixin avatar-size($size, $margin-right) { + width: $size; + height: $size; + margin-right: $margin-right; +} + +.avatar-container { float: left; - margin-right: 12px; + margin-right: 15px; + border-radius: $avatar_radius; + border: 1px solid rgba(0, 0, 0, .1); + &.s16 { @include avatar-size(16px, 6px); } + &.s20 { @include avatar-size(20px, 7px); } + &.s24 { @include avatar-size(24px, 8px); } + &.s26 { @include avatar-size(26px, 8px); } + &.s32 { @include avatar-size(32px, 10px); } + &.s36 { @include avatar-size(36px, 10px); } + &.s40 { @include avatar-size(40px, 10px); } + &.s46 { @include avatar-size(46px, 15px); } + &.s48 { @include avatar-size(48px, 10px); } + &.s60 { @include avatar-size(60px, 12px); } + &.s70 { @include avatar-size(70px, 14px); } + &.s90 { @include avatar-size(90px, 15px); } + &.s110 { @include avatar-size(110px, 15px); } + &.s140 { @include avatar-size(140px, 15px); } + &.s160 { @include avatar-size(160px, 20px); } +} + +.avatar { + @extend .avatar-container; width: 40px; height: 40px; padding: 0; - border-radius: $avatar_radius; - border: 1px solid rgba(0, 0, 0, .1); &.avatar-inline { float: none; @@ -20,22 +45,6 @@ border-radius: 0; border: none; } - - &.s16 { width: 16px; height: 16px; margin-right: 6px; } - &.s20 { width: 20px; height: 20px; margin-right: 7px; } - &.s24 { width: 24px; height: 24px; margin-right: 8px; } - &.s26 { width: 26px; height: 26px; margin-right: 8px; } - &.s32 { width: 32px; height: 32px; margin-right: 10px; } - &.s36 { width: 36px; height: 36px; margin-right: 10px; } - &.s40 { width: 40px; height: 40px; margin-right: 10px; } - &.s46 { width: 46px; height: 46px; margin-right: 15px; } - &.s48 { width: 48px; height: 48px; margin-right: 10px; } - &.s60 { width: 60px; height: 60px; margin-right: 12px; } - &.s70 { width: 70px; height: 70px; margin-right: 14px; } - &.s90 { width: 90px; height: 90px; margin-right: 15px; } - &.s110 { width: 110px; height: 110px; margin-right: 15px; } - &.s140 { width: 140px; height: 140px; margin-right: 20px; } - &.s160 { width: 160px; height: 160px; margin-right: 20px; } } .identicon { @@ -54,3 +63,17 @@ &.s140 { font-size: 72px; line-height: 138px; } &.s160 { font-size: 96px; line-height: 158px; } } + +.image-container { + @extend .avatar-container; + overflow: hidden; + display: flex; + + .avatar { + border-radius: 0; + border: none; + height: auto; + margin: 0; + align-self: center; + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index c0e9c8bf829..ed21ad83a1c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -216,7 +216,7 @@ svg, .fa { &:not(:last-child) { - margin-right: 3px; + margin-right: 5px; } } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 1de246600fd..baa38ab60c8 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -15,6 +15,7 @@ @media (max-width: $screen-xs-max) { width: 100%; + min-width: 240px; } } @@ -485,7 +486,7 @@ font-size: 20px; text-indent: 0; - &:before { + &::before { display: block; position: relative; top: -2px; @@ -517,7 +518,7 @@ background-color: transparent; border: 0; - .ui-icon:before { + .ui-icon::before { color: $md-link-color; } } @@ -526,7 +527,7 @@ .ui-datepicker-prev { left: 0; - .ui-icon:before { + .ui-icon::before { content: '\f104'; text-align: left; } @@ -535,7 +536,7 @@ .ui-datepicker-next { right: 0; - .ui-icon:before { + .ui-icon::before { content: '\f105'; text-align: right; } diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 3f877d86a26..91ab1503439 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -21,57 +21,66 @@ background: $color-darker; } - .nav-sidebar li { - a { - color: $color-light; - - &:hover, - &:focus, - &:active { - background: $color-dark; - } + .sidebar-header, + .sidebar-action-buttons { + color: $color-light; + background-color: lighten($color-darker, 5%); + } - i { + .nav-sidebar { + li { + a { color: $color-light; - } - - path, - polygon { - fill: $color-light; - } - .count { - color: $color-light; - background: $color-dark; + &:hover, + &:focus, + &:active { + background: $color-dark; + } + + i { + color: $color-light; + } + + path, + polygon { + fill: $color-light; + } + + .count { + color: $color-light; + background: $color-dark; + } + + svg { + position: relative; + top: 3px; + } } - svg { - position: relative; - top: 3px; + &.separate-item { + border-top: 1px solid $color; } - } - - &.separate-item { - border-top: 1px solid $color; - } - &.active a { - color: $white-light; - background: $color-dark; + &.active a { + color: $white-light; + background: $color-dark; - &.no-highlight { - border: none; - } + &.no-highlight { + border: none; + } - i { - color: $white-light; - } + i { + color: $white-light; + } - path, - polygon { - fill: $white-light; + path, + polygon { + fill: $white-light; + } } } + } } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 142076f65b2..4993ca7572a 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -49,12 +49,16 @@ header { font-size: 18px; padding: 0; margin: ($header-height - 28) / 2 0; - margin-left: 10px; + margin-left: 8px; height: 28px; min-width: 28px; line-height: 28px; text-align: center; + &.header-user-dropdown-toggle { + margin-left: 14px; + } + &:hover, &:focus, &:active { @@ -227,7 +231,7 @@ header { float: none !important; .visible-xs, - .visable-sm { + .visible-sm { display: table-cell !important; } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 48e34a0066e..bc0610cc417 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -14,7 +14,7 @@ border-bottom: 1px solid #eee; border-bottom: 1px solid rgba(0, 0, 0, 0.05); - &:after { + &::after { content: " "; display: table; clear: both; @@ -38,7 +38,7 @@ &.smoke { background-color: $background-color; } - &:hover { + &:not(.ui-sort-disabled):hover { background: $row-hover; } @@ -142,10 +142,6 @@ ul.content-list { } } - .avatar { - margin-right: 15px; - } - .controls { float: right; diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss index b6f21fd8c91..cb2c351c368 100644 --- a/app/assets/stylesheets/framework/pagination.scss +++ b/app/assets/stylesheets/framework/pagination.scss @@ -7,8 +7,70 @@ .pagination { padding: 0; } + + .gap, + .gap:hover { + background-color: $gray-light; + padding: $gl-vert-padding; + cursor: default; + } } .panel > .gl-pagination { margin: 0; } + +/** + * Extra-small screen pagination. + */ +@media (max-width: 320px) { + .gl-pagination { + .first, + .last { + display: none; + } + + .page { + display: none; + + &.active { + display: inline; + } + } + } +} + +/** + * Small screen pagination + */ +@media (max-width: $screen-xs) { + .gl-pagination { + .pagination li a { + padding: 6px 10px; + } + + .page { + display: none; + + &.active { + display: inline; + } + } + } +} + +/** + * Medium screen pagination + */ +@media (min-width: $screen-xs) and (max-width: $screen-md-max) { + .gl-pagination { + .page { + display: none; + + &.active, + &.sibling { + display: inline; + } + } + } +} diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 1d8e64a0e4b..d74c14ee2a4 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -59,6 +59,11 @@ padding: 0 !important; } + .sidebar-header { + padding: 11px 22px 12px; + font-size: 20px; + } + li { &.separate-item { padding-top: 10px; @@ -164,6 +169,18 @@ padding-left: $sidebar_width; } } + + .merge-request-tabs-holder.affix { + @media (min-width: $sidebar-breakpoint) { + left: $sidebar_width; + } + } + + &.right-sidebar-expanded { + .line-resolve-all-container { + display: none; + } + } } header.header-sidebar-pinned { diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index eb63a9f214b..875cded8b4e 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -45,7 +45,7 @@ @media (max-width: $screen-xs-max) { .timeline { - &:before { + &::before { background: none; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 266a8024809..070e42d63d2 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -148,7 +148,7 @@ a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] { - &:before { + &::before { margin-right: 4px; font: normal normal normal 14px/1 FontAwesome; @@ -158,13 +158,13 @@ content: "\f0c6"; } - &:hover:before { + &:hover::before { text-decoration: none; } } a.no-attachment-icon { - &:before { + &::before { display: none; } } @@ -183,13 +183,13 @@ position: absolute; text-decoration: none; - &:after { + &::after { content: image-url('icon_anchor.svg'); visibility: hidden; } } - &:hover > a.anchor:after { + &:hover > a.anchor::after { visibility: visible; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b271f8cf332..be2a7ceefff 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -90,6 +90,8 @@ $table-border-color: #f0f0f0; $background-color: $gray-light; $dark-background-color: #f5f5f5; $table-text-gray: #8f8f8f; +$widget-expand-item: #e8f2f7; +$widget-inner-border: #eef0f2; /* * Text diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index 63396a6bb29..6cefafd8fc7 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -80,10 +80,13 @@ display: -webkit-flex; display: -ms-flexbox; display: flex; + white-space: nowrap; } .user-details { flex: 1 1 auto; + overflow: hidden; + padding-right: 8px; } .user-name { @@ -91,6 +94,12 @@ font-weight: 600; } + .user-name, + .user-email { + overflow: hidden; + text-overflow: ellipsis; + } + .dropdown { .btn-block { margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 9282e0ae03b..486ad16ea26 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -1,7 +1,7 @@ .awards { .emoji-icon { - width: 20px; - height: 20px; + width: 19px; + height: 19px; } } @@ -94,7 +94,7 @@ .award-control { margin: 3px 5px 3px 0; - padding: 6px 5px; + padding: 5px 6px; outline: 0; &:hover, @@ -127,7 +127,7 @@ .award-control-icon { float: left; margin-right: 5px; - font-size: 20px; + font-size: 19px; } .award-control-icon-loading { diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index d8fabbdcebe..ef6833c9845 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -45,6 +45,15 @@ .page-with-sidebar { padding-bottom: 0; } + + .issues-filters { + position: relative; + z-index: 999999; + } +} + +.boards-app { + position: relative; } .boards-app-loading { @@ -66,6 +75,10 @@ height: 475px; // Needed for PhantomJS height: calc(100vh - 220px); min-height: 475px; + + &.is-compact { + width: calc(100% - 290px); + } } } @@ -184,6 +197,10 @@ margin-bottom: 5px; } + &.is-active { + background-color: $row-hover; + } + .label { border: 0; outline: 0; @@ -212,6 +229,10 @@ margin-right: 5px; font-size: (14px / $issue-boards-font-size) * 1em; } + + .avatar { + margin-left: 0; + } } .card-number { @@ -264,3 +285,48 @@ border-width: 1px 0 1px 1px; } } + +.issue-boards-sidebar { + &.right-sidebar { + top: 153px; + bottom: 0; + + @media (min-width: $screen-sm-min) { + top: 220px; + } + } + + .issuable-sidebar-header { + position: relative; + } + + .gutter-toggle { + position: absolute; + top: 0; + bottom: 15px; + right: 0; + width: 22px; + color: $gray-darkest; + + svg { + position: absolute; + top: 50%; + margin-top: (-11px / 2); + } + + &:hover { + path { + fill: $gray-darkest; + } + } + } + + .issuable-header-text { + width: 100%; + padding-right: 35px; + + > strong { + font-weight: 600; + } + } +} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index d6a55fbd464..6300ac9662f 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -52,10 +52,25 @@ .build-header { position: relative; - padding-right: 40px; + padding: 0; + display: flex; + min-height: 58px; + align-items: center; - @media (min-width: $screen-sm-min) { - padding-right: 0; + .btn-inverted { + @include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light); + } + + @media (max-width: $screen-sm-max) { + padding-right: 40px; + + .btn-inverted { + display: none; + } + } + + .header-content { + flex: 1; } a { @@ -137,10 +152,15 @@ .retry-link { color: $gl-link-color; + display: none; &:hover { text-decoration: underline; } + + @media (max-width: $screen-sm-max) { + display: block; + } } .stage-item { diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 8ecac08137b..8ecf7fcb96d 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -33,10 +33,8 @@ &.commit-info-row-header { line-height: 34px; - - @media (min-width: $screen-sm-min) { - margin-bottom: 0; - } + padding: 10px 0; + margin-bottom: 0; .commit-options-dropdown-caret { @media (max-width: $screen-sm) { @@ -80,6 +78,58 @@ } } +.js-details-expand { + &:hover { + text-decoration: none; + } +} + +.commit-info-widget { + background: $background-color; + color: $gl-gray; + border: 1px solid $border-color; + border-radius: $border-radius-default; + + .widget-row { + padding: $gl-padding; + + &:not(:last-of-type) { + border-bottom: 1px solid $widget-inner-border; + } + + &.branch-info { + .monospace, + .commit-info { + margin-left: 4px; + } + } + } + + .icon-container { + display: inline-block; + margin-right: 8px; + + svg { + position: relative; + top: 2px; + height: 16px; + width: 16px; + } + + &.commit-icon { + svg { + path { + fill: $gl-text-color; + } + } + } + } + + .label.label-gray { + background-color: $widget-expand-item; + } +} + .ci-status-link { svg { overflow: visible; @@ -88,6 +138,7 @@ .commit-box { border-top: 1px solid $border-color; + padding: $gl-padding 0; .commit-title { margin: 0; @@ -138,6 +189,9 @@ } .commit-action-buttons { + position: relative; + top: -1px; + i { color: $gl-icon-color; font-size: 13px; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index ad315cfae62..52d6a39bd59 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -33,21 +33,22 @@ color: $gl-dark-link-color; } - .text-expander { - display: inline-block; - background: $gray-light; - color: $gl-placeholder-color; - padding: 0 5px; - cursor: pointer; - border: 1px solid $border-gray-dark; - border-radius: $border-radius-default; - margin-left: 5px; - line-height: 1; - - &:hover { - background-color: darken($gray-light, 10%); - text-decoration: none; - } +} + +.text-expander { + display: inline-block; + background: $gray-light; + color: $gl-placeholder-color; + padding: 0 5px; + cursor: pointer; + border: 1px solid $border-gray-dark; + border-radius: $border-radius-default; + margin-left: 5px; + line-height: 1; + + &:hover { + background-color: darken($gray-light, 10%); + text-decoration: none; } } diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 76225ed8d06..016bab104eb 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -36,10 +36,6 @@ } } -.dash-project-avatar { - float: left; -} - .dash-project-access-icon { float: left; margin-right: 5px; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index e0367d1d942..fde138c874d 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -94,14 +94,14 @@ position: relative; &.old { - &:before { + &::before { content: '-'; position: absolute; } } &.new { - &:before { + &::before { content: '+'; position: absolute; } @@ -471,7 +471,7 @@ .file-holder { .diff-line-num:not(.js-unfold-bottom) { a { - &:before { + &::before { content: attr(data-linenumber); } } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index ee2a398f031..4375e29c8db 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -3,12 +3,14 @@ } .dashboard .side .panel .panel-heading .input-group { + .form-control { height: 42px; } } .group-row { + .stats { float: right; line-height: $list-text-height; @@ -21,12 +23,14 @@ } .ldap-group-links { + .form-actions { margin-bottom: $gl-padding; } } .groups-cover-block { + .container-fluid { position: relative; } @@ -41,9 +45,14 @@ background-color: $background-color; } } + + .group-avatar { + border: 0; + } } .groups-header { + @media (min-width: $screen-sm-min) { .nav-links { width: 35%; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 3d2b024fe5c..a2f5c6c6bd3 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -54,7 +54,6 @@ margin: 0 0 10px; } - .login-footer { margin-top: 10px; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 70afa568554..f8e31a624ec 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -60,7 +60,7 @@ } .ci_widget { - border-bottom: 1px solid #eef0f2; + border-bottom: 1px solid $widget-inner-border; svg { margin-right: 4px; @@ -279,6 +279,10 @@ #modal_merge_info .modal-dialog { width: 600px; + .dark { + margin-right: 40px; + } + .btn-clipboard { @extend .pull-right; @@ -439,12 +443,12 @@ } .merge-request-tabs-holder { - background-color: #fff; + background-color: $white-light; &.affix { top: 100px; left: 0; - z-index: 9; + z-index: 10; transition: right .15s; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index b90c91831f2..526e9ae5cdd 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -105,11 +105,6 @@ ul.notes { padding: 2px; margin-top: 10px; } - - .award-control { - font-size: 13px; - padding: 2px 5px; - } } .note-header { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index f88175365c6..a8e8bbcb208 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -474,8 +474,8 @@ } .arrow { - &:before, - &:after { + &::before, + &::after { content: ''; display: inline-block; position: absolute; @@ -486,14 +486,14 @@ top: 18px; } - &:before { + &::before { left: -5px; margin-top: -6px; border-width: 7px 5px 7px 0; border-right-color: $border-color; } - &:after { + &::after { left: -4px; margin-top: -9px; border-width: 10px 7px 10px 0; @@ -573,8 +573,7 @@ .build { // Remove right connecting horizontal line from first build in last stage &:first-child { - &::after, - &::before { + &::after { border: none; } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 3f6fdaebc1d..ede29db1979 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -77,14 +77,14 @@ // Middle dot divider between each element in a list of items. .middle-dot-divider { - &:after { + &::after { content: "\00B7"; // Middle Dot padding: 0 6px; font-weight: bold; } &:last-child { - &:after { + &::after { content: ""; padding: 0; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f6355941837..f7d54564530 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -96,8 +96,8 @@ .project-avatar { float: none; - margin-left: auto; - margin-right: auto; + margin: 0 auto; + border: none; &.identicon { border-radius: 50%; @@ -193,7 +193,7 @@ margin-left: 4px; .arrow { - &:before { + &::before { content: ''; display: inline-block; position: absolute; @@ -209,7 +209,7 @@ pointer-events: none; } - &:after { + &::after { content: ''; position: absolute; width: 0; @@ -351,7 +351,7 @@ a.deploy-project-label { line-height: 36px; margin: 0; - > li + li:before { + > li + li::before { padding: 0 3px; color: #999; } @@ -790,7 +790,7 @@ pre.light-well { top: 7px; color: $location-icon-color; - &:before { + &::before { font-family: FontAwesome; font-weight: normal; font-style: normal; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 6d472e8293f..bf688af50e2 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -72,7 +72,7 @@ top: 0; color: $location-icon-color; - &:before { + &::before { font-family: FontAwesome; font-weight: normal; font-style: normal; diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index ea76fe18876..b3aef2fdd32 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -161,3 +161,63 @@ } } } + +.todos-empty { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; + max-width: 900px; + margin-left: auto; + margin-right: auto; + + @media (min-width: $screen-sm-min) { + -webkit-flex-direction: row; + flex-direction: row; + padding-top: 80px; + } +} + +.todos-empty-content { + -webkit-align-self: center; + align-self: center; + max-width: 480px; + margin-right: 20px; +} + +.todos-empty-hero { + width: 200px; + margin-left: auto; + margin-right: auto; + + @media (min-width: $screen-sm-min) { + width: 300px; + margin-right: 0; + -webkit-order: 2; + order: 2; + } +} + +.todos-all-done { + padding-top: 20px; + + @media (min-width: $screen-sm-min) { + padding-top: 50px; + } + + > svg { + display: block; + max-width: 300px; + margin: 0 auto 20px; + } + + p { + max-width: 470px; + margin-left: auto; + margin-right: auto; + } + + a { + font-weight: 600; + } +} diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss index 587bd6a1e8a..e73cecc92be 100644 --- a/app/assets/stylesheets/pages/ui_dev_kit.scss +++ b/app/assets/stylesheets/pages/ui_dev_kit.scss @@ -5,7 +5,7 @@ } .example { - &:before { + &::before { content: "Example"; color: #bbb; } diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss index 8239b7e6879..0ff3c3f5472 100644 --- a/app/assets/stylesheets/print.scss +++ b/app/assets/stylesheets/print.scss @@ -35,7 +35,7 @@ nav.navbar-collapse.collapse, .nav, .btn, ul.notes-form, -.merge-request-ci-status .ci-status-link:after, +.merge-request-ci-status .ci-status-link::after, .issuable-gutter-toggle, .gutter-toggle, .issuable-details .content-block-small, diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index f35f4a8c811..bb912ed10cc 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -3,7 +3,7 @@ class Admin::UsersController < Admin::ApplicationController def index @users = User.order_name_asc.filter(params[:filter]) - @users = @users.search(params[:name]) if params[:name].present? + @users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present? @users = @users.sort(@sort = params[:sort]) @users = @users.page(params[:page]) end diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index 4cb3be41064..c33d7eecb9f 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -18,7 +18,7 @@ module ServiceParams :add_pusher, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, :server_host, :server_port, :default_irc_uri, :enable_ssl_verification, - :jira_issue_transition_id] + :jira_issue_transition_id, :url, :project_key] # Parameters to ignore if no value is specified FILTER_BLANK_PARAMS = [:password] diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index a2b01ff43dc..dc33e1405f2 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -73,10 +73,13 @@ module Projects def serialize_as_json(resource) resource.as_json( labels: true, - only: [:iid, :title, :confidential], + only: [:iid, :title, :confidential, :due_date], include: { - assignee: { only: [:id, :name, :username], methods: [:avatar_url] } - }) + assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, + milestone: { only: [:id, :title] } + }, + user: current_user + ) end end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index cb649264146..3f1a1d1c511 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -112,7 +112,7 @@ class Projects::IssuesController < Projects::ApplicationController end format.json do - render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) + render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) end end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 4f855134368..42fd09e9b7e 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -126,7 +126,7 @@ class Projects::LabelsController < Projects::ApplicationController alias_method :subscribable_resource, :label def find_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities) + @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute end def authorize_admin_labels! diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2ee53f7ceda..30f1cf4e5be 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -278,7 +278,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.target_project, @merge_request]) end format.json do - render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) + render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) end end else diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d08f490de18..699a56ae2f8 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -25,18 +25,15 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def create - if params[:user_ids].blank? - return redirect_to(namespace_project_project_members_path(@project.namespace, @project), alert: 'No users or groups specified.') - end + status = Members::CreateService.new(@project, current_user, params).execute - @project.team.add_users( - params[:user_ids].split(','), - params[:access_level], - expires_at: params[:expires_at], - current_user: current_user - ) + redirect_url = namespace_project_project_members_path(@project.namespace, @project) - redirect_to namespace_project_project_members_path(@project.namespace, @project), notice: 'Users were successfully added.' + if status + redirect_to redirect_url, notice: 'Users were successfully added.' + else + redirect_to redirect_url, alert: 'No users or groups specified.' + end end def update diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 8fea20cefef..953091492ae 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -23,7 +23,7 @@ class Projects::TagsController < Projects::ApplicationController return render_404 unless @tag @release = @project.releases.find_or_initialize_by(tag: @tag.name) - @commit = @repository.commit(@tag.target) + @commit = @repository.commit(@tag.dereferenced_target) end def create diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 76b730198d4..bce5e29d8d8 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -30,6 +30,8 @@ class ProjectsController < Projects::ApplicationController @project = ::Projects::CreateService.new(current_user, project_params).execute if @project.saved? + cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) } + redirect_to( project_path(@project), notice: "Project '#{@project.name}' was successfully created." @@ -287,7 +289,8 @@ class ProjectsController < Projects::ApplicationController render 'projects/empty' if @project.empty_repo? else if @project.wiki_enabled? - @wiki_home = @project.wiki.find_page('home', params[:version_id]) + @project_wiki = @project.wiki + @wiki_home = @project_wiki.find_page('home', params[:version_id]) elsif @project.feature_available?(:issues, current_user) @issues = issues_collection @issues = @issues.page(params[:page]) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index e27986ef95b..cc2073081b5 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -126,7 +126,7 @@ class IssuableFinder @labels = if labels? && !filter_by_no_label? - LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute + LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true) else Label.none end @@ -273,7 +273,7 @@ class IssuableFinder items = items.with_label(label_names, params[:sort]) if projects - label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id) + label_ids = LabelsFinder.new(current_user, project_ids: projects).execute(skip_authorization: true).select(:id) items = items.where(labels: { id: label_ids }) end end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 95e62cdb02a..865f093f04a 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -4,9 +4,8 @@ class LabelsFinder < UnionFinder @params = params end - def execute(authorized_only: true) - @authorized_only = authorized_only - + def execute(skip_authorization: false) + @skip_authorization = skip_authorization items = find_union(label_ids, Label) items = with_title(items) sort(items) @@ -14,7 +13,7 @@ class LabelsFinder < UnionFinder private - attr_reader :current_user, :params, :authorized_only + attr_reader :current_user, :params, :skip_authorization def label_ids label_ids = [] @@ -50,7 +49,7 @@ class LabelsFinder < UnionFinder end def projects_ids - params[:project_ids].presence + params[:project_ids] end def title @@ -70,17 +69,17 @@ class LabelsFinder < UnionFinder end def find_project - if authorized_only - available_projects.find_by(id: project_id) - else + if skip_authorization Project.find_by(id: project_id) + else + available_projects.find_by(id: project_id) end end def projects return @projects if defined?(@projects) - @projects = authorized_only ? available_projects : Project.all + @projects = skip_authorization ? Project.all : available_projects @projects = @projects.in_namespace(group_id) if group_id @projects = @projects.where(id: projects_ids) if projects_ids @projects = @projects.reorder(nil) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 85e1dc33ee8..dee3c78df47 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -16,13 +16,14 @@ module ButtonHelper # See http://clipboardjs.com/#usage def clipboard_button(data = {}) css_class = data[:class] || 'btn-clipboard btn-transparent' + title = data[:title] || 'Copy to Clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), class: "btn #{css_class}", data: data, type: :button, - title: 'Copy to Clipboard' + title: title end def http_clone_button(project, placement = 'right', append_link: true) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index f8ded05c31a..00e64076408 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -39,6 +39,12 @@ module EventsHelper end end + def event_filter_visible(feature_key) + return true unless @project + + @project.feature_available?(feature_key, current_user) + end + def event_preposition(event) if event.push? || event.commented? || event.target "at" diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 03b2db1bc91..ef6cfb235a9 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -71,6 +71,14 @@ module IssuablesHelper author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true) author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") end + + if issuable.tasks? + output << " ".html_safe + output << content_tag(:span, issuable.task_status, id: "task_status", class: "hidden-xs") + output << content_tag(:span, issuable.task_status_short, id: "task_status_short", class: "hidden-sm hidden-md hidden-lg") + end + + output end def issuable_todo(issuable) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d26b4018be6..42c00ec3cd5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -174,10 +174,14 @@ module ProjectsHelper nav_tabs << :merge_requests end - if can?(current_user, :read_build, project) + if can?(current_user, :read_pipeline, project) nav_tabs << :pipelines end + if can?(current_user, :read_build, project) + nav_tabs << :builds + end + if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) nav_tabs << :container_registry end diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb index d440edc55ba..56749d80bd3 100644 --- a/app/helpers/sidekiq_helper.rb +++ b/app/helpers/sidekiq_helper.rb @@ -5,7 +5,7 @@ module SidekiqHelper (?<mem>[\d\.,]+)\s+ (?<state>[DRSTWXZNLsl\+<]+)\s+ (?<start>.+)\s+ - (?<command>sidekiq.*\])\s+ + (?<command>sidekiq.*\])\s* \z/x def parse_sidekiq_ps(line) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 826539d5c4d..3fee6c18770 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -30,23 +30,23 @@ module Ci end event :run do - transition any => :running + transition any - [:running] => :running end event :skip do - transition any => :skipped + transition any - [:skipped] => :skipped end event :drop do - transition any => :failed + transition any - [:failed] => :failed end event :succeed do - transition any => :success + transition any - [:success] => :success end event :cancel do - transition any => :canceled + transition any - [:canceled] => :canceled end # IMPORTANT @@ -271,7 +271,7 @@ module Ci end def update_status - with_lock do + Gitlab::OptimisticLocking.retry_lock(self) do case latest_builds_status when 'pending' then enqueue when 'running' then run diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 4cb3a69416e..d159fc6c5c7 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -73,16 +73,16 @@ class CommitStatus < ActiveRecord::Base transition [:created, :pending, :running] => :canceled end - after_transition created: [:pending, :running] do |commit_status| - commit_status.update_attributes queued_at: Time.now + before_transition created: [:pending, :running] do |commit_status| + commit_status.queued_at = Time.now end - after_transition [:created, :pending] => :running do |commit_status| - commit_status.update_attributes started_at: Time.now + before_transition [:created, :pending] => :running do |commit_status| + commit_status.started_at = Time.now end - after_transition any => [:success, :failed, :canceled] do |commit_status| - commit_status.update_attributes finished_at: Time.now + before_transition any => [:success, :failed, :canceled] do |commit_status| + commit_status.finished_at = Time.now end after_transition do |commit_status, transition| diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 17c3b526c97..613444e0d70 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -12,6 +12,7 @@ module Issuable include Subscribable include StripAttribute include Awardable + include Taskable included do cache_markdown_field :title, pipeline: :single_line diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb index 9216122923e..6d88951c713 100644 --- a/app/models/concerns/project_features_compatibility.rb +++ b/app/models/concerns/project_features_compatibility.rb @@ -31,7 +31,7 @@ module ProjectFeaturesCompatibility def write_feature_attribute(field, value) build_project_feature unless project_feature - access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED + access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED project_feature.update_attribute(field, access_level) end end diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 12b23f00769..7edb0acd56c 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -38,16 +38,21 @@ module Sortable private - def highest_label_priority(target_type:, target_column:, project_column:, excluded_labels: []) + def highest_label_priority(target_type_column: nil, target_type: nil, target_column:, project_column:, excluded_labels: []) query = Label.select(LabelPriority.arel_table[:priority].minimum). left_join_priorities. joins(:label_links). where("label_priorities.project_id = #{project_column}"). - where(label_links: { target_type: target_type }). where("label_links.target_id = #{target_column}"). reorder(nil) - query.where.not(title: excluded_labels) if excluded_labels.present? + if target_type_column + query = query.where("label_links.target_type = #{target_type_column}") + else + query = query.where(label_links: { target_type: target_type }) + end + + query = query.where.not(title: excluded_labels) if excluded_labels.present? query end diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index a3ac577cf3e..ebc75100a54 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -53,10 +53,22 @@ module Taskable # Return a string that describes the current state of this Taskable's task # list items, e.g. "12 of 20 tasks completed" - def task_status + def task_status(short: false) return '' if description.blank? + prep, completed = if short + ['/', ''] + else + [' of ', ' completed'] + end + sum = tasks.summary - "#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed" + "#{sum.complete_count}#{prep}#{sum.item_count} #{'task'.pluralize(sum.item_count)}#{completed}" + end + + # Return a short string that describes the current state of this Taskable's + # task list items -- for small screens + def task_status_short + task_status(short: true) end end diff --git a/app/models/event.rb b/app/models/event.rb index 3993b35f96d..43e67069b70 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,6 +1,6 @@ class Event < ActiveRecord::Base include Sortable - default_scope { where.not(author_id: nil) } + default_scope { reorder(nil).where.not(author_id: nil) } CREATED = 1 UPDATED = 2 diff --git a/app/models/group.rb b/app/models/group.rb index 552e1154df6..d9e90cd256a 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -68,7 +68,7 @@ class Group < Namespace end def web_url - Gitlab::Routing.url_helpers.group_url(self) + Gitlab::Routing.url_helpers.group_canonical_url(self) end def human_name diff --git a/app/models/group_label.rb b/app/models/group_label.rb index a698b532d19..68841ace2e6 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -5,6 +5,10 @@ class GroupLabel < Label alias_attribute :subject, :group + def subject_foreign_key + 'group_id' + end + def to_reference(source_project = nil, target_project = nil, format: :id) super(source_project, target_project, format: format) end diff --git a/app/models/issue.rb b/app/models/issue.rb index 89158a50353..4f02b02c488 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -5,7 +5,6 @@ class Issue < ActiveRecord::Base include Issuable include Referable include Sortable - include Taskable include Spammable include FasterCacheKeys @@ -287,10 +286,12 @@ class Issue < ActiveRecord::Base def as_json(options = {}) super(options).tap do |json| + json[:subscribed] = subscribed?(options[:user]) if options.has_key?(:user) + if options.has_key?(:labels) json[:labels] = labels.as_json( project: project, - only: [:id, :title, :description, :color], + only: [:id, :title, :description, :color, :priority], methods: [:text_color] ) end diff --git a/app/models/label.rb b/app/models/label.rb index 149fd98ecb3..d9287f2dc29 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -92,16 +92,23 @@ class Label < ActiveRecord::Base nil end - def open_issues_count(user = nil, project = nil) - issues_count(user, project_id: project.try(:id) || project_id, state: 'opened') + def open_issues_count(user = nil) + issues_count(user, state: 'opened') end - def closed_issues_count(user = nil, project = nil) - issues_count(user, project_id: project.try(:id) || project_id, state: 'closed') + def closed_issues_count(user = nil) + issues_count(user, state: 'closed') end - def open_merge_requests_count(user = nil, project = nil) - merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened') + def open_merge_requests_count(user = nil) + params = { + subject_foreign_key => subject.id, + label_name: title, + scope: 'all', + state: 'opened' + } + + MergeRequestsFinder.new(user, params.with_indifferent_access).execute.count end def prioritize!(project, value) @@ -167,15 +174,8 @@ class Label < ActiveRecord::Base end def issues_count(user, params = {}) - IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) - .execute - .count - end - - def merge_requests_count(user, params = {}) - MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) - .execute - .count + params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all') + IssuesFinder.new(user, params.with_indifferent_access).execute.count end def label_format_reference(format = :id) diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 18657c3e1c8..7712d5783e0 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -17,4 +17,10 @@ class LfsObject < ActiveRecord::Base def project_allowed_access?(project) projects.exists?(storage_project(project).id) end + + def self.destroy_unreferenced + joins("LEFT JOIN lfs_objects_projects ON lfs_objects_projects.lfs_object_id = #{table_name}.id") + .where(lfs_objects_projects: { id: nil }) + .destroy_all + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4872f8b8649..0397c57f935 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -3,7 +3,6 @@ class MergeRequest < ActiveRecord::Base include Issuable include Referable include Sortable - include Taskable include Importable belongs_to :target_project, class_name: "Project" diff --git a/app/models/project.rb b/app/models/project.rb index fbf7012972e..d5512dfaf9c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -30,6 +30,11 @@ class Project < ActiveRecord::Base default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for(:repository_storage) { current_application_settings.repository_storage } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } + default_value_for :issues_enabled, gitlab_config_features.issues + default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests + default_value_for :builds_enabled, gitlab_config_features.builds + default_value_for :wiki_enabled, gitlab_config_features.wiki + default_value_for :snippets_enabled, gitlab_config_features.snippets after_create :ensure_dir_exist after_create :create_project_feature, unless: :project_feature @@ -390,7 +395,7 @@ class Project < ActiveRecord::Base end def group_ids - joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id) + joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id) end end @@ -738,7 +743,7 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| params = label.attributes.except('id', 'template', 'created_at', 'updated_at') - Labels::FindOrCreateService.new(owner, self, params).execute + Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true) end end diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 33c2b617715..82f47f0e8fd 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -12,6 +12,10 @@ class ProjectLabel < Label alias_attribute :subject, :project + def subject_foreign_key + 'project_id' + end + def to_reference(target_project = nil, format: :id) super(project, target_project, format: format) end diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb index 81af55aa29a..338e685339a 100644 --- a/app/models/project_services/bugzilla_service.rb +++ b/app/models/project_services/bugzilla_service.rb @@ -1,4 +1,6 @@ class BugzillaService < IssueTrackerService + validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url def title diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index d9fba3d4a41..b2f426dc2ac 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -1,4 +1,6 @@ class CustomIssueTrackerService < IssueTrackerService + validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url def title diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 5d17c358330..6bd8d4ec568 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -1,6 +1,8 @@ class GitlabIssueTrackerService < IssueTrackerService include Gitlab::Routing.url_helpers + validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url default_value_for :default, true diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index b26ddd518d7..207bb816ad1 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -1,6 +1,4 @@ class IssueTrackerService < Service - validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? - default_value_for :category, 'issue_tracker' # Pattern used to extract links from comments @@ -38,18 +36,24 @@ class IssueTrackerService < Service ] end - def initialize_properties - if properties.nil? - if enabled_in_gitlab_config + # Initialize with default properties values + # or receive a block with custom properties + def initialize_properties(&block) + return unless properties.nil? + + if enabled_in_gitlab_config + if block_given? + yield + else self.properties = { title: issues_tracker['title'], project_url: issues_tracker['project_url'], issues_url: issues_tracker['issues_url'], new_issue_url: issues_tracker['new_issue_url'] } - else - self.properties = {} end + else + self.properties = {} end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index f81b66fd219..5bcf199d468 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -1,15 +1,32 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# build_events :boolean default(FALSE), not null +# + class JiraService < IssueTrackerService - include HTTParty include Gitlab::Routing.url_helpers - DEFAULT_API_VERSION = 2 - - prop_accessor :username, :password, :api_url, :jira_issue_transition_id, - :title, :description, :project_url, :issues_url, :new_issue_url + validates :url, url: true, presence: true, if: :activated? + validates :project_key, presence: true, if: :activated? - validates :api_url, presence: true, url: true, if: :activated? - - before_validation :set_api_url, :set_jira_issue_transition_id + prop_accessor :username, :password, :url, :project_key, + :jira_issue_transition_id, :title, :description before_update :reset_password @@ -18,16 +35,46 @@ class JiraService < IssueTrackerService @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)} end + def initialize_properties + super do + self.properties = { + title: issues_tracker['title'], + url: issues_tracker['url'] + } + end + end + def reset_password # don't reset the password if a new one is provided - if api_url_changed? && !password_touched? + if url_changed? && !password_touched? self.password = nil end end + def options + url = URI.parse(self.url) + + { + username: self.username, + password: self.password, + site: URI.join(url, '/').to_s, + context_path: url.path, + auth_type: :basic, + read_timeout: 120, + use_ssl: url.scheme == 'https' + } + end + + def client + @client ||= JIRA::Client.new(options) + end + + def jira_project + @jira_project ||= client.Project.find(project_key) + end + def help - 'Setting `project_url`, `issues_url` and `new_issue_url` will '\ - 'allow a user to easily navigate to the Jira issue tracker. See the '\ + 'See the ' \ '[integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) '\ 'for details.' end @@ -53,12 +100,26 @@ class JiraService < IssueTrackerService end def fields - super.push( - { type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' }, + [ + { type: 'text', name: 'url', title: 'URL', placeholder: 'https://jira.example.com' }, + { type: 'text', name: 'project_key', placeholder: 'Project Key' }, { type: 'text', name: 'username', placeholder: '' }, { type: 'password', name: 'password', placeholder: '' }, { type: 'text', name: 'jira_issue_transition_id', placeholder: '2' } - ) + ] + end + + # URLs to redirect from Gitlab issues pages to jira issue tracker + def project_url + "#{url}/issues/?jql=project=#{project_key}" + end + + def issues_url + "#{url}/browse/:id" + end + + def new_issue_url + "#{url}/secure/CreateIssue.jspa" end def execute(push, issue = nil) @@ -72,7 +133,7 @@ class JiraService < IssueTrackerService end def create_cross_reference_note(mentioned, noteable, author) - issue_name = mentioned.id + issue_key = mentioned.id project = self.project noteable_name = noteable.class.name.underscore.downcase noteable_id = if noteable.is_a?(Commit) @@ -99,58 +160,28 @@ class JiraService < IssueTrackerService } } - add_comment(data, issue_name) + add_comment(data, issue_key) end def test_settings - return unless api_url.present? - result = JiraService.get( - jira_api_test_url, - headers: { - 'Content-Type' => 'application/json', - 'Authorization' => "Basic #{auth}" - } - ) + return unless url.present? + # Test settings by getting the project + jira_project - case result.code - when 201, 200 - Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.") - true - else - Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}") - false - end - rescue Errno::ECONNREFUSED => e - Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}." + rescue Errno::ECONNREFUSED, JIRA::HTTPError => e + Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{url}." false end private - def build_api_url_from_project_url - server = URI(project_url) - default_ports = [["http", 80], ["https", 443]].include?([server.scheme, server.port]) - server_url = "#{server.scheme}://#{server.host}" - server_url.concat(":#{server.port}") unless default_ports - "#{server_url}/rest/api/#{DEFAULT_API_VERSION}" - rescue - "" # looks like project URL was not valid - end - - def set_api_url - self.api_url = build_api_url_from_project_url if self.api_url.blank? - end - - def set_jira_issue_transition_id - self.jira_issue_transition_id ||= "2" - end - def close_issue(entity, issue) commit_id = if entity.is_a?(Commit) entity.id elsif entity.is_a?(MergeRequest) entity.diff_head_sha end + commit_url = build_entity_url(:commit, commit_id) # Depending on the JIRA project's workflow, a comment during transition @@ -161,24 +192,16 @@ class JiraService < IssueTrackerService end def transition_issue(issue) - message = { - transition: { - id: jira_issue_transition_id - } - } - send_message(close_issue_url(issue.iid), message.to_json) + issue = client.Issue.find(issue.iid) + issue.transitions.build.save(transition: { id: jira_issue_transition_id }) end def add_issue_solved_comment(issue, commit_id, commit_url) - comment = { - body: "Issue solved with [#{commit_id}|#{commit_url}]." - } - - send_message(comment_url(issue.iid), comment.to_json) + comment = "Issue solved with [#{commit_id}|#{commit_url}]." + send_message(issue.iid, comment) end - def add_comment(data, issue_name) - url = comment_url(issue_name) + def add_comment(data, issue_key) user_name = data[:user][:name] user_url = data[:user][:url] entity_name = data[:entity][:name] @@ -186,68 +209,31 @@ class JiraService < IssueTrackerService entity_title = data[:entity][:title] project_name = data[:project][:name] - message = { - body: %Q{[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'} - } + message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'" - unless existing_comment?(issue_name, message[:body]) - send_message(url, message.to_json) + unless comment_exists?(issue_key, message) + send_message(issue_key, message) end end - def auth - require 'base64' - Base64.urlsafe_encode64("#{self.username}:#{self.password}") - end - - def send_message(url, message) - return unless api_url.present? - result = JiraService.post( - url, - body: message, - headers: { - 'Content-Type' => 'application/json', - 'Authorization' => "Basic #{auth}" - } - ) - - message = case result.code - when 201, 200, 204 - "#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}." - when 401 - "#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again." - else - "#{self.class.name} ERROR #{result.code}: #{result.parsed_response}" - end - - Rails.logger.info(message) - message - rescue URI::InvalidURIError, Errno::ECONNREFUSED => e - Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}." + def comment_exists?(issue_key, message) + comments = client.Issue.find(issue_key).comments + comments.map { |comment| comment.body.include?(message) }.any? end - def existing_comment?(issue_name, new_comment) - return unless api_url.present? - result = JiraService.get( - comment_url(issue_name), - headers: { - 'Content-Type' => 'application/json', - 'Authorization' => "Basic #{auth}" - } - ) + def send_message(issue_key, message) + return unless url.present? - case result.code - when 201, 200 - existing_comments = JSON.parse(result.body)['comments'] + issue = client.Issue.find(issue_key) - if existing_comments.present? - return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any? - end + if issue.comments.build.save!(body: message) + result_message = "#{self.class.name} SUCCESS: Successfully posted to #{url}." end - false - rescue JSON::ParserError - false + Rails.logger.info(result_message) + result_message + rescue URI::InvalidURIError, Errno::ECONNREFUSED, JIRA::HTTPError => e + Rails.logger.info "#{self.class.name} Send message ERROR: #{url} - #{e.message}" end def resource_url(resource) @@ -267,16 +253,4 @@ class JiraService < IssueTrackerService ) ) end - - def close_issue_url(issue_name) - "#{self.api_url}/issue/#{issue_name}/transitions" - end - - def comment_url(issue_name) - "#{self.api_url}/issue/#{issue_name}/comment" - end - - def jira_api_test_url - "#{self.api_url}/myself" - end end diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index f634e0772c0..f9da273cf08 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -1,4 +1,6 @@ class RedmineService < IssueTrackerService + validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url def title diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 79d041d2775..a6e911df9bd 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -125,14 +125,8 @@ class ProjectTeam max_member_access(user.id) == Gitlab::Access::MASTER end - def member?(user, min_member_access = nil) - member = !!find_member(user.id) - - if min_member_access - member && max_member_access(user.id) >= min_member_access - else - member - end + def member?(user, min_member_access = Gitlab::Access::GUEST) + max_member_access(user.id) >= min_member_access end def human_max_access(user_id) diff --git a/app/models/repository.rb b/app/models/repository.rb index 4ae9c20726f..30be7262438 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -11,6 +11,20 @@ class Repository attr_accessor :path_with_namespace, :project + def self.storages + Gitlab.config.repositories.storages + end + + def self.remove_storage_from_path(repo_path) + storages.find do |_, storage_path| + if repo_path.start_with?(storage_path) + return repo_path.sub(storage_path, '') + end + end + + repo_path + end + def initialize(path_with_namespace, project) @path_with_namespace = path_with_namespace @project = project @@ -181,7 +195,7 @@ class Repository before_remove_branch branch = find_branch(branch_name) - oldrev = branch.try(:target).try(:id) + oldrev = branch.try(:dereferenced_target).try(:id) newrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name @@ -297,10 +311,10 @@ class Repository # Rugged seems to throw a `ReferenceError` when given branch_names rather # than SHA-1 hashes number_commits_behind = raw_repository. - count_commits_between(branch.target.sha, root_ref_hash) + count_commits_between(branch.dereferenced_target.sha, root_ref_hash) number_commits_ahead = raw_repository. - count_commits_between(root_ref_hash, branch.target.sha) + count_commits_between(root_ref_hash, branch.dereferenced_target.sha) { behind: number_commits_behind, ahead: number_commits_ahead } end @@ -682,11 +696,11 @@ class Repository branches.sort_by(&:name) when 'updated_desc' branches.sort do |a, b| - commit(b.target).committed_date <=> commit(a.target).committed_date + commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date end when 'updated_asc' branches.sort do |a, b| - commit(a.target).committed_date <=> commit(b.target).committed_date + commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date end else branches @@ -861,7 +875,7 @@ class Repository branch = find_branch(ref) if branch - last_commit = branch.target + last_commit = branch.dereferenced_target index.read_tree(last_commit.raw_commit.tree) parents = [last_commit.sha] end @@ -948,7 +962,7 @@ class Repository end def revert(user, commit, base_branch, revert_tree_id = nil) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha revert_tree_id ||= check_revert_content(commit, base_branch) return false unless revert_tree_id @@ -965,7 +979,7 @@ class Repository end def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) return false unless cherry_pick_tree_id @@ -994,7 +1008,7 @@ class Repository end def check_revert_content(commit, base_branch) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha args = [commit.id, source_sha] args << { mainline: 1 } if commit.merge_commit? @@ -1008,7 +1022,7 @@ class Repository end def check_cherry_pick_content(commit, base_branch) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha args = [commit.id, source_sha] args << 1 if commit.merge_commit? @@ -1081,7 +1095,7 @@ class Repository if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? oldrev = Gitlab::Git::BLANK_SHA else - oldrev = rugged.merge_base(newrev, target_branch.target.sha) + oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha) end GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do @@ -1141,7 +1155,7 @@ class Repository end def tags_sorted_by_committed_date - tags.sort_by { |tag| tag.target.committed_date } + tags.sort_by { |tag| tag.dereferenced_target.committed_date } end def keep_around_ref_name(sha) diff --git a/app/models/todo.rb b/app/models/todo.rb index 11c072dd000..f5ade1cc293 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -53,7 +53,7 @@ class Todo < ActiveRecord::Base # Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue" def order_by_labels_priority params = { - target_type: ['Issue', 'MergeRequest'], + target_type_column: "todos.target_type", target_column: "todos.target_id", project_column: "todos.project_id" } diff --git a/app/models/user.rb b/app/models/user.rb index 9e76df63d31..af3c0b7dc02 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -93,8 +93,10 @@ class User < ActiveRecord::Base # # Validations # + # Note: devise :validatable above adds validations for :email and :password validates :name, presence: true - validates :notification_email, presence: true, email: true + validates :notification_email, presence: true + validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email } validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true validates :bio, length: { maximum: 255 }, allow_blank: true validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 } @@ -256,6 +258,24 @@ class User < ActiveRecord::Base ) end + # searches user by given pattern + # it compares name, email, username fields and user's secondary emails with given pattern + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + + def search_with_secondary_emails(query) + table = arel_table + email_table = Email.arel_table + pattern = "%#{query}%" + matched_by_emails_user_ids = email_table.project(email_table[:user_id]).where(email_table[:email].matches(pattern)) + + where( + table[:name].matches(pattern). + or(table[:email].matches(pattern)). + or(table[:username].matches(pattern)). + or(table[:id].in(matched_by_emails_user_ids)) + ) + end + def by_login(login) return nil unless login diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index d3dd30b2588..8face432d97 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -10,17 +10,14 @@ module Ci create_builds! end - @pipeline.with_lock do - new_builds = - stage_indexes_of_created_builds.map do |index| - process_stage(index) - end + new_builds = + stage_indexes_of_created_builds.map do |index| + process_stage(index) + end - @pipeline.update_status + @pipeline.update_status - # Return a flag if a when builds got enqueued - new_builds.flatten.any? - end + new_builds.flatten.any? end private @@ -32,9 +29,11 @@ module Ci def process_stage(index) current_status = status_for_prior_stages(index) - created_builds_in_stage(index).select do |build| - if HasStatus::COMPLETED_STATUSES.include?(current_status) - process_build(build, current_status) + if HasStatus::COMPLETED_STATUSES.include?(current_status) + created_builds_in_stage(index).select do |build| + Gitlab::OptimisticLocking.retry_lock(build) do |subject| + process_build(subject, current_status) + end end end end diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index 6973191b203..74b5ebf372b 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -28,17 +28,14 @@ module Ci if build # In case when 2 runners try to assign the same build, second runner will be declined - # with StateMachines::InvalidTransition in run! method. - build.with_lock do - build.runner_id = current_runner.id - build.save! - build.run! - end + # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. + build.runner_id = current_runner.id + build.run! end build - rescue StateMachines::InvalidTransition + rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError nil end diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index 918eddaa53a..3e5dd4ebb86 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -42,7 +42,7 @@ class DeleteBranchService < BaseService Gitlab::DataBuilder::Push.build( project, current_user, - branch.target.sha, + branch.dereferenced_target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index d0cb151a010..d824406cb49 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -36,7 +36,7 @@ class DeleteTagService < BaseService Gitlab::DataBuilder::Push.build( project, current_user, - tag.target.sha, + tag.dereferenced_target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index e6002b03b93..20a4445bddf 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -27,8 +27,8 @@ class GitTagPushService < BaseService tag_name = Gitlab::Git.ref_name(params[:ref]) tag = project.repository.find_tag(tag_name) - if tag && tag.object_sha == params[:newrev] - commit = project.commit(tag.target) + if tag && tag.target == params[:newrev] + commit = project.commit(tag.dereferenced_target) commits = [commit].compact message = tag.message end diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb index 74291312c4e..d622f9edd33 100644 --- a/app/services/labels/find_or_create_service.rb +++ b/app/services/labels/find_or_create_service.rb @@ -2,21 +2,24 @@ module Labels class FindOrCreateService def initialize(current_user, project, params = {}) @current_user = current_user - @group = project.group @project = project @params = params.dup end - def execute + def execute(skip_authorization: false) + @skip_authorization = skip_authorization find_or_create_label end private - attr_reader :current_user, :group, :project, :params + attr_reader :current_user, :project, :params, :skip_authorization def available_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute + @available_labels ||= LabelsFinder.new( + current_user, + project_id: project.id + ).execute(skip_authorization: skip_authorization) end def find_or_create_label diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb index 416aee2ab51..c13f289f61e 100644 --- a/app/services/members/approve_access_request_service.rb +++ b/app/services/members/approve_access_request_service.rb @@ -4,17 +4,25 @@ module Members attr_accessor :source + # source - The source object that respond to `#requesters` (i.g. project or group) + # current_user - The user that performs the access request approval + # params - A hash of parameters + # :user_id - User ID used to retrieve the access requester + # :id - Member ID used to retrieve the access requester + # :access_level - Optional access level set when the request is accepted def initialize(source, current_user, params = {}) @source = source @current_user = current_user - @params = params + @params = params.slice(:user_id, :id, :access_level) end - def execute + # opts - A hash of options + # :force - Bypass permission check: current_user can be nil in that case + def execute(opts = {}) condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] } access_requester = source.requesters.find_by!(condition) - raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester) + raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester, opts) access_requester.access_level = params[:access_level] if params[:access_level] access_requester.accept_request @@ -24,8 +32,11 @@ module Members private - def can_update_access_requester?(access_requester) - access_requester && can?(current_user, action_member_permission(:update, access_requester), access_requester) + def can_update_access_requester?(access_requester, opts = {}) + access_requester && ( + opts[:force] || + can?(current_user, action_member_permission(:update, access_requester), access_requester) + ) end end end diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb new file mode 100644 index 00000000000..e4b24ccef92 --- /dev/null +++ b/app/services/members/create_service.rb @@ -0,0 +1,16 @@ +module Members + class CreateService < BaseService + def execute + return false if params[:user_ids].blank? + + project.team.add_users( + params[:user_ids].split(','), + params[:access_level], + expires_at: params[:expires_at], + current_user: current_user + ) + + true + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 404f75616b5..f415244068b 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -13,20 +13,8 @@ module MergeRequests merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_branch ||= merge_request.target_project.default_branch - if merge_request.target_branch.blank? || merge_request.source_branch.blank? - message = - if params[:source_branch] || params[:target_branch] - "You must select source and target branch" - end - - return build_failed(merge_request, message) - end - - if merge_request.source_project == merge_request.target_project && - merge_request.target_branch == merge_request.source_branch - - return build_failed(merge_request, 'You must select different branches') - end + messages = validate_branches(merge_request) + return build_failed(merge_request, messages) unless messages.empty? compare = CompareService.new.execute( merge_request.source_project, @@ -43,6 +31,34 @@ module MergeRequests private + def validate_branches(merge_request) + messages = [] + + if merge_request.target_branch.blank? || merge_request.source_branch.blank? + messages << + if params[:source_branch] || params[:target_branch] + "You must select source and target branch" + end + end + + if merge_request.source_project == merge_request.target_project && + merge_request.target_branch == merge_request.source_branch + + messages << 'You must select different branches' + end + + # See if source and target branches exist + unless merge_request.source_project.commit(merge_request.source_branch) + messages << "Source branch \"#{merge_request.source_branch}\" does not exist" + end + + unless merge_request.target_project.commit(merge_request.target_branch) + messages << "Target branch \"#{merge_request.target_branch}\" does not exist" + end + + messages + end + # When your branch name starts with an iid followed by a dash this pattern will be # interpreted as the user wants to close that issue on this project. # @@ -91,8 +107,10 @@ module MergeRequests merge_request end - def build_failed(merge_request, message) - merge_request.errors.add(:base, message) unless message.nil? + def build_failed(merge_request, messages) + messages.compact.each do |message| + merge_request.errors.add(:base, message) + end merge_request.compare_commits = [] merge_request.can_be_created = false merge_request diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index e466ffa60eb..d7221fe993c 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -29,7 +29,7 @@ module Projects if unknown_url? # In this case, we only want to import issues, not a repository. create_repository - else + elsif !project.repository_exists? import_repository end end diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index adfa1eaafc9..05c88ca1cc8 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -16,7 +16,8 @@ %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} = visibility_level_icon(group.visibility_level, fw: false) - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + .image-container.s40 + = image_tag group_icon(group), class: "avatar s40 hidden-xs" .title = link_to [:admin, group], class: 'group-name' do = group.name diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 0188ed448ce..a7c1a4f5038 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -13,7 +13,8 @@ Group info: %ul.well-list %li - = image_tag group_icon(@group), class: "avatar s60" + .image-container.s60 + = image_tag group_icon(@group), class: "avatar s60" %li %span.light Name: %strong= @group.name diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 339cfc613fe..10dce6f3d8f 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -76,7 +76,8 @@ .title = link_to [:admin, project.namespace.becomes(Namespace), project] do .dash-project-avatar - = project_icon(project, alt: '', class: 'avatar project-avatar s40') + .image-container.s40 + = project_icon(project, alt: '', class: 'avatar project-avatar s40') %span.project-full-name %span.namespace-name - if project.namespace diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 357123c2c13..d3038ae644f 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -10,7 +10,7 @@ = hidden_field_tag "filter", h(params[:filter]) .search-holder .search-field-holder - = search_field_tag :name, params[:name], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false + = search_field_tag :search_query, params[:search_query], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false = icon("search", class: "search-icon") .dropdown - toggle_text = if @sort.present? then sort_options_hash[@sort] else sort_title_name end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 2a0302638ba..2411cc45724 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -1,69 +1,70 @@ - page_title "Todos" - header_title "Todos", dashboard_todos_path -.top-area - %ul.nav-links - - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending') - %li{class: "todos-pending #{todo_pending_active}"} - = link_to todos_filter_path(state: 'pending') do - %span - To do - %span.badge - = number_with_delimiter(todos_pending_count) - - todo_done_active = ('active' if params[:state] == 'done') - %li{class: "todos-done #{todo_done_active}"} - = link_to todos_filter_path(state: 'done') do - %span - Done - %span.badge - = number_with_delimiter(todos_done_count) +- if current_user.todos.any? + .top-area + %ul.nav-links + - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending') + %li{class: "todos-pending #{todo_pending_active}"} + = link_to todos_filter_path(state: 'pending') do + %span + To do + %span.badge + = number_with_delimiter(todos_pending_count) + - todo_done_active = ('active' if params[:state] == 'done') + %li{class: "todos-done #{todo_done_active}"} + = link_to todos_filter_path(state: 'done') do + %span + Done + %span.badge + = number_with_delimiter(todos_done_count) - .nav-controls - - if @todos.any?(&:pending?) - = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do - Mark all as done - = icon('spinner spin') + .nav-controls + - if @todos.any?(&:pending?) + = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do + Mark all as done + = icon('spinner spin') -.todos-filters - .row-content-block.second-block - = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do - .filter-item.inline - - if params[:project_id].present? - = hidden_field_tag(:project_id, params[:project_id]) - = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', - placeholder: 'Search projects', data: { data: todo_projects_options } }) - .filter-item.inline - - if params[:author_id].present? - = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', - placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) - .filter-item.inline - - if params[:type].present? - = hidden_field_tag(:type, params[:type]) - = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', - data: { data: todo_types_options } }) - .filter-item.inline.actions-filter - - if params[:action_id].present? - = hidden_field_tag(:action_id, params[:action_id]) - = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', - data: { data: todo_actions_options }}) - .pull-right - .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_created - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort - %li - = link_to todos_filter_path(sort: sort_value_priority) do - = sort_title_priority - = link_to todos_filter_path(sort: sort_value_recently_created) do + .todos-filters + .row-content-block.second-block + = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do + .filter-item.inline + - if params[:project_id].present? + = hidden_field_tag(:project_id, params[:project_id]) + = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', + placeholder: 'Search projects', data: { data: todo_projects_options } }) + .filter-item.inline + - if params[:author_id].present? + = hidden_field_tag(:author_id, params[:author_id]) + = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', + placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) + .filter-item.inline + - if params[:type].present? + = hidden_field_tag(:type, params[:type]) + = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', + data: { data: todo_types_options } }) + .filter-item.inline.actions-filter + - if params[:action_id].present? + = hidden_field_tag(:action_id, params[:action_id]) + = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', + data: { data: todo_actions_options }}) + .pull-right + .dropdown.inline.prepend-left-10 + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else = sort_title_recently_created - = link_to todos_filter_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort + %li + = link_to todos_filter_path(sort: sort_value_priority) do + = sort_title_priority + = link_to todos_filter_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to todos_filter_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created .prepend-top-default @@ -78,5 +79,29 @@ %ul.content-list.todos-list = render group[1] = paginate @todos, theme: "gitlab" + - elsif current_user.todos.any? + .todos-all-done + = render "shared/empty_states/todos_all_done.svg" + %h4.text-center + Good job! Looks like you don't have any todos left. + %p.text-center + Are you looking for things to do? Take a look at + = succeed "," do + = link_to "the opened issues", issues_dashboard_path + contribute to + = link_to "merge requests", merge_requests_dashboard_path + or mention someone in a comment to assign a new todo automatically. - else - .nothing-here-block You're all done! + .todos-empty + .todos-empty-hero + = render "shared/empty_states/todos_empty.svg" + .todos-empty-content + %h4 + Todos let you see what you should do next. + %p + When an issue or merge request is assigned to you, or when you + %strong + @mention + in a comment, this will trigger a new item in your todo list, automatically. + %p + You will always know what to work on next. diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index c766370d5a0..f84ac37fa8f 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -8,7 +8,8 @@ .form-group .col-sm-offset-2.col-sm-10 - = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' + .image-container.s160 + = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' %p.light - if @group.avatar? You can change your group avatar here diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 70783a63409..45325d6bc4b 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -13,7 +13,7 @@ .other-labels - if @labels.present? %ul.content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', collection: @labels, as: :label + = render partial: 'shared/label', subject: @group, collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - else .nothing-here-block diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index fab61f447c2..275581b3af8 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -6,7 +6,8 @@ .cover-block.groups-cover-block %div{ class: container_class } - = image_tag group_icon(@group), class: "avatar group-avatar s70 avatar-tile" + .image-container.s70.group-avatar + = image_tag group_icon(@group), class: "avatar s70 avatar-tile" .group-info .cover-title %h1 diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index d16bd61b779..070ed90da6d 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -461,7 +461,7 @@ .panel-body = lorem - %h2#alert Alerts + %h2#alerts Alerts .row .col-md-6 diff --git a/app/views/kaminari/gitlab/_gap.html.haml b/app/views/kaminari/gitlab/_gap.html.haml index 80ca30f36e6..889514c4755 100644 --- a/app/views/kaminari/gitlab/_gap.html.haml +++ b/app/views/kaminari/gitlab/_gap.html.haml @@ -4,6 +4,6 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li{class: "page"} - %span.page.gap +%li + %span.gap = raw(t 'views.pagination.truncate') diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml index 522e4d1d05f..750aed8f329 100644 --- a/app/views/kaminari/gitlab/_page.html.haml +++ b/app/views/kaminari/gitlab/_page.html.haml @@ -6,5 +6,5 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li{class: "page#{' active' if page.current?}"} +%li{class: "page#{' active' if page.current?}#{' sibling' if page.next? || page.prev?}"} = link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil} diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 7faa8bded86..7a9859262f7 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -29,10 +29,6 @@ = icon('bell fw') %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } = todos_pending_count - - if current_user.can_create_project? - %li - = link_to new_project_path, title: 'New project', aria: { label: "New project" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do - = icon('plus fw') - if Gitlab::Sherlock.enabled? %li = link_to sherlock_transactions_path, title: 'Sherlock Transactions', @@ -48,6 +44,8 @@ = link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username } %li = link_to "Profile Settings", profile_path, aria: { label: "Profile Settings" } + %li + = link_to "Help", help_path, aria: { label: "Help" } %li.divider %li = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" } diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 67f558c854b..a0356feef95 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,49 +1,38 @@ -%ul.nav.nav-sidebar - = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do - = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do - %span - Projects - = nav_link(controller: :todos) do - = link_to dashboard_todos_path, title: 'Todos' do - %span - Todos - %span.count= number_with_delimiter(todos_pending_count) - = nav_link(path: 'dashboard#activity') do - = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do - %span - Activity - - if koding_enabled? - = nav_link(controller: :koding) do - = link_to koding_path, title: 'Koding' do +.nav-sidebar + .sidebar-header Across GitLab + %ul.nav + = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do + = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do %span - Koding - = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do - = link_to dashboard_groups_path, title: 'Groups' do - %span - Groups - = nav_link(controller: 'dashboard/milestones') do - = link_to dashboard_milestones_path, title: 'Milestones' do - %span - Milestones - = nav_link(path: 'dashboard#issues') do - = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do - %span - Issues - %span.count= number_with_delimiter(current_user.assigned_issues.opened.count) - = nav_link(path: 'dashboard#merge_requests') do - = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do - %span - Merge Requests - %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) - = nav_link(controller: 'dashboard/snippets') do - = link_to dashboard_snippets_path, title: 'Snippets' do - %span - Snippets - = nav_link(controller: :help) do - = link_to help_path, title: 'Help' do - %span - Help - = nav_link(html_options: {class: profile_tab_class}) do - = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do - %span - Profile Settings + Projects + = nav_link(path: 'dashboard#activity') do + = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do + %span + Activity + - if koding_enabled? + = nav_link(controller: :koding) do + = link_to koding_path, title: 'Koding' do + %span + Koding + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do + = link_to dashboard_groups_path, title: 'Groups' do + %span + Groups + = nav_link(controller: 'dashboard/milestones') do + = link_to dashboard_milestones_path, title: 'Milestones' do + %span + Milestones + = nav_link(path: 'dashboard#issues') do + = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do + %span + Issues + %span.count= number_with_delimiter(current_user.assigned_issues.opened.count) + = nav_link(path: 'dashboard#merge_requests') do + = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do + %span + Merge Requests + %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) + = nav_link(controller: 'dashboard/snippets') do + = link_to dashboard_snippets_path, title: 'Snippets' do + %span + Snippets diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index d3987fc9c4f..e67b66d1fff 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,7 +1,8 @@ - empty_repo = @project.empty_repo? .project-home-panel.text-center{ class: ("empty-project" if empty_repo) } %div{ class: container_class } - = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70 avatar-tile') + .image-container.s70.project-avatar + = project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile') %h1.project-title = @project.name %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)} diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index ba1502c97b6..f7071051efc 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -11,7 +11,9 @@ .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) }" } - {{ list.title }} + %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 }} diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index c6d718a1cd1..8fce702314c 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -7,8 +7,11 @@ ":issue-link-base" => "issueLinkBase", ":disabled" => "disabled", "track-by" => "id" } - %li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id }", - ":index" => "index" } + %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", @@ -18,6 +21,11 @@ %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)", @@ -26,8 +34,3 @@ ":title" => "label.description", data: { container: 'body' } } {{ label.title }} - %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 } diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml new file mode 100644 index 00000000000..f0c0c6953e0 --- /dev/null +++ b/app/views/projects/boards/components/_sidebar.html.haml @@ -0,0 +1,23 @@ +%board-sidebar{ "inline-template" => true, + ":current-user" => "#{current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) if current_user}" } + %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" } + .issuable-sidebar + .block.issuable-sidebar-header + %span.issuable-header-text.hide-collapsed.pull-left + %strong + {{ issue.title }} + %br/ + %span + = precede "#" do + {{ issue.id }} + %a.gutter-toggle.pull-right{ role: "button", + href: "#", + "@click.prevent" => "closeSidebar", + "aria-label" => "Toggle sidebar" } + = custom_icon("icon_close", size: 15) + .js-issuable-update + = render "projects/boards/components/sidebar/assignee" + = render "projects/boards/components/sidebar/milestone" + = render "projects/boards/components/sidebar/due_date" + = render "projects/boards/components/sidebar/labels" + = render "projects/boards/components/sidebar/notifications" diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml new file mode 100644 index 00000000000..604e13858d1 --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -0,0 +1,40 @@ +.block.assignee + .title.hide-collapsed + Assignee + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value.hide-collapsed + %span.assign-yourself.no-value{ "v-if" => "!issue.assignee" } + No assignee + - if can?(current_user, :admin_issue, @project) + \- + %a.js-assign-yourself{ href: "#" } + assign yourself + %a.author_link.bold{ ":href" => "'#{root_url}' + issue.assignee.username", + "v-if" => "issue.assignee" } + %img.avatar.avatar-inline.s32{ ":src" => "issue.assignee.avatar", + width: "32" } + %span.author + {{ issue.assignee.name }} + %span.username + = precede "@" do + {{ issue.assignee.username }} + - if can?(current_user, :admin_issue, @project) + .selectbox.hide-collapsed + %input{ type: "hidden", + name: "issue[assignee_id]", + id: "issue_assignee_id", + ":value" => "issue.assignee.id", + "v-if" => "issue.assignee" } + .dropdown + %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true" }, + ":data-issuable-id" => "issue.id", + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } + Select assignee + = icon("chevron-down") + .dropdown-menu.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author + = dropdown_title("Assign to") + = dropdown_filter("Search users") + = dropdown_content + = dropdown_loading diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml new file mode 100644 index 00000000000..c7da1d0d4ac --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml @@ -0,0 +1,32 @@ +.block.due_date + .title + Due date + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value + .value-content + %span.no-value{ "v-if" => "!issue.dueDate" } + No due date + %span.bold{ "v-if" => "issue.dueDate" } + {{ issue.dueDate | due-date }} + - if can?(current_user, :admin_issue, @project) + %span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" } + \- + %a.js-remove-due-date{ href: "#", role: "button" } + remove due date + - if can?(current_user, :admin_issue, @project) + .selectbox + %input{ type: "hidden", + name: "issue[due_date]", + ":value" => "issue.dueDate" } + .dropdown + %button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button', + data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" }, + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } + %span.dropdown-toggle-text Due date + = icon('chevron-down') + .dropdown-menu.dropdown-menu-due-date + = dropdown_title('Due date') + = dropdown_content do + .js-due-date-calendar diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml new file mode 100644 index 00000000000..ce68e5e1998 --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_labels.html.haml @@ -0,0 +1,30 @@ +.block.labels + .title + Labels + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value.issuable-show-labels + %span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" } + None + %a{ href: "#", + "v-for" => "label in issue.labels" } + %span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } + {{ label.title }} + - if can?(current_user, :admin_issue, @project) + .selectbox + %input{ type: "hidden", + name: "issue[label_names][]", + "v-for" => "label in issue.labels", + ":value" => "label.id" } + .dropdown + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button", + data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json), namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) }, + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } + %span.dropdown-toggle-text + Label + = icon('chevron-down') + .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable + = render partial: "shared/issuable/label_page_default" + - if can? current_user, :admin_label, @project and @project + = render partial: "shared/issuable/label_page_create" diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml new file mode 100644 index 00000000000..3cd20d1c0f7 --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -0,0 +1,28 @@ +.block.milestone + .title + Milestone + = icon("spinner spin", class: "block-loading") + - if can?(current_user, :admin_issue, @project) + = link_to "Edit", "#", class: "edit-link pull-right" + .value + %span.no-value{ "v-if" => "!issue.milestone" } + None + %span.bold.has-tooltip{ "v-if" => "issue.milestone" } + {{ issue.milestone.title }} + - if can?(current_user, :admin_issue, @project) + .selectbox + %input{ type: "hidden", + ":value" => "issue.milestone.id", + name: "issue[milestone_id]", + "v-if" => "issue.milestone" } + .dropdown + %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" }, + ":data-issuable-id" => "issue.id", + ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } + Milestone + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-selectable + = dropdown_title("Assignee milestone") + = dropdown_filter("Search milestones") + = dropdown_content + = dropdown_loading diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml new file mode 100644 index 00000000000..21c9563e9db --- /dev/null +++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml @@ -0,0 +1,11 @@ +- if current_user + .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" } + .title + Notifications + %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } + {{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }} + .subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" } + .unsubscribed{ "v-show" => "!issue.subscribed" } + You're not receiving notifications from this thread. + .subscribed{ "v-show" => "issue.subscribed" } + You're receiving notifications because you're subscribed to this thread. diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 885f8e34b55..29c9a43a0c1 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -10,7 +10,9 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, data: board_data } - .boards-app-loading.text-center{ "v-if" => "loading" } - = icon("spinner spin") - = render "projects/boards/components/board" +#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" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 885f8e34b55..29c9a43a0c1 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -10,7 +10,9 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, data: board_data } - .boards-app-loading.text-center{ "v-if" => "loading" } - = icon("spinner spin") - = render "projects/boards/components/board" +#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" diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 4480b2f22c3..9135cee8364 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,4 +1,4 @@ -- commit = @repository.commit(branch.target) +- commit = @repository.commit(branch.dereferenced_target) - bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0 - diverging_commit_counts = @repository.diverging_commit_counts(branch) - number_commits_behind = diverging_commit_counts[:behind] @@ -31,7 +31,12 @@ = render 'projects/buttons/download', project: @project, ref: branch.name - if can?(current_user, :push_code, @project) - = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: "btn btn-remove remove-row has-tooltip #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do + = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), + class: "btn btn-remove remove-row #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", + method: :delete, + data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, + remote: true, + "aria-label" => "Delete branch" do = icon("trash-o") - if branch.name != @repository.root_ref diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index 51b5bd9db42..3f2ce7377fd 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,16 +1,19 @@ .content-block.build-header - = ci_status_with_icon(@build.status) - Build - %strong ##{@build.id} - for commit - = link_to ci_status_path(@build.pipeline) do - %strong= @build.pipeline.short_sha - from - = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do - %code - = @build.ref - - if @build.user - = render "user" - = time_ago_with_tooltip(@build.created_at) + .header-content + = ci_status_with_icon(@build.status) + Build + %strong ##{@build.id} + for commit + = link_to ci_status_path(@build.pipeline) do + %strong= @build.pipeline.short_sha + from + = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do + %code + = @build.ref + - if @build.user + = render "user" + = time_ago_with_tooltip(@build.created_at) + - if can?(current_user, :update_build, @build) && @build.retryable? + = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted pull-right', method: :post %button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" } = icon('angle-double-left') diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index b1053028279..28f519f11b2 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -44,7 +44,7 @@ .title Build details - if can?(current_user, :update_build, @build) && @build.retryable? - = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post + = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post - if @build.merge_request %p.build-detail-row %span.build-light-text Merge Request: diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 29d549a60f5..27da86b9efe 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -5,10 +5,10 @@ = custom_icon('icon_fork') %span Fork - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn has-tooltip' do + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn' do = custom_icon('icon_fork') %span Fork %div.count-with-arrow %span.arrow - = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count has-tooltip' do + = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count' do = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 311583037e5..12d35101770 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,5 +1,5 @@ - if current_user - = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do + = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do - if current_user.starred?(@project) = icon('star') %span.starred Unstar diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 55965172d3f..93dca81e6f9 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,14 +1,13 @@ - is_playable = subject.playable? && can?(current_user, :update_build, @project) - if is_playable = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.pipeline-graph', placement: 'bottom' } do - = render_status_with_link('build', 'play') + = ci_icon_for_status('play') .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } do %span.ci-status-icon - = render_status_with_link('build', subject.status) + = ci_icon_for_status(subject.status) .ci-status-text= subject.name - else %span.ci-status-icon - = render_status_with_link('build', subject.status) - = ci_icon_for_status(subject.status) + = ci_icon_for_status(subject.status) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index c6f359f5679..840f468dc05 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -5,10 +5,7 @@ %tr.commit %td.commit-link = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do - - if defined?(status_icon_only) && status_icon_only - = ci_icon_for_status(status) - - else - = ci_status_with_icon(status) + = ci_status_with_icon(status) %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do @@ -40,7 +37,7 @@ %p.commit-title - if commit = pipeline.commit = author_avatar(commit, size: 20) - = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message" + = link_to_gfm truncate(commit.title, length: 60, escape: false), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message" - else Cant find HEAD commit for this branch diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 6c82a4e5600..d8c95376b94 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,8 +1,23 @@ .commit-info-row.commit-info-row-header - %span.hidden-xs Authored by + %span.hidden-xs.hidden-sm Commit + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace js-details-short" + = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do + %span.text-expander + \... + %span.js-details-content.hide + = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace hidden-xs hidden-sm" + = clipboard_button(clipboard_text: @commit.id) + %span.hidden-xs authored + #{time_ago_with_tooltip(@commit.authored_date)} + %span by + = author_avatar(@commit, size: 24) %strong = commit_author_link(@commit, avatar: true, size: 24) - #{time_ago_with_tooltip(@commit.authored_date)} + - if @commit.different_committer? + %span.light Committed by + %strong + = commit_committer_link(@commit, avatar: true, size: 24) + #{time_ago_with_tooltip(@commit.committed_date)} .pull-right.commit-action-buttons - if defined?(@notes_count) && @notes_count > 0 @@ -33,42 +48,35 @@ %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) %li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) -- if @commit.different_committer? - .commit-info-row - %span.light Committed by - %strong - = commit_committer_link(@commit, avatar: true, size: 24) - #{time_ago_with_tooltip(@commit.committed_date)} - -.commit-info-row - %span.hidden-xs.hidden-sm Commit - = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace hidden-xs hidden-sm" - = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace visible-xs-inline visible-sm-inline" - = clipboard_button(clipboard_text: @commit.id) - %span.cgray= pluralize(@commit.parents.count, "parent") - - @commit.parents.each do |parent| - = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" - - %span.commit-info.branches - %i.fa.fa-spinner.fa-spin - -- if @commit.status - .commit-info-row - Builds for - = pluralize(@commit.pipelines.count, 'pipeline') - = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do - = ci_icon_for_status(@commit.status) - %span.ci-status-label - = ci_label_for_status(@commit.status) - in - = time_interval_in_words @commit.pipelines.total_duration - -.commit-box.content-block +.commit-box %h3.commit-title = markdown(@commit.title, pipeline: :single_line, author: @commit.author) - if @commit.description.present? %pre.commit-description = preserve(markdown(@commit.description, pipeline: :single_line, author: @commit.author)) +.commit-info-widget + .widget-row.branch-info + .icon-container.commit-icon + = custom_icon("icon_commit") + %span.cgray= pluralize(@commit.parents.count, "parent") + - @commit.parents.each do |parent| + = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" + %span.commit-info.branches + %i.fa.fa-spinner.fa-spin + + - if @commit.status + .widget-row.pipeline-info + .icon-container + = ci_icon_for_status(@commit.status) + Pipeline + = link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + for + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" + %span.ci-status-label + = ci_label_for_status(@commit.status) + in + = time_interval_in_words @commit.pipelines.total_duration + :javascript $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index f2d71fa6989..18daa2ee693 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,7 +1,7 @@ - group_status = CommitStatus.where(id: subject).status %button.dropdown-menu-toggle.has-tooltip{ type: 'button', data: { toggle: 'dropdown', title: "#{name} - #{group_status}" } } %span.ci-status-icon - = render_status_with_link('build', group_status) + = ci_icon_for_status(group_status) %span.ci-status-text = name %span.badge= subject.size diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index ac451441eec..2dc91a9b762 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -12,4 +12,4 @@ %th Stages %th %th - = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, show_commit: false + = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml index f9d7eac3542..077b2d2725b 100644 --- a/app/views/projects/commit/builds.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -3,8 +3,7 @@ = render "projects/commits/head" %div{ class: container_class } - .prepend-top-default - = render "commit_box" + = render "commit_box" = render "ci_menu" = render "builds" diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index d85d6729a81..8233e26e4e7 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -1,7 +1,6 @@ - page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits" -.prepend-top-default - = render "commit_box" += render "commit_box" = render "ci_menu" = render "pipelines_list", pipelines: @ci_pipelines diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index cebf58d63df..b8c64d1f13e 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -4,8 +4,7 @@ = render "projects/commits/head" %div{ class: container_class } - .prepend-top-default - = render "commit_box" + = render "commit_box" - if @commit.status = render "ci_menu" - else diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index b647882efa0..247d612ba6f 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -1,5 +1,9 @@ - @no_container = true - page_title "Cycle Analytics" + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('cycle_analytics/cycle_analytics_bundle.js') + = render "projects/pipelines/head" #cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) }} diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 257e0a855bd..8f4f9ad4a80 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -8,7 +8,6 @@ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ - = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-file-option') - if editable_diff?(diff_file) - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {} = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index a6a2e5690b5..73993f35b39 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -21,6 +21,8 @@ - if diff_file.deleted_file deleted + = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy filename to clipboard') + - if diff_file.mode_changed? %small = "#{diff_file.a_mode} → #{diff_file.b_mode}" diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 28aad3f4725..78aa9fb7391 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,5 +1,5 @@ / Side-by-side diff view -%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data } +%div.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data } %table - last_line = 0 - diff_file.parallel_diff_lines.each do |line| diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 30473d14b9b..ff6a899b92a 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -118,7 +118,8 @@ Project avatar .form-group - if @project.avatar? - = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160') + .image-container.s160 + = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160') %p.light - if @project.avatar_in_git Project avatar in repository: #{ @project.avatar_in_git } diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index c45b73e4225..1c457244a7a 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -2,9 +2,9 @@ - if subject.target_url = link_to subject.target_url do %span.ci-status-icon - = render_status_with_link('commit status', subject.status) + = ci_icon_for_status(subject.status) %span.ci-status-text= subject.name - else %span.ci-status-icon - = render_status_with_link('commit status', subject.status) + = ci_icon_for_status(subject.status) %span.ci-status-text= subject.name diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index 44683c8bcdb..1892ebb512f 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -4,7 +4,7 @@ %ul.unstyled-list.related-merge-requests - @related_branches.each do |branch| %li - - target = @project.repository.find_branch(branch).target + - target = @project.repository.find_branch(branch).dereferenced_target - pipeline = @project.pipeline_for(branch, target.sha) if target - if pipeline %span.related-branch-ci-status diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index f135bf6f6b4..05a8475dcd6 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -22,14 +22,14 @@ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - if @prioritized_labels.present? - = render partial: 'shared/label', collection: @prioritized_labels, as: :label + = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label .other-labels - if can?(current_user, :admin_label, @project) %h5{ class: ('hide' if hide) } Other Labels %ul.content-list.manage-labels-list.js-other-labels - if @labels.present? - = render partial: 'shared/label', collection: @labels, as: :label + = render partial: 'shared/label', subject: @project, collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - if @labels.blank? .nothing-here-block diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index bebf0ccd54d..96221a20502 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -5,33 +5,59 @@ %h4.prepend-top-0 = page_title .col-lg-9 - %h5.prepend-top-0 - Pipelines = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f| %fieldset.builds-feature - unless @repository.gitlab_ci_yml .form-group %p Pipelines need to be configured before you can begin using Continuous Integration. = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' + %hr + .form-group.append-bottom-default + = f.label :runners_token, "Runner token", class: 'label-light' + = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89' + %p.help-block The secure token used by the Runner to checkout the project + + %hr .form-group - %p Get recent application code using the following command: + %h5.prepend-top-0 + Git strategy for pipelines + %p + Choose between <code>clone</code> or <code>fetch</code> to get the recent application code + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy') .radio = f.label :build_allow_git_fetch_false do = f.radio_button :build_allow_git_fetch, 'false' %strong git clone %br - %span.descr Slower but makes sure you have a clean dir before every build + %span.descr + Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job .radio = f.label :build_allow_git_fetch_true do = f.radio_button :build_allow_git_fetch, 'true' %strong git fetch %br - %span.descr Faster + %span.descr + Faster as it re-uses the project workspace (falling back to clone if it doesn't exist) + %hr .form-group = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light' = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0' - %p.help-block per build in minutes + %p.help-block + Per job in minutes. If a job passes this threshold, it will be marked as failed. + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout') + + %hr + .form-group + .checkbox + = f.label :public_builds do + = f.check_box :public_builds + %strong Public pipelines + .help-block + Allow everyone to access pipelines for public and internal projects + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines') + + %hr .form-group = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light' .input-group @@ -39,8 +65,9 @@ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered' %span.input-group-addon / %p.help-block - We will use this regular expression to find test coverage output in build trace. - Leave blank if you want to disable this feature + A regular expression that will be used to find the test coverage + output in the build trace. Leave blank to disable + = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing') .bs-callout.bs-callout-info %p Below are examples of regex for existing tools: %ul @@ -57,21 +84,9 @@ gcovr (C/C++) - %code ^TOTAL.*\s+(\d+\%)$ %li - tap --coverage-report=text-summary (Node.js) - + tap --coverage-report=text-summary (NodeJS) - %code ^Statements\s*:\s*([^%]+) - .form-group - .checkbox - = f.label :public_builds do - = f.check_box :public_builds - %strong Public builds - .help-block Allow everyone to access builds traces for Public and Internal projects - - .form-group.append-bottom-default - = f.label :runners_token, "Runners token", class: 'label-light' - = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89' - %p.help-block The secure token used to checkout project. - = f.submit 'Save changes', class: "btn btn-save" %hr diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 05fccb4f976..c42641afea0 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -1,4 +1,4 @@ -- commit = @repository.commit(tag.target) +- commit = @repository.commit(tag.dereferenced_target) - release = @releases.find { |release| release.tag == tag.name } %li %div diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 3480800369a..c367ae336db 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,6 +1,9 @@ %ul.nav-links.event-filter.scrolling-tabs = event_filter_link EventFilter.all, 'All' - = event_filter_link EventFilter.push, 'Push events' - = event_filter_link EventFilter.merged, 'Merge events' - = event_filter_link EventFilter.comments, 'Comments' + - if event_filter_visible(:repository) + = event_filter_link EventFilter.push, 'Push events' + - if event_filter_visible(:merge_requests) + = event_filter_link EventFilter.merged, 'Merge events' + - if event_filter_visible(:issues) + = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 40c8d2af226..6ccdef0df46 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -1,6 +1,7 @@ - label_css_id = dom_id(label) -- open_issues_count = label.open_issues_count(current_user, @project) -- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) +- open_issues_count = label.open_issues_count(current_user) +- open_merge_requests_count = label.open_merge_requests_count(current_user) +- subject = local_assigns[:subject] %li{id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label @@ -12,10 +13,10 @@ .dropdown-menu.dropdown-menu-align-right %ul %li - = link_to_label(label, subject: @project, type: :merge_request) do + = link_to_label(label, subject: subject, type: :merge_request) do = pluralize open_merge_requests_count, 'merge request' %li - = link_to_label(label, subject: @project) do + = link_to_label(label, subject: subject) do = pluralize open_issues_count, 'open issue' - if current_user %li.label-subscription{ data: toggle_subscription_data(label) } @@ -28,9 +29,9 @@ = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_label(label, subject: @project, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do = pluralize open_merge_requests_count, 'merge request' - = link_to_label(label, subject: @project, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do = pluralize open_issues_count, 'open issue' - if current_user diff --git a/app/views/shared/empty_states/_todos_all_done.svg b/app/views/shared/empty_states/_todos_all_done.svg new file mode 100644 index 00000000000..94b5c2e0ea0 --- /dev/null +++ b/app/views/shared/empty_states/_todos_all_done.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 293 216"><g fill="none" fill-rule="evenodd"><g transform="rotate(-5 211.388 -693.89)"><rect width="163.6" height="200" x=".2" fill="#FFF" stroke="#EEE" stroke-width="3" stroke-linecap="round" stroke-dasharray="6 9" rx="6"/><g transform="translate(24 38)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/></g><g transform="translate(24 83)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g><g transform="translate(24 130)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g></g><path fill="#FFCE29" d="M30 11l-1.8 4-2-4-4-1.8 4-2 2-4 2 4 4 2M286 60l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8M263 97l-2 4-2-4-4-2 4-2 2-4 2 4 4 2M12 85l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8"/></g></svg> diff --git a/app/views/shared/empty_states/_todos_empty.svg b/app/views/shared/empty_states/_todos_empty.svg new file mode 100644 index 00000000000..b1e661268fb --- /dev/null +++ b/app/views/shared/empty_states/_todos_empty.svg @@ -0,0 +1,110 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 284 337" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <rect id="a" width="180" height="220" x="66.2" y="74.4" rx="6"/> + <mask id="l" width="180" height="220" x="0" y="0" fill="#fff"> + <use xlink:href="#a"/> + </mask> + <rect id="b" width="180" height="220" rx="6"/> + <mask id="m" width="180" height="220" x="0" y="0" fill="#fff"> + <use xlink:href="#b"/> + </mask> + <rect id="c" width="28" height="28" rx="4"/> + <mask id="n" width="28" height="28" x="0" y="0" fill="#fff"> + <use xlink:href="#c"/> + </mask> + <rect id="d" width="28" height="28" rx="4"/> + <mask id="o" width="28" height="28" x="0" y="0" fill="#fff"> + <use xlink:href="#d"/> + </mask> + <circle id="e" cx="21.5" cy="21.5" r="21.5"/> + <mask id="p" width="43" height="43" x="0" y="0" fill="#fff"> + <use xlink:href="#e"/> + </mask> + <circle id="f" cx="26.5" cy="26.5" r="26.5"/> + <mask id="q" width="53" height="53" x="0" y="0" fill="#fff"> + <use xlink:href="#f"/> + </mask> + <circle id="g" cx="9.5" cy="4.5" r="4.5"/> + <mask id="r" width="13" height="13" x="-2" y="-2"> + <path fill="#fff" d="M3-2h13v13H3z"/> + <use xlink:href="#g"/> + </mask> + <circle id="h" cx="26.5" cy="26.5" r="26.5"/> + <mask id="s" width="53" height="53" x="0" y="0" fill="#fff"> + <use xlink:href="#h"/> + </mask> + <circle id="i" cx="21.5" cy="21.5" r="21.5"/> + <mask id="t" width="43" height="43" x="0" y="0" fill="#fff"> + <use xlink:href="#i"/> + </mask> + <path id="j" d="M18 38h15c10.5 0 19-8.5 19-19S43.5 0 33 0H19C8.5 0 0 8.5 0 19c0 6.3 3 12 7.8 15.3l5.2 9c.6 1 1.4 1 2 0l3-5.3z"/> + <mask id="u" width="52" height="44" x="0" y="0" fill="#fff"> + <use xlink:href="#j"/> + </mask> + <circle id="k" cx="18.5" cy="18.5" r="18.5"/> + <mask id="v" width="37" height="37" x="0" y="0" fill="#fff"> + <use xlink:href="#k"/> + </mask> + </defs> + <g fill="none" fill-rule="evenodd" transform="translate(-6 -4)"> + <use stroke="#EEE" stroke-width="6" mask="url(#l)" transform="rotate(-5 156.245 184.425)" xlink:href="#a"/> + <g transform="rotate(5 -707.333 618.042)"> + <use fill="#FFF" stroke="#EEE" stroke-width="6" mask="url(#m)" xlink:href="#b"/> + <g transform="translate(29 24)"> + <path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/> + <path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/> + <rect width="86" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/> + <rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/> + </g> + <g transform="translate(29 69)"> + <path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/> + <path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/> + <rect width="86" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/> + <rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/> + </g> + <g transform="translate(28 160)"> + <use stroke="#E5E5E5" stroke-width="6" mask="url(#n)" opacity=".7" xlink:href="#c"/> + <rect width="26" height="3" x="41" y="7" fill="#ECECEC" rx="1.5"/> + <rect width="43" height="3" x="41" y="17" fill="#ECECEC" rx="1.5"/> + </g> + <g transform="translate(28 116)"> + <use stroke="#E5E5E5" stroke-width="6" mask="url(#o)" xlink:href="#d"/> + <rect width="86" height="3" x="41" y="7" fill="#E5E5E5" rx="1.5"/> + <rect width="43" height="3" x="41" y="17" fill="#E5E5E5" rx="1.5"/> + </g> + </g> + <g transform="rotate(-15 601.917 -782.362)"> + <use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#p)" xlink:href="#e"/> + <text fill="#6B4FBB" font-family="SourceSansPro-Black, Source Sans Pro" font-size="20" font-weight="700" letter-spacing="-.1"> + <tspan x="12" y="27">@</tspan> + </text> + </g> + <g transform="rotate(15 -686.59 1035.907)"> + <use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#q)" xlink:href="#f"/> + <path fill="#FC6D26" d="M26.5 38.2c3.3 0 9.5-2.5 9.5-9.6 0-7-2.4-6.6-9.5-6.6-7 0-9.5-.4-9.5 6.6s6.2 9.6 9.5 9.6z"/> + <g transform="translate(17 14)"> + <use fill="#FC6D26" xlink:href="#g"/> + <use stroke="#FFF" stroke-width="4" mask="url(#r)" xlink:href="#g"/> + </g> + </g> + <g transform="rotate(15 -85.125 65.185)"> + <use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#s)" xlink:href="#h"/> + <path fill="#6B4FBB" d="M24 18.5c0-1.4 1-2.5 2.5-2.5 1.4 0 2.5 1 2.5 2.5v9c0 1.4-1 2.5-2.5 2.5-1.4 0-2.5-1-2.5-2.5v-9zM26.5 37c1.4 0 2.5-1 2.5-2.5 0-1.4-1-2.5-2.5-2.5-1.4 0-2.5 1-2.5 2.5 0 1.4 1 2.5 2.5 2.5z"/> + </g> + <g transform="rotate(-15 716.492 78.873)"> + <use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#t)" xlink:href="#i"/> + <path fill="#FC6D26" d="M20 23v-3h3v3h-3zm0 3v1.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-2.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-3h-1.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-2.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h3v-1.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h2.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v3h1.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v2.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-3z"/> + </g> + <g transform="rotate(-15 129.114 -585.74)"> + <use stroke="#FDE5D8" stroke-width="6" mask="url(#u)" xlink:href="#j"/> + <circle cx="16" cy="20" r="2" fill="#FC6D26"/> + <circle cx="27" cy="20" r="2" fill="#FC6D26"/> + <circle cx="38" cy="20" r="2" fill="#FC6D26"/> + </g> + <g transform="rotate(-15 1254.8 -458.986)"> + <use stroke="#FDE5D8" stroke-width="6" mask="url(#v)" xlink:href="#k"/> + <path fill="#FC6D26" d="M10.6 19l2-2c.5-.5.5-1 0-1.5-.3-.4-1-.4-1.3 0l-2.8 2.8c-.2.2-.3.4-.3.7 0 .3 0 .5.3.7l2.8 2.8c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4l-2-2zm14.8 0l-2-2c-.5-.5-.5-1 0-1.5.3-.4 1-.4 1.3 0l2.8 2.8c.2.2.3.4.3.7 0 .3 0 .5-.3.7l-2.8 2.8c-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4l2-2z"/> + <rect width="2" height="7" x="17" y="15.1" fill="#FC6D26" opacity=".5" transform="rotate(15 18.002 18.64)" rx="1"/> + </g> + </g> +</svg> diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index dc4ee3074d2..562291a61df 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -24,7 +24,8 @@ %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} = visibility_level_icon(group.visibility_level, fw: false) - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + .image-container.s40 + = image_tag group_icon(group), class: "avatar s40 hidden-xs" .title = link_to group, class: 'group-name' do = group.name diff --git a/app/views/shared/icons/_icon_close.svg b/app/views/shared/icons/_icon_close.svg new file mode 100644 index 00000000000..9d62012518b --- /dev/null +++ b/app/views/shared/icons/_icon_close.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M9,7.5l5.83-5.91a.48.48,0,0,0,0-.69L14.11.15a.46.46,0,0,0-.68,0l-5.93,6L1.57.15a.46.46,0,0,0-.68,0L.15.9a.48.48,0,0,0,0,.69L6,7.5.15,13.41a.48.48,0,0,0,0,.69l.74.75a.46.46,0,0,0,.68,0l5.93-6,5.93,6a.46.46,0,0,0,.68,0l.74-.75a.48.48,0,0,0,0-.69Z"/></svg>
\ No newline at end of file diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index 3c03c220ddd..9e1b0379428 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -3,8 +3,9 @@ - assignee = issuable.assignee - issuable_type = issuable.class.table_name - base_url_args = [project.namespace.becomes(Namespace), project, issuable_type] +- can_update = can?(current_user, :"update_#{issuable.to_ability_name}", issuable) -%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) } +%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'ui-sort-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) } %span - if show_project_name %strong #{project.name} · diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index e8668048703..3d2122a159c 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -32,10 +32,11 @@ = link_to project_path(project), class: dom_class(project) do - if avatar .dash-project-avatar - - if use_creator_avatar - = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' - - else - = project_icon(project, alt: '', class: 'avatar project-avatar s40') + .image-container.s40 + - if use_creator_avatar + = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' + - else + = project_icon(project, alt: '', class: 'avatar project-avatar s40') %span.project-full-name %span.namespace-name - if project.namespace && !skip_namespace diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index f360fbb3d5d..78f253f9023 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,4 +1,5 @@ .clearfix - groups.each do |group| = link_to group, class: 'profile-groups-avatars inline', title: group.name do - = image_tag group_icon(group), class: 'avatar group-avatar s40' + .image-container.s40 + = image_tag group_icon(group), class: 'avatar group-avatar s40' diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb index 71b274e0c99..4dfa745fb50 100644 --- a/app/workers/project_cache_worker.rb +++ b/app/workers/project_cache_worker.rb @@ -9,6 +9,18 @@ class ProjectCacheWorker LEASE_TIMEOUT = 15.minutes.to_i + def self.lease_for(project_id) + Gitlab::ExclusiveLease. + new("project_cache_worker:#{project_id}", timeout: LEASE_TIMEOUT) + end + + # Overwrite Sidekiq's implementation so we only schedule when actually needed. + def self.perform_async(project_id) + # If a lease for this project is still being held there's no point in + # scheduling a new job. + super unless lease_for(project_id).exists? + end + def perform(project_id) if try_obtain_lease_for(project_id) Rails.logger. @@ -37,8 +49,6 @@ class ProjectCacheWorker end def try_obtain_lease_for(project_id) - Gitlab::ExclusiveLease. - new("project_cache_worker:#{project_id}", timeout: LEASE_TIMEOUT). - try_obtain + self.class.lease_for(project_id).try_obtain end end diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb index efb85eafd15..d973e662ff2 100644 --- a/app/workers/project_web_hook_worker.rb +++ b/app/workers/project_web_hook_worker.rb @@ -2,6 +2,8 @@ class ProjectWebHookWorker include Sidekiq::Worker include DedicatedSidekiqQueue + sidekiq_options retry: 4 + def perform(hook_id, data, hook_name) data = data.with_indifferent_access WebHook.find(hook_id).execute(data, hook_name) diff --git a/app/workers/remove_unreferenced_lfs_objects_worker.rb b/app/workers/remove_unreferenced_lfs_objects_worker.rb new file mode 100644 index 00000000000..b80f131d5f7 --- /dev/null +++ b/app/workers/remove_unreferenced_lfs_objects_worker.rb @@ -0,0 +1,8 @@ +class RemoveUnreferencedLfsObjectsWorker + include Sidekiq::Worker + include CronjobQueue + + def perform + LfsObject.destroy_unreferenced + end +end |