diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-02 12:09:02 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-02 12:09:02 +0000 |
commit | b0107e8756bf3287f8a6221252c800209a9c46f6 (patch) | |
tree | f9bd9f37b44a946deaa39fab82085538a5e12105 | |
parent | 6aab18704a534ea04f7477146aaa02104e738fcb (diff) | |
download | gitlab-ce-b0107e8756bf3287f8a6221252c800209a9c46f6.tar.gz |
Add latest changes from gitlab-org/gitlab@master
37 files changed, 938 insertions, 124 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 438f27b0c87..bff2b7a32b1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -328,6 +328,9 @@ Cop/SidekiqOptionsQueue: Graphql/AuthorizeTypes: Enabled: true + Include: + - 'app/graphql/types/**/*' + - 'ee/app/graphql/types/**/*' Exclude: - 'spec/**/*.rb' - 'ee/spec/**/*.rb' diff --git a/app/assets/javascripts/jobs/components/log/collapsible_section.vue b/app/assets/javascripts/jobs/components/log/collapsible_section.vue index 0c7b78a3da7..55cdfb691f4 100644 --- a/app/assets/javascripts/jobs/components/log/collapsible_section.vue +++ b/app/assets/javascripts/jobs/components/log/collapsible_section.vue @@ -3,7 +3,7 @@ import LogLine from './line.vue'; import LogLineHeader from './line_header.vue'; export default { - name: 'CollpasibleLogSection', + name: 'CollapsibleLogSection', components: { LogLine, LogLineHeader, diff --git a/app/assets/javascripts/jobs/components/log/log.vue b/app/assets/javascripts/jobs/components/log/log.vue index f0bdbde0602..0134e5dafe8 100644 --- a/app/assets/javascripts/jobs/components/log/log.vue +++ b/app/assets/javascripts/jobs/components/log/log.vue @@ -1,11 +1,11 @@ <script> import { mapState, mapActions } from 'vuex'; -import CollpasibleLogSection from './collapsible_section.vue'; +import CollapsibleLogSection from './collapsible_section.vue'; import LogLine from './line.vue'; export default { components: { - CollpasibleLogSection, + CollapsibleLogSection, LogLine, }, computed: { @@ -51,7 +51,7 @@ export default { <template> <code class="job-log d-block" data-qa-selector="job_log_content"> <template v-for="(section, index) in trace"> - <collpasible-log-section + <collapsible-log-section v-if="section.isHeader" :key="`collapsible-${index}`" :section="section" diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index 0ce8dfe4442..4bd8d6f58a6 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -195,7 +195,7 @@ export const receiveTraceError = ({ dispatch }) => { 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 * * @param {Object} section diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js index 3b6b8a2c851..8d6e5aac566 100644 --- a/app/assets/javascripts/jobs/store/utils.js +++ b/app/assets/javascripts/jobs/store/utils.js @@ -11,7 +11,7 @@ export const parseLine = (line = {}, lineNumber) => ({ /** * When a line has `section_header` set to true, we create a new * structure to allow to nest the lines that belong to the - * collpasible section + * collapsible section * * @param Object line * @param Number lineNumber @@ -91,7 +91,7 @@ export const getIncrementalLineNumber = acc => { * Parses the job log content into a structure usable by the template * * 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 isHeader property to handle template logic * - adds the section_duration diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue new file mode 100644 index 00000000000..77753521342 --- /dev/null +++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue @@ -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> diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue new file mode 100644 index 00000000000..792c2f3db34 --- /dev/null +++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue @@ -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> diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 129d0fbb2c0..c70ce9bebcc 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -17,11 +17,8 @@ module Routable after_validation :set_path_errors - before_validation do - if full_path_changed? || full_name_changed? - prepare_route - end - end + before_validation :prepare_route + before_save :prepare_route # in case validation is skipped end class_methods do @@ -118,6 +115,8 @@ module Routable end def prepare_route + return unless full_path_changed? || full_name_changed? + route || build_route(source: self) route.path = build_full_path route.name = build_full_name diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb index 1d8e72c902a..5424abd069f 100644 --- a/app/models/project_statistics.rb +++ b/app/models/project_statistics.rb @@ -14,6 +14,7 @@ class ProjectStatistics < ApplicationRecord 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 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) } @@ -31,7 +32,7 @@ class ProjectStatistics < ApplicationRecord 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 end @@ -110,6 +111,10 @@ class ProjectStatistics < ApplicationRecord Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id) end end + + def namespace_relatable_columns + Feature.enabled?(:namespace_snippets_size_stat) ? FLAGGED_NAMESPACE_RELATABLE_COLUMNS : NAMESPACE_RELATABLE_COLUMNS + end end ProjectStatistics.prepend_if_ee('EE::ProjectStatistics') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index e3ef825a770..84c80335754 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1527,7 +1527,7 @@ - :name: project_update_repository_storage :feature_category: :gitaly :has_external_dependencies: - :urgency: :low + :urgency: :throttled :resource_boundary: :unknown :weight: 1 :idempotent: true diff --git a/app/workers/project_update_repository_storage_worker.rb b/app/workers/project_update_repository_storage_worker.rb index 4c44454fdcd..7c0b1ae07fa 100644 --- a/app/workers/project_update_repository_storage_worker.rb +++ b/app/workers/project_update_repository_storage_worker.rb @@ -5,6 +5,7 @@ class ProjectUpdateRepositoryStorageWorker idempotent! feature_category :gitaly + urgency :throttled def perform(project_id, new_repository_storage_key, repository_storage_move_id = nil) repository_storage_move = diff --git a/changelogs/unreleased/an-throttle-project-update-repository-storage-worker.yml b/changelogs/unreleased/an-throttle-project-update-repository-storage-worker.yml new file mode 100644 index 00000000000..f06c1e97b9e --- /dev/null +++ b/changelogs/unreleased/an-throttle-project-update-repository-storage-worker.yml @@ -0,0 +1,5 @@ +--- +title: Throttle ProjectUpdateRepositoryStorageWorker Jobs +merge_request: 35230 +author: +type: other diff --git a/changelogs/unreleased/fix-routes-for-internal-users.yml b/changelogs/unreleased/fix-routes-for-internal-users.yml new file mode 100644 index 00000000000..5f96dd7f227 --- /dev/null +++ b/changelogs/unreleased/fix-routes-for-internal-users.yml @@ -0,0 +1,5 @@ +--- +title: Create associated routes when a new bot user is created +merge_request: 35711 +author: +type: fixed diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb index cb6dbb7504d..536d9f9e2ba 100644 --- a/db/fixtures/development/24_forks.rb +++ b/db/fixtures/development/24_forks.rb @@ -10,17 +10,30 @@ Sidekiq::Testing.inline! do # we use randomized approach (e.g. `Array#sample`). return unless source_project - fork_project = Projects::ForkService.new( - source_project, - user, - namespace: user.namespace, - skip_disk_validation: true - ).execute + Sidekiq::Worker.skipping_transaction_check do + fork_project = Projects::ForkService.new( + source_project, + user, + namespace: user.namespace, + skip_disk_validation: true + ).execute - if fork_project.valid? - print '.' - else - print 'F' + # Seed-Fu runs this entire fixture in a transaction, so the `after_commit` + # hook won't run until after the fixture is loaded. That is too late + # since the Sidekiq::Testing block has already exited. Force clearing + # 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 diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 12b025e25b6..2429a0da8c6 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -11311,6 +11311,56 @@ type RunDASTScanPayload { } """ +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 """ type SecurityReportSummary { @@ -11350,6 +11400,31 @@ Represents a section of a summary of a security report """ 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 """ scannedResourcesCount: Int diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index d91f0e74c2b..758be106669 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -33219,6 +33219,159 @@ }, { "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", "name": "SecurityReportSummary", "description": "Represents summary of a security report", "fields": [ @@ -33320,6 +33473,59 @@ "description": "Represents a section of a summary of a security report", "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", "description": "Total number of scanned resources", "args": [ diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 25d2ef9010f..5e1eafa0645 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1637,6 +1637,15 @@ Autogenerated return type of RunDASTScan | `errors` | String! => Array | Errors encountered during execution of the mutation. | | `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 Represents summary of a security report diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md index ebf711e105b..bab81629221 100644 --- a/doc/customization/issue_and_merge_request_template.md +++ b/doc/customization/issue_and_merge_request_template.md @@ -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). diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md index 18e0f9e50fe..105bd915681 100644 --- a/doc/development/telemetry/usage_ping.md +++ b/doc/development/telemetry/usage_ping.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 | | `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 | -| `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 | +| `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_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 | @@ -873,6 +874,8 @@ The following is example content of the Usage Ping payload. } }, "topology": { + "duration_s": 0.013836685999194742, + "application_requests_per_hour": 4224, "nodes": [ { "node_memory_total_bytes": 33269903360, @@ -897,8 +900,7 @@ The following is example content of the Usage Ping payload. ... }, ... - ], - "duration_s": 0.013836685999194742 + ] } } ``` diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index 0f90c321a14..aa5987bf5f9 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -81,7 +81,7 @@ changes you made after picking the template and return it to its initial status. ![Description templates](img/description_templates.png) -## 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. > - Templates for issues were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28) in GitLab EE 8.1. diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb index 53cbd5b21ea..d652719721e 100644 --- a/lib/gitlab/seeder.rb +++ b/lib/gitlab/seeder.rb @@ -18,6 +18,7 @@ module Gitlab MASS_INSERT_PROJECT_START = 'mass_insert_project_' MASS_INSERT_USER_START = 'mass_insert_user_' + REPORTED_USER_START = 'reported_user_' ESTIMATED_INSERT_PER_MINUTE = 2_000_000 MASS_INSERT_ENV = 'MASS_INSERT' @@ -36,7 +37,7 @@ module Gitlab included 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 diff --git a/lib/gitlab/usage_data_concerns/topology.rb b/lib/gitlab/usage_data_concerns/topology.rb index e05272d665f..d10fd56b5a2 100644 --- a/lib/gitlab/usage_data_concerns/topology.rb +++ b/lib/gitlab/usage_data_concerns/topology.rb @@ -28,11 +28,20 @@ module Gitlab def topology_fetch_all_data with_prometheus_client(fallback: {}) do |client| { + application_requests_per_hour: topology_app_requests_per_hour(client), nodes: topology_node_data(client) - } + }.compact 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) # node-level data by_instance_mem = topology_node_memory(client) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 28f17414d15..5d081cc1266 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10964,6 +10964,9 @@ msgstr "" msgid "Go to find file" msgstr "" +msgid "Go to fork" +msgstr "" + msgid "Go to issue boards" msgstr "" @@ -11531,6 +11534,9 @@ msgstr "" msgid "Groups and projects" msgstr "" +msgid "Groups and subgroups" +msgstr "" + msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}." msgstr "" @@ -15131,6 +15137,9 @@ msgstr "" msgid "No authentication methods configured." msgstr "" +msgid "No available groups to fork the project." +msgstr "" + msgid "No available namespaces to fork the project." msgstr "" @@ -19831,6 +19840,9 @@ msgstr "" msgid "Search by author" msgstr "" +msgid "Search by name" +msgstr "" + msgid "Search files" msgstr "" @@ -22979,6 +22991,9 @@ msgstr "" msgid "There was a problem communicating with your device." msgstr "" +msgid "There was a problem fetching groups." +msgstr "" + msgid "There was a problem fetching project branches." msgstr "" @@ -26256,6 +26271,9 @@ msgstr "" msgid "You must have maintainer access to force delete a lock" 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." msgstr "" diff --git a/package.json b/package.json index 1d7471a1e05..680c850bd9d 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@babel/plugin-syntax-import-meta": "^7.10.1", "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.5", - "@gitlab/svgs": "1.146.0", + "@gitlab/svgs": "1.147.0", "@gitlab/ui": "17.10.1", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-1", diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index 591aa449219..babdfc96265 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -83,13 +83,13 @@ module QA end def api_get_from(get_path) - url = Runtime::API::Request.new(api_client, get_path).url - response = get(url) + request = Runtime::API::Request.new(api_client, get_path) + response = get(request.url) 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 - 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 response @@ -108,11 +108,11 @@ module QA end def api_delete - url = Runtime::API::Request.new(api_client, api_delete_path).url - response = delete(url) + request = Runtime::API::Request.new(api_client, api_delete_path) + response = delete(request.url) 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 response diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb index af09899100a..ba8e8635c87 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true 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 let(:jira_project_key) { "JITD" } let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" } diff --git a/qa/spec/runtime/api/request_spec.rb b/qa/spec/runtime/api/request_spec.rb index a20f1cf8559..8354eff6234 100644 --- a/qa/spec/runtime/api/request_spec.rb +++ b/qa/spec/runtime/api/request_spec.rb @@ -22,6 +22,12 @@ describe QA::Runtime::API::Request do 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 it 'prepends the api path' do expect(request.request_path('/users')).to eq '/api/v4/users' diff --git a/rubocop/cop/graphql/authorize_types.rb b/rubocop/cop/graphql/authorize_types.rb index 7aaa9299362..c6dbe447b4a 100644 --- a/rubocop/cop/graphql/authorize_types.rb +++ b/rubocop/cop/graphql/authorize_types.rb @@ -7,8 +7,6 @@ module RuboCop MSG = 'Add an `authorize :ability` call to the type: '\ '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 WHITELISTED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType QueryType GraphQL::Schema BaseUnion].freeze @@ -18,7 +16,6 @@ module RuboCop PATTERN def on_class(node) - return unless in_type?(node) return if whitelisted?(class_constant(node)) return if whitelisted?(superclass_constant(node)) @@ -27,12 +24,6 @@ module RuboCop private - def in_type?(node) - path = node.location.expression.source_buffer.name - - path.include? TYPES_DIR - end - def whitelisted?(class_node) class_const = class_node&.const_name diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js index 3a16521a986..bf2f8c05806 100644 --- a/spec/frontend/jobs/components/log/collapsible_section_spec.js +++ b/spec/frontend/jobs/components/log/collapsible_section_spec.js @@ -1,5 +1,5 @@ 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'; describe('Job Log Collapsible Section', () => { @@ -11,7 +11,7 @@ describe('Job Log Collapsible Section', () => { const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg'); const createComponent = (props = {}) => { - wrapper = mount(CollpasibleSection, { + wrapper = mount(CollapsibleSection, { propsData: { ...props, }, diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js index 8819f39dee0..294f88bbc74 100644 --- a/spec/frontend/jobs/store/utils_spec.js +++ b/spec/frontend/jobs/store/utils_spec.js @@ -181,7 +181,7 @@ describe('Jobs Store Utils', () => { }); }); - describe('collpasible section', () => { + describe('collapsible section', () => { it('adds a `isClosed` property', () => { expect(result[1].isClosed).toEqual(false); }); @@ -190,7 +190,7 @@ describe('Jobs Store Utils', () => { 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[0].content).toEqual(utilsMockData[2].content); expect(result[1].lines[1].content).toEqual(utilsMockData[3].content); diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js new file mode 100644 index 00000000000..73e3c385d33 --- /dev/null +++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js @@ -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); + }); +}); diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js new file mode 100644 index 00000000000..979dff78eba --- /dev/null +++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js @@ -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'); + }); +}); diff --git a/spec/lib/gitlab/usage_data_concerns/topology_spec.rb b/spec/lib/gitlab/usage_data_concerns/topology_spec.rb index 5cc6f37b5c2..0aa3c5abe32 100644 --- a/spec/lib/gitlab/usage_data_concerns/topology_spec.rb +++ b/spec/lib/gitlab/usage_data_concerns/topology_spec.rb @@ -22,6 +22,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do context 'tracking node metrics' do it 'contains node level metrics for each instance' do expect_prometheus_api_to( + receive_app_request_volume_query, receive_node_memory_query, receive_node_cpu_count_query, receive_node_service_memory_rss_query, @@ -32,6 +33,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do expect(subject[:topology]).to eq({ duration_s: 0, + application_requests_per_hour: 36, nodes: [ { node_memory_total_bytes: 512, @@ -76,6 +78,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do context 'and some node memory metrics are missing' do it 'removes the respective entries' do expect_prometheus_api_to( + receive_app_request_volume_query(result: []), receive_node_memory_query(result: []), receive_node_cpu_count_query, receive_node_service_memory_rss_query(result: []), @@ -149,6 +152,17 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do 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) receive(:query) .with(/node_memory_total_bytes/, an_instance_of(Hash)) diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index 3659e6b973e..52c64a6f0cb 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -189,6 +189,26 @@ RSpec.describe ProjectStatistics do statistics.refresh! 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 context 'when the column is not namespace relatable' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 01a908a34d7..c0712755396 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4766,6 +4766,12 @@ RSpec.describe User do end.to change { User.where(user_type: bot_type).count }.by(1) 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 described_class.public_send(bot_type) diff --git a/spec/rubocop/cop/graphql/authorize_types_spec.rb b/spec/rubocop/cop/graphql/authorize_types_spec.rb index 5d35690c8b7..df637a26ec5 100644 --- a/spec/rubocop/cop/graphql/authorize_types_spec.rb +++ b/spec/rubocop/cop/graphql/authorize_types_spec.rb @@ -10,83 +10,60 @@ RSpec.describe RuboCop::Cop::Graphql::AuthorizeTypes, type: :rubocop do subject(:cop) { described_class.new } - context 'when NOT in a type folder' do - before do - allow(cop).to receive(:in_type?).and_return(false) - end - - it 'does not add an offense even though there is no authorize call' do - expect_no_offenses(<<~TYPE.strip) - module Types - class AType < BaseObject - field :a_thing - field :another_thing - end - end - TYPE - end - end - - context 'when in a type folder' do - before do - allow(cop).to receive(:in_type?).and_return(true) - end - - it 'adds an offense when there is no authorize call' do - inspect_source(<<~TYPE) - module Types - class AType < BaseObject - field :a_thing - field :another_thing - end + it 'adds an offense when there is no authorize call' do + inspect_source(<<~TYPE) + module Types + class AType < BaseObject + field :a_thing + field :another_thing end - TYPE + end + TYPE - expect(cop.offenses.size).to eq 1 - end + expect(cop.offenses.size).to eq 1 + end - it 'does not add an offense for classes that have an authorize call' do - expect_no_offenses(<<~TYPE.strip) - module Types - class AType < BaseObject - graphql_name 'ATypeName' + it 'does not add an offense for classes that have an authorize call' do + expect_no_offenses(<<~TYPE.strip) + module Types + class AType < BaseObject + graphql_name 'ATypeName' - authorize :an_ability, :second_ability + authorize :an_ability, :second_ability - field :a_thing - end + field :a_thing end - TYPE - end + end + TYPE + end - it 'does not add an offense for classes that only have an authorize call' do - expect_no_offenses(<<~TYPE.strip) - module Types - class AType < SuperClassWithFields - authorize :an_ability - end + it 'does not add an offense for classes that only have an authorize call' do + expect_no_offenses(<<~TYPE.strip) + module Types + class AType < SuperClassWithFields + authorize :an_ability end - TYPE - end + end + TYPE + end - it 'does not add an offense for base types' do - expect_no_offenses(<<~TYPE) - module Types - class AType < BaseEnum - field :a_thing - end + it 'does not add an offense for base types' do + expect_no_offenses(<<~TYPE) + module Types + class AType < BaseEnum + field :a_thing end - TYPE - end + end + TYPE + end - it 'does not add an offense for Enums' do - expect_no_offenses(<<~TYPE) - module Types - class ATypeEnum < AnotherEnum - field :a_thing - end + it 'does not add an offense for Enums' do + expect_no_offenses(<<~TYPE) + module Types + class ATypeEnum < AnotherEnum + field :a_thing end - TYPE - end + end + TYPE end end diff --git a/yarn.lock b/yarn.lock index 094949cbd18..a4de32ca84a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -843,10 +843,10 @@ eslint-plugin-vue "^6.2.1" vue-eslint-parser "^7.0.0" -"@gitlab/svgs@1.146.0": - version "1.146.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.146.0.tgz#c74118a3f1ab47ae77211d42597f553f395deb5d" - integrity sha512-2/k9pAZPgHpZ5Ad0fz9i1109sWcShDE4XcjrjzltNNksbi86lqCKbsSe580ujtlG8KShgGMkDkmUa6AHZi64Xw== +"@gitlab/svgs@1.147.0": + version "1.147.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.147.0.tgz#1b2cc986cb3219609136cab641e2c384d724700f" + integrity sha512-KnjN7ms7bEPajYl7q0nKv7HMKtqR/JxCVSBRGXH5ezkeGKy4wb4yEYtvRK8no7ix+Iw4rc0KTqOwKp9nkl/KdA== "@gitlab/ui@17.10.1": version "17.10.1" |