diff --git a/Gemfile b/Gemfile index ee2f925a69ffdd3a0cf33f0ea78dfefa1bc5b1b1..25fb2a248853d4d500dddc2cad2763833b554536 100644 --- a/Gemfile +++ b/Gemfile @@ -21,7 +21,7 @@ gem 'rugged', '~> 0.24.0' # Authentication libraries gem 'devise', '~> 4.2' gem 'doorkeeper', '~> 4.2.0' -gem 'omniauth', '~> 1.3.1' +gem 'omniauth', '~> 1.3.2' gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-cas3', '~> 1.1.2' diff --git a/Gemfile.lock b/Gemfile.lock index d16733ba6c4c67d165f6e0bfff544a4dd5b4f186..a2d12850c52205bb81f74d3f778a88c9333d9cd2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -473,7 +473,7 @@ GEM octokit (4.6.2) sawyer (~> 0.8.0, >= 0.5.3) oj (2.17.4) - omniauth (1.3.1) + omniauth (1.3.2) hashie (>= 1.2, < 4) rack (>= 1.0, < 3) omniauth-auth0 (1.4.1) @@ -958,7 +958,7 @@ DEPENDENCIES oauth2 (~> 1.2.0) octokit (~> 4.6.2) oj (~> 2.17.4) - omniauth (~> 1.3.1) + omniauth (~> 1.3.2) omniauth-auth0 (~> 1.4.1) omniauth-authentiq (~> 0.2.0) omniauth-azure-oauth2 (~> 0.0.6) diff --git a/README.md b/README.md index 1053bab9e8da4dc49a3ae048d47cb8ee0055af97..af9b5ca24dd35236201c0978ef974244b3523301 100644 --- a/README.md +++ b/README.md @@ -143,4 +143,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ## Is it awesome? Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. -[These people](https://twitter.com/gitlab/favorites) seem to like it. +[These people](https://twitter.com/gitlab/likes) seem to like it. diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 3a367f6b42eef6f956f572c69853fea5a5d60c6a..01f489a02a6d6471cf8c13d443a84a5ec8de8167 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -20,6 +20,9 @@ if (selected.tagName === 'LI') { if (selected.hasAttribute('data-value')) { this.dismissDropdown(); + } else if (selected.getAttribute('data-action') === 'submit') { + this.dismissDropdown(); + this.dispatchFormSubmitEvent(); } else { const token = selected.querySelector('.js-filter-hint').innerText.trim(); const tag = selected.querySelector('.js-filter-tag').innerText.trim(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 9128ea907b3ce6ac1e14caba4d49b0f503d0f575..859d6515531e7801b5883c92af9a6b27ab2d5f60 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -39,6 +39,7 @@ } this.dismissDropdown(); + this.dispatchInputEvent(); } } @@ -84,6 +85,12 @@ })); } + dispatchFormSubmitEvent() { + // dispatchEvent() is necessary as form.submit() does not + // trigger event handlers + this.input.form.dispatchEvent(new Event('submit')); + } + hideDropdown() { this.getCurrentHook().list.hide(); } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 843db6fd7b5a859aebd7d7445cde2d9ffab620e1..4a66908f66912f1f28d125e8c57cfc95a709d25b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -66,11 +66,19 @@ const word = `${tokenName}:${tokenValue}`; // Get the string to replace - const selectionStart = input.selectionStart; + let newCaretPosition = input.selectionStart; const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input); input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`; - gl.FilteredSearchDropdownManager.updateInputCaretPosition(selectionStart, input); + + // If we have added a tokenValue at the end of the input, + // add a space and set selection to the end + if (right >= inputValue.length && tokenValue !== '') { + input.value += ' '; + newCaretPosition = input.value.length; + } + + gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input); } static updateInputCaretPosition(selectionStart, input) { 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 c7b72b365615b3445c872f68cf1bcb5c35e60014..ae19bb683600491f860f1e64e2644b2351a51e77 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -25,6 +25,7 @@ } bindEvents() { + this.handleFormSubmit = this.handleFormSubmit.bind(this); this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this); this.checkForEnterWrapper = this.checkForEnter.bind(this); @@ -32,6 +33,7 @@ this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); this.tokenChange = this.tokenChange.bind(this); + this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit); this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); @@ -42,6 +44,7 @@ } unbindEvents() { + this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit); this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); @@ -88,6 +91,11 @@ this.dropdownManager.resetDropdowns(); } + handleFormSubmit(e) { + e.preventDefault(); + this.search(); + } + loadSearchParamsFromURL() { const params = gl.utils.getUrlParamsArray(); const usernameParams = this.getUsernameParams(); diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index dccf5177e351338b5c6a5ca9714710878305ecbf..868f28cd3564f65248ce6877c7ce82a370d84f14 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -15,6 +15,7 @@ } .ci-status-icon-pending, +.ci-status-icon-failed_with_warnings, .ci-status-icon-success_with_warnings { color: $gl-warning; diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page-header.scss index 4decee2c525c5a0fc660d7f450adb3703d73d144..5f4211147f30fdf05edaecf7cf40443c6474febb 100644 --- a/app/assets/stylesheets/framework/page-header.scss +++ b/app/assets/stylesheets/framework/page-header.scss @@ -46,10 +46,6 @@ font-weight: bold; } - .fa-clipboard { - color: $dropdown-title-btn-color; - } - .commit-info { &.branches { margin-left: 8px; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index cbe38b60d60b42945153be153443af3e3e33af15..da0caa30c267341bb37ed8d6dace3d3fbbdc6445 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -195,10 +195,10 @@ ul.notes { } .note-body { - overflow: auto; + overflow-x: auto; + overflow-y: hidden; .note-text { - overflow: auto; word-wrap: break-word; @include md-typography; // Reset ul style types since we're nested inside a ul already diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 12bff32bbf38643e3c85d78662ee98cb519adcb0..88ea92c5afb1e7e7d8184d984426cc3cb5a1cdfd 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -18,7 +18,6 @@ .file-finder-input:hover, .issuable-search-form:hover, .search-text-input:hover, -textarea:hover, .form-control:hover { border-color: lighten($dropdown-input-focus-border, 20%); box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%); diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index f19275770be9dfcc65d5e1c34bdb3448b064be29..6f31d4ed78977dba58a05528ac620aa289c6e98d 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -19,7 +19,8 @@ overflow: visible; } - &.ci-failed { + &.ci-failed, + &.ci-failed_with_warnings { color: $gl-danger; border-color: $gl-danger; diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 9bab140e60abc141634e4cc931916e7f5131117a..715e5893a2cd917723e377c7854362c6e93a66b8 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -1,23 +1,23 @@ module ServicesHelper def service_event_description(event) case event - when "push" + when "push", "push_events" "Event will be triggered by a push to the repository" - when "tag_push" + when "tag_push", "tag_push_events" "Event will be triggered when a new tag is pushed to the repository" - when "note" + when "note", "note_events" "Event will be triggered when someone adds a comment" - when "issue" + when "issue", "issue_events" "Event will be triggered when an issue is created/updated/closed" - when "confidential_issue" + when "confidential_issue", "confidential_issue_events" "Event will be triggered when a confidential issue is created/updated/closed" - when "merge_request" + when "merge_request", "merge_request_events" "Event will be triggered when a merge request is created/updated/merged" - when "build" + when "build", "build_events" "Event will be triggered when a build status changes" - when "wiki_page" + when "wiki_page", "wiki_page_events" "Event will be triggered when a wiki page is created/updated" - when "commit" + when "commit", "commit_events" "Event will be triggered when a commit is created/updated" end end @@ -26,4 +26,6 @@ def service_event_field_name(event) event = event.pluralize if %w[merge_request issue confidential_issue].include?(event) "#{event}_events" end + + extend self end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 3b50dad256d63d5651f1b3305f638402621b5670..9a8d81ccf4d487919603142a0672f10aca9fcfbb 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -108,15 +108,11 @@ def message_id(model) def mail_thread(model, headers = {}) add_project_headers + add_unsubscription_headers_and_links + headers["X-GitLab-#{model.class.name}-ID"] = model.id headers['X-GitLab-Reply-Key'] = reply_key - if !@labels_url && @sent_notification && @sent_notification.unsubscribable? - headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>" - - @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification) - end - if Gitlab::IncomingEmail.enabled? address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key)) address.display_name = @project.name_with_namespace @@ -172,4 +168,16 @@ def add_project_headers headers['X-GitLab-Project-Id'] = @project.id headers['X-GitLab-Project-Path'] = @project.path_with_namespace end + + def add_unsubscription_headers_and_links + return unless !@labels_url && @sent_notification && @sent_notification.unsubscribable? + + list_unsubscribe_methods = [unsubscribe_sent_notification_url(@sent_notification, force: true)] + if Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard? + list_unsubscribe_methods << "mailto:#{Gitlab::IncomingEmail.unsubscribe_address(reply_key)}" + end + + headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',') + @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification) + end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2a97e8bae4a8ea9785fd5402c2945ae9fee3d711..fab8497ec7db820fb7c09a7d86330d76c7e81604 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -128,16 +128,21 @@ def stages_name end def stages + # TODO, this needs refactoring, see gitlab-ce#26481. + + stages_query = statuses + .group('stage').select(:stage).order('max(stage_idx)') + status_sql = statuses.latest.where('stage=sg.stage').status_sql - stages_query = statuses.group('stage').select(:stage) - .order('max(stage_idx)') + warnings_sql = statuses.latest.select('COUNT(*) > 0') + .where('stage=sg.stage').failed_but_allowed.to_sql - stages_with_statuses = CommitStatus.from(stages_query, :sg). - pluck('sg.stage', status_sql) + stages_with_statuses = CommitStatus.from(stages_query, :sg) + .pluck('sg.stage', status_sql, "(#{warnings_sql})") stages_with_statuses.map do |stage| - Ci::Stage.new(self, name: stage.first, status: stage.last) + Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)]) end end diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index d035eda6df56084f45a97a673e0b1e95f0464314..ca74c91b0627b5861e8052b0c94c31a081c22ee1 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -8,10 +8,11 @@ class Stage delegate :project, to: :pipeline - def initialize(pipeline, name:, status: nil) + def initialize(pipeline, name:, status: nil, warnings: nil) @pipeline = pipeline @name = name @status = status + @warnings = warnings end def to_param @@ -39,5 +40,17 @@ def statuses def builds @builds ||= pipeline.builds.where(stage: name) end + + def success? + status.to_s == 'success' + end + + def has_warnings? + if @warnings.nil? + statuses.latest.failed_but_allowed.any? + else + @warnings + end + end end end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 90432fc4050ac029830daa658bd41f3dc67eed90..431c035496917aef14665c8d925bd487d0d8f3e5 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -1,6 +1,7 @@ module HasStatus extend ActiveSupport::Concern + DEFAULT_STATUS = 'created' AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] STARTED_STATUSES = %w[running success failed skipped] ACTIVE_STATUSES = %w[pending running] diff --git a/app/models/namespace.rb b/app/models/namespace.rb index c976a9304764861cc81389a39bc88b8dae6a054c..2dd3e000034713f16e6a45c62c45906661ba58a0 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -133,6 +133,8 @@ def move_dir Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) Gitlab::PagesTransfer.new.rename_namespace(path_was, path) + remove_exports! + # If repositories moved successfully we need to # send update instructions to users. # However we cannot allow rollback since we moved namespace dir @@ -225,6 +227,8 @@ def rm_dir GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) end end + + remove_exports! end def refresh_access_of_projects_invited_groups @@ -237,4 +241,20 @@ def refresh_access_of_projects_invited_groups def full_path_changed? path_changed? || parent_id_changed? end + + def remove_exports! + Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) + end + + def export_path + File.join(Gitlab::ImportExport.storage_path, full_path_was) + end + + def full_path_was + if parent + parent.full_path + '/' + path_was + else + path_was + end + end end diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 7c23b76676374c80af5853b58ddebc234aae87d1..3728f5642e432253a1276d1f289580718a08bc61 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -25,7 +25,7 @@ def help http://app.asana.com/-/account_api' end - def to_param + def self.to_param 'asana' end @@ -44,7 +44,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index d839221d315e04dc6e53f80a64e47e2bf3b88ef7..aeeff8917bf58e2b58fa74db3b54b06432ad6bb0 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -12,7 +12,7 @@ def description 'Project Management Software (Source Commits Endpoint)' end - def to_param + def self.to_param 'assembla' end @@ -23,7 +23,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 4819bdbef8cae042fa8f2e1a6e35e830f23f5d8d..400020ee04aa8ebd87a50159e84b433a106faf94 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -40,7 +40,7 @@ def help 'You must set up automatic revision labeling and a repository trigger in Bamboo.' end - def to_param + def self.to_param 'bamboo' end @@ -56,10 +56,6 @@ def fields ] end - def supported_events - %w(push) - end - def build_page(sha, ref) with_reactive_cache(sha, ref) {|cached| cached[:build_page] } end diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb index 338e685339a03c1536fcdb9fa973ce52482238c7..046e2809f454ffacf99ec63fd9b8e794a6147758 100644 --- a/app/models/project_services/bugzilla_service.rb +++ b/app/models/project_services/bugzilla_service.rb @@ -19,7 +19,7 @@ def description end end - def to_param + def self.to_param 'bugzilla' end end diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index e77942d8f3cff099a5be9f3f93d51e94469655a1..0956c4a4ede143a41930732633962ee2505941c6 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -24,10 +24,6 @@ def compose_service_hook hook.save end - def supported_events - %w(push) - end - def execute(data) return unless supported_events.include?(data[:object_kind]) @@ -54,7 +50,7 @@ def description 'Continuous integration and deployments' end - def to_param + def self.to_param 'buildkite' end diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index 201b94b065ba899d607c4d9ce98782c413e72e30..ebd21e3718910bcb25076fbff710d7b97e93c4d3 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -19,11 +19,11 @@ def description 'Email the builds status to a list of recipients.' end - def to_param + def self.to_param 'builds_email' end - def supported_events + def self.supported_events %w(build) end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 5af93860d09df945945a21ed4621a6cbb7b97a71..0de59af5652de6997f04aa189081107ca0e91e1f 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -12,7 +12,7 @@ def description 'Simple web-based real-time group chat' end - def to_param + def self.to_param 'campfire' end @@ -24,7 +24,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index b7ef44c3054523edc10666a5f59e1da85387ab3a..8468934425fc113a0ee40147528820d9165c13a0 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -25,7 +25,7 @@ def can_test? valid? end - def supported_events + def self.supported_events %w[push issue confidential_issue merge_request note tag_push build pipeline wiki_page] end @@ -82,19 +82,19 @@ def default_channel_placeholder def get_message(object_kind, data) case object_kind when "push", "tag_push" - PushMessage.new(data) + ChatMessage::PushMessage.new(data) when "issue" - IssueMessage.new(data) unless is_update?(data) + ChatMessage::IssueMessage.new(data) unless is_update?(data) when "merge_request" - MergeMessage.new(data) unless is_update?(data) + ChatMessage::MergeMessage.new(data) unless is_update?(data) when "note" - NoteMessage.new(data) + ChatMessage::NoteMessage.new(data) when "build" - BuildMessage.new(data) if should_build_be_notified?(data) + ChatMessage::BuildMessage.new(data) if should_build_be_notified?(data) when "pipeline" - PipelineMessage.new(data) if should_pipeline_be_notified?(data) + ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data) when "wiki_page" - WikiPageMessage.new(data) + ChatMessage::WikiPageMessage.new(data) end end diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index 0bc160af6042638afcd815222bc074e24854c570..2bcff541cc0967271ae81cf04aedc661d2cd892f 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -13,8 +13,8 @@ def valid_token?(token) ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) end - def supported_events - [] + def self.supported_events + %w() end def can_test? diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 4de0106707e644f8446a1968f11a778081d38824..82979c8bd34ed76a60563b04113f24f8b5db0d46 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -8,7 +8,7 @@ def valid_token?(token) self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index b2f426dc2acc4e6bb42fd965491a18bcdbd6a7cb..dea915a4d056c11dab5fa06ab0010094465ced7e 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -23,7 +23,7 @@ def description end end - def to_param + def self.to_param 'custom_issue_tracker' end diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb index ab353a1abe61c26e4aa720fda95f9eaf07e6e9e7..91a55514a9a64d5d57e2425379ccf4895ac9d625 100644 --- a/app/models/project_services/deployment_service.rb +++ b/app/models/project_services/deployment_service.rb @@ -5,8 +5,8 @@ class DeploymentService < Service default_value_for :category, 'deployment' - def supported_events - [] + def self.supported_events + %w() end def predefined_variables diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 4bbbebf54cba17bb64405cb1e89807b3bdc2fd6b..0a217d8cabab8379aeedda02e2f655b80ccf8712 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -32,7 +32,7 @@ def allow_target_ci? true end - def supported_events + def self.supported_events %w(push merge_request tag_push) end @@ -87,7 +87,7 @@ def description 'Drone is a Continuous Integration platform built on Docker, written in Go' end - def to_param + def self.to_param 'drone_ci' end diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 79285cbd26de1182ea5cb23d25becc3ccdd623dc..f4f913ee0b60518488ee735d2cad40cfefe0b6d2 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -12,11 +12,11 @@ def description 'Email the commits and diff of each push to a list of recipients.' end - def to_param + def self.to_param 'emails_on_push' end - def supported_events + def self.supported_events %w(push tag_push) end diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index d7b6e505191b8c82bda2d5fbf741e9c1ca1f94e8..bdf6fa6a5860eba9b017a39d57cdae8745deb7e2 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -13,7 +13,7 @@ def description 'Replaces the link to the internal wiki with a link to an external wiki.' end - def to_param + def self.to_param 'external_wiki' end @@ -29,4 +29,8 @@ def execute(_data) nil end end + + def self.supported_events + %w() + end end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index dd00275187f63533e21249d810294ce9b4ae9ee3..10a13c3fbdcb57962dd6ebe0f71bddefe819b5ed 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -12,7 +12,7 @@ def description 'Flowdock is a collaboration web app for technical teams.' end - def to_param + def self.to_param 'flowdock' end @@ -22,7 +22,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 598aca5e06d28bf517fbfbdb935efe287a7b0a6e..f271e1f1739c9186b4e8a946553dde56f14da48b 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -12,7 +12,7 @@ def description 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.' end - def to_param + def self.to_param 'gemnasium' end @@ -23,7 +23,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 6bd8d4ec5682fd9395989fd1cf76b338a4fe854e..ad4eb9536e1157ba3a0141e07b057a71e7dcca44 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -7,7 +7,7 @@ class GitlabIssueTrackerService < IssueTrackerService default_value_for :default, true - def to_param + def self.to_param 'gitlab' end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index a4b742c05dff96d489fb432b56488e4458eeef3a..3471207e557a3c0d7317f9b2c33805cf9910c56e 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -27,7 +27,7 @@ def description 'Private group chat and IM' end - def to_param + def self.to_param 'hipchat' end @@ -45,7 +45,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push issue confidential_issue merge_request note tag_push build) end diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 7355918feab407b6c52ad74f54d97e0e1e9b8570..5d93064f9b311af129db78d44cce4b4efa250114 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -17,11 +17,11 @@ def description 'gateway.' end - def to_param + def self.to_param 'irker' end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index fd4034db97921180c1b5c2ef7a170203fcbe1b7e..3edc06671d29e25cbd904c5b941e49324c950d6d 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -61,7 +61,7 @@ def initialize_properties(&block) end end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/jenkins_deprecated_service.rb b/app/models/project_services/jenkins_deprecated_service.rb index 02781f04d6bd3bef9dc386b1ea252f01babee270..46873339e15cc6014daa0931b39a967996bc0526 100644 --- a/app/models/project_services/jenkins_deprecated_service.rb +++ b/app/models/project_services/jenkins_deprecated_service.rb @@ -33,7 +33,7 @@ def help 'is deprecated. Use "Jenkins CI" service instead.' end - def to_param + def self.to_param 'jenkins_deprecated' end diff --git a/app/models/project_services/jenkins_service.rb b/app/models/project_services/jenkins_service.rb index 9c4e229ad27c71ffcc3d05685d9607f04ea86a86..992e75b77170466ee6da9423f547d4e80ad32d40 100644 --- a/app/models/project_services/jenkins_service.rb +++ b/app/models/project_services/jenkins_service.rb @@ -52,7 +52,7 @@ def hook_url File.join(jenkins_url, "project/#{project_name}").to_s end - def supported_events + def self.supported_events %w(push merge_request tag_push) end @@ -68,7 +68,7 @@ def help 'You must have installed the Git Plugin and GitLab Plugin in Jenkins' end - def to_param + def self.to_param 'jenkins' end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 2d969d2fcb66bf7539df1bab914cf349b5f7af67..2ac76e97de00d0dccc26f4b1258016fc99d9fb14 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -12,7 +12,7 @@ class JiraService < IssueTrackerService # This is confusing, but JiraService does not really support these events. # The values here are required to display correct options in the service # configuration screen. - def supported_events + def self.supported_events %w(commit merge_request) end @@ -81,7 +81,7 @@ def description end end - def to_param + def self.to_param 'jira' end diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 085125ca9dce64662005f3ca0a963699c5ddefd2..fa3cedc4354bbdfcaab1e9f4a43683b97079d6e2 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -52,7 +52,7 @@ def help 'deployments with `app=$CI_ENVIRONMENT_SLUG`' end - def to_param + def self.to_param 'kubernetes' end diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb index ee8a0b5527561b2f66793ba3ded8b7ec47da7d26..4ebc5318da1f36c14a4bdedf1b566cfa2530d608 100644 --- a/app/models/project_services/mattermost_service.rb +++ b/app/models/project_services/mattermost_service.rb @@ -7,7 +7,7 @@ def description 'Receive event notifications in Mattermost' end - def to_param + def self.to_param 'mattermost' end @@ -36,6 +36,6 @@ def default_fields end def default_channel_placeholder - "#town-square" + "town-square" end end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 2cb481182d7073fb7461481661a3416e94f0ffa5..50a011db74ed17af82060c6b81bbc33cbecef6eb 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -15,7 +15,7 @@ def description "Perform common operations on GitLab in Mattermost" end - def to_param + def self.to_param 'mattermost_slash_commands' end diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 745f9bd1b43f492f9d1c3fb999e901ba7dc73d84..ac617f409d9417bbf4cbdce6e37c27d1ada590ef 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -15,11 +15,11 @@ def description 'Email the pipelines status to a list of recipients.' end - def to_param + def self.to_param 'pipelines_email' end - def supported_events + def self.supported_events %w[pipeline] end diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index 5301f9fa0ff600aa49ef7c5b7d68a1ea89ee2687..9cc642591f4364104ad77e5ac253fe5dbaf10761 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -14,7 +14,7 @@ def description 'Project Management Software (Source Commits Endpoint)' end - def to_param + def self.to_param 'pivotaltracker' end @@ -34,7 +34,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index 3dd878e4c7d77356dcc67c684d5968a6fc1f1718..a963d27a37652e491cd712115364babbc52f942d 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -13,7 +13,7 @@ def description 'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.' end - def to_param + def self.to_param 'pushover' end @@ -61,7 +61,7 @@ def fields ] end - def supported_events + def self.supported_events %w(push) end diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index f9da273cf0849ea24bb423080bf861cdb4e8f27e..6acf611eba581c9a9e4322b3232db46553f8639c 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -19,7 +19,7 @@ def description end end - def to_param + def self.to_param 'redmine' end end diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 76d233a3cca24dd2930f53b65c5b3e93196eb888..f77d2d7c60ba925c6eaba1ce41e777b85dc9f03d 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -7,7 +7,7 @@ def description 'Receive event notifications in Slack' end - def to_param + def self.to_param 'slack' end diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 5a7cc0fb329799c90d25c92f6551f2b61cf7aa91..c34991e426287da2b74ae9ae296d5f2c5dfe2e2c 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -9,7 +9,7 @@ def description "Perform common operations on GitLab in Slack" end - def to_param + def self.to_param 'slack_slash_commands' end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 6726082048fa579298792a824fbb87ee819410b0..cbaffb8ce48558370314be5056c0c94b9ec6ceb2 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -43,14 +43,10 @@ def help 'requests build, that setting is in the vsc root advanced settings.' end - def to_param + def self.to_param 'teamcity' end - def supported_events - %w(push) - end - def fields [ { type: 'text', name: 'teamcity_url', diff --git a/app/models/service.rb b/app/models/service.rb index 97ed251099e7eee511bed93b5b73366a7c5e1e21..a16a0a1fb8cd7a28174e6b3735273ddffed2e58b 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -76,6 +76,11 @@ def help def to_param # implement inside child + self.class.to_param + end + + def self.to_param + raise NotImplementedError end def fields @@ -92,7 +97,11 @@ def event_channel_names end def event_names - supported_events.map { |event| "#{event}_events" } + self.class.event_names + end + + def self.event_names + self.supported_events.map { |event| "#{event}_events" } end def event_field(event) @@ -104,6 +113,10 @@ def global_fields end def supported_events + self.class.supported_events + end + + def self.supported_events %w(push tag_push issue confidential_issue merge_request wiki_page) end diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 1ba03c36839832088dbc773b7fa6d29d5ef2ee7f..7c4f35bbe53873cf4bbdbad6d34fbf94b22c4456 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -17,7 +17,7 @@ = icon('times') #js-dropdown-hint.dropdown-menu.hint-dropdown %ul{ 'data-dropdown' => true } - %li.filter-dropdown-item{ 'data-value' => '' } + %li.filter-dropdown-item{ 'data-action' => 'submit' } %button.btn.btn-link = icon('search') %span @@ -139,10 +139,6 @@ new MilestoneSelect(); new IssueStatusSelect(); new SubscriptionSelect(); - $('form.filter-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '&' + $(this).serialize()); - }); $(document).off('page:restore').on('page:restore', function (event) { if (gl.FilteredSearchManager) { diff --git a/changelogs/unreleased/22619-add-an-email-address-to-unsubscribe-list-header-in-email b/changelogs/unreleased/22619-add-an-email-address-to-unsubscribe-list-header-in-email new file mode 100644 index 0000000000000000000000000000000000000000..f4011b756a521e4bb2c9d49c622faf75b140b7e0 --- /dev/null +++ b/changelogs/unreleased/22619-add-an-email-address-to-unsubscribe-list-header-in-email @@ -0,0 +1,4 @@ +--- +title: Handle unsubscribe from email notifications via replying to reply+%{key}+unsubscribe@ address +merge_request: 6597 +author: diff --git a/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml b/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml new file mode 100644 index 0000000000000000000000000000000000000000..2c6883bcf7b5d7c7859b8f30f18cccfe6e0d89f8 --- /dev/null +++ b/changelogs/unreleased/22638-creating-a-branch-matching-a-wildcard-fails.yml @@ -0,0 +1,4 @@ +--- +title: Allow creating protected branches when user can merge to such branch +merge_request: 8458 +author: diff --git a/changelogs/unreleased/22974-trigger-service-events-through-api.yml b/changelogs/unreleased/22974-trigger-service-events-through-api.yml new file mode 100644 index 0000000000000000000000000000000000000000..57106e8c676b320d43179f1547ee3150c7dbfcb6 --- /dev/null +++ b/changelogs/unreleased/22974-trigger-service-events-through-api.yml @@ -0,0 +1,4 @@ +--- +title: Adds service trigger events to api +merge_request: 8324 +author: diff --git a/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml b/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml new file mode 100644 index 0000000000000000000000000000000000000000..e67a9c0da152b1f89852d61e59947ac9ca5e26d4 --- /dev/null +++ b/changelogs/unreleased/25989-fix-rogue-scrollbars-on-comments.yml @@ -0,0 +1,4 @@ +--- +title: Remove rogue scrollbars for issue comments with inline elements +merge_request: +author: diff --git a/changelogs/unreleased/26787-add-copy-icon-hover-state.yml b/changelogs/unreleased/26787-add-copy-icon-hover-state.yml new file mode 100644 index 0000000000000000000000000000000000000000..31f1812c6f805d2337484b022e440c81a920ba12 --- /dev/null +++ b/changelogs/unreleased/26787-add-copy-icon-hover-state.yml @@ -0,0 +1,4 @@ +--- +title: Add hover style to copy icon on commit page header +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/27066-textarea-border.yml b/changelogs/unreleased/27066-textarea-border.yml new file mode 100644 index 0000000000000000000000000000000000000000..e45cb3aced52795d0382b1ea29f3fe4f916b653c --- /dev/null +++ b/changelogs/unreleased/27066-textarea-border.yml @@ -0,0 +1,4 @@ +--- +title: Remove blue border from comment box hover +merge_request: +author: diff --git a/changelogs/unreleased/8-15-stable.yml b/changelogs/unreleased/8-15-stable.yml new file mode 100644 index 0000000000000000000000000000000000000000..75502e139e7b3cb64e921b35605ce0297a5dc273 --- /dev/null +++ b/changelogs/unreleased/8-15-stable.yml @@ -0,0 +1,4 @@ +--- +title: Ensure export files are removed after a namespace is deleted +merge_request: +author: diff --git a/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml b/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml new file mode 100644 index 0000000000000000000000000000000000000000..5fba0332881958fcb9ba1a3dab1fded8eb7eb3df --- /dev/null +++ b/changelogs/unreleased/feature-success-warning-icons-in-stages-builds.yml @@ -0,0 +1,4 @@ +--- +title: Use warning icon in mini-graph if stage passed conditionally +merge_request: 8503 +author: diff --git a/changelogs/unreleased/fix-api-mr-permissions.yml b/changelogs/unreleased/fix-api-mr-permissions.yml new file mode 100644 index 0000000000000000000000000000000000000000..33b677b1f2919f9e273b63fb694db2569ec6bed5 --- /dev/null +++ b/changelogs/unreleased/fix-api-mr-permissions.yml @@ -0,0 +1,4 @@ +--- +title: Don't allow project guests to subscribe to merge requests through the API +merge_request: +author: Robert Schilling diff --git a/changelogs/unreleased/fix-guest-access-posting-to-notes.yml b/changelogs/unreleased/fix-guest-access-posting-to-notes.yml new file mode 100644 index 0000000000000000000000000000000000000000..81377c0c6f0814d10a2f2c1f886d3bfdf9a5afdb --- /dev/null +++ b/changelogs/unreleased/fix-guest-access-posting-to-notes.yml @@ -0,0 +1,4 @@ +--- +title: Prevent users from creating notes on resources they can't access +merge_request: +author: diff --git a/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml b/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml new file mode 100644 index 0000000000000000000000000000000000000000..c9edd1de86c89f16befdedc24be482b588ff8e06 --- /dev/null +++ b/changelogs/unreleased/fix-users-deleting-public-deployment-keys.yml @@ -0,0 +1,4 @@ +--- +title: Prevent users from deleting system deploy keys via the project deploy key API +merge_request: +author: diff --git a/changelogs/unreleased/issue-filter-click-to-search.yml b/changelogs/unreleased/issue-filter-click-to-search.yml new file mode 100644 index 0000000000000000000000000000000000000000..c024ea48dc71836f80d876dd9e82f64b37110c4f --- /dev/null +++ b/changelogs/unreleased/issue-filter-click-to-search.yml @@ -0,0 +1,4 @@ +--- +title: allow issue filter bar to be operated with mouse only +merge_request: 8681 +author: diff --git a/changelogs/unreleased/upgrade-omniauth.yml b/changelogs/unreleased/upgrade-omniauth.yml new file mode 100644 index 0000000000000000000000000000000000000000..7e0334566dcaa91dd1af307ec5b8f9dcff779b2d --- /dev/null +++ b/changelogs/unreleased/upgrade-omniauth.yml @@ -0,0 +1,4 @@ +--- +title: Upgrade omniauth gem to 1.3.2 +merge_request: +author: diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 1ef34c79971b9629be492caeb1515a52fdedd029..e4a0e0b92bc43be875f23cb38b90297280dafb87 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -9,7 +9,7 @@ code is effective, understandable, and maintainable. Any developer can, and is encouraged to, perform code review on merge requests of colleagues and contributors. However, the final decision to accept a merge -request is up to one of our merge request "endbosses", denoted on the +request is up to one the project's maintainers, denoted on the [team page](https://about.gitlab.com/team). ## Everyone @@ -81,15 +81,15 @@ balance in how deep the reviewer can interfere with the code created by a reviewee. - Learning how to find the right balance takes time; that is why we have - minibosses that become merge request endbosses after some time spent on - reviewing merge requests. + reviewers that become maintainers after some time spent on reviewing merge + requests. - Finding bugs and improving code style is important, but thinking about good design is important as well. Building abstractions and good design is what makes it possible to hide complexity and makes future changes easier. - Asking the reviewee to change the design sometimes means the complete rewrite - of the contributed code. It's usually a good idea to ask another merge - request endboss before doing it, but have the courage to do it when you - believe it is important. + of the contributed code. It's usually a good idea to ask another maintainer or + reviewer before doing it, but have the courage to do it when you believe it is + important. - There is a difference in doing things right and doing things right now. Ideally, we should do the former, but in the real world we need the latter as well. A good example is a security fix which should be released as soon as diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md index 0363bf8c1d54b1f5c3d377e6e38e0b5343ae4dfd..8232a0a113cbdde742186e5265fe6fe1b24b6c31 100644 --- a/doc/development/merge_request_performance_guidelines.md +++ b/doc/development/merge_request_performance_guidelines.md @@ -3,7 +3,7 @@ To ensure a merge request does not negatively impact performance of GitLab _every_ merge request **must** adhere to the guidelines outlined in this document. There are no exceptions to this rule unless specifically discussed -with and agreed upon by merge request endbosses and performance specialists. +with and agreed upon by backend maintainers and performance specialists. To measure the impact of a merge request you can use [Sherlock](profiling.md#sherlock). It's also highly recommended that you read @@ -40,9 +40,9 @@ section below for more information. about the impact. Sometimes it's hard to assess the impact of a merge request. In this case you -should ask one of the merge request (mini) endbosses to review your changes. You -can find a list of these endbosses at . An -endboss in turn can request a performance specialist to review the changes. +should ask one of the merge request reviewers to review your changes. You can +find a list of these reviewers at . A reviewer +in turn can request a performance specialist to review the changes. ## Query Counts diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 853607308412a80e83fb0f562673b1bda4b98690..f6cb17bafd819f273e66dd55293cb026ce01ac43 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -105,15 +105,19 @@ class DeployKeys < Grape::API present key.deploy_key, with: Entities::SSHKey end - desc 'Delete existing deploy key of currently authenticated user' do + desc 'Delete deploy key for a project' do success Key end params do requires :key_id, type: Integer, desc: 'The ID of the deploy key' end delete ":id/#{path}/:key_id" do - key = user_project.deploy_keys.find(params[:key_id]) - key.destroy + key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id]) + if key + key.destroy + else + not_found!('Deploy Key') + end end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index dc8c2eb0151e3cc8d6d5acd0ea0a9414f07c020f..651342c74009b2b1c1bbbd882133dcbe5fd46a35 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -90,6 +90,12 @@ def find_project_merge_request(id) MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id) end + def find_merge_request_with_access(id, access_level = :read_merge_request) + merge_request = user_project.merge_requests.find(id) + authorize! access_level, merge_request + merge_request + end + def authenticate! unauthorized! unless current_user end diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index 07435d78468ea343777a66303fc611bba9f9feb1..bc3d69f6904b6de2cbd08f20655f15c1cf2d40b7 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -15,10 +15,8 @@ class MergeRequestDiffs < Grape::API end get ":id/merge_requests/:merge_request_id/versions" do - merge_request = user_project.merge_requests. - find(params[:merge_request_id]) + merge_request = find_merge_request_with_access(params[:merge_request_id]) - authorize! :read_merge_request, merge_request present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff end @@ -34,10 +32,8 @@ class MergeRequestDiffs < Grape::API end get ":id/merge_requests/:merge_request_id/versions/:version_id" do - merge_request = user_project.merge_requests. - find(params[:merge_request_id]) + merge_request = find_merge_request_with_access(params[:merge_request_id]) - authorize! :read_merge_request, merge_request present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 2d79272cfb68c5ed50d483126b6ee36199348b22..b79e1b54af859c482cdc0b51ea4904c7ef54f800 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -119,8 +119,8 @@ def handle_merge_request_errors!(errors) success Entities::MergeRequest end get path do - merge_request = find_project_merge_request(params[:merge_request_id]) - authorize! :read_merge_request, merge_request + merge_request = find_merge_request_with_access(params[:merge_request_id]) + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end @@ -128,8 +128,8 @@ def handle_merge_request_errors!(errors) success Entities::RepoCommit end get "#{path}/commits" do - merge_request = find_project_merge_request(params[:merge_request_id]) - authorize! :read_merge_request, merge_request + merge_request = find_merge_request_with_access(params[:merge_request_id]) + present merge_request.commits, with: Entities::RepoCommit end @@ -137,8 +137,8 @@ def handle_merge_request_errors!(errors) success Entities::MergeRequestChanges end get "#{path}/changes" do - merge_request = find_project_merge_request(params[:merge_request_id]) - authorize! :read_merge_request, merge_request + merge_request = find_merge_request_with_access(params[:merge_request_id]) + present merge_request, with: Entities::MergeRequestChanges, current_user: current_user end @@ -156,8 +156,7 @@ def handle_merge_request_errors!(errors) :remove_source_branch end put path do - merge_request = find_project_merge_request(params.delete(:merge_request_id)) - authorize! :update_merge_request, merge_request + merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request) mr_params = declared_params(include_missing: false) mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? @@ -236,10 +235,7 @@ def handle_merge_request_errors!(errors) use :pagination end get "#{path}/comments" do - merge_request = find_project_merge_request(params[:merge_request_id]) - - authorize! :read_merge_request, merge_request - + merge_request = find_merge_request_with_access(params[:merge_request_id]) present paginate(merge_request.notes.fresh), with: Entities::MRNote end @@ -251,8 +247,7 @@ def handle_merge_request_errors!(errors) requires :note, type: String, desc: 'The text of the comment' end post "#{path}/comments" do - merge_request = find_project_merge_request(params[:merge_request_id]) - authorize! :create_note, merge_request + merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note) opts = { note: params[:note], @@ -276,7 +271,7 @@ def handle_merge_request_errors!(errors) use :pagination end get "#{path}/closes_issues" do - merge_request = find_project_merge_request(params[:merge_request_id]) + merge_request = find_merge_request_with_access(params[:merge_request_id]) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) present paginate(issues), with: issue_entity(user_project), current_user: current_user end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 284e4cf549ad9fb7920046c7dee90eaced1116fa..4d2a8f482670c35bd5ea1a5208de01918af8a8da 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -70,21 +70,27 @@ class Notes < Grape::API end post ":id/#{noteables_str}/:noteable_id/notes" do opts = { - note: params[:body], - noteable_type: noteables_str.classify, - noteable_id: params[:noteable_id] + note: params[:body], + noteable_type: noteables_str.classify, + noteable_id: params[:noteable_id] } - if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) - opts[:created_at] = params[:created_at] - end + noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) + + if can?(current_user, noteable_read_ability_name(noteable), noteable) + if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) + opts[:created_at] = params[:created_at] + end - note = ::Notes::CreateService.new(user_project, current_user, opts).execute + note = ::Notes::CreateService.new(user_project, current_user, opts).execute - if note.valid? - present note, with: Entities::const_get(note.class.name) + if note.valid? + present note, with: Entities::const_get(note.class.name) + else + not_found!("Note #{note.errors.messages}") + end else - not_found!("Note #{note.errors.messages}") + not_found!("Note") end end diff --git a/lib/api/services.rb b/lib/api/services.rb index 20c9762e68d86bb3f3e5049d0de733de760eb880..4781acc3b27778e8d1c1a49a62fe84a5655505ca 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -145,7 +145,7 @@ class Services < Grape::API name: :room, type: String, desc: 'Campfire room' - }, + } ], 'custom-issue-tracker' => [ { @@ -580,7 +580,38 @@ class Services < Grape::API desc: 'Should unstable builds be treated as passing?' } ] - }.freeze + } + + service_classes = [ + AsanaService, + AssemblaService, + BambooService, + BugzillaService, + BuildkiteService, + BuildsEmailService, + CampfireService, + CustomIssueTrackerService, + DroneCiService, + EmailsOnPushService, + ExternalWikiService, + FlowdockService, + GemnasiumService, + HipchatService, + IrkerService, + JiraService, + KubernetesService, + MattermostSlashCommandsService, + SlackSlashCommandsService, + PipelinesEmailService, + PivotaltrackerService, + PushoverService, + RedmineService, + SlackService, + MattermostService, + TeamcityService, + JenkinsService, + JenkinsDeprecatedService + ].freeze trigger_services = { 'mattermost-slash-commands' => [ @@ -614,6 +645,19 @@ def service_attributes(service) services.each do |service_slug, settings| desc "Set #{service_slug} service for project" params do + service_classes.each do |service| + event_names = service.try(:event_names) || [] + event_names.each do |event_name| + services[service.to_param.tr("_", "-")] << { + required: false, + name: event_name.to_sym, + type: String, + desc: ServicesHelper.service_event_description(event_name) + } + end + end + services.freeze + settings.each do |setting| if setting[:required] requires setting[:name], type: setting[:type], desc: setting[:desc] @@ -627,7 +671,7 @@ def service_attributes(service) service_params = declared_params(include_missing: false).merge(active: true) if service.update_attributes(service_params) - true + present service, with: Entities::ProjectService, include_passwords: current_user.is_admin? else render_api_error!('400 Bad Request', 400) end diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 10749b34004fe8e0e62dde7f09722cb4c08c07a4..e11d7537cc9c9fbeb90a71d6a376b7ce3dd5f548 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -3,8 +3,8 @@ class Subscriptions < Grape::API before { authenticate! } subscribable_types = { - 'merge_request' => proc { |id| user_project.merge_requests.find(id) }, - 'merge_requests' => proc { |id| user_project.merge_requests.find(id) }, + 'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) }, + 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) }, 'issues' => proc { |id| find_project_issue(id) }, 'labels' => proc { |id| find_project_label(id) }, } diff --git a/lib/api/todos.rb b/lib/api/todos.rb index ed8f48aa1e3b3a55726a091fc96d28bdeca2ee1f..9bd077263a7e013b7d567da13718394b657c5ab4 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -5,7 +5,7 @@ class Todos < Grape::API before { authenticate! } ISSUABLE_TYPES = { - 'merge_requests' => ->(id) { user_project.merge_requests.find(id) }, + 'merge_requests' => ->(id) { find_merge_request_with_access(id) }, 'issues' => ->(id) { find_project_issue(id) } } diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index eee9a64120b287ad29724d425d1432717e75074b..38ac6edc9f188ece3d76b7a273e37cd07982f9d9 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -4,8 +4,11 @@ module Status module Build class Factory < Status::Factory def self.extended_statuses - [Status::Build::Stop, Status::Build::Play, - Status::Build::Cancelable, Status::Build::Retryable] + [[Status::Build::Cancelable, + Status::Build::Retryable], + [Status::Build::FailedAllowed, + Status::Build::Play, + Status::Build::Stop]] end def self.common_helpers diff --git a/lib/gitlab/ci/status/build/failed_allowed.rb b/lib/gitlab/ci/status/build/failed_allowed.rb new file mode 100644 index 0000000000000000000000000000000000000000..807afe24bd5765f695cd3cf88d8c6b618a4930c8 --- /dev/null +++ b/lib/gitlab/ci/status/build/failed_allowed.rb @@ -0,0 +1,27 @@ +module Gitlab + module Ci + module Status + module Build + class FailedAllowed < SimpleDelegator + include Status::Extended + + def label + 'failed (allowed to fail)' + end + + def icon + 'icon_status_warning' + end + + def group + 'failed_with_warnings' + end + + def self.matches?(build, user) + build.failed? && build.allow_failure? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb index ae9ef895df49d27e3bb9c81ac02df667fee58e1d..15836c699c7ce68e598d16a2046cefa68add68c0 100644 --- a/lib/gitlab/ci/status/factory.rb +++ b/lib/gitlab/ci/status/factory.rb @@ -5,41 +5,46 @@ class Factory def initialize(subject, user) @subject = subject @user = user + @status = subject.status || HasStatus::DEFAULT_STATUS end def fabricate! - if extended_status - extended_status.new(core_status) - else + if extended_statuses.none? core_status + else + compound_extended_status end end - def self.extended_statuses - [] + def core_status + Gitlab::Ci::Status + .const_get(@status.capitalize) + .new(@subject, @user) + .extend(self.class.common_helpers) end - def self.common_helpers - Module.new + def compound_extended_status + extended_statuses.inject(core_status) do |status, extended| + extended.new(status) + end end - private + def extended_statuses + return @extended_statuses if defined?(@extended_statuses) - def simple_status - @simple_status ||= @subject.status || :created + groups = self.class.extended_statuses.map do |group| + Array(group).find { |status| status.matches?(@subject, @user) } + end + + @extended_statuses = groups.flatten.compact end - def core_status - Gitlab::Ci::Status - .const_get(simple_status.capitalize) - .new(@subject, @user) - .extend(self.class.common_helpers) + def self.extended_statuses + [] end - def extended_status - @extended ||= self.class.extended_statuses.find do |status| - status.matches?(@subject, @user) - end + def self.common_helpers + Module.new end end end diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb index 16dcb326be9c46503d0fcd6a19ee6fc1057159b0..13c8343b12a2b6170fab96d8b9323f2d3eded876 100644 --- a/lib/gitlab/ci/status/pipeline/factory.rb +++ b/lib/gitlab/ci/status/pipeline/factory.rb @@ -4,7 +4,7 @@ module Status module Pipeline class Factory < Status::Factory def self.extended_statuses - [Pipeline::SuccessWithWarnings] + [Status::SuccessWarning] end def self.common_helpers diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb deleted file mode 100644 index 24bf8b869e04cb45900ace17f9e42402dac14792..0000000000000000000000000000000000000000 --- a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Gitlab - module Ci - module Status - module Pipeline - class SuccessWithWarnings < SimpleDelegator - include Status::Extended - - def text - 'passed' - end - - def label - 'passed with warnings' - end - - def icon - 'icon_status_warning' - end - - def group - 'success_with_warnings' - end - - def self.matches?(pipeline, user) - pipeline.success? && pipeline.has_warnings? - end - end - end - end - end -end diff --git a/lib/gitlab/ci/status/stage/factory.rb b/lib/gitlab/ci/status/stage/factory.rb index 689a5dd45bc3911e9d323c0fde6c02ec60a4afce..4c37f084d07d2c6d8d997483a7d83ddd794e8e30 100644 --- a/lib/gitlab/ci/status/stage/factory.rb +++ b/lib/gitlab/ci/status/stage/factory.rb @@ -3,6 +3,10 @@ module Ci module Status module Stage class Factory < Status::Factory + def self.extended_statuses + [Status::SuccessWarning] + end + def self.common_helpers Status::Stage::Common end diff --git a/lib/gitlab/ci/status/success_warning.rb b/lib/gitlab/ci/status/success_warning.rb new file mode 100644 index 0000000000000000000000000000000000000000..d4cdab6957a00c835c7996b02aac7f62bb14e88a --- /dev/null +++ b/lib/gitlab/ci/status/success_warning.rb @@ -0,0 +1,33 @@ +module Gitlab + module Ci + module Status + ## + # Extended status used when pipeline or stage passed conditionally. + # This means that failed jobs that are allowed to fail were present. + # + class SuccessWarning < SimpleDelegator + include Status::Extended + + def text + 'passed' + end + + def label + 'passed with warnings' + end + + def icon + 'icon_status_warning' + end + + def group + 'success_with_warnings' + end + + def self.matches?(subject, user) + subject.success? && subject.has_warnings? + end + end + end + end +end diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb index bd3267e2a80ba54f31753d732b664b19e8838583..bd2f5d3615eba9c75e6087e4a9666e7e4f906edb 100644 --- a/lib/gitlab/email/handler.rb +++ b/lib/gitlab/email/handler.rb @@ -1,10 +1,11 @@ require 'gitlab/email/handler/create_note_handler' require 'gitlab/email/handler/create_issue_handler' +require 'gitlab/email/handler/unsubscribe_handler' module Gitlab module Email module Handler - HANDLERS = [CreateNoteHandler, CreateIssueHandler] + HANDLERS = [UnsubscribeHandler, CreateNoteHandler, CreateIssueHandler] def self.for(mail, mail_key) HANDLERS.find do |klass| diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index 7cccf465334f5ceacfc68b2809ca704e73ac7aab..3f6ace0311a6e54b39ff04215d25ca0a2255958b 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -9,52 +9,13 @@ def initialize(mail, mail_key) @mail_key = mail_key end - def message - @message ||= process_message - end - - def author + def can_execute? raise NotImplementedError end - def project + def execute raise NotImplementedError end - - private - - def validate_permission!(permission) - raise UserNotFoundError unless author - raise UserBlockedError if author.blocked? - raise ProjectNotFound unless author.can?(:read_project, project) - raise UserNotAuthorizedError unless author.can?(permission, project) - end - - def process_message - message = ReplyParser.new(mail).execute.strip - add_attachments(message) - end - - def add_attachments(reply) - attachments = Email::AttachmentUploader.new(mail).execute(project) - - reply + attachments.map do |link| - "\n\n#{link[:markdown]}" - end.join - end - - def verify_record!(record:, invalid_exception:, record_name:) - return if record.persisted? - return if record.errors.key?(:commands_only) - - error_title = "The #{record_name} could not be created for the following reasons:" - - msg = error_title + record.errors.full_messages.map do |error| - "\n\n- #{error}" - end.join - - raise invalid_exception, msg - end end end end diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 9f90a3ec2b2f70645ae27db6dbabf5557d31cfb8..127fae159d546b67abbc2eca196c69e9961f5237 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -5,6 +5,7 @@ module Gitlab module Email module Handler class CreateIssueHandler < BaseHandler + include ReplyProcessing attr_reader :project_path, :incoming_email_token def initialize(mail, mail_key) diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 447c7a6a6b9465e63ea3c8ada822cef1d13b0233..d87ba427f4b7c06b658dbeaa8389aa72d9323367 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -1,10 +1,13 @@ require 'gitlab/email/handler/base_handler' +require 'gitlab/email/handler/reply_processing' module Gitlab module Email module Handler class CreateNoteHandler < BaseHandler + include ReplyProcessing + def can_handle? mail_key =~ /\A\w+\z/ end @@ -24,6 +27,8 @@ def execute record_name: 'comment') end + private + def author sent_notification.recipient end @@ -36,8 +41,6 @@ def sent_notification @sent_notification ||= SentNotification.for(mail_key) end - private - def create_note Notes::CreateService.new( project, diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb new file mode 100644 index 0000000000000000000000000000000000000000..32c5caf93e8118af01dbe3b41962b4301b4d910f --- /dev/null +++ b/lib/gitlab/email/handler/reply_processing.rb @@ -0,0 +1,54 @@ +module Gitlab + module Email + module Handler + module ReplyProcessing + private + + def author + raise NotImplementedError + end + + def project + raise NotImplementedError + end + + def message + @message ||= process_message + end + + def process_message + message = ReplyParser.new(mail).execute.strip + add_attachments(message) + end + + def add_attachments(reply) + attachments = Email::AttachmentUploader.new(mail).execute(project) + + reply + attachments.map do |link| + "\n\n#{link[:markdown]}" + end.join + end + + def validate_permission!(permission) + raise UserNotFoundError unless author + raise UserBlockedError if author.blocked? + raise ProjectNotFound unless author.can?(:read_project, project) + raise UserNotAuthorizedError unless author.can?(permission, project) + end + + def verify_record!(record:, invalid_exception:, record_name:) + return if record.persisted? + return if record.errors.key?(:commands_only) + + error_title = "The #{record_name} could not be created for the following reasons:" + + msg = error_title + record.errors.full_messages.map do |error| + "\n\n- #{error}" + end.join + + raise invalid_exception, msg + end + end + end + end +end diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb new file mode 100644 index 0000000000000000000000000000000000000000..97d7a8d65ff95e168e8d684708c18c75167355ff --- /dev/null +++ b/lib/gitlab/email/handler/unsubscribe_handler.rb @@ -0,0 +1,32 @@ +require 'gitlab/email/handler/base_handler' + +module Gitlab + module Email + module Handler + class UnsubscribeHandler < BaseHandler + def can_handle? + mail_key =~ /\A\w+#{Regexp.escape(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)}\z/ + end + + def execute + raise SentNotificationNotFoundError unless sent_notification + return unless sent_notification.unsubscribable? + + noteable = sent_notification.noteable + raise NoteableNotFoundError unless noteable + noteable.unsubscribe(sent_notification.recipient) + end + + private + + def sent_notification + @sent_notification ||= SentNotification.for(reply_key) + end + + def reply_key + mail_key.sub(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX, '') + end + end + end + end +end diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 801dfde9a368f90edefa2f7d20331c6ec670e2cf..b91012d6405b182ec41ccc16b3772b0b14040748 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -1,5 +1,6 @@ module Gitlab module IncomingEmail + UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze WILDCARD_PLACEHOLDER = '%{key}'.freeze class << self @@ -18,7 +19,11 @@ def supports_issue_creation? end def reply_address(key) - config.address.gsub(WILDCARD_PLACEHOLDER, key) + config.address.sub(WILDCARD_PLACEHOLDER, key) + end + + def unsubscribe_address(key) + config.address.sub(WILDCARD_PLACEHOLDER, "#{key}#{UNSUBSCRIBE_SUFFIX}") end def key_from_address(address) @@ -49,7 +54,7 @@ def address_regex return nil unless wildcard_address regex = Regexp.escape(wildcard_address) - regex = regex.gsub(Regexp.escape('%{key}'), "(.+)") + regex = regex.sub(Regexp.escape(WILDCARD_PLACEHOLDER), '(.+)') Regexp.new(regex).freeze end end diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 6c7e673fb9fbf332714a07d18a4c26fba75c0edb..6ce9b22929435512b83b58d317e0a890ac5fed43 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -35,7 +35,9 @@ def can_push_to_branch?(ref) return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user) access_levels = project.protected_branches.matching(ref).map(&:push_access_levels).flatten - access_levels.any? { |access_level| access_level.check_access(user) } + has_access = access_levels.any? { |access_level| access_level.check_access(user) } + + has_access || !project.repository.branch_exists?(ref) && can_merge_to_branch?(ref) else user.can?(:push_code, project) end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 2e44b5128b421dcbf4527167112b614eeb484fcf..a6e708c01e4a08910173d3abaae449440ac02d35 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -54,6 +54,7 @@ context 'on successful update' do it 'sets the flash' do expect(service).to receive(:to_param).and_return('hipchat') + expect(service).to receive(:event_names).and_return(HipchatService.event_names) put :update, namespace_id: project.namespace.id, diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb index ee3b17b8bf1fe0b485b215b4a79b5ae41d5977e2..7f557b25ccbb11af99b8e5b4345cd6b6968c9843 100644 --- a/spec/factories/ci/stages.rb +++ b/spec/factories/ci/stages.rb @@ -3,11 +3,12 @@ transient do name 'test' status nil + warnings nil pipeline factory: :ci_empty_pipeline end initialize_with do - Ci::Stage.new(pipeline, name: name, status: status) + Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings) end end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index a5b88812b75594e1b7f75571b3de04ef85e46049..87a8f62687aa918051731b8321e7b7862038228e 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -10,7 +10,7 @@ end describe "GET /admin/projects" do - let!(:archived_project) { create :project, :public, archived: true } + let!(:archived_project) { create :project, :public, :archived } before do visit admin_projects_path diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index 30b80aa82b02fe2bb81562c5f1d7c67029abb8e5..78a11ffee99d466c7921f8a18a2c986cb8acfbfc 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -7,7 +7,7 @@ include_examples 'project features apply to issuables', MergeRequest context 'archived issuable' do - let(:project_archived) { create(:project, group: group, merge_requests_access_level: ProjectFeature::ENABLED, archived: true) } + let(:project_archived) { create(:project, :archived, group: group, merge_requests_access_level: ProjectFeature::ENABLED) } let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') } let(:access_level) { ProjectFeature::ENABLED } let(:user) { user_in_group } diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 16dcc487812e4f810476f5c0d0e273963cd21d07..8a155c3bfc5dbcbda63a04f9930af76eb3e1af1a 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -134,14 +134,14 @@ def click_assignee(text) click_button 'Assigned to me' end - expect(filtered_search.value).to eq("assignee:#{user.to_reference}") + expect(filtered_search.value).to eq("assignee:#{user.to_reference} ") end it 'fills in the assignee username when the assignee has not been filtered' do click_assignee(user_jacob.name) expect(page).to have_css(js_dropdown_assignee, visible: false) - expect(filtered_search.value).to eq("assignee:@#{user_jacob.username}") + expect(filtered_search.value).to eq("assignee:@#{user_jacob.username} ") end it 'fills in the assignee username when the assignee has been filtered' do @@ -149,14 +149,14 @@ def click_assignee(text) click_assignee(user.name) expect(page).to have_css(js_dropdown_assignee, visible: false) - expect(filtered_search.value).to eq("assignee:@#{user.username}") + expect(filtered_search.value).to eq("assignee:@#{user.username} ") end it 'selects `no assignee`' do find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click expect(page).to have_css(js_dropdown_assignee, visible: false) - expect(filtered_search.value).to eq("assignee:none") + expect(filtered_search.value).to eq("assignee:none ") end end diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 464749d01e307fff82832a1271bf302973a2e6f3..a5d5d9d4c5e025f9db44d1cc7b0552b2f74ebd9b 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -121,14 +121,14 @@ def click_author(text) click_author(user_jacob.name) expect(page).to have_css(js_dropdown_author, visible: false) - expect(filtered_search.value).to eq("author:@#{user_jacob.username}") + expect(filtered_search.value).to eq("author:@#{user_jacob.username} ") end it 'fills in the author username when the author has been filtered' do click_author(user.name) expect(page).to have_css(js_dropdown_author, visible: false) - expect(filtered_search.value).to eq("author:@#{user.username}") + expect(filtered_search.value).to eq("author:@#{user.username} ") end end diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index 89c144141c9366dbd80165554dbcef2fb00e0792..bea00160f96413440cb70ae47da2a1c255be3a0d 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -159,7 +159,7 @@ def click_label(text) click_label(bug_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{bug_label.title}") + expect(filtered_search.value).to eq("label:~#{bug_label.title} ") end it 'fills in the label name when the label is partially filled' do @@ -167,49 +167,49 @@ def click_label(text) click_label(bug_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{bug_label.title}") + expect(filtered_search.value).to eq("label:~#{bug_label.title} ") end it 'fills in the label name that contains multiple words' do click_label(two_words_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\"") + expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\" ") end it 'fills in the label name that contains multiple words and is very long' do click_label(long_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~\"#{long_label.title}\"") + expect(filtered_search.value).to eq("label:~\"#{long_label.title}\" ") end it 'fills in the label name that contains double quotes' do click_label(wont_fix_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}'") + expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}' ") end it 'fills in the label name with the correct capitalization' do click_label(uppercase_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{uppercase_label.title}") + expect(filtered_search.value).to eq("label:~#{uppercase_label.title} ") end it 'fills in the label name with special characters' do click_label(special_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{special_label.title}") + expect(filtered_search.value).to eq("label:~#{special_label.title} ") end it 'selects `no label`' do find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:none") + expect(filtered_search.value).to eq("label:none ") end end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index e5a271b663f186a6c4b7053e2365aeb31bdb4538..134e58ad586e1ccf0e18cab8360c81b193126848 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -127,7 +127,7 @@ def click_static_milestone(text) click_milestone(milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{milestone.title} ") end it 'fills in the milestone name when the milestone is partially filled' do @@ -135,56 +135,56 @@ def click_static_milestone(text) click_milestone(milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{milestone.title} ") end it 'fills in the milestone name that contains multiple words' do click_milestone(two_words_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\"") + expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\" ") end it 'fills in the milestone name that contains multiple words and is very long' do click_milestone(long_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\"") + expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\" ") end it 'fills in the milestone name that contains double quotes' do click_milestone(wont_fix_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}'") + expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}' ") end it 'fills in the milestone name with the correct capitalization' do click_milestone(uppercase_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title} ") end it 'fills in the milestone name with special characters' do click_milestone(special_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{special_milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{special_milestone.title} ") end it 'selects `no milestone`' do click_static_milestone('No Milestone') expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:none") + expect(filtered_search.value).to eq("milestone:none ") end it 'selects `upcoming milestone`' do click_static_milestone('Upcoming') expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:upcoming") + expect(filtered_search.value).to eq("milestone:upcoming ") end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 1cdac520181c112e5d85204f1ef0770afa972cf4..f48a0193545511eb59faf2f8683164577085848f 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -539,7 +539,7 @@ def select_search_at_index(pos) click_button user2.username end - expect(filtered_search.value).to eq("author:@#{user2.username}") + expect(filtered_search.value).to eq("author:@#{user2.username} ") end it 'changes label' do @@ -551,7 +551,7 @@ def select_search_at_index(pos) click_button label.name end - expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name}") + expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name} ") end it 'changes label correctly space is in previous label' do @@ -563,7 +563,7 @@ def select_search_at_index(pos) click_button label.name end - expect(filtered_search.value).to eq("label:~#{label.name}") + expect(filtered_search.value).to eq("label:~#{label.name} ") end end diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d0bafc6168cf7aeb44b73c9d416e8b54e738b20c --- /dev/null +++ b/spec/features/projects/import_export/namespace_export_file_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +feature 'Import/Export - Namespace export file cleanup', feature: true, js: true do + let(:export_path) { "#{Dir::tmpdir}/import_file_spec" } + let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } + + let(:project) { create(:empty_project) } + + background do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + end + + after do + FileUtils.rm_rf(export_path, secure: true) + end + + context 'admin user' do + before do + login_as(:admin) + end + + context 'moving the namespace' do + scenario 'removes the export file' do + setup_export_project + + old_export_path = project.export_path.dup + + expect(File).to exist(old_export_path) + + project.namespace.update(path: 'new_path') + + expect(File).not_to exist(old_export_path) + end + end + + context 'deleting the namespace' do + scenario 'removes the export file' do + setup_export_project + + old_export_path = project.export_path.dup + + expect(File).to exist(old_export_path) + + project.namespace.destroy + + expect(File).not_to exist(old_export_path) + end + end + + def setup_export_project + visit edit_namespace_project_path(project.namespace, project) + + expect(page).to have_content('Export project') + + click_link 'Export project' + + visit edit_namespace_project_path(project.namespace, project) + + expect(page).to have_content('Download export') + end + end +end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 1d698d465785b013bf2ebeb65843b2a57ffafb39..1c322e18a3eef6e86218d833e8a0601c854c61ba 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -6,7 +6,7 @@ let(:project1) { create(:project) } let(:project2) { create(:project, forked_from_project: project1) } - let(:project3) { create(:project, forked_from_project: project1, archived: true) } + let(:project3) { create(:project, :archived, forked_from_project: project1) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb index 8488dbd2a1634bd495e910d8aac6c4afcc75b97b..dea87980e25f8459520c8c22ac81d2bd9754b785 100644 --- a/spec/finders/move_to_project_finder_spec.rb +++ b/spec/finders/move_to_project_finder_spec.rb @@ -36,7 +36,7 @@ it 'does not return archived projects' do reporter_project.team << [user, :reporter] - reporter_project.update_attributes(archived: true) + reporter_project.archive! other_reporter_project = create(:empty_project) other_reporter_project.team << [user, :reporter] diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 index d0d27ceb4a6f8b012da929d357e8b5e4ab5410b3..4bd45eb457d07505dd27fe9edb94485ce74efca1 100644 --- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 +++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 @@ -31,7 +31,7 @@ it('should add tokenName and tokenValue', () => { gl.FilteredSearchDropdownManager.addWordToInput('label', 'none'); - expect(getInputValue()).toBe('label:none'); + expect(getInputValue()).toBe('label:none '); }); }); @@ -45,13 +45,13 @@ it('should replace tokenValue', () => { setInputValue('author:roo'); gl.FilteredSearchDropdownManager.addWordToInput('author', '@root'); - expect(getInputValue()).toBe('author:@root'); + expect(getInputValue()).toBe('author:@root '); }); it('should add tokenValues containing spaces', () => { setInputValue('label:~"test'); gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\''); - expect(getInputValue()).toBe('label:~\'"test me"\''); + expect(getInputValue()).toBe('label:~\'"test me"\' '); }); }); }); diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index dccb29b5ef606b380137e2b9090bf616bbfe4afe..0c40fca0c1a649186c5ecb7d2c869144270ae369 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -3,15 +3,23 @@ describe Gitlab::Ci::Status::Build::Factory do let(:user) { create(:user) } let(:project) { build.project } - - subject { described_class.new(build, user) } - let(:status) { subject.fabricate! } + let(:status) { factory.fabricate! } + let(:factory) { described_class.new(build, user) } before { project.team << [user, :developer] } context 'when build is successful' do let(:build) { create(:ci_build, :success) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable] + end + it 'fabricates a retryable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Retryable end @@ -26,24 +34,72 @@ end context 'when build is failed' do - let(:build) { create(:ci_build, :failed) } + context 'when build is not allowed to fail' do + let(:build) { create(:ci_build, :failed) } - it 'fabricates a retryable build status' do - expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable] + end + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'icon_status_failed' + expect(status.label).to eq 'failed' + expect(status).to have_details + expect(status).to have_action + end end - it 'fabricates status with correct details' do - expect(status.text).to eq 'failed' - expect(status.icon).to eq 'icon_status_failed' - expect(status.label).to eq 'failed' - expect(status).to have_details - expect(status).to have_action + context 'when build is allowed to fail' do + let(:build) { create(:ci_build, :failed, :allowed_to_fail) } + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable, + Gitlab::Ci::Status::Build::FailedAllowed] + end + + it 'fabricates a failed but allowed build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::FailedAllowed + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'icon_status_warning' + expect(status.label).to eq 'failed (allowed to fail)' + expect(status).to have_details + expect(status).to have_action + expect(status.action_title).to include 'Retry' + expect(status.action_path).to include 'retry' + end end end context 'when build is a canceled' do let(:build) { create(:ci_build, :canceled) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Canceled + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable] + end + it 'fabricates a retryable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Retryable end @@ -60,6 +116,15 @@ context 'when build is running' do let(:build) { create(:ci_build, :running) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Running + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Cancelable] + end + it 'fabricates a canceable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable end @@ -76,6 +141,15 @@ context 'when build is pending' do let(:build) { create(:ci_build, :pending) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Pending + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Cancelable] + end + it 'fabricates a cancelable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable end @@ -92,6 +166,14 @@ context 'when build is skipped' do let(:build) { create(:ci_build, :skipped) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'does not match extended statuses' do + expect(factory.extended_statuses).to be_empty + end + it 'fabricates a core skipped status' do expect(status).to be_a Gitlab::Ci::Status::Skipped end @@ -109,6 +191,15 @@ context 'when build is a play action' do let(:build) { create(:ci_build, :playable) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Play] + end + it 'fabricates a core skipped status' do expect(status).to be_a Gitlab::Ci::Status::Build::Play end @@ -119,12 +210,22 @@ expect(status.label).to eq 'manual play action' expect(status).to have_details expect(status).to have_action + expect(status.action_path).to include 'play' end end context 'when build is an environment stop action' do let(:build) { create(:ci_build, :playable, :teardown_environment) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Stop] + end + it 'fabricates a core skipped status' do expect(status).to be_a Gitlab::Ci::Status::Build::Stop end diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..20f714597385bf5170dbd754371710500502c5a5 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::FailedAllowed do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'returns a warning icon' do + expect(subject.icon).to eq 'icon_status_warning' + end + end + + describe '#label' do + it 'returns information about failed but allowed to fail status' do + expect(subject.label).to eq 'failed (allowed to fail)' + end + end + + describe '#group' do + it 'returns status failed with warnings status group' do + expect(subject.group).to eq 'failed_with_warnings' + end + end + + describe 'action details' do + describe '#has_action?' do + it 'does not decorate action details' do + expect(status).to receive(:has_action?) + + subject.has_action? + end + end + + describe '#action_path' do + it 'does not decorate action path' do + expect(status).to receive(:action_path) + + subject.action_path + end + end + + describe '#action_icon' do + it 'does not decorate action icon' do + expect(status).to receive(:action_icon) + + subject.action_icon + end + end + + describe '#action_title' do + it 'does not decorate action title' do + expect(status).to receive(:action_title) + + subject.action_title + end + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'when build is failed' do + context 'when build is allowed to fail' do + let(:build) { create(:ci_build, :failed, :allowed_to_fail) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not allowed to fail' do + let(:build) { create(:ci_build, :failed) } + + it 'is not a correct match' do + expect(subject).not_to be true + end + end + end + + context 'when build did not fail' do + context 'when build is allowed to fail' do + let(:build) { create(:ci_build, :success, :allowed_to_fail) } + + it 'is not a correct match' do + expect(subject).not_to be true + end + end + + context 'when build is not allowed to fail' do + let(:build) { create(:ci_build, :success) } + + it 'is not a correct match' do + expect(subject).not_to be true + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb index f92a1c149bf1c604c47fe2f81b917b57d649c395..bbf9c7c83a3877aaf0180d4c504c192da3c39a68 100644 --- a/spec/lib/gitlab/ci/status/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/factory_spec.rb @@ -1,24 +1,135 @@ require 'spec_helper' describe Gitlab::Ci::Status::Factory do - subject do - described_class.new(resource, user) + let(:user) { create(:user) } + let(:fabricated_status) { factory.fabricate! } + let(:factory) { described_class.new(resource, user) } + + context 'when object has a core status' do + HasStatus::AVAILABLE_STATUSES.each do |simple_status| + context "when simple core status is #{simple_status}" do + let(:resource) { double('resource', status: simple_status) } + + let(:expected_status) do + Gitlab::Ci::Status.const_get(simple_status.capitalize) + end + + it "fabricates a core status #{simple_status}" do + expect(fabricated_status).to be_a expected_status + end + + it "matches a valid core status for #{simple_status}" do + expect(factory.core_status).to be_a expected_status + end + + it "does not match any extended statuses for #{simple_status}" do + expect(factory.extended_statuses).to be_empty + end + end + end end - let(:user) { create(:user) } + context 'when resource supports multiple extended statuses' do + let(:resource) { double('resource', status: :success) } - let(:status) { subject.fabricate! } + let(:first_extended_status) do + Class.new(SimpleDelegator) do + def first_method + 'first return value' + end - context 'when object has a core status' do - HasStatus::AVAILABLE_STATUSES.each do |core_status| - context "when core status is #{core_status}" do - let(:resource) { double(status: core_status) } + def second_method + 'second return value' + end + + def self.matches?(*) + true + end + end + end - it "fabricates a core status #{core_status}" do - expect(status).to be_a( - Gitlab::Ci::Status.const_get(core_status.capitalize)) + let(:second_extended_status) do + Class.new(SimpleDelegator) do + def first_method + 'decorated return value' end + + def third_method + 'third return value' + end + + def self.matches?(*) + true + end + end + end + + shared_examples 'compound decorator factory' do + it 'fabricates compound decorator' do + expect(fabricated_status.first_method).to eq 'decorated return value' + expect(fabricated_status.second_method).to eq 'second return value' + expect(fabricated_status.third_method).to eq 'third return value' end + + it 'delegates to core status' do + expect(fabricated_status.text).to eq 'passed' + end + + it 'latest matches status becomes a status name' do + expect(fabricated_status.class).to eq second_extended_status + end + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [first_extended_status, second_extended_status] + end + end + + context 'when exclusive statuses are matches' do + before do + allow(described_class).to receive(:extended_statuses) + .and_return([[first_extended_status, second_extended_status]]) + end + + it 'does not fabricate compound decorator' do + expect(fabricated_status.first_method).to eq 'first return value' + expect(fabricated_status.second_method).to eq 'second return value' + expect(fabricated_status).not_to respond_to(:third_method) + end + + it 'delegates to core status' do + expect(fabricated_status.text).to eq 'passed' + end + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses).to eq [first_extended_status] + end + end + + context 'when exclusive statuses are not matched' do + before do + allow(described_class).to receive(:extended_statuses) + .and_return([[first_extended_status], [second_extended_status]]) + end + + it_behaves_like 'compound decorator factory' + end + + context 'when using simplified status grouping' do + before do + allow(described_class).to receive(:extended_statuses) + .and_return([first_extended_status, second_extended_status]) + end + + it_behaves_like 'compound decorator factory' end end end diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index d4a2dc7fcc1e1bb0d5213ffb099dc12cd8a61f44..b10a447c27a8dade794b3dbfcd06368937e57834 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -3,29 +3,32 @@ describe Gitlab::Ci::Status::Pipeline::Factory do let(:user) { create(:user) } let(:project) { pipeline.project } - - subject do - described_class.new(pipeline, user) - end - - let(:status) do - subject.fabricate! - end + let(:status) { factory.fabricate! } + let(:factory) { described_class.new(pipeline, user) } before do project.team << [user, :developer] end context 'when pipeline has a core status' do - HasStatus::AVAILABLE_STATUSES.each do |core_status| - context "when core status is #{core_status}" do - let(:pipeline) do - create(:ci_pipeline, status: core_status) + HasStatus::AVAILABLE_STATUSES.each do |simple_status| + context "when core status is #{simple_status}" do + let(:pipeline) { create(:ci_pipeline, status: simple_status) } + + let(:expected_status) do + Gitlab::Ci::Status.const_get(simple_status.capitalize) + end + + it "matches correct core status for #{simple_status}" do + expect(factory.core_status).to be_a expected_status end - it "fabricates a core status #{core_status}" do - expect(status).to be_a( - Gitlab::Ci::Status.const_get(core_status.capitalize)) + it 'does not matche extended statuses' do + expect(factory.extended_statuses).to be_empty + end + + it "fabricates a core status #{simple_status}" do + expect(status).to be_a expected_status end it 'extends core status with common pipeline methods' do @@ -47,13 +50,22 @@ create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline) end + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::SuccessWarning] + end + it 'fabricates extended "success with warnings" status' do - expect(status) - .to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings + expect(status).to be_a Gitlab::Ci::Status::SuccessWarning end - it 'extends core status with common pipeline methods' do + it 'extends core status with common pipeline method' do expect(status).to have_details + expect(status.details_path).to include "pipelines/#{pipeline.id}" end end end diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb deleted file mode 100644 index 979160eb9c461d28c5bf8c1034c4e663b928866e..0000000000000000000000000000000000000000 --- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do - subject do - described_class.new(double('status')) - end - - describe '#test' do - it { expect(subject.text).to eq 'passed' } - end - - describe '#label' do - it { expect(subject.label).to eq 'passed with warnings' } - end - - describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_warning' } - end - - describe '#group' do - it { expect(subject.group).to eq 'success_with_warnings' } - end - - describe '.matches?' do - context 'when pipeline is successful' do - let(:pipeline) do - create(:ci_pipeline, status: :success) - end - - context 'when pipeline has warnings' do - before do - allow(pipeline).to receive(:has_warnings?).and_return(true) - end - - it 'is a correct match' do - expect(described_class.matches?(pipeline, double)).to eq true - end - end - - context 'when pipeline does not have warnings' do - it 'does not match' do - expect(described_class.matches?(pipeline, double)).to eq false - end - end - end - - context 'when pipeline is not successful' do - let(:pipeline) do - create(:ci_pipeline, status: :skipped) - end - - context 'when pipeline has warnings' do - before do - allow(pipeline).to receive(:has_warnings?).and_return(true) - end - - it 'does not match' do - expect(described_class.matches?(pipeline, double)).to eq false - end - end - - context 'when pipeline does not have warnings' do - it 'does not match' do - expect(described_class.matches?(pipeline, double)).to eq false - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 6f8721d30c22c14de01d88ea1095397a6643de73..bbb40e2c1abbad8af5f4b35bd8886bc48073f985 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -43,4 +43,25 @@ end end end + + context 'when stage has warnings' do + let(:stage) do + build(:ci_stage, name: 'test', status: :success, pipeline: pipeline) + end + + before do + create(:ci_build, :allowed_to_fail, :failed, + stage: 'test', pipeline: stage.pipeline) + end + + it 'fabricates extended "success with warnings" status' do + expect(status) + .to be_a Gitlab::Ci::Status::SuccessWarning + end + + it 'extends core status with common stage method' do + expect(status).to have_details + expect(status.details_path).to include "pipelines/#{pipeline.id}##{stage.name}" + end + end end diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..7e2269397c667691e9450cbd0bd1b12355d7627f --- /dev/null +++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::SuccessWarning do + subject do + described_class.new(double('status')) + end + + describe '#test' do + it { expect(subject.text).to eq 'passed' } + end + + describe '#label' do + it { expect(subject.label).to eq 'passed with warnings' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_warning' } + end + + describe '#group' do + it { expect(subject.group).to eq 'success_with_warnings' } + end + + describe '.matches?' do + let(:matchable) { double('matchable') } + + context 'when matchable subject is successful' do + before do + allow(matchable).to receive(:success?).and_return(true) + end + + context 'when matchable subject has warnings' do + before do + allow(matchable).to receive(:has_warnings?).and_return(true) + end + + it 'is a correct match' do + expect(described_class.matches?(matchable, double)).to eq true + end + end + + context 'when matchable subject does not have warnings' do + before do + allow(matchable).to receive(:has_warnings?).and_return(false) + end + + it 'does not match' do + expect(described_class.matches?(matchable, double)).to eq false + end + end + end + + context 'when matchable subject is not successful' do + before do + allow(matchable).to receive(:success?).and_return(false) + end + + context 'when matchable subject has warnings' do + before do + allow(matchable).to receive(:has_warnings?).and_return(true) + end + + it 'does not match' do + expect(described_class.matches?(matchable, double)).to eq false + end + end + + context 'when matchable subject does not have warnings' do + it 'does not match' do + expect(described_class.matches?(matchable, double)).to eq false + end + end + end + end +end diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb index 19298e261e39866313a183cdc879d13a8b0bf614..9d806fc524d0bea1b456df3e40d8d1106634e3a3 100644 --- a/spec/lib/gitlab/email/email_shared_blocks.rb +++ b/spec/lib/gitlab/email/email_shared_blocks.rb @@ -18,7 +18,7 @@ def setup_attachment end end -shared_examples :email_shared_examples do +shared_examples :reply_processing_shared_examples do context "when the user could not be found" do before do user.destroy diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index cb3651e3845be2137f4be158b7ff56cdb822370f..08897a4c310f1680986de81ee2436a7e10d0b9f6 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -3,7 +3,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do include_context :email_shared_context - it_behaves_like :email_shared_examples + it_behaves_like :reply_processing_shared_examples before do stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 48660d1dd1b035c12e9a1da674e6bc9ef33a31d4..cebbeff50cfb9c2704913d1796418729b8da18b8 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -3,7 +3,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do include_context :email_shared_context - it_behaves_like :email_shared_examples + it_behaves_like :reply_processing_shared_examples before do stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a444257754b58a3637339664908bd225da578edd --- /dev/null +++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require_relative '../email_shared_blocks' + +describe Gitlab::Email::Handler::UnsubscribeHandler, lib: true do + include_context :email_shared_context + + before do + stub_incoming_email_setting(enabled: true, address: 'reply+%{key}@appmail.adventuretime.ooo') + stub_config_setting(host: 'localhost') + end + + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") } + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + let(:noteable) { create(:issue, project: project) } + + let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) } + + context 'when notification concerns a commit' do + let(:commit) { create(:commit, project: project) } + let!(:sent_notification) { SentNotification.record(commit, user.id, mail_key) } + + it 'handler does not raise an error' do + expect { receiver.execute }.not_to raise_error + end + end + + context 'user is unsubscribed' do + it 'leaves user unsubscribed' do + expect { receiver.execute }.not_to change { noteable.subscribed?(user) }.from(false) + end + end + + context 'user is subscribed' do + before do + noteable.subscribe(user) + end + + it 'unsubscribes user from notable' do + expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false) + end + end + + context 'when the noteable could not be found' do + before do + noteable.destroy + end + + it 'raises a NoteableNotFoundError' do + expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError) + end + end + + context 'when no sent notification for the mail key could be found' do + let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') } + + it 'raises a SentNotificationNotFoundError' do + expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) + end + end +end diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 1dcf2c0668b75f3e748a868c83022be5b65fc7af..7e951e3fcdd74bc50de478dad9ec0366fd2fcb2c 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -23,6 +23,48 @@ end end + describe 'self.supports_wildcard?' do + context 'address contains the wildard placeholder' do + before do + stub_incoming_email_setting(address: 'replies+%{key}@example.com') + end + + it 'confirms that wildcard is supported' do + expect(described_class.supports_wildcard?).to be_truthy + end + end + + context "address doesn't contain the wildcard placeholder" do + before do + stub_incoming_email_setting(address: 'replies@example.com') + end + + it 'returns that wildcard is not supported' do + expect(described_class.supports_wildcard?).to be_falsey + end + end + + context 'address is not set' do + before do + stub_incoming_email_setting(address: nil) + end + + it 'returns that wildard is not supported' do + expect(described_class.supports_wildcard?).to be_falsey + end + end + end + + context 'self.unsubscribe_address' do + before do + stub_incoming_email_setting(address: 'replies+%{key}@example.com') + end + + it 'returns the address with interpolated reply key and unsubscribe suffix' do + expect(described_class.unsubscribe_address('key')).to eq('replies+key+unsubscribe@example.com') + end + end + context "self.reply_address" do before do stub_incoming_email_setting(address: "replies+%{key}@example.com") diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index d3c3b800b94b3683d42e0fd0a432099f87d7f6d7..369e55f61f1d490eef4aaf5b9949cc1ffe67e2fd 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -66,7 +66,8 @@ end describe 'push to protected branch' do - let(:branch) { create :protected_branch, project: project } + let(:branch) { create :protected_branch, project: project, name: "test" } + let(:not_existing_branch) { create :protected_branch, :developers_can_merge, project: project } it 'returns true if user is a master' do project.team << [user, :master] @@ -85,6 +86,12 @@ expect(access.can_push_to_branch?(branch.name)).to be_falsey end + + it 'returns true if branch does not exist and user has permission to merge' do + project.team << [user, :developer] + + expect(access.can_push_to_branch?(not_existing_branch.name)).to be_truthy + end end describe 'push to protected branch if allowed for developers' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index d1aee27057abe3cd2a100d6111301abd4fa63aa6..2bdd611aeed41bd5f33a43d6a737f192ceaf0a81 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -122,55 +122,80 @@ def create_build(name, status) end end - describe '#stages' do + describe 'pipeline stages' do before do - create(:commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success') - create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed') - create(:commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running') - create(:commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success') - end - - subject { pipeline.stages } - - context 'stages list' do - it 'returns ordered list of stages' do - expect(subject.map(&:name)).to eq(%w[build test deploy]) + create(:commit_status, pipeline: pipeline, + stage: 'build', + name: 'linux', + stage_idx: 0, + status: 'success') + + create(:commit_status, pipeline: pipeline, + stage: 'build', + name: 'mac', + stage_idx: 0, + status: 'failed') + + create(:commit_status, pipeline: pipeline, + stage: 'deploy', + name: 'staging', + stage_idx: 2, + status: 'running') + + create(:commit_status, pipeline: pipeline, + stage: 'test', + name: 'rspec', + stage_idx: 1, + status: 'success') + end + + describe '#stages' do + subject { pipeline.stages } + + context 'stages list' do + it 'returns ordered list of stages' do + expect(subject.map(&:name)).to eq(%w[build test deploy]) + end end - end - it 'returns a valid number of stages' do - expect(pipeline.stages_count).to eq(3) - end + context 'stages with statuses' do + let(:statuses) do + subject.map { |stage| [stage.name, stage.status] } + end - it 'returns a valid names of stages' do - expect(pipeline.stages_name).to eq(['build', 'test', 'deploy']) - end + it 'returns list of stages with correct statuses' do + expect(statuses).to eq([['build', 'failed'], + ['test', 'success'], + ['deploy', 'running']]) + end - context 'stages with statuses' do - let(:statuses) do - subject.map do |stage| - [stage.name, stage.status] + context 'when commit status is retried' do + before do + create(:commit_status, pipeline: pipeline, + stage: 'build', + name: 'mac', + stage_idx: 0, + status: 'success') + end + + it 'ignores the previous state' do + expect(statuses).to eq([['build', 'success'], + ['test', 'success'], + ['deploy', 'running']]) + end end end + end - it 'returns list of stages with statuses' do - expect(statuses).to eq([['build', 'failed'], - ['test', 'success'], - ['deploy', 'running'] - ]) + describe '#stages_count' do + it 'returns a valid number of stages' do + expect(pipeline.stages_count).to eq(3) end + end - context 'when build is retried' do - before do - create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success') - end - - it 'ignores the previous state' do - expect(statuses).to eq([['build', 'success'], - ['test', 'success'], - ['deploy', 'running'] - ]) - end + describe '#stages_name' do + it 'returns a valid names of stages' do + expect(pipeline.stages_name).to eq(['build', 'test', 'deploy']) end end end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 742bedb37e49620f9ed9868e8754a5c994350787..c4a9743a4e245b8d6acd96ed61d652d5a2fac15e 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -142,6 +142,78 @@ end end + describe '#success?' do + context 'when stage is successful' do + before do + create_job(:ci_build, status: :success) + create_job(:generic_commit_status, status: :success) + end + + it 'is successful' do + expect(stage).to be_success + end + end + + context 'when stage is not successful' do + before do + create_job(:ci_build, status: :failed) + create_job(:generic_commit_status, status: :success) + end + + it 'is not successful' do + expect(stage).not_to be_success + end + end + end + + describe '#has_warnings?' do + context 'when stage has warnings' do + context 'when using memoized warnings flag' do + context 'when there are warnings' do + let(:stage) { build(:ci_stage, warnings: true) } + + it 'has memoized warnings' do + expect(stage).not_to receive(:statuses) + expect(stage).to have_warnings + end + end + + context 'when there are no warnings' do + let(:stage) { build(:ci_stage, warnings: false) } + + it 'has memoized warnings' do + expect(stage).not_to receive(:statuses) + expect(stage).not_to have_warnings + end + end + end + + context 'when calculating warnings from statuses' do + before do + create(:ci_build, :failed, :allowed_to_fail, + stage: stage_name, pipeline: pipeline) + end + + it 'has warnings calculated from statuses' do + expect(stage).to receive(:statuses).and_call_original + expect(stage).to have_warnings + end + end + end + + context 'when stage does not have warnings' do + before do + create(:ci_build, :success, stage: stage_name, + pipeline: pipeline) + end + + it 'does not have warnings calculated from statuses' do + expect(stage).to receive(:statuses).and_call_original + expect(stage).not_to have_warnings + end + end + end + def create_job(type, status: 'success', stage: stage_name) create(type, pipeline: pipeline, stage: stage, status: status) end diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 4d0f51fe82a3ec5778e5c0fc96d7d6e3b1dccff4..dbfe3cd2d3631e16d6676c5bf10a733a78b1f9cf 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -219,4 +219,10 @@ end end end + + describe '::DEFAULT_STATUS' do + it 'is a status created' do + expect(described_class::DEFAULT_STATUS).to eq 'created' + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 4542f0fa2dc7cbc648828b2f1a4841f9d29ffaf4..ef427b718399a3369eb98f13a143418f20c21a3b 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -117,6 +117,7 @@ new_path = @namespace.path + "_new" allow(@namespace).to receive(:path_was).and_return(@namespace.path) allow(@namespace).to receive(:path).and_return(new_path) + expect(@namespace).to receive(:remove_exports!) expect(@namespace.move_dir).to be_truthy end @@ -151,11 +152,17 @@ let!(:project) { create(:project, namespace: namespace) } let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) } - before { namespace.destroy } - it "removes its dirs when deleted" do + namespace.destroy + expect(File.exist?(path)).to be(false) end + + it 'removes the exports folder' do + expect(namespace).to receive(:remove_exports!) + + namespace.destroy + end end describe '.find_by_path_or_name' do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index fd00298500043378d9cfcb79f1236b3160a57ca7..d1b00783e8336f62c98f23893baa56d62cb532fa 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -687,6 +687,17 @@ def create_merge_request(approvals_before_merge) expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['id']).to eq(issue.id) end + + it 'returns 403 if the user has no access to the merge request' do + project = create(:empty_project, :private) + merge_request = create(:merge_request, :simple, source_project: project) + guest = create(:user) + project.team << [guest, :guest] + + get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest) + + expect(response).to have_http_status(403) + end end describe 'POST :id/merge_requests/:merge_request_id/subscription' do @@ -708,6 +719,15 @@ def create_merge_request(approvals_before_merge) expect(response).to have_http_status(404) end + + it 'returns 403 if user has no access to read code' do + guest = create(:user) + project.team << [guest, :guest] + + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) + + expect(response).to have_http_status(403) + end end describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do @@ -729,6 +749,15 @@ def create_merge_request(approvals_before_merge) expect(response).to have_http_status(404) end + + it 'returns 403 if user has no access to read code' do + guest = create(:user) + project.team << [guest, :guest] + + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) + + expect(response).to have_http_status(403) + end end describe 'GET :id/merge_requests/:merge_request_id/approvals' do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 0f8d054b31e97787d8ad59bbdacdbb9c0b314b04..0353ebea9e59b3c518ea17bcf01a98e24c0f3aa1 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -264,6 +264,18 @@ end end + context 'when user does not have access to read the noteable' do + it 'responds with 404' do + project = create(:empty_project, :private) { |p| p.add_guest(user) } + issue = create(:issue, :confidential, project: project) + + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), + body: 'Foo' + + expect(response).to have_http_status(404) + end + end + context 'when user does not have access to create noteable' do let(:private_issue) { create(:issue, project: create(:empty_project, :private)) } diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 39c9e0505d1a9c200c42eae28ba9f15294c30d05..776dc6556506bad9e8b4b94380bdb93e22b7388e 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -6,7 +6,7 @@ let(:user) { create(:user) } let(:admin) { create(:admin) } let(:user2) { create(:user) } - let(:project) {create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } Service.available_services_names.each do |service| describe "PUT /projects/:id/services/#{service.dasherize}" do @@ -16,6 +16,15 @@ put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs expect(response).to have_http_status(200) + + current_service = project.services.first + event = current_service.event_names.empty? ? "foo" : current_service.event_names.first + state = current_service[event] || false + + put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), service_attrs + + expect(response).to have_http_status(200) + expect(project.services.first[event]).not_to eq(state) unless event == "foo" end it "returns if required fields missing" do diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 6fe695626caa762b5cfe8bb7a0bb44c3460bf0eb..56dc017ce54ba8c9e17e39717e75024f2ac3fb43 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -183,12 +183,25 @@ expect(response.status).to eq(404) end + + it 'returns an error if the issuable is not accessible' do + guest = create(:user) + project_1.team << [guest, :guest] + + post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", guest) + + if issuable_type == 'merge_requests' + expect(response).to have_http_status(403) + else + expect(response).to have_http_status(404) + end + end end describe 'POST :id/issuable_type/:issueable_id/todo' do context 'for an issue' do it_behaves_like 'an issuable', 'issues' do - let(:issuable) { create(:issue, author: author_1, project: project_1) } + let(:issuable) { create(:issue, :confidential, author: author_1, project: project_1) } end end diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb index 49867aa5cc4dbdd0512d9b1d77c10928ee6d6d7e..a3724b801b3f6e0495f413577d2a33ae2cc29012 100644 --- a/spec/support/notify_shared_examples.rb +++ b/spec/support/notify_shared_examples.rb @@ -179,9 +179,24 @@ end shared_examples 'an unsubscribeable thread' do + it_behaves_like 'an unsubscribeable thread with incoming address without %{key}' + + it 'has a List-Unsubscribe header in the correct format' do + is_expected.to have_header 'List-Unsubscribe', /unsubscribe/ + is_expected.to have_header 'List-Unsubscribe', /mailto/ + is_expected.to have_header 'List-Unsubscribe', /^<.+,.+>$/ + end + + it { is_expected.to have_body_text /unsubscribe/ } +end + +shared_examples 'an unsubscribeable thread with incoming address without %{key}' do + include_context 'reply-by-email is enabled with incoming address without %{key}' + it 'has a List-Unsubscribe header in the correct format' do is_expected.to have_header 'List-Unsubscribe', /unsubscribe/ - is_expected.to have_header 'List-Unsubscribe', /^<.+>$/ + is_expected.not_to have_header 'List-Unsubscribe', /mailto/ + is_expected.to have_header 'List-Unsubscribe', /^<[^,]+>$/ end it { is_expected.to have_body_text /unsubscribe/ }