diff options
121 files changed, 765 insertions, 228 deletions
diff --git a/.eslintrc b/.eslintrc index e13f76b213c..9ab0145820d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,6 +15,7 @@ "filenames" ], "rules": { - "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"] + "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"], + "no-multiple-empty-lines": ["error", { "max": 1 }] } } diff --git a/.haml-lint.yml b/.haml-lint.yml index 7c8a9c4fd17..528f99d08d2 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -46,7 +46,7 @@ linters: max: 80 MultilinePipe: - enabled: false + enabled: true MultilineScript: enabled: true @@ -77,7 +77,7 @@ linters: - Style/WhileUntilModifier RubyComments: - enabled: false + enabled: true SpaceBeforeScript: enabled: true @@ -97,7 +97,7 @@ linters: enabled: true UnnecessaryInterpolation: - enabled: false + enabled: true UnnecessaryStringOutput: - enabled: false + enabled: true diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index c79f0230951..8b14191395b 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill'); var utils = require('./utils'); var DropDown = function(list) { + this.currentIndex = 0; this.hidden = true; this.list = list; this.items = []; @@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, { }, show: function() { - // debugger - this.list.style.display = 'block'; - this.hidden = false; + if (this.hidden) { + // debugger + this.list.style.display = 'block'; + this.currentIndex = 0; + this.hidden = false; + } }, hide: function() { - // debugger - this.list.style.display = 'none'; - this.hidden = true; + if (!this.hidden) { + // debugger + this.list.style.display = 'none'; + this.currentIndex = 0; + this.hidden = true; + } }, destroy: function() { @@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, { this.input = function input(e) { if(self.hasRemovedEvents) return; + self.list.show(); + var inputEvent = new CustomEvent('input.dl', { detail: { hook: self, @@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, { cancelable: true }); e.target.dispatchEvent(inputEvent); - self.list.show(); } this.keyup = function keyup(e) { @@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, { } function keyEvent(e, keyEventName){ + self.list.show(); + var keyEvent = new CustomEvent(keyEventName, { detail: { hook: self, @@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, { cancelable: true }); e.target.dispatchEvent(keyEvent); - self.list.show(); } this.events = this.events || {}; @@ -572,24 +581,43 @@ require('./window')(function(w){ module.exports = function(){ var currentKey; var currentFocus; - var currentIndex = 0; var isUpArrow = false; var isDownArrow = false; var removeHighlight = function removeHighlight(list) { - var listItems = list.list.querySelectorAll('li'); + var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0); + var listItemsTmp = []; for(var i = 0; i < listItems.length; i++) { - listItems[i].classList.remove('dropdown-active'); + var listItem = listItems[i]; + listItem.classList.remove('dropdown-active'); + + if (listItem.style.display !== 'none') { + listItemsTmp.push(listItem); + } } - return listItems; + return listItemsTmp; }; var setMenuForArrows = function setMenuForArrows(list) { var listItems = removeHighlight(list); - if(currentIndex>0){ - if(!listItems[currentIndex-1]){ - currentIndex = currentIndex-1; + if(list.currentIndex>0){ + if(!listItems[list.currentIndex-1]){ + list.currentIndex = list.currentIndex-1; + } + + if (listItems[list.currentIndex-1]) { + var el = listItems[list.currentIndex-1]; + var filterDropdownEl = el.closest('.filter-dropdown'); + el.classList.add('dropdown-active'); + + if (filterDropdownEl) { + var filterDropdownBottom = filterDropdownEl.offsetHeight; + var elOffsetTop = el.offsetTop - 30; + + if (elOffsetTop > filterDropdownBottom) { + filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom; + } + } } - listItems[currentIndex-1].classList.add('dropdown-active'); } }; @@ -597,13 +625,13 @@ require('./window')(function(w){ var list = e.detail.hook.list; removeHighlight(list); list.show(); - currentIndex = 0; + list.currentIndex = 0; isUpArrow = false; isDownArrow = false; }; var selectItem = function selectItem(list) { var listItems = removeHighlight(list); - var currentItem = listItems[currentIndex-1]; + var currentItem = listItems[list.currentIndex-1]; var listEvent = new CustomEvent('click.dl', { detail: { list: list, @@ -617,6 +645,8 @@ require('./window')(function(w){ var keydown = function keydown(e){ var typedOn = e.target; + var list = e.detail.hook.list; + var currentIndex = list.currentIndex; isUpArrow = false; isDownArrow = false; @@ -648,6 +678,7 @@ require('./window')(function(w){ if(isUpArrow){ currentIndex--; } if(isDownArrow){ currentIndex++; } if(currentIndex < 0){ currentIndex = 0; } + list.currentIndex = currentIndex; setMenuForArrows(e.detail.hook.list); }; diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index f20610b3811..f7fed0987a2 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -29,6 +29,7 @@ require('../window')(function(w){ init: function init(hook) { var self = this; var config = hook.config.droplabAjax; + this.hook = hook; if (!config || !config.endpoint || !config.method) { return; @@ -52,19 +53,26 @@ require('../window')(function(w){ this._loadUrlData(config.endpoint) .then(function(d) { if (config.loadingTemplate) { - var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]'); + var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); if (dataLoadingTemplate) { dataLoadingTemplate.outerHTML = self.listTemplate; } } - hook.list[config.method].call(hook.list, d); + + if (!self.hook.list.hidden) { + self.hook.list[config.method].call(self.hook.list, d); + } }).catch(function(e) { throw new droplabAjaxException(e.message || e); }); }, destroy: function() { + if (this.listTemplate) { + var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); + dynamicList.outerHTML = this.listTemplate; + } } }; }); @@ -76,4 +84,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -});
\ No newline at end of file +}); diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index af163f76851..86a08d0d01d 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -93,6 +93,7 @@ require('../window')(function(w){ self.hook.list.setData.call(self.hook.list, data); } self.notLoading(); + self.hook.list.currentIndex = 0; }); }, @@ -142,4 +143,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -});
\ No newline at end of file +}); diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 41a220831f9..9b40a3f20a4 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -6,6 +6,8 @@ require('../window')(function(w){ w.droplabFilter = { keydownWrapper: function(e){ + var hiddenCount = 0; + var dataHiddenCount = 0; var list = e.detail.hook.list; var data = list.data; var value = e.detail.hook.trigger.value.toLowerCase(); @@ -27,10 +29,22 @@ require('../window')(function(w){ }; } + dataHiddenCount = data.filter(function(o) { + return !o.droplab_hidden; + }).length; + matches = data.map(function(o) { return filterFunction(o, value); }); - list.render(matches); + + hiddenCount = matches.filter(function(o) { + return !o.droplab_hidden; + }).length; + + if (dataHiddenCount !== hiddenCount) { + list.render(matches); + list.currentIndex = 0; + } }, init: function init(hookInput) { @@ -57,4 +71,4 @@ module.exports = function(callback) { }; },{}]},{},[1])(1) -});
\ No newline at end of file +}); diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 9f24a6a4f88..3b003f6f661 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -3,7 +3,6 @@ //= require ./components/environment //= require ./vue_resource_interceptor - $(() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index 443ac222f70..eeab10fba17 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -84,7 +84,7 @@ let inputValue = input.value; // Replace all spaces inside quote marks with underscores // This helps with matching the beginning & end of a token:key - inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_')); + inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_')); // Get the right position for the word selected // Regex matches first space diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index ae19bb68360..8d62324b79f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -64,13 +64,26 @@ } checkForEnter(e) { + if (e.keyCode === 38 || e.keyCode === 40) { + const selectionStart = this.filteredSearchInput.selectionStart; + + e.preventDefault(); + this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart); + } + if (e.keyCode === 13) { + const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; + const dropdownEl = dropdown.element; + const activeElements = dropdownEl.querySelectorAll('.dropdown-active'); + e.preventDefault(); - // Prevent droplab from opening dropdown - this.dropdownManager.destroyDroplab(); + if (!activeElements.length) { + // Prevent droplab from opening dropdown + this.dropdownManager.destroyDroplab(); - this.search(); + this.search(); + } } } diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6 index c260ad03d47..e0ebd36a65c 100644 --- a/app/assets/javascripts/issues_bulk_assignment.js.es6 +++ b/app/assets/javascripts/issues_bulk_assignment.js.es6 @@ -61,7 +61,6 @@ return labels; } - /** * Will return only labels that were marked previously and the user has unmarked * @return {Array} Label IDs @@ -80,7 +79,6 @@ return result; } - /** * Simple form serialization, it will return just what we need * Returns key/value pairs from form data diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 6d57d31f380..7452879d9a3 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -159,5 +159,19 @@ if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); }; + + /** + this will take in the headers from an API response and normalize them + this way we don't run into production issues when nginx gives us lowercased header keys + */ + w.gl.utils.normalizeHeaders = (headers) => { + const upperCaseHeaders = {}; + + Object.keys(headers).forEach((e) => { + upperCaseHeaders[e.toUpperCase()] = headers[e]; + }); + + return upperCaseHeaders; + }; })(window); }).call(this); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 06a72efa21d..9db830a7ada 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -220,7 +220,6 @@ })(this)); }; - /* Increase @pollingInterval up to 120 seconds on every function call, if `shouldReset` has a truthy value, 'null' or 'undefined' the variable @@ -244,7 +243,6 @@ return this.initRefresh(); }; - Notes.prototype.handleCreateChanges = function(note) { if (typeof note === 'undefined') { return; @@ -294,7 +292,6 @@ } }; - /* Check if note does not exists on page */ @@ -307,7 +304,6 @@ return this.view === 'parallel'; }; - /* Render note in discussion area. @@ -358,7 +354,6 @@ return this.updateNotesCount(1); }; - /* Called in response the main target form has been successfully submitted. @@ -390,7 +385,6 @@ return form.find(".js-note-text").trigger("input"); }; - /* Shows the main form and does some setup on it. @@ -415,7 +409,6 @@ return this.parentTimeline = form.parents('.timeline'); }; - /* General note form setup. @@ -432,7 +425,6 @@ return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]); }; - /* Called in response to the new note form being submitted @@ -448,7 +440,6 @@ return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline); }; - /* Called in response to the new note form being submitted @@ -473,7 +464,6 @@ this.removeDiscussionNoteForm($form); }; - /* Called in response to the edit note form being submitted @@ -498,7 +488,6 @@ } }; - Notes.prototype.checkContentToAllowEditing = function($el) { var initialContent = $el.find('.original-note-content').text().trim(); var currentContent = $el.find('.note-textarea').val(); @@ -522,7 +511,6 @@ return isAllowed; }; - /* Called in response to clicking the edit note link @@ -551,7 +539,6 @@ this.putEditFormInPlace($target); }; - /* Called in response to clicking the edit note link @@ -596,7 +583,6 @@ return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note')); }; - /* Called in response to deleting a note of any kind. @@ -636,7 +622,6 @@ return this.updateNotesCount(-1); }; - /* Called in response to clicking the delete attachment link @@ -653,7 +638,6 @@ return note.find(".current-note-edit-form").remove(); }; - /* Called when clicking on the "reply" button for a diff line. @@ -673,7 +657,6 @@ return this.setupDiscussionNoteForm(replyLink, form); }; - /* Shows the diff or discussion form and does some setup on it. @@ -715,7 +698,6 @@ .addClass("discussion-form js-discussion-note-form"); }; - /* Called when clicking on the "add a comment" button on the side of a diff line. @@ -772,7 +754,6 @@ } }; - /* Called in response to "cancel" on a diff note form. @@ -806,7 +787,6 @@ return this.removeDiscussionNoteForm(form); }; - /* Called after an attachment file has been selected. @@ -821,7 +801,6 @@ return form.find(".js-attachment-filename").text(filename); }; - /* Called when the tab visibility changes */ diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6 index 1982142853a..9e19b1564dc 100644 --- a/app/assets/javascripts/vue_pipelines_index/store.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6 @@ -4,19 +4,15 @@ ((gl) => { const pageValues = (headers) => { - const normalizedHeaders = {}; - - Object.keys(headers).forEach((e) => { - normalizedHeaders[e.toUpperCase()] = headers[e]; - }); + const normalized = gl.utils.normalizeHeaders(headers); const paginationInfo = { - perPage: +normalizedHeaders['X-PER-PAGE'], - page: +normalizedHeaders['X-PAGE'], - total: +normalizedHeaders['X-TOTAL'], - totalPages: +normalizedHeaders['X-TOTAL-PAGES'], - nextPage: +normalizedHeaders['X-NEXT-PAGE'], - previousPage: +normalizedHeaders['X-PREV-PAGE'], + perPage: +normalized['X-PER-PAGE'], + page: +normalized['X-PAGE'], + total: +normalized['X-TOTAL'], + totalPages: +normalized['X-TOTAL-PAGES'], + nextPage: +normalized['X-NEXT-PAGE'], + previousPage: +normalized['X-PREV-PAGE'], }; return paginationInfo; diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index d957ec64654..4b05ec691a8 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -79,6 +79,16 @@ overflow: auto; } +%filter-dropdown-item-btn-hover { + background-color: $dropdown-hover-color; + color: $white-light; + text-decoration: none; + + .avatar { + border-color: $white-light; + } +} + .filter-dropdown-item { .btn { border: none; @@ -103,13 +113,7 @@ &:hover, &:focus { - background-color: $dropdown-hover-color; - color: $white-light; - text-decoration: none; - - .avatar { - border-color: $white-light; - } + @extend %filter-dropdown-item-btn-hover; } } @@ -131,6 +135,12 @@ } } +.filter-dropdown-item.dropdown-active { + .btn { + @extend %filter-dropdown-item-btn-hover; + } +} + .hint-dropdown { width: 250px; } diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index aa54ee07bdc..2aa0449c46e 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -3,7 +3,7 @@ module CompareHelper from.present? && to.present? && from != to && - project.feature_available?(:merge_requests, current_user) && + can?(current_user, :create_merge_request, project) && project.repository.branch_names.include?(from) && project.repository.branch_names.include?(to) end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 0d20c9092c4..46fa6fd9f6d 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -38,6 +38,14 @@ module Emails mail_answer_thread(@snippet, note_thread_options(recipient_id)) end + def note_personal_snippet_email(recipient_id, note_id) + setup_note_mail(note_id, recipient_id) + + @snippet = @note.noteable + @target_url = snippet_url(@note.noteable) + mail_answer_thread(@snippet, note_thread_options(recipient_id)) + end + private def note_target_url_options diff --git a/app/models/ability.rb b/app/models/ability.rb index fa8f8bc3a5f..ad6c588202e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -22,6 +22,17 @@ class Ability end end + # Given a list of users and a snippet this method returns the users that can + # read the given snippet. + def users_that_can_read_personal_snippet(users, snippet) + case snippet.visibility_level + when Snippet::INTERNAL, Snippet::PUBLIC + users + when Snippet::PRIVATE + users.include?(snippet.author) ? [snippet.author] : [] + end + end + # Returns an Array of Issues that can be read by the given user. # # issues - The issues to reduce down to those readable by the user. diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 90bd6490a02..a600f9c14c5 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -51,6 +51,10 @@ module CacheMarkdownField CACHING_CLASSES.map(&:constantize) end + def skip_project_check? + false + end + extend ActiveSupport::Concern included do @@ -112,7 +116,8 @@ module CacheMarkdownField invalidation_method = "#{html_field}_invalidated?".to_sym define_method(cache_method) do - html = Banzai::Renderer.cacheless_render_field(self, markdown_field) + options = { skip_project_check: skip_project_check? } + html = Banzai::Renderer.cacheless_render_field(self, markdown_field, options) __send__("#{html_field}=", html) true end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 8ab0401d288..ef2c1e5d414 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -49,7 +49,11 @@ module Mentionable self.class.mentionable_attrs.each do |attr, options| text = __send__(attr) - options = options.merge(cache_key: [self, attr], author: author) + options = options.merge( + cache_key: [self, attr], + author: author, + skip_project_check: skip_project_check? + ) extractor.analyze(text, options) end @@ -121,4 +125,8 @@ module Mentionable def cross_reference_exists?(target) SystemNoteService.cross_reference_exists?(target, local_reference) end + + def skip_project_check? + false + end end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 70740c76e43..4865c0a14b1 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -96,6 +96,11 @@ module Participable participants.merge(ext.users) - Ability.users_that_can_read_project(participants.to_a, project) + case self + when PersonalSnippet + Ability.users_that_can_read_personal_snippet(participants.to_a, self) + else + Ability.users_that_can_read_project(participants.to_a, project) + end end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index cd5b345bae5..6753504acff 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -865,9 +865,11 @@ class MergeRequest < ActiveRecord::Base paths: paths ) - active_diff_notes.each do |note| - service.execute(note) - Gitlab::Timeless.timeless(note, &:save) + transaction do + active_diff_notes.each do |note| + service.execute(note) + Gitlab::Timeless.timeless(note, &:save) + end end end diff --git a/app/models/note.rb b/app/models/note.rb index 0c1b05dabf2..bf090a0438c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -43,7 +43,8 @@ class Note < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true delegate :title, to: :noteable, allow_nil: true - validates :note, :project, presence: true + validates :note, presence: true + validates :project, presence: true, unless: :for_personal_snippet? # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } @@ -53,7 +54,7 @@ class Note < ActiveRecord::Base validates :commit_id, presence: true, if: :for_commit? validates :author, presence: true - validate unless: [:for_commit?, :importing?] do |note| + validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note| unless note.noteable.try(:project) == note.project errors.add(:invalid_project, 'Note and noteable project mismatch') end @@ -83,7 +84,7 @@ class Note < ActiveRecord::Base after_initialize :ensure_discussion_id before_validation :nullify_blank_type, :nullify_blank_line_code before_validation :set_discussion_id - after_save :keep_around_commit + after_save :keep_around_commit, unless: :for_personal_snippet? class << self def model_name @@ -165,6 +166,14 @@ class Note < ActiveRecord::Base noteable_type == "Snippet" end + def for_personal_snippet? + noteable.is_a?(PersonalSnippet) + end + + def skip_project_check? + for_personal_snippet? + end + # override to return commits, which are not active record def noteable if for_commit? @@ -220,6 +229,10 @@ class Note < ActiveRecord::Base note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] end + def to_ability_name + for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore + end + private def keep_around_commit diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index cdd765c85eb..b4f8b33d564 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -3,9 +3,10 @@ module Notes def execute merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) - note = project.notes.new(params) - note.author = current_user - note.system = false + note = Note.new(params) + note.project = project + note.author = current_user + note.system = false if note.award_emoji? noteable = note.noteable diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb index e4cd3fc7833..6a10e172483 100644 --- a/app/services/notes/post_process_service.rb +++ b/app/services/notes/post_process_service.rb @@ -10,6 +10,9 @@ module Notes # Skip system notes, like status changes and cross-references and awards unless @note.system? EventCreateService.new.leave_note(@note, @note.author) + + return if @note.for_personal_snippet? + @note.create_cross_references! execute_note_hooks end diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb index aaea9717fc4..56913568cae 100644 --- a/app/services/notes/slash_commands_service.rb +++ b/app/services/notes/slash_commands_service.rb @@ -12,7 +12,7 @@ module Notes def self.supported?(note, current_user) noteable_update_service(note) && current_user && - current_user.can?(:"update_#{note.noteable_type.underscore}", note.noteable) + current_user.can?(:"update_#{note.to_ability_name}", note.noteable) end def supported?(note) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index c3b61e68eab..f74e6cac174 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -178,8 +178,15 @@ class NotificationService recipients = [] mentioned_users = note.mentioned_users + + ability, subject = if note.for_personal_snippet? + [:read_personal_snippet, note.noteable] + else + [:read_project, note.project] + end + mentioned_users.select! do |user| - user.can?(:read_project, note.project) + user.can?(ability, subject) end # Add all users participating in the thread (author, assignee, comment authors) @@ -192,11 +199,13 @@ class NotificationService recipients = recipients.concat(participants) - # Merge project watchers - recipients = add_project_watchers(recipients, note.project) + unless note.for_personal_snippet? + # Merge project watchers + recipients = add_project_watchers(recipients, note.project) - # Merge project with custom notification - recipients = add_custom_notifications(recipients, note.project, :new_note) + # Merge project with custom notification + recipients = add_custom_notifications(recipients, note.project, :new_note) + end # Reject users with Mention notification level, except those mentioned in _this_ note. recipients = reject_mention_users(recipients - mentioned_users, note.project) @@ -211,8 +220,7 @@ class NotificationService recipients.delete(note.author) recipients = recipients.uniq - # build notify method like 'note_commit_email' - notify_method = "note_#{note.noteable_type.underscore}_email".to_sym + notify_method = "note_#{note.to_ability_name}_email".to_sym recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 3132d157f29..2269fb1fd8c 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -4,7 +4,7 @@ - if @broadcast_message.message.present? = render_broadcast_message(@broadcast_message) - else - = "Your message here" + Your message here = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| = form_errors(@broadcast_message) diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml index 7362d904b94..8c658905bd6 100644 --- a/app/views/admin/identities/_identity.html.haml +++ b/app/views/admin/identities/_identity.html.haml @@ -1,6 +1,6 @@ %tr %td - = "#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})" + #{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider}) %td = identity.extern_uid %td diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 37bb6a3b0e0..124f970524e 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -11,7 +11,7 @@ that for future communication. %br Registration token is - %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token} + %code#runners-token= current_application_settings.runners_registration_token .bs-callout.clearfix .pull-left diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index ca503e35623..39e103e3062 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -100,7 +100,7 @@ %td.build-link - if project = link_to ci_status_path(build.pipeline) do - %strong #{build.pipeline.short_sha} + %strong= build.pipeline.short_sha %td.timestamp - if build.finished_at diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index bfc6142067a..2e5f120c4e4 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -10,7 +10,7 @@ %h4 CPU .data - if @cpus - %h1= "#{@cpus.length} cores" + %h1 #{@cpus.length} cores - else = icon('warning', class: 'text-warning') Unable to collect CPU info @@ -19,7 +19,7 @@ %h4 Memory .data - if @memory - %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}" + %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)} - else = icon('warning', class: 'text-warning') Unable to collect memory info @@ -28,6 +28,6 @@ %h4 Disks .data - @disks.each do |disk| - %h1= "#{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}" - %p= "#{disk[:disk_name]}" - %p= "#{disk[:mount_path]}" + %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])} + %p= disk[:disk_name] + %p= disk[:mount_path] diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index a71240986c9..76b1291fe10 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -186,6 +186,6 @@ - if @user.solo_owned_groups.present? %p This user is currently an owner in these groups: - %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %strong= @user.solo_owned_groups.map(&:name).join(', ') %p You must transfer ownership or delete these groups before you can delete this user. diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index 95eb9a57152..b0bee1c6204 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -13,7 +13,7 @@ .file-holder .file-title.clearfix Content of .gitlab-ci.yml - #ci-editor.ci-editor #{@content} + #ci-editor.ci-editor= @content = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true) .col-sm-12 .pull-left.prepend-top-10 diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml index ef16b516e2c..3a19e021643 100644 --- a/app/views/discussions/_parallel_diff_discussion.html.haml +++ b/app/views/discussions/_parallel_diff_discussion.html.haml @@ -6,7 +6,7 @@ .content{ class: ('hide' unless discussion_left.expanded?) } = render "discussions/notes", discussion: discussion_left, line_type: 'old' - else - %td.notes_line.old= "" + %td.notes_line.old= ("") %td.notes_content.parallel.old .content @@ -16,6 +16,6 @@ .content{ class: ('hide' unless discussion_right.expanded?) } = render "discussions/notes", discussion: discussion_right, line_type: 'new' - else - %td.notes_line.new= "" + %td.notes_line.new= ("") %td.notes_content.parallel.new .content diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index 2a0e301c8dd..a196561f381 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -10,7 +10,7 @@ %p = icon("exclamation-triangle fw") You are an admin, which means granting access to - %strong #{@pre_auth.client.name} + %strong= @pre_auth.client.name will allow them to interact with GitLab as an admin as well. Proceed with caution. - if @pre_auth.scopes diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index bc5d3c797ac..f4c432a095a 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -25,7 +25,7 @@ .panel.panel-default .panel-heading Users with access to - %strong #{@group.name} + %strong= @group.name %span.badge= @members.total_count %ul.content-list = render partial: 'shared/members/member', collection: @members, as: :member diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index b4aa4f24d9e..6ad03a60b3a 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -18,7 +18,7 @@ .row-content-block.second-block Only issues from the - %strong #{@group.name} + %strong= @group.name group are listed here. - if current_user To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index dbbdb583a24..af73554086b 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -10,7 +10,7 @@ .row-content-block.second-block Only merge requests from - %strong #{@group.name} + %strong= @group.name group are listed here. - if current_user To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index a8fdbd8c426..cd5388fe402 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -10,7 +10,7 @@ .row-content-block Only milestones from - %strong #{@group.name} + %strong= @group.name group are listed here. .milestones diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index 864c5c0ff95..0e7f0b5ed4f 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -16,7 +16,7 @@ %colgroup.import-jobs-status-col %thead %tr - %th= "From #{provider_title}" + %th From #{provider_title} %th To GitLab %th Status %tbody diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml index 07338736bac..9999a4362c6 100644 --- a/app/views/import/fogbugz/new_user_map.html.haml +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -37,7 +37,7 @@ %tbody - @user_map.each do |id, user| %tr - %td= id + %td= (id) %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control' %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control' %td diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index 97e5e51abe0..5de5da5e6a2 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -50,7 +50,7 @@ %td = repo.name %td.import-target - = "#{current_user.username}/#{repo.name}" + #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 9f1507cade6..5e01af008be 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -55,7 +55,7 @@ %td = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" %td.import-target - = "#{current_user.username}/#{repo.name}" + #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml index 56d81b2ed2e..fd35713f79c 100644 --- a/app/views/notify/_reassigned_issuable_email.html.haml +++ b/app/views/notify/_reassigned_issuable_email.html.haml @@ -2,9 +2,9 @@ Assignee changed - if @previous_assignee from - %strong #{@previous_assignee.name} + %strong= @previous_assignee.name to - if issuable.assignee_id - %strong #{issuable.assignee_name} + %strong= issuable.assignee_name - else %strong Unassigned diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml index 56c18cd83cd..b7284dd819b 100644 --- a/app/views/notify/closed_issue_email.html.haml +++ b/app/views/notify/closed_issue_email.html.haml @@ -1,2 +1,2 @@ %p - = "Issue was closed by #{@updated_by.name}" + Issue was closed by #{@updated_by.name} diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml index ac703b31edd..bc12e38675f 100644 --- a/app/views/notify/closed_issue_email.text.haml +++ b/app/views/notify/closed_issue_email.text.haml @@ -1,3 +1,3 @@ -= "Issue was closed by #{@updated_by.name}" +Issue was closed by #{@updated_by.name} Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)} diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml index 81c7c88fc96..44e018304e1 100644 --- a/app/views/notify/closed_merge_request_email.html.haml +++ b/app/views/notify/closed_merge_request_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" + Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name} diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml index b435067d5a6..d0c96b83976 100644 --- a/app/views/notify/closed_merge_request_email.text.haml +++ b/app/views/notify/closed_merge_request_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" +Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name} Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml index 482c884a9db..b6051b11cea 100644 --- a/app/views/notify/issue_status_changed_email.html.haml +++ b/app/views/notify/issue_status_changed_email.html.haml @@ -1,2 +1,2 @@ %p - = "Issue was #{@issue_status} by #{@updated_by.name}" + Issue was #{@issue_status} by #{@updated_by.name} diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml index 41a320d6bd8..b487e26b122 100644 --- a/app/views/notify/merge_request_status_email.html.haml +++ b/app/views/notify/merge_request_status_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" + Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name} diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml index 7a5074a1dc3..4c9719ba732 100644 --- a/app/views/notify/merge_request_status_email.text.haml +++ b/app/views/notify/merge_request_status_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" +Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name} Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml index fbe506d4f4d..0fe54e73313 100644 --- a/app/views/notify/merged_merge_request_email.html.haml +++ b/app/views/notify/merged_merge_request_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request #{@merge_request.to_reference} was merged" + Merge Request #{@merge_request.to_reference} was merged diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml index bfbae01094f..46c1c9dee0b 100644 --- a/app/views/notify/merged_merge_request_email.text.haml +++ b/app/views/notify/merged_merge_request_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request #{@merge_request.to_reference} was merged" +Merge Request #{@merge_request.to_reference} was merged Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/note_personal_snippet_email.html.haml b/app/views/notify/note_personal_snippet_email.html.haml new file mode 100644 index 00000000000..2fa2f784661 --- /dev/null +++ b/app/views/notify/note_personal_snippet_email.html.haml @@ -0,0 +1 @@ += render 'note_message' diff --git a/app/views/notify/note_personal_snippet_email.text.erb b/app/views/notify/note_personal_snippet_email.text.erb new file mode 100644 index 00000000000..b2a8809a23b --- /dev/null +++ b/app/views/notify/note_personal_snippet_email.text.erb @@ -0,0 +1,8 @@ +New comment for Snippet <%= @snippet.id %> + +<%= url_for(snippet_url(@snippet, anchor: "note_#{@note.id}")) %> + + +Author: <%= @note.author_name %> + +<%= @note.note %> diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 82c7fe229b8..d9ebbaa2704 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -139,7 +139,7 @@ had = failed.size failed - = "#{'build'.pluralize(failed.size)}." + #{'build'.pluralize(failed.size)}. %tr.warning %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" } Logs may contain sensitive data. Please consider before forwarding this email. diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 6dddb3b6373..8add2e18206 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -138,9 +138,9 @@ %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = "\##{@pipeline.id}" successfully completed - = "#{build_count} #{'build'.pluralize(build_count)}" + #{build_count} #{'build'.pluralize(build_count)} in - = "#{stage_count} #{'stage'.pluralize(stage_count)}." + #{stage_count} #{'stage'.pluralize(stage_count)}. %tr.footer %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml index 27785165c2d..6c6902994a1 100644 --- a/app/views/notify/project_was_not_exported_email.text.haml +++ b/app/views/notify/project_was_not_exported_email.text.haml @@ -1,6 +1,6 @@ -= "Project #{@project.name} couldn't be exported." +Project #{@project.name} couldn't be exported. -= "The errors we encountered were:" +The errors we encountered were: - @errors.each do |error| #{error} diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 36858fa6f34..c6b1db17f91 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -17,7 +17,7 @@ %ul - @message.commits.each do |commit| %li - %strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))} + %strong= link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit)) %div %span by #{commit.author_name} %i at #{commit.committed_date.to_s(:iso8601)} diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 72f658d1b68..14b330d16ad 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -102,7 +102,7 @@ = f.text_field :username, required: true, class: 'form-control' .help-block Current path: - = "#{root_url}#{current_user.username}" + #{root_url}#{current_user.username} .prepend-top-default = f.button class: "btn btn-warning", type: "submit" do = icon "spinner spin", class: "hidden loading-username" @@ -128,7 +128,7 @@ - if @user.solo_owned_groups.present? %p Your account is currently an owner in these groups: - %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %strong= @user.solo_owned_groups.map(&:name).join(', ') %p You must transfer ownership or delete these groups before you can delete your account. .append-bottom-default diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index c0c82cde2f6..d551754a2e5 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -62,7 +62,7 @@ %span.help-block Please click the link in the confirmation email before continuing. It was sent to = succeed "." do - %strong #{@user.unconfirmed_email} + %strong= @user.unconfirmed_email %p = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 1d058daa094..228ac61fc8c 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -34,7 +34,7 @@ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' .file-editor.code - %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]} + %pre.js-edit-mode-pane#editor= params[:content] || local_assigns[:blob_data] - if local_assigns[:path] .js-edit-mode-pane#preview.hide .center diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml index a486b2fe491..f864702d862 100644 --- a/app/views/projects/blob/_image.html.haml +++ b/app/views/projects/blob/_image.html.haml @@ -1,8 +1,8 @@ .file-content.image_file - if blob.svg? - if blob.size_within_svg_limits? - - # We need to scrub SVG but we cannot do so in the RawController: it would - - # be wrong/strange if RawController modified the data. + -# We need to scrub SVG but we cannot do so in the RawController: it would + -# be wrong/strange if RawController modified the data. - blob.load_all_data!(@repository) - blob = sanitize_svg(blob) %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" } diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 61a7ffdd0ab..4924c73cf8e 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -3,7 +3,7 @@ .modal-content .modal-header %a.close{ href: "#", "data-dismiss" => "modal" } × - %h3.page-title #{title} + %h3.page-title= title .modal-body = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do .dropzone diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 520113639b7..c1e496455d1 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -85,7 +85,7 @@ - if build.finished_at %p.finished-at = icon("calendar") - %span #{time_ago_with_tooltip(build.finished_at)} + %span= time_ago_with_tooltip(build.finished_at) %td.coverage - if coverage && build.try(:coverage) diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index fcc367951ad..904cdb5767f 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -2,7 +2,7 @@ - commits, hidden = limited_commits(@commits) - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - %li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}" + %li.commit-header #{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')} %li.commits-row %ul.content-list.commit-list.table-list.table-wide = render commits, project: project, ref: ref diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 819e9bc15ae..9c8f58d4aea 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -16,9 +16,9 @@ There isn't anything to compare. %p.slead - if params[:to] == params[:from] - %span.label-branch #{params[:from]} + %span.label-branch= params[:from] and - %span.label-branch #{params[:to]} + %span.label-branch= params[:to] are the same. - else You'll need to use different branch names to get a valid comparison. diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 9238f232c7e..c468202569f 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -1,6 +1,6 @@ %tr.deployment %td - %strong= "##{deployment.iid}" + %strong ##{deployment.iid} %td = render 'projects/deployments/commit', deployment: deployment @@ -8,7 +8,7 @@ %td.build-column - if deployment.deployable = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do - = "#{deployment.deployable.name} (##{deployment.deployable.id})" + #{deployment.deployable.name} (##{deployment.deployable.id}) - if deployment.user by = user_avatar(user: deployment.user, size: 20) diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index 52a1ece7d60..b87b79b170e 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -1,5 +1,5 @@ .diff-content.diff-wrap-lines - - # Skip all non non-supported blobs + -# Skip all non non-supported blobs - return unless blob.respond_to?(:text?) - if diff_file.too_large? .nothing-here-block This diff could not be displayed because it is too large. diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index 90c9a0c6c2b..ddec775b789 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -25,4 +25,4 @@ - if diff_file.mode_changed? %small - = "#{diff_file.a_mode} → #{diff_file.b_mode}" + #{diff_file.a_mode} → #{diff_file.b_mode} diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml index 1bccaaf5273..ca10921c5e2 100644 --- a/app/views/projects/diffs/_image.html.haml +++ b/app/views/projects/diffs/_image.html.haml @@ -9,7 +9,7 @@ %span.wrap .frame{ class: image_diff_class(diff) } %img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path } - %p.image-info= "#{number_to_human_size file.size}" + %p.image-info= number_to_human_size(file.size) - else .image .two-up.view @@ -18,7 +18,7 @@ %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) } %img{ src: old_file_raw_path, alt: diff.old_path } %p.image-info.hide - %span.meta-filesize= "#{number_to_human_size old_file.size}" + %span.meta-filesize= number_to_human_size(old_file.size) | %b W: %span.meta-width @@ -30,7 +30,7 @@ %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) } %img{ src: file_raw_path, alt: diff.new_path } %p.image-info.hide - %span.meta-filesize= "#{number_to_human_size file.size}" + %span.meta-filesize= number_to_human_size(file.size) | %b W: %span.meta-width diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 290f696d582..8e24e28765f 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -2,7 +2,7 @@ .commit-stat-summary Showing = link_to '#', class: 'js-toggle-button' do - %strong #{pluralize(diff_files.size, "changed file")} + %strong= pluralize(diff_files.size, "changed file") with %strong.cgreen #{diff_files.sum(&:added_lines)} additions and diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 6c8a6f051a9..f4aa523b32d 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -1,7 +1,7 @@ .top-area .nav-text - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private" - = "#{pluralize(@total_forks_count, 'fork')}: #{full_count_title}" + #{pluralize(@total_forks_count, 'fork')}: #{full_count_title} .nav-controls = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 6d7af1685fd..07fb80750d6 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -77,7 +77,7 @@ - if generic_commit_status.finished_at %p.finished-at = icon("calendar") - %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} + %span= time_ago_with_tooltip(generic_commit_status.finished_at) %td.coverage - if coverage && generic_commit_status.try(:coverage) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 7e34a89f9ae..c8a82f7bca3 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -11,7 +11,7 @@ %p.lead Commit statistics for - %strong #{@ref} + %strong= @ref #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} .row @@ -19,19 +19,19 @@ %ul %li %p.lead - %strong #{@commits_graph.commits.size} + %strong= @commits_graph.commits.size commits during - %strong #{@commits_graph.duration} + %strong= @commits_graph.duration days %li %p.lead Average - %strong #{@commits_graph.commit_per_day} + %strong= @commits_graph.commit_per_day commits per day %li %p.lead Contributed by - %strong #{@commits_graph.authors} + %strong= @commits_graph.authors authors .col-md-6 %div diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 36c6e7a8dad..d3c013b3f21 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -3,9 +3,9 @@ %p.slead - source_title, target_title = format_mr_branch_names(@merge_request) From - %strong.label-branch #{source_title} + %strong.label-branch= source_title %span into - %strong.label-branch #{target_title} + %strong.label-branch= target_title %span.pull-right = link_to 'Change branches', mr_change_branches_path(@merge_request) @@ -43,7 +43,7 @@ #commits.commits.tab-pane.active = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX - if @pipelines.any? #pipelines.pipelines.tab-pane = render "projects/merge_requests/show/pipelines" diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 2a7cd3a19d0..eade0c2a668 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -92,11 +92,11 @@ = render "projects/merge_requests/discussion" #commits.commits.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #pipelines.pipelines.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX .mr-loading-status = spinner diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index b0f3c86fd21..74a7b1dc498 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -25,7 +25,7 @@ latest version - else version #{version_index(merge_request_diff)} - .monospace #{short_sha(merge_request_diff.head_commit_sha)} + .monospace= short_sha(merge_request_diff.head_commit_sha) %small #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)}, = time_ago_with_tooltip(merge_request_diff.created_at) @@ -55,14 +55,14 @@ latest version - else version #{version_index(merge_request_diff)} - .monospace #{short_sha(merge_request_diff.head_commit_sha)} + .monospace= short_sha(merge_request_diff.head_commit_sha) %small = time_ago_with_tooltip(merge_request_diff.created_at) %li = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do %strong #{@merge_request.target_branch} (base) - .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + .monospace= short_sha(@merge_request_diff.base_commit_sha) - if different_base?(@start_version, @merge_request_diff) .content-block @@ -72,7 +72,7 @@ = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do new commits from - %code #{@merge_request.target_branch} + %code= @merge_request.target_branch - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index c80dc33058d..5faa6c43f9f 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -13,8 +13,8 @@ %span.ci-coverage - elsif @merge_request.has_ci? - - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API + -# Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX + -# TODO, remove in later versions when services like Jenkins will set CI status via Commit status API .mr-widget-heading - %w[success skipped canceled failed running pending].each do |status| .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" } diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 36c388c3318..09339e520dd 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -69,7 +69,7 @@ - if note_editable .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } } #{note.note} - %textarea.hidden.js-task-list-field.original-task-list #{note.note} + %textarea.hidden.js-task-list-field.original-task-list= note.note .note-awards = render 'award_emoji/awards_block', awardable: note, inline: false - if note.system diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index 9738f369a35..c7996077bc7 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading Group members with access to - %strong #{@group.name} + %strong= @group.name %span.badge= members.size - if can?(current_user, :admin_group_member, @group) .controls diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index d7f5fa96527..fdeb5f21fbe 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -1,7 +1,7 @@ .panel.panel-default.project-members-groups .panel-heading Groups with access to - %strong #{@project.name} + %strong= @project.name %span.badge= group_links.size %ul.content-list = render partial: 'shared/members/group', collection: group_links, as: :group_link diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml index 77370c14def..7902ddb1ae9 100644 --- a/app/views/projects/project_members/_shared_group_members.html.haml +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -5,9 +5,9 @@ .panel.panel-default .panel-heading Shared with - %strong #{shared_group.name} + %strong= shared_group.name group, members with - %strong #{group_links.human_access} + %strong= group_links.human_access role (#{shared_group_users_count}) - if can?(current_user, :admin_group, shared_group) .panel-head-actions diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 5292e73be7a..81d57c77edf 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading Members with access to - %strong #{@project.name} + %strong= @project.name %span.badge= @project_members.total_count = form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do .form-group diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 33d5cbff420..79d8d721aa9 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -7,7 +7,7 @@ .oneline .title Release notes for tag - %strong #{@tag.name} + %strong= @tag.name = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 51b0939564e..dcff675eafc 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -9,10 +9,10 @@ (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it). %li Specify the following URL during the Runner setup: - %code #{ci_root_url(only_path: false)} + %code= ci_root_url(only_path: false) %li Use the following registration token during setup: - %code #{@project.runners_token} + %code= @project.runners_token %li Start the Runner! diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml index c929eee3bb9..fcc91be11cd 100644 --- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml @@ -4,4 +4,4 @@ .col-sm-9.col-sm-offset-3 = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do = custom_icon('mattermost_logo', size: 15) - = 'Add to Mattermost' + Add to Mattermost diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml index 05a63016c09..821a39d61f5 100644 --- a/app/views/search/results/_empty.html.haml +++ b/app/views/search/results/_empty.html.haml @@ -3,4 +3,4 @@ %h4 = icon('search') We couldn't find any results matching - %code #{@search_term} + %code= @search_term diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 07b17bc69c0..2e6adf3027c 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -2,7 +2,7 @@ %h4 = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do %span.term.str-truncated= merge_request.title - .pull-right #{merge_request.to_reference} + .pull-right= merge_request.to_reference - if merge_request.description.present? .description.term = preserve do diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index c9b7bd154af..23ca6479414 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -9,7 +9,7 @@ = link_to user_snippets_path(snippet.author) do = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = snippet.author_name - %span.light #{time_ago_with_tooltip(snippet.created_at)} + %span.light= time_ago_with_tooltip(snippet.created_at) %h4.snippet-title - snippet_path = reliable_snippet_path(snippet) = link_to snippet_path do diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index 027d42396b4..704d1d01a81 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -20,4 +20,4 @@ = link_to user_snippets_path(snippet_title.author) do = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: '' = snippet_title.author_name - %span.light #{time_ago_with_tooltip(snippet_title.created_at)} + %span.light= time_ago_with_tooltip(snippet_title.created_at) diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml index e7cb93b17a7..f94f8ffc604 100644 --- a/app/views/shared/_confirm_modal.html.haml +++ b/app/views/shared/_confirm_modal.html.haml @@ -14,7 +14,7 @@ To prevent accidental actions we ask you to confirm your intention. %br Please type - %code.js-confirm-danger-match #{phrase} + %code.js-confirm-danger-match= phrase to proceed or close this modal to cancel. .form-group diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index 39294fe1a09..704893b4d5b 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -6,14 +6,14 @@ = link_to milestones_filter_path(state: 'opened') do Open - if @project - %span.badge #{counts[:opened]} + %span.badge= counts[:opened] %li{ class: milestone_class_for_state(params[:state], 'closed') }> = link_to milestones_filter_path(state: 'closed') do Closed - if @project - %span.badge #{counts[:closed]} + %span.badge= counts[:closed] %li{ class: milestone_class_for_state(params[:state], 'all') }> = link_to milestones_filter_path(state: 'all') do All - if @project - %span.badge #{counts[:all]} + %span.badge= counts[:all] diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index f9a7aa4e29b..dd9e433491b 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -32,7 +32,7 @@ - if group_member as - %span #{group_member.human_access} + %span= group_member.human_access - if group.description.present? .description diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c0e8a498316..0a4de709fcd 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -68,7 +68,7 @@ - if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) .inline.prepend-left-10 Please review the - %strong #{link_to 'contribution guidelines', guide_url} + %strong= link_to('contribution guidelines', guide_url) for this project. - if issuable.new_record? diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml index 5074afb63a1..8bbaf431536 100644 --- a/app/views/shared/tokens/_scopes_form.html.haml +++ b/app/views/shared/tokens/_scopes_form.html.haml @@ -5,5 +5,5 @@ - scopes.each do |scope| %fieldset = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}" - = label_tag "#{prefix}_scopes_#{scope}", scope - %span= "(#{t(scope, scope: [:doorkeeper, :scopes])})" + = label_tag ("#{prefix}_scopes_#{scope}"), scope + %span= t(scope, scope: [:doorkeeper, :scopes]) diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml index f51599212db..b09782749f5 100644 --- a/app/views/users/calendar_activities.html.haml +++ b/app/views/users/calendar_activities.html.haml @@ -1,6 +1,6 @@ %h4.prepend-top-20 Contributions for - %strong #{@calendar_date.to_s(:short)} + %strong= @calendar_date.to_s(:short) - if @events.any? %ul.bordered-list diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index fb25eed4f37..c3d33d49c1e 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -110,16 +110,16 @@ = spinner #groups.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #contributed.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #projects.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX #snippets.tab-pane - - # This tab is always loaded via AJAX + -# This tab is always loaded via AJAX .loading-status = spinner diff --git a/changelogs/unreleased/fix_broken_diff_discussions.yml b/changelogs/unreleased/fix_broken_diff_discussions.yml new file mode 100644 index 00000000000..4551212759f --- /dev/null +++ b/changelogs/unreleased/fix_broken_diff_discussions.yml @@ -0,0 +1,4 @@ +--- +title: Make MR-review-discussions more reliable +merge_request: +author: diff --git a/changelogs/unreleased/newline-eslint-rule.yml b/changelogs/unreleased/newline-eslint-rule.yml new file mode 100644 index 00000000000..5ce080b6912 --- /dev/null +++ b/changelogs/unreleased/newline-eslint-rule.yml @@ -0,0 +1,4 @@ +--- +title: Flag multiple empty lines in eslint, fix offenses. +merge_request: 8137 +author: diff --git a/changelogs/unreleased/no_project_notes.yml b/changelogs/unreleased/no_project_notes.yml new file mode 100644 index 00000000000..6106c027360 --- /dev/null +++ b/changelogs/unreleased/no_project_notes.yml @@ -0,0 +1,4 @@ +--- +title: Support notes when a project is not specified (personal snippet notes) +merge_request: 8468 +author: diff --git a/changelogs/unreleased/tc-only-mr-button-if-allowed.yml b/changelogs/unreleased/tc-only-mr-button-if-allowed.yml new file mode 100644 index 00000000000..a7f5dcb560c --- /dev/null +++ b/changelogs/unreleased/tc-only-mr-button-if-allowed.yml @@ -0,0 +1,4 @@ +--- +title: Only show Merge Request button when user can create a MR +merge_request: 8639 +author: diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index f3c2e72341f..33b9b28433a 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -27,6 +27,7 @@ Ruby Version: 2.1.5p273 Gem Version: 2.4.3 Bundler Version: 1.7.6 Rake Version: 10.3.2 +Redis Version: 3.2.5 Sidekiq Version: 2.17.8 GitLab information diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 0950c3d2e88..be659fa4a6a 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -129,12 +129,7 @@ module API end end - # Delete all merged branches - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/repository/branches/delete_merged + desc 'Delete all merged branches' delete ":id/repository/merged_branches" do DeleteMergedBranchesService.new(user_project, current_user).async_execute diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index ab7af1cad21..6640168bfa2 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -53,6 +53,10 @@ module Banzai context[:project] end + def skip_project_check? + context[:skip_project_check] + end + def reference_class(type) "gfm gfm-#{type} has-tooltip" end diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index f842b1fb779..1aa9355b256 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -24,7 +24,7 @@ module Banzai end def call - return doc if project.nil? + return doc if project.nil? && !skip_project_check? ref_pattern = User.reference_pattern ref_pattern_start = /\A#{ref_pattern}\z/ @@ -58,7 +58,7 @@ module Banzai # have `gfm` and `gfm-project_member` class names attached for styling. def user_link_filter(text, link_content: nil) self.class.references_in(text) do |match, username| - if username == 'all' + if username == 'all' && !skip_project_check? link_to_all(link_content: link_content) elsif namespace = namespaces[username] link_to_namespace(namespace, link_content: link_content) || match diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index f31fb6c3f71..74663556cbb 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -52,9 +52,9 @@ module Banzai end # Same as +render_field+, but without consulting or updating the cache field - def cacheless_render_field(object, field) + def cacheless_render_field(object, field, options = {}) text = object.__send__(field) - context = object.banzai_render_context(field) + context = object.banzai_render_context(field).merge(options) cacheless_render(text, context) end diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index dffea8ed155..f7c831892ee 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -11,8 +11,10 @@ namespace :gitlab do gem_version = run_command(%W(gem --version)) # check Bundler version bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s) - # check Bundler version + # check Rake version rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s) + # check redis version + redis_version = run_and_match(%W(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a puts "" puts "System information".color(:yellow) @@ -24,6 +26,7 @@ namespace :gitlab do puts "Gem Version:\t#{gem_version || "unknown".color(:red)}" puts "Bundler Version:#{bunder_version || "unknown".color(:red)}" puts "Rake Version:\t#{rake_version || "unknown".color(:red)}" + puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}" puts "Sidekiq Version:#{Sidekiq::VERSION}" diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index a10ba629760..476f2a37793 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -13,6 +13,7 @@ FactoryGirl.define do factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] factory :note_on_merge_request, traits: [:on_merge_request] factory :note_on_project_snippet, traits: [:on_project_snippet] + factory :note_on_personal_snippet, traits: [:on_personal_snippet] factory :system_note, traits: [:system] factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote @@ -70,6 +71,11 @@ FactoryGirl.define do noteable { create(:project_snippet, project: project) } end + trait :on_personal_snippet do + noteable { create(:personal_snippet) } + project nil + end + trait :system do system true end diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index bea00160f96..f09ad2dd86b 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do visit namespace_project_issues_path(project.namespace, project) end + describe 'keyboard navigation' do + it 'selects label' do + send_keys_to_filtered_search('label:') + + filtered_search.native.send_keys(:down, :down, :enter) + + expect(filtered_search.value).to eq("label:~#{special_label.name} ") + end + end + describe 'behavior' do it 'opens when the search bar has label:' do filtered_search.set('label:') diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 56b1d354eb0..90eb60eb337 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do left_style.to_s.gsub('left: ', '').to_f end + describe 'keyboard navigation' do + it 'makes item active' do + filtered_search.native.send_keys(:down) + + page.within '#js-dropdown-hint' do + expect(page).to have_selector('.dropdown-active') + end + end + + it 'selects item' do + filtered_search.native.send_keys(:down, :down, :enter) + + expect(filtered_search.value).to eq('author:') + end + end + describe 'clear search button' do it 'clears text' do search_text = 'search_text' diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb new file mode 100644 index 00000000000..b6728960fb8 --- /dev/null +++ b/spec/features/projects/merge_request_button_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +feature 'Merge Request button', feature: true do + shared_examples 'Merge Request button only shown when allowed' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:forked_project) { create(:project, :public, forked_from_project: project) } + + context 'not logged in' do + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + end + + context 'logged in as developer' do + before do + login_as(user) + project.team << [user, :developer] + end + + it 'shows Create Merge Request button' do + href = new_namespace_project_merge_request_path(project.namespace, + project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) + + visit url + + within("#content-body") do + expect(page).to have_link(label, href: href) + end + end + + context 'merge requests are disabled' do + before do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED) + end + + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + end + end + + context 'logged in as non-member' do + before do + login_as(user) + end + + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + + context 'on own fork of project' do + let(:user) { forked_project.owner } + + it 'shows Create Merge Request button' do + href = new_namespace_project_merge_request_path(forked_project.namespace, + forked_project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) + + visit fork_url + + within("#content-body") do + expect(page).to have_link(label, href: href) + end + end + end + end + end + + context 'on branches page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Merge Request' } + let(:url) { namespace_project_branches_path(project.namespace, project) } + let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) } + end + end + + context 'on compare page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Create Merge Request' } + let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') } + let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') } + end + end + + context 'on commits page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Create Merge Request' } + let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') } + let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') } + end + end +end diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6 index cf19aa05031..a2d57824585 100644 --- a/spec/javascripts/abuse_reports_spec.js.es6 +++ b/spec/javascripts/abuse_reports_spec.js.es6 @@ -21,7 +21,6 @@ messages = $('.abuse-reports .message'); }); - it('should truncate long messages', () => { const $longMessage = findMessage('LONG MESSAGE'); expect($longMessage.data('original-message')).toEqual(jasmine.anything()); diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6 index 21241116e29..95796f23894 100644 --- a/spec/javascripts/environments/environment_rollback_spec.js.es6 +++ b/spec/javascripts/environments/environment_rollback_spec.js.es6 @@ -33,7 +33,6 @@ describe('Rollback Component', () => { expect(component.$el.querySelector('span').textContent).toContain('Re-deploy'); }); - it('Should render Rollback label when isLastDeployment is false', () => { const component = new window.gl.environmentsList.RollbackComponent({ el: document.querySelector('.test-dom-element'), diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6 index 031f9ca03c9..1ce8f28e568 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js.es6 +++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6 @@ -52,5 +52,22 @@ expect(value).toBe(null); }); }); + + describe('gl.utils.normalizedHeaders', () => { + it('should upperCase all the header keys to keep them consistent', () => { + const apiHeaders = { + 'X-Something-Workhorse': { workhorse: 'ok' }, + 'x-something-nginx': { nginx: 'ok' }, + }; + + const normalized = gl.utils.normalizeHeaders(apiHeaders); + + const WORKHORSE = 'X-SOMETHING-WORKHORSE'; + const NGINX = 'X-SOMETHING-NGINX'; + + expect(normalized[WORKHORSE].workhorse).toBe('ok'); + expect(normalized[NGINX].nginx).toBe('ok'); + }); + }); }); })(); diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 5bfeb82e738..3e1ac9fb2b2 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -152,6 +152,30 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end end + context 'when a project is not specified' do + let(:project) { nil } + + it 'does not link a User' do + doc = reference_filter("Hey #{reference}") + + expect(doc).not_to include('a') + end + + context 'when skip_project_check set to true' do + it 'links to a User' do + doc = reference_filter("Hey #{reference}", skip_project_check: true) + + expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) + end + + it 'does not link users using @all reference' do + doc = reference_filter("Hey #{User.reference_prefix}all", skip_project_check: true) + + expect(doc).not_to include('a') + end + end + end + describe '#namespaces' do it 'returns a Hash containing all Namespaces' do document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>") diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 1bdf005c823..4d57efd3c53 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -171,6 +171,33 @@ describe Ability, lib: true do end end + describe '.users_that_can_read_personal_snippet' do + def users_for_snippet(snippet) + described_class.users_that_can_read_personal_snippet(users, snippet) + end + + let(:users) { create_list(:user, 3) } + let(:author) { users[0] } + + it 'private snippet is readable only by its author' do + snippet = create(:personal_snippet, :private, author: author) + + expect(users_for_snippet(snippet)).to match_array([author]) + end + + it 'internal snippet is readable by all registered users' do + snippet = create(:personal_snippet, :public, author: author) + + expect(users_for_snippet(snippet)).to match_array(users) + end + + it 'public snippet is readable by all users' do + snippet = create(:personal_snippet, :public, author: author) + + expect(users_for_snippet(snippet)).to match_array(users) + end + end + describe '.issues_readable_by_user' do context 'with an admin user' do it 'returns all given issues' do diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 132858950d5..b73028f0bc0 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -30,12 +30,20 @@ describe Issue, "Mentionable" do describe '#mentioned_users' do let!(:user) { create(:user, username: 'stranger') } let!(:user2) { create(:user, username: 'john') } - let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } + let!(:user3) { create(:user, username: 'jim') } + let(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } subject { issue.mentioned_users } - it { is_expected.to include(user) } - it { is_expected.not_to include(user2) } + it { expect(subject).to contain_exactly(user) } + + context 'when a note on personal snippet' do + let!(:note) { create(:note_on_personal_snippet, note: "#{user.to_reference} mentioned #{user3.to_reference}") } + + subject { note.mentioned_users } + + it { expect(subject).to contain_exactly(user, user3) } + end end describe '#referenced_mentionables' do @@ -138,6 +146,16 @@ describe Issue, "Mentionable" do issue.update_attributes(description: issues[1].to_reference) issue.create_new_cross_references! end + + it 'notifies new references from project snippet note' do + snippet = create(:snippet, project: project) + note = create(:note, note: issues[0].to_reference, noteable: snippet, project: project, author: author) + + expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args) + + note.update_attributes(note: issues[1].to_reference) + note.create_new_cross_references! + end end def create_issue(description:) diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 310fecd8a5c..1b8ae356f1f 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -52,6 +52,19 @@ describe Note, models: true do subject { create(:note) } it { is_expected.to be_valid } end + + context 'when project is missing for a project related note' do + subject { build(:note, project: nil, noteable: build_stubbed(:issue)) } + it { is_expected.to be_invalid } + end + + context 'when noteable is a personal snippet' do + subject { build(:note_on_personal_snippet) } + + it 'is valid without project' do + is_expected.to be_valid + end + end end describe "Commit notes" do @@ -139,6 +152,7 @@ describe Note, models: true do with([{ text: note1.note, context: { + skip_project_check: false, pipeline: :note, cache_key: [note1, "note"], project: note1.project, @@ -150,6 +164,7 @@ describe Note, models: true do with([{ text: note2.note, context: { + skip_project_check: false, pipeline: :note, cache_key: [note2, "note"], project: note2.project, @@ -306,4 +321,70 @@ describe Note, models: true do end end end + + describe '#for_personal_snippet?' do + it 'returns false for a project snippet note' do + expect(build(:note_on_project_snippet).for_personal_snippet?).to be_falsy + end + + it 'returns true for a personal snippet note' do + expect(build(:note_on_personal_snippet).for_personal_snippet?).to be_truthy + end + end + + describe '#to_ability_name' do + it 'returns snippet for a project snippet note' do + expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet') + end + + it 'returns personal_snippet for a personal snippet note' do + expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet') + end + + it 'returns merge_request for an MR note' do + expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request') + end + + it 'returns issue for an issue note' do + expect(build(:note_on_issue).to_ability_name).to eq('issue') + end + + it 'returns issue for a commit note' do + expect(build(:note_on_commit).to_ability_name).to eq('commit') + end + end + + describe '#cache_markdown_field' do + let(:html) { '<p>some html</p>'} + + context 'note for a project snippet' do + let(:note) { build(:note_on_project_snippet) } + + before do + expect(Banzai::Renderer).to receive(:cacheless_render_field). + with(note, :note, { skip_project_check: false }).and_return(html) + + note.save + end + + it 'creates a note' do + expect(note.note_html).to eq(html) + end + end + + context 'note for a personal snippet' do + let(:note) { build(:note_on_personal_snippet) } + + before do + expect(Banzai::Renderer).to receive(:cacheless_render_field). + with(note, :note, { skip_project_check: true }).and_return(html) + + note.save + end + + it 'creates a note' do + expect(note.note_html).to eq(html) + end + end + end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index b0cc3ce5f5a..9c92a5080c6 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -15,39 +15,45 @@ describe Notes::CreateService, services: true do context "valid params" do it 'returns a valid note' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid end it 'returns a persisted note' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_persisted end it 'note has valid content' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note.note).to eq(opts[:note]) end + it 'note belongs to the correct project' do + note = described_class.new(project, user, opts).execute + + expect(note.project).to eq(project) + end + it 'TodoService#new_note is called' do - note = build(:note) - allow(project).to receive_message_chain(:notes, :new).with(opts) { note } + note = build(:note, project: project) + allow(Note).to receive(:new).with(opts) { note } expect_any_instance_of(TodoService).to receive(:new_note).with(note, user) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end it 'enqueues NewNoteWorker' do - note = build(:note, id: 999) - allow(project).to receive_message_chain(:notes, :new).with(opts) { note } + note = build(:note, id: 999, project: project) + allow(Note).to receive(:new).with(opts) { note } expect(NewNoteWorker).to receive(:perform_async).with(note.id) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end end @@ -75,6 +81,27 @@ describe Notes::CreateService, services: true do end end end + + describe 'personal snippet note' do + subject { described_class.new(nil, user, params).execute } + + let(:snippet) { create(:personal_snippet) } + let(:params) do + { note: 'comment', noteable_type: 'Snippet', noteable_id: snippet.id } + end + + it 'returns a valid note' do + expect(subject).to be_valid + end + + it 'returns a persisted note' do + expect(subject).to be_persisted + end + + it 'note has valid content' do + expect(subject.note).to eq(params[:note]) + end + end end describe "award emoji" do @@ -88,7 +115,7 @@ describe Notes::CreateService, services: true do noteable_type: 'Issue', noteable_id: issue.id } - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid expect(note.name).to eq('smile') @@ -100,7 +127,7 @@ describe Notes::CreateService, services: true do noteable_type: 'Issue', noteable_id: issue.id } - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid expect(note.note).to eq(opts[:note]) @@ -115,7 +142,7 @@ describe Notes::CreateService, services: true do expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f3e80ac22a0..bfbee7ca35f 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -269,6 +269,55 @@ describe NotificationService, services: true do end end + context 'personal snippet note' do + let(:snippet) { create(:personal_snippet, :public, author: @u_snippet_author) } + let(:note) { create(:note_on_personal_snippet, noteable: snippet, note: '@mentioned note', author: @u_note_author) } + + before do + @u_watcher = create_global_setting_for(create(:user), :watch) + @u_participant = create_global_setting_for(create(:user), :participating) + @u_disabled = create_global_setting_for(create(:user), :disabled) + @u_mentioned = create_global_setting_for(create(:user, username: 'mentioned'), :mention) + @u_mentioned_level = create_global_setting_for(create(:user, username: 'participator'), :mention) + @u_note_author = create(:user, username: 'note_author') + @u_snippet_author = create(:user, username: 'snippet_author') + @u_not_mentioned = create_global_setting_for(create(:user, username: 'regular'), :participating) + + reset_delivered_emails! + end + + let!(:notes) do + [ + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_watcher), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_participant), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_mentioned), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_disabled), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author), + ] + end + + describe '#new_note' do + it 'notifies the participants' do + notification.new_note(note) + + # it emails participants + should_email(@u_watcher) + should_email(@u_participant) + should_email(@u_watcher) + should_email(@u_snippet_author) + + # it emails mentioned users + should_email(@u_mentioned) + + # it does not email participants with mention notification level + should_not_email(@u_mentioned_level) + + # it does not email note author + should_not_email(@u_note_author) + end + end + end + context 'commit note' do let(:project) { create(:project, :public) } let(:note) { create(:note_on_commit, project: project) } |