diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-06 09:09:20 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-06 09:09:20 +0300 |
commit | f713e4eb11cae4593cfe70554a4a23536ab91357 (patch) | |
tree | 5da1a55d3eb2a9d26712c8c76dfdf713a09680e6 | |
parent | 43c0fa568b3e3689abd3d57eb058433f33c9df1c (diff) |
Add latest changes from gitlab-org/gitlab@master
44 files changed, 313 insertions, 393 deletions
diff --git a/app/assets/javascripts/pipeline_wizard/components/step_nav.vue b/app/assets/javascripts/pipeline_wizard/components/step_nav.vue index 8f9198855c6..e3d825bbcc7 100644 --- a/app/assets/javascripts/pipeline_wizard/components/step_nav.vue +++ b/app/assets/javascripts/pipeline_wizard/components/step_nav.vue @@ -27,12 +27,13 @@ export default { </script> <template> - <div> + <div class="gl-display-flex"> <slot name="before"></slot> <gl-button v-if="showBackButton" category="secondary" data-testid="back-button" + class="gl-mr-3" @click="$emit('back')" > {{ __('Back') }} diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue index da4b21a2790..cb38b3e13bb 100644 --- a/app/assets/javascripts/vue_shared/components/awards_list.vue +++ b/app/assets/javascripts/vue_shared/components/awards_list.vue @@ -159,10 +159,7 @@ export default { return; } - // 100 and 1234 emoji are a number. Callback for v-for click sends it as a string - const parsedName = /^[0-9]+$/.test(awardName) ? Number(awardName) : awardName; - - this.$emit('award', parsedName); + this.$emit('award', awardName); if (document.activeElement) document.activeElement.blur(); }, diff --git a/app/graphql/graphql_triggers.rb b/app/graphql/graphql_triggers.rb index 710e7fe110c..7f83b62a2ff 100644 --- a/app/graphql/graphql_triggers.rb +++ b/app/graphql/graphql_triggers.rb @@ -44,6 +44,14 @@ module GraphqlTriggers merge_request ) end + + def self.merge_request_approval_state_updated(merge_request) + GitlabSchema.subscriptions.trigger( + 'mergeRequestApprovalStateUpdated', + { issuable_id: merge_request.to_gid }, + merge_request + ) + end end GraphqlTriggers.prepend_mod diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 03993506d80..ce95b152fb7 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1271,7 +1271,7 @@ class MergeRequest < ApplicationRecord end def mergeable_discussions_state? - return true unless project.only_allow_merge_if_all_discussions_are_resolved? + return true unless project.only_allow_merge_if_all_discussions_are_resolved?(inherit_group_setting: true) unresolved_notes.none?(&:to_be_resolved?) end @@ -1452,9 +1452,9 @@ class MergeRequest < ApplicationRecord end def mergeable_ci_state? - return true unless project.only_allow_merge_if_pipeline_succeeds? + return true unless project.only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: true) return false unless actual_head_pipeline - return true if project.allow_merge_on_skipped_pipeline? && actual_head_pipeline.skipped? + return true if project.allow_merge_on_skipped_pipeline?(inherit_group_setting: true) && actual_head_pipeline.skipped? actual_head_pipeline.success? end diff --git a/app/models/project.rb b/app/models/project.rb index 4cb8ada8ea1..8d66c0d6ea5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -742,6 +742,29 @@ class Project < ApplicationRecord end end + # Defines instance methods: + # + # - only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: false) + # - allow_merge_on_skipped_pipeline?(inherit_group_setting: false) + # - only_allow_merge_if_all_discussions_are_resolved?(inherit_group_setting: false) + # - only_allow_merge_if_pipeline_succeeds_locked? + # - allow_merge_on_skipped_pipeline_locked? + # - only_allow_merge_if_all_discussions_are_resolved_locked? + def self.cascading_with_parent_namespace(attribute) + # method overriden in EE + define_method("#{attribute}?") do |inherit_group_setting: false| + self.public_send(attribute) # rubocop:disable GitlabSecurity/PublicSend + end + + define_method("#{attribute}_locked?") do + false + end + end + + cascading_with_parent_namespace :only_allow_merge_if_pipeline_succeeds + cascading_with_parent_namespace :allow_merge_on_skipped_pipeline + cascading_with_parent_namespace :only_allow_merge_if_all_discussions_are_resolved + def self.with_feature_available_for_user(feature, user) with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user)) end diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb index ab180b35b29..cef3f4555df 100644 --- a/app/serializers/merge_request_poll_widget_entity.rb +++ b/app/serializers/merge_request_poll_widget_entity.rb @@ -31,7 +31,7 @@ class MergeRequestPollWidgetEntity < Grape::Entity end expose :only_allow_merge_if_pipeline_succeeds do |merge_request| - merge_request.project.only_allow_merge_if_pipeline_succeeds? + merge_request.project.only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: true) end # CI related diff --git a/app/services/merge_requests/approval_service.rb b/app/services/merge_requests/approval_service.rb index 72f398ce415..8560a15b7c4 100644 --- a/app/services/merge_requests/approval_service.rb +++ b/app/services/merge_requests/approval_service.rb @@ -13,6 +13,7 @@ module MergeRequests merge_request_activity_counter.track_approve_mr_action(user: current_user, merge_request: merge_request) trigger_merge_request_merge_status_updated(merge_request) trigger_merge_request_reviewers_updated(merge_request) + trigger_merge_request_approval_state_updated(merge_request) # Approval side effects (things not required to be done immediately but # should happen after a successful approval) should be done asynchronously diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index fabf006979b..cccba5add13 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -262,6 +262,10 @@ module MergeRequests GraphqlTriggers.merge_request_merge_status_updated(merge_request) end + def trigger_merge_request_approval_state_updated(merge_request) + GraphqlTriggers.merge_request_approval_state_updated(merge_request) + end + def capture_suggested_reviewers_accepted(merge_request) # Implemented in EE end diff --git a/app/services/merge_requests/remove_approval_service.rb b/app/services/merge_requests/remove_approval_service.rb index 8387c23fe3f..c0bb257eda6 100644 --- a/app/services/merge_requests/remove_approval_service.rb +++ b/app/services/merge_requests/remove_approval_service.rb @@ -19,6 +19,7 @@ module MergeRequests merge_request_activity_counter.track_unapprove_mr_action(user: current_user) trigger_merge_request_merge_status_updated(merge_request) trigger_merge_request_reviewers_updated(merge_request) + trigger_merge_request_approval_state_updated(merge_request) end success diff --git a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml index 415606c055d..d7bb3a85f3a 100644 --- a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml +++ b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml @@ -20,7 +20,7 @@ label_options: { class: 'gl-font-weight-bold!' } .form-group.js-toggle-colors-container - %button.btn.gl-button.btn-link.js-toggle-colors-link{ type: 'button' } + = render Pajamas::ButtonComponent.new(variant: :link, button_options: { class: 'js-toggle-colors-link' }) do = _('Customize colors') .form-group.js-toggle-colors-container.hide = form.label :message_background_color, _('Background Color'), class: 'col-form-label label-bold' diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index a1afb1ddbfa..f9ebda2bc21 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -31,5 +31,7 @@ = visibility_level_icon(group.visibility_level) .controls.gl-flex-shrink-0.gl-ml-5 - = link_to _('Edit'), admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn gl-button btn-default' - = link_to _('Delete'), [:admin, group], aria: { label: _('Remove') }, data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name }, confirm_btn_variant: 'danger' }, method: :delete, class: 'gl-button btn btn-danger' + = render Pajamas::ButtonComponent.new(href: admin_group_edit_path(group), button_options: { id: "edit_#{dom_id(group)}" }) do + = _('Edit') + = render Pajamas::ButtonComponent.new(href: [:admin, group], variant: :danger, button_options: { data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name }, confirm_btn_variant: 'danger', method: :delete } }) do + = _('Delete') diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 1c82b30ed8d..09e2e35c617 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -3,8 +3,8 @@ - if current_user.can_create_group? .page-title-controls - = link_to _("New group"), new_group_path, class: "gl-button btn btn-confirm", data: { qa_selector: "new_group_button", testid: "new-group-button" } - + = render Pajamas::ButtonComponent.new(href: new_group_path, variant: :confirm, button_options: { data: { qa_selector: "new_group_button", testid: "new-group-button" } }) do + = _("New group") .top-area = gl_tabs_nav({ class: 'gl-flex-grow-1 gl-border-0' }) do = gl_tab_link_to _("Your groups"), dashboard_groups_path diff --git a/app/views/groups/_new_group_fields.html.haml b/app/views/groups/_new_group_fields.html.haml index 94b0b018084..95990e8937c 100644 --- a/app/views/groups/_new_group_fields.html.haml +++ b/app/views/groups/_new_group_fields.html.haml @@ -30,5 +30,7 @@ = recaptcha_tags nonce: content_security_policy_nonce .row .col-sm-12 - = f.submit submit_label, class: "btn gl-button btn-confirm", data: { qa_selector: 'create_group_button' } - = link_to _('Cancel'), dashboard_groups_path, class: 'btn gl-button btn-default btn-cancel' + = f.submit submit_label, pajamas_button: true, data: { qa_selector: 'create_group_button' } + = render Pajamas::ButtonComponent.new(href: dashboard_groups_path) do + = _('Cancel') + diff --git a/app/views/groups/settings/_export.html.haml b/app/views/groups/settings/_export.html.haml index 66c1341fb15..5d79d0f8e79 100644 --- a/app/views/groups/settings/_export.html.haml +++ b/app/views/groups/settings/_export.html.haml @@ -25,10 +25,10 @@ %li= _('Runner tokens') %li= _('SAML discovery tokens') - if group.export_file_exists? - = link_to _('Download export'), download_export_group_path(group), - rel: 'nofollow', method: :get, class: 'btn gl-button btn-default', data: { qa_selector: 'download_export_link' } - = link_to _('Regenerate export'), export_group_path(group), - method: :post, class: 'btn gl-button btn-default', data: { qa_selector: 'regenerate_export_group_link' } + = render Pajamas::ButtonComponent.new(href: download_export_group_path(group), button_options: { rel: 'nofollow', data: { method: :get, qa_selector: 'download_export_link' } }) do + = _('Download export') + = render Pajamas::ButtonComponent.new(href: export_group_path(group), button_options: { data: { method: :post, qa_selector: 'regenerate_export_group_link' } }) do + = _('Regenerate export') - else - = link_to _('Export group'), export_group_path(group), - method: :post, class: 'btn gl-button btn-default', data: { qa_selector: 'export_group_link' } + = render Pajamas::ButtonComponent.new(href: export_group_path(group), button_options: { data: { method: :post, qa_selector: 'export_group_link' } }) do + = _('Export group') diff --git a/doc/user/application_security/dast/browser_based.md b/doc/user/application_security/dast/browser_based.md index 909490fa6e5..a79dceb3ce5 100644 --- a/doc/user/application_security/dast/browser_based.md +++ b/doc/user/application_security/dast/browser_based.md @@ -12,24 +12,79 @@ type: reference, howto WARNING: This product is in an early-access stage and is considered a [beta](../../../policy/alpha-beta-support.md#beta-features) feature. -GitLab DAST's browser-based analyzer was built by GitLab to test Single Page Applications (SPAs) and -traditional web applications. It both crawls the web application and analyzes the resulting output -for vulnerabilities. Analysis of modern applications, heavily reliant on JavaScript, is vital to -ensuring DAST coverage. - -The browser-based scanner works by loading the target application into a specially-instrumented -Chromium browser. A snapshot of the page is taken before a search to find any actions that a user -might perform, such as selecting on a link or filling in a form. For each action found, the -browser-based scanner executes it, takes a new snapshot, and determines what in the page changed -from the previous snapshot. Crawling continues by taking more snapshots and finding subsequent -actions. The benefit of scanning by following user actions in a browser is that the crawler can -interact with the target application much like a real user would, identifying complex flows that -traditional web crawlers don't understand. This results in better coverage of the website. - -The browser-based scanner should provide greater coverage for most web applications, compared -with the current DAST AJAX crawler. While both crawlers are -used together with the current DAST scanner, the combination of the browser-based crawler with the -current DAST scanner is much more effective at finding and testing every page in an application. +WARNING: +Do not run DAST scans against a production server. Not only can it perform *any* function that +a user can, such as clicking buttons or submitting forms, but it may also trigger bugs, leading to modification or loss of production data. Only run DAST scans against a test server. + +The DAST browser-based analyzer was built by GitLab to scan modern-day web applications for vulnerabilities. +Scans run in a browser to optimize testing applications heavily dependent on JavaScript, such as single-page applications. +See [how DAST scans an application](#how-dast-scans-an-application) for more information. + +To add the analyzer to your CI/CD pipeline, see [enable browser-based analyzer](#enable-browser-based-analyzer). + +## How DAST scans an application + +A scan performs the following steps: + +1. [Authenticate](authentication.md), if configured. +1. [Crawl](#crawling-an-application) the target application to discover the surface area of the application by performing user actions such as following links, clicking buttons, and filling out forms. +1. [Passive scan](#passive-scans) to search for vulnerabilities in HTTP messages and pages discovered while crawling. +1. [Active scan](#active-scans) to search for vulnerabilities by injecting payloads into HTTP requests recorded during the crawl phase. + +### Crawling an application + +A "navigation" is an action a user might take on a page, such as clicking buttons, clicking anchor links, opening menu items, or filling out forms. +A "navigation path" is a sequence of navigation actions representing how a user might traverse an application. +DAST discovers the surface area of an application by crawling pages and content and identifying navigation paths. + +Crawling is initialized with a navigation path containing one navigation that loads the target application URL in a specially-instrumented Chromium browser. +DAST then crawls navigation paths until all have been crawled. + +To crawl a navigation path, DAST opens a browser window and instructs it to perform all the navigation actions in the navigation path. +When the browser has finished loading the result of the final action, DAST inspects the page for actions a user might take, +creates a new navigation for each found, and adds them to the navigation path to form new navigation paths. For example: + +- DAST processes navigation path `LoadURL[https://example.com]`. +- DAST finds two user actions, `LeftClick[class=menu]` and `LeftClick[id=users]`. +- DAST creates two new navigation paths, `LoadURL[https://example.com] -> LeftClick[class=menu]` and `LoadURL[https://example.com] -> LeftClick[id=users]`. +- Crawling begins on the two new navigation paths. + +It's common for an HTML element to exist in multiple places in an application, such as a menu visible on every page. +Duplicate elements can cause crawlers to crawl the same pages again or become stuck in a loop. +DAST uses an element uniqueness calculation based on HTML attributes to discard new navigation actions it has previously crawled. + +### Passive scans + +Passive scans check for vulnerabilities in the pages discovered during the crawl phase of the scan. +Passive scans are enabled by default. + +The checks search HTTP messages, cookies, storage events, console events, and DOM for vulnerabilities. +Examples of passive checks include searching for exposed credit cards, exposed secret tokens, missing content security policies, and redirection to untrusted locations. + +See [checks](checks/index.md) for more information about individual checks. + +### Active scans + +Active scans check for vulnerabilities by injecting attack payloads into HTTP requests recorded during the crawl phase of the scan. +Active scans are disabled by default due to the nature of their probing attacks. + +DAST analyzes each recorded HTTP request for injection locations, such as query values, header values, cookie values, form posts, and JSON string values. +Attack payloads are injected into the injection location, forming a new request. +DAST sends the request to the target application and uses the HTTP response to determine attack success. + +Active scans run two types of active check: + +- A match response attack analyzes the response content to determine attack success. For example, if an attack attempts to read the system password file, a finding is created when the response body contains evidence of the password file. +- A timing attack uses the response time to determine attack success. For example, if an attack attempts to force the target application to sleep, a finding is created when the application takes longer to respond than the sleep time. Timing attacks are repeated multiple times with different attack payloads to minimize false positives. + +A simplified timing attack works as follows: + +1. The crawl phase records the HTTP request `https://example.com?search=people`. +1. DAST analyzes the URL and finds a URL parameter injection location `https://example.com?search=[INJECT]`. +1. The active check defines a payload, `sleep 10`, that attempts to get a Linux host to sleep. +1. DAST send a new HTTP request to the target application with the injected payload `https://example.com?search=sleep%2010`. +1. The target application is vulnerable if it executes the query parameter value as a system command without validation, for example, `system(params[:search])` +1. DAST creates a finding if the response time takes longer than 10 seconds. ## Enable browser-based analyzer diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index ccf3ccc6a96..f8012242480 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -6,7 +6,7 @@ require 'spec_helper' # It looks up for any sensitive word inside the JSON, so if a sensitive word is found # we'll have to either include it adding the model that includes it to the +safe_list+ # or make sure the attribute is blacklisted in the +import_export.yml+ configuration -RSpec.describe 'Import/Export - project export integration test', :js do +RSpec.describe 'Import/Export - project export integration test', :js, feature_category: :importers do include Select2Helper include ExportFileHelper diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 6f015f9cd22..8fb11f06cdd 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Import/Export - project import integration test', :js do +RSpec.describe 'Import/Export - project import integration test', :js, feature_category: :importers do let(:user) { create(:user) } let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } diff --git a/spec/features/projects/integrations/disable_triggers_spec.rb b/spec/features/projects/integrations/disable_triggers_spec.rb index b039d610ecb..f7afce6d87c 100644 --- a/spec/features/projects/integrations/disable_triggers_spec.rb +++ b/spec/features/projects/integrations/disable_triggers_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Disable individual triggers', :js do +RSpec.describe 'Disable individual triggers', :js, feature_category: :integrations do include_context 'project integration activation' let(:checkbox_selector) { 'input[name$="_events]"]' } diff --git a/spec/features/projects/integrations/project_integrations_spec.rb b/spec/features/projects/integrations/project_integrations_spec.rb index 708a5bca8c1..d99b6ca9092 100644 --- a/spec/features/projects/integrations/project_integrations_spec.rb +++ b/spec/features/projects/integrations/project_integrations_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project integrations', :js do +RSpec.describe 'Project integrations', :js, feature_category: :integrations do include_context 'project integration activation' it_behaves_like 'integration settings form' do diff --git a/spec/features/projects/integrations/user_activates_asana_spec.rb b/spec/features/projects/integrations/user_activates_asana_spec.rb index 9ec9f00529a..b99ca2ebc1c 100644 --- a/spec/features/projects/integrations/user_activates_asana_spec.rb +++ b/spec/features/projects/integrations/user_activates_asana_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Asana' do +RSpec.describe 'User activates Asana', feature_category: :integrations do include_context 'project integration activation' it 'activates integration', :js do diff --git a/spec/features/projects/integrations/user_activates_assembla_spec.rb b/spec/features/projects/integrations/user_activates_assembla_spec.rb index be9034ec5ba..db5774e4514 100644 --- a/spec/features/projects/integrations/user_activates_assembla_spec.rb +++ b/spec/features/projects/integrations/user_activates_assembla_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Assembla' do +RSpec.describe 'User activates Assembla', feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_activates_atlassian_bamboo_ci_spec.rb b/spec/features/projects/integrations/user_activates_atlassian_bamboo_ci_spec.rb index 49f62a34bd2..a532c1b8644 100644 --- a/spec/features/projects/integrations/user_activates_atlassian_bamboo_ci_spec.rb +++ b/spec/features/projects/integrations/user_activates_atlassian_bamboo_ci_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Atlassian Bamboo CI' do +RSpec.describe 'User activates Atlassian Bamboo CI', feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_activates_emails_on_push_spec.rb b/spec/features/projects/integrations/user_activates_emails_on_push_spec.rb index 168779aad07..9a2d693a9f0 100644 --- a/spec/features/projects/integrations/user_activates_emails_on_push_spec.rb +++ b/spec/features/projects/integrations/user_activates_emails_on_push_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Emails on push' do +RSpec.describe 'User activates Emails on push', feature_category: :integrations do include_context 'project integration activation' it 'activates integration', :js do diff --git a/spec/features/projects/integrations/user_activates_flowdock_spec.rb b/spec/features/projects/integrations/user_activates_flowdock_spec.rb index df1a4feddfb..9786ea9a692 100644 --- a/spec/features/projects/integrations/user_activates_flowdock_spec.rb +++ b/spec/features/projects/integrations/user_activates_flowdock_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Flowdock' do +RSpec.describe 'User activates Flowdock', feature_category: :integrations do include_context 'project integration activation' do let(:project) { create(:project, :repository) } end diff --git a/spec/features/projects/integrations/user_activates_irker_spec.rb b/spec/features/projects/integrations/user_activates_irker_spec.rb index 23b5f2a5c47..17c46bfaff7 100644 --- a/spec/features/projects/integrations/user_activates_irker_spec.rb +++ b/spec/features/projects/integrations/user_activates_irker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates irker (IRC gateway)' do +RSpec.describe 'User activates irker (IRC gateway)', feature_category: :integrations do include_context 'project integration activation' it 'activates integration', :js do diff --git a/spec/features/projects/integrations/user_activates_jetbrains_teamcity_ci_spec.rb b/spec/features/projects/integrations/user_activates_jetbrains_teamcity_ci_spec.rb index f86a1b8a0a4..a18c052beb9 100644 --- a/spec/features/projects/integrations/user_activates_jetbrains_teamcity_ci_spec.rb +++ b/spec/features/projects/integrations/user_activates_jetbrains_teamcity_ci_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates JetBrains TeamCity CI' do +RSpec.describe 'User activates JetBrains TeamCity CI', feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_activates_jira_spec.rb b/spec/features/projects/integrations/user_activates_jira_spec.rb index dad201ffbb6..e4b10aeb340 100644 --- a/spec/features/projects/integrations/user_activates_jira_spec.rb +++ b/spec/features/projects/integrations/user_activates_jira_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Jira', :js do +RSpec.describe 'User activates Jira', :js, feature_category: :integrations do include_context 'project integration activation' include_context 'project integration Jira context' diff --git a/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb index 54c9ec0f62e..16c7a3ff226 100644 --- a/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Set up Mattermost slash commands', :js do +RSpec.describe 'Set up Mattermost slash commands', :js, feature_category: :integrations do describe 'user visits the mattermost slash command config page' do include_context 'project integration activation' diff --git a/spec/features/projects/integrations/user_activates_packagist_spec.rb b/spec/features/projects/integrations/user_activates_packagist_spec.rb index 0892843e840..2d77abfea7c 100644 --- a/spec/features/projects/integrations/user_activates_packagist_spec.rb +++ b/spec/features/projects/integrations/user_activates_packagist_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Packagist' do +RSpec.describe 'User activates Packagist', feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb b/spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb index fe6ed786ace..b4dec8ffdb5 100644 --- a/spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb +++ b/spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates PivotalTracker' do +RSpec.describe 'User activates PivotalTracker', feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_activates_prometheus_spec.rb b/spec/features/projects/integrations/user_activates_prometheus_spec.rb index 56b895919b8..5b2d885410f 100644 --- a/spec/features/projects/integrations/user_activates_prometheus_spec.rb +++ b/spec/features/projects/integrations/user_activates_prometheus_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Prometheus' do +RSpec.describe 'User activates Prometheus', feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_activates_pushover_spec.rb b/spec/features/projects/integrations/user_activates_pushover_spec.rb index 616efdc836f..a705c354a1e 100644 --- a/spec/features/projects/integrations/user_activates_pushover_spec.rb +++ b/spec/features/projects/integrations/user_activates_pushover_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Pushover' do +RSpec.describe 'User activates Pushover', feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb b/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb index e89f6e309ea..01c202baf70 100644 --- a/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb +++ b/spec/features/projects/integrations/user_activates_slack_notifications_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User activates Slack notifications', :js do +RSpec.describe 'User activates Slack notifications', :js, feature_category: :integrations do include_context 'project integration activation' context 'when integration is not configured yet' do diff --git a/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb b/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb index df8cd84ffdb..0f6d721565e 100644 --- a/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb +++ b/spec/features/projects/integrations/user_activates_slack_slash_command_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Slack slash commands', :js do +RSpec.describe 'Slack slash commands', :js, feature_category: :integrations do include_context 'project integration activation' before do diff --git a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb index 8a2881c95dc..e0063a9c733 100644 --- a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb +++ b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User uses inherited settings', :js do +RSpec.describe 'User uses inherited settings', :js, feature_category: :integrations do include JiraIntegrationHelpers include_context 'project integration activation' diff --git a/spec/features/projects/integrations/user_views_services_spec.rb b/spec/features/projects/integrations/user_views_services_spec.rb index 559461f911f..e6be300c0a9 100644 --- a/spec/features/projects/integrations/user_views_services_spec.rb +++ b/spec/features/projects/integrations/user_views_services_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User views integrations', :js do +RSpec.describe 'User views integrations', :js, feature_category: :integrations do include_context 'project integration activation' it 'shows the list of available integrations' do diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap deleted file mode 100644 index 06be40fc941..00000000000 --- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap +++ /dev/null @@ -1,305 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` -<div - class="awards js-awards-block" -> - <button - class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button" - data-testid="award-button" - title="Ada, Leonardo, and Marie reacted with :thumbsup:" - type="button" - > - <!----> - - <!----> - - <span - class="award-emoji-block" - data-testid="award-html" - > - <gl-emoji - data-name="thumbsup" - /> - </span> - - <span - class="gl-button-text" - > - - <span - class="js-counter" - > - 3 - </span> - </span> - </button> - <button - class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" - data-testid="award-button" - title="You, Ada, and Marie reacted with :thumbsdown:" - type="button" - > - <!----> - - <!----> - - <span - class="award-emoji-block" - data-testid="award-html" - > - <gl-emoji - data-name="thumbsdown" - /> - </span> - - <span - class="gl-button-text" - > - - <span - class="js-counter" - > - 3 - </span> - </span> - </button> - <button - class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button" - data-testid="award-button" - title="Ada and Jane reacted with :smile:" - type="button" - > - <!----> - - <!----> - - <span - class="award-emoji-block" - data-testid="award-html" - > - <gl-emoji - data-name="smile" - /> - </span> - - <span - class="gl-button-text" - > - - <span - class="js-counter" - > - 2 - </span> - </span> - </button> - <button - class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" - data-testid="award-button" - title="You, Ada, Jane, and Leonardo reacted with :ok_hand:" - type="button" - > - <!----> - - <!----> - - <span - class="award-emoji-block" - data-testid="award-html" - > - <gl-emoji - data-name="ok_hand" - /> - </span> - - <span - class="gl-button-text" - > - - <span - class="js-counter" - > - 4 - </span> - </span> - </button> - <button - class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" - data-testid="award-button" - title="You reacted with :cactus:" - type="button" - > - <!----> - - <!----> - - <span - class="award-emoji-block" - data-testid="award-html" - > - <gl-emoji - data-name="cactus" - /> - </span> - - <span - class="gl-button-text" - > - - <span - class="js-counter" - > - 1 - </span> - </span> - </button> - <button - class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button" - data-testid="award-button" - title="Marie reacted with :a:" - type="button" - > - <!----> - - <!----> - - <span - class="award-emoji-block" - data-testid="award-html" - > - <gl-emoji - data-name="a" - /> - </span> - - <span - class="gl-button-text" - > - - <span - class="js-counter" - > - 1 - </span> - </span> - </button> - <button - class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" - data-testid="award-button" - title="You reacted with :b:" - type="button" - > - <!----> - - <!----> - - <span - class="award-emoji-block" - data-testid="award-html" - > - <gl-emoji - data-name="b" - /> - </span> - - <span - class="gl-button-text" - > - - <span - class="js-counter" - > - 1 - </span> - </span> - </button> - - <div - class="award-menu-holder gl-my-2" - > - <div - class="emoji-picker" - data-testid="emoji-picker" - title="Add reaction" - > - <div - boundary="scrollParent" - class="dropdown b-dropdown gl-dropdown btn-group" - id="__BVID__13" - lazy="" - menu-class="dropdown-extended-height" - no-flip="" - > - <!----> - <button - aria-expanded="false" - aria-haspopup="true" - class="btn dropdown-toggle btn-default btn-md add-reaction-button btn-icon gl-relative! gl-button gl-dropdown-toggle btn-default-secondary" - id="__BVID__13__BV_toggle_" - type="button" - > - <span - class="gl-sr-only" - > - Add reaction - </span> - - <span - class="reaction-control-icon reaction-control-icon-neutral" - > - <svg - aria-hidden="true" - class="gl-icon s16" - data-testid="slight-smile-icon" - role="img" - > - <use - href="#slight-smile" - /> - </svg> - </span> - - <span - class="reaction-control-icon reaction-control-icon-positive" - > - <svg - aria-hidden="true" - class="gl-icon s16" - data-testid="smiley-icon" - role="img" - > - <use - href="#smiley" - /> - </svg> - </span> - - <span - class="reaction-control-icon reaction-control-icon-super-positive" - > - <svg - aria-hidden="true" - class="gl-icon s16" - data-testid="smile-icon" - role="img" - > - <use - href="#smile" - /> - </svg> - </span> - </button> - <ul - aria-labelledby="__BVID__13__BV_toggle_" - class="dropdown-menu dropdown-extended-height dropdown-menu-right" - role="menu" - tabindex="-1" - > - <!----> - </ul> - </div> - </div> - </div> -</div> -`; diff --git a/spec/frontend/vue_shared/components/awards_list_spec.js b/spec/frontend/vue_shared/components/awards_list_spec.js index 1c8cf726aca..c7f9d8fd8d5 100644 --- a/spec/frontend/vue_shared/components/awards_list_spec.js +++ b/spec/frontend/vue_shared/components/awards_list_spec.js @@ -38,7 +38,18 @@ const TEST_AWARDS = [ createAward(EMOJI_CACTUS, USERS.root), createAward(EMOJI_A, USERS.marie), createAward(EMOJI_B, USERS.root), + createAward(EMOJI_100, USERS.ada), ]; +const TEST_AWARDS_LENGTH = [ + EMOJI_SMILE, + EMOJI_OK, + EMOJI_THUMBSUP, + EMOJI_THUMBSDOWN, + EMOJI_A, + EMOJI_B, + EMOJI_CACTUS, + EMOJI_100, +].length; const TEST_ADD_BUTTON_CLASS = 'js-test-add-button-class'; const REACTION_CONTROL_CLASSES = [ @@ -88,10 +99,6 @@ describe('vue_shared/components/awards_list', () => { }); }); - it('matches snapshot', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - it('shows awards in correct order', () => { expect(findAwardsData()).toEqual([ { @@ -108,6 +115,12 @@ describe('vue_shared/components/awards_list', () => { }, { classes: REACTION_CONTROL_CLASSES, + count: 1, + html: matchingEmojiTag(EMOJI_100), + title: `Ada reacted with :${EMOJI_100}:`, + }, + { + classes: REACTION_CONTROL_CLASSES, count: 2, html: matchingEmojiTag(EMOJI_SMILE), title: `Ada and Jane reacted with :${EMOJI_SMILE}:`, @@ -142,33 +155,23 @@ describe('vue_shared/components/awards_list', () => { it('with award clicked, it emits award', () => { expect(wrapper.emitted().award).toBeUndefined(); - findAwardButtons().at(2).vm.$emit('click'); + findAwardButtons().at(3).vm.$emit('click'); expect(wrapper.emitted().award).toEqual([[EMOJI_SMILE]]); }); - it('shows add award button', () => { - const btn = findAddAwardButton(); + it('with numeric award clicked, it emits award as is', () => { + expect(wrapper.emitted().award).toBeUndefined(); - expect(btn.exists()).toBe(true); - }); - }); + findAwardButtons().at(2).vm.$emit('click'); - describe('with numeric award', () => { - beforeEach(() => { - createComponent({ - awards: [createAward(EMOJI_100, USERS.ada)], - canAwardEmoji: true, - currentUserId: USERS.root.id, - }); + expect(wrapper.emitted().award).toEqual([[EMOJI_100]]); }); - it('when clicked, it emits award as number', () => { - expect(wrapper.emitted().award).toBeUndefined(); - - findAwardButtons().at(0).vm.$emit('click'); + it('shows add award button', () => { + const btn = findAddAwardButton(); - expect(wrapper.emitted().award).toEqual([[Number(EMOJI_100)]]); + expect(btn.exists()).toBe(true); }); }); @@ -210,7 +213,7 @@ describe('vue_shared/components/awards_list', () => { it('disables award buttons', () => { const buttons = findAwardButtons(); - expect(buttons.length).toBe(7); + expect(buttons.length).toBe(TEST_AWARDS_LENGTH); expect(buttons.wrappers.every((x) => x.classes('disabled'))).toBe(true); }); }); diff --git a/spec/graphql/graphql_triggers_spec.rb b/spec/graphql/graphql_triggers_spec.rb index a54cb8a7988..00b5aec366e 100644 --- a/spec/graphql/graphql_triggers_spec.rb +++ b/spec/graphql/graphql_triggers_spec.rb @@ -116,4 +116,18 @@ RSpec.describe GraphqlTriggers do GraphqlTriggers.merge_request_merge_status_updated(merge_request) end end + + describe '.merge_request_approval_state_updated' do + it 'triggers the mergeRequestApprovalStateUpdated subscription' do + merge_request = build_stubbed(:merge_request) + + expect(GitlabSchema.subscriptions).to receive(:trigger).with( + 'mergeRequestApprovalStateUpdated', + { issuable_id: merge_request.to_gid }, + merge_request + ).and_call_original + + GraphqlTriggers.merge_request_approval_state_updated(merge_request) + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3af02340ded..a6dbaef3a66 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -8380,6 +8380,70 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to be(false) } end + describe '.cascading_with_parent_namespace' do + let_it_be_with_reload(:group) { create(:group, :with_root_storage_statistics) } + let_it_be_with_reload(:subgroup) { create(:group, parent: group) } + let_it_be_with_reload(:project) { create(:project, group: subgroup) } + let_it_be_with_reload(:project_without_group) { create(:project) } + + shared_examples 'cascading settings' do |attribute| + it 'return self value when no parent' do + expect(project_without_group.group).to be_nil + + project_without_group.update!(attribute => true) + expect(project_without_group.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project_without_group.public_send("#{attribute}_locked?")).to be_falsey + + project_without_group.update!(attribute => false) + expect(project_without_group.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project_without_group.public_send("#{attribute}_locked?")).to be_falsey + end + + it 'return self value when unlocked' do + subgroup.namespace_settings.update!(attribute => false) + group.namespace_settings.update!(attribute => false) + + project.update!(attribute => true) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project.public_send("#{attribute}_locked?")).to be_falsey + + project.update!(attribute => false) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project.public_send("#{attribute}_locked?")).to be_falsey + end + + it 'still return self value when locked subgroup' do + subgroup.namespace_settings.update!(attribute => true) + group.namespace_settings.update!(attribute => false) + + project.update!(attribute => true) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project.public_send("#{attribute}_locked?")).to be_falsey + + project.update!(attribute => false) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project.public_send("#{attribute}_locked?")).to be_falsey + end + + it 'still return unlocked value when locked group' do + subgroup.namespace_settings.update!(attribute => false) + group.namespace_settings.update!(attribute => true) + + project.update!(attribute => true) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project.public_send("#{attribute}_locked?")).to be_falsey + + project.update!(attribute => false) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project.public_send("#{attribute}_locked?")).to be_falsey + end + end + + it_behaves_like 'cascading settings', :only_allow_merge_if_pipeline_succeeds + it_behaves_like 'cascading settings', :allow_merge_on_skipped_pipeline + it_behaves_like 'cascading settings', :only_allow_merge_if_all_discussions_are_resolved + end + private def finish_job(export_job) diff --git a/spec/services/merge_requests/approval_service_spec.rb b/spec/services/merge_requests/approval_service_spec.rb index da6492aca95..1d6427900b9 100644 --- a/spec/services/merge_requests/approval_service_spec.rb +++ b/spec/services/merge_requests/approval_service_spec.rb @@ -91,6 +91,10 @@ RSpec.describe MergeRequests::ApprovalService do it_behaves_like 'triggers GraphQL subscription mergeRequestReviewersUpdated' do let(:action) { service.execute(merge_request) } end + + it_behaves_like 'triggers GraphQL subscription mergeRequestApprovalStateUpdated' do + let(:action) { service.execute(merge_request) } + end end context 'user cannot update the merge request' do @@ -109,6 +113,10 @@ RSpec.describe MergeRequests::ApprovalService do it_behaves_like 'does not trigger GraphQL subscription mergeRequestReviewersUpdated' do let(:action) { service.execute(merge_request) } end + + it_behaves_like 'does not trigger GraphQL subscription mergeRequestApprovalStateUpdated' do + let(:action) { service.execute(merge_request) } + end end end end diff --git a/spec/services/merge_requests/remove_approval_service_spec.rb b/spec/services/merge_requests/remove_approval_service_spec.rb index 7b38f0d1c45..fd8240935e8 100644 --- a/spec/services/merge_requests/remove_approval_service_spec.rb +++ b/spec/services/merge_requests/remove_approval_service_spec.rb @@ -53,6 +53,10 @@ RSpec.describe MergeRequests::RemoveApprovalService do it_behaves_like 'triggers GraphQL subscription mergeRequestReviewersUpdated' do let(:action) { execute! } end + + it_behaves_like 'triggers GraphQL subscription mergeRequestApprovalStateUpdated' do + let(:action) { execute! } + end end context 'with a user who has not approved' do @@ -77,6 +81,10 @@ RSpec.describe MergeRequests::RemoveApprovalService do it_behaves_like 'does not trigger GraphQL subscription mergeRequestReviewersUpdated' do let(:action) { execute! } end + + it_behaves_like 'does not trigger GraphQL subscription mergeRequestApprovalStateUpdated' do + let(:action) { execute! } + end end end end diff --git a/spec/support/shared_examples/services/approval_state_updated_trigger_shared_examples.rb b/spec/support/shared_examples/services/approval_state_updated_trigger_shared_examples.rb new file mode 100644 index 00000000000..455fd308be8 --- /dev/null +++ b/spec/support/shared_examples/services/approval_state_updated_trigger_shared_examples.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'triggers GraphQL subscription mergeRequestApprovalStateUpdated' do + specify do + expect(GraphqlTriggers).to receive(:merge_request_approval_state_updated).with(merge_request) + + action + end +end + +RSpec.shared_examples 'does not trigger GraphQL subscription mergeRequestApprovalStateUpdated' do + specify do + expect(GraphqlTriggers).not_to receive(:merge_request_approval_state_updated) + + action + end +end diff --git a/vendor/gems/bundler-checksum/lib/bundler/checksum/command/init.rb b/vendor/gems/bundler-checksum/lib/bundler/checksum/command/init.rb index fed0e11080f..47a9b676f1d 100644 --- a/vendor/gems/bundler-checksum/lib/bundler/checksum/command/init.rb +++ b/vendor/gems/bundler-checksum/lib/bundler/checksum/command/init.rb @@ -16,14 +16,22 @@ module Bundler::Checksum::Command .send(:compact_index_client) .instance_variable_get(:@cache) - seen = [] Bundler.definition.resolve.sort_by(&:name).each do |spec| next unless spec.source.is_a?(Bundler::Source::Rubygems) + spec_identifier = "#{spec.name}==#{spec.version}" - next if seen.include?(spec.name) - seen << spec.name + previous_checksum = previous_checksums.select do |checksum| + checksum[:name] == spec.name && checksum[:version] == spec.version.to_s + end + + if !previous_checksum.empty? + $stderr.puts "Using #{spec_identifier}" + checksums += previous_checksum + + next + end - $stderr.puts "Adding #{spec.name}==#{spec.version}" + $stderr.puts "Adding #{spec_identifier}" compact_index_dependencies = compact_index_cache.dependencies(spec.name).select { |item| item.first == spec.version.to_s } @@ -54,6 +62,15 @@ module Bundler::Checksum::Command private + def previous_checksums + @previous_checksums ||= + if File.exist?(checksum_file) + ::Bundler::Checksum.checksums_from_file + else + [] + end + end + def checksum_file ::Bundler::Checksum.checksum_file end |