Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									6aab18704a
								
							
						
					
					
						commit
						b0107e8756
					
				|  | @ -328,6 +328,9 @@ Cop/SidekiqOptionsQueue: | ||||||
| 
 | 
 | ||||||
| Graphql/AuthorizeTypes: | Graphql/AuthorizeTypes: | ||||||
|   Enabled: true |   Enabled: true | ||||||
|  |   Include: | ||||||
|  |     - 'app/graphql/types/**/*' | ||||||
|  |     - 'ee/app/graphql/types/**/*' | ||||||
|   Exclude: |   Exclude: | ||||||
|     - 'spec/**/*.rb' |     - 'spec/**/*.rb' | ||||||
|     - 'ee/spec/**/*.rb' |     - 'ee/spec/**/*.rb' | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import LogLine from './line.vue'; | ||||||
| import LogLineHeader from './line_header.vue'; | import LogLineHeader from './line_header.vue'; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   name: 'CollpasibleLogSection', |   name: 'CollapsibleLogSection', | ||||||
|   components: { |   components: { | ||||||
|     LogLine, |     LogLine, | ||||||
|     LogLineHeader, |     LogLineHeader, | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| <script> | <script> | ||||||
| import { mapState, mapActions } from 'vuex'; | import { mapState, mapActions } from 'vuex'; | ||||||
| import CollpasibleLogSection from './collapsible_section.vue'; | import CollapsibleLogSection from './collapsible_section.vue'; | ||||||
| import LogLine from './line.vue'; | import LogLine from './line.vue'; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     CollpasibleLogSection, |     CollapsibleLogSection, | ||||||
|     LogLine, |     LogLine, | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|  | @ -51,7 +51,7 @@ export default { | ||||||
| <template> | <template> | ||||||
|   <code class="job-log d-block" data-qa-selector="job_log_content"> |   <code class="job-log d-block" data-qa-selector="job_log_content"> | ||||||
|     <template v-for="(section, index) in trace"> |     <template v-for="(section, index) in trace"> | ||||||
|       <collpasible-log-section |       <collapsible-log-section | ||||||
|         v-if="section.isHeader" |         v-if="section.isHeader" | ||||||
|         :key="`collapsible-${index}`" |         :key="`collapsible-${index}`" | ||||||
|         :section="section" |         :section="section" | ||||||
|  |  | ||||||
|  | @ -195,7 +195,7 @@ export const receiveTraceError = ({ dispatch }) => { | ||||||
|   flash(__('An error occurred while fetching the job log.')); |   flash(__('An error occurred while fetching the job log.')); | ||||||
| }; | }; | ||||||
| /** | /** | ||||||
|  * When the user clicks a collpasible line in the job |  * When the user clicks a collapsible line in the job | ||||||
|  * log, we commit a mutation to update the state |  * log, we commit a mutation to update the state | ||||||
|  * |  * | ||||||
|  * @param {Object} section |  * @param {Object} section | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ export const parseLine = (line = {}, lineNumber) => ({ | ||||||
| /** | /** | ||||||
|  * When a line has `section_header` set to true, we create a new |  * When a line has `section_header` set to true, we create a new | ||||||
|  * structure to allow to nest the lines that belong to the |  * structure to allow to nest the lines that belong to the | ||||||
|  * collpasible section |  * collapsible section | ||||||
|  * |  * | ||||||
|  * @param Object line |  * @param Object line | ||||||
|  * @param Number lineNumber |  * @param Number lineNumber | ||||||
|  | @ -91,7 +91,7 @@ export const getIncrementalLineNumber = acc => { | ||||||
|  * Parses the job log content into a structure usable by the template |  * Parses the job log content into a structure usable by the template | ||||||
|  * |  * | ||||||
|  * For collaspible lines (section_header = true): |  * For collaspible lines (section_header = true): | ||||||
|  *    - creates a new array to hold the lines that are collpasible, |  *    - creates a new array to hold the lines that are collapsible, | ||||||
|  *    - adds a isClosed property to handle toggle |  *    - adds a isClosed property to handle toggle | ||||||
|  *    - adds a isHeader property to handle template logic |  *    - adds a isHeader property to handle template logic | ||||||
|  *    - adds the section_duration |  *    - adds the section_duration | ||||||
|  |  | ||||||
|  | @ -0,0 +1,91 @@ | ||||||
|  | <script> | ||||||
|  | import { GlTabs, GlTab, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui'; | ||||||
|  | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { __ } from '~/locale'; | ||||||
|  | import createFlash from '~/flash'; | ||||||
|  | import ForkGroupsListItem from './fork_groups_list_item.vue'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   components: { | ||||||
|  |     GlTabs, | ||||||
|  |     GlTab, | ||||||
|  |     GlLoadingIcon, | ||||||
|  |     GlSearchBoxByType, | ||||||
|  |     ForkGroupsListItem, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     hasReachedProjectLimit: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |     endpoint: { | ||||||
|  |       type: String, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       namespaces: null, | ||||||
|  |       filter: '', | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     filteredNamespaces() { | ||||||
|  |       return this.namespaces.filter(n => n.name.toLowerCase().includes(this.filter.toLowerCase())); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   mounted() { | ||||||
|  |     this.loadGroups(); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   methods: { | ||||||
|  |     loadGroups() { | ||||||
|  |       axios | ||||||
|  |         .get(this.endpoint) | ||||||
|  |         .then(response => { | ||||||
|  |           this.namespaces = response.data.namespaces; | ||||||
|  |         }) | ||||||
|  |         .catch(() => createFlash(__('There was a problem fetching groups.'))); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   i18n: { | ||||||
|  |     searchPlaceholder: __('Search by name'), | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <gl-tabs class="fork-groups"> | ||||||
|  |     <gl-tab :title="__('Groups and subgroups')"> | ||||||
|  |       <gl-loading-icon v-if="!namespaces" size="md" class="gl-mt-3" /> | ||||||
|  |       <template v-else-if="namespaces.length === 0"> | ||||||
|  |         <div class="gl-text-center"> | ||||||
|  |           <div class="h5">{{ __('No available groups to fork the project.') }}</div> | ||||||
|  |           <p class="gl-mt-5"> | ||||||
|  |             {{ __('You must have permission to create a project in a group before forking.') }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </template> | ||||||
|  |       <div v-else-if="filteredNamespaces.length === 0" class="gl-text-center gl-mt-3"> | ||||||
|  |         {{ s__('GroupsTree|No groups matched your search') }} | ||||||
|  |       </div> | ||||||
|  |       <ul v-else class="groups-list group-list-tree"> | ||||||
|  |         <fork-groups-list-item | ||||||
|  |           v-for="(namespace, index) in filteredNamespaces" | ||||||
|  |           :key="index" | ||||||
|  |           :group="namespace" | ||||||
|  |           :has-reached-project-limit="hasReachedProjectLimit" | ||||||
|  |         /> | ||||||
|  |       </ul> | ||||||
|  |     </gl-tab> | ||||||
|  |     <template #tabs-end> | ||||||
|  |       <gl-search-box-by-type | ||||||
|  |         v-if="namespaces && namespaces.length" | ||||||
|  |         v-model="filter" | ||||||
|  |         :placeholder="$options.i18n.searchPlaceholder" | ||||||
|  |         class="gl-align-self-center gl-ml-auto fork-filtered-search" | ||||||
|  |       /> | ||||||
|  |     </template> | ||||||
|  |   </gl-tabs> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,147 @@ | ||||||
|  | <script> | ||||||
|  | import { | ||||||
|  |   GlLink, | ||||||
|  |   GlButton, | ||||||
|  |   GlIcon, | ||||||
|  |   GlAvatar, | ||||||
|  |   GlTooltipDirective, | ||||||
|  |   GlTooltip, | ||||||
|  |   GlBadge, | ||||||
|  | } from '@gitlab/ui'; | ||||||
|  | import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants'; | ||||||
|  | import { __ } from '~/locale'; | ||||||
|  | import csrf from '~/lib/utils/csrf'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   components: { | ||||||
|  |     GlIcon, | ||||||
|  |     GlAvatar, | ||||||
|  |     GlBadge, | ||||||
|  |     GlButton, | ||||||
|  |     GlTooltip, | ||||||
|  |     GlLink, | ||||||
|  |   }, | ||||||
|  |   directives: { | ||||||
|  |     GlTooltip: GlTooltipDirective, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     group: { | ||||||
|  |       type: Object, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |     hasReachedProjectLimit: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { namespaces: null }; | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   computed: { | ||||||
|  |     rowClass() { | ||||||
|  |       return { | ||||||
|  |         'has-description': this.group.description, | ||||||
|  |         'being-removed': this.isGroupPendingRemoval, | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |     isGroupPendingRemoval() { | ||||||
|  |       return this.group.marked_for_deletion; | ||||||
|  |     }, | ||||||
|  |     hasForkedProject() { | ||||||
|  |       return Boolean(this.group.forked_project_path); | ||||||
|  |     }, | ||||||
|  |     visibilityIcon() { | ||||||
|  |       return VISIBILITY_TYPE_ICON[this.group.visibility]; | ||||||
|  |     }, | ||||||
|  |     visibilityTooltip() { | ||||||
|  |       return GROUP_VISIBILITY_TYPE[this.group.visibility]; | ||||||
|  |     }, | ||||||
|  |     isSelectButtonDisabled() { | ||||||
|  |       return this.hasReachedProjectLimit || !this.group.can_create_project; | ||||||
|  |     }, | ||||||
|  |     selectButtonDisabledTooltip() { | ||||||
|  |       return this.hasReachedProjectLimit | ||||||
|  |         ? this.$options.i18n.hasReachedProjectLimitMessage | ||||||
|  |         : this.$options.i18n.insufficientPermissionsMessage; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   i18n: { | ||||||
|  |     hasReachedProjectLimitMessage: __('You have reached your project limit'), | ||||||
|  |     insufficientPermissionsMessage: __( | ||||||
|  |       'You must have permission to create a project in a namespace before forking.', | ||||||
|  |     ), | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   csrf, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <li :class="rowClass" class="group-row"> | ||||||
|  |     <div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5"> | ||||||
|  |       <div class="folder-toggle-wrap gl-mr-2 gl-display-flex gl-align-items-center"> | ||||||
|  |         <gl-icon name="folder-o" /> | ||||||
|  |       </div> | ||||||
|  |       <gl-link | ||||||
|  |         :href="group.relative_path" | ||||||
|  |         class="gl-display-none gl-flex-shrink-0 gl-display-sm-flex gl-mr-3" | ||||||
|  |       > | ||||||
|  |         <gl-avatar :size="32" shape="rect" :entity-name="group.name" :src="group.avatarUrl" /> | ||||||
|  |       </gl-link> | ||||||
|  |       <div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center"> | ||||||
|  |         <div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1"> | ||||||
|  |           <div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3"> | ||||||
|  |             <gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">{{ | ||||||
|  |               group.full_name | ||||||
|  |             }}</gl-link> | ||||||
|  |             <gl-icon | ||||||
|  |               v-gl-tooltip.hover.bottom | ||||||
|  |               class="gl-mr-0 gl-inline-flex gl-mt-3 text-secondary" | ||||||
|  |               :name="visibilityIcon" | ||||||
|  |               :title="visibilityTooltip" | ||||||
|  |             /> | ||||||
|  |             <gl-badge | ||||||
|  |               v-if="isGroupPendingRemoval" | ||||||
|  |               variant="warning" | ||||||
|  |               class="gl-display-none gl-display-sm-flex gl-mt-3 gl-mr-1" | ||||||
|  |               >{{ __('pending removal') }}</gl-badge | ||||||
|  |             > | ||||||
|  |             <span v-if="group.permission" class="user-access-role gl-mt-3"> | ||||||
|  |               {{ group.permission }} | ||||||
|  |             </span> | ||||||
|  |           </div> | ||||||
|  |           <div v-if="group.description" class="description"> | ||||||
|  |             <span v-html="group.markdown_description"> </span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="gl-display-flex gl-flex-shrink-0"> | ||||||
|  |           <gl-button | ||||||
|  |             v-if="hasForkedProject" | ||||||
|  |             class="gl-h-7 gl-text-decoration-none!" | ||||||
|  |             :href="group.forked_project_path" | ||||||
|  |             >{{ __('Go to fork') }}</gl-button | ||||||
|  |           > | ||||||
|  |           <template v-else> | ||||||
|  |             <div ref="selectButtonWrapper"> | ||||||
|  |               <form method="POST" :action="group.fork_path"> | ||||||
|  |                 <input type="hidden" name="authenticity_token" :value="$options.csrf.token" /> | ||||||
|  |                 <gl-button | ||||||
|  |                   type="submit" | ||||||
|  |                   class="gl-h-7 gl-text-decoration-none!" | ||||||
|  |                   :data-qa-name="group.full_name" | ||||||
|  |                   variant="success" | ||||||
|  |                   :disabled="isSelectButtonDisabled" | ||||||
|  |                   >{{ __('Select') }}</gl-button | ||||||
|  |                 > | ||||||
|  |               </form> | ||||||
|  |             </div> | ||||||
|  |             <gl-tooltip v-if="isSelectButtonDisabled" :target="() => $refs.selectButtonWrapper"> | ||||||
|  |               {{ selectButtonDisabledTooltip }} | ||||||
|  |             </gl-tooltip> | ||||||
|  |           </template> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </li> | ||||||
|  | </template> | ||||||
|  | @ -17,11 +17,8 @@ module Routable | ||||||
| 
 | 
 | ||||||
|     after_validation :set_path_errors |     after_validation :set_path_errors | ||||||
| 
 | 
 | ||||||
|     before_validation do |     before_validation :prepare_route | ||||||
|       if full_path_changed? || full_name_changed? |     before_save :prepare_route # in case validation is skipped | ||||||
|         prepare_route |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   class_methods do |   class_methods do | ||||||
|  | @ -118,6 +115,8 @@ module Routable | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def prepare_route |   def prepare_route | ||||||
|  |     return unless full_path_changed? || full_name_changed? | ||||||
|  | 
 | ||||||
|     route || build_route(source: self) |     route || build_route(source: self) | ||||||
|     route.path = build_full_path |     route.path = build_full_path | ||||||
|     route.name = build_full_name |     route.name = build_full_name | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ class ProjectStatistics < ApplicationRecord | ||||||
|   COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count, :snippets_size].freeze |   COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count, :snippets_size].freeze | ||||||
|   INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze |   INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze | ||||||
|   NAMESPACE_RELATABLE_COLUMNS = [:repository_size, :wiki_size, :lfs_objects_size].freeze |   NAMESPACE_RELATABLE_COLUMNS = [:repository_size, :wiki_size, :lfs_objects_size].freeze | ||||||
|  |   FLAGGED_NAMESPACE_RELATABLE_COLUMNS = [*NAMESPACE_RELATABLE_COLUMNS, :snippets_size].freeze | ||||||
| 
 | 
 | ||||||
|   scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) } |   scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) } | ||||||
| 
 | 
 | ||||||
|  | @ -31,7 +32,7 @@ class ProjectStatistics < ApplicationRecord | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     if only.empty? || only.any? { |column| NAMESPACE_RELATABLE_COLUMNS.include?(column) } |     if only.empty? || only.any? { |column| namespace_relatable_columns.include?(column) } | ||||||
|       schedule_namespace_aggregation_worker |       schedule_namespace_aggregation_worker | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -110,6 +111,10 @@ class ProjectStatistics < ApplicationRecord | ||||||
|       Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id) |       Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   def namespace_relatable_columns | ||||||
|  |     Feature.enabled?(:namespace_snippets_size_stat) ? FLAGGED_NAMESPACE_RELATABLE_COLUMNS : NAMESPACE_RELATABLE_COLUMNS | ||||||
|  |   end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| ProjectStatistics.prepend_if_ee('EE::ProjectStatistics') | ProjectStatistics.prepend_if_ee('EE::ProjectStatistics') | ||||||
|  |  | ||||||
|  | @ -1527,7 +1527,7 @@ | ||||||
| - :name: project_update_repository_storage | - :name: project_update_repository_storage | ||||||
|   :feature_category: :gitaly |   :feature_category: :gitaly | ||||||
|   :has_external_dependencies:  |   :has_external_dependencies:  | ||||||
|   :urgency: :low |   :urgency: :throttled | ||||||
|   :resource_boundary: :unknown |   :resource_boundary: :unknown | ||||||
|   :weight: 1 |   :weight: 1 | ||||||
|   :idempotent: true |   :idempotent: true | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ class ProjectUpdateRepositoryStorageWorker | ||||||
| 
 | 
 | ||||||
|   idempotent! |   idempotent! | ||||||
|   feature_category :gitaly |   feature_category :gitaly | ||||||
|  |   urgency :throttled | ||||||
| 
 | 
 | ||||||
|   def perform(project_id, new_repository_storage_key, repository_storage_move_id = nil) |   def perform(project_id, new_repository_storage_key, repository_storage_move_id = nil) | ||||||
|     repository_storage_move = |     repository_storage_move = | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Throttle ProjectUpdateRepositoryStorageWorker Jobs | ||||||
|  | merge_request: 35230 | ||||||
|  | author: | ||||||
|  | type: other | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Create associated routes when a new bot user is created | ||||||
|  | merge_request: 35711 | ||||||
|  | author: | ||||||
|  | type: fixed | ||||||
|  | @ -10,17 +10,30 @@ Sidekiq::Testing.inline! do | ||||||
|       # we use randomized approach (e.g. `Array#sample`). |       # we use randomized approach (e.g. `Array#sample`). | ||||||
|       return unless source_project |       return unless source_project | ||||||
| 
 | 
 | ||||||
|       fork_project = Projects::ForkService.new( |       Sidekiq::Worker.skipping_transaction_check do | ||||||
|         source_project, |         fork_project = Projects::ForkService.new( | ||||||
|         user, |           source_project, | ||||||
|         namespace: user.namespace, |           user, | ||||||
|         skip_disk_validation: true |           namespace: user.namespace, | ||||||
|       ).execute |           skip_disk_validation: true | ||||||
|  |         ).execute | ||||||
| 
 | 
 | ||||||
|       if fork_project.valid? |         # Seed-Fu runs this entire fixture in a transaction, so the `after_commit` | ||||||
|         print '.' |         # hook won't run until after the fixture is loaded. That is too late | ||||||
|       else |         # since the Sidekiq::Testing block has already exited. Force clearing | ||||||
|         print 'F' |         # the `after_commit` queue to ensure the job is run now. | ||||||
|  |         fork_project.send(:_run_after_commit_queue) | ||||||
|  |         fork_project.import_state.send(:_run_after_commit_queue) | ||||||
|  | 
 | ||||||
|  |         # Expire repository cache after import to ensure | ||||||
|  |         # valid_repo? call below returns a correct answer | ||||||
|  |         fork_project.repository.expire_all_method_caches | ||||||
|  | 
 | ||||||
|  |         if fork_project.valid? && fork_project.valid_repo? | ||||||
|  |           print '.' | ||||||
|  |         else | ||||||
|  |           print 'F' | ||||||
|  |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -11310,6 +11310,56 @@ type RunDASTScanPayload { | ||||||
|   pipelineUrl: String |   pipelineUrl: String | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | """ | ||||||
|  | Represents a resource scanned by a security scan | ||||||
|  | """ | ||||||
|  | type ScannedResource { | ||||||
|  |   """ | ||||||
|  |   The HTTP request method used to access the URL | ||||||
|  |   """ | ||||||
|  |   requestMethod: String | ||||||
|  | 
 | ||||||
|  |   """ | ||||||
|  |   The URL scanned by the scanner | ||||||
|  |   """ | ||||||
|  |   url: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | The connection type for ScannedResource. | ||||||
|  | """ | ||||||
|  | type ScannedResourceConnection { | ||||||
|  |   """ | ||||||
|  |   A list of edges. | ||||||
|  |   """ | ||||||
|  |   edges: [ScannedResourceEdge] | ||||||
|  | 
 | ||||||
|  |   """ | ||||||
|  |   A list of nodes. | ||||||
|  |   """ | ||||||
|  |   nodes: [ScannedResource] | ||||||
|  | 
 | ||||||
|  |   """ | ||||||
|  |   Information to aid in pagination. | ||||||
|  |   """ | ||||||
|  |   pageInfo: PageInfo! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | An edge in a connection. | ||||||
|  | """ | ||||||
|  | type ScannedResourceEdge { | ||||||
|  |   """ | ||||||
|  |   A cursor for use in pagination. | ||||||
|  |   """ | ||||||
|  |   cursor: String! | ||||||
|  | 
 | ||||||
|  |   """ | ||||||
|  |   The item at the end of the edge. | ||||||
|  |   """ | ||||||
|  |   node: ScannedResource | ||||||
|  | } | ||||||
|  | 
 | ||||||
| """ | """ | ||||||
| Represents summary of a security report | Represents summary of a security report | ||||||
| """ | """ | ||||||
|  | @ -11349,6 +11399,31 @@ type SecurityReportSummary { | ||||||
| Represents a section of a summary of a security report | Represents a section of a summary of a security report | ||||||
| """ | """ | ||||||
| type SecurityReportSummarySection { | type SecurityReportSummarySection { | ||||||
|  |   """ | ||||||
|  |   A list of the first 20 scanned resources | ||||||
|  |   """ | ||||||
|  |   scannedResources( | ||||||
|  |     """ | ||||||
|  |     Returns the elements in the list that come after the specified cursor. | ||||||
|  |     """ | ||||||
|  |     after: String | ||||||
|  | 
 | ||||||
|  |     """ | ||||||
|  |     Returns the elements in the list that come before the specified cursor. | ||||||
|  |     """ | ||||||
|  |     before: String | ||||||
|  | 
 | ||||||
|  |     """ | ||||||
|  |     Returns the first _n_ elements from the list. | ||||||
|  |     """ | ||||||
|  |     first: Int | ||||||
|  | 
 | ||||||
|  |     """ | ||||||
|  |     Returns the last _n_ elements from the list. | ||||||
|  |     """ | ||||||
|  |     last: Int | ||||||
|  |   ): ScannedResourceConnection | ||||||
|  | 
 | ||||||
|   """ |   """ | ||||||
|   Total number of scanned resources |   Total number of scanned resources | ||||||
|   """ |   """ | ||||||
|  |  | ||||||
|  | @ -33217,6 +33217,159 @@ | ||||||
|           "enumValues": null, |           "enumValues": null, | ||||||
|           "possibleTypes": null |           "possibleTypes": null | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "kind": "OBJECT", | ||||||
|  |           "name": "ScannedResource", | ||||||
|  |           "description": "Represents a resource scanned by a security scan", | ||||||
|  |           "fields": [ | ||||||
|  |             { | ||||||
|  |               "name": "requestMethod", | ||||||
|  |               "description": "The HTTP request method used to access the URL", | ||||||
|  |               "args": [ | ||||||
|  | 
 | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "SCALAR", | ||||||
|  |                 "name": "String", | ||||||
|  |                 "ofType": null | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "name": "url", | ||||||
|  |               "description": "The URL scanned by the scanner", | ||||||
|  |               "args": [ | ||||||
|  | 
 | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "SCALAR", | ||||||
|  |                 "name": "String", | ||||||
|  |                 "ofType": null | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             } | ||||||
|  |           ], | ||||||
|  |           "inputFields": null, | ||||||
|  |           "interfaces": [ | ||||||
|  | 
 | ||||||
|  |           ], | ||||||
|  |           "enumValues": null, | ||||||
|  |           "possibleTypes": null | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "kind": "OBJECT", | ||||||
|  |           "name": "ScannedResourceConnection", | ||||||
|  |           "description": "The connection type for ScannedResource.", | ||||||
|  |           "fields": [ | ||||||
|  |             { | ||||||
|  |               "name": "edges", | ||||||
|  |               "description": "A list of edges.", | ||||||
|  |               "args": [ | ||||||
|  | 
 | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "LIST", | ||||||
|  |                 "name": null, | ||||||
|  |                 "ofType": { | ||||||
|  |                   "kind": "OBJECT", | ||||||
|  |                   "name": "ScannedResourceEdge", | ||||||
|  |                   "ofType": null | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "name": "nodes", | ||||||
|  |               "description": "A list of nodes.", | ||||||
|  |               "args": [ | ||||||
|  | 
 | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "LIST", | ||||||
|  |                 "name": null, | ||||||
|  |                 "ofType": { | ||||||
|  |                   "kind": "OBJECT", | ||||||
|  |                   "name": "ScannedResource", | ||||||
|  |                   "ofType": null | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "name": "pageInfo", | ||||||
|  |               "description": "Information to aid in pagination.", | ||||||
|  |               "args": [ | ||||||
|  | 
 | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "NON_NULL", | ||||||
|  |                 "name": null, | ||||||
|  |                 "ofType": { | ||||||
|  |                   "kind": "OBJECT", | ||||||
|  |                   "name": "PageInfo", | ||||||
|  |                   "ofType": null | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             } | ||||||
|  |           ], | ||||||
|  |           "inputFields": null, | ||||||
|  |           "interfaces": [ | ||||||
|  | 
 | ||||||
|  |           ], | ||||||
|  |           "enumValues": null, | ||||||
|  |           "possibleTypes": null | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "kind": "OBJECT", | ||||||
|  |           "name": "ScannedResourceEdge", | ||||||
|  |           "description": "An edge in a connection.", | ||||||
|  |           "fields": [ | ||||||
|  |             { | ||||||
|  |               "name": "cursor", | ||||||
|  |               "description": "A cursor for use in pagination.", | ||||||
|  |               "args": [ | ||||||
|  | 
 | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "NON_NULL", | ||||||
|  |                 "name": null, | ||||||
|  |                 "ofType": { | ||||||
|  |                   "kind": "SCALAR", | ||||||
|  |                   "name": "String", | ||||||
|  |                   "ofType": null | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               "name": "node", | ||||||
|  |               "description": "The item at the end of the edge.", | ||||||
|  |               "args": [ | ||||||
|  | 
 | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "OBJECT", | ||||||
|  |                 "name": "ScannedResource", | ||||||
|  |                 "ofType": null | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             } | ||||||
|  |           ], | ||||||
|  |           "inputFields": null, | ||||||
|  |           "interfaces": [ | ||||||
|  | 
 | ||||||
|  |           ], | ||||||
|  |           "enumValues": null, | ||||||
|  |           "possibleTypes": null | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           "kind": "OBJECT", |           "kind": "OBJECT", | ||||||
|           "name": "SecurityReportSummary", |           "name": "SecurityReportSummary", | ||||||
|  | @ -33319,6 +33472,59 @@ | ||||||
|           "name": "SecurityReportSummarySection", |           "name": "SecurityReportSummarySection", | ||||||
|           "description": "Represents a section of a summary of a security report", |           "description": "Represents a section of a summary of a security report", | ||||||
|           "fields": [ |           "fields": [ | ||||||
|  |             { | ||||||
|  |               "name": "scannedResources", | ||||||
|  |               "description": "A list of the first 20 scanned resources", | ||||||
|  |               "args": [ | ||||||
|  |                 { | ||||||
|  |                   "name": "after", | ||||||
|  |                   "description": "Returns the elements in the list that come after the specified cursor.", | ||||||
|  |                   "type": { | ||||||
|  |                     "kind": "SCALAR", | ||||||
|  |                     "name": "String", | ||||||
|  |                     "ofType": null | ||||||
|  |                   }, | ||||||
|  |                   "defaultValue": null | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   "name": "before", | ||||||
|  |                   "description": "Returns the elements in the list that come before the specified cursor.", | ||||||
|  |                   "type": { | ||||||
|  |                     "kind": "SCALAR", | ||||||
|  |                     "name": "String", | ||||||
|  |                     "ofType": null | ||||||
|  |                   }, | ||||||
|  |                   "defaultValue": null | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   "name": "first", | ||||||
|  |                   "description": "Returns the first _n_ elements from the list.", | ||||||
|  |                   "type": { | ||||||
|  |                     "kind": "SCALAR", | ||||||
|  |                     "name": "Int", | ||||||
|  |                     "ofType": null | ||||||
|  |                   }, | ||||||
|  |                   "defaultValue": null | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   "name": "last", | ||||||
|  |                   "description": "Returns the last _n_ elements from the list.", | ||||||
|  |                   "type": { | ||||||
|  |                     "kind": "SCALAR", | ||||||
|  |                     "name": "Int", | ||||||
|  |                     "ofType": null | ||||||
|  |                   }, | ||||||
|  |                   "defaultValue": null | ||||||
|  |                 } | ||||||
|  |               ], | ||||||
|  |               "type": { | ||||||
|  |                 "kind": "OBJECT", | ||||||
|  |                 "name": "ScannedResourceConnection", | ||||||
|  |                 "ofType": null | ||||||
|  |               }, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             }, | ||||||
|             { |             { | ||||||
|               "name": "scannedResourcesCount", |               "name": "scannedResourcesCount", | ||||||
|               "description": "Total number of scanned resources", |               "description": "Total number of scanned resources", | ||||||
|  |  | ||||||
|  | @ -1637,6 +1637,15 @@ Autogenerated return type of RunDASTScan | ||||||
| | `errors` | String! => Array | Errors encountered during execution of the mutation. | | | `errors` | String! => Array | Errors encountered during execution of the mutation. | | ||||||
| | `pipelineUrl` | String | URL of the pipeline that was created. | | | `pipelineUrl` | String | URL of the pipeline that was created. | | ||||||
| 
 | 
 | ||||||
|  | ## ScannedResource | ||||||
|  | 
 | ||||||
|  | Represents a resource scanned by a security scan | ||||||
|  | 
 | ||||||
|  | | Name  | Type  | Description | | ||||||
|  | | ---   |  ---- | ----------  | | ||||||
|  | | `requestMethod` | String | The HTTP request method used to access the URL | | ||||||
|  | | `url` | String | The URL scanned by the scanner | | ||||||
|  | 
 | ||||||
| ## SecurityReportSummary | ## SecurityReportSummary | ||||||
| 
 | 
 | ||||||
| Represents summary of a security report | Represents summary of a security report | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| --- | --- | ||||||
| redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter' | redirect_to: '../user/project/description_templates.md' | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter). | This document was moved to [description_templates](../user/project/description_templates.md). | ||||||
|  |  | ||||||
|  | @ -666,8 +666,9 @@ appear to be associated to any of the services running, since they all appear to | ||||||
| | `clusters_applications_runner`                            | `usage_activity_by_stage`            | `verify`      |                  |         | Unique clusters with Runner enabled                                        | | | `clusters_applications_runner`                            | `usage_activity_by_stage`            | `verify`      |                  |         | Unique clusters with Runner enabled                                        | | ||||||
| | `projects_reporting_ci_cd_back_to_github: 0`              | `usage_activity_by_stage`            | `verify`      |                  |         | Unique projects with a GitHub pipeline enabled                             | | | `projects_reporting_ci_cd_back_to_github: 0`              | `usage_activity_by_stage`            | `verify`      |                  |         | Unique projects with a GitHub pipeline enabled                             | | ||||||
| | `merge_requests_users`                                    | `usage_activity_by_stage_monthly`    | `create`      |                  |         | Unique count of users who used a merge request                             | | | `merge_requests_users`                                    | `usage_activity_by_stage_monthly`    | `create`      |                  |         | Unique count of users who used a merge request                             | | ||||||
| | `nodes`                                                   | `topology`                           | `enablement`  |                  |         | The list of server nodes on which GitLab components are running            | |  | ||||||
| | `duration_s`                                              | `topology`                           | `enablement`  |                  |         | Time it took to collect topology data                                      | | | `duration_s`                                              | `topology`                           | `enablement`  |                  |         | Time it took to collect topology data                                      | | ||||||
|  | | `application_requests_per_hour`                           | `topology`                           | `enablement`  |                  |         | Number of requests to the web application per hour                         | | ||||||
|  | | `nodes`                                                   | `topology`                           | `enablement`  |                  |         | The list of server nodes on which GitLab components are running            | | ||||||
| | `node_memory_total_bytes`                                 | `topology > nodes`                   | `enablement`  |                  |         | The total available memory of this node                                    | | | `node_memory_total_bytes`                                 | `topology > nodes`                   | `enablement`  |                  |         | The total available memory of this node                                    | | ||||||
| | `node_cpus`                                               | `topology > nodes`                   | `enablement`  |                  |         | The number of CPU cores of this node                                       | | | `node_cpus`                                               | `topology > nodes`                   | `enablement`  |                  |         | The number of CPU cores of this node                                       | | ||||||
| | `node_services`                                           | `topology > nodes`                   | `enablement`  |                  |         | The list of GitLab services running on this node                           | | | `node_services`                                           | `topology > nodes`                   | `enablement`  |                  |         | The list of GitLab services running on this node                           | | ||||||
|  | @ -873,6 +874,8 @@ The following is example content of the Usage Ping payload. | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "topology": { |   "topology": { | ||||||
|  |     "duration_s": 0.013836685999194742, | ||||||
|  |     "application_requests_per_hour": 4224, | ||||||
|     "nodes": [ |     "nodes": [ | ||||||
|       { |       { | ||||||
|         "node_memory_total_bytes": 33269903360, |         "node_memory_total_bytes": 33269903360, | ||||||
|  | @ -897,8 +900,7 @@ The following is example content of the Usage Ping payload. | ||||||
|         ... |         ... | ||||||
|       }, |       }, | ||||||
|       ... |       ... | ||||||
|     ], |     ] | ||||||
|     "duration_s": 0.013836685999194742 |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | @ -81,7 +81,7 @@ changes you made after picking the template and return it to its initial status. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| ## Setting a default template for merge requests and issues  **(STARTER)** | ## Setting a default template for merge requests and issues **(STARTER)** | ||||||
| 
 | 
 | ||||||
| > - This feature was introduced before [description templates](#overview) and is available in [GitLab Starter](https://about.gitlab.com/pricing/). It can be enabled in the project's settings. | > - This feature was introduced before [description templates](#overview) and is available in [GitLab Starter](https://about.gitlab.com/pricing/). It can be enabled in the project's settings. | ||||||
| > - Templates for issues were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28) in GitLab EE 8.1. | > - Templates for issues were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28) in GitLab EE 8.1. | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ module Gitlab | ||||||
| 
 | 
 | ||||||
|     MASS_INSERT_PROJECT_START = 'mass_insert_project_' |     MASS_INSERT_PROJECT_START = 'mass_insert_project_' | ||||||
|     MASS_INSERT_USER_START = 'mass_insert_user_' |     MASS_INSERT_USER_START = 'mass_insert_user_' | ||||||
|  |     REPORTED_USER_START = 'reported_user_' | ||||||
|     ESTIMATED_INSERT_PER_MINUTE = 2_000_000 |     ESTIMATED_INSERT_PER_MINUTE = 2_000_000 | ||||||
|     MASS_INSERT_ENV = 'MASS_INSERT' |     MASS_INSERT_ENV = 'MASS_INSERT' | ||||||
| 
 | 
 | ||||||
|  | @ -36,7 +37,7 @@ module Gitlab | ||||||
| 
 | 
 | ||||||
|       included do |       included do | ||||||
|         scope :not_mass_generated, -> do |         scope :not_mass_generated, -> do | ||||||
|           where.not("username LIKE '#{MASS_INSERT_USER_START}%'") |           where.not("username LIKE '#{MASS_INSERT_USER_START}%' OR username LIKE '#{REPORTED_USER_START}%'") | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -28,11 +28,20 @@ module Gitlab | ||||||
|       def topology_fetch_all_data |       def topology_fetch_all_data | ||||||
|         with_prometheus_client(fallback: {}) do |client| |         with_prometheus_client(fallback: {}) do |client| | ||||||
|           { |           { | ||||||
|  |             application_requests_per_hour: topology_app_requests_per_hour(client), | ||||||
|             nodes: topology_node_data(client) |             nodes: topology_node_data(client) | ||||||
|           } |           }.compact | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       def topology_app_requests_per_hour(client) | ||||||
|  |         result = client.query(one_week_average('gitlab_usage_ping:ops:rate5m')).first | ||||||
|  |         return unless result | ||||||
|  | 
 | ||||||
|  |         # the metric is recorded as a per-second rate | ||||||
|  |         (result['value'].last.to_f * 1.hour).to_i | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       def topology_node_data(client) |       def topology_node_data(client) | ||||||
|         # node-level data |         # node-level data | ||||||
|         by_instance_mem = topology_node_memory(client) |         by_instance_mem = topology_node_memory(client) | ||||||
|  |  | ||||||
|  | @ -10964,6 +10964,9 @@ msgstr "" | ||||||
| msgid "Go to find file" | msgid "Go to find file" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Go to fork" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Go to issue boards" | msgid "Go to issue boards" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -11531,6 +11534,9 @@ msgstr "" | ||||||
| msgid "Groups and projects" | msgid "Groups and projects" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Groups and subgroups" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}." | msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -15131,6 +15137,9 @@ msgstr "" | ||||||
| msgid "No authentication methods configured." | msgid "No authentication methods configured." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "No available groups to fork the project." | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "No available namespaces to fork the project." | msgid "No available namespaces to fork the project." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -19831,6 +19840,9 @@ msgstr "" | ||||||
| msgid "Search by author" | msgid "Search by author" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Search by name" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Search files" | msgid "Search files" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -22979,6 +22991,9 @@ msgstr "" | ||||||
| msgid "There was a problem communicating with your device." | msgid "There was a problem communicating with your device." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "There was a problem fetching groups." | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "There was a problem fetching project branches." | msgid "There was a problem fetching project branches." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -26256,6 +26271,9 @@ msgstr "" | ||||||
| msgid "You must have maintainer access to force delete a lock" | msgid "You must have maintainer access to force delete a lock" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "You must have permission to create a project in a group before forking." | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "You must have permission to create a project in a namespace before forking." | msgid "You must have permission to create a project in a namespace before forking." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ | ||||||
|     "@babel/plugin-syntax-import-meta": "^7.10.1", |     "@babel/plugin-syntax-import-meta": "^7.10.1", | ||||||
|     "@babel/preset-env": "^7.10.1", |     "@babel/preset-env": "^7.10.1", | ||||||
|     "@gitlab/at.js": "1.5.5", |     "@gitlab/at.js": "1.5.5", | ||||||
|     "@gitlab/svgs": "1.146.0", |     "@gitlab/svgs": "1.147.0", | ||||||
|     "@gitlab/ui": "17.10.1", |     "@gitlab/ui": "17.10.1", | ||||||
|     "@gitlab/visual-review-tools": "1.6.1", |     "@gitlab/visual-review-tools": "1.6.1", | ||||||
|     "@rails/actioncable": "^6.0.3-1", |     "@rails/actioncable": "^6.0.3-1", | ||||||
|  |  | ||||||
|  | @ -83,13 +83,13 @@ module QA | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def api_get_from(get_path) |       def api_get_from(get_path) | ||||||
|         url = Runtime::API::Request.new(api_client, get_path).url |         request = Runtime::API::Request.new(api_client, get_path) | ||||||
|         response = get(url) |         response = get(request.url) | ||||||
| 
 | 
 | ||||||
|         if response.code == HTTP_STATUS_SERVER_ERROR |         if response.code == HTTP_STATUS_SERVER_ERROR | ||||||
|           raise InternalServerError, "Failed to GET #{url} - (#{response.code}): `#{response}`." |           raise InternalServerError, "Failed to GET #{request.mask_url} - (#{response.code}): `#{response}`." | ||||||
|         elsif response.code != HTTP_STATUS_OK |         elsif response.code != HTTP_STATUS_OK | ||||||
|           raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`." |           raise ResourceNotFoundError, "Resource at #{request.mask_url} could not be found (#{response.code}): `#{response}`." | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         response |         response | ||||||
|  | @ -108,11 +108,11 @@ module QA | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def api_delete |       def api_delete | ||||||
|         url = Runtime::API::Request.new(api_client, api_delete_path).url |         request = Runtime::API::Request.new(api_client, api_delete_path) | ||||||
|         response = delete(url) |         response = delete(request.url) | ||||||
| 
 | 
 | ||||||
|         unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED].include? response.code |         unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED].include? response.code | ||||||
|           raise ResourceNotDeletedError, "Resource at #{url} could not be deleted (#{response.code}): `#{response}`." |           raise ResourceNotDeletedError, "Resource at #{request.mask_url} could not be deleted (#{response.code}): `#{response}`." | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         response |         response | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| module QA | module QA | ||||||
|   context 'Plan', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225409', type: :bug } do |   context 'Plan' do | ||||||
|     describe 'Jira issue import', :jira, :orchestrated, :requires_admin do |     describe 'Jira issue import', :jira, :orchestrated, :requires_admin do | ||||||
|       let(:jira_project_key) { "JITD" } |       let(:jira_project_key) { "JITD" } | ||||||
|       let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" } |       let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" } | ||||||
|  |  | ||||||
|  | @ -22,6 +22,12 @@ describe QA::Runtime::API::Request do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   describe '#mask_url' do | ||||||
|  |     it 'returns the full API request url with the token masked' do | ||||||
|  |       expect(request.mask_url).to eq 'http://example.com/api/v4/users?private_token=[****]' | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   describe '#request_path' do |   describe '#request_path' do | ||||||
|     it 'prepends the api path' do |     it 'prepends the api path' do | ||||||
|       expect(request.request_path('/users')).to eq '/api/v4/users' |       expect(request.request_path('/users')).to eq '/api/v4/users' | ||||||
|  |  | ||||||
|  | @ -7,8 +7,6 @@ module RuboCop | ||||||
|         MSG = 'Add an `authorize :ability` call to the type: '\ |         MSG = 'Add an `authorize :ability` call to the type: '\ | ||||||
|               'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization' |               'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization' | ||||||
| 
 | 
 | ||||||
|         TYPES_DIR = 'app/graphql/types' |  | ||||||
| 
 |  | ||||||
|         # We want to exclude our own basetypes and scalars |         # We want to exclude our own basetypes and scalars | ||||||
|         WHITELISTED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType |         WHITELISTED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType | ||||||
|                                QueryType GraphQL::Schema BaseUnion].freeze |                                QueryType GraphQL::Schema BaseUnion].freeze | ||||||
|  | @ -18,7 +16,6 @@ module RuboCop | ||||||
|         PATTERN |         PATTERN | ||||||
| 
 | 
 | ||||||
|         def on_class(node) |         def on_class(node) | ||||||
|           return unless in_type?(node) |  | ||||||
|           return if whitelisted?(class_constant(node)) |           return if whitelisted?(class_constant(node)) | ||||||
|           return if whitelisted?(superclass_constant(node)) |           return if whitelisted?(superclass_constant(node)) | ||||||
| 
 | 
 | ||||||
|  | @ -27,12 +24,6 @@ module RuboCop | ||||||
| 
 | 
 | ||||||
|         private |         private | ||||||
| 
 | 
 | ||||||
|         def in_type?(node) |  | ||||||
|           path = node.location.expression.source_buffer.name |  | ||||||
| 
 |  | ||||||
|           path.include? TYPES_DIR |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def whitelisted?(class_node) |         def whitelisted?(class_node) | ||||||
|           class_const = class_node&.const_name |           class_const = class_node&.const_name | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { mount } from '@vue/test-utils'; | import { mount } from '@vue/test-utils'; | ||||||
| import CollpasibleSection from '~/jobs/components/log/collapsible_section.vue'; | import CollapsibleSection from '~/jobs/components/log/collapsible_section.vue'; | ||||||
| import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data'; | import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data'; | ||||||
| 
 | 
 | ||||||
| describe('Job Log Collapsible Section', () => { | describe('Job Log Collapsible Section', () => { | ||||||
|  | @ -11,7 +11,7 @@ describe('Job Log Collapsible Section', () => { | ||||||
|   const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg'); |   const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg'); | ||||||
| 
 | 
 | ||||||
|   const createComponent = (props = {}) => { |   const createComponent = (props = {}) => { | ||||||
|     wrapper = mount(CollpasibleSection, { |     wrapper = mount(CollapsibleSection, { | ||||||
|       propsData: { |       propsData: { | ||||||
|         ...props, |         ...props, | ||||||
|       }, |       }, | ||||||
|  |  | ||||||
|  | @ -181,7 +181,7 @@ describe('Jobs Store Utils', () => { | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('collpasible section', () => { |     describe('collapsible section', () => { | ||||||
|       it('adds a `isClosed` property', () => { |       it('adds a `isClosed` property', () => { | ||||||
|         expect(result[1].isClosed).toEqual(false); |         expect(result[1].isClosed).toEqual(false); | ||||||
|       }); |       }); | ||||||
|  | @ -190,7 +190,7 @@ describe('Jobs Store Utils', () => { | ||||||
|         expect(result[1].isHeader).toEqual(true); |         expect(result[1].isHeader).toEqual(true); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('creates a lines array property with the content of the collpasible section', () => { |       it('creates a lines array property with the content of the collapsible section', () => { | ||||||
|         expect(result[1].lines.length).toEqual(2); |         expect(result[1].lines.length).toEqual(2); | ||||||
|         expect(result[1].lines[0].content).toEqual(utilsMockData[2].content); |         expect(result[1].lines[0].content).toEqual(utilsMockData[2].content); | ||||||
|         expect(result[1].lines[1].content).toEqual(utilsMockData[3].content); |         expect(result[1].lines[1].content).toEqual(utilsMockData[3].content); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,78 @@ | ||||||
|  | import { shallowMount } from '@vue/test-utils'; | ||||||
|  | import { GlBadge, GlButton, GlLink } from '@gitlab/ui'; | ||||||
|  | import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue'; | ||||||
|  | 
 | ||||||
|  | describe('Fork groups list item component', () => { | ||||||
|  |   let wrapper; | ||||||
|  | 
 | ||||||
|  |   const DEFAULT_PROPS = { | ||||||
|  |     hasReachedProjectLimit: false, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const DEFAULT_GROUP_DATA = { | ||||||
|  |     id: 22, | ||||||
|  |     name: 'Gitlab Org', | ||||||
|  |     description: 'Ad et ipsam earum id aut nobis.', | ||||||
|  |     visibility: 'public', | ||||||
|  |     full_name: 'Gitlab Org', | ||||||
|  |     created_at: '2020-06-22T03:32:05.664Z', | ||||||
|  |     updated_at: '2020-06-22T03:32:05.664Z', | ||||||
|  |     avatar_url: null, | ||||||
|  |     fork_path: '/twitter/typeahead-js/-/forks?namespace_key=22', | ||||||
|  |     forked_project_path: null, | ||||||
|  |     permission: 'Owner', | ||||||
|  |     relative_path: '/gitlab-org', | ||||||
|  |     markdown_description: | ||||||
|  |       '<p data-sourcepos="1:1-1:31" dir="auto">Ad et ipsam earum id aut nobis.</p>', | ||||||
|  |     can_create_project: true, | ||||||
|  |     marked_for_deletion: false, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const DUMMY_PATH = '/dummy/path'; | ||||||
|  | 
 | ||||||
|  |   const createWrapper = propsData => { | ||||||
|  |     wrapper = shallowMount(ForkGroupsListItem, { | ||||||
|  |       propsData: { | ||||||
|  |         ...DEFAULT_PROPS, | ||||||
|  |         ...propsData, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   it('renders pending removal badge if applicable', () => { | ||||||
|  |     createWrapper({ group: { ...DEFAULT_GROUP_DATA, marked_for_deletion: true } }); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.find(GlBadge).text()).toBe('pending removal'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('renders go to fork button if has forked project', () => { | ||||||
|  |     createWrapper({ group: { ...DEFAULT_GROUP_DATA, forked_project_path: DUMMY_PATH } }); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.find(GlButton).text()).toBe('Go to fork'); | ||||||
|  |     expect(wrapper.find(GlButton).attributes().href).toBe(DUMMY_PATH); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('renders select button if has no forked project', () => { | ||||||
|  |     createWrapper({ | ||||||
|  |       group: { ...DEFAULT_GROUP_DATA, forked_project_path: null, fork_path: DUMMY_PATH }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.find(GlButton).text()).toBe('Select'); | ||||||
|  |     expect(wrapper.find('form').attributes().action).toBe(DUMMY_PATH); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('renders link to current group', () => { | ||||||
|  |     const DUMMY_FULL_NAME = 'dummy'; | ||||||
|  |     createWrapper({ | ||||||
|  |       group: { ...DEFAULT_GROUP_DATA, relative_path: DUMMY_PATH, full_name: DUMMY_FULL_NAME }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     expect( | ||||||
|  |       wrapper | ||||||
|  |         .findAll(GlLink) | ||||||
|  |         .filter(w => w.text() === DUMMY_FULL_NAME) | ||||||
|  |         .at(0) | ||||||
|  |         .attributes().href, | ||||||
|  |     ).toBe(DUMMY_PATH); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,133 @@ | ||||||
|  | import AxiosMockAdapter from 'axios-mock-adapter'; | ||||||
|  | import axios from '~/lib/utils/axios_utils'; | ||||||
|  | import { shallowMount } from '@vue/test-utils'; | ||||||
|  | import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui'; | ||||||
|  | import { nextTick } from 'vue'; | ||||||
|  | import createFlash from '~/flash'; | ||||||
|  | import ForkGroupsList from '~/pages/projects/forks/new/components/fork_groups_list.vue'; | ||||||
|  | import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue'; | ||||||
|  | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
|  | 
 | ||||||
|  | jest.mock('~/flash', () => jest.fn()); | ||||||
|  | 
 | ||||||
|  | describe('Fork groups list component', () => { | ||||||
|  |   let wrapper; | ||||||
|  |   let axiosMock; | ||||||
|  | 
 | ||||||
|  |   const DEFAULT_PROPS = { | ||||||
|  |     endpoint: '/dummy', | ||||||
|  |     hasReachedProjectLimit: false, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args); | ||||||
|  | 
 | ||||||
|  |   const createWrapper = propsData => { | ||||||
|  |     wrapper = shallowMount(ForkGroupsList, { | ||||||
|  |       propsData: { | ||||||
|  |         ...DEFAULT_PROPS, | ||||||
|  |         ...propsData, | ||||||
|  |       }, | ||||||
|  |       stubs: { | ||||||
|  |         GlTabs: { | ||||||
|  |           template: '<div><slot></slot><slot name="tabs-end"></slot></div>', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   beforeEach(() => { | ||||||
|  |     axiosMock = new AxiosMockAdapter(axios); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   afterEach(() => { | ||||||
|  |     axiosMock.reset(); | ||||||
|  | 
 | ||||||
|  |     if (wrapper) { | ||||||
|  |       wrapper.destroy(); | ||||||
|  |       wrapper = null; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('fires load groups request on mount', async () => { | ||||||
|  |     replyWith(200, { namespaces: [] }); | ||||||
|  |     createWrapper(); | ||||||
|  | 
 | ||||||
|  |     await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |     expect(axiosMock.history.get[0].url).toBe(DEFAULT_PROPS.endpoint); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('displays flash if loading groups fails', async () => { | ||||||
|  |     replyWith(500); | ||||||
|  |     createWrapper(); | ||||||
|  | 
 | ||||||
|  |     await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |     expect(createFlash).toHaveBeenCalled(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('displays loading indicator while loading groups', () => { | ||||||
|  |     replyWith(() => new Promise(() => {})); | ||||||
|  |     createWrapper(); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.contains(GlLoadingIcon)).toBe(true); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('displays empty text if no groups are available', async () => { | ||||||
|  |     const EMPTY_TEXT = 'No available groups to fork the project.'; | ||||||
|  |     replyWith(200, { namespaces: [] }); | ||||||
|  |     createWrapper(); | ||||||
|  | 
 | ||||||
|  |     await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.text()).toContain(EMPTY_TEXT); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('displays filter field when groups are available', async () => { | ||||||
|  |     replyWith(200, { namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }] }); | ||||||
|  |     createWrapper(); | ||||||
|  | 
 | ||||||
|  |     await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.contains(GlSearchBoxByType)).toBe(true); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('renders list items for each available group', async () => { | ||||||
|  |     const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }]; | ||||||
|  |     const hasReachedProjectLimit = true; | ||||||
|  | 
 | ||||||
|  |     replyWith(200, { namespaces }); | ||||||
|  |     createWrapper({ hasReachedProjectLimit }); | ||||||
|  | 
 | ||||||
|  |     await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(namespaces.length); | ||||||
|  | 
 | ||||||
|  |     namespaces.forEach((namespace, idx) => { | ||||||
|  |       expect( | ||||||
|  |         wrapper | ||||||
|  |           .findAll(ForkGroupsListItem) | ||||||
|  |           .at(idx) | ||||||
|  |           .props(), | ||||||
|  |       ).toStrictEqual({ group: namespace, hasReachedProjectLimit }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('filters repositories on the fly', async () => { | ||||||
|  |     replyWith(200, { | ||||||
|  |       namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }], | ||||||
|  |     }); | ||||||
|  |     createWrapper(); | ||||||
|  |     await waitForPromises(); | ||||||
|  |     wrapper.find(GlSearchBoxByType).vm.$emit('input', 'other'); | ||||||
|  |     await nextTick(); | ||||||
|  | 
 | ||||||
|  |     expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(1); | ||||||
|  |     expect( | ||||||
|  |       wrapper | ||||||
|  |         .findAll(ForkGroupsListItem) | ||||||
|  |         .at(0) | ||||||
|  |         .props().group.name, | ||||||
|  |     ).toBe('otherdummy'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -22,6 +22,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do | ||||||
|       context 'tracking node metrics' do |       context 'tracking node metrics' do | ||||||
|         it 'contains node level metrics for each instance' do |         it 'contains node level metrics for each instance' do | ||||||
|           expect_prometheus_api_to( |           expect_prometheus_api_to( | ||||||
|  |             receive_app_request_volume_query, | ||||||
|             receive_node_memory_query, |             receive_node_memory_query, | ||||||
|             receive_node_cpu_count_query, |             receive_node_cpu_count_query, | ||||||
|             receive_node_service_memory_rss_query, |             receive_node_service_memory_rss_query, | ||||||
|  | @ -32,6 +33,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do | ||||||
| 
 | 
 | ||||||
|           expect(subject[:topology]).to eq({ |           expect(subject[:topology]).to eq({ | ||||||
|             duration_s: 0, |             duration_s: 0, | ||||||
|  |             application_requests_per_hour: 36, | ||||||
|             nodes: [ |             nodes: [ | ||||||
|               { |               { | ||||||
|                 node_memory_total_bytes: 512, |                 node_memory_total_bytes: 512, | ||||||
|  | @ -76,6 +78,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do | ||||||
|       context 'and some node memory metrics are missing' do |       context 'and some node memory metrics are missing' do | ||||||
|         it 'removes the respective entries' do |         it 'removes the respective entries' do | ||||||
|           expect_prometheus_api_to( |           expect_prometheus_api_to( | ||||||
|  |             receive_app_request_volume_query(result: []), | ||||||
|             receive_node_memory_query(result: []), |             receive_node_memory_query(result: []), | ||||||
|             receive_node_cpu_count_query, |             receive_node_cpu_count_query, | ||||||
|             receive_node_service_memory_rss_query(result: []), |             receive_node_service_memory_rss_query(result: []), | ||||||
|  | @ -149,6 +152,17 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def receive_app_request_volume_query(result: nil) | ||||||
|  |     receive(:query) | ||||||
|  |       .with(/gitlab_usage_ping:ops:rate/) | ||||||
|  |       .and_return(result || [ | ||||||
|  |         { | ||||||
|  |           'metric' => { 'component' => 'http_requests', 'service' => 'workhorse' }, | ||||||
|  |           'value' => [1000, '0.01'] | ||||||
|  |         } | ||||||
|  |       ]) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def receive_node_memory_query(result: nil) |   def receive_node_memory_query(result: nil) | ||||||
|     receive(:query) |     receive(:query) | ||||||
|       .with(/node_memory_total_bytes/, an_instance_of(Hash)) |       .with(/node_memory_total_bytes/, an_instance_of(Hash)) | ||||||
|  |  | ||||||
|  | @ -189,6 +189,26 @@ RSpec.describe ProjectStatistics do | ||||||
|           statistics.refresh! |           statistics.refresh! | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  | 
 | ||||||
|  |       context 'when snippets_size is updated' do | ||||||
|  |         it 'schedules the aggregation worker' do | ||||||
|  |           expect(Namespaces::ScheduleAggregationWorker) | ||||||
|  |             .to receive(:perform_async) | ||||||
|  | 
 | ||||||
|  |           statistics.refresh!(only: [:snippets_size]) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when feature flag :namespace_snippets_size_stat is disabled' do | ||||||
|  |           it 'does not schedules an aggregation worker' do | ||||||
|  |             stub_feature_flags(namespace_snippets_size_stat: false) | ||||||
|  | 
 | ||||||
|  |             expect(Namespaces::ScheduleAggregationWorker) | ||||||
|  |               .not_to receive(:perform_async) | ||||||
|  | 
 | ||||||
|  |             statistics.refresh!(only: [:snippets_size]) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'when the column is not namespace relatable' do |     context 'when the column is not namespace relatable' do | ||||||
|  |  | ||||||
|  | @ -4766,6 +4766,12 @@ RSpec.describe User do | ||||||
|         end.to change { User.where(user_type: bot_type).count }.by(1) |         end.to change { User.where(user_type: bot_type).count }.by(1) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       it 'creates a route for the namespace of the created user' do | ||||||
|  |         bot_user = described_class.public_send(bot_type) | ||||||
|  | 
 | ||||||
|  |         expect(bot_user.namespace.route).to be_present | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       it 'does not create a new user if it already exists' do |       it 'does not create a new user if it already exists' do | ||||||
|         described_class.public_send(bot_type) |         described_class.public_send(bot_type) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,83 +10,60 @@ RSpec.describe RuboCop::Cop::Graphql::AuthorizeTypes, type: :rubocop do | ||||||
| 
 | 
 | ||||||
|   subject(:cop) { described_class.new } |   subject(:cop) { described_class.new } | ||||||
| 
 | 
 | ||||||
|   context 'when NOT in a type folder' do |   it 'adds an offense when there is no authorize call' do | ||||||
|     before do |     inspect_source(<<~TYPE) | ||||||
|       allow(cop).to receive(:in_type?).and_return(false) |       module Types | ||||||
|     end |         class AType < BaseObject | ||||||
| 
 |           field :a_thing | ||||||
|     it 'does not add an offense even though there is no authorize call' do |           field :another_thing | ||||||
|       expect_no_offenses(<<~TYPE.strip) |  | ||||||
|         module Types |  | ||||||
|           class AType < BaseObject |  | ||||||
|             field :a_thing |  | ||||||
|             field :another_thing |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|       TYPE |       end | ||||||
|     end |     TYPE | ||||||
|  | 
 | ||||||
|  |     expect(cop.offenses.size).to eq 1 | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   context 'when in a type folder' do |   it 'does not add an offense for classes that have an authorize call' do | ||||||
|     before do |     expect_no_offenses(<<~TYPE.strip) | ||||||
|       allow(cop).to receive(:in_type?).and_return(true) |       module Types | ||||||
|     end |         class AType < BaseObject | ||||||
|  |           graphql_name 'ATypeName' | ||||||
| 
 | 
 | ||||||
|     it 'adds an offense when there is no authorize call' do |           authorize :an_ability, :second_ability | ||||||
|       inspect_source(<<~TYPE) | 
 | ||||||
|         module Types |           field :a_thing | ||||||
|           class AType < BaseObject |  | ||||||
|             field :a_thing |  | ||||||
|             field :another_thing |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|       TYPE |       end | ||||||
|  |     TYPE | ||||||
|  |   end | ||||||
| 
 | 
 | ||||||
|       expect(cop.offenses.size).to eq 1 |   it 'does not add an offense for classes that only have an authorize call' do | ||||||
|     end |     expect_no_offenses(<<~TYPE.strip) | ||||||
| 
 |       module Types | ||||||
|     it 'does not add an offense for classes that have an authorize call' do |         class AType < SuperClassWithFields | ||||||
|       expect_no_offenses(<<~TYPE.strip) |           authorize :an_ability | ||||||
|         module Types |  | ||||||
|           class AType < BaseObject |  | ||||||
|             graphql_name 'ATypeName' |  | ||||||
| 
 |  | ||||||
|             authorize :an_ability, :second_ability |  | ||||||
| 
 |  | ||||||
|             field :a_thing |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|       TYPE |       end | ||||||
|     end |     TYPE | ||||||
|  |   end | ||||||
| 
 | 
 | ||||||
|     it 'does not add an offense for classes that only have an authorize call' do |   it 'does not add an offense for base types' do | ||||||
|       expect_no_offenses(<<~TYPE.strip) |     expect_no_offenses(<<~TYPE) | ||||||
|         module Types |       module Types | ||||||
|           class AType < SuperClassWithFields |         class AType < BaseEnum | ||||||
|             authorize :an_ability |           field :a_thing | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|       TYPE |       end | ||||||
|     end |     TYPE | ||||||
|  |   end | ||||||
| 
 | 
 | ||||||
|     it 'does not add an offense for base types' do |   it 'does not add an offense for Enums' do | ||||||
|       expect_no_offenses(<<~TYPE) |     expect_no_offenses(<<~TYPE) | ||||||
|         module Types |       module Types | ||||||
|           class AType < BaseEnum |         class ATypeEnum < AnotherEnum | ||||||
|             field :a_thing |           field :a_thing | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|       TYPE |       end | ||||||
|     end |     TYPE | ||||||
| 
 |  | ||||||
|     it 'does not add an offense for Enums' do |  | ||||||
|       expect_no_offenses(<<~TYPE) |  | ||||||
|         module Types |  | ||||||
|           class ATypeEnum < AnotherEnum |  | ||||||
|             field :a_thing |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       TYPE |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -843,10 +843,10 @@ | ||||||
|     eslint-plugin-vue "^6.2.1" |     eslint-plugin-vue "^6.2.1" | ||||||
|     vue-eslint-parser "^7.0.0" |     vue-eslint-parser "^7.0.0" | ||||||
| 
 | 
 | ||||||
| "@gitlab/svgs@1.146.0": | "@gitlab/svgs@1.147.0": | ||||||
|   version "1.146.0" |   version "1.147.0" | ||||||
|   resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.146.0.tgz#c74118a3f1ab47ae77211d42597f553f395deb5d" |   resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.147.0.tgz#1b2cc986cb3219609136cab641e2c384d724700f" | ||||||
|   integrity sha512-2/k9pAZPgHpZ5Ad0fz9i1109sWcShDE4XcjrjzltNNksbi86lqCKbsSe580ujtlG8KShgGMkDkmUa6AHZi64Xw== |   integrity sha512-KnjN7ms7bEPajYl7q0nKv7HMKtqR/JxCVSBRGXH5ezkeGKy4wb4yEYtvRK8no7ix+Iw4rc0KTqOwKp9nkl/KdA== | ||||||
| 
 | 
 | ||||||
| "@gitlab/ui@17.10.1": | "@gitlab/ui@17.10.1": | ||||||
|   version "17.10.1" |   version "17.10.1" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue