Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									b90cf01a88
								
							
						
					
					
						commit
						5db6a7a014
					
				|  | @ -389,10 +389,7 @@ export default { | |||
|       () => { | ||||
|         this.setDiscussions(); | ||||
| 
 | ||||
|         if ( | ||||
|           this.$store.state.notes.doneFetchingBatchDiscussions && | ||||
|           window.gon?.features?.paginatedMrDiscussions | ||||
|         ) { | ||||
|         if (this.$store.state.notes.doneFetchingBatchDiscussions) { | ||||
|           this.unwatchDiscussions(); | ||||
|         } | ||||
|       }, | ||||
|  | @ -402,10 +399,6 @@ export default { | |||
|       () => `${this.retrievingBatches}:${this.$store.state.notes.discussions.length}`, | ||||
|       () => { | ||||
|         if (!this.retrievingBatches && this.$store.state.notes.discussions.length) { | ||||
|           if (!window.gon?.features?.paginatedMrDiscussions) { | ||||
|             this.unwatchDiscussions(); | ||||
|           } | ||||
| 
 | ||||
|           this.unwatchRetrievingBatches(); | ||||
|         } | ||||
|       }, | ||||
|  |  | |||
|  | @ -56,13 +56,9 @@ export default { | |||
|   }, | ||||
|   watch: { | ||||
|     discussionTabCounter(val) { | ||||
|       if (this.glFeatures.paginatedMrDiscussions) { | ||||
|       if (this.doneFetchingBatchDiscussions) { | ||||
|         this.discussionCounter = val; | ||||
|       } | ||||
|       } else { | ||||
|         this.discussionCounter = val; | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|  |  | |||
|  | @ -19,6 +19,25 @@ export default { | |||
|     artifactsLabel: __('Artifacts'), | ||||
|     parametersLabel: __('Parameters'), | ||||
|     metricsLabel: __('Metrics'), | ||||
|     metadataLabel: __('Metadata'), | ||||
|   }, | ||||
|   computed: { | ||||
|     sections() { | ||||
|       return [ | ||||
|         { | ||||
|           sectionName: this.$options.i18n.parametersLabel, | ||||
|           sectionValues: this.candidate.params, | ||||
|         }, | ||||
|         { | ||||
|           sectionName: this.$options.i18n.metricsLabel, | ||||
|           sectionValues: this.candidate.metrics, | ||||
|         }, | ||||
|         { | ||||
|           sectionName: this.$options.i18n.metadataLabel, | ||||
|           sectionValues: this.candidate.metadata, | ||||
|         }, | ||||
|       ]; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|  | @ -67,27 +86,18 @@ export default { | |||
|           </td> | ||||
|         </tr> | ||||
| 
 | ||||
|         <tr class="divider"></tr> | ||||
|         <template v-for="{ sectionName, sectionValues } in sections"> | ||||
|           <tr :key="sectionName" class="divider"></tr> | ||||
| 
 | ||||
|         <tr v-for="(param, index) in candidate.params" :key="param.name"> | ||||
|           <td v-if="index == 0" class="gl-text-secondary gl-font-weight-bold"> | ||||
|             {{ $options.i18n.parametersLabel }} | ||||
|           <tr v-for="(item, index) in sectionValues" :key="item.name"> | ||||
|             <td v-if="index === 0" class="gl-text-secondary gl-font-weight-bold"> | ||||
|               {{ sectionName }} | ||||
|             </td> | ||||
|             <td v-else></td> | ||||
|           <td class="gl-font-weight-bold">{{ param.name }}</td> | ||||
|           <td>{{ param.value }}</td> | ||||
|         </tr> | ||||
| 
 | ||||
|         <tr class="divider"></tr> | ||||
| 
 | ||||
|         <tr v-for="(metric, index) in candidate.metrics" :key="metric.name"> | ||||
|           <td v-if="index == 0" class="gl-text-secondary gl-font-weight-bold"> | ||||
|             {{ $options.i18n.metricsLabel }} | ||||
|           </td> | ||||
|           <td v-else></td> | ||||
|           <td class="gl-font-weight-bold">{{ metric.name }}</td> | ||||
|           <td>{{ metric.value }}</td> | ||||
|             <td class="gl-font-weight-bold">{{ item.name }}</td> | ||||
|             <td>{{ item.value }}</td> | ||||
|           </tr> | ||||
|         </template> | ||||
|       </tbody> | ||||
|     </table> | ||||
|   </div> | ||||
|  |  | |||
|  | @ -101,7 +101,7 @@ export const fetchDiscussions = ( | |||
| 
 | ||||
|   if ( | ||||
|     getters.noteableType === constants.ISSUE_NOTEABLE_TYPE || | ||||
|     window.gon?.features?.paginatedMrDiscussions | ||||
|     getters.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE | ||||
|   ) { | ||||
|     return dispatch('fetchDiscussionsBatch', { path, config, perPage: 20 }); | ||||
|   } | ||||
|  |  | |||
|  | @ -20,4 +20,4 @@ export const PROJECT_DATA = { | |||
|   fullName: 'name_with_namespace', | ||||
| }; | ||||
| 
 | ||||
| export const SYNTAX_OPTIONS_DOCUMENT = 'drawers/user/search/advanced_search.md'; | ||||
| export const SYNTAX_OPTIONS_DOCUMENT = 'drawers/drawers/advanced_search_syntax.md'; | ||||
|  |  | |||
|  | @ -150,7 +150,7 @@ class Import::GithubController < Import::BaseController | |||
|   end | ||||
| 
 | ||||
|   def client_repos_response | ||||
|     @client_repos_response ||= client_proxy.repos(sanitized_filter_param, pagination_options) | ||||
|     @client_repos_response ||= client_proxy.repos(sanitized_filter_param, fetch_repos_options) | ||||
|   end | ||||
| 
 | ||||
|   def client_repos | ||||
|  | @ -160,7 +160,11 @@ class Import::GithubController < Import::BaseController | |||
|   def sanitized_filter_param | ||||
|     super | ||||
| 
 | ||||
|     @filter = @filter&.tr(' ', '')&.tr(':', '') | ||||
|     @filter = sanitize_query_param(@filter) | ||||
|   end | ||||
| 
 | ||||
|   def sanitize_query_param(value) | ||||
|     value.to_s.first(255).gsub(/[ :]/, '') | ||||
|   end | ||||
| 
 | ||||
|   def verify_import_enabled | ||||
|  | @ -222,6 +226,10 @@ class Import::GithubController < Import::BaseController | |||
|     head :too_many_requests | ||||
|   end | ||||
| 
 | ||||
|   def fetch_repos_options | ||||
|     pagination_options.merge(relation_options) | ||||
|   end | ||||
| 
 | ||||
|   def pagination_options | ||||
|     { | ||||
|       before: params[:before].presence, | ||||
|  | @ -233,6 +241,13 @@ class Import::GithubController < Import::BaseController | |||
|       per_page: PAGE_LENGTH | ||||
|     } | ||||
|   end | ||||
| 
 | ||||
|   def relation_options | ||||
|     { | ||||
|       relation_type: params[:relation_type], | ||||
|       organization_login: sanitize_query_param(params[:organization_login]) | ||||
|     } | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| Import::GithubController.prepend_mod_with('Import::GithubController') | ||||
|  |  | |||
|  | @ -40,7 +40,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo | |||
|     push_frontend_feature_flag(:refactor_security_extension, @project) | ||||
|     push_frontend_feature_flag(:refactor_code_quality_inline_findings, project) | ||||
|     push_frontend_feature_flag(:moved_mr_sidebar, project) | ||||
|     push_frontend_feature_flag(:paginated_mr_discussions, project) | ||||
|     push_frontend_feature_flag(:mr_review_submit_comment, project) | ||||
|     push_frontend_feature_flag(:mr_experience_survey, project) | ||||
|     push_frontend_feature_flag(:realtime_reviewers, project) | ||||
|  |  | |||
|  | @ -58,8 +58,9 @@ module EnvironmentHelper | |||
|         s_('Deployment|blocked') | ||||
|       end | ||||
| 
 | ||||
|     klass = "ci-status ci-#{status.dasherize}" | ||||
|     text = "#{ci_icon_for_status(status)} #{status_text}".html_safe | ||||
|     ci_icon_utilities = "gl-display-inline-flex gl-align-items-center gl-line-height-0 gl-px-3 gl-py-2 gl-rounded-base" | ||||
|     klass = "ci-status ci-#{status.dasherize} #{ci_icon_utilities}" | ||||
|     text = "#{ci_icon_for_status(status)} <span class=\"gl-ml-2\">#{status_text}</span>".html_safe | ||||
| 
 | ||||
|     if deployment.deployable | ||||
|       link_to(text, deployment_path(deployment), class: klass) | ||||
|  |  | |||
|  | @ -32,7 +32,8 @@ module Projects | |||
|             experiment_name: candidate.experiment.name, | ||||
|             path_to_experiment: link_to_experiment(candidate), | ||||
|             status: candidate.status | ||||
|           } | ||||
|           }, | ||||
|           metadata: candidate.metadata | ||||
|         } | ||||
| 
 | ||||
|         Gitlab::Json.generate(data) | ||||
|  |  | |||
|  | @ -36,6 +36,8 @@ module BulkImports | |||
|     end | ||||
| 
 | ||||
|     def execute | ||||
|       validate! | ||||
| 
 | ||||
|       bulk_import = create_bulk_import | ||||
| 
 | ||||
|       Gitlab::Tracking.event(self.class.name, 'create', label: 'bulk_import_group') | ||||
|  | @ -43,7 +45,8 @@ module BulkImports | |||
|       BulkImportWorker.perform_async(bulk_import.id) | ||||
| 
 | ||||
|       ServiceResponse.success(payload: bulk_import) | ||||
|     rescue ActiveRecord::RecordInvalid, BulkImports::NetworkError => e | ||||
| 
 | ||||
|     rescue ActiveRecord::RecordInvalid, BulkImports::Error, BulkImports::NetworkError => e | ||||
|       ServiceResponse.error( | ||||
|         message: e.message, | ||||
|         http_status: :unprocessable_entity | ||||
|  | @ -52,6 +55,11 @@ module BulkImports | |||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     def validate! | ||||
|       client.validate_instance_version! | ||||
|       client.validate_import_scopes! | ||||
|     end | ||||
| 
 | ||||
|     def create_bulk_import | ||||
|       BulkImport.transaction do | ||||
|         bulk_import = BulkImport.create!( | ||||
|  |  | |||
|  | @ -49,7 +49,6 @@ module Issuable | |||
| 
 | ||||
|     def paginator | ||||
|       return if params[:per_page].blank? | ||||
|       return if issuable.instance_of?(MergeRequest) && Feature.disabled?(:paginated_mr_discussions, issuable.project) | ||||
| 
 | ||||
|       strong_memoize(:paginator) do | ||||
|         issuable | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ | |||
|               = render "projects/merge_requests/awards_block" | ||||
|               = render "projects/merge_requests/widget" | ||||
|               - if mr_action === "show" | ||||
|                 - add_page_startup_api_call Feature.enabled?(:paginated_mr_discussions, @project) ? discussions_path(@merge_request, per_page: 20) : discussions_path(@merge_request) | ||||
|                 - add_page_startup_api_call discussions_path(@merge_request, per_page: 20) | ||||
|                 - add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, format: :json) | ||||
|                 - add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json) | ||||
|               #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request).to_json, | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ An example configuration file for Redis is in this directory under the name | |||
| `resque.yml.example`. | ||||
| 
 | ||||
| | Name               | Fallback instance | Purpose                                                                                                     | | ||||
| | --- | --- | --- | | ||||
| |--------------------|-------------------|-------------------------------------------------------------------------------------------------------------| | ||||
| | `cache`            |                   | Volatile non-persistent data                                                                                | | ||||
| | `queues`           |                   | Background job processing queues                                                                            | | ||||
| | `shared_state`     |                   | Persistent application state                                                                                | | ||||
|  |  | |||
|  | @ -59,6 +59,7 @@ module Gitlab | |||
|     require_dependency Rails.root.join('lib/gitlab/redis/trace_chunks') | ||||
|     require_dependency Rails.root.join('lib/gitlab/redis/rate_limiting') | ||||
|     require_dependency Rails.root.join('lib/gitlab/redis/sessions') | ||||
|     require_dependency Rails.root.join('lib/gitlab/redis/repository_cache') | ||||
|     require_dependency Rails.root.join('lib/gitlab/current_settings') | ||||
|     require_dependency Rails.root.join('lib/gitlab/middleware/read_only') | ||||
|     require_dependency Rails.root.join('lib/gitlab/middleware/compressed_json') | ||||
|  |  | |||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: paginated_mr_discussions | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88905 | ||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364497 | ||||
| milestone: '15.1' | ||||
| type: development | ||||
| group: group::code review | ||||
| default_enabled: true | ||||
|  | @ -3488,7 +3488,7 @@ any subkeys. All additional details and related topics are the same. | |||
| 
 | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - An array of file paths. In GitLab 13.6 and later, [file paths can include variables](../jobs/job_control.md#variables-in-ruleschanges). | ||||
| - An array of file paths. [File paths can include variables](../jobs/job_control.md#variables-in-ruleschanges). | ||||
| 
 | ||||
| **Example of `rules:changes:paths`**: | ||||
| 
 | ||||
|  |  | |||
|  | @ -456,7 +456,13 @@ Log in to your **primary** node, executing the following: | |||
| 1. To get the database migrations and latest code in place, run: | ||||
| 
 | ||||
|    ```shell | ||||
|    sudo SKIP_POST_DEPLOYMENT_MIGRATIONS=true gitlab-ctl reconfigure | ||||
|    sudo gitlab-ctl reconfigure | ||||
|    ``` | ||||
| 
 | ||||
| 1. After the node is updated and reconfigure finished successfully, complete the migrations: | ||||
| 
 | ||||
|    ```shell | ||||
|    sudo SKIP_POST_DEPLOYMENT_MIGRATIONS=true gitlab-rake db:migrate | ||||
|    ``` | ||||
| 
 | ||||
| ### Update the Geo secondary site | ||||
|  | @ -486,7 +492,13 @@ On each **secondary** node, executing the following: | |||
| 1. To get the database migrations and latest code in place, run: | ||||
| 
 | ||||
|    ```shell | ||||
|    sudo SKIP_POST_DEPLOYMENT_MIGRATIONS=true gitlab-ctl reconfigure | ||||
|    sudo gitlab-ctl reconfigure | ||||
|    ``` | ||||
| 
 | ||||
| 1. After the node is updated and reconfigure finished successfully, complete the migrations: | ||||
| 
 | ||||
|    ```shell | ||||
|    sudo SKIP_POST_DEPLOYMENT_MIGRATIONS=true gitlab-rake db:migrate | ||||
|    ``` | ||||
| 
 | ||||
| 1. Run post-deployment database migrations, specific to the Geo database: | ||||
|  |  | |||
|  | @ -159,13 +159,6 @@ the default option of one corpus per job. | |||
| The corpus registry uses the package registry to store the project's corpuses. Corpuses stored in | ||||
| the registry are hidden to ensure data integrity. | ||||
| 
 | ||||
| In the GitLab UI, with corpus management you can: | ||||
| 
 | ||||
| - View details of the corpus registry. | ||||
| - Download a corpus. | ||||
| - Delete a corpus. | ||||
| - Create a new corpus. | ||||
| 
 | ||||
| When you download a corpus, the file is named `artifacts.zip`, regardless of the filename used when | ||||
| the corpus was initially uploaded. This file contains only the corpus, which is different to the | ||||
| artifacts files you can download from the CI/CD pipeline. Also, a project member with a Reporter or above privilege can download the corpus using the direct download link. | ||||
|  |  | |||
|  | @ -363,12 +363,7 @@ Threads on lines that don't change and top-level resolvable threads are not reso | |||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340172) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `paginated_mr_discussions`. Disabled by default. | ||||
| > - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/364497) in GitLab 15.2. | ||||
| > - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/364497) in GitLab 15.3. | ||||
| 
 | ||||
| FLAG: | ||||
| On self-managed GitLab, by default this feature is available. To hide the feature | ||||
| per project or for your entire instance, ask an administrator to | ||||
| [disable the feature flag](../../administration/feature_flags.md) named `paginated_mr_discussions`. | ||||
| On GitLab.com, this feature is available. | ||||
| > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/370075) in GitLab 15.8. Feature flag `paginated_mr_discussions` removed. | ||||
| 
 | ||||
| A merge request can have many discussions. Loading them all in a single request | ||||
| can be slow. To improve the performance of loading discussions, they are split into multiple | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ module BulkImports | |||
|       API_VERSION = 'v4' | ||||
|       DEFAULT_PAGE = 1 | ||||
|       DEFAULT_PER_PAGE = 30 | ||||
|       PAT_ENDPOINT_MIN_VERSION = '15.5.0' | ||||
| 
 | ||||
|       def initialize(url:, token:, page: DEFAULT_PAGE, per_page: DEFAULT_PER_PAGE, api_version: API_VERSION) | ||||
|         @url = url | ||||
|  | @ -66,38 +67,57 @@ module BulkImports | |||
|         instance_version >= BulkImport.min_gl_version_for_project_migration | ||||
|       end | ||||
| 
 | ||||
|       private | ||||
|       def options | ||||
|         { headers: { 'Content-Type' => 'application/json' }, query: { private_token: @token } } | ||||
|       end | ||||
| 
 | ||||
|       def validate_import_scopes! | ||||
|         return true unless instance_version >= ::Gitlab::VersionInfo.parse(PAT_ENDPOINT_MIN_VERSION) | ||||
| 
 | ||||
|         response = with_error_handling do | ||||
|           Gitlab::HTTP.get(resource_url("personal_access_tokens/self"), options) | ||||
|         end | ||||
| 
 | ||||
|         return true if response['scopes']&.include?('api') | ||||
| 
 | ||||
|         raise ::BulkImports::Error.scope_validation_failure | ||||
|       end | ||||
| 
 | ||||
|       def validate_instance_version! | ||||
|         return if @compatible_instance_version | ||||
|         return true unless instance_version.major < BulkImport::MIN_MAJOR_VERSION | ||||
| 
 | ||||
|         if instance_version.major < BulkImport::MIN_MAJOR_VERSION | ||||
|         raise ::BulkImports::Error.unsupported_gitlab_version | ||||
|         else | ||||
|           @compatible_instance_version = true | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       def metadata | ||||
|         response = begin | ||||
|           with_error_handling do | ||||
|             Gitlab::HTTP.get(resource_url(:version), default_options) | ||||
|             Gitlab::HTTP.get(resource_url(:version), options) | ||||
|           end | ||||
|         rescue BulkImports::NetworkError | ||||
|           # `version` endpoint is not available, try `metadata` endpoint instead | ||||
|           with_error_handling do | ||||
|             Gitlab::HTTP.get(resource_url(:metadata), default_options) | ||||
|             Gitlab::HTTP.get(resource_url(:metadata), options) | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         response.parsed_response | ||||
|       rescue BulkImports::NetworkError => e | ||||
|         case e&.response&.code | ||||
|         when 401, 403 | ||||
|           raise ::BulkImports::Error.scope_validation_failure | ||||
|         when 404 | ||||
|           raise ::BulkImports::Error.invalid_url | ||||
|         else | ||||
|           raise | ||||
|         end | ||||
|       end | ||||
|       strong_memoize_attr :metadata | ||||
| 
 | ||||
|       # rubocop:disable GitlabSecurity/PublicSend | ||||
|       def request(method, resource, options = {}, &block) | ||||
|         validate_instance_version! | ||||
| 
 | ||||
|         with_error_handling do | ||||
|           Gitlab::HTTP.public_send( | ||||
|             method, | ||||
|  | @ -134,9 +154,10 @@ module BulkImports | |||
|       def with_error_handling | ||||
|         response = yield | ||||
| 
 | ||||
|         raise ::BulkImports::NetworkError.new("Unsuccessful response #{response.code} from #{response.request.path.path}. Body: #{response.parsed_response}", response: response) unless response.success? | ||||
|         return response if response.success? | ||||
| 
 | ||||
|         raise ::BulkImports::NetworkError.new("Unsuccessful response #{response.code} from #{response.request.path.path}. Body: #{response.parsed_response}", response: response) | ||||
| 
 | ||||
|         response | ||||
|       rescue *Gitlab::HTTP::HTTP_ERRORS => e | ||||
|         raise ::BulkImports::NetworkError, e | ||||
|       end | ||||
|  |  | |||
|  | @ -5,5 +5,13 @@ module BulkImports | |||
|     def self.unsupported_gitlab_version | ||||
|       self.new("Unsupported GitLab Version. Minimum Supported Gitlab Version #{BulkImport::MIN_MAJOR_VERSION}.") | ||||
|     end | ||||
| 
 | ||||
|     def self.scope_validation_failure | ||||
|       self.new("Import aborted as the provided personal access token does not have the required 'api' scope.") | ||||
|     end | ||||
| 
 | ||||
|     def self.invalid_url | ||||
|       self.new("Import aborted as it was not possible to connect to the provided GitLab instance URL.") | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -30,6 +30,8 @@ module Gitlab | |||
|               secret_detection: VERSIONS_TO_REMOVE_IN_16_0 | ||||
|             }.freeze | ||||
| 
 | ||||
|             CURRENT_VERSIONS = SUPPORTED_VERSIONS.to_h { |k, v| [k, v - DEPRECATED_VERSIONS[k]] } | ||||
| 
 | ||||
|             class Schema | ||||
|               def root_path | ||||
|                 File.join(__dir__, 'schemas') | ||||
|  | @ -187,11 +189,15 @@ module Gitlab | |||
|             def add_deprecated_report_version_message | ||||
|               log_warnings(problem_type: 'using_deprecated_schema_version') | ||||
| 
 | ||||
|               template = _("Version %{report_version} for report type %{report_type} has been deprecated,"\ | ||||
|               " supported versions for this report type are: %{supported_schema_versions}."\ | ||||
|               " GitLab will attempt to parse and ingest this report if valid.") | ||||
|               template = _("version %{report_version} for report type %{report_type} is deprecated. "\ | ||||
|               "However, GitLab will still attempt to parse and ingest this report. "\ | ||||
|               "Upgrade the security report to one of the following versions: %{current_schema_versions}.") | ||||
| 
 | ||||
|               message = format(template, report_version: report_version, report_type: report_type, supported_schema_versions: supported_schema_versions) | ||||
|               message = format( | ||||
|                 template, | ||||
|                 report_version: report_version, | ||||
|                 report_type: report_type, | ||||
|                 current_schema_versions: current_schema_versions) | ||||
| 
 | ||||
|               add_message_as(level: :deprecation_warning, message: message) | ||||
|             end | ||||
|  | @ -212,6 +218,10 @@ module Gitlab | |||
|               ) | ||||
|             end | ||||
| 
 | ||||
|             def current_schema_versions | ||||
|               CURRENT_VERSIONS[report_type].join(", ") | ||||
|             end | ||||
| 
 | ||||
|             def supported_schema_versions | ||||
|               SUPPORTED_VERSIONS[report_type].join(", ") | ||||
|             end | ||||
|  |  | |||
|  | @ -264,18 +264,6 @@ module Gitlab | |||
| 
 | ||||
|       private | ||||
| 
 | ||||
|       def collaborations_subquery | ||||
|         each_object(:repos, nil, { affiliation: 'collaborator' }) | ||||
|           .map { |repo| "repo:#{repo[:full_name]}" } | ||||
|           .join(' ') | ||||
|       end | ||||
| 
 | ||||
|       def organizations_subquery | ||||
|         each_object(:organizations) | ||||
|           .map { |org| "org:#{org[:login]}" } | ||||
|           .join(' ') | ||||
|       end | ||||
| 
 | ||||
|       def with_retry | ||||
|         Retriable.retriable(on: CLIENT_CONNECTION_ERROR, on_retry: on_retry) do | ||||
|           yield | ||||
|  |  | |||
|  | @ -10,24 +10,24 @@ module Gitlab | |||
|           @client = pick_client(access_token, client_options) | ||||
|         end | ||||
| 
 | ||||
|         def repos(search_text, pagination_options) | ||||
|         def repos(search_text, options) | ||||
|           return { repos: filtered(client.repos, search_text) } if use_legacy? | ||||
| 
 | ||||
|           if use_graphql? | ||||
|             fetch_repos_via_graphql(search_text, pagination_options) | ||||
|             fetch_repos_via_graphql(search_text, options) | ||||
|           else | ||||
|             fetch_repos_via_rest(search_text, pagination_options) | ||||
|             fetch_repos_via_rest(search_text, options) | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         private | ||||
| 
 | ||||
|         def fetch_repos_via_rest(search_text, pagination_options) | ||||
|           { repos: client.search_repos_by_name(search_text, pagination_options)[:items] } | ||||
|         def fetch_repos_via_rest(search_text, options) | ||||
|           { repos: client.search_repos_by_name(search_text, options)[:items] } | ||||
|         end | ||||
| 
 | ||||
|         def fetch_repos_via_graphql(search_text, pagination_options) | ||||
|           response = client.search_repos_by_name_graphql(search_text, pagination_options) | ||||
|         def fetch_repos_via_graphql(search_text, options) | ||||
|           response = client.search_repos_by_name_graphql(search_text, options) | ||||
|           { | ||||
|             repos: response.dig(:data, :search, :nodes), | ||||
|             page_info: response.dig(:data, :search, :pageInfo) | ||||
|  |  | |||
|  | @ -14,18 +14,17 @@ module Gitlab | |||
|         end | ||||
| 
 | ||||
|         def search_repos_by_name(name, options = {}) | ||||
|           search_query = search_repos_query(name, options) | ||||
| 
 | ||||
|           with_retry do | ||||
|             octokit.search_repositories( | ||||
|               search_repos_query(str: name, type: :name), | ||||
|               options | ||||
|             ).to_h | ||||
|             octokit.search_repositories(search_query, options).to_h | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         private | ||||
| 
 | ||||
|         def graphql_search_repos_body(name, options) | ||||
|           query = search_repos_query(str: name, type: :name) | ||||
|           query = search_repos_query(name, options) | ||||
|           query = "query: \"#{query}\"" | ||||
|           first = options[:first].present? ? ", first: #{options[:first]}" : '' | ||||
|           after = options[:after].present? ? ", after: \"#{options[:after]}\"" : '' | ||||
|  | @ -52,13 +51,49 @@ module Gitlab | |||
|           TEXT | ||||
|         end | ||||
| 
 | ||||
|         def search_repos_query(str:, type:, include_collaborations: true, include_orgs: true) | ||||
|           query = "#{str} in:#{type} is:public,private user:#{octokit.user.to_h[:login]}" | ||||
|         def search_repos_query(string, options = {}) | ||||
|           base = "#{string} in:name is:public,private" | ||||
| 
 | ||||
|           query = [query, collaborations_subquery].join(' ') if include_collaborations | ||||
|           query = [query, organizations_subquery].join(' ') if include_orgs | ||||
|           case options[:relation_type] | ||||
|           when 'organization' then organization_repos_query(base, options) | ||||
|           when 'collaborated' then collaborated_repos_query(base) | ||||
|           when 'owned' then owned_repos_query(base) | ||||
|           # TODO: remove after https://gitlab.com/gitlab-org/gitlab/-/issues/385113 get done | ||||
|           else legacy_all_repos_query(base) | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|           query | ||||
|         def organization_repos_query(search_string, options) | ||||
|           "#{search_string} org:#{options[:organization_login]}" | ||||
|         end | ||||
| 
 | ||||
|         def collaborated_repos_query(search_string) | ||||
|           "#{search_string} #{collaborations_subquery}" | ||||
|         end | ||||
| 
 | ||||
|         def owned_repos_query(search_string) | ||||
|           "#{search_string} user:#{octokit.user.to_h[:login]}" | ||||
|         end | ||||
| 
 | ||||
|         def legacy_all_repos_query(search_string) | ||||
|           [ | ||||
|             search_string, | ||||
|             "user:#{octokit.user.to_h[:login]}", | ||||
|             collaborations_subquery, | ||||
|             organizations_subquery | ||||
|           ].join(' ') | ||||
|         end | ||||
| 
 | ||||
|         def collaborations_subquery | ||||
|           each_object(:repos, nil, { affiliation: 'collaborator' }) | ||||
|             .map { |repo| "repo:#{repo[:full_name]}" } | ||||
|             .join(' ') | ||||
|         end | ||||
| 
 | ||||
|         def organizations_subquery | ||||
|           each_object(:organizations) | ||||
|             .map { |org| "org:#{org[:login]}" } | ||||
|             .join(' ') | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ module Gitlab | |||
|       Gitlab::Redis::Cache, | ||||
|       Gitlab::Redis::Queues, | ||||
|       Gitlab::Redis::RateLimiting, | ||||
|       Gitlab::Redis::RepositoryCache, | ||||
|       Gitlab::Redis::Sessions, | ||||
|       Gitlab::Redis::SharedState, | ||||
|       Gitlab::Redis::TraceChunks | ||||
|  |  | |||
|  | @ -0,0 +1,12 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module Gitlab | ||||
|   module Redis | ||||
|     class RepositoryCache < ::Gitlab::Redis::Wrapper | ||||
|       # The data we store on RepositoryCache used to be stored on Cache. | ||||
|       def self.config_fallback | ||||
|         Cache | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -26325,6 +26325,9 @@ msgstr "" | |||
| msgid "Messages" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Metadata" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Method" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -45892,9 +45895,6 @@ msgstr "" | |||
| msgid "Version" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Version %{report_version} for report type %{report_type} has been deprecated, supported versions for this report type are: %{supported_schema_versions}. GitLab will attempt to parse and ingest this report if valid." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Version %{report_version} for report type %{report_type} is unsupported, supported versions for this report type are: %{supported_schema_versions}. GitLab will attempt to validate this report against the earliest supported versions of this report type, to show all the errors but will not ingest the report" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -50785,6 +50785,9 @@ msgstr "" | |||
| msgid "verify ownership" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "version %{report_version} for report type %{report_type} is deprecated. However, GitLab will still attempt to parse and ingest this report. Upgrade the security report to one of the following versions: %{current_schema_versions}." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "version %{versionIndex}" | ||||
| msgstr "" | ||||
| 
 | ||||
|  |  | |||
|  | @ -102,6 +102,18 @@ RSpec.describe Import::BulkImportsController, feature_category: :importers do | |||
|             ) | ||||
|           end | ||||
| 
 | ||||
|           let(:source_version) do | ||||
|             Gitlab::VersionInfo.new(::BulkImport::MIN_MAJOR_VERSION, | ||||
|                                     ::BulkImport::MIN_MINOR_VERSION_FOR_PROJECT) | ||||
|           end | ||||
| 
 | ||||
|           before do | ||||
|             allow_next_instance_of(BulkImports::Clients::HTTP) do |instance| | ||||
|               allow(instance).to receive(:instance_version).and_return(source_version) | ||||
|               allow(instance).to receive(:instance_enterprise).and_return(false) | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           it 'returns serialized group data' do | ||||
|             get_status | ||||
| 
 | ||||
|  | @ -203,8 +215,15 @@ RSpec.describe Import::BulkImportsController, feature_category: :importers do | |||
|         end | ||||
| 
 | ||||
|         context 'when connection error occurs' do | ||||
|           let(:source_version) do | ||||
|             Gitlab::VersionInfo.new(::BulkImport::MIN_MAJOR_VERSION, | ||||
|                                     ::BulkImport::MIN_MINOR_VERSION_FOR_PROJECT) | ||||
|           end | ||||
| 
 | ||||
|           before do | ||||
|             allow_next_instance_of(BulkImports::Clients::HTTP) do |instance| | ||||
|               allow(instance).to receive(:instance_version).and_return(source_version) | ||||
|               allow(instance).to receive(:instance_enterprise).and_return(false) | ||||
|               allow(instance).to receive(:get).and_raise(BulkImports::Error) | ||||
|             end | ||||
|           end | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe Import::GithubController do | ||||
| RSpec.describe Import::GithubController, feature_category: :import do | ||||
|   include ImportSpecHelper | ||||
| 
 | ||||
|   let(:provider) { :github } | ||||
|  | @ -138,7 +138,7 @@ RSpec.describe Import::GithubController do | |||
|       it 'calls repos list from provider with expected args' do | ||||
|         expect_next_instance_of(Gitlab::GithubImport::Clients::Proxy) do |client| | ||||
|           expect(client).to receive(:repos) | ||||
|             .with(expected_filter, expected_pagination_options) | ||||
|             .with(expected_filter, expected_options) | ||||
|             .and_return({ repos: [], page_info: {} }) | ||||
|         end | ||||
| 
 | ||||
|  | @ -155,11 +155,16 @@ RSpec.describe Import::GithubController do | |||
|     let(:provider_token) { 'asdasd12345' } | ||||
|     let(:client_auth_success) { true } | ||||
|     let(:client_stub) { instance_double(Gitlab::GithubImport::Client, user: { login: 'user' }) } | ||||
|     let(:expected_pagination_options) { pagination_params.merge(first: 25, page: 1, per_page: 25) } | ||||
|     let(:expected_filter) { nil } | ||||
|     let(:params) { nil } | ||||
|     let(:pagination_params) { { before: nil, after: nil } } | ||||
|     let(:relation_params) { { relation_type: nil, organization_login: '' } } | ||||
|     let(:provider_repos) { [] } | ||||
|     let(:expected_filter) { '' } | ||||
|     let(:expected_options) do | ||||
|       pagination_params.merge(relation_params).merge( | ||||
|         first: 25, page: 1, per_page: 25 | ||||
|       ) | ||||
|     end | ||||
| 
 | ||||
|     before do | ||||
|       allow_next_instance_of(Gitlab::GithubImport::Clients::Proxy) do |proxy| | ||||
|  | @ -277,8 +282,34 @@ RSpec.describe Import::GithubController do | |||
| 
 | ||||
|       context 'when page is specified' do | ||||
|         let(:pagination_params) { { before: nil, after: nil, page: 2 } } | ||||
|         let(:expected_pagination_options) { pagination_params.merge(first: 25, page: 2, per_page: 25) } | ||||
|         let(:params) { pagination_params } | ||||
|         let(:expected_options) do | ||||
|           pagination_params.merge(relation_params).merge(first: 25, page: 2, per_page: 25) | ||||
|         end | ||||
| 
 | ||||
|         it_behaves_like 'calls repos through Clients::Proxy with expected args' | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when relation type params present' do | ||||
|       let(:organization_login) { 'test-login' } | ||||
|       let(:params) { pagination_params.merge(relation_type: 'organization', organization_login: organization_login) } | ||||
|       let(:pagination_defaults) { { first: 25, page: 1, per_page: 25 } } | ||||
|       let(:expected_options) do | ||||
|         pagination_defaults.merge(pagination_params).merge( | ||||
|           relation_type: 'organization', organization_login: organization_login | ||||
|         ) | ||||
|       end | ||||
| 
 | ||||
|       it_behaves_like 'calls repos through Clients::Proxy with expected args' | ||||
| 
 | ||||
|       context 'when organization_login is too long and with ":"' do | ||||
|         let(:organization_login) { ":#{Array.new(270) { ('a'..'z').to_a.sample }.join}" } | ||||
|         let(:expected_options) do | ||||
|           pagination_defaults.merge(pagination_params).merge( | ||||
|             relation_type: 'organization', organization_login: organization_login.slice(1, 254) | ||||
|           ) | ||||
|         end | ||||
| 
 | ||||
|         it_behaves_like 'calls repos through Clients::Proxy with expected args' | ||||
|       end | ||||
|  |  | |||
|  | @ -72,6 +72,19 @@ RSpec.describe BranchesFinder, feature_category: :source_code_management do | |||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'by string' do | ||||
|         let(:params) { { search: 'add' } } | ||||
| 
 | ||||
|         it 'returns all branches contain name' do | ||||
|           result = subject | ||||
| 
 | ||||
|           result.each do |branch| | ||||
|             expect(branch.name).to include('add') | ||||
|           end | ||||
|           expect(result.count).to eq(5) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'by provided names' do | ||||
|         let(:params) { { names: %w[fix csv lfs does-not-exist] } } | ||||
| 
 | ||||
|  |  | |||
|  | @ -107,6 +107,7 @@ describe('diffs/components/app', () => { | |||
|     beforeEach(() => { | ||||
|       const fetchResolver = () => { | ||||
|         store.state.diffs.retrievingBatches = false; | ||||
|         store.state.notes.doneFetchingBatchDiscussions = true; | ||||
|         store.state.notes.discussions = 'test'; | ||||
|         return Promise.resolve({ real_size: 100 }); | ||||
|       }; | ||||
|  |  | |||
|  | @ -190,7 +190,6 @@ exports[`MlCandidate renders correctly 1`] = ` | |||
|           3 | ||||
|         </td> | ||||
|       </tr> | ||||
|         | ||||
|       <tr | ||||
|         class="divider" | ||||
|       /> | ||||
|  | @ -227,6 +226,42 @@ exports[`MlCandidate renders correctly 1`] = ` | |||
|           .99 | ||||
|         </td> | ||||
|       </tr> | ||||
|       <tr | ||||
|         class="divider" | ||||
|       /> | ||||
|         | ||||
|       <tr> | ||||
|         <td | ||||
|           class="gl-text-secondary gl-font-weight-bold" | ||||
|         > | ||||
|            | ||||
|             Metadata | ||||
|            | ||||
|         </td> | ||||
|           | ||||
|         <td | ||||
|           class="gl-font-weight-bold" | ||||
|         > | ||||
|           FileName | ||||
|         </td> | ||||
|           | ||||
|         <td> | ||||
|           test.py | ||||
|         </td> | ||||
|       </tr> | ||||
|       <tr> | ||||
|         <td /> | ||||
|           | ||||
|         <td | ||||
|           class="gl-font-weight-bold" | ||||
|         > | ||||
|           ExecutionTime | ||||
|         </td> | ||||
|           | ||||
|         <td> | ||||
|           .0856 | ||||
|         </td> | ||||
|       </tr> | ||||
|     </tbody> | ||||
|   </table> | ||||
| </div> | ||||
|  |  | |||
|  | @ -15,6 +15,10 @@ describe('MlCandidate', () => { | |||
|         { name: 'AUC', value: '.55' }, | ||||
|         { name: 'Accuracy', value: '.99' }, | ||||
|       ], | ||||
|       metadata: [ | ||||
|         { name: 'FileName', value: 'test.py' }, | ||||
|         { name: 'ExecutionTime', value: '.0856' }, | ||||
|       ], | ||||
|       info: { | ||||
|         iid: 'candidate_iid', | ||||
|         artifact_link: 'path_to_artifact', | ||||
|  |  | |||
|  | @ -1442,7 +1442,7 @@ describe('Actions Notes Store', () => { | |||
|       return testAction( | ||||
|         actions.fetchDiscussions, | ||||
|         {}, | ||||
|         { noteableType: notesConstants.MERGE_REQUEST_NOTEABLE_TYPE }, | ||||
|         { noteableType: notesConstants.EPIC_NOTEABLE_TYPE }, | ||||
|         [ | ||||
|           { type: mutationTypes.ADD_OR_UPDATE_DISCUSSIONS, payload: { discussion } }, | ||||
|           { type: mutationTypes.SET_FETCHING_DISCUSSIONS, payload: false }, | ||||
|  | @ -1472,9 +1472,7 @@ describe('Actions Notes Store', () => { | |||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('dispatches `fetchDiscussionsBatch` action if `paginatedMrDiscussions` feature flag is enabled', () => { | ||||
|       window.gon = { features: { paginatedMrDiscussions: true } }; | ||||
| 
 | ||||
|     it('dispatches `fetchDiscussionsBatch` action if noteable is a MergeRequest', () => { | ||||
|       return testAction( | ||||
|         actions.fetchDiscussions, | ||||
|         { path: 'test-path', filter: 'test-filter', persistFilter: 'test-persist-filter' }, | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe BulkImports::Clients::HTTP do | ||||
| RSpec.describe BulkImports::Clients::HTTP, feature_category: :importers do | ||||
|   include ImportSpecHelper | ||||
| 
 | ||||
|   let(:url) { 'http://gitlab.example' } | ||||
|  | @ -22,12 +22,6 @@ RSpec.describe BulkImports::Clients::HTTP do | |||
|     ) | ||||
|   end | ||||
| 
 | ||||
|   before do | ||||
|     allow(Gitlab::HTTP).to receive(:get) | ||||
|       .with('http://gitlab.example/api/v4/version', anything) | ||||
|       .and_return(metadata_response) | ||||
|   end | ||||
| 
 | ||||
|   subject { described_class.new(url: url, token: token) } | ||||
| 
 | ||||
|   shared_examples 'performs network request' do | ||||
|  | @ -39,7 +33,7 @@ RSpec.describe BulkImports::Clients::HTTP do | |||
| 
 | ||||
|     context 'error handling' do | ||||
|       context 'when error occurred' do | ||||
|         it 'raises BulkImports::Error' do | ||||
|         it 'raises BulkImports::NetworkError' do | ||||
|           allow(Gitlab::HTTP).to receive(method).and_raise(Errno::ECONNREFUSED) | ||||
| 
 | ||||
|           expect { subject.public_send(method, resource) }.to raise_exception(BulkImports::NetworkError) | ||||
|  | @ -47,7 +41,7 @@ RSpec.describe BulkImports::Clients::HTTP do | |||
|       end | ||||
| 
 | ||||
|       context 'when response is not success' do | ||||
|         it 'raises BulkImports::Error' do | ||||
|         it 'raises BulkImports::NetworkError' do | ||||
|           response_double = double(code: 503, success?: false, parsed_response: 'Error', request: double(path: double(path: '/test'))) | ||||
| 
 | ||||
|           allow(Gitlab::HTTP).to receive(method).and_return(response_double) | ||||
|  | @ -210,33 +204,149 @@ RSpec.describe BulkImports::Clients::HTTP do | |||
| 
 | ||||
|   describe '#instance_version' do | ||||
|     it 'returns version as an instance of Gitlab::VersionInfo' do | ||||
|       response = { version: version } | ||||
| 
 | ||||
|       stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token') | ||||
|         .to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|       expect(subject.instance_version).to eq(Gitlab::VersionInfo.parse(version)) | ||||
|     end | ||||
| 
 | ||||
|     context 'when /version endpoint is not available' do | ||||
|       it 'requests /metadata endpoint' do | ||||
|         response_double = double(code: 404, success?: false, parsed_response: 'Not Found', request: double(path: double(path: '/version'))) | ||||
|         response = { version: version } | ||||
| 
 | ||||
|         allow(Gitlab::HTTP).to receive(:get) | ||||
|           .with('http://gitlab.example/api/v4/version', anything) | ||||
|           .and_return(response_double) | ||||
| 
 | ||||
|         expect(Gitlab::HTTP).to receive(:get) | ||||
|           .with('http://gitlab.example/api/v4/metadata', anything) | ||||
|           .and_return(metadata_response) | ||||
|         stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) | ||||
|         stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') | ||||
|           .to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|         expect(subject.instance_version).to eq(Gitlab::VersionInfo.parse(version)) | ||||
|       end | ||||
| 
 | ||||
|       context 'when /metadata endpoint returns a 401' do | ||||
|         it 'raises a BulkImports:Error' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') | ||||
|             .to_return(status: 401, body: "", headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|           expect { subject.instance_version }.to raise_exception(BulkImports::Error, "Import aborted as the provided personal access token does not have the required 'api' scope.") | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when /metadata endpoint returns a 403' do | ||||
|         it 'raises a BulkImports:Error' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') | ||||
|             .to_return(status: 403, body: "", headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|           expect { subject.instance_version }.to raise_exception(BulkImports::Error, "Import aborted as the provided personal access token does not have the required 'api' scope.") | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when /metadata endpoint returns a 404' do | ||||
|         it 'raises a BulkImports:Error' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') | ||||
|             .to_return(status: 404, body: "", headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|           expect { subject.instance_version }.to raise_exception(BulkImports::Error, 'Import aborted as it was not possible to connect to the provided GitLab instance URL.') | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when /metadata endpoint returns any other BulkImports::NetworkError' do | ||||
|         it 'raises a BulkImports:NetworkError' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') | ||||
|             .to_return(status: 418, body: "", headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|           expect { subject.instance_version }.to raise_exception(BulkImports::NetworkError) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#validate_instance_version!' do | ||||
|     before do | ||||
|       allow(subject).to receive(:instance_version).and_return(source_version) | ||||
|     end | ||||
| 
 | ||||
|     context 'when instance version is greater than or equal to the minimum major version' do | ||||
|       let(:source_version) { Gitlab::VersionInfo.new(14) } | ||||
| 
 | ||||
|       it { expect(subject.validate_instance_version!).to eq(true) } | ||||
|     end | ||||
| 
 | ||||
|     context 'when instance version is less than the minimum major version' do | ||||
|       let(:source_version) { Gitlab::VersionInfo.new(13, 10, 0) } | ||||
| 
 | ||||
|       it { expect { subject.validate_instance_version! }.to raise_exception(BulkImports::Error) } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#validate_import_scopes!' do | ||||
|     context 'when the source_version is < 15.5' do | ||||
|       let(:source_version) { Gitlab::VersionInfo.new(15, 0) } | ||||
| 
 | ||||
|       it 'skips validation' do | ||||
|         allow(subject).to receive(:instance_version).and_return(source_version) | ||||
| 
 | ||||
|         expect(subject.validate_import_scopes!).to eq(true) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when source version is 15.5 or higher' do | ||||
|       let(:source_version) { Gitlab::VersionInfo.new(15, 6) } | ||||
| 
 | ||||
|       before do | ||||
|         allow(subject).to receive(:instance_version).and_return(source_version) | ||||
|       end | ||||
| 
 | ||||
|       context 'when an HTTP error is raised' do | ||||
|         let(:response) { { enterprise: false } } | ||||
| 
 | ||||
|         it 'raises BulkImports::NetworkError' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/personal_access_tokens/self?private_token=token') | ||||
|             .to_return(status: 404) | ||||
| 
 | ||||
|           expect { subject.validate_import_scopes! }.to raise_exception(BulkImports::NetworkError) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when scopes are valid' do | ||||
|         it 'returns true' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/personal_access_tokens/self?private_token=token') | ||||
|             .to_return(status: 200, body: { 'scopes' => ['api'] }.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|           expect(subject.validate_import_scopes!).to eq(true) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when scopes are invalid' do | ||||
|         it 'raises a BulkImports error' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/personal_access_tokens/self?private_token=token') | ||||
|             .to_return(status: 200, body: { 'scopes' => ['read_user'] }.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||
| 
 | ||||
|           expect(subject.instance_version).to eq(Gitlab::VersionInfo.parse(source_version)) | ||||
|           expect { subject.validate_import_scopes! }.to raise_exception(BulkImports::Error) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#instance_enterprise' do | ||||
|     let(:response) { { enterprise: false } } | ||||
| 
 | ||||
|     before do | ||||
|       stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token') | ||||
|         .to_return(status: 200, body: response.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||
|     end | ||||
| 
 | ||||
|     it 'returns source instance enterprise information' do | ||||
|       expect(subject.instance_enterprise).to eq(false) | ||||
|     end | ||||
| 
 | ||||
|     context 'when enterprise information is missing' do | ||||
|       let(:enterprise) { nil } | ||||
|       let(:response) { {} } | ||||
| 
 | ||||
|       it 'defaults to true' do | ||||
|         expect(subject.instance_enterprise).to eq(true) | ||||
|  | @ -245,14 +355,20 @@ RSpec.describe BulkImports::Clients::HTTP do | |||
|   end | ||||
| 
 | ||||
|   describe '#compatible_for_project_migration?' do | ||||
|     before do | ||||
|       allow(subject).to receive(:instance_version).and_return(Gitlab::VersionInfo.parse(version)) | ||||
|     end | ||||
| 
 | ||||
|     context 'when instance version is lower the the expected minimum' do | ||||
|       let(:version) { '14.3.0' } | ||||
| 
 | ||||
|       it 'returns false' do | ||||
|         expect(subject.compatible_for_project_migration?).to be false | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when instance version is at least the expected minimum' do | ||||
|       let(:version) { "14.4.4" } | ||||
|       let(:version) { '14.4.4' } | ||||
| 
 | ||||
|       it 'returns true' do | ||||
|         expect(subject.compatible_for_project_migration?).to be true | ||||
|  | @ -260,18 +376,6 @@ RSpec.describe BulkImports::Clients::HTTP do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when source instance is incompatible' do | ||||
|     let(:version) { '13.0.0' } | ||||
| 
 | ||||
|     it 'raises an error' do | ||||
|       expect { subject.get(resource) } | ||||
|         .to raise_error( | ||||
|           ::BulkImports::Error, | ||||
|           "Unsupported GitLab Version. Minimum Supported Gitlab Version #{BulkImport::MIN_MAJOR_VERSION}." | ||||
|         ) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when url is relative' do | ||||
|     let(:url) { 'http://website.example/gitlab' } | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ require 'spec_helper' | |||
| RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator, feature_category: :vulnerability_management do | ||||
|   let_it_be(:project) { create(:project) } | ||||
| 
 | ||||
|   let(:current_dast_versions) { described_class::CURRENT_VERSIONS[:dast].join(', ') } | ||||
|   let(:supported_dast_versions) { described_class::SUPPORTED_VERSIONS[:dast].join(', ') } | ||||
|   let(:deprecated_schema_version_message) {} | ||||
|   let(:missing_schema_version_message) do | ||||
|  | @ -466,8 +467,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator, featu | |||
| 
 | ||||
|       let(:report_version) { described_class::DEPRECATED_VERSIONS[report_type].last } | ||||
|       let(:expected_deprecation_message) do | ||||
|         "Version #{report_version} for report type #{report_type} has been deprecated, supported versions for this "\ | ||||
|         "report type are: #{supported_dast_versions}. GitLab will attempt to parse and ingest this report if valid." | ||||
|         "version #{report_version} for report type #{report_type} is deprecated. "\ | ||||
|         "However, GitLab will still attempt to parse and ingest this report. "\ | ||||
|         "Upgrade the security report to one of the following versions: #{current_dast_versions}." | ||||
|       end | ||||
| 
 | ||||
|       let(:expected_deprecation_warnings) do | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe Gitlab::GithubImport::Client do | ||||
| RSpec.describe Gitlab::GithubImport::Client, feature_category: :importer do | ||||
|   subject(:client) { described_class.new('foo', parallel: parallel) } | ||||
| 
 | ||||
|   let(:parallel) { true } | ||||
|  | @ -614,6 +614,46 @@ RSpec.describe Gitlab::GithubImport::Client do | |||
|         client.search_repos_by_name_graphql('test') | ||||
|       end | ||||
| 
 | ||||
|       context 'when relation type option present' do | ||||
|         context 'when relation type is owned' do | ||||
|           let(:expected_query) { 'test in:name is:public,private user:user' } | ||||
| 
 | ||||
|           it 'searches for repositories within the organization based on name' do | ||||
|             expect(client.octokit).to receive(:post).with( | ||||
|               '/graphql', { query: expected_graphql }.to_json | ||||
|             ) | ||||
| 
 | ||||
|             client.search_repos_by_name_graphql('test', relation_type: 'owned') | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'when relation type is organization' do | ||||
|           let(:expected_query) { 'test in:name is:public,private org:test-login' } | ||||
| 
 | ||||
|           it 'searches for repositories within the organization based on name' do | ||||
|             expect(client.octokit).to receive(:post).with( | ||||
|               '/graphql', { query: expected_graphql }.to_json | ||||
|             ) | ||||
| 
 | ||||
|             client.search_repos_by_name_graphql( | ||||
|               'test', relation_type: 'organization', organization_login: 'test-login' | ||||
|             ) | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'when relation type is collaborated' do | ||||
|           let(:expected_query) { 'test in:name is:public,private repo:repo1 repo:repo2' } | ||||
| 
 | ||||
|           it 'searches for collaborated repositories based on name' do | ||||
|             expect(client.octokit).to receive(:post).with( | ||||
|               '/graphql', { query: expected_graphql }.to_json | ||||
|             ) | ||||
| 
 | ||||
|             client.search_repos_by_name_graphql('test', relation_type: 'collaborated') | ||||
|           end | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when pagination options present' do | ||||
|         context 'with "first" option' do | ||||
|           let(:expected_graphql_params) do | ||||
|  |  | |||
|  | @ -0,0 +1,7 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe Gitlab::Redis::RepositoryCache, feature_category: :scalability do | ||||
|   include_examples "redis_new_instance_shared_examples", 'repository_cache', Gitlab::Redis::Cache | ||||
| end | ||||
|  | @ -91,22 +91,6 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :source_code | |||
|         expect(discussions.count).to eq(1) | ||||
|         expect(notes).to match([a_hash_including('id' => discussion_2.id.to_s)]) | ||||
|       end | ||||
| 
 | ||||
|       context 'when paginated_mr_discussions is disabled' do | ||||
|         before do | ||||
|           stub_feature_flags(paginated_mr_discussions: false) | ||||
|         end | ||||
| 
 | ||||
|         it 'returns all discussions and ignores per_page param' do | ||||
|           get_discussions(per_page: 2) | ||||
| 
 | ||||
|           discussions = Gitlab::Json.parse(response.body) | ||||
|           notes = discussions.flat_map { |d| d['notes'] } | ||||
| 
 | ||||
|           expect(discussions.count).to eq(4) | ||||
|           expect(notes.count).to eq(5) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,19 +13,19 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do | |||
|         source_type: 'group_entity', | ||||
|         source_full_path: 'full/path/to/group1', | ||||
|         destination_slug: 'destination group 1', | ||||
|         destination_namespace: 'full/path/to/destination1' | ||||
|         destination_namespace: 'parent-group' | ||||
|       }, | ||||
|       { | ||||
|         source_type: 'group_entity', | ||||
|         source_full_path: 'full/path/to/group2', | ||||
|         destination_slug: 'destination group 2', | ||||
|         destination_namespace: 'full/path/to/destination2' | ||||
|         destination_namespace: 'parent-group' | ||||
|       }, | ||||
|       { | ||||
|         source_type: 'project_entity', | ||||
|         source_full_path: 'full/path/to/project1', | ||||
|         destination_slug: 'destination project 1', | ||||
|         destination_namespace: 'full/path/to/destination1' | ||||
|         destination_namespace: 'parent-group' | ||||
|       } | ||||
|     ] | ||||
|   end | ||||
|  | @ -33,7 +33,92 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do | |||
|   subject { described_class.new(user, params, credentials) } | ||||
| 
 | ||||
|   describe '#execute' do | ||||
|     let_it_be(:source_version) do | ||||
|     context 'when gitlab version is 15.5 or higher' do | ||||
|       let(:source_version) { { version: "15.6.0", enterprise: false } } | ||||
| 
 | ||||
|       context 'when a BulkImports::Error is raised while validating the instance version' do | ||||
|         before do | ||||
|           allow_next_instance_of(BulkImports::Clients::HTTP) do |client| | ||||
|             allow(client) | ||||
|               .to receive(:validate_instance_version!) | ||||
|               .and_raise(BulkImports::Error, "This is a BulkImports error.") | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         it 'rescues the error and raises a ServiceResponse::Error' do | ||||
|           result = subject.execute | ||||
| 
 | ||||
|           expect(result).to be_a(ServiceResponse) | ||||
|           expect(result).to be_error | ||||
|           expect(result.message).to eq("This is a BulkImports error.") | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when required scopes are not present' do | ||||
|         it 'returns ServiceResponse with error if token does not have api scope' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') | ||||
|             .to_return( | ||||
|               status: 200, | ||||
|               body: source_version.to_json, | ||||
|               headers: { 'Content-Type' => 'application/json' } | ||||
|             ) | ||||
| 
 | ||||
|           allow_next_instance_of(BulkImports::Clients::HTTP) do |client| | ||||
|             allow(client).to receive(:validate_instance_version!).and_raise(BulkImports::Error.scope_validation_failure) | ||||
|           end | ||||
| 
 | ||||
|           result = subject.execute | ||||
| 
 | ||||
|           expect(result).to be_a(ServiceResponse) | ||||
|           expect(result).to be_error | ||||
|           expect(result.message) | ||||
|             .to eq( | ||||
|               "Import aborted as the provided personal access token does not have the required 'api' scope." | ||||
|             ) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when token validation succeeds' do | ||||
|         it 'creates bulk import' do | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') | ||||
|             .to_return(status: 200, body: source_version.to_json, headers: { 'Content-Type' => 'application/json' }) | ||||
|           stub_request(:get, 'http://gitlab.example/api/v4/personal_access_tokens/self?private_token=token') | ||||
|             .to_return( | ||||
|               status: 200, | ||||
|               body: { 'scopes' => ['api'] }.to_json, | ||||
|               headers: { 'Content-Type' => 'application/json' } | ||||
|             ) | ||||
| 
 | ||||
|           parent_group.add_owner(user) | ||||
|           expect { subject.execute }.to change { BulkImport.count }.by(1) | ||||
| 
 | ||||
|           last_bulk_import = BulkImport.last | ||||
|           expect(last_bulk_import.user).to eq(user) | ||||
|           expect(last_bulk_import.source_version).to eq(source_version[:version]) | ||||
|           expect(last_bulk_import.user).to eq(user) | ||||
|           expect(last_bulk_import.source_enterprise).to eq(false) | ||||
| 
 | ||||
|           expect_snowplow_event( | ||||
|             category: 'BulkImports::CreateService', | ||||
|             action: 'create', | ||||
|             label: 'bulk_import_group' | ||||
|           ) | ||||
| 
 | ||||
|           expect_snowplow_event( | ||||
|             category: 'BulkImports::CreateService', | ||||
|             action: 'create', | ||||
|             label: 'import_access_level', | ||||
|             user: user, | ||||
|             extra: { user_role: 'Owner', import_type: 'bulk_import_group' } | ||||
|           ) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when gitlab version is lower than 15.5' do | ||||
|       let(:source_version) do | ||||
|         Gitlab::VersionInfo.new(::BulkImport::MIN_MAJOR_VERSION, | ||||
|                                 ::BulkImport::MIN_MINOR_VERSION_FOR_PROJECT) | ||||
|       end | ||||
|  | @ -102,22 +187,6 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do | |||
|         expect(result.message).to eq("Validation failed: Source full path can't be blank") | ||||
|       end | ||||
| 
 | ||||
|     context 'when the token is invalid' do | ||||
|       before do | ||||
|         allow_next_instance_of(BulkImports::Clients::HTTP) do |client| | ||||
|           allow(client).to receive(:instance_version).and_raise(BulkImports::NetworkError, "401 Unauthorized") | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       it 'rescues the error and raises a ServiceResponse::Error' do | ||||
|         result = subject.execute | ||||
| 
 | ||||
|         expect(result).to be_a(ServiceResponse) | ||||
|         expect(result).to be_error | ||||
|         expect(result.message).to eq("401 Unauthorized") | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|       describe '#user-role' do | ||||
|         context 'when there is a parent_namespace and the user is a member' do | ||||
|           let(:group2) { create(:group, path: 'destination200', source_id: parent_group.id ) } | ||||
|  | @ -146,18 +215,6 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do | |||
|           end | ||||
|         end | ||||
| 
 | ||||
|       context 'when there is a parent_namespace and the user is not a member' do | ||||
|         let(:params) do | ||||
|           [ | ||||
|             { | ||||
|               source_type: 'group_entity', | ||||
|               source_full_path: 'full/path/to/group1', | ||||
|               destination_slug: 'destination-group-1', | ||||
|               destination_namespace: 'parent-group' | ||||
|             } | ||||
|           ] | ||||
|         end | ||||
| 
 | ||||
|         it 'defines access_level as not a member' do | ||||
|           subject.execute | ||||
|           expect_snowplow_event( | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe BulkImports::GetImportableDataService do | ||||
| RSpec.describe BulkImports::GetImportableDataService, feature_category: :importers do | ||||
|   describe '#execute' do | ||||
|     include_context 'bulk imports requests context', 'https://gitlab.example.com' | ||||
| 
 | ||||
|  | @ -34,6 +34,18 @@ RSpec.describe BulkImports::GetImportableDataService do | |||
|       ] | ||||
|     end | ||||
| 
 | ||||
|     let(:source_version) do | ||||
|       Gitlab::VersionInfo.new(::BulkImport::MIN_MAJOR_VERSION, | ||||
|                               ::BulkImport::MIN_MINOR_VERSION_FOR_PROJECT) | ||||
|     end | ||||
| 
 | ||||
|     before do | ||||
|       allow_next_instance_of(BulkImports::Clients::HTTP) do |instance| | ||||
|         allow(instance).to receive(:instance_version).and_return(source_version) | ||||
|         allow(instance).to receive(:instance_enterprise).and_return(false) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     subject do | ||||
|       described_class.new(params, query_params, credentials).execute | ||||
|     end | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ RSpec.shared_context 'bulk imports requests context' do |url| | |||
|   let(:request_headers) { { 'Content-Type' => 'application/json' } } | ||||
| 
 | ||||
|   before do | ||||
|     stub_request(:get, "#{url}/api/v4/version?page=1&per_page=20&private_token=demo-pat") | ||||
|     stub_request(:get, "#{url}/api/v4/version?private_token=demo-pat") | ||||
|       .with(headers: request_headers) | ||||
|       .to_return( | ||||
|         status: 200, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue