Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									e79ee0307d
								
							
						
					
					
						commit
						a85d15fdb3
					
				|  | @ -1879,26 +1879,6 @@ Layout/ArgumentAlignment: | ||||||
|     - 'spec/rubocop/cop/rspec/env_mocking_spec.rb' |     - 'spec/rubocop/cop/rspec/env_mocking_spec.rb' | ||||||
|     - 'spec/rubocop/cop/style/regexp_literal_mixed_preserve_spec.rb' |     - 'spec/rubocop/cop/style/regexp_literal_mixed_preserve_spec.rb' | ||||||
|     - 'spec/rubocop/formatter/graceful_formatter_spec.rb' |     - 'spec/rubocop/formatter/graceful_formatter_spec.rb' | ||||||
|     - 'spec/services/design_management/save_designs_service_spec.rb' |  | ||||||
|     - 'spec/services/discussions/resolve_service_spec.rb' |  | ||||||
|     - 'spec/services/draft_notes/publish_service_spec.rb' |  | ||||||
|     - 'spec/services/environments/stop_service_spec.rb' |  | ||||||
|     - 'spec/services/environments/stop_stale_service_spec.rb' |  | ||||||
|     - 'spec/services/loose_foreign_keys/batch_cleaner_service_spec.rb' |  | ||||||
|     - 'spec/services/metrics/dashboard/clone_dashboard_service_spec.rb' |  | ||||||
|     - 'spec/services/note_summary_spec.rb' |  | ||||||
|     - 'spec/services/notification_service_spec.rb' |  | ||||||
|     - 'spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb' |  | ||||||
|     - 'spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb' |  | ||||||
|     - 'spec/services/preview_markdown_service_spec.rb' |  | ||||||
|     - 'spec/services/protected_branches/api_service_spec.rb' |  | ||||||
|     - 'spec/services/push_event_payload_service_spec.rb' |  | ||||||
|     - 'spec/services/quick_actions/interpret_service_spec.rb' |  | ||||||
|     - 'spec/services/releases/destroy_service_spec.rb' |  | ||||||
|     - 'spec/services/resource_access_tokens/revoke_service_spec.rb' |  | ||||||
|     - 'spec/services/resource_events/merge_into_notes_service_spec.rb' |  | ||||||
|     - 'spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb' |  | ||||||
|     - 'spec/services/security/merge_reports_service_spec.rb' |  | ||||||
|     - 'spec/sidekiq/cron/job_gem_dependency_spec.rb' |     - 'spec/sidekiq/cron/job_gem_dependency_spec.rb' | ||||||
|     - 'spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb' |     - 'spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb' | ||||||
|     - 'spec/support/shared_examples/integrations/integration_settings_form.rb' |     - 'spec/support/shared_examples/integrations/integration_settings_form.rb' | ||||||
|  |  | ||||||
|  | @ -10,16 +10,18 @@ import { | ||||||
|   TYPE_ISSUE, |   TYPE_ISSUE, | ||||||
|   WORKSPACE_PROJECT, |   WORKSPACE_PROJECT, | ||||||
| } from '~/issues/constants'; | } from '~/issues/constants'; | ||||||
|  | import updateDescription from '~/issues/show/utils/update_description'; | ||||||
|  | import { sanitize } from '~/lib/dompurify'; | ||||||
|  | import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | ||||||
| import Poll from '~/lib/utils/poll'; | import Poll from '~/lib/utils/poll'; | ||||||
|  | import { containsSensitiveToken, confirmSensitiveAction, i18n } from '~/lib/utils/secret_detection'; | ||||||
| import { visitUrl } from '~/lib/utils/url_utility'; | import { visitUrl } from '~/lib/utils/url_utility'; | ||||||
| import { __, sprintf } from '~/locale'; | import { __, sprintf } from '~/locale'; | ||||||
| import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; | import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; | ||||||
| import { containsSensitiveToken, confirmSensitiveAction, i18n } from '~/lib/utils/secret_detection'; |  | ||||||
| import { ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH, POLLING_DELAY } from '../constants'; | import { ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH, POLLING_DELAY } from '../constants'; | ||||||
| import eventHub from '../event_hub'; | import eventHub from '../event_hub'; | ||||||
| import getIssueStateQuery from '../queries/get_issue_state.query.graphql'; | import getIssueStateQuery from '../queries/get_issue_state.query.graphql'; | ||||||
| import Service from '../services/index'; | import Service from '../services/index'; | ||||||
| import Store from '../stores'; |  | ||||||
| import DescriptionComponent from './description.vue'; | import DescriptionComponent from './description.vue'; | ||||||
| import EditedComponent from './edited.vue'; | import EditedComponent from './edited.vue'; | ||||||
| import FormComponent from './form.vue'; | import FormComponent from './form.vue'; | ||||||
|  | @ -234,7 +236,16 @@ export default { | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     const store = new Store({ |     return { | ||||||
|  |       formState: { | ||||||
|  |         title: '', | ||||||
|  |         description: '', | ||||||
|  |         lockedWarningVisible: false, | ||||||
|  |         updateLoading: false, | ||||||
|  |         lock_version: 0, | ||||||
|  |         issuableTemplates: {}, | ||||||
|  |       }, | ||||||
|  |       state: { | ||||||
|         titleHtml: this.initialTitleHtml, |         titleHtml: this.initialTitleHtml, | ||||||
|         titleText: this.initialTitleText, |         titleText: this.initialTitleText, | ||||||
|         descriptionHtml: this.initialDescriptionHtml, |         descriptionHtml: this.initialDescriptionHtml, | ||||||
|  | @ -244,11 +255,7 @@ export default { | ||||||
|         updatedByPath: this.updatedByPath, |         updatedByPath: this.updatedByPath, | ||||||
|         taskCompletionStatus: this.initialTaskCompletionStatus, |         taskCompletionStatus: this.initialTaskCompletionStatus, | ||||||
|         lock_version: this.lockVersion, |         lock_version: this.lockVersion, | ||||||
|     }); |       }, | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|       store, |  | ||||||
|       state: store.state, |  | ||||||
|       showForm: false, |       showForm: false, | ||||||
|       templatesRequested: false, |       templatesRequested: false, | ||||||
|       isStickyHeaderShowing: false, |       isStickyHeaderShowing: false, | ||||||
|  | @ -264,17 +271,9 @@ export default { | ||||||
|     headerClasses() { |     headerClasses() { | ||||||
|       return this.issuableType === TYPE_INCIDENT ? 'gl-mb-3' : 'gl-mb-6'; |       return this.issuableType === TYPE_INCIDENT ? 'gl-mb-3' : 'gl-mb-6'; | ||||||
|     }, |     }, | ||||||
|     issuableTemplates() { |  | ||||||
|       return this.store.formState.issuableTemplates; |  | ||||||
|     }, |  | ||||||
|     formState() { |  | ||||||
|       return this.store.formState; |  | ||||||
|     }, |  | ||||||
|     issueChanged() { |     issueChanged() { | ||||||
|       const { |       const { | ||||||
|         store: { |  | ||||||
|         formState: { description, title }, |         formState: { description, title }, | ||||||
|         }, |  | ||||||
|         initialDescriptionText, |         initialDescriptionText, | ||||||
|         initialTitleText, |         initialTitleText, | ||||||
|       } = this; |       } = this; | ||||||
|  | @ -322,7 +321,7 @@ export default { | ||||||
|     this.poll = new Poll({ |     this.poll = new Poll({ | ||||||
|       resource: this.service, |       resource: this.service, | ||||||
|       method: 'getData', |       method: 'getData', | ||||||
|       successCallback: (res) => this.store.updateState(res.data), |       successCallback: (res) => this.updateState(res.data), | ||||||
|       errorCallback(err) { |       errorCallback(err) { | ||||||
|         throw new Error(err); |         throw new Error(err); | ||||||
|       }, |       }, | ||||||
|  | @ -360,23 +359,37 @@ export default { | ||||||
|       } |       } | ||||||
|       return undefined; |       return undefined; | ||||||
|     }, |     }, | ||||||
|  |     updateState(data) { | ||||||
|  |       const stateShouldUpdate = | ||||||
|  |         this.state.titleText !== data.title_text || | ||||||
|  |         this.state.descriptionText !== data.description_text; | ||||||
| 
 | 
 | ||||||
|     updateStoreState() { |       if (stateShouldUpdate) { | ||||||
|  |         this.formState.lockedWarningVisible = true; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       Object.assign(this.state, convertObjectPropsToCamelCase(data)); | ||||||
|  |       // find if there is an open details node inside of the issue description. | ||||||
|  |       const descriptionSection = document.body.querySelector( | ||||||
|  |         '.detail-page-description.content-block', | ||||||
|  |       ); | ||||||
|  |       const details = | ||||||
|  |         descriptionSection != null && descriptionSection.getElementsByTagName('details'); | ||||||
|  | 
 | ||||||
|  |       this.state.descriptionHtml = updateDescription(sanitize(data.description), details); | ||||||
|  |       this.state.titleHtml = sanitize(data.title); | ||||||
|  |       this.state.lock_version = data.lock_version; | ||||||
|  |     }, | ||||||
|  |     refetchData() { | ||||||
|       return this.service |       return this.service | ||||||
|         .getData() |         .getData() | ||||||
|         .then((res) => res.data) |         .then((res) => res.data) | ||||||
|         .then((data) => { |         .then(this.updateState) | ||||||
|           this.store.updateState(data); |         .catch(() => createAlert({ message: this.defaultErrorMessage })); | ||||||
|         }) |  | ||||||
|         .catch(() => { |  | ||||||
|           createAlert({ |  | ||||||
|             message: this.defaultErrorMessage, |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     setFormState(state) { |     setFormState(state) { | ||||||
|       this.store.setFormState(state); |       this.formState = { ...this.formState, ...state }; | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     updateFormState(templates = {}) { |     updateFormState(templates = {}) { | ||||||
|  | @ -416,7 +429,7 @@ export default { | ||||||
|         this.templatesRequested = true; |         this.templatesRequested = true; | ||||||
|         this.requestTemplatesAndShowForm(); |         this.requestTemplatesAndShowForm(); | ||||||
|       } else { |       } else { | ||||||
|         this.updateAndShowForm(this.issuableTemplates); |         this.updateAndShowForm(this.formState.issuableTemplates); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|  | @ -427,10 +440,7 @@ export default { | ||||||
|     async updateIssuable() { |     async updateIssuable() { | ||||||
|       this.setFormState({ updateLoading: true }); |       this.setFormState({ updateLoading: true }); | ||||||
| 
 | 
 | ||||||
|       const { |       const { formState, issueState } = this; | ||||||
|         store: { formState }, |  | ||||||
|         issueState, |  | ||||||
|       } = this; |  | ||||||
|       const issuablePayload = issueState.isDirty |       const issuablePayload = issueState.isDirty | ||||||
|         ? { ...formState, issue_type: issueState.issueType } |         ? { ...formState, issue_type: issueState.issueType } | ||||||
|         : formState; |         : formState; | ||||||
|  | @ -464,7 +474,7 @@ export default { | ||||||
|             visitUrl(URI); |             visitUrl(URI); | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
|         .then(this.updateStoreState) |         .then(this.refetchData) | ||||||
|         .then(() => { |         .then(() => { | ||||||
|           eventHub.$emit('close.form'); |           eventHub.$emit('close.form'); | ||||||
|         }) |         }) | ||||||
|  | @ -518,7 +528,7 @@ export default { | ||||||
|       this.poll.enable(); |       this.poll.enable(); | ||||||
|       this.poll.makeDelayedRequest(POLLING_DELAY); |       this.poll.makeDelayedRequest(POLLING_DELAY); | ||||||
| 
 | 
 | ||||||
|       this.updateStoreState(); |       this.refetchData(); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  | @ -531,7 +541,7 @@ export default { | ||||||
|         :endpoint="endpoint" |         :endpoint="endpoint" | ||||||
|         :form-state="formState" |         :form-state="formState" | ||||||
|         :initial-description-text="initialDescriptionText" |         :initial-description-text="initialDescriptionText" | ||||||
|         :issuable-templates="issuableTemplates" |         :issuable-templates="formState.issuableTemplates" | ||||||
|         :markdown-docs-path="markdownDocsPath" |         :markdown-docs-path="markdownDocsPath" | ||||||
|         :markdown-preview-path="markdownPreviewPath" |         :markdown-preview-path="markdownPreviewPath" | ||||||
|         :project-path="projectPath" |         :project-path="projectPath" | ||||||
|  |  | ||||||
|  | @ -1,46 +0,0 @@ | ||||||
| import { sanitize } from '~/lib/dompurify'; |  | ||||||
| import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; |  | ||||||
| import updateDescription from '../utils/update_description'; |  | ||||||
| 
 |  | ||||||
| export default class Store { |  | ||||||
|   constructor(initialState) { |  | ||||||
|     this.state = initialState; |  | ||||||
|     this.formState = { |  | ||||||
|       title: '', |  | ||||||
|       description: '', |  | ||||||
|       lockedWarningVisible: false, |  | ||||||
|       updateLoading: false, |  | ||||||
|       lock_version: 0, |  | ||||||
|       issuableTemplates: {}, |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   updateState(data) { |  | ||||||
|     if (this.stateShouldUpdate(data)) { |  | ||||||
|       this.formState.lockedWarningVisible = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Object.assign(this.state, convertObjectPropsToCamelCase(data)); |  | ||||||
|     // find if there is an open details node inside of the issue description.
 |  | ||||||
|     const descriptionSection = document.body.querySelector( |  | ||||||
|       '.detail-page-description.content-block', |  | ||||||
|     ); |  | ||||||
|     const details = |  | ||||||
|       descriptionSection != null && descriptionSection.getElementsByTagName('details'); |  | ||||||
| 
 |  | ||||||
|     this.state.descriptionHtml = updateDescription(sanitize(data.description), details); |  | ||||||
|     this.state.titleHtml = sanitize(data.title); |  | ||||||
|     this.state.lock_version = data.lock_version; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   stateShouldUpdate(data) { |  | ||||||
|     return ( |  | ||||||
|       this.state.titleText !== data.title_text || |  | ||||||
|       this.state.descriptionText !== data.description_text |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   setFormState(state) { |  | ||||||
|     this.formState = Object.assign(this.formState, state); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | @ -154,6 +154,15 @@ module Integrations | ||||||
|       supported_events.map { |event| event_channel_name(event) } |       supported_events.map { |event| event_channel_name(event) } | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     override :api_field_names | ||||||
|  |     def api_field_names | ||||||
|  |       if mask_configurable_channels? | ||||||
|  |         super - event_channel_names | ||||||
|  |       else | ||||||
|  |         super | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def form_fields |     def form_fields | ||||||
|       super.reject { |field| field[:name].end_with?('channel') } |       super.reject { |field| field[:name].end_with?('channel') } | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -180,11 +180,7 @@ class Packages::Package < ApplicationRecord | ||||||
|   scope :preload_conan_metadatum, -> { preload(:conan_metadatum) } |   scope :preload_conan_metadatum, -> { preload(:conan_metadatum) } | ||||||
| 
 | 
 | ||||||
|   scope :with_npm_scope, ->(scope) do |   scope :with_npm_scope, ->(scope) do | ||||||
|     if Feature.enabled?(:npm_package_registry_fix_group_path_validation) |  | ||||||
|     npm.where("position('/' in packages_packages.name) > 0 AND split_part(packages_packages.name, '/', 1) = :package_scope", package_scope: "@#{sanitize_sql_like(scope)}") |     npm.where("position('/' in packages_packages.name) > 0 AND split_part(packages_packages.name, '/', 1) = :package_scope", package_scope: "@#{sanitize_sql_like(scope)}") | ||||||
|     else |  | ||||||
|       npm.where("name ILIKE :package_name", package_name: "@#{sanitize_sql_like(scope)}/%") |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) } |   scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) } | ||||||
|  |  | ||||||
|  | @ -47,10 +47,6 @@ module Groups | ||||||
|     private |     private | ||||||
| 
 | 
 | ||||||
|     def valid_path_change? |     def valid_path_change? | ||||||
|       unless Feature.enabled?(:npm_package_registry_fix_group_path_validation) |  | ||||||
|         return valid_path_change_with_npm_packages? |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       return true unless group.packages_feature_enabled? |       return true unless group.packages_feature_enabled? | ||||||
|       return true if params[:path].blank? |       return true if params[:path].blank? | ||||||
|       return true if group.has_parent? |       return true if group.has_parent? | ||||||
|  | @ -68,21 +64,6 @@ module Groups | ||||||
|       false |       false | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     # TODO: delete this function along with npm_package_registry_fix_group_path_validation |  | ||||||
|     def valid_path_change_with_npm_packages? |  | ||||||
|       return true unless group.packages_feature_enabled? |  | ||||||
|       return true if params[:path].blank? |  | ||||||
|       return true if !group.has_parent? && group.path == params[:path] |  | ||||||
| 
 |  | ||||||
|       npm_packages = ::Packages::GroupPackagesFinder.new(current_user, group, package_type: :npm).execute |  | ||||||
|       if npm_packages.exists? |  | ||||||
|         group.errors.add(:path, s_('GroupSettings|cannot change when group contains projects with NPM packages')) |  | ||||||
|         return |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       true |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def before_assignment_hook(group, params) |     def before_assignment_hook(group, params) | ||||||
|       @full_path_before = group.full_path |       @full_path_before = group.full_path | ||||||
|       @path_before = group.path |       @path_before = group.path | ||||||
|  |  | ||||||
|  | @ -9,15 +9,15 @@ module Environments | ||||||
|     feature_category :continuous_delivery |     feature_category :continuous_delivery | ||||||
| 
 | 
 | ||||||
|     def perform(job_id, _params = {}) |     def perform(job_id, _params = {}) | ||||||
|       Ci::Build.find_by_id(job_id).try do |build| |       Ci::Processable.find_by_id(job_id).try do |job| | ||||||
|         stop_environment(build) if build.stops_environment? && build.stop_action_successful? |         stop_environment(job) if job.stops_environment? && job.stop_action_successful? | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private |     private | ||||||
| 
 | 
 | ||||||
|     def stop_environment(build) |     def stop_environment(job) | ||||||
|       build.persisted_environment.fire_state_event(:stop_complete) |       job.persisted_environment.fire_state_event(:stop_complete) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: npm_package_registry_fix_group_path_validation |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127164 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420160 |  | ||||||
| milestone: '16.3' |  | ||||||
| type: development |  | ||||||
| group: group::package registry |  | ||||||
| default_enabled: false |  | ||||||
|  | @ -872,10 +872,6 @@ When reached, limits _do_ result in disconnects that negatively impact users. | ||||||
| For consistent and stable performance, you should first explore other options such as | For consistent and stable performance, you should first explore other options such as | ||||||
| adjusting node specifications, and [reviewing large repositories](../../user/project/repository/managing_large_repositories.md) or workloads. | adjusting node specifications, and [reviewing large repositories](../../user/project/repository/managing_large_repositories.md) or workloads. | ||||||
| 
 | 
 | ||||||
| FLAG: |  | ||||||
| On self-managed GitLab, by default repository cgroups are not available. To make it available, an administrator can |  | ||||||
| [enable the feature flag](../feature_flags.md) named `gitaly_run_cmds_in_cgroup`. |  | ||||||
| 
 |  | ||||||
| When enabling cgroups for memory, you should ensure that no swap is configured on the Gitaly nodes as | When enabling cgroups for memory, you should ensure that no swap is configured on the Gitaly nodes as | ||||||
| processes may switch to using that instead of being terminated. This situation could lead to notably compromised | processes may switch to using that instead of being terminated. This situation could lead to notably compromised | ||||||
| performance. | performance. | ||||||
|  |  | ||||||
|  | @ -24,6 +24,8 @@ Users on self-managed GitLab can disable this rate limit. | ||||||
| 
 | 
 | ||||||
| ## Configure GitLab Shell operation limit | ## Configure GitLab Shell operation limit | ||||||
| 
 | 
 | ||||||
|  | > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123761) in GitLab 16.2. | ||||||
|  | 
 | ||||||
| `Git operations using SSH` is enabled by default. Defaults to 600 per user per minute. | `Git operations using SSH` is enabled by default. Defaults to 600 per user per minute. | ||||||
| 
 | 
 | ||||||
| 1. On the left sidebar, select **Your work > Admin Area**. | 1. On the left sidebar, select **Your work > Admin Area**. | ||||||
|  |  | ||||||
|  | @ -26,8 +26,10 @@ For example, if a pipeline contains DAST and SAST jobs, but the DAST job fails b | ||||||
| 
 | 
 | ||||||
| The pipeline vulnerability report only shows results contained in the security report artifacts. This report differs from | The pipeline vulnerability report only shows results contained in the security report artifacts. This report differs from | ||||||
| the [Vulnerability Report](index.md), which contains cumulative results of all successful jobs, and from the merge request | the [Vulnerability Report](index.md), which contains cumulative results of all successful jobs, and from the merge request | ||||||
| [security widget](../index.md#view-security-scan-information-in-merge-requests), which combines the branch results with | [security widget](../index.md#view-security-scan-information-in-merge-requests), which contains new vulnerability findings that don't already exist in the default branch.  | ||||||
| cumulative results. | 
 | ||||||
|  | NOTE: | ||||||
|  | If a new advisory is added to our advisory database and the last pipeline for the default branch is stale, the resulting vulnerability may appear in the MR widget as "New" when it is already in the default branch. This will be resolved by [Continuous Vulnerability Scans](https://gitlab.com/groups/gitlab-org/-/epics/7886). | ||||||
| 
 | 
 | ||||||
| The pipeline vulnerability report only displays after the pipeline is complete. If the pipeline has a [blocking manual job](../../../ci/jobs/job_control.md#types-of-manual-jobs), the pipeline waits for the manual job and the vulnerabilities cannot be displayed if the blocking manual job did not run. | The pipeline vulnerability report only displays after the pipeline is complete. If the pipeline has a [blocking manual job](../../../ci/jobs/job_control.md#types-of-manual-jobs), the pipeline waits for the manual job and the vulnerabilities cannot be displayed if the blocking manual job did not run. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -178,12 +178,11 @@ To show one file per page on the **Changes** tab: | ||||||
| 
 | 
 | ||||||
| Then, to move between files on the **Changes** tab, below each file, select the **Previous** and **Next** buttons. | Then, to move between files on the **Changes** tab, below each file, select the **Previous** and **Next** buttons. | ||||||
| 
 | 
 | ||||||
| ### Autocomplete characters | ### Auto-enclose characters | ||||||
| 
 | 
 | ||||||
| When you type an opening character, like a bracket or quote mark, in a description or comment box, | Automatically add the corresponding closing character to text when you type the opening character. For example, you can automatically insert a closing bracket when you type an opening bracket. This setting works only in description and comment boxes and for the following characters: `**"`, `'`, ```, `(`, `[`, `{`, `<`, `*`, `_**`. | ||||||
| GitLab can automatically insert the closing character as you type. For example, if you begin your text with an open bracket, GitLab can insert the closing bracket. |  | ||||||
| 
 | 
 | ||||||
| To autocomplete characters in description and comment boxes: | To auto-enclose characters in description and comment boxes: | ||||||
| 
 | 
 | ||||||
| 1. On the left sidebar, select your avatar. | 1. On the left sidebar, select your avatar. | ||||||
| 1. Select **Preferences**. | 1. Select **Preferences**. | ||||||
|  | @ -191,9 +190,12 @@ To autocomplete characters in description and comment boxes: | ||||||
| 1. Select the **Surround text selection when typing quotes or brackets** checkbox. | 1. Select the **Surround text selection when typing quotes or brackets** checkbox. | ||||||
| 1. Select **Save changes**. | 1. Select **Save changes**. | ||||||
| 
 | 
 | ||||||
|  | In a description or comment box, you can now type a word, highlight it, then type an | ||||||
|  | opening character. Instead of replacing the text, the closing character is added to the end. | ||||||
|  | 
 | ||||||
| ### Automate new list items | ### Automate new list items | ||||||
| 
 | 
 | ||||||
| Create a new list item when you press <kbd>Enter</kbd> within a list in description and comment boxes. | Create a new list item when you press <kbd>Enter</kbd> in a list in description and comment boxes. | ||||||
| 
 | 
 | ||||||
| To add a new list item when you press the <kbd>Enter</kbd> key: | To add a new list item when you press the <kbd>Enter</kbd> key: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -510,3 +510,11 @@ To disable the feature flag, run this command: | ||||||
| # Disable | # Disable | ||||||
| Feature.disable(:github_importer_lower_per_page_limit, group) | Feature.disable(:github_importer_lower_per_page_limit, group) | ||||||
| ``` | ``` | ||||||
|  | 
 | ||||||
|  | ## Known limitations | ||||||
|  | 
 | ||||||
|  | When importing a GitHub pull request with assigned reviewers that do not exist in the GitLab instance, the reviewers will not be imported. | ||||||
|  | 
 | ||||||
|  | In this case, the import will create comment events showing the non-existent users were added as reviewers and approvers. However, the actual reviewer status and approval are not applied to the merge request in GitLab. | ||||||
|  | 
 | ||||||
|  | There is currently no workaround to map the reviewers if they do not exist in the GitLab instance. The importer cannot apply approvals or reviewers from users that cannot be mapped. | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ module Quality | ||||||
|   module Seeders |   module Seeders | ||||||
|     class Issues |     class Issues | ||||||
|       DEFAULT_BACKFILL_WEEKS = 52 |       DEFAULT_BACKFILL_WEEKS = 52 | ||||||
|       DEFAULT_AVERAGE_ISSUES_PER_WEEK = 10 |       DEFAULT_AVERAGE_ISSUES_PER_WEEK = 20 | ||||||
| 
 | 
 | ||||||
|       attr_reader :project, :user |       attr_reader :project, :user | ||||||
| 
 | 
 | ||||||
|  | @ -14,23 +14,27 @@ module Quality | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def seed(backfill_weeks: DEFAULT_BACKFILL_WEEKS, average_issues_per_week: DEFAULT_AVERAGE_ISSUES_PER_WEEK) |       def seed(backfill_weeks: DEFAULT_BACKFILL_WEEKS, average_issues_per_week: DEFAULT_AVERAGE_ISSUES_PER_WEEK) | ||||||
|  |         create_milestones! | ||||||
|  |         create_team_members! | ||||||
|  | 
 | ||||||
|         created_at = backfill_weeks.to_i.weeks.ago |         created_at = backfill_weeks.to_i.weeks.ago | ||||||
|         team = project.team.users |         team = project.team.users | ||||||
|         created_issues_count = 0 |         created_issues_count = 0 | ||||||
| 
 | 
 | ||||||
|         loop do |         loop do | ||||||
|           rand(average_issues_per_week * 2).times do |           rand(1..average_issues_per_week).times do | ||||||
|             params = { |             params = { | ||||||
|               title: FFaker::Lorem.sentence(6), |               title: FFaker::Lorem.sentence(6), | ||||||
|               description: FFaker::Lorem.sentence, |               description: FFaker::Lorem.sentence, | ||||||
|               created_at: created_at + rand(6).days, |               created_at: created_at + rand(6).days, | ||||||
|               state: %w[opened closed].sample, |               state: %w[opened closed].sample, | ||||||
|               milestone: project.milestones.sample, |               milestone_id: project.milestones.sample&.id, | ||||||
|               assignee_ids: Array(team.pluck(:id).sample(3)), |               assignee_ids: Array(team.pluck(:id).sample(rand(3))), | ||||||
|  |               due_date: rand(10).days.from_now, | ||||||
|               labels: labels.join(',') |               labels: labels.join(',') | ||||||
|             } |             }.merge(additional_params) | ||||||
|             params[:closed_at] = params[:created_at] + rand(35).days if params[:state] == 'closed' |  | ||||||
| 
 | 
 | ||||||
|  |             params[:closed_at] = params[:created_at] + rand(35).days if params[:state] == 'closed' | ||||||
|             create_result = ::Issues::CreateService.new(container: project, current_user: team.sample, params: params, perform_spam_check: false).execute_without_rate_limiting |             create_result = ::Issues::CreateService.new(container: project, current_user: team.sample, params: params, perform_spam_check: false).execute_without_rate_limiting | ||||||
| 
 | 
 | ||||||
|             if create_result.success? |             if create_result.success? | ||||||
|  | @ -49,6 +53,46 @@ module Quality | ||||||
| 
 | 
 | ||||||
|       private |       private | ||||||
| 
 | 
 | ||||||
|  |       # Overriden on Quality::Seeders::Insights::Issues | ||||||
|  |       def additional_params | ||||||
|  |         {} | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       def create_team_members! | ||||||
|  |         3.times do |i| | ||||||
|  |           user = FactoryBot.create( | ||||||
|  |             :user, | ||||||
|  |             name: "I User#{i}", | ||||||
|  |             username: "i-user-#{i}-#{suffix}", | ||||||
|  |             email: "i-user-#{i}@#{suffix}.com" | ||||||
|  |           ) | ||||||
|  | 
 | ||||||
|  |           # need owner access to allow changing Issue#created_at | ||||||
|  |           project.add_owner(user) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         AuthorizedProjectUpdate::ProjectRecalculateService.new(project).execute | ||||||
|  |         # Refind object toreload ProjectTeam association which is memoized at Project model | ||||||
|  |         @project = Project.find(project.id) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       def create_milestones! | ||||||
|  |         3.times do |i| | ||||||
|  |           params = { | ||||||
|  |             project: project, | ||||||
|  |             title: "Sprint #{i}", | ||||||
|  |             description: FFaker::Lorem.sentence, | ||||||
|  |             state: [:active, :closed].sample | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           FactoryBot.create(:milestone, **params) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       def suffix | ||||||
|  |         @suffix ||= Time.now.to_i | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       def labels |       def labels | ||||||
|         @labels_pool ||= project.labels.limit(rand(3)).pluck(:title).tap do |labels_array| |         @labels_pool ||= project.labels.limit(rand(3)).pluck(:title).tap do |labels_array| | ||||||
|           labels_array.concat(project.group.labels.limit(rand(3)).pluck(:title)) if project.group |           labels_array.concat(project.group.labels.limit(rand(3)).pluck(:title)) if project.group | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ module QA | ||||||
|           return |           return | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         Page::Project::Menu.perform(&:click_merge_requests) |         Page::Project::Menu.perform(&:go_to_merge_requests) | ||||||
|         Page::MergeRequest::Index.perform(&:click_new_merge_request) |         Page::MergeRequest::Index.perform(&:click_new_merge_request) | ||||||
|         Page::MergeRequest::New.perform do |merge_request| |         Page::MergeRequest::New.perform do |merge_request| | ||||||
|           merge_request.select_source_branch(source_branch) |           merge_request.select_source_branch(source_branch) | ||||||
|  |  | ||||||
|  | @ -5,174 +5,13 @@ module QA | ||||||
|     module Group |     module Group | ||||||
|       class Menu < Page::Base |       class Menu < Page::Base | ||||||
|         include QA::Page::SubMenus::Common |         include QA::Page::SubMenus::Common | ||||||
| 
 |         include Page::SubMenus::SuperSidebar::Manage | ||||||
|         if Runtime::Env.super_sidebar_enabled? |         include Page::SubMenus::SuperSidebar::Plan | ||||||
|           prepend Page::SubMenus::SuperSidebar::Manage |         include Page::SubMenus::SuperSidebar::Settings | ||||||
|           prepend Page::SubMenus::SuperSidebar::Plan |         include SubMenus::SuperSidebar::Main | ||||||
|           prepend Page::SubMenus::SuperSidebar::Settings |         include SubMenus::SuperSidebar::Build | ||||||
|           prepend SubMenus::SuperSidebar::Main |         include SubMenus::SuperSidebar::Operate | ||||||
|           prepend SubMenus::SuperSidebar::Build |         include SubMenus::SuperSidebar::Deploy | ||||||
|           prepend SubMenus::SuperSidebar::Operate |  | ||||||
|           prepend SubMenus::SuperSidebar::Deploy |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_group_members_item |  | ||||||
|           return go_to_members if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           hover_group_information do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Members') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_subgroup_members_item |  | ||||||
|           return go_to_members if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           hover_subgroup_information do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Members') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_settings |  | ||||||
|           within_sidebar do |  | ||||||
|             click_element(:sidebar_menu_link, menu_item: 'Settings') |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_group_general_settings_item |  | ||||||
|           return go_to_general_settings if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           hover_group_settings do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'General') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def go_to_milestones |  | ||||||
|           hover_issues do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Milestones') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def go_to_runners |  | ||||||
|           hover_group_ci_cd do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Runners') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def go_to_package_settings |  | ||||||
|           hover_group_settings do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Packages and registries') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def go_to_group_packages |  | ||||||
|           return go_to_package_registry if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           hover_group_packages do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Package Registry') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def go_to_group_dependency_proxy |  | ||||||
|           return go_to_dependency_proxy if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           hover_group_packages do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Dependency Proxy') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def go_to_repository_settings |  | ||||||
|           hover_group_settings do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Repository') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def go_to_access_token_settings |  | ||||||
|           hover_group_settings do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Access Tokens') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         private |  | ||||||
| 
 |  | ||||||
|         def hover_settings |  | ||||||
|           within_sidebar do |  | ||||||
|             scroll_to_element(:sidebar_menu_link, menu_item: 'Settings') |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'Settings').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def hover_issues |  | ||||||
|           within_sidebar do |  | ||||||
|             scroll_to_element(:sidebar_menu_link, menu_item: 'Issues') |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'Issues').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def hover_group_information |  | ||||||
|           within_sidebar do |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'Group information').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def hover_subgroup_information |  | ||||||
|           within_sidebar do |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'Subgroup information').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def hover_group_ci_cd |  | ||||||
|           within_sidebar do |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'CI/CD').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def hover_group_packages |  | ||||||
|           within_sidebar do |  | ||||||
|             scroll_to_element(:sidebar_menu_link, menu_item: 'Packages and registries') |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'Packages and registries').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def hover_group_settings |  | ||||||
|           within_sidebar do |  | ||||||
|             scroll_to_element(:sidebar_menu_link, menu_item: 'Settings') |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'Settings').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ module QA | ||||||
|           module Deploy |           module Deploy | ||||||
|             extend QA::Page::PageConcern |             extend QA::Page::PageConcern | ||||||
| 
 | 
 | ||||||
|             def self.prepended(base) |             def self.included(base) | ||||||
|               super |               super | ||||||
| 
 | 
 | ||||||
|               base.class_eval do |               base.class_eval do | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ module QA | ||||||
|           module Main |           module Main | ||||||
|             extend QA::Page::PageConcern |             extend QA::Page::PageConcern | ||||||
| 
 | 
 | ||||||
|             def self.prepended(base) |             def self.included(base) | ||||||
|               super |               super | ||||||
| 
 | 
 | ||||||
|               base.class_eval do |               base.class_eval do | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ module QA | ||||||
|           module Operate |           module Operate | ||||||
|             extend QA::Page::PageConcern |             extend QA::Page::PageConcern | ||||||
| 
 | 
 | ||||||
|             def self.prepended(base) |             def self.included(base) | ||||||
|               super |               super | ||||||
| 
 | 
 | ||||||
|               base.class_eval do |               base.class_eval do | ||||||
|  |  | ||||||
|  | @ -4,64 +4,27 @@ module QA | ||||||
|   module Page |   module Page | ||||||
|     module Profile |     module Profile | ||||||
|       class Menu < Page::Base |       class Menu < Page::Base | ||||||
|         prepend QA::Mobile::Page::SubMenus::Common if QA::Runtime::Env.mobile_layout? |         include SubMenus::CreateNewMenu | ||||||
|         # TODO: integrate back once super sidebar becomes default |         include SubMenus::SuperSidebar::ContextSwitcher | ||||||
|         prepend QA::Page::Profile::SuperSidebar::Menu if QA::Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|         view 'lib/sidebars/user_settings/menus/access_tokens_menu.rb' do |  | ||||||
|           element :access_token_link |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         view 'lib/sidebars/user_settings/menus/ssh_keys_menu.rb' do |  | ||||||
|           element :ssh_keys_link |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         view 'lib/sidebars/user_settings/menus/emails_menu.rb' do |  | ||||||
|           element :profile_emails_link |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         view 'lib/sidebars/user_settings/menus/password_menu.rb' do |  | ||||||
|           element :profile_password_link |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         view 'lib/sidebars/user_settings/menus/account_menu.rb' do |  | ||||||
|           element :profile_account_link |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_access_tokens |  | ||||||
|           within_sidebar do |  | ||||||
|             click_element(:access_token_link) |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 | 
 | ||||||
|         def click_ssh_keys |         def click_ssh_keys | ||||||
|           within_sidebar do |           click_element(:nav_item_link, submenu_item: 'SSH Keys') | ||||||
|             click_element(:ssh_keys_link) |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def click_account |         def click_account | ||||||
|           within_sidebar do |           click_element(:nav_item_link, submenu_item: 'Account') | ||||||
|             click_element(:profile_account_link) |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def click_emails |         def click_emails | ||||||
|           within_sidebar do |           click_element(:nav_item_link, submenu_item: 'Emails') | ||||||
|             click_element(:profile_emails_link) |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def click_password |         def click_password | ||||||
|           within_sidebar do |           click_element(:nav_item_link, submenu_item: 'Password') | ||||||
|             click_element(:profile_password_link) |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         private |         def click_access_tokens | ||||||
| 
 |           click_element(:nav_item_link, submenu_item: 'Access Tokens') | ||||||
|         def within_sidebar(&block) |  | ||||||
|           page.within('.sidebar-top-level-items', &block) |  | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Profile |  | ||||||
|       module SuperSidebar |  | ||||||
|         module Menu |  | ||||||
|           def click_ssh_keys |  | ||||||
|             click_element(:nav_item_link, submenu_item: 'SSH Keys') |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_account |  | ||||||
|             click_element(:nav_item_link, submenu_item: 'Account') |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_emails |  | ||||||
|             click_element(:nav_item_link, submenu_item: 'Emails') |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_password |  | ||||||
|             click_element(:nav_item_link, submenu_item: 'Password') |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_access_tokens |  | ||||||
|             click_element(:nav_item_link, submenu_item: 'Access Tokens') |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -5,20 +5,7 @@ module QA | ||||||
|     module Project |     module Project | ||||||
|       class Menu < Page::Base |       class Menu < Page::Base | ||||||
|         include SubMenus::Common |         include SubMenus::Common | ||||||
|         include SubMenus::Project |  | ||||||
|         include SubMenus::CiCd |  | ||||||
|         include SubMenus::Issues |  | ||||||
|         include SubMenus::Deployments |  | ||||||
|         include SubMenus::Monitor |  | ||||||
|         include SubMenus::Infrastructure |  | ||||||
|         include SubMenus::Repository |  | ||||||
|         include SubMenus::Settings |  | ||||||
|         include SubMenus::Packages |  | ||||||
|         include SubMenus::CreateNewMenu |         include SubMenus::CreateNewMenu | ||||||
| 
 |  | ||||||
|         if Runtime::Env.super_sidebar_enabled? |  | ||||||
|           include Page::SubMenus::SuperSidebar::Manage |  | ||||||
|           include Page::SubMenus::SuperSidebar::Deploy |  | ||||||
|         include SubMenus::SuperSidebar::Plan |         include SubMenus::SuperSidebar::Plan | ||||||
|         include SubMenus::SuperSidebar::Settings |         include SubMenus::SuperSidebar::Settings | ||||||
|         include SubMenus::SuperSidebar::Code |         include SubMenus::SuperSidebar::Code | ||||||
|  | @ -26,62 +13,8 @@ module QA | ||||||
|         include SubMenus::SuperSidebar::Operate |         include SubMenus::SuperSidebar::Operate | ||||||
|         include SubMenus::SuperSidebar::Monitor |         include SubMenus::SuperSidebar::Monitor | ||||||
|         include SubMenus::SuperSidebar::Main |         include SubMenus::SuperSidebar::Main | ||||||
|         end |         include Page::SubMenus::SuperSidebar::Manage | ||||||
| 
 |         include Page::SubMenus::SuperSidebar::Deploy | ||||||
|         def click_merge_requests |  | ||||||
|           return go_to_merge_requests if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           within_sidebar do |  | ||||||
|             click_element(:sidebar_menu_link, menu_item: 'Merge requests') |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_wiki |  | ||||||
|           return go_to_wiki if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           within_sidebar do |  | ||||||
|             click_element(:sidebar_menu_link, menu_item: 'Wiki') |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_activity |  | ||||||
|           return go_to_activity if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           hover_project_information do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Activity') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_snippets |  | ||||||
|           return go_to_snippets if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           within_sidebar do |  | ||||||
|             click_element(:sidebar_menu_link, menu_item: 'Snippets') |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def click_members |  | ||||||
|           return go_to_members if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|           hover_project_information do |  | ||||||
|             within_submenu do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Members') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         private |  | ||||||
| 
 |  | ||||||
|         def hover_project_information |  | ||||||
|           within_sidebar do |  | ||||||
|             scroll_to_element(:sidebar_menu_link, menu_item: 'Project information') |  | ||||||
|             find_element(:sidebar_menu_link, menu_item: 'Project information').hover |  | ||||||
| 
 |  | ||||||
|             yield |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -1,47 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module CiCd |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_pipelines |  | ||||||
|             within_sidebar do |  | ||||||
|               click_element(:sidebar_menu_link, menu_item: 'CI/CD') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_pipeline_editor |  | ||||||
|             hover_ci_cd_pipelines do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Editor') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_ci_cd_pipelines |  | ||||||
|             within_sidebar do |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'CI/CD').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| QA::Page::Project::SubMenus::CiCd.prepend_mod_with('Page::Project::SubMenus::CiCd', namespace: QA) |  | ||||||
|  | @ -13,14 +13,6 @@ module QA | ||||||
|             base.class_eval do |             base.class_eval do | ||||||
|               include QA::Page::SubMenus::Common |               include QA::Page::SubMenus::Common | ||||||
| 
 | 
 | ||||||
|               view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do |  | ||||||
|                 element :sidebar_menu_item_link |  | ||||||
|               end |  | ||||||
| 
 |  | ||||||
|               view 'app/views/shared/nav/_sidebar_menu.html.haml' do |  | ||||||
|                 element :sidebar_menu_link |  | ||||||
|               end |  | ||||||
| 
 |  | ||||||
|               view 'app/views/layouts/nav/_top_bar.html.haml' do |               view 'app/views/layouts/nav/_top_bar.html.haml' do | ||||||
|                 element :toggle_mobile_nav_button |                 element :toggle_mobile_nav_button | ||||||
|               end |               end | ||||||
|  |  | ||||||
|  | @ -11,21 +11,12 @@ module QA | ||||||
|             super |             super | ||||||
| 
 | 
 | ||||||
|             base.class_eval do |             base.class_eval do | ||||||
|               # TODO: remove this when the super sidebar is enabled by default |               include QA::Page::SubMenus::CreateNewMenu | ||||||
|               view 'app/helpers/nav/new_dropdown_helper.rb' do |  | ||||||
|                 element :new_issue_link |  | ||||||
|               end |  | ||||||
| 
 |  | ||||||
|               view 'app/helpers/sidebars_helper.rb' do |  | ||||||
|                 element :create_menu_item |  | ||||||
|               end |  | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           def go_to_new_issue |           def go_to_new_issue | ||||||
|             within_new_item_menu do |             within_new_item_menu do | ||||||
|               next click_element(:new_issue_link) unless QA::Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|               click_element(:create_menu_item, create_menu_item: 'new_issue') |               click_element(:create_menu_item, create_menu_item: 'new_issue') | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|  |  | ||||||
|  | @ -1,40 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Deployments |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_deployments_environments |  | ||||||
|             hover_deployments do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Environments') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_deployments |  | ||||||
|             within_sidebar do |  | ||||||
|               scroll_to_element(:sidebar_menu_link, menu_item: 'Deployments') |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'Deployments').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -1,40 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Infrastructure |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_infrastructure_kubernetes |  | ||||||
|             hover_infrastructure do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_link('Kubernetes clusters') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_infrastructure |  | ||||||
|             within_sidebar do |  | ||||||
|               scroll_to_element(:sidebar_menu_link, menu_item: 'Infrastructure') |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'Infrastructure').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -1,78 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Issues |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_issues |  | ||||||
|             within_sidebar do |  | ||||||
|               click_element(:sidebar_menu_link, menu_item: 'Issues') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_milestones |  | ||||||
|             within_sidebar do |  | ||||||
|               click_element(:sidebar_menu_item_link, menu_item: 'Milestones') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_issue_boards |  | ||||||
|             hover_issues do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Boards') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_labels |  | ||||||
|             hover_issues do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Labels') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_milestones |  | ||||||
|             hover_issues do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Milestones') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_jira_issues |  | ||||||
|             hover_issues do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Jira issues') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_issues |  | ||||||
|             within_sidebar do |  | ||||||
|               scroll_to_element(:sidebar_menu_link, menu_item: 'Issues') |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'Issues').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| QA::Page::Project::SubMenus::Issues.prepend_mod_with('Page::Project::SubMenus::Issues', namespace: QA) |  | ||||||
|  | @ -1,64 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Monitor |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_monitor_incidents |  | ||||||
|             hover_monitor do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Incidents') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_monitor_alerts |  | ||||||
|             hover_monitor do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Alerts') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_monitor_on_call_schedules |  | ||||||
|             hover_monitor do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'On-call Schedules') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_monitor_escalation_policies |  | ||||||
|             hover_monitor do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Escalation Policies') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_monitor |  | ||||||
|             within_sidebar do |  | ||||||
|               scroll_to_element(:sidebar_menu_link, menu_item: 'Monitor') |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'Monitor').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Packages |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def go_to_package_registry |  | ||||||
|             hover_registry do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Package Registry') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_container_registry |  | ||||||
|             hover_registry do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_link('Container Registry') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_infrastructure_registry |  | ||||||
|             hover_registry do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_link('Terraform modules') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_registry |  | ||||||
|             within_sidebar do |  | ||||||
|               scroll_to_element(:sidebar_menu_link, menu_item: 'Packages and registries') |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'Packages and registries').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -1,29 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Project |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_project |  | ||||||
|             retry_on_exception do |  | ||||||
|               within_sidebar do |  | ||||||
|                 click_element(:sidebar_menu_link, menu_item: 'Project scope') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -1,63 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Repository |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_repository |  | ||||||
|             within_sidebar do |  | ||||||
|               click_element(:sidebar_menu_link, menu_item: 'Repository') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_repository_branches |  | ||||||
|             hover_repository do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Branches') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_repository_tags |  | ||||||
|             hover_repository do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Tags') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_repository_contributors |  | ||||||
|             return go_to_contributor_statistics if Runtime::Env.super_sidebar_enabled? |  | ||||||
| 
 |  | ||||||
|             hover_repository do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Contributor statistics') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_repository |  | ||||||
|             within_sidebar do |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'Repository').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -1,102 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module QA |  | ||||||
|   module Page |  | ||||||
|     module Project |  | ||||||
|       module SubMenus |  | ||||||
|         module Settings |  | ||||||
|           extend QA::Page::PageConcern |  | ||||||
| 
 |  | ||||||
|           def self.included(base) |  | ||||||
|             super |  | ||||||
| 
 |  | ||||||
|             base.class_eval do |  | ||||||
|               include QA::Page::Project::SubMenus::Common |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_ci_cd_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'CI/CD') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_repository_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Repository') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_general_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'General') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def click_settings |  | ||||||
|             within_sidebar do |  | ||||||
|               click_element(:sidebar_menu_link, menu_item: 'Settings') |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_integrations_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Integrations') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_monitor_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Monitor') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_access_token_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Access Tokens') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_pages_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Pages') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           def go_to_merge_request_settings |  | ||||||
|             hover_settings do |  | ||||||
|               within_submenu do |  | ||||||
|                 click_element(:sidebar_menu_item_link, menu_item: 'Merge requests') |  | ||||||
|               end |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           private |  | ||||||
| 
 |  | ||||||
|           def hover_settings |  | ||||||
|             within_sidebar do |  | ||||||
|               scroll_to_element(:sidebar_menu_link, menu_item: 'Settings') |  | ||||||
|               find_element(:sidebar_menu_link, menu_item: 'Settings').hover |  | ||||||
| 
 |  | ||||||
|               yield |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -4,12 +4,12 @@ module QA | ||||||
|   module Page |   module Page | ||||||
|     module SubMenus |     module SubMenus | ||||||
|       module Common |       module Common | ||||||
|         prepend Mobile::Page::SubMenus::Common if QA::Runtime::Env.mobile_layout? |  | ||||||
| 
 |  | ||||||
|         def self.included(base) |         def self.included(base) | ||||||
|           super |           super | ||||||
| 
 | 
 | ||||||
|           base.class_eval do |           base.class_eval do | ||||||
|  |             prepend Mobile::Page::SubMenus::Common if QA::Runtime::Env.mobile_layout? | ||||||
|  | 
 | ||||||
|             view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do |             view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do | ||||||
|               element :navbar |               element :navbar | ||||||
|             end |             end | ||||||
|  | @ -33,7 +33,7 @@ module QA | ||||||
|           yield |           yield | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         # Implementation for super-sidebar, will replace within_submenu |         # Open sidebar navigation submenu | ||||||
|         # |         # | ||||||
|         # @param [String] parent_menu_name |         # @param [String] parent_menu_name | ||||||
|         # @param [String] parent_section_id |         # @param [String] parent_section_id | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ module QA | ||||||
|         module ContextSwitcher |         module ContextSwitcher | ||||||
|           extend QA::Page::PageConcern |           extend QA::Page::PageConcern | ||||||
| 
 | 
 | ||||||
|           def self.prepended(base) |           def self.included(base) | ||||||
|             super |             super | ||||||
| 
 | 
 | ||||||
|             base.class_eval do |             base.class_eval do | ||||||
|  |  | ||||||
|  | @ -4,10 +4,6 @@ module QA | ||||||
|   module Page |   module Page | ||||||
|     module User |     module User | ||||||
|       class Show < Page::Base |       class Show < Page::Base | ||||||
|         view 'app/views/users/show.html.haml' do |  | ||||||
|           element :following_tab |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         view 'app/views/users/_follow_user.html.haml' do |         view 'app/views/users/_follow_user.html.haml' do | ||||||
|           element :follow_user_link |           element :follow_user_link | ||||||
|         end |         end | ||||||
|  | @ -25,9 +21,7 @@ module QA | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def click_following_tab |         def click_following_tab | ||||||
|           return click_element(:nav_item_link, submenu_item: 'Following') if Runtime::Env.super_sidebar_enabled? |           click_element(:nav_item_link, submenu_item: 'Following') | ||||||
| 
 |  | ||||||
|           click_element(:following_tab) |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def click_user_link(username) |         def click_user_link(username) | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ module QA | ||||||
|       def fabricate! |       def fabricate! | ||||||
|         project.visit! |         project.visit! | ||||||
| 
 | 
 | ||||||
|         Page::Project::Menu.perform { |sidebar| sidebar.click_snippets } |         Page::Project::Menu.perform(&:go_to_snippets) | ||||||
| 
 | 
 | ||||||
|         Page::Project::Snippet::New.perform do |new_snippet| |         Page::Project::Snippet::New.perform do |new_snippet| | ||||||
|           new_snippet.click_create_first_snippet |           new_snippet.click_create_first_snippet | ||||||
|  |  | ||||||
|  | @ -667,10 +667,6 @@ module QA | ||||||
|         ENV['DEFAULT_CHROME_DOWNLOAD_PATH'] || Dir.tmpdir |         ENV['DEFAULT_CHROME_DOWNLOAD_PATH'] || Dir.tmpdir | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def super_sidebar_enabled? |  | ||||||
|         enabled?(ENV['QA_SUPER_SIDEBAR_ENABLED'], default: true) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       def require_slack_env! |       def require_slack_env! | ||||||
|         missing_env = %i[slack_workspace slack_email slack_password].select do |method| |         missing_env = %i[slack_workspace slack_email slack_password].select do |method| | ||||||
|           ::QA::Runtime::Env.public_send(method).nil? |           ::QA::Runtime::Env.public_send(method).nil? | ||||||
|  |  | ||||||
|  | @ -17,25 +17,31 @@ module QA | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       let(:group) do |       let(:group) do | ||||||
|         create(:group, sandbox: sandbox_group, api_client: owner_api_client, path: "group-with-2fa-#{SecureRandom.hex(8)}") |         create(:group, sandbox: sandbox_group, api_client: owner_api_client, | ||||||
|  |           path: "group-with-2fa-#{SecureRandom.hex(8)}") | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       let(:developer_user) { create(:user, username: "developer_user_#{SecureRandom.hex(4)}", api_client: admin_api_client) } |       let(:developer_user) { create(:user, username: "developer_user_#{SecureRandom.hex(4)}", api_client: admin_api_client) } | ||||||
| 
 | 
 | ||||||
|       let(:two_fa_expected_text) { /The group settings for.*require you to enable Two-Factor Authentication for your account.*You need to do this before/ } |       let(:two_fa_expected_text) do | ||||||
|  |         /The group settings for.*require you to enable Two-Factor Authentication for your account.*You need to do this before/ | ||||||
|  |       end | ||||||
| 
 | 
 | ||||||
|       before do |       before do | ||||||
|         group.add_member(developer_user, Resource::Members::AccessLevel::DEVELOPER) |         group.add_member(developer_user, Resource::Members::AccessLevel::DEVELOPER) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       after do | ||||||
|  |         group.set_require_two_factor_authentication(value: 'false') | ||||||
|  |         group.remove_via_api! do |resource| | ||||||
|  |           resource.api_client = admin_api_client | ||||||
|  |         end | ||||||
|  |         developer_user.remove_via_api! | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       it( |       it( | ||||||
|         'allows enforcing 2FA via UI and logging in with 2FA', |         'allows enforcing 2FA via UI and logging in with 2FA', | ||||||
|         testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347931', |         testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347931' | ||||||
|         quarantine: { |  | ||||||
|           type: :bug, |  | ||||||
|           only: { condition: -> { QA::Runtime::Env.super_sidebar_enabled? } }, |  | ||||||
|           issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/409336' |  | ||||||
|         } |  | ||||||
|       ) do |       ) do | ||||||
|         enforce_two_factor_authentication_on_group(group) |         enforce_two_factor_authentication_on_group(group) | ||||||
| 
 | 
 | ||||||
|  | @ -58,21 +64,13 @@ module QA | ||||||
|         expect(Page::Main::Menu.perform(&:signed_in?)).to be_truthy |         expect(Page::Main::Menu.perform(&:signed_in?)).to be_truthy | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       after do |  | ||||||
|         group.set_require_two_factor_authentication(value: 'false') |  | ||||||
|         group.remove_via_api! do |resource| |  | ||||||
|           resource.api_client = admin_api_client |  | ||||||
|         end |  | ||||||
|         developer_user.remove_via_api! |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       # We are intentionally using the UI to enforce 2FA to exercise the flow with UI. |       # We are intentionally using the UI to enforce 2FA to exercise the flow with UI. | ||||||
|       # Any future tests should use the API for this purpose. |       # Any future tests should use the API for this purpose. | ||||||
|       def enforce_two_factor_authentication_on_group(group) |       def enforce_two_factor_authentication_on_group(group) | ||||||
|         Flow::Login.while_signed_in(as: owner_user) do |         Flow::Login.while_signed_in(as: owner_user) do | ||||||
|           group.visit! |           group.visit! | ||||||
| 
 | 
 | ||||||
|           Page::Group::Menu.perform(&:click_group_general_settings_item) |           Page::Group::Menu.perform(&:go_to_general_settings) | ||||||
|           Page::Group::Settings::General.perform(&:set_require_2fa_enabled) |           Page::Group::Settings::General.perform(&:set_require_2fa_enabled) | ||||||
| 
 | 
 | ||||||
|           QA::Support::Retrier.retry_on_exception(reload_page: page) do |           QA::Support::Retrier.retry_on_exception(reload_page: page) do | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ module QA | ||||||
|       it 'is received by a user for project invitation', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347961' do |       it 'is received by a user for project invitation', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347961' do | ||||||
|         project.visit! |         project.visit! | ||||||
| 
 | 
 | ||||||
|         Page::Project::Menu.perform(&:click_members) |         Page::Project::Menu.perform(&:go_to_members) | ||||||
|         Page::Project::Members.perform do |member_settings| |         Page::Project::Members.perform do |member_settings| | ||||||
|           member_settings.add_member(user.username) |           member_settings.add_member(user.username) | ||||||
|         end |         end | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ module QA | ||||||
|           user |           user | ||||||
|           Flow::Login.sign_in_as_admin |           Flow::Login.sign_in_as_admin | ||||||
|           project.visit! |           project.visit! | ||||||
|           Page::Project::Menu.perform(&:click_members) |           Page::Project::Menu.perform(&:go_to_members) | ||||||
|           Page::Project::Members.perform do |members| |           Page::Project::Members.perform do |members| | ||||||
|             members.add_member(user.username) |             members.add_member(user.username) | ||||||
|           end |           end | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ module QA | ||||||
|         testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347809' do |         testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347809' do | ||||||
|         project.visit! |         project.visit! | ||||||
| 
 | 
 | ||||||
|         Page::Project::Menu.perform(&:click_wiki) |         Page::Project::Menu.perform(&:go_to_wiki) | ||||||
|         Page::Project::Wiki::Show.perform(&:click_create_your_first_page) |         Page::Project::Wiki::Show.perform(&:click_create_your_first_page) | ||||||
| 
 | 
 | ||||||
|         Page::Project::Wiki::Edit.perform do |edit| |         Page::Project::Wiki::Edit.perform do |edit| | ||||||
|  |  | ||||||
|  | @ -142,7 +142,7 @@ module QA | ||||||
| 
 | 
 | ||||||
|           project.group.visit! |           project.group.visit! | ||||||
| 
 | 
 | ||||||
|           Page::Group::Menu.perform(&:go_to_group_dependency_proxy) |           Page::Group::Menu.perform(&:go_to_dependency_proxy) | ||||||
| 
 | 
 | ||||||
|           Page::Group::DependencyProxy.perform do |index| |           Page::Group::DependencyProxy.perform do |index| | ||||||
|             expect(index).to have_blob_count("Contains 1 blobs of images") |             expect(index).to have_blob_count("Contains 1 blobs of images") | ||||||
|  |  | ||||||
|  | @ -163,7 +163,7 @@ module QA | ||||||
| 
 | 
 | ||||||
|           project.group.visit! |           project.group.visit! | ||||||
| 
 | 
 | ||||||
|           Page::Group::Menu.perform(&:go_to_group_packages) |           Page::Group::Menu.perform(&:go_to_package_registry) | ||||||
| 
 | 
 | ||||||
|           Page::Project::Packages::Index.perform do |index| |           Page::Project::Packages::Index.perform do |index| | ||||||
|             expect(index).to have_package(package.name) |             expect(index).to have_package(package.name) | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ module QA | ||||||
| 
 | 
 | ||||||
|       it 'transfers a subgroup to another group', |       it 'transfers a subgroup to another group', | ||||||
|         testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347692' do |         testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347692' do | ||||||
|         Page::Group::Menu.perform(&:click_group_general_settings_item) |         Page::Group::Menu.perform(&:go_to_general_settings) | ||||||
|         Page::Group::Settings::General.perform do |general| |         Page::Group::Settings::General.perform do |general| | ||||||
|           general.transfer_group(sub_group_for_transfer, target_group) |           general.transfer_group(sub_group_for_transfer, target_group) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ module QA | ||||||
|         project = create(:project, name: 'add-member-project') |         project = create(:project, name: 'add-member-project') | ||||||
|         project.visit! |         project.visit! | ||||||
| 
 | 
 | ||||||
|         Page::Project::Menu.perform(&:click_members) |         Page::Project::Menu.perform(&:go_to_members) | ||||||
|         Page::Project::Members.perform do |members| |         Page::Project::Members.perform do |members| | ||||||
|           members.add_member(user.username) |           members.add_member(user.username) | ||||||
|           members.search_member(user.username) |           members.search_member(user.username) | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ module QA | ||||||
|     describe 'Invite group', :reliable, product_group: :tenant_scale do |     describe 'Invite group', :reliable, product_group: :tenant_scale do | ||||||
|       shared_examples 'invites group to project' do |       shared_examples 'invites group to project' do | ||||||
|         it 'grants group and members correct access level' do |         it 'grants group and members correct access level' do | ||||||
|           Page::Project::Menu.perform(&:click_members) |           Page::Project::Menu.perform(&:go_to_members) | ||||||
|           Page::Project::Members.perform do |project_members| |           Page::Project::Members.perform do |project_members| | ||||||
|             project_members.invite_group(group.path, 'Developer') |             project_members.invite_group(group.path, 'Developer') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ module QA | ||||||
|           end.project |           end.project | ||||||
| 
 | 
 | ||||||
|           project.visit! |           project.visit! | ||||||
|           Page::Project::Menu.perform(&:click_activity) |           Page::Project::Menu.perform(&:go_to_activity) | ||||||
|           Page::Project::Activity.perform do |activity| |           Page::Project::Activity.perform do |activity| | ||||||
|             activity.click_push_events |             activity.click_push_events | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ module QA | ||||||
|           Flow::Login.while_signed_in_as_admin do |           Flow::Login.while_signed_in_as_admin do | ||||||
|             group.visit! |             group.visit! | ||||||
| 
 | 
 | ||||||
|             Page::Group::Menu.perform(&:click_subgroup_members_item) |             Page::Group::Menu.perform(&:go_to_members) | ||||||
|             Page::Group::Members.perform do |members_page| |             Page::Group::Members.perform do |members_page| | ||||||
|               members_page.search_member(user.username) |               members_page.search_member(user.username) | ||||||
|               members_page.remove_member(user.username) |               members_page.remove_member(user.username) | ||||||
|  |  | ||||||
|  | @ -116,12 +116,12 @@ module QA | ||||||
|         # @return [String] 'gitlab' or 'jihulab' |         # @return [String] 'gitlab' or 'jihulab' | ||||||
|         def production_domain(tld) |         def production_domain(tld) | ||||||
|           return 'gitlab' unless GitlabEdition.jh? |           return 'gitlab' unless GitlabEdition.jh? | ||||||
|           return 'gitlab' if tld == 'hk' |           return 'gitlab' if tld == 'hk' || tld == 'cn' | ||||||
|           return 'jihulab' if tld == 'com' |           return 'jihulab' if tld == 'com' | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def opts_tld |         def opts_tld | ||||||
|           GitlabEdition.jh? ? '(.com|.hk)' : '(.com|.net)' |           GitlabEdition.jh? ? '(.com|.hk|.cn)' : '(.com|.net)' | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def get_tld(host) |         def get_tld(host) | ||||||
|  |  | ||||||
|  | @ -30,11 +30,19 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'returns true when url has .net' do |     it 'returns true when url has .net' do | ||||||
|  |       allow(GitlabEdition).to receive(:jh?).and_return(false) | ||||||
|       QA::Runtime::Scenario.define(:gitlab_address, "https://release.gitlab.net") |       QA::Runtime::Scenario.define(:gitlab_address, "https://release.gitlab.net") | ||||||
| 
 | 
 | ||||||
|       expect(described_class.context_matches?).to be_truthy |       expect(described_class.context_matches?).to be_truthy | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     it 'returns true when url has .cn on jh side' do | ||||||
|  |       allow(GitlabEdition).to receive(:jh?).and_return(true) | ||||||
|  |       QA::Runtime::Scenario.define(:gitlab_address, "https://release.gitlab.cn") | ||||||
|  | 
 | ||||||
|  |       expect(described_class.context_matches?).to be_truthy | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     it 'returns false when url does not have .com' do |     it 'returns false when url does not have .com' do | ||||||
|       QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test") |       QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test") | ||||||
| 
 | 
 | ||||||
|  | @ -83,6 +91,9 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do | ||||||
| 
 | 
 | ||||||
|         QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.hk/") |         QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.hk/") | ||||||
|         expect(described_class.context_matches?(:production)).to be_truthy |         expect(described_class.context_matches?(:production)).to be_truthy | ||||||
|  | 
 | ||||||
|  |         QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.cn/") | ||||||
|  |         expect(described_class.context_matches?(:production)).to be_truthy | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it 'matches domain' do |       it 'matches domain' do | ||||||
|  |  | ||||||
|  | @ -1,39 +0,0 @@ | ||||||
| import Store from '~/issues/show/stores'; |  | ||||||
| import updateDescription from '~/issues/show/utils/update_description'; |  | ||||||
| 
 |  | ||||||
| jest.mock('~/issues/show/utils/update_description'); |  | ||||||
| 
 |  | ||||||
| describe('Store', () => { |  | ||||||
|   let store; |  | ||||||
| 
 |  | ||||||
|   beforeEach(() => { |  | ||||||
|     store = new Store({ |  | ||||||
|       descriptionHtml: '<p>This is a description</p>', |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('updateState', () => { |  | ||||||
|     beforeEach(() => { |  | ||||||
|       document.body.innerHTML = ` |  | ||||||
|             <div class="detail-page-description content-block"> |  | ||||||
|               <details open> |  | ||||||
|                 <summary>One</summary> |  | ||||||
|               </details> |  | ||||||
|               <details> |  | ||||||
|                 <summary>Two</summary> |  | ||||||
|               </details> |  | ||||||
|             </div> |  | ||||||
|           `;
 |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     afterEach(() => { |  | ||||||
|       document.getElementsByTagName('html')[0].innerHTML = ''; |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('calls updateDetailsState', () => { |  | ||||||
|       store.updateState({ description: '' }); |  | ||||||
| 
 |  | ||||||
|       expect(updateDescription).toHaveBeenCalledTimes(1); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
|  | @ -364,4 +364,17 @@ RSpec.describe Integrations::BaseChatNotification, feature_category: :integratio | ||||||
|       expect { subject.event_channel_value(:foo) }.to raise_error(NoMethodError) |       expect { subject.event_channel_value(:foo) }.to raise_error(NoMethodError) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   describe '#api_field_names' do | ||||||
|  |     context 'when channels are masked' do | ||||||
|  |       let(:project) { build(:project) } | ||||||
|  |       let(:integration) { build(:discord_integration, project: project, webhook: 'https://discord.com/api/') } | ||||||
|  | 
 | ||||||
|  |       it 'does not include channel properties', :aggregate_failures do | ||||||
|  |         integration.event_channel_names.each do |field| | ||||||
|  |           expect(integration.api_field_names).not_to include(field) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -875,14 +875,6 @@ RSpec.describe Packages::Package, type: :model, feature_category: :package_regis | ||||||
|     subject { described_class.with_npm_scope('test') } |     subject { described_class.with_npm_scope('test') } | ||||||
| 
 | 
 | ||||||
|     it { is_expected.to contain_exactly(package1) } |     it { is_expected.to contain_exactly(package1) } | ||||||
| 
 |  | ||||||
|     context 'when npm_package_registry_fix_group_path_validation is disabled' do |  | ||||||
|       before do |  | ||||||
|         stub_feature_flags(npm_package_registry_fix_group_path_validation: false) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it { is_expected.to contain_exactly(package1) } |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '.without_nuget_temporary_name' do |   describe '.without_nuget_temporary_name' do | ||||||
|  |  | ||||||
|  | @ -43,9 +43,7 @@ RSpec.describe DesignManagement::SaveDesignsService, feature_category: :design_m | ||||||
|     design_files = files_to_upload || files |     design_files = files_to_upload || files | ||||||
|     design_files.each(&:rewind) |     design_files.each(&:rewind) | ||||||
| 
 | 
 | ||||||
|     service = described_class.new(project, user, |     service = described_class.new(project, user, issue: issue, files: design_files) | ||||||
|                                   issue: issue, |  | ||||||
|                                   files: design_files) |  | ||||||
|     service.execute |     service.execute | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  | @ -390,9 +388,12 @@ RSpec.describe DesignManagement::SaveDesignsService, feature_category: :design_m | ||||||
|         before do |         before do | ||||||
|           path = File.join(build(:design, issue: issue, filename: filename).full_path) |           path = File.join(build(:design, issue: issue, filename: filename).full_path) | ||||||
|           design_repository.create_if_not_exists |           design_repository.create_if_not_exists | ||||||
|           design_repository.create_file(user, path, 'something fake', |           design_repository.create_file( | ||||||
|  |             user, | ||||||
|  |             path, 'something fake', | ||||||
|             branch_name: project.default_branch_or_main, |             branch_name: project.default_branch_or_main, | ||||||
|                                         message: 'Somehow created without being tracked in db') |             message: 'Somehow created without being tracked in db' | ||||||
|  |           ) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         it 'creates the design and a new version for it' do |         it 'creates the design and a new version for it' do | ||||||
|  |  | ||||||
|  | @ -94,9 +94,11 @@ RSpec.describe Discussions::ResolveService, feature_category: :code_review_workf | ||||||
| 
 | 
 | ||||||
|     it 'raises an argument error if discussions do not belong to the same noteable' do |     it 'raises an argument error if discussions do not belong to the same noteable' do | ||||||
|       other_merge_request = create(:merge_request) |       other_merge_request = create(:merge_request) | ||||||
|       other_discussion = create(:diff_note_on_merge_request, |       other_discussion = create( | ||||||
|  |         :diff_note_on_merge_request, | ||||||
|         noteable: other_merge_request, |         noteable: other_merge_request, | ||||||
|                                 project: other_merge_request.source_project).to_discussion |         project: other_merge_request.source_project | ||||||
|  |       ).to_discussion | ||||||
|       expect do |       expect do | ||||||
|         described_class.new(project, user, one_or_more_discussions: [discussion, other_discussion]) |         described_class.new(project, user, one_or_more_discussions: [discussion, other_discussion]) | ||||||
|       end.to raise_error( |       end.to raise_error( | ||||||
|  |  | ||||||
|  | @ -292,9 +292,12 @@ RSpec.describe DraftNotes::PublishService, feature_category: :code_review_workfl | ||||||
|       other_user = create(:user) |       other_user = create(:user) | ||||||
|       project.add_developer(other_user) |       project.add_developer(other_user) | ||||||
| 
 | 
 | ||||||
|       create(:draft_note, merge_request: merge_request, |       create( | ||||||
|  |         :draft_note, | ||||||
|  |         merge_request: merge_request, | ||||||
|         author: user, |         author: user, | ||||||
|                           note: "thanks\n/assign #{other_user.to_reference}") |         note: "thanks\n/assign #{other_user.to_reference}" | ||||||
|  |       ) | ||||||
| 
 | 
 | ||||||
|       expect { publish }.to change { DraftNote.count }.by(-1).and change { Note.count }.by(2) |       expect { publish }.to change { DraftNote.count }.by(-1).and change { Note.count }.by(2) | ||||||
|       expect(merge_request.reload.assignees).to match_array([other_user]) |       expect(merge_request.reload.assignees).to match_array([other_user]) | ||||||
|  |  | ||||||
|  | @ -135,8 +135,7 @@ RSpec.describe Environments::StopService, feature_category: :continuous_delivery | ||||||
|       context 'when branch for stop action is protected' do |       context 'when branch for stop action is protected' do | ||||||
|         before do |         before do | ||||||
|           project.add_developer(user) |           project.add_developer(user) | ||||||
|           create(:protected_branch, :no_one_can_push, |           create(:protected_branch, :no_one_can_push, name: 'master', project: project) | ||||||
|                  name: 'master', project: project) |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         it 'does not stop environment' do |         it 'does not stop environment' do | ||||||
|  |  | ||||||
|  | @ -264,18 +264,6 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'not allowing a path update' |         it_behaves_like 'not allowing a path update' | ||||||
|         it_behaves_like 'allowing an update', on: :name |         it_behaves_like 'allowing an update', on: :name | ||||||
| 
 |  | ||||||
|         context 'when npm_package_registry_fix_group_path_validation is disabled' do |  | ||||||
|           before do |  | ||||||
|             stub_feature_flags(npm_package_registry_fix_group_path_validation: false) |  | ||||||
|             expect_next_instance_of(::Groups::UpdateService) do |service| |  | ||||||
|               expect(service).to receive(:valid_path_change_with_npm_packages?).and_call_original |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           it_behaves_like 'not allowing a path update' |  | ||||||
|           it_behaves_like 'allowing an update', on: :name |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'updating the subgroup' do |       context 'updating the subgroup' do | ||||||
|  | @ -283,18 +271,6 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'allowing an update', on: :path |         it_behaves_like 'allowing an update', on: :path | ||||||
|         it_behaves_like 'allowing an update', on: :name |         it_behaves_like 'allowing an update', on: :name | ||||||
| 
 |  | ||||||
|         context 'when npm_package_registry_fix_group_path_validation is disabled' do |  | ||||||
|           before do |  | ||||||
|             stub_feature_flags(npm_package_registry_fix_group_path_validation: false) |  | ||||||
|             expect_next_instance_of(::Groups::UpdateService) do |service| |  | ||||||
|               expect(service).to receive(:valid_path_change_with_npm_packages?).and_call_original |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           it_behaves_like 'not allowing a path update' |  | ||||||
|           it_behaves_like 'allowing an update', on: :name |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -306,18 +282,6 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'allowing an update', on: :path |         it_behaves_like 'allowing an update', on: :path | ||||||
|         it_behaves_like 'allowing an update', on: :name |         it_behaves_like 'allowing an update', on: :name | ||||||
| 
 |  | ||||||
|         context 'when npm_package_registry_fix_group_path_validation is disabled' do |  | ||||||
|           before do |  | ||||||
|             stub_feature_flags(npm_package_registry_fix_group_path_validation: false) |  | ||||||
|             expect_next_instance_of(::Groups::UpdateService) do |service| |  | ||||||
|               expect(service).to receive(:valid_path_change_with_npm_packages?).and_call_original |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           it_behaves_like 'not allowing a path update' |  | ||||||
|           it_behaves_like 'allowing an update', on: :name |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'updating the subgroup' do |       context 'updating the subgroup' do | ||||||
|  | @ -325,18 +289,6 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'allowing an update', on: :path |         it_behaves_like 'allowing an update', on: :path | ||||||
|         it_behaves_like 'allowing an update', on: :name |         it_behaves_like 'allowing an update', on: :name | ||||||
| 
 |  | ||||||
|         context 'when npm_package_registry_fix_group_path_validation is disabled' do |  | ||||||
|           before do |  | ||||||
|             stub_feature_flags(npm_package_registry_fix_group_path_validation: false) |  | ||||||
|             expect_next_instance_of(::Groups::UpdateService) do |service| |  | ||||||
|               expect(service).to receive(:valid_path_change_with_npm_packages?).and_call_original |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           it_behaves_like 'not allowing a path update' |  | ||||||
|           it_behaves_like 'allowing an update', on: :name |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -348,18 +300,6 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'allowing an update', on: :path |         it_behaves_like 'allowing an update', on: :path | ||||||
|         it_behaves_like 'allowing an update', on: :name |         it_behaves_like 'allowing an update', on: :name | ||||||
| 
 |  | ||||||
|         context 'when npm_package_registry_fix_group_path_validation is disabled' do |  | ||||||
|           before do |  | ||||||
|             stub_feature_flags(npm_package_registry_fix_group_path_validation: false) |  | ||||||
|             expect_next_instance_of(::Groups::UpdateService) do |service| |  | ||||||
|               expect(service).to receive(:valid_path_change_with_npm_packages?).and_call_original |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           it_behaves_like 'not allowing a path update' |  | ||||||
|           it_behaves_like 'allowing an update', on: :name |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'updating the subgroup' do |       context 'updating the subgroup' do | ||||||
|  | @ -367,18 +307,6 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'allowing an update', on: :path |         it_behaves_like 'allowing an update', on: :path | ||||||
|         it_behaves_like 'allowing an update', on: :name |         it_behaves_like 'allowing an update', on: :name | ||||||
| 
 |  | ||||||
|         context 'when npm_package_registry_fix_group_path_validation is disabled' do |  | ||||||
|           before do |  | ||||||
|             stub_feature_flags(npm_package_registry_fix_group_path_validation: false) |  | ||||||
|             expect_next_instance_of(::Groups::UpdateService) do |service| |  | ||||||
|               expect(service).to receive(:valid_path_change_with_npm_packages?).and_call_original |  | ||||||
|             end |  | ||||||
|           end |  | ||||||
| 
 |  | ||||||
|           it_behaves_like 'not allowing a path update' |  | ||||||
|           it_behaves_like 'allowing an update', on: :name |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -88,7 +88,8 @@ RSpec.describe LooseForeignKeys::BatchCleanerService, feature_category: :databas | ||||||
|       expect(loose_fk_child_table_1.count).to eq(4) |       expect(loose_fk_child_table_1.count).to eq(4) | ||||||
|       expect(loose_fk_child_table_2.count).to eq(4) |       expect(loose_fk_child_table_2.count).to eq(4) | ||||||
| 
 | 
 | ||||||
|       described_class.new(parent_table: '_test_loose_fk_parent_table', |       described_class.new( | ||||||
|  |         parent_table: '_test_loose_fk_parent_table', | ||||||
|         loose_foreign_key_definitions: loose_foreign_key_definitions, |         loose_foreign_key_definitions: loose_foreign_key_definitions, | ||||||
|         deleted_parent_records: LooseForeignKeys::DeletedRecord.load_batch_for_table('public._test_loose_fk_parent_table', 100) |         deleted_parent_records: LooseForeignKeys::DeletedRecord.load_batch_for_table('public._test_loose_fk_parent_table', 100) | ||||||
|       ).execute |       ).execute | ||||||
|  | @ -125,7 +126,8 @@ RSpec.describe LooseForeignKeys::BatchCleanerService, feature_category: :databas | ||||||
|       let(:deleted_records_incremented_counter) { Gitlab::Metrics.registry.get(:loose_foreign_key_incremented_deleted_records) } |       let(:deleted_records_incremented_counter) { Gitlab::Metrics.registry.get(:loose_foreign_key_incremented_deleted_records) } | ||||||
| 
 | 
 | ||||||
|       let(:cleaner) do |       let(:cleaner) do | ||||||
|         described_class.new(parent_table: '_test_loose_fk_parent_table', |         described_class.new( | ||||||
|  |           parent_table: '_test_loose_fk_parent_table', | ||||||
|           loose_foreign_key_definitions: loose_foreign_key_definitions, |           loose_foreign_key_definitions: loose_foreign_key_definitions, | ||||||
|           deleted_parent_records: LooseForeignKeys::DeletedRecord.load_batch_for_table('public._test_loose_fk_parent_table', 100), |           deleted_parent_records: LooseForeignKeys::DeletedRecord.load_batch_for_table('public._test_loose_fk_parent_table', 100), | ||||||
|           modification_tracker: modification_tracker |           modification_tracker: modification_tracker | ||||||
|  |  | ||||||
|  | @ -24,8 +24,13 @@ RSpec.describe NoteSummary, feature_category: :code_review_workflow do | ||||||
|   describe '#note' do |   describe '#note' do | ||||||
|     it 'returns note hash' do |     it 'returns note hash' do | ||||||
|       freeze_time do |       freeze_time do | ||||||
|         expect(create_note_summary.note).to eq(noteable: noteable, project: project, author: user, note: 'note', |         expect(create_note_summary.note).to eq( | ||||||
|                                                created_at: Time.current) |           noteable: noteable, | ||||||
|  |           project: project, | ||||||
|  |           author: user, | ||||||
|  |           note: 'note', | ||||||
|  |           created_at: Time.current | ||||||
|  |         ) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1219,9 +1219,11 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do | ||||||
|       let_it_be(:member_and_not_mentioned) { create(:user, developer_projects: [project]) } |       let_it_be(:member_and_not_mentioned) { create(:user, developer_projects: [project]) } | ||||||
|       let_it_be(:non_member_and_mentioned) { create(:user) } |       let_it_be(:non_member_and_mentioned) { create(:user) } | ||||||
|       let_it_be(:note) do |       let_it_be(:note) do | ||||||
|         create(:diff_note_on_design, |         create( | ||||||
|  |           :diff_note_on_design, | ||||||
|           noteable: design, |           noteable: design, | ||||||
|            note: "Hello #{member_and_mentioned.to_reference}, G'day #{non_member_and_mentioned.to_reference}") |           note: "Hello #{member_and_mentioned.to_reference}, G'day #{non_member_and_mentioned.to_reference}" | ||||||
|  |         ) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       let_it_be(:note_2) do |       let_it_be(:note_2) do | ||||||
|  | @ -3506,12 +3508,14 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do | ||||||
|       let(:commit) { project.commit } |       let(:commit) { project.commit } | ||||||
| 
 | 
 | ||||||
|       def create_pipeline(user, status) |       def create_pipeline(user, status) | ||||||
|         create(:ci_pipeline, status, |         create( | ||||||
|  |           :ci_pipeline, status, | ||||||
|           project: project, |           project: project, | ||||||
|           user: user, |           user: user, | ||||||
|           ref: 'refs/heads/master', |           ref: 'refs/heads/master', | ||||||
|           sha: commit.id, |           sha: commit.id, | ||||||
|                before_sha: '00000000') |           before_sha: '00000000' | ||||||
|  |         ) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       before_all do |       before_all do | ||||||
|  | @ -4020,12 +4024,14 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do | ||||||
|       project.add_maintainer(reviewer) |       project.add_maintainer(reviewer) | ||||||
|       merge_request.assignees.each { |assignee| project.add_maintainer(assignee) } |       merge_request.assignees.each { |assignee| project.add_maintainer(assignee) } | ||||||
| 
 | 
 | ||||||
|       create(:diff_note_on_merge_request, |       create( | ||||||
|  |         :diff_note_on_merge_request, | ||||||
|         project: project, |         project: project, | ||||||
|         noteable: merge_request, |         noteable: merge_request, | ||||||
|         author: reviewer, |         author: reviewer, | ||||||
|         review: review, |         review: review, | ||||||
|              note: "cc @mention") |         note: "cc @mention" | ||||||
|  |       ) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'sends emails' do |     it 'sends emails' do | ||||||
|  | @ -4067,10 +4073,18 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do | ||||||
|     subject { notification.inactive_project_deletion_warning(project, deletion_date) } |     subject { notification.inactive_project_deletion_warning(project, deletion_date) } | ||||||
| 
 | 
 | ||||||
|     it "sends email to project owners and maintainers" do |     it "sends email to project owners and maintainers" do | ||||||
|       expect { subject }.to have_enqueued_email(project, maintainer, deletion_date, |       expect { subject }.to have_enqueued_email( | ||||||
|                                                 mail: "inactive_project_deletion_warning_email") |         project, | ||||||
|       expect { subject }.not_to have_enqueued_email(project, developer, deletion_date, |         maintainer, | ||||||
|                                                     mail: "inactive_project_deletion_warning_email") |         deletion_date, | ||||||
|  |         mail: "inactive_project_deletion_warning_email" | ||||||
|  |       ) | ||||||
|  |       expect { subject }.not_to have_enqueued_email( | ||||||
|  |         project, | ||||||
|  |         developer, | ||||||
|  |         deletion_date, | ||||||
|  |         mail: "inactive_project_deletion_warning_email" | ||||||
|  |       ) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,9 +21,9 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService, feature_category: | ||||||
|       project.mark_pages_as_deployed |       project.mark_pages_as_deployed | ||||||
|       expect(project.pages_metadatum.reload.deployed).to eq(true) |       expect(project.pages_metadatum.reload.deployed).to eq(true) | ||||||
| 
 | 
 | ||||||
|       expect(service.execute).to( |       expect(service.execute).to eq( | ||||||
|         eq(status: :success, |         status: :success, | ||||||
|            message: "Archive not created. Missing public directory in #{project.pages_path}? Marked project as not deployed") |         message: "Archive not created. Missing public directory in #{project.pages_path}? Marked project as not deployed" | ||||||
|       ) |       ) | ||||||
| 
 | 
 | ||||||
|       expect(project.pages_metadatum.reload.deployed).to eq(false) |       expect(project.pages_metadatum.reload.deployed).to eq(false) | ||||||
|  | @ -35,9 +35,9 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService, feature_category: | ||||||
|       project.mark_pages_as_deployed |       project.mark_pages_as_deployed | ||||||
|       expect(project.pages_metadatum.reload.deployed).to eq(true) |       expect(project.pages_metadatum.reload.deployed).to eq(true) | ||||||
| 
 | 
 | ||||||
|       expect(service.execute).to( |       expect(service.execute).to eq( | ||||||
|         eq(status: :success, |         status: :success, | ||||||
|            message: "Archive not created. Missing public directory in #{project.pages_path}? Marked project as not deployed") |         message: "Archive not created. Missing public directory in #{project.pages_path}? Marked project as not deployed" | ||||||
|       ) |       ) | ||||||
| 
 | 
 | ||||||
|       expect(project.pages_metadatum.reload.deployed).to eq(true) |       expect(project.pages_metadatum.reload.deployed).to eq(true) | ||||||
|  | @ -49,9 +49,9 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService, feature_category: | ||||||
| 
 | 
 | ||||||
|     expect(project.pages_metadatum.reload.deployed).to eq(true) |     expect(project.pages_metadatum.reload.deployed).to eq(true) | ||||||
| 
 | 
 | ||||||
|     expect(service.execute).to( |     expect(service.execute).to eq( | ||||||
|       eq(status: :error, |       status: :error, | ||||||
|          message: "Archive not created. Missing public directory in #{project.pages_path}") |       message: "Archive not created. Missing public directory in #{project.pages_path}" | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     expect(project.pages_metadatum.reload.deployed).to eq(true) |     expect(project.pages_metadatum.reload.deployed).to eq(true) | ||||||
|  | @ -60,9 +60,9 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService, feature_category: | ||||||
|   it 'removes pages archive when can not save deployment' do |   it 'removes pages archive when can not save deployment' do | ||||||
|     archive = fixture_file_upload("spec/fixtures/pages.zip") |     archive = fixture_file_upload("spec/fixtures/pages.zip") | ||||||
|     expect_next_instance_of(::Pages::ZipDirectoryService) do |zip_service| |     expect_next_instance_of(::Pages::ZipDirectoryService) do |zip_service| | ||||||
|       expect(zip_service).to receive(:execute).and_return(status: :success, |       expect(zip_service).to receive(:execute).and_return( | ||||||
|                                                           archive_path: archive.path, |         status: :success, archive_path: archive.path, entries_count: 3 | ||||||
|                                                           entries_count: 3) |       ) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     expect_next_instance_of(PagesDeployment) do |deployment| |     expect_next_instance_of(PagesDeployment) do |deployment| | ||||||
|  |  | ||||||
|  | @ -132,8 +132,7 @@ RSpec.describe PagesDomains::ObtainLetsEncryptCertificateService, feature_catego | ||||||
|         ef.create_extension("basicConstraints", "CA:TRUE", true), |         ef.create_extension("basicConstraints", "CA:TRUE", true), | ||||||
|         ef.create_extension("subjectKeyIdentifier", "hash") |         ef.create_extension("subjectKeyIdentifier", "hash") | ||||||
|       ] |       ] | ||||||
|       cert.add_extension ef.create_extension("authorityKeyIdentifier", |       cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") | ||||||
|                                              "keyid:always,issuer:always") |  | ||||||
| 
 | 
 | ||||||
|       cert.sign key, OpenSSL::Digest.new('SHA256') |       cert.sign key, OpenSSL::Digest.new('SHA256') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,9 +28,11 @@ RSpec.describe PreviewMarkdownService, feature_category: :team_planning do | ||||||
| 
 | 
 | ||||||
|     let(:text) { "```suggestion\nfoo\n```" } |     let(:text) { "```suggestion\nfoo\n```" } | ||||||
|     let(:params) do |     let(:params) do | ||||||
|       suggestion_params.merge(text: text, |       suggestion_params.merge( | ||||||
|  |         text: text, | ||||||
|         target_type: 'MergeRequest', |         target_type: 'MergeRequest', | ||||||
|                               target_id: merge_request.iid) |         target_id: merge_request.iid | ||||||
|  |       ) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     let(:service) { described_class.new(project, user, params) } |     let(:service) { described_class.new(project, user, params) } | ||||||
|  | @ -52,15 +54,16 @@ RSpec.describe PreviewMarkdownService, feature_category: :team_planning do | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it 'returns suggestions referenced in text' do |       it 'returns suggestions referenced in text' do | ||||||
|         position = Gitlab::Diff::Position.new(new_path: path, |         position = Gitlab::Diff::Position.new(new_path: path, new_line: line, diff_refs: diff_refs) | ||||||
|                                               new_line: line, |  | ||||||
|                                               diff_refs: diff_refs) |  | ||||||
| 
 | 
 | ||||||
|         expect(Gitlab::Diff::SuggestionsParser) |         expect(Gitlab::Diff::SuggestionsParser) | ||||||
|           .to receive(:parse) |           .to receive(:parse) | ||||||
|           .with(text, position: position, |           .with( | ||||||
|  |             text, | ||||||
|  |             position: position, | ||||||
|             project: merge_request.project, |             project: merge_request.project, | ||||||
|                       supports_suggestion: true) |             supports_suggestion: true | ||||||
|  |           ) | ||||||
|           .and_call_original |           .and_call_original | ||||||
| 
 | 
 | ||||||
|         result = service.execute |         result = service.execute | ||||||
|  |  | ||||||
|  | @ -6,7 +6,9 @@ RSpec.describe ProtectedBranches::ApiService, feature_category: :compliance_mana | ||||||
|   shared_examples 'execute with entity' do |   shared_examples 'execute with entity' do | ||||||
|     it 'creates a protected branch with prefilled defaults' do |     it 'creates a protected branch with prefilled defaults' do | ||||||
|       expect(::ProtectedBranches::CreateService).to receive(:new).with( |       expect(::ProtectedBranches::CreateService).to receive(:new).with( | ||||||
|         entity, user, hash_including( |         entity, | ||||||
|  |         user, | ||||||
|  |         hash_including( | ||||||
|           push_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }], |           push_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }], | ||||||
|           merge_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }] |           merge_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }] | ||||||
|         ) |         ) | ||||||
|  | @ -17,7 +19,9 @@ RSpec.describe ProtectedBranches::ApiService, feature_category: :compliance_mana | ||||||
| 
 | 
 | ||||||
|     it 'updates a protected branch without prefilled defaults' do |     it 'updates a protected branch without prefilled defaults' do | ||||||
|       expect(::ProtectedBranches::UpdateService).to receive(:new).with( |       expect(::ProtectedBranches::UpdateService).to receive(:new).with( | ||||||
|         entity, user, hash_including( |         entity, | ||||||
|  |         user, | ||||||
|  |         hash_including( | ||||||
|           push_access_levels_attributes: [], |           push_access_levels_attributes: [], | ||||||
|           merge_access_levels_attributes: [] |           merge_access_levels_attributes: [] | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -190,9 +190,7 @@ RSpec.describe PushEventPayloadService, feature_category: :source_code_managemen | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'returns :removed when removing an existing ref' do |     it 'returns :removed when removing an existing ref' do | ||||||
|       service = described_class.new(event, |       service = described_class.new(event, before: '123', after: Gitlab::Git::BLANK_SHA) | ||||||
|                                     before: '123', |  | ||||||
|                                     after: Gitlab::Git::BLANK_SHA) |  | ||||||
| 
 | 
 | ||||||
|       expect(service.action).to eq(:removed) |       expect(service.action).to eq(:removed) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -29,9 +29,11 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   before do |   before do | ||||||
|     stub_licensed_features(multiple_issue_assignees: false, |     stub_licensed_features( | ||||||
|  |       multiple_issue_assignees: false, | ||||||
|       multiple_merge_request_reviewers: false, |       multiple_merge_request_reviewers: false, | ||||||
|                            multiple_merge_request_assignees: false) |       multiple_merge_request_assignees: false | ||||||
|  |     ) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#execute' do |   describe '#execute' do | ||||||
|  |  | ||||||
|  | @ -42,9 +42,7 @@ RSpec.describe Releases::DestroyService, feature_category: :release_orchestratio | ||||||
|       let!(:release) {} |       let!(:release) {} | ||||||
| 
 | 
 | ||||||
|       it 'returns an error' do |       it 'returns an error' do | ||||||
|         is_expected.to include(status: :error, |         is_expected.to include(status: :error, message: 'Release does not exist', http_status: 404) | ||||||
|                                message: 'Release does not exist', |  | ||||||
|                                http_status: 404) |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -52,9 +50,7 @@ RSpec.describe Releases::DestroyService, feature_category: :release_orchestratio | ||||||
|       let(:user) { repoter } |       let(:user) { repoter } | ||||||
| 
 | 
 | ||||||
|       it 'returns an error' do |       it 'returns an error' do | ||||||
|         is_expected.to include(status: :error, |         is_expected.to include(status: :error, message: 'Access Denied', http_status: 403) | ||||||
|                                message: 'Access Denied', |  | ||||||
|                                http_status: 403) |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -33,8 +33,7 @@ RSpec.describe ResourceAccessTokens::RevokeService, feature_category: :system_ac | ||||||
|         subject |         subject | ||||||
| 
 | 
 | ||||||
|         expect( |         expect( | ||||||
|           Users::GhostUserMigration.where(user: resource_bot, |           Users::GhostUserMigration.where(user: resource_bot, initiator_user: user) | ||||||
|                                           initiator_user: user) |  | ||||||
|         ).to be_exists |         ).to be_exists | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -61,8 +61,7 @@ RSpec.describe ResourceEvents::MergeIntoNotesService, feature_category: :team_pl | ||||||
|       create_event(created_at: 4.days.ago) |       create_event(created_at: 4.days.ago) | ||||||
|       event = create_event(created_at: 1.day.ago) |       event = create_event(created_at: 1.day.ago) | ||||||
| 
 | 
 | ||||||
|       notes = described_class.new(resource, user, |       notes = described_class.new(resource, user, last_fetched_at: 2.days.ago).execute | ||||||
|                                   last_fetched_at: 2.days.ago).execute |  | ||||||
| 
 | 
 | ||||||
|       expect(notes.count).to eq 1 |       expect(notes.count).to eq 1 | ||||||
|       expect(notes.first.discussion_id).to eq event.reload.discussion_id |       expect(notes.first.discussion_id).to eq event.reload.discussion_id | ||||||
|  |  | ||||||
|  | @ -16,7 +16,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   let(:identifier_wasc) { build(:ci_reports_security_identifier, external_id: '13', external_type: 'wasc') } |   let(:identifier_wasc) { build(:ci_reports_security_identifier, external_id: '13', external_type: 'wasc') } | ||||||
| 
 | 
 | ||||||
|   let(:finding_id_1) do |   let(:finding_id_1) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_1_primary, identifier_1_cve], |       identifiers: [identifier_1_primary, identifier_1_cve], | ||||||
|       scanner: scanner_1, |       scanner: scanner_1, | ||||||
|       severity: :low |       severity: :low | ||||||
|  | @ -24,7 +25,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_id_1_extra) do |   let(:finding_id_1_extra) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_1_primary, identifier_1_cve], |       identifiers: [identifier_1_primary, identifier_1_cve], | ||||||
|       scanner: scanner_1, |       scanner: scanner_1, | ||||||
|       severity: :low |       severity: :low | ||||||
|  | @ -32,7 +34,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_id_2_loc_1) do |   let(:finding_id_2_loc_1) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_2_primary, identifier_2_cve], |       identifiers: [identifier_2_primary, identifier_2_cve], | ||||||
|       location: build(:ci_reports_security_locations_sast, start_line: 32, end_line: 34), |       location: build(:ci_reports_security_locations_sast, start_line: 32, end_line: 34), | ||||||
|       scanner: scanner_2, |       scanner: scanner_2, | ||||||
|  | @ -41,7 +44,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_id_2_loc_1_extra) do |   let(:finding_id_2_loc_1_extra) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_2_primary, identifier_2_cve], |       identifiers: [identifier_2_primary, identifier_2_cve], | ||||||
|       location: build(:ci_reports_security_locations_sast, start_line: 32, end_line: 34), |       location: build(:ci_reports_security_locations_sast, start_line: 32, end_line: 34), | ||||||
|       scanner: scanner_2, |       scanner: scanner_2, | ||||||
|  | @ -50,7 +54,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_id_2_loc_2) do |   let(:finding_id_2_loc_2) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_2_primary, identifier_2_cve], |       identifiers: [identifier_2_primary, identifier_2_cve], | ||||||
|       location: build(:ci_reports_security_locations_sast, start_line: 42, end_line: 44), |       location: build(:ci_reports_security_locations_sast, start_line: 42, end_line: 44), | ||||||
|       scanner: scanner_2, |       scanner: scanner_2, | ||||||
|  | @ -59,7 +64,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_cwe_1) do |   let(:finding_cwe_1) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_cwe], |       identifiers: [identifier_cwe], | ||||||
|       scanner: scanner_3, |       scanner: scanner_3, | ||||||
|       severity: :high |       severity: :high | ||||||
|  | @ -67,7 +73,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_cwe_2) do |   let(:finding_cwe_2) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_cwe], |       identifiers: [identifier_cwe], | ||||||
|       scanner: scanner_1, |       scanner: scanner_1, | ||||||
|       severity: :critical |       severity: :critical | ||||||
|  | @ -75,7 +82,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_wasc_1) do |   let(:finding_wasc_1) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_wasc], |       identifiers: [identifier_wasc], | ||||||
|       scanner: scanner_1, |       scanner: scanner_1, | ||||||
|       severity: :medium |       severity: :medium | ||||||
|  | @ -83,7 +91,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   let(:finding_wasc_2) do |   let(:finding_wasc_2) do | ||||||
|     build(:ci_reports_security_finding, |     build( | ||||||
|  |       :ci_reports_security_finding, | ||||||
|       identifiers: [identifier_wasc], |       identifiers: [identifier_wasc], | ||||||
|       scanner: scanner_2, |       scanner: scanner_2, | ||||||
|       severity: :critical |       severity: :critical | ||||||
|  |  | ||||||
|  | @ -4,13 +4,11 @@ require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Environments::StopJobSuccessWorker, feature_category: :continuous_delivery do | RSpec.describe Environments::StopJobSuccessWorker, feature_category: :continuous_delivery do | ||||||
|   describe '#perform' do |   describe '#perform' do | ||||||
|     subject { described_class.new.perform(build.id) } |     let_it_be_with_refind(:environment) { create(:environment, state: :available) } | ||||||
| 
 | 
 | ||||||
|     context 'when build exists' do |     subject { described_class.new.perform(job.id) } | ||||||
|       context 'when the build will stop an environment' do |  | ||||||
|         let!(:build) { create(:ci_build, :stop_review_app, environment: environment.name, project: environment.project, status: :success) } # rubocop:disable Layout/LineLength |  | ||||||
|         let(:environment) { create(:environment, state: :available) } |  | ||||||
| 
 | 
 | ||||||
|  |     shared_examples_for 'stopping an associated environment' do | ||||||
|       it 'stops the environment' do |       it 'stops the environment' do | ||||||
|         expect(environment).to be_available |         expect(environment).to be_available | ||||||
| 
 | 
 | ||||||
|  | @ -19,9 +17,9 @@ RSpec.describe Environments::StopJobSuccessWorker, feature_category: :continuous | ||||||
|         expect(environment.reload).to be_stopped |         expect(environment.reload).to be_stopped | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|         context 'when the build fails' do |       context 'when the job fails' do | ||||||
|         before do |         before do | ||||||
|             build.update!(status: :failed) |           job.update!(status: :failed) | ||||||
|           environment.update!(state: :available) |           environment.update!(state: :available) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -34,9 +32,26 @@ RSpec.describe Environments::StopJobSuccessWorker, feature_category: :continuous | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     context 'with build job' do | ||||||
|  |       let!(:job) do | ||||||
|  |         create(:ci_build, :stop_review_app, environment: environment.name, project: environment.project, | ||||||
|  |           status: :success) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|     context 'when build does not exist' do |       it_behaves_like 'stopping an associated environment' | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'with bridge job' do | ||||||
|  |       let!(:job) do | ||||||
|  |         create(:ci_bridge, :stop_review_app, environment: environment.name, project: environment.project, | ||||||
|  |           status: :success) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it_behaves_like 'stopping an associated environment' | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when job does not exist' do | ||||||
|       it 'does not raise exception' do |       it 'does not raise exception' do | ||||||
|         expect { described_class.new.perform(123) } |         expect { described_class.new.perform(123) } | ||||||
|           .not_to raise_error |           .not_to raise_error | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue