diff options
177 files changed, 1252 insertions, 845 deletions
diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml index c4a64f505f1..8fb2327f049 100644 --- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml +++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml @@ -629,7 +629,6 @@ RSpec/FactoryBot/AvoidCreate: - 'spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb' - 'spec/views/projects/pipelines/show.html.haml_spec.rb' - 'spec/views/projects/project_members/index.html.haml_spec.rb' - - 'spec/views/projects/runners/_specific_runners.html.haml_spec.rb' - 'spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb' - 'spec/views/projects/settings/integrations/edit.html.haml_spec.rb' - 'spec/views/projects/settings/merge_requests/show.html.haml_spec.rb' @@ -544,7 +544,7 @@ gem 'lockbox', '~> 1.1.1' gem 'valid_email', '~> 0.1' # JSON -gem 'json', '~> 2.5.1' +gem 'json', '~> 2.6.3' gem 'json_schemer', '~> 0.2.18' gem 'oj', '~> 3.13.21' gem 'oj-introspect', '~> 0.7' diff --git a/Gemfile.checksum b/Gemfile.checksum index c75e1530c47..0b00383ccef 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -295,8 +295,8 @@ {"name":"jira-ruby","version":"2.1.4","platform":"ruby","checksum":"4267c095cac8323b9eef3ba866eb28bb1388b7623a5abb60c1e7caf12d4adb9e"}, {"name":"jmespath","version":"1.6.2","platform":"ruby","checksum":"238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1"}, {"name":"js_regex","version":"3.8.0","platform":"ruby","checksum":"7934bcdd5a0e6d5af4a520288fd4684a02a472ae55831d9178ccaf82356344b5"}, -{"name":"json","version":"2.5.1","platform":"java","checksum":"be284a0c4a9d0373e81b0d5dfe71ed5b18d0479f05970e60a77be89a2978ce6c"}, -{"name":"json","version":"2.5.1","platform":"ruby","checksum":"918d8c41dacb7cfdbe0c7bbd6014a5372f0cf1c454ca150e9f4010fe80cc3153"}, +{"name":"json","version":"2.6.3","platform":"java","checksum":"ea8c47427a2c876121b9a0ab53043ca390013a76374330eabd923bd81914e563"}, +{"name":"json","version":"2.6.3","platform":"ruby","checksum":"86aaea16adf346a2b22743d88f8dcceeb1038843989ab93cda44b5176c845459"}, {"name":"json-jwt","version":"1.15.3","platform":"ruby","checksum":"66db4f14e538a774c15502a5b5b26b1f3e7585481bbb96df490aa74b5c2d6110"}, {"name":"json_schemer","version":"0.2.18","platform":"ruby","checksum":"3362c21efbefdd12ce994e541a1e7fdb86fd267a6541dd8715e8a580fe3b6be6"}, {"name":"jsonpath","version":"1.1.2","platform":"ruby","checksum":"6804124c244d04418218acb85b15c7caa79c592d7d6970195300428458946d3a"}, diff --git a/Gemfile.lock b/Gemfile.lock index 3b65e2b4061..2298cb8bd83 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -799,7 +799,7 @@ GEM character_set (~> 1.4) regexp_parser (~> 2.5) regexp_property_values (~> 1.0) - json (2.5.1) + json (2.6.3) json-jwt (1.15.3) activesupport (>= 4.2) aes_key_wrap @@ -1712,7 +1712,7 @@ DEPENDENCIES ipynbdiff! jira-ruby (~> 2.1.4) js_regex (~> 3.8) - json (~> 2.5.1) + json (~> 2.6.3) json_schemer (~> 0.2.18) jwt (~> 2.1.0) kaminari (~> 1.2.2) diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue index 75ea9beb5cf..b8719d0cd4d 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue @@ -116,7 +116,7 @@ export default { category="tertiary" :aria-label="__('Remove')" :title="__('Remove')" - class="gl-align-self-center gl-p-1! gl-absolute! gl-w-auto! gl-top-4 gl-right-4" + class="gl-align-self-center gl-p-1! gl-absolute! gl-w-auto! gl-right-4 gl-top-half gl-translate-y-n50" data-testid="item-remove" @click.stop.prevent="removeFrequentItem(itemId)" > diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index 46d5341ea97..148bf0a98ee 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -1,6 +1,7 @@ <script> import { GlLoadingIcon, GlModal, GlEmptyState } from '@gitlab/ui'; import { createAlert } from '~/flash'; +import { HTTP_STATUS_FORBIDDEN } from '~/lib/utils/http_status'; import { mergeUrlParams, getParameterByName } from '~/lib/utils/url_utility'; import { __, s__, sprintf } from '~/locale'; @@ -225,7 +226,7 @@ export default { }) .catch((err) => { let message = COMMON_STR.FAILURE; - if (err.status === 403) { + if (err.status === HTTP_STATUS_FORBIDDEN) { message = COMMON_STR.LEAVE_FORBIDDEN; } createAlert({ message }); diff --git a/app/assets/javascripts/pages/admin/projects/components/namespace_select.vue b/app/assets/javascripts/pages/admin/projects/components/namespace_select.vue index c75c031b0b1..0b287c7ffd9 100644 --- a/app/assets/javascripts/pages/admin/projects/components/namespace_select.vue +++ b/app/assets/javascripts/pages/admin/projects/components/namespace_select.vue @@ -1,34 +1,31 @@ <script> -import { - GlDropdown, - GlDropdownItem, - GlDropdownDivider, - GlSearchBoxByType, - GlLoadingIcon, -} from '@gitlab/ui'; +import { debounce } from 'lodash'; +import { GlCollapsibleListbox } from '@gitlab/ui'; import Api from '~/api'; +import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { __ } from '~/locale'; export default { i18n: { - dropdownHeader: __('Namespaces'), + headerText: __('Namespaces'), searchPlaceholder: __('Search for Namespace'), - anyNamespace: __('Any namespace'), + reset: __('Clear'), }, components: { - GlDropdown, - GlDropdownItem, - GlDropdownDivider, - GlLoadingIcon, - GlSearchBoxByType, + GlCollapsibleListbox, }, props: { - showAny: { - type: Boolean, + origSelectedId: { + type: String, + required: false, + default: '', + }, + origSelectedText: { + type: String, required: false, - default: false, + default: '', }, - placeholder: { + toggleTextPlaceholder: { type: String, required: false, default: __('Namespace'), @@ -42,50 +39,66 @@ export default { data() { return { namespaceOptions: [], - selectedNamespaceId: null, - selectedNamespace: null, + selectedNamespaceId: this.origSelectedId, + selectedNamespaceText: this.origSelectedText, searchTerm: '', isLoading: false, }; }, computed: { - selectedNamespaceName() { - if (this.selectedNamespaceId === null) { - return this.placeholder; - } - return this.selectedNamespace; + toggleText() { + return this.selectedNamespaceText || this.toggleTextPlaceholder; }, }, watch: { - searchTerm() { - this.fetchNamespaces(this.searchTerm); + selectedNamespaceId(val) { + if (!val) { + this.selectedNamespaceText = null; + } + + this.selectedNamespaceText = this.namespaceOptions.find(({ value }) => value === val)?.text; }, }, mounted() { this.fetchNamespaces(); }, methods: { - fetchNamespaces(filter) { + fetchNamespaces() { this.isLoading = true; this.namespaceOptions = []; - return Api.namespaces(filter, (namespaces) => { - this.namespaceOptions = namespaces; + + return Api.namespaces(this.searchTerm, (namespaces) => { + this.namespaceOptions = this.formatNamespaceOptions(namespaces); this.isLoading = false; }); }, - selectNamespace(key) { - this.selectedNamespaceId = this.namespaceOptions[key].id; - this.selectedNamespace = this.getNamespaceString(this.namespaceOptions[key]); - this.$emit('setNamespace', this.selectedNamespaceId); + formatNamespaceOptions(namespaces) { + if (!namespaces) { + return []; + } + + return namespaces.map((namespace) => { + return { + value: String(namespace.id), + text: this.getNamespaceString(namespace), + }; + }); }, - selectAnyNamespace() { - this.selectedNamespaceId = null; - this.selectedNamespace = null; - this.$emit('setNamespace', null); + selectNamespace(value) { + this.selectedNamespaceId = value; + this.$emit('setNamespace', this.selectedNamespaceId); }, getNamespaceString(namespace) { return `${namespace.kind}: ${namespace.full_path}`; }, + search: debounce(function debouncedSearch(searchQuery) { + this.searchTerm = searchQuery?.trim(); + this.fetchNamespaces(); + }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS), + onReset() { + this.selectedNamespaceId = null; + this.$emit('setNamespace', null); + }, }, }; </script> @@ -99,45 +112,19 @@ export default { type="hidden" data-testid="hidden-input" /> - <gl-dropdown - :text="selectedNamespaceName" - :header-text="$options.i18n.dropdownHeader" - toggle-class="dropdown-menu-toggle large" - data-testid="namespace-dropdown" - :right="true" - > - <template #header> - <gl-search-box-by-type - v-model.trim="searchTerm" - class="namespace-search-box" - debounce="250" - :placeholder="$options.i18n.searchPlaceholder" - /> - </template> - - <template v-if="showAny"> - <gl-dropdown-item @click="selectAnyNamespace"> - {{ $options.i18n.anyNamespace }} - </gl-dropdown-item> - <gl-dropdown-divider /> - </template> - - <gl-loading-icon v-if="isLoading" /> - - <gl-dropdown-item - v-for="(namespace, key) in namespaceOptions" - :key="namespace.id" - @click="selectNamespace(key)" - > - {{ getNamespaceString(namespace) }} - </gl-dropdown-item> - </gl-dropdown> + <gl-collapsible-listbox + :items="namespaceOptions" + :header-text="$options.i18n.headerText" + :reset-button-label="$options.i18n.reset" + :toggle-text="toggleText" + :search-placeholder="$options.i18n.searchPlaceholder" + :searching="isLoading" + :selected="selectedNamespaceId" + toggle-class="gl-w-full gl-flex-direction-column gl-align-items-stretch!" + searchable + @reset="onReset" + @search="search" + @select="selectNamespace" + /> </div> </template> - -<style scoped> -/* workaround position: relative imposed by .top-area .nav-controls */ -.namespace-search-box >>> input { - position: static; -} -</style> diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js index 3098d06510b..49ee89de772 100644 --- a/app/assets/javascripts/pages/admin/projects/index.js +++ b/app/assets/javascripts/pages/admin/projects/index.js @@ -1,5 +1,4 @@ import Vue from 'vue'; -import { parseBoolean } from '~/lib/utils/common_utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import ProjectsList from '~/projects_list'; import NamespaceSelect from './components/namespace_select.vue'; @@ -12,16 +11,17 @@ function mountNamespaceSelect() { return false; } - const { showAny, fieldName, placeholder, updateLocation } = el.dataset; + const { fieldName, toggleTextPlaceholder, selectedId, selectedText, updateLocation } = el.dataset; return new Vue({ el, render(createComponent) { return createComponent(NamespaceSelect, { props: { - showAny: parseBoolean(showAny), fieldName, - placeholder, + toggleTextPlaceholder, + origSelectedId: selectedId, + origSelectedText: selectedText, }, on: { setNamespace(newNamespace) { diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 0800d635d92..ed0e019d02b 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -115,6 +115,7 @@ class RegistrationsController < Devise::RegistrationsController def after_request_hook(user) return unless user.persisted? + track_creation user: user Gitlab::Tracking.event(self.class.name, 'successfully_submitted_form', user: user) end @@ -145,6 +146,11 @@ class RegistrationsController < Devise::RegistrationsController users_sign_up_welcome_path(glm_tracking_params) end + def track_creation(user:) + label = user_invited? ? 'invited' : 'signup' + Gitlab::Tracking.event(self.class.name, 'create_user', label: label, user: user) + end + def ensure_destroy_prerequisites_met if current_user.solo_owned_groups.present? redirect_to profile_account_path, @@ -252,9 +258,15 @@ class RegistrationsController < Devise::RegistrationsController end end - def after_pending_invitations_hook - member_id = session.delete(:originating_member_id) + def user_invited? + !!member_id + end + def member_id + @member_id ||= session.delete(:originating_member_id) + end + + def after_pending_invitations_hook return unless member_id # if invited multiple times to different projects, only the email clicked will be counted as accepted diff --git a/app/finders/groups/accepting_project_shares_finder.rb b/app/finders/groups/accepting_project_shares_finder.rb new file mode 100644 index 00000000000..c4963fcc352 --- /dev/null +++ b/app/finders/groups/accepting_project_shares_finder.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# AcceptingProjectSharesFinder +# +# Used to filter Shareable Groups by a set of params +# +# Arguments: +# current_user - which user is requesting groups +# params: +# search: string +module Groups + class AcceptingProjectSharesFinder < Base + def initialize(current_user, project_to_be_shared, params = {}) + @current_user = current_user + @params = params + @project_to_be_shared = project_to_be_shared + end + + def execute + return Group.none unless can_share_project? + + groups = if has_admin_access? + Group.all + else + groups_with_guest_access_plus + end + + groups = groups.search(params[:search]) if params[:search].present? + + sort(groups).with_route + end + + private + + attr_reader :current_user, :project_to_be_shared, :params + + def has_admin_access? + current_user&.can_read_all_resources? + end + + # rubocop: disable CodeReuse/Finder + def groups_with_guest_access_plus + GroupsFinder.new(current_user, min_access_level: Gitlab::Access::GUEST).execute + end + # rubocop: enable CodeReuse/Finder + + def can_share_project? + Ability.allowed?(current_user, :admin_project, project_to_be_shared) && + project_to_be_shared.allowed_to_share_with_group? + end + end +end diff --git a/app/finders/groups/base.rb b/app/finders/groups/base.rb index d7f56b1a7a6..9d2f9f60a63 100644 --- a/app/finders/groups/base.rb +++ b/app/finders/groups/base.rb @@ -5,7 +5,7 @@ module Groups private def sort(items) - items.order(Group.arel_table[:path].asc, Group.arel_table[:id].asc) # rubocop: disable CodeReuse/ActiveRecord + items.reorder(Group.arel_table[:path].asc, Group.arel_table[:id].asc) # rubocop: disable CodeReuse/ActiveRecord end def by_search(items) diff --git a/app/graphql/resolvers/ci/runner_jobs_resolver.rb b/app/graphql/resolvers/ci/runner_jobs_resolver.rb index b818be3f018..467a3525867 100644 --- a/app/graphql/resolvers/ci/runner_jobs_resolver.rb +++ b/app/graphql/resolvers/ci/runner_jobs_resolver.rb @@ -30,6 +30,7 @@ module Resolvers previous_stage_jobs_or_needs: [:needs, :pipeline], artifacts: [:job_artifacts], pipeline: [:user], + project: [{ project: [:route, { namespace: [:route] }] }], detailed_status: [ :metadata, { pipeline: [:merge_request] }, diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index 4447a10a74e..4c466f97d61 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -97,6 +97,8 @@ module Types field :web_path, GraphQL::Types::String, null: true, description: 'Web path of the job.' + field :project, Types::ProjectType, null: true, description: 'Project that the job belongs to.' + def kind return ::Ci::Build unless [::Ci::Build, ::Ci::Bridge].include?(object.class) diff --git a/app/models/packages/debian/file_metadatum.rb b/app/models/packages/debian/file_metadatum.rb index af51f256e18..eb1b03a8e9d 100644 --- a/app/models/packages/debian/file_metadatum.rb +++ b/app/models/packages/debian/file_metadatum.rb @@ -13,10 +13,11 @@ class Packages::Debian::FileMetadatum < ApplicationRecord } validates :file_type, presence: true - validates :file_type, inclusion: { in: %w[unknown] }, if: -> { package_file&.package&.debian_incoming? } + validates :file_type, inclusion: { in: %w[unknown] }, + if: -> { package_file&.package&.debian_incoming? || package_file&.package&.processing? } validates :file_type, inclusion: { in: %w[source dsc deb udeb buildinfo changes] }, - if: -> { package_file&.package&.debian_package? } + if: -> { package_file&.package&.debian_package? && !package_file&.package&.processing? } validates :component, presence: true, diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 050db3b6870..d4793beffb9 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -43,14 +43,12 @@ class ProtectedBranch < ApplicationRecord end def self.new_cache(project, ref_name, dry_run: true) - if Feature.enabled?(:hash_based_cache_for_protected_branches, project) - ProtectedBranches::CacheService.new(project).fetch(ref_name, dry_run: dry_run) do # rubocop: disable CodeReuse/ServiceClass - self.matching(ref_name, protected_refs: protected_refs(project)).present? - end + ProtectedBranches::CacheService.new(project).fetch(ref_name, dry_run: dry_run) do # rubocop: disable CodeReuse/ServiceClass + self.matching(ref_name, protected_refs: protected_refs(project)).present? end end - # Deprecated: https://gitlab.com/gitlab-org/gitlab/-/issues/368279 + # Deprecated: https://gitlab.com/gitlab-org/gitlab/-/issues/370608 # ---------------------------------------------------------------- CACHE_EXPIRE_IN = 1.hour diff --git a/app/services/ci/runners/register_runner_service.rb b/app/services/ci/runners/register_runner_service.rb index abd32610cec..db16b86d5e6 100644 --- a/app/services/ci/runners/register_runner_service.rb +++ b/app/services/ci/runners/register_runner_service.rb @@ -46,10 +46,10 @@ module Ci # Create shared runner. Requires admin access { runner_type: :instance_type } elsif runner_registrar_valid?('project') && project = ::Project.find_by_runners_token(registration_token) - # Create a specific runner for the project + # Create a project runner { runner_type: :project_type, scope: project } elsif runner_registrar_valid?('group') && group = ::Group.find_by_runners_token(registration_token) - # Create a specific runner for the group + # Create a group runner { runner_type: :group_type, scope: group } end end diff --git a/app/services/packages/debian/process_package_file_service.rb b/app/services/packages/debian/process_package_file_service.rb index 59e8ac3425b..2568085d303 100644 --- a/app/services/packages/debian/process_package_file_service.rb +++ b/app/services/packages/debian/process_package_file_service.rb @@ -10,19 +10,23 @@ module Packages # used by ExclusiveLeaseGuard DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze - def initialize(package_file, creator, distribution_name, component_name) + def initialize(package_file, distribution_name, component_name) @package_file = package_file - @creator = creator @distribution_name = distribution_name @component_name = component_name end def execute + return if @package_file.package.pending_destruction? + try_obtain_lease do validate! - @package_file.transaction do + package.transaction do + rename_package_and_set_version + update_package update_file_metadata + cleanup_temp_package end ::Packages::Debian::GenerateDistributionWorker.perform_async(:project, package.debian_distribution.id) @@ -40,6 +44,80 @@ module Packages raise ArgumentError, "invalid package file type: #{file_metadata[:file_type]}" end + def file_metadata + ::Packages::Debian::ExtractMetadataService.new(@package_file).execute + end + strong_memoize_attr :file_metadata + + def package + package = temp_package.project + .packages + .debian + .with_name(package_name) + .with_version(package_version) + .with_debian_codename(@distribution_name) + .not_pending_destruction + .last + package || temp_package + end + strong_memoize_attr :package + + def temp_package + @package_file.package + end + strong_memoize_attr :temp_package + + def package_name + package_name_and_version[0] + end + + def package_version + package_name_and_version[1] + end + + def package_name_and_version + package_name = file_metadata[:fields]['Package'] + package_version = file_metadata[:fields]['Version'] + + if file_metadata[:fields]['Source'] + # "sample" or "sample (1.2.3~alpha2)" + source_field_parts = file_metadata[:fields]['Source'].split(SOURCE_FIELD_SPLIT_REGEX) + package_name = source_field_parts[0] + package_version = source_field_parts[2] || package_version + end + + [package_name, package_version] + end + strong_memoize_attr :package_name_and_version + + def rename_package_and_set_version + package.update!( + name: package_name, + version: package_version, + status: :default + ) + end + + def update_package + return unless using_temporary_package? + + package.update!( + debian_publication_attributes: { distribution_id: distribution.id } + ) + end + + def using_temporary_package? + package.id == temp_package.id + end + + def distribution + Packages::Debian::DistributionsFinder.new( + @package_file.package.project, + codename: @distribution_name + ).execute.last! + end + strong_memoize_attr :distribution + def update_file_metadata ::Packages::UpdatePackageFileService.new(@package_file, package_id: package.id) .execute @@ -55,36 +133,8 @@ module Packages ) end - def package - strong_memoize(:package) do - package_name = file_metadata[:fields]['Package'] - package_version = file_metadata[:fields]['Version'] - - if file_metadata[:fields]['Source'] - # "sample" or "sample (1.2.3~alpha2)" - source_field_parts = file_metadata[:fields]['Source'].split(SOURCE_FIELD_SPLIT_REGEX) - package_name = source_field_parts[0] - package_version = source_field_parts[2] || package_version - end - - params = { - 'name': package_name, - 'version': package_version, - 'distribution_name': @distribution_name - } - response = Packages::Debian::FindOrCreatePackageService.new(project, @creator, params).execute - response.payload[:package] - end - end - - def file_metadata - strong_memoize(:metadata) do - ::Packages::Debian::ExtractMetadataService.new(@package_file).execute - end - end - - def project - @package_file.package.project + def cleanup_temp_package + temp_package.destroy unless using_temporary_package? end # used by ExclusiveLeaseGuard diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 18cd3400c60..0a99614c74b 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -16,13 +16,10 @@ .nav-controls.gl-pl-2 .search-holder = render 'shared/projects/search_form', autofocus: true, admin_view: true - - current_namespace = _('Namespace') - if params[:namespace_id].present? - namespace = Namespace.find(params[:namespace_id]) - - current_namespace = "#{namespace.kind}: #{namespace.full_path}" - %button.dropdown-menu-toggle.btn.btn-default.btn-md.gl-button.js-namespace-select{ data: { show_any: 'true', field_name: 'namespace_id', placeholder: current_namespace, update_location: 'true' }, type: 'button' } - %span.gl-dropdown-button-text - = current_namespace + - selected_text = "#{namespace.kind}: #{namespace.full_path}" if namespace + .js-namespace-select{ data: { field_name: 'namespace_id', selected_id: namespace&.id, selected_text: selected_text, update_location: 'true' } } = link_to new_project_path, class: 'gl-button btn btn-confirm' do = _('New Project') diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index bfa17daf1c2..464027e73f4 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -140,10 +140,7 @@ .col-sm-3.col-form-label = f.label :new_namespace_id, _("Namespace") .col-sm-9 - - placeholder = _('Search for Namespace') - %button.dropdown-menu-toggle.btn.btn-default.btn-md.gl-button.js-namespace-select{ data: { field_name: 'new_namespace_id', placeholder: placeholder }, type: 'button' } - %span.gl-dropdown-button-text - = placeholder + .js-namespace-select{ data: { field_name: 'new_namespace_id', toggle_text_placeholder: _('Search for Namespace') } } .form-group.row .offset-sm-3.col-sm-9 diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 8763912438b..f1ed1311195 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -30,6 +30,7 @@ = render_if_exists 'groups/merge_request_approval_settings', expanded: expanded, group: @group, user: current_user = render_if_exists 'groups/insights', expanded: expanded += render_if_exists 'groups/analytics_dashboards', expanded: expanded %section.settings.no-animate#js-badge-settings{ class: ('expanded' if expanded) } .settings-header diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_project_runners.html.haml index f3a7037bdab..fc4377e2f98 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_project_runners.html.haml @@ -1,9 +1,9 @@ %h4 - = _('Specific runners') + = s_('Runners|Project runners') .bs-callout.help-callout - if can?(current_user, :register_project_runners, @project) - = _('These runners are specific to this project.') + = s_('Runners|These runners are assigned to this project.') - if params[:ci_runner_templates] %hr = render partial: 'ci/runner/setup_runner_in_aws', @@ -11,7 +11,7 @@ %hr = render partial: 'ci/runner/how_to_setup_runner', locals: { registration_token: @project.runners_token, - type: s_('Runners|specific'), + type: _('project'), reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path, project_path: @project.path_with_namespace, group_path: '' } @@ -22,13 +22,13 @@ %hr - if @project_runners.any? - %h4.underlined-title= _('Available specific runners') - %ul.bordered-list.activated-specific-runners + %h4.underlined-title= s_('Runners|Assigned project runners') + %ul.bordered-list{ data: { testid: 'assigned_project_runners' } } = render partial: 'projects/runners/runner', collection: @project_runners, as: :runner = paginate @project_runners, theme: "gitlab", param_name: "project_page", params: { expand_runners: true, anchor: 'js-runners-settings' } - if @assignable_runners.any? %h4.underlined-title= _('Other available runners') - %ul.bordered-list.available-specific-runners + %ul.bordered-list{ data: { testid: 'available_project_runners' } } = render partial: 'projects/runners/runner', collection: @assignable_runners, as: :runner = paginate @assignable_runners, theme: "gitlab", param_name: "specific_page", :params => { :anchor => 'js-runners-settings'} diff --git a/app/views/projects/runners/_settings.html.haml b/app/views/projects/runners/_settings.html.haml index a02bdac442b..f85b5fd9866 100644 --- a/app/views/projects/runners/_settings.html.haml +++ b/app/views/projects/runners/_settings.html.haml @@ -2,7 +2,7 @@ .row .col-sm-6 - = render 'projects/runners/specific_runners' + = render 'projects/runners/project_runners' .col-sm-6 = render 'projects/runners/shared_runners' = render 'projects/runners/group_runners' diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 42a146a4f65..360a3f3eb89 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -5,5 +5,4 @@ = render partial: 'projects/issues/issue', collection: @issues = paginate @issues, theme: "gitlab" - else - - project_select_button = local_assigns.fetch(:project_select_button, false) - = render 'shared/empty_states/issues', project_select_button: project_select_button + = render 'shared/empty_states/issues' diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index fb410274859..37f7fbc0de5 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -1,7 +1,5 @@ - button_path = local_assigns.fetch(:new_project_issue_button_path, false) -- project_select_button = local_assigns.fetch(:project_select_button, false) - show_import_button = local_assigns.fetch(:show_import_button, false) && can?(current_user, :import_issues, @project) -- has_button = button_path || project_select_button - closed_issues_count = issuables_count_for_state(:issues, :closed) - opened_issues_count = issuables_count_for_state(:issues, :opened) - is_opened_state = params[:state] == 'opened' @@ -39,11 +37,9 @@ = _("The Issue Tracker is the place to add things that need to be improved or solved in a project") %p = _("Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.") - - if has_button + - if button_path .text-center - - if project_select_button - = render 'shared/new_project_item_select', path: 'issues/new', label: _('issue'), type: :issues, with_feature_enabled: 'issues' - - elsif show_new_issue_link?(@project) + - if show_new_issue_link?(@project) = link_to _('New issue'), button_path, class: 'gl-button btn btn-confirm', id: 'new_issue_link' - if show_import_button diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml index fe602db4393..8e4051fa335 100644 --- a/app/views/shared/empty_states/_merge_requests.html.haml +++ b/app/views/shared/empty_states/_merge_requests.html.haml @@ -1,6 +1,4 @@ - button_path = local_assigns.fetch(:button_path, false) -- project_select_button = local_assigns.fetch(:project_select_button, false) -- has_button = button_path || project_select_button - closed_merged_count = issuables_count_for_state(:merged, :closed) - opened_merged_count = issuables_count_for_state(:merged, :opened) - is_opened_state = params[:state] == 'opened' @@ -37,9 +35,6 @@ = _("Merge requests are a place to propose changes you've made to a project and discuss those changes with others") %p = _("Interested parties can even contribute by pushing commits if they want to.") - - if has_button + - if button_path .text-center - - if project_select_button - = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _('merge request'), type: :merge_requests, with_feature_enabled: 'merge_requests' - - else - = link_to _('New merge request'), button_path, class: 'gl-button btn btn-confirm', title: _('New merge request'), id: 'new_merge_request_link', data: { qa_selector: "new_merge_request_button" } + = link_to _('New merge request'), button_path, class: 'gl-button btn btn-confirm', title: _('New merge request'), id: 'new_merge_request_link', data: { qa_selector: "new_merge_request_button" } diff --git a/app/views/shared/runners/_runner_type_alert.html.haml b/app/views/shared/runners/_runner_type_alert.html.haml index a1599b3ec49..63ecdaf4149 100644 --- a/app/views/shared/runners/_runner_type_alert.html.haml +++ b/app/views/shared/runners/_runner_type_alert.html.haml @@ -12,5 +12,5 @@ title: s_('Runners|This runner is associated with specific projects.'), dismissible: false) do |c| = c.body do - = s_('Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared or group runner.') - = link_to _('Learn more.'), help_page_path('ci/runners/runners_scope', anchor: 'specific-runners'), target: '_blank', rel: 'noopener noreferrer' + = s_('Runners|You can set up a project runner to be used by multiple projects but you cannot make this a shared or group runner.') + = link_to _('Learn more.'), help_page_path('ci/runners/runners_scope', anchor: 'project-runners'), target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/shared/runners/_runner_type_badge.html.haml b/app/views/shared/runners/_runner_type_badge.html.haml index c6a18c804da..a8a93f3dd76 100644 --- a/app/views/shared/runners/_runner_type_badge.html.haml +++ b/app/views/shared/runners/_runner_type_badge.html.haml @@ -4,4 +4,4 @@ - elsif runner.group_type? = gl_badge_tag s_('Runners|group'), variant: :success - else - = gl_badge_tag s_('Runners|specific'), variant: :info + = gl_badge_tag s_('Runners|project'), variant: :info diff --git a/app/workers/packages/debian/process_package_file_worker.rb b/app/workers/packages/debian/process_package_file_worker.rb index cac5cefa862..e9d6ad57749 100644 --- a/app/workers/packages/debian/process_package_file_worker.rb +++ b/app/workers/packages/debian/process_package_file_worker.rb @@ -14,21 +14,20 @@ module Packages queue_namespace :package_repositories feature_category :package_registry - def perform(package_file_id, user_id, distribution_name, component_name) + def perform(package_file_id, distribution_name, component_name) @package_file_id = package_file_id - @user_id = user_id @distribution_name = distribution_name @component_name = component_name - return unless package_file && user && distribution_name && component_name + return unless package_file && distribution_name && component_name # return if file has already been processed return unless package_file.debian_file_metadatum&.unknown? - ::Packages::Debian::ProcessPackageFileService.new(package_file, user, distribution_name, component_name).execute + ::Packages::Debian::ProcessPackageFileService.new(package_file, distribution_name, component_name).execute rescue StandardError => e - Gitlab::ErrorTracking.log_exception(e, package_file_id: @package_file_id, user_id: @user_id, + Gitlab::ErrorTracking.log_exception(e, package_file_id: @package_file_id, distribution_name: @distribution_name, component_name: @component_name) - package_file.destroy! + package_file.package.update_column(:status, :error) end private @@ -37,11 +36,6 @@ module Packages ::Packages::PackageFile.find_by_id(@package_file_id) end strong_memoize_attr :package_file - - def user - ::User.find_by_id(@user_id) - end - strong_memoize_attr :user end end end diff --git a/config/feature_flags/development/hash_based_cache_for_protected_branches.yml b/config/feature_flags/development/hash_based_cache_for_protected_branches.yml deleted file mode 100644 index 58372ff3516..00000000000 --- a/config/feature_flags/development/hash_based_cache_for_protected_branches.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: hash_based_cache_for_protected_branches -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92934 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368279 -milestone: '15.3' -type: development -group: group::source code -default_enabled: true diff --git a/config/feature_flags/development/use_primary_and_secondary_stores_for_duplicate_jobs.yml b/config/feature_flags/development/use_primary_and_secondary_stores_for_duplicate_jobs.yml deleted file mode 100644 index 323ad754627..00000000000 --- a/config/feature_flags/development/use_primary_and_secondary_stores_for_duplicate_jobs.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: use_primary_and_secondary_stores_for_duplicate_jobs -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85740 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364381 -milestone: -type: development -group: group::scalability -default_enabled: false diff --git a/config/feature_flags/development/use_primary_store_as_default_for_duplicate_jobs.yml b/config/feature_flags/development/use_primary_store_as_default_for_duplicate_jobs.yml deleted file mode 100644 index 3cd25d7b735..00000000000 --- a/config/feature_flags/development/use_primary_store_as_default_for_duplicate_jobs.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: use_primary_store_as_default_for_duplicate_jobs -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85740 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364381 -milestone: -type: development -group: group::scalability -default_enabled: false diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml index cf473270d61..c4be4cc1800 100644 --- a/config/gitlab_loose_foreign_keys.yml +++ b/config/gitlab_loose_foreign_keys.yml @@ -196,6 +196,10 @@ dast_profiles_pipelines: - table: ci_pipelines column: ci_pipeline_id on_delete: async_delete +dast_profiles_tags: + - table: tags + column: tag_id + on_delete: async_delete dast_scanner_profiles_builds: - table: ci_builds column: ci_build_id diff --git a/db/docs/dast_profiles_tags.yml b/db/docs/dast_profiles_tags.yml new file mode 100644 index 00000000000..8210b93f3af --- /dev/null +++ b/db/docs/dast_profiles_tags.yml @@ -0,0 +1,10 @@ +--- +table_name: dast_profiles_tags +classes: + - Dast::ScannerProfileTag +feature_categories: + - dynamic_application_security_testing +description: Join Table for Runner tags and DAST Profiles +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108371 +milestone: '15.8' +gitlab_schema: gitlab_main diff --git a/db/migrate/20230106184809_create_dast_profiles_tags.rb b/db/migrate/20230106184809_create_dast_profiles_tags.rb new file mode 100644 index 00000000000..f31eaea5fa8 --- /dev/null +++ b/db/migrate/20230106184809_create_dast_profiles_tags.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class CreateDastProfilesTags < Gitlab::Database::Migration[2.1] + def up + create_table :dast_profiles_tags do |t| + t.references :dast_profile, null: false, foreign_key: { on_delete: :cascade }, + index: { name: 'i_dast_profiles_tags_on_scanner_profiles_id' } + + t.bigint :tag_id, null: false + + t.index :tag_id, name: :index_dast_profiles_tags_on_tag_id + end + end + + def down + drop_table :dast_profiles_tags + end +end diff --git a/db/schema_migrations/20230106184809 b/db/schema_migrations/20230106184809 new file mode 100644 index 00000000000..95318b9ea02 --- /dev/null +++ b/db/schema_migrations/20230106184809 @@ -0,0 +1 @@ +dad6e8972db3829dc6c02013ee87b08aa9bf4c50e58b35b0dbd67935ee4c266a
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index e6450762ce1..0bbc0e1d70f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14573,6 +14573,21 @@ CREATE TABLE dast_profiles_pipelines ( COMMENT ON TABLE dast_profiles_pipelines IS '{"owner":"group::dynamic analysis","description":"Join table between DAST Profiles and CI Pipelines"}'; +CREATE TABLE dast_profiles_tags ( + id bigint NOT NULL, + dast_profile_id bigint NOT NULL, + tag_id bigint NOT NULL +); + +CREATE SEQUENCE dast_profiles_tags_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE dast_profiles_tags_id_seq OWNED BY dast_profiles_tags.id; + CREATE TABLE dast_scanner_profiles ( id bigint NOT NULL, created_at timestamp with time zone NOT NULL, @@ -24104,6 +24119,8 @@ ALTER TABLE ONLY dast_profile_schedules ALTER COLUMN id SET DEFAULT nextval('das ALTER TABLE ONLY dast_profiles ALTER COLUMN id SET DEFAULT nextval('dast_profiles_id_seq'::regclass); +ALTER TABLE ONLY dast_profiles_tags ALTER COLUMN id SET DEFAULT nextval('dast_profiles_tags_id_seq'::regclass); + ALTER TABLE ONLY dast_scanner_profiles ALTER COLUMN id SET DEFAULT nextval('dast_scanner_profiles_id_seq'::regclass); ALTER TABLE ONLY dast_scanner_profiles_tags ALTER COLUMN id SET DEFAULT nextval('dast_scanner_profiles_tags_id_seq'::regclass); @@ -25979,6 +25996,9 @@ ALTER TABLE ONLY dast_profiles_pipelines ALTER TABLE ONLY dast_profiles ADD CONSTRAINT dast_profiles_pkey PRIMARY KEY (id); +ALTER TABLE ONLY dast_profiles_tags + ADD CONSTRAINT dast_profiles_tags_pkey PRIMARY KEY (id); + ALTER TABLE ONLY dast_scanner_profiles_builds ADD CONSTRAINT dast_scanner_profiles_builds_pkey PRIMARY KEY (dast_scanner_profile_id, ci_build_id); @@ -28305,6 +28325,8 @@ CREATE INDEX i_compliance_frameworks_on_id_and_created_at ON compliance_manageme CREATE INDEX i_dast_pre_scan_verification_steps_on_pre_scan_verification_id ON dast_pre_scan_verification_steps USING btree (dast_pre_scan_verification_id); +CREATE INDEX i_dast_profiles_tags_on_scanner_profiles_id ON dast_profiles_tags USING btree (dast_profile_id); + CREATE INDEX i_dast_scanner_profiles_tags_on_scanner_profiles_id ON dast_scanner_profiles_tags USING btree (dast_scanner_profile_id); CREATE UNIQUE INDEX i_pm_licenses_on_spdx_identifier ON pm_licenses USING btree (spdx_identifier); @@ -29287,6 +29309,8 @@ CREATE UNIQUE INDEX index_dast_profiles_on_project_id_and_name ON dast_profiles CREATE UNIQUE INDEX index_dast_profiles_pipelines_on_ci_pipeline_id ON dast_profiles_pipelines USING btree (ci_pipeline_id); +CREATE INDEX index_dast_profiles_tags_on_tag_id ON dast_profiles_tags USING btree (tag_id); + CREATE UNIQUE INDEX index_dast_scanner_profiles_on_project_id_and_name ON dast_scanner_profiles USING btree (project_id, name); CREATE INDEX index_dast_scanner_profiles_tags_on_tag_id ON dast_scanner_profiles_tags USING btree (tag_id); @@ -35332,6 +35356,9 @@ ALTER TABLE ONLY merge_request_user_mentions ALTER TABLE ONLY x509_commit_signatures ADD CONSTRAINT fk_rails_ab07452314 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY dast_profiles_tags + ADD CONSTRAINT fk_rails_ab9e643cd8 FOREIGN KEY (dast_profile_id) REFERENCES dast_profiles(id) ON DELETE CASCADE; + ALTER TABLE ONLY resource_iteration_events ADD CONSTRAINT fk_rails_abf5d4affa FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE; diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 341738a5f47..a765333d26b 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1961,6 +1961,7 @@ Input type: `DastProfileCreateInput` | <a id="mutationdastprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the profile belongs to. | | <a id="mutationdastprofilecreatename"></a>`name` | [`String!`](#string) | Name of the profile. | | <a id="mutationdastprofilecreaterunaftercreate"></a>`runAfterCreate` | [`Boolean`](#boolean) | Run scan using profile after creation. Defaults to false. | +| <a id="mutationdastprofilecreatetaglist"></a>`tagList` | [`[String!]`](#string) | Indicates the runner tags associated with the profile. | #### Fields @@ -2027,6 +2028,7 @@ Input type: `DastProfileUpdateInput` | <a id="mutationdastprofileupdateid"></a>`id` | [`DastProfileID!`](#dastprofileid) | ID of the profile to be deleted. | | <a id="mutationdastprofileupdatename"></a>`name` | [`String`](#string) | Name of the profile. | | <a id="mutationdastprofileupdaterunafterupdate"></a>`runAfterUpdate` | [`Boolean`](#boolean) | Run scan using profile after update. Defaults to false. | +| <a id="mutationdastprofileupdatetaglist"></a>`tagList` | [`[String!]`](#string) | Indicates the runner tags associated with the profile. | #### Fields @@ -2051,7 +2053,7 @@ Input type: `DastScannerProfileCreateInput` | <a id="mutationdastscannerprofilecreatescantype"></a>`scanType` | [`DastScanTypeEnum`](#dastscantypeenum) | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. | | <a id="mutationdastscannerprofilecreateshowdebugmessages"></a>`showDebugMessages` | [`Boolean`](#boolean) | Indicates if debug messages should be included in DAST console output. True to include the debug messages. | | <a id="mutationdastscannerprofilecreatespidertimeout"></a>`spiderTimeout` | [`Int`](#int) | Maximum number of minutes allowed for the spider to traverse the site. | -| <a id="mutationdastscannerprofilecreatetaglist"></a>`tagList` | [`[String!]`](#string) | Indicates the runner tags associated with the scanner profile. | +| <a id="mutationdastscannerprofilecreatetaglist"></a>`tagList` **{warning-solid}** | [`[String!]`](#string) | **Deprecated:** Moved to DastProfile. Deprecated in 15.8. | | <a id="mutationdastscannerprofilecreatetargettimeout"></a>`targetTimeout` | [`Int`](#int) | Maximum number of seconds allowed for the site under test to respond to a request. | | <a id="mutationdastscannerprofilecreateuseajaxspider"></a>`useAjaxSpider` | [`Boolean`](#boolean) | Indicates if the AJAX spider should be used to crawl the target site. True to run the AJAX spider in addition to the traditional spider, and false to run only the traditional spider. | @@ -2098,7 +2100,7 @@ Input type: `DastScannerProfileUpdateInput` | <a id="mutationdastscannerprofileupdatescantype"></a>`scanType` | [`DastScanTypeEnum`](#dastscantypeenum) | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. | | <a id="mutationdastscannerprofileupdateshowdebugmessages"></a>`showDebugMessages` | [`Boolean`](#boolean) | Indicates if debug messages should be included in DAST console output. True to include the debug messages. | | <a id="mutationdastscannerprofileupdatespidertimeout"></a>`spiderTimeout` | [`Int!`](#int) | Maximum number of minutes allowed for the spider to traverse the site. | -| <a id="mutationdastscannerprofileupdatetaglist"></a>`tagList` | [`[String!]`](#string) | Indicates the runner tags associated with the scanner profile. | +| <a id="mutationdastscannerprofileupdatetaglist"></a>`tagList` **{warning-solid}** | [`[String!]`](#string) | **Deprecated:** Moved to DastProfile. Deprecated in 15.8. | | <a id="mutationdastscannerprofileupdatetargettimeout"></a>`targetTimeout` | [`Int!`](#int) | Maximum number of seconds allowed for the site under test to respond to a request. | | <a id="mutationdastscannerprofileupdateuseajaxspider"></a>`useAjaxSpider` | [`Boolean`](#boolean) | Indicates if the AJAX spider should be used to crawl the target site. True to run the AJAX spider in addition to the traditional spider, and false to run only the traditional spider. | @@ -11241,6 +11243,7 @@ CI/CD variables for a GitLab instance. | <a id="cijobpipeline"></a>`pipeline` | [`Pipeline`](#pipeline) | Pipeline the job belongs to. | | <a id="cijobplayable"></a>`playable` | [`Boolean!`](#boolean) | Indicates the job can be played. | | <a id="cijobpreviousstagejobsorneeds"></a>`previousStageJobsOrNeeds` | [`JobNeedUnionConnection`](#jobneedunionconnection) | Jobs that must complete before the job runs. Returns `BuildNeed`, which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise. (see [Connections](#connections)) | +| <a id="cijobproject"></a>`project` | [`Project`](#project) | Project that the job belongs to. | | <a id="cijobqueuedat"></a>`queuedAt` | [`Time`](#time) | When the job was enqueued and marked as pending. | | <a id="cijobqueuedduration"></a>`queuedDuration` | [`Duration`](#duration) | How long the job was enqueued before starting. | | <a id="cijobrefname"></a>`refName` | [`String`](#string) | Ref name of the job. | @@ -12001,6 +12004,7 @@ Represents a DAST Profile. | <a id="dastprofileeditpath"></a>`editPath` | [`String`](#string) | Relative web path to the edit page of a profile. | | <a id="dastprofileid"></a>`id` | [`DastProfileID!`](#dastprofileid) | ID of the profile. | | <a id="dastprofilename"></a>`name` | [`String`](#string) | Name of the profile. | +| <a id="dastprofiletaglist"></a>`tagList` | [`[String!]`](#string) | Runner tags associated with the profile. | ### `DastProfileBranch` @@ -12055,7 +12059,7 @@ Represents a DAST scanner profile. | <a id="dastscannerprofilescantype"></a>`scanType` | [`DastScanTypeEnum`](#dastscantypeenum) | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. | | <a id="dastscannerprofileshowdebugmessages"></a>`showDebugMessages` | [`Boolean!`](#boolean) | Indicates if debug messages should be included in DAST console output. True to include the debug messages. | | <a id="dastscannerprofilespidertimeout"></a>`spiderTimeout` | [`Int`](#int) | Maximum number of minutes allowed for the spider to traverse the site. | -| <a id="dastscannerprofiletaglist"></a>`tagList` | [`[String!]`](#string) | Runner tags associated with the scanner profile. | +| <a id="dastscannerprofiletaglist"></a>`tagList` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in 15.8. Moved to DastProfile. | | <a id="dastscannerprofiletargettimeout"></a>`targetTimeout` | [`Int`](#int) | Maximum number of seconds allowed for the site under test to respond to a request. | | <a id="dastscannerprofileuseajaxspider"></a>`useAjaxSpider` | [`Boolean!`](#boolean) | Indicates if the AJAX spider should be used to crawl the target site. True to run the AJAX spider in addition to the traditional spider, and false to run only the traditional spider. | diff --git a/doc/api/groups.md b/doc/api/groups.md index 6e233674f17..dd3f8baf338 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1135,6 +1135,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab > - Immediately deleting subgroups was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/360008) in GitLab 15.3 [with a flag](../administration/feature_flags.md) named `immediate_delete_subgroup_api`. Disabled by default. > - Immediately deleting subgroups was [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/368276) in GitLab 15.4. > - Immediately deleting subgroups was [enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/368276) by default in GitLab 15.4. +> - The flag `immediate_delete_subgroup_api` for immediately deleting subgroups was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/374069) in GitLab 15.9. Only available to group owners and administrators. diff --git a/doc/api/projects.md b/doc/api/projects.md index 5dad1fe7abe..c22fd8d3fc2 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1173,6 +1173,40 @@ GET /projects/:id/groups ] ``` +## List a project's shareable groups + +Get a list of groups that can be shared with a project + +```plaintext +GET /projects/:id/share_locations +``` + +| Attribute | Type | Required | Description | +|-----------------------------|-------------------|------------------------|-------------| +| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). | +| `search` | string | **{dotted-circle}** No | Search for specific groups. | + +```json +[ + { + "id": 22, + "web_url": "http://127.0.0.1:3000/groups/gitlab-org", + "name": "Gitlab Org", + "avatar_url": null, + "full_name": "Gitlab Org", + "full_path": "gitlab-org" + }, + { + "id": 25, + "web_url": "http://127.0.0.1:3000/groups/gnuwget", + "name": "Gnuwget", + "avatar_url": null, + "full_name": "Gnuwget", + "full_path": "gnuwget" + } +] +``` + ## Get project events Refer to the [Events API documentation](events.md#list-a-projects-visible-events). @@ -2586,20 +2620,6 @@ GET /projects/:id/push_rule } ``` -Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) -can also see the `commit_committer_check` and `reject_unsigned_commits` -parameters: - -```json -{ - "id": 1, - "project_id": 3, - "commit_committer_check": false, - "reject_unsigned_commits": false - ... -} -``` - ### Add project push rule Adds a push rule to a specified project. diff --git a/doc/api/runners.md b/doc/api/runners.md index 90b5a17f133..5593cb52113 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -33,7 +33,7 @@ GitLab and the runner are then connected. ## List owned runners -Get a list of specific runners available to the user. +Get a list of runners available to the user. ```plaintext GET /runners @@ -46,7 +46,7 @@ GET /runners?tag_list=tag1,tag2 | Attribute | Type | Required | Description | |------------|--------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | +| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | | `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | | `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 | | `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | @@ -97,7 +97,7 @@ Example response: ## List all runners **(FREE SELF)** -Get a list of all runners in the GitLab instance (specific and shared). Access +Get a list of all runners in the GitLab instance (project and shared). Access is restricted to users with administrator access. ```plaintext @@ -325,7 +325,7 @@ Example response: ### Pause a runner -Pause a specific runner. +Pause a runner. ```plaintext PUT --form "paused=true" /runners/:runner_id @@ -463,7 +463,7 @@ GET /projects/:id/runners?tag_list=tag1,tag2 | Attribute | Type | Required | Description | |------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user | -| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | +| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to return, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided | | `type` | string | no | The type of runners to return, one of: `instance_type`, `group_type`, `project_type` | | `status` | string | no | The status of runners to return, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 | | `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs | @@ -514,7 +514,7 @@ Example response: ## Enable a runner in project -Enable an available specific runner in the project. +Enable an available project runner in the project. ```plaintext POST /projects/:id/runners @@ -548,7 +548,7 @@ Example response: ## Disable a runner from project -Disable a specific runner from the project. It works only if the project isn't +Disable a project runner from the project. It works only if the project isn't the only project associated with the specified runner. If so, an error is returned. Use the call to [delete a runner](#delete-a-runner) instead. diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md index c93d2b6a82a..3f8c40a3aba 100644 --- a/doc/ci/caching/index.md +++ b/doc/ci/caching/index.md @@ -47,7 +47,7 @@ To ensure maximum availability of the cache, do one or more of the following: - [Tag your runners](../runners/configure_runners.md#use-tags-to-control-which-jobs-a-runner-can-run) and use the tag on jobs that share the cache. -- [Use runners that are only available to a particular project](../runners/runners_scope.md#prevent-a-specific-runner-from-being-enabled-for-other-projects). +- [Use runners that are only available to a particular project](../runners/runners_scope.md#prevent-a-project-runner-from-being-enabled-for-other-projects). - [Use a `key`](../yaml/index.md#cachekey) that fits your workflow. For example, you can configure a different cache for each branch. diff --git a/doc/ci/cloud_services/index.md b/doc/ci/cloud_services/index.md index b7129d92205..ab2a7a56e7e 100644 --- a/doc/ci/cloud_services/index.md +++ b/doc/ci/cloud_services/index.md @@ -8,15 +8,30 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - `CI_JOB_JWT` variable for reading secrets from Vault [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) in GitLab 12.10. > - `CI_JOB_JWT_V2` variable to support additional OIDC providers [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/346737) in GitLab 14.7. +> - [ID tokens](../yaml/index.md) to support any OIDC provider, including HashiCorp Vault, [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356986) in GitLab 15.7. -GitLab CI/CD supports [OpenID Connect (OIDC)](https://openid.net/connect/faq/) that allows your build and deployment job access to cloud credentials and services. Historically, teams stored secrets in projects or applied permissions on the GitLab Runner instance to build and deploy. To support this, a predefined variable named `CI_JOB_JWT_V2` is included in the CI/CD job allowing you to follow a scalable and least-privilege security approach. +GitLab CI/CD supports [OpenID Connect (OIDC)](https://openid.net/connect/faq/) to +give your build and deployment jobs access to cloud credentials and services. +Historically, teams stored secrets in projects or applied permissions on the GitLab Runner +instance to build and deploy. OIDC capable [ID tokens](../yaml/index.md#id_tokens) are configurable +in the CI/CD job allowing you to follow a scalable and least-privilege security approach. + +In GitLab 15.6 and earlier, you must use `CI_JOB_JWT_V2` instead of an ID token, +but it is not customizable. In GitLab 14.6 an earlier you must use the `CI_JOB_JWT`, which has limited support. ## Requirements - Account on GitLab. - Access to a cloud provider that supports OIDC to configure authorization and create roles. -The original implementation of `CI_JOB_JWT` supports [HashiCorp Vault integration](../examples/authenticating-with-hashicorp-vault/index.md). The updated implementation of `CI_JOB_JWT_V2` supports additional cloud providers with OIDC including AWS, Azure, GCP, and Vault. +ID tokens and `CI_JOB_JWT_V2` support cloud providers with OIDC, including: + +- AWS +- Azure +- GCP +- HashiCorp Vault + +The `CI_JOB_JWT` only supports the [HashiCorp Vault integration](../examples/authenticating-with-hashicorp-vault/index.md). NOTE: Configuring OIDC enables JWT token access to the target environments for all pipelines. @@ -25,10 +40,6 @@ review for the pipeline, focusing on the additional access. You can use the [sof as a starting point, and for more information about supply chain attacks, see [How a DevOps Platform helps protect against supply chain attacks](https://about.gitlab.com/blog/2021/04/28/devops-platform-supply-chain-attacks/). -The `CI_JOB_JWT_V2` variable is available for testing, but the full feature is planned -to be generally available when [issue 360657](https://gitlab.com/gitlab-org/gitlab/-/issues/360657) -is complete. - ## Use cases - Removes the need to store secrets in your GitLab group or project. Temporary credentials can be retrieved from your cloud provider through OIDC. @@ -39,18 +50,18 @@ is complete. ## How it works -Each job has a JSON web token (JWT) provided as a CI/CD [predefined variable](../variables/predefined_variables.md) named `CI_JOB_JWT` or `CI_JOB_JWT_V2`. This JWT can be used to authenticate with the OIDC-supported cloud provider such as AWS, Azure, GCP, or Vault. +Each job can be configured with ID tokens, which are provided as a CI/CD variable. These JWTs can be used to authenticate with the OIDC-supported cloud provider such as AWS, Azure, GCP, or Vault. The following fields are included in the JWT: | Field | When | Description | | ----------------------- | ------ | ----------- | +| `aud` | Always | Specified in the [ID tokens](../yaml/index.md#id_tokens) configuration | | `jti` | Always | Unique identifier for this token | | `iss` | Always | Issuer, the domain of your GitLab instance | | `iat` | Always | Issued at | | `nbf` | Always | Not valid before | | `exp` | Always | Expires at | -| `aud` | Always | Issuer, the domain of your GitLab instance | | `sub` | Always |`project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}` | | `namespace_id` | Always | Use this to scope to group or user level namespace by ID | | `namespace_path` | Always | Use this to scope to group or user level namespace by path | @@ -72,7 +83,7 @@ The following fields are included in the JWT: { "jti": "c82eeb0c-5c6f-4a33-abf5-4c474b92b558", "iss": "https://gitlab.example.com", - "aud": "https://gitlab.example.com", + "aud": "https://vault.example.com", "iat": 1585710286, "nbf": 1585798372, "exp": 1585713886, @@ -102,8 +113,8 @@ sequenceDiagram participant GitLab Note right of Cloud: Create OIDC identity provider Note right of Cloud: Create role with conditionals - Note left of GitLab: CI/CD job with CI_JOB_JWT_V2 - GitLab->>+Cloud: Call cloud API with CI_JOB_JWT_V2 + Note left of GitLab: CI/CD job with ID token + GitLab->>+Cloud: Call cloud API with ID token Note right of Cloud: Decode & verify JWT with public key (https://gitlab/-/jwks) Note right of Cloud: Validate audience defined in OIDC Note right of Cloud: Validate conditional (sub, aud) role @@ -115,14 +126,25 @@ sequenceDiagram 1. Create an OIDC identity provider in the cloud (for example, AWS, Azure, GCP, Vault). 1. Create a conditional role in the cloud service that filters to a group, project, branch, or tag. -1. The CI/CD job includes a predefined variable `CI_JOB_JWT_V2` that is a JWT token. You can use this token for authorization with your cloud API. +1. The CI/CD job includes an ID token which is a JWT token. You can use this token for authorization with your cloud API. 1. The cloud verifies the token, validates the conditional role from the payload, and returns a temporary credential. ## Configure a conditional role with OIDC claims -To configure the trust between GitLab and OIDC, you must create a conditional role in the cloud provider that checks against the JWT token. The condition is validated against the JWT to create a trust specifically against two claims, the audience and subject. +To configure the trust between GitLab and OIDC, you must create a conditional role in the cloud provider that checks against the JWT. +The condition is validated against the JWT to create a trust specifically against two claims, the audience and subject. + +- Audience or `aud`: Configured as part of the ID token: + + ```yaml + job_needing_oidc_auth: + id_tokens: + OIDC_TOKEN: + aud: https://oidc.provider.com + script: + - echo $OIDC_TOKEN + ``` -- Audience or `aud`: The URL of the GitLab instance. This is defined when the identity provider is first configured in your cloud provider. - Subject or `sub`: A concatenation of metadata describing the GitLab CI/CD workflow including the group, project, branch, and tag. The `sub` field is in the following format: - `project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}` diff --git a/doc/ci/pipelines/cicd_minutes.md b/doc/ci/pipelines/cicd_minutes.md index e69c510291d..1a94c253176 100644 --- a/doc/ci/pipelines/cicd_minutes.md +++ b/doc/ci/pipelines/cicd_minutes.md @@ -33,7 +33,7 @@ On self-managed GitLab instances: - Administrators can [assign more CI/CD minutes](#set-the-quota-of-cicd-minutes-for-a-specific-namespace) if a namespace uses all the CI/CD minutes in its monthly quota. -[Specific runners](../runners/runners_scope.md#specific-runners) are not subject to a quota of CI/CD minutes. +[Project runners](../runners/runners_scope.md#project-runners) are not subject to a quota of CI/CD minutes. ## Set the quota of CI/CD minutes for all namespaces @@ -216,9 +216,9 @@ The cost factors on self-managed instances are: #### Cost factor for community contributions to GitLab projects -Community contributors can use up to 300,000 minutes on shared runners when contributing to open source projects +Community contributors can use up to 300,000 minutes on shared runners when contributing to open source projects maintained by GitLab. The maximum of 300,000 minutes would only be possible if contributing exclusively to projects [part of the GitLab product](https://about.gitlab.com/handbook/engineering/metrics/#projects-that-are-part-of-the-product). The total number of minutes available on shared runners -is reduced by the CI/CD minutes used by pipelines from other projects. +is reduced by the CI/CD minutes used by pipelines from other projects. The 300,000 minutes applies to all SaaS tiers, and the cost factor calculation is: - `Monthly minute quota / 300,000 job duration minutes = Cost factor` diff --git a/doc/ci/pipelines/downstream_pipelines.md b/doc/ci/pipelines/downstream_pipelines.md index 1ada4c4fac1..b84bc28de3f 100644 --- a/doc/ci/pipelines/downstream_pipelines.md +++ b/doc/ci/pipelines/downstream_pipelines.md @@ -473,6 +473,7 @@ a few different methods, based on where the variable is created or defined. ### Pass YAML-defined CI/CD variables You can use the `variables` keyword to pass CI/CD variables to a downstream pipeline. +These variables are "trigger variables" for [variable precedence](../variables/index.md#cicd-variable-precedence). For example: diff --git a/doc/ci/runners/configure_runners.md b/doc/ci/runners/configure_runners.md index 28a856e3dda..dc57e8f570f 100644 --- a/doc/ci/runners/configure_runners.md +++ b/doc/ci/runners/configure_runners.md @@ -28,7 +28,7 @@ On GitLab.com, you cannot override the job timeout for shared runners and must u To set the maximum job timeout: 1. In a project, go to **Settings > CI/CD > Runners**. -1. Select your specific runner to edit the settings. +1. Select your project runner to edit the settings. 1. Enter a value under **Maximum job timeout**. Must be 10 minutes or more. If not defined, the [project's job timeout setting](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run) is used. @@ -89,7 +89,7 @@ To protect or unprotect a runner: 1. Check the **Protected** option. 1. Select **Save changes**. -![specific runners edit icon](img/protected_runners_check_box_v14_1.png) +![Protect project runners checkbox](img/protected_runners_check_box_v14_1.png) ### Forks @@ -150,7 +150,7 @@ the source of the HTTP requests it makes to GitLab when polling for jobs. The IP address is always kept up to date so if the runner IP changes it automatically updates in GitLab. -The IP address for shared runners and specific runners can be found in +The IP address for shared runners and project runners can be found in different places. ### Determine the IP address of a shared runner @@ -164,16 +164,16 @@ the GitLab instance. To determine this: ![shared runner IP address](img/shared_runner_ip_address_14_5.png) -### Determine the IP address of a specific runner +### Determine the IP address of a project runner -To can find the IP address of a runner for a specific project, +To can find the IP address of a runner for a project project, you must have the Owner role for the project. 1. Go to the project's **Settings > CI/CD** and expand the **Runners** section. 1. On the details page you should see a row for **IP Address**. -![specific runner IP address](img/specific_runner_ip_address.png) +![Project runner IP address](img/project_runner_ip_address.png) ## Use tags to control which jobs a runner can run diff --git a/doc/ci/runners/img/specific_runner_ip_address.png b/doc/ci/runners/img/project_runner_ip_address.png Binary files differindex e08663109ba..e08663109ba 100644 --- a/doc/ci/runners/img/specific_runner_ip_address.png +++ b/doc/ci/runners/img/project_runner_ip_address.png diff --git a/doc/ci/runners/runners_scope.md b/doc/ci/runners/runners_scope.md index 6354a519810..402478e81ba 100644 --- a/doc/ci/runners/runners_scope.md +++ b/doc/ci/runners/runners_scope.md @@ -11,8 +11,8 @@ Runners are available based on who you want to have access: - [Shared runners](#shared-runners) are available to all groups and projects in a GitLab instance. - [Group runners](#group-runners) are available to all projects and subgroups in a group. -- [Specific runners](#specific-runners) are associated with specific projects. - Typically, specific runners are used for one project at a time. +- [Project runners](#project-runners) are associated with specific projects. + Typically, project runners are used by one project at a time. ## Shared runners @@ -241,78 +241,78 @@ You must have the Owner role for the group. You must remove it from each project first. 1. On the confirmation dialog, select **OK**. -## Specific runners +## Project runners -Use _specific runners_ when you want to use runners for specific projects. For example, +Use _project runners_ when you want to use runners for specific projects. For example, when you have: - Jobs with specific requirements, like a deploy job that requires credentials. - Projects with a lot of CI activity that can benefit from being separate from other runners. -You can set up a specific runner to be used by multiple projects. Specific runners +You can set up a project runner to be used by multiple projects. Project runners must be enabled for each project explicitly. -Specific runners process jobs by using a first in, first out ([FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))) queue. +Project runners process jobs by using a first in, first out ([FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))) queue. NOTE: -Specific runners do not get shared with forked projects automatically. +Project runners do not get shared with forked projects automatically. A fork *does* copy the CI/CD settings of the cloned repository. -### Create a specific runner +### Create a project runner -You can create a specific runner for your self-managed GitLab instance or for GitLab.com. +You can create a project runner for your self-managed GitLab instance or for GitLab.com. Prerequisite: - You must have at least the Maintainer role for the project. -To create a specific runner: +To create a project runner: 1. [Install GitLab Runner](https://docs.gitlab.com/runner/install/). 1. On the top bar, select **Main menu > Projects** and find the project where you want to use the runner. 1. On the left sidebar, select **Settings > CI/CD**. 1. Expand **Runners**. -1. In the **Specific runners** section, note the URL and token. +1. In the **Project runners** section, note the URL and token. 1. [Register the runner](https://docs.gitlab.com/runner/register/). The runner is now enabled for the project. -### Enable a specific runner for a different project +### Enable a project runner for a different project -After a specific runner is created, you can enable it for other projects. +After a project runner is created, you can enable it for other projects. Prerequisites: You must have at least the Maintainer role for: - The project where the runner is already enabled. - The project where you want to enable the runner. -- The specific runner must not be [locked](#prevent-a-specific-runner-from-being-enabled-for-other-projects). +- The project runner must not be [locked](#prevent-a-project-runner-from-being-enabled-for-other-projects). -To enable a specific runner for a project: +To enable a project runner for a project: 1. On the top bar, select **Main menu > Projects** and find the project where you want to enable the runner. 1. On the left sidebar, select **Settings > CI/CD**. 1. Expand **Runners**. -1. In the **Specific runners** area, by the runner you want, select **Enable for this project**. +1. In the **Project runners** area, by the runner you want, select **Enable for this project**. -You can edit a specific runner from any of the projects it's enabled for. +You can edit a project runner from any of the projects it's enabled for. The modifications, which include unlocking and editing tags and the description, affect all projects that use the runner. -An administrator can [enable the runner for multiple projects](../../user/admin_area/settings/continuous_integration.md#enable-a-specific-runner-for-multiple-projects). +An administrator can [enable the runner for multiple projects](../../user/admin_area/settings/continuous_integration.md#enable-a-project-runner-for-multiple-projects). -### Prevent a specific runner from being enabled for other projects +### Prevent a project runner from being enabled for other projects -You can configure a specific runner so it is "locked" and cannot be enabled for other projects. +You can configure a project runner so it is "locked" and cannot be enabled for other projects. This setting can be enabled when you first [register a runner](https://docs.gitlab.com/runner/register/), but can also be changed later. -To lock or unlock a specific runner: +To lock or unlock a project runner: 1. On the top bar, select **Main menu > Projects** and find the project where you want to enable the runner. 1. On the left sidebar, select **Settings > CI/CD**. 1. Expand **Runners**. -1. Find the specific runner you want to lock or unlock. Make sure it's enabled. You cannot lock shared or group runners. +1. Find the project runner you want to lock or unlock. Make sure it's enabled. You cannot lock shared or group runners. 1. Select **Edit** (**{pencil}**). 1. Select the **Lock to current projects** checkbox. 1. Select **Save changes**. diff --git a/doc/ci/secrets/id_token_authentication.md b/doc/ci/secrets/id_token_authentication.md new file mode 100644 index 00000000000..ab1cb66e1a9 --- /dev/null +++ b/doc/ci/secrets/id_token_authentication.md @@ -0,0 +1,103 @@ +--- +stage: Verify +group: Pipeline Authoring +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +type: tutorial +--- + +# OpenID Connect (OIDC) Authentication Using ID Tokens **(FREE)** + +You can authenticate with third party services using GitLab CI/CD's +[ID tokens](../yaml/index.md#id_tokens). + +## ID Tokens + +[ID tokens](../yaml/index.md#id_tokens) are JSON Web Tokens (JWTs) that can be added to a GitLab CI/CD job. They can be used for OIDC +authentication with third-party services, and are used by the [`secrets`](../yaml/index.md#secrets) keyword to authenticate with HashiCorp Vault. + +ID tokens are configured in the `.gitlab-ci.yml`. For example: + +```yaml +job_with_id_tokens: + id_tokens: + FIRST_ID_TOKEN: + aud: https://first.service.com + SECOND_ID_TOKEN: + aud: https://second.service.com + script: + - first-service-authentication-script.sh $FIRST_ID_TOKEN + - second-service-authentication-script.sh $SECOND_ID_TOKEN +``` + +In this example, the two tokens have different `aud` claims. Third party services can be configured to reject tokens +that do not have an `aud` claim matching their bound audience. Use this functionality to reduce the number of +services with which a token can authenticate. This reduces the severity of having a token compromised. + +## Manual ID Token authentication + +You can use ID tokens for OIDC authentication with a third party service. For example: + +```yaml +manual_authentication: + variables: + VAULT_ADDR: http://vault.example.com:8200 + image: vault:latest + id_tokens: + VAULT_ID_TOKEN: + aud: http://vault.example.com:8200 + script: + - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-example jwt=$VAULT_ID_TOKEN)" + - export PASSWORD="$(vault kv get -field=password secret/myproject/example/db)" + - my-authentication-script.sh $VAULT_TOKEN $PASSWORD +``` + +## Automatic ID Token authentication with HashiCorp Vault **(PREMIUM)** + +You can use ID tokens to automatically fetch secrets from HashiCorp Vault with the +[`secrets`](../yaml/index.md#secrets) keyword. + +### Enable automatic ID token authentication + +To enable automatic ID token authentication: + +1. On the top bar, select **Main menu > Projects** and find your project. +1. On the left sidebar, select **Settings > CI/CD**. +1. Expand **Token Access**. +1. Toggle **Limit JSON Web Token (JWT) access** to enabled. + +### Configure automatic ID Token authentication + +If one ID token is defined, the `secrets` keyword automatically uses it to authenticate with Vault. For example: + +```yaml +job_with_secrets: + id_tokens: + VAULT_ID_TOKEN: + aud: https://example.vault.com + secrets: + PROD_DB_PASSWORD: + vault: example/db/password # authenticates using $VAULT_ID_TOKEN + script: + - access-prod-db.sh --token $PROD_DB_PASSWORD +``` + +If more than one ID token is defined, use the `token` keyword to specify which token should be used. For example: + +```yaml +job_with_secrets: + id_tokens: + FIRST_ID_TOKEN: + aud: https://first.service.com + SECOND_ID_TOKEN: + aud: https://second.service.com + secrets: + FIRST_DB_PASSWORD: + vault: first/db/password + token: $FIRST_ID_TOKEN + SECOND_DB_PASSWORD: + vault: second/db/password + token: $SECOND_ID_TOKEN + script: + - access-first-db.sh --token $FIRST_DB_PASSWORD + - access-second-db.sh --token $SECOND_DB_PASSWORD +``` diff --git a/doc/ci/secrets/index.md b/doc/ci/secrets/index.md index ba12508beeb..14f771431e5 100644 --- a/doc/ci/secrets/index.md +++ b/doc/ci/secrets/index.md @@ -23,10 +23,16 @@ GitLab has selected [Vault by HashiCorp](https://www.vaultproject.io) as the first supported provider, and [KV-V2](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2) as the first supported secrets engine. -GitLab authenticates using Vault's +By default, GitLab authenticates using Vault's [JSON Web Token (JWT) authentication method](https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication), using -the [JSON Web Token](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) (`CI_JOB_JWT`) -introduced in GitLab 12.10. +the [JSON Web Token](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) (`CI_JOB_JWT`). + +[ID tokens](../yaml/index.md#id_tokens) is the preferred secure way to authenticate with Vault, +because ID tokens are defined per-job. GitLab can also authenticate with Vault by using the `CI_JOB_JWT`, +but that token is provided to every job, which can be a security risk. + +The [Authenticating and Reading Secrets With HashiCorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) +tutorial has more details about authenticating with ID tokens. You must [configure your Vault server](#configure-your-vault-server) before you can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job). @@ -106,9 +112,14 @@ After [configuring your Vault server](#configure-your-vault-server), you can use the secrets stored in Vault by defining them with the `vault` keyword: ```yaml -secrets: - DATABASE_PASSWORD: - vault: production/db/password@ops # translates to secret `ops/data/production/db`, field `password` +job_using_vault: + id_tokens: + VAULT_ID_TOKEN: + aud: https://gitlab.com + secrets: + DATABASE_PASSWORD: + vault: production/db/password@ops # translates to secret `ops/data/production/db`, field `password` + token: $VAULT_ID_TOKEN ``` In this example: @@ -125,9 +136,13 @@ To overwrite the default behavior, set the `file` option explicitly: ```yaml secrets: + id_tokens: + VAULT_ID_TOKEN: + aud: https://gitlab.com DATABASE_PASSWORD: vault: production/db/password@ops file: false + token: $VAULT_ID_TOKEN ``` In this example, the secret value is put directly in the `DATABASE_PASSWORD` variable @@ -177,8 +192,8 @@ Always restrict your roles to a project or namespace by using one of the provide claims like `project_id` or `namespace_id`. Without these restrictions, any JWT generated by this GitLab instance may be allowed to authenticate using this role. -For a full list of `CI_JOB_JWT` claims, read the -[How it works](../examples/authenticating-with-hashicorp-vault/index.md#how-it-works) section of the +For a full list of ID token JWT claims, read the +[How It Works](../examples/authenticating-with-hashicorp-vault/index.md#how-it-works) section of the [Authenticating and Reading Secrets With HashiCorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) tutorial. You can also specify some attributes for the resulting Vault tokens, such as time-to-live, @@ -188,7 +203,7 @@ for the JSON web token method. ## Using a self-signed Vault server -When the Vault server is using a self-signed certificate, you will see the following error in the job logs: +When the Vault server is using a self-signed certificate, you see the following error in the job logs: ```plaintext ERROR: Job failed (system failure): resolving secrets: initializing Vault service: preparing authenticated client: checking Vault server health: Get https://vault.example.com:8000/v1/sys/health?drsecondarycode=299&performancestandbycode=299&sealedcode=299&standbycode=299&uninitcode=299: x509: certificate signed by unknown authority @@ -197,7 +212,7 @@ ERROR: Job failed (system failure): resolving secrets: initializing Vault servic You have two options to solve this error: - Add the self-signed certificate to the GitLab Runner server's CA store. - If you deployed GitLab Runner using the [Helm chart](https://docs.gitlab.com/runner/install/kubernetes.html), you will have to create your own GitLab Runner image. + If you deployed GitLab Runner using the [Helm chart](https://docs.gitlab.com/runner/install/kubernetes.html), you have to create your own GitLab Runner image. - Use the `VAULT_CACERT` environment variable to configure GitLab Runner to trust the certificate: - If you are using systemd to manage GitLab Runner, see [how to add an environment variable for GitLab Runner](https://docs.gitlab.com/runner/configuration/init.html#setting-custom-environment-variables). - If you deployed GitLab Runner using the [Helm chart](https://docs.gitlab.com/runner/install/kubernetes.html): diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 3796eed54e1..60fa2096cee 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -3794,6 +3794,43 @@ job: - The `file` keyword is a setting for the CI/CD variable and must be nested under the CI/CD variable name, not in the `vault` section. +#### `secrets:token` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356986) in GitLab 15.8. + +Use `secrets:token` to explicitly select a token to use when authenticating with Vault by referencing the token's CI/CD variable. + +This keyword has no effect if [**Limit JSON Web Token (JWT) access**](../secrets/id_token_authentication.md#enable-automatic-id-token-authentication) +is disabled. + +**Keyword type**: Job keyword. You can use it only as part of a job. + +**Possible inputs**: + +- The name of an ID token + +**Example of `secrets:token`**: + +```yaml +job: + id_tokens: + AWS_TOKEN: + aud: https://aws.example.com + VAULT_TOKEN: + aud: https://vault.example.com + secrets: + DB_PASSWORD: + vault: gitlab/production/db + token: $VAULT_TOKEN +``` + +**Additional details**: + +- When the `token` keyword is not set and **Limit JSON Web Token (JWT) access** enabled, the first ID token + is used to authenticate. +- When **Limit JSON Web Token (JWT) access** is disabled, the `token` keyword is ignored and the `CI_JOB_JWT` + CI/CD variable is used to authenticate. + ### `services` Use `services` to specify any additional Docker images that your scripts require to run successfully. The [`services` image](../services/index.md) is linked diff --git a/doc/development/application_slis/rails_request_apdex.md b/doc/development/application_slis/rails_request_apdex.md index 8fcd725f74d..ae53ddc1c00 100644 --- a/doc/development/application_slis/rails_request_apdex.md +++ b/doc/development/application_slis/rails_request_apdex.md @@ -231,15 +231,7 @@ end ### Error budget attribution and ownership This SLI is used for service level monitoring. It feeds into the -[error budget for stage groups](../stage_group_observability/index.md#error-budget). For this -particular SLI, we have opted everyone out by default to give time to -set the correct urgencies on endpoints before it affects a group's -error budget. - -To include this SLI in the error budget, remove the `rails_requests` -from the `ignored_components` array in the entry for your group. Read -more about what is configurable in the -[runbooks documentation](https://gitlab.com/gitlab-com/runbooks/-/tree/master/services#teamsyml). +[error budget for stage groups](../stage_group_observability/index.md#error-budget). For more information, read the epic for [defining custom SLIs and incorporating them into error budgets](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/525)). diff --git a/doc/development/cicd/index.md b/doc/development/cicd/index.md index 2a60ca18169..641d0ea4f6f 100644 --- a/doc/development/cicd/index.md +++ b/doc/development/cicd/index.md @@ -69,7 +69,7 @@ looks for the next jobs to be transitioned towards completion. While doing that, updates the status of jobs, stages and the overall pipeline. On the right side of the diagram we have a list of [runners](../../ci/runners/index.md) -connected to the GitLab instance. These can be shared runners, group runners, or project-specific runners. +connected to the GitLab instance. These can be shared runners, group runners, or project runners. The communication between runners and the Rails server occurs through a set of API endpoints, grouped as the `Runner API Gateway`. @@ -131,7 +131,7 @@ After the runner is [registered](https://docs.gitlab.com/runner/register/) using - The type of runner it is registered as: - a shared runner - a group runner - - a project specific runner + - a project runner - Any associated tags. The runner initiates the communication by requesting jobs to execute with `POST /api/v4/jobs/request`. Although polling happens every few seconds, we leverage caching through HTTP headers to reduce the server-side work load if the job queue doesn't change. diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 921bb13721b..bc38909acf4 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -320,7 +320,7 @@ You can use these fake tokens as examples: | Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` | | Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` | | CI/CD variable | `Li8j-mLUVA3eZYjPfd_H` | -| Specific runner token | `yrnZW46BrtBFqM7xDzE7dddd` | +| Project runner token | `yrnZW46BrtBFqM7xDzE7dddd` | | Shared runner token | `6Vk7ZsosqQyfreAxXTZr` | | Trigger token | `be20d8dcc028677c931e04f3871a9b` | | Webhook secret token | `6XhDroRcYPM5by_h-HLY` | diff --git a/doc/development/feature_categorization/index.md b/doc/development/feature_categorization/index.md index e0c6d4cfa9f..ff8c149e631 100644 --- a/doc/development/feature_categorization/index.md +++ b/doc/development/feature_categorization/index.md @@ -176,7 +176,7 @@ Or the feature category can be specified in the action itself: ```ruby module API class Users < ::API::Base - get ':id', feature_category: :users do + get ':id', feature_category: :user_profile do end end end diff --git a/doc/development/workhorse/gitlab_features.md b/doc/development/workhorse/gitlab_features.md index 8b899242935..824ebda0441 100644 --- a/doc/development/workhorse/gitlab_features.md +++ b/doc/development/workhorse/gitlab_features.md @@ -66,7 +66,6 @@ memory than it costs to have Workhorse look after it. - Workhorse does not connect to PostgreSQL, only to Rails and (optionally) Redis. - We assume that all requests that reach Workhorse pass through an upstream proxy such as NGINX or Apache first. -- Workhorse does not accept HTTPS connections. - Workhorse does not clean up idle client connections. - We assume that all requests to Rails pass through Workhorse. diff --git a/doc/topics/autodevops/requirements.md b/doc/topics/autodevops/requirements.md index f5e06843e60..aa9627c1247 100644 --- a/doc/topics/autodevops/requirements.md +++ b/doc/topics/autodevops/requirements.md @@ -139,7 +139,7 @@ To make full use of Auto DevOps with Kubernetes, you need: [Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html). Runners should be registered as [shared runners](../../ci/runners/runners_scope.md#shared-runners) - for the entire GitLab instance, or [specific runners](../../ci/runners/runners_scope.md#specific-runners) + for the entire GitLab instance, or [project runners](../../ci/runners/runners_scope.md#project-runners) that are assigned to specific projects. - **Prometheus** (for [Auto Monitoring](stages.md#auto-monitoring)) diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index 3081fd1666d..483954ce7a9 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -44,12 +44,12 @@ Any time a new project is created, the shared runners are available. As an administrator you can set either a global or namespace-specific limit on the number of [CI/CD minutes](../../../ci/pipelines/cicd_minutes.md) you can use. -## Enable a specific runner for multiple projects +## Enable a project runner for multiple projects -If you have already registered a [specific runner](../../../ci/runners/runners_scope.md#specific-runners) +If you have already registered a [project runner](../../../ci/runners/runners_scope.md#project-runners) you can assign that runner to other projects. -To enable a specific runner for more than one project: +To enable a project runner for more than one project: 1. On the top bar, select **Main menu > Admin**. 1. From the left sidebar, select **CI/CD > Runners**. diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 1275e3a21e4..fcb02e76095 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > Introduced in GitLab 11.0. -This page describes SAML for groups. For instance-wide SAML on self-managed GitLab instances, see [SAML OmniAuth Provider](../../../integration/saml.md). +This page describes SAML for groups. For instance-wide SAML on self-managed GitLab instances, see [SAML SSO for self-managed GitLab instances](../../../integration/saml.md). [View the differences between SaaS and Self-Managed Authentication and Authorization Options](../../../administration/auth/index.md#saas-vs-self-managed-comparison). SAML on GitLab.com allows users to sign in through their SAML identity provider. If the user is not already a member, the sign-in process automatically adds the user to the appropriate group. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 0c4b742c73a..6bbcb23bf2c 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -222,8 +222,8 @@ The following table lists project permissions available for each role: 1. On self-managed GitLab instances, guest users are able to perform this action only on public and internal projects (not on private projects). [External users](admin_area/external_users.md) - must be given explicit access even if the project is internal. Users with the Guest role on GitLab.com are - only able to perform this action on public projects because internal visibility is not available. + must be given explicit access even if the project is internal. Users with the Guest role on GitLab.com are + only able to perform this action on public projects because internal visibility is not available. 2. Guest users can only view the [confidential issues](project/issues/confidential_issues.md) they created themselves or are assigned to. 3. Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [protected branches](project/protected_branches.md). 4. If the [branch is protected](project/protected_branches.md), this depends on the access given to Developers and Maintainers. @@ -287,7 +287,7 @@ More details about the permissions for some project-level features follow. | View a job with [debug logging](../ci/variables/index.md#enable-debug-logging) | | | | ✓ | ✓ | ✓ | | Use pipeline editor | | | | ✓ | ✓ | ✓ | | Run [interactive web terminals](../ci/interactive_web_terminal/index.md) | | | | ✓ | ✓ | ✓ | -| Add specific runners to project | | | | | ✓ | ✓ | +| Add project runners to project | | | | | ✓ | ✓ | | Clear runner caches manually | | | | | ✓ | ✓ | | Enable shared runners in project | | | | | ✓ | ✓ | | Manage CI/CD settings | | | | | ✓ | ✓ | diff --git a/doc/user/todos.md b/doc/user/todos.md index 221e89d658c..cf42f371b94 100644 --- a/doc/user/todos.md +++ b/doc/user/todos.md @@ -117,17 +117,27 @@ Hi, please message @frank :incoming_envelope: ## Actions that mark a to-do item as done -Any action to an issue, merge request, or epic marks its +Various actions on the to-do item object (like issue, merge request, or epic) mark its corresponding to-do item as done. -Actions that dismiss to-do items include: +To-do items are marked as done if you: -- Changing the assignee -- Changing the milestone -- Closing the issue or merge request -- Adding or removing a label -- Commenting on the issue -- Resolving a [design discussion thread](project/issues/design_management.md#resolve-a-discussion-thread-on-a-design) +- Add an award emoji to the description or comment. +- Add or remove a label. +- Change the assignee. +- Change the milestone. +- Close the to-do item's object. +- Create a comment. +- Edit the description. +- Resolve a [design discussion thread](project/issues/design_management.md#resolve-a-discussion-thread-on-a-design). + +To-do items are **not** marked as done if you: + +- Add a linked item (like a [linked issue](project/issues/related_issues.md)). +- Add a child item (like [child epic](group/epics/manage_epics.md#multi-level-child-epics) or [task](tasks.md)). +- Add a [time entry](project/time_tracking.md). +- Assign yourself. +- Change the [health status](project/issues/managing_issues.md#health-status). If someone else closes, merges, or takes action on an issue, merge request, or epic, your to-do item remains pending. diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 4a6c58b4987..f2f0f32261a 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -13,7 +13,7 @@ module API helpers do params :deprecated_filter_params do optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, - desc: 'Deprecated: Use `type` or `status` instead. The scope of specific runners to return' + desc: 'Deprecated: Use `type` or `status` instead. The scope of runners to return' end params :filter_params do @@ -111,9 +111,9 @@ module API present paginate(runners), with: Entities::Ci::Runner end - desc 'Get all runners - shared and specific' do + desc 'Get all runners - shared and project' do summary 'List all runners' - detail 'Get a list of all runners in the GitLab instance (specific and shared). ' \ + detail 'Get a list of all runners in the GitLab instance (shared and project). ' \ 'Access is restricted to users with administrator access.' success Entities::Ci::Runner failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']] @@ -286,7 +286,7 @@ module API end desc 'Enable a runner in project' do - detail "Enable an available specific runner in the project." + detail "Enable an available project runner in the project." success Entities::Ci::Runner failure [[400, 'Bad Request'], [403, 'No access granted'], [403, 'Runner is a group runner'], [403, 'Runner is locked'], @@ -308,7 +308,7 @@ module API end desc "Disable project's runner" do - summary "Disable a specific runner from the project" + summary "Disable a project runner from the project" detail "It works only if the project isn't the only project associated with the specified runner. " \ "If so, an error is returned. Use the call to delete a runner instead." success Entities::Ci::Runner diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 5077f02fcc1..7271615b623 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -9,7 +9,11 @@ module API before { authenticate_non_get! } - feature_category :projects, ['/projects/:id/custom_attributes', '/projects/:id/custom_attributes/:key'] + feature_category :projects, %w[ + /projects/:id/custom_attributes + /projects/:id/custom_attributes/:key + /projects/:id/share_locations + ] PROJECT_ATTACHMENT_SIZE_EXEMPT = 1.gigabyte @@ -351,6 +355,20 @@ module API render_validation_error!(project) end end + + desc 'Returns group that can be shared with the given project' do + success Entities::Group + end + params do + requires :id, type: Integer, desc: 'The id of the project' + optional :search, type: String, desc: 'Return list of groups matching the search criteria' + end + get ':id/share_locations' do + groups = ::Groups::AcceptingProjectSharesFinder.new(current_user, user_project, declared_params(include_missing: false)).execute + + present_groups groups + end + # rubocop: enable CodeReuse/ActiveRecord end diff --git a/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb b/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb index 914ababa5c2..db3f98bc2ba 100644 --- a/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb +++ b/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb @@ -68,7 +68,7 @@ module Gitlab def valid_json?(metadata) Oj.load(metadata) true - rescue Oj::ParseError, EncodingError, JSON::ParserError, Encoding::UndefinedConversionError + rescue Oj::ParseError, EncodingError, JSON::ParserError, JSON::GeneratorError, Encoding::UndefinedConversionError false end diff --git a/lib/gitlab/ci/ansi2json/line.rb b/lib/gitlab/ci/ansi2json/line.rb index abe2f272ca7..21fc2980cdc 100644 --- a/lib/gitlab/ci/ansi2json/line.rb +++ b/lib/gitlab/ci/ansi2json/line.rb @@ -26,7 +26,7 @@ module Gitlab # Without forcing the encoding to UTF-8 and then replacing # invalid UTF-8 sequences we can get an error when serializing # the Hash to JSON. - # Encoding::UndefinedConversionError: + # Encoding::UndefinedConversionError (or possibly JSON::GeneratorError in json 2.6.1+): # "\xE2" from ASCII-8BIT to UTF-8 { text: encode_utf8_no_detect(text) }.tap do |result| result[:style] = style.to_s if style.set? diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb index f6431483a15..7aabf699a59 100644 --- a/lib/gitlab/etag_caching/middleware.rb +++ b/lib/gitlab/etag_caching/middleware.rb @@ -65,13 +65,15 @@ module Gitlab status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429 - add_instrument_for_cache_hit(status_code, route, request) - Gitlab::ApplicationContext.push( feature_category: route.feature_category, - caller_id: route.caller_id + caller_id: route.caller_id, + remote_ip: request.remote_ip ) + request.env[Gitlab::Metrics::RequestsRackMiddleware::REQUEST_URGENCY_KEY] = route.urgency + add_instrument_for_cache_hit(status_code, route, request) + new_headers = { 'ETag' => etag, 'X-Gitlab-From-Cache' => 'true' @@ -102,7 +104,10 @@ module Gitlab format: request.format.ref, method: request.request_method, path: request.filtered_path, - status: status + status: status, + metadata: Gitlab::ApplicationContext.current, + request_urgency: route.urgency.name, + target_duration_s: route.urgency.duration } ActiveSupport::Notifications.instrument( diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb index 684afc6762a..754b54fda81 100644 --- a/lib/gitlab/etag_caching/router.rb +++ b/lib/gitlab/etag_caching/router.rb @@ -3,24 +3,34 @@ module Gitlab module EtagCaching module Router - Route = Struct.new(:router, :regexp, :name, :feature_category, :caller_id) do + Route = Struct.new(:router, :regexp, :name, :feature_category, :caller_id, :urgency, keyword_init: true) do delegate :match, to: :regexp delegate :cache_key, to: :router end module Helpers - def build_route(attrs) - EtagCaching::Router::Route.new(self, *attrs) + def build_graphql_route(regexp, name, feature_category) + EtagCaching::Router::Route.new( + router: self, + regexp: regexp, + name: name, + feature_category: feature_category, + # This information can be loaded from the graphql query, but is not + # included yet + # https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/665 + caller_id: nil, + urgency: Gitlab::EndpointAttributes::DEFAULT_URGENCY + ) end - def build_rails_route(attrs) - regexp, name, controller, action_name = *attrs + def build_rails_route(regexp, name, controller, action_name) EtagCaching::Router::Route.new( - self, - regexp, - name, - controller.feature_category_for_action(action_name).to_s, - controller.endpoint_id_for_action(action_name).to_s + router: self, + regexp: regexp, + name: name, + feature_category: controller.feature_category_for_action(action_name).to_s, + caller_id: controller.endpoint_id_for_action(action_name).to_s, + urgency: controller.urgency_for_action(action_name) ) end end diff --git a/lib/gitlab/etag_caching/router/graphql.rb b/lib/gitlab/etag_caching/router/graphql.rb index 1f56670ee7f..7a0fb2ac269 100644 --- a/lib/gitlab/etag_caching/router/graphql.rb +++ b/lib/gitlab/etag_caching/router/graphql.rb @@ -23,7 +23,7 @@ module Gitlab 'on_demand_scans', 'dynamic_application_security_testing' ] - ].map(&method(:build_route)).freeze + ].map { |attrs| build_graphql_route(*attrs) }.freeze def self.match(request) return unless request.path_info == graphql_api_path diff --git a/lib/gitlab/etag_caching/router/rails.rb b/lib/gitlab/etag_caching/router/rails.rb index d80c003fe53..2924370f494 100644 --- a/lib/gitlab/etag_caching/router/rails.rb +++ b/lib/gitlab/etag_caching/router/rails.rb @@ -104,7 +104,7 @@ module Gitlab ::Projects::MergeRequests::ContentController, :cached_widget ] - ].map(&method(:build_rails_route)).freeze + ].map { |attrs| build_rails_route(*attrs) }.freeze # Overridden in EE to add more routes def self.all_routes diff --git a/lib/gitlab/json.rb b/lib/gitlab/json.rb index 8332e4f6d56..bdfbe2041cd 100644 --- a/lib/gitlab/json.rb +++ b/lib/gitlab/json.rb @@ -103,7 +103,7 @@ module Gitlab opts = standardize_opts(opts) Oj.load(string, opts) - rescue Oj::ParseError, EncodingError, Encoding::UndefinedConversionError => ex + rescue Oj::ParseError, EncodingError, Encoding::UndefinedConversionError, JSON::GeneratorError => ex raise parser_error, ex end diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb index cfdac5264e0..ca7b0f443c5 100644 --- a/lib/gitlab/metrics/requests_rack_middleware.rb +++ b/lib/gitlab/metrics/requests_rack_middleware.rb @@ -27,6 +27,8 @@ module Gitlab 'not_owned', 'source_code_management', FEATURE_CATEGORY_DEFAULT].freeze + REQUEST_URGENCY_KEY = 'gitlab.request_urgency' + def initialize(app) @app = app end @@ -142,7 +144,9 @@ module Gitlab def urgency_for_env(env) endpoint_urgency = - if env['api.endpoint'].present? + if env[REQUEST_URGENCY_KEY].present? + env[REQUEST_URGENCY_KEY] + elsif env['api.endpoint'].present? env['api.endpoint'].options[:for].try(:urgency_for_app, env['api.endpoint']) elsif env['action_controller.instance'].present? && env['action_controller.instance'].respond_to?(:urgency) env['action_controller.instance'].urgency diff --git a/lib/gitlab/redis/duplicate_jobs.rb b/lib/gitlab/redis/duplicate_jobs.rb deleted file mode 100644 index c76d298da18..00000000000 --- a/lib/gitlab/redis/duplicate_jobs.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Redis - # Pseudo-store to transition `Gitlab::SidekiqMiddleware::DuplicateJobs` from - # using `Sidekiq.redis` to using the `SharedState` redis store. - class DuplicateJobs < ::Gitlab::Redis::Wrapper - class << self - def store_name - 'SharedState' - end - - private - - def redis - primary_store = ::Redis.new(Gitlab::Redis::SharedState.params) - - # `Sidekiq.redis` is a namespaced redis connection. This means keys are actually being stored under - # "resque:gitlab:resque:gitlab:duplicate:". For backwards compatibility, we make the secondary store - # namespaced in the same way, but omit it from the primary so keys have proper format there. - # rubocop:disable Cop/RedisQueueUsage - secondary_store = ::Redis::Namespace.new( - Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE, redis: ::Redis.new(Gitlab::Redis::Queues.params) - ) - # rubocop:enable Cop/RedisQueueUsage - - MultiStore.new(primary_store, secondary_store, name.demodulize) - end - end - end - end -end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 828cf65fb82..eccd500e910 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -120,7 +120,8 @@ module Gitlab @debian_version_regex ||= %r{ \A(?: (?:([0-9]{1,9}):)? (?# epoch) - ([0-9][0-9a-z\.+~]*-?){1,15} (?# version-revision) + ([0-9][0-9a-z\.+~]*) (?# version) + (-[0-9a-z\.+~]+){0,14} (?# -revision) (?<!-) )\z}xi.freeze end diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb index 4f7cd340461..b6e2209b475 100644 --- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb +++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb @@ -251,16 +251,8 @@ module Gitlab !scheduled? && options[:if_deduplicated] == :reschedule_once end - def with_redis - if Feature.enabled?(:use_primary_and_secondary_stores_for_duplicate_jobs) || - Feature.enabled?(:use_primary_store_as_default_for_duplicate_jobs) - # TODO: Swap for Gitlab::Redis::SharedState after store transition - # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/923 - Gitlab::Redis::DuplicateJobs.with { |redis| yield redis } - else - # Keep the old behavior intact if neither feature flag is turned on - Sidekiq.redis { |redis| yield redis } # rubocop:disable Cop/SidekiqRedisCall - end + def with_redis(&block) + Sidekiq.redis(&block) # rubocop:disable Cop/SidekiqRedisCall end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 96b50bf19e3..d82867713c5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4713,9 +4713,6 @@ msgstr "" msgid "Any milestone" msgstr "" -msgid "Any namespace" -msgstr "" - msgid "App ID" msgstr "" @@ -5980,9 +5977,6 @@ msgstr "" msgid "Available on-demand" msgstr "" -msgid "Available specific runners" -msgstr "" - msgid "Avatar for %{assigneeName}" msgstr "" @@ -19867,6 +19861,9 @@ msgstr "" msgid "GroupSelect|Select a group" msgstr "" +msgid "GroupSettings|Analytics Dashboards" +msgstr "" + msgid "GroupSettings|Applied to all subgroups unless overridden by a group owner. Groups already added to the project lose access." msgstr "" @@ -19969,6 +19966,12 @@ msgstr "" msgid "GroupSettings|Select parent group" msgstr "" +msgid "GroupSettings|Select the project containing Analytics Dashboards configuration files" +msgstr "" + +msgid "GroupSettings|Select the project containing Analytics Dashboards configuration files." +msgstr "" + msgid "GroupSettings|Select the project containing the %{code_start}.gitlab/insights.yml%{code_end} file" msgstr "" @@ -20008,6 +20011,9 @@ msgstr "" msgid "GroupSettings|What are badges?" msgstr "" +msgid "GroupSettings|What is Analytics Dashboards?" +msgstr "" + msgid "GroupSettings|What is Insights?" msgstr "" @@ -22847,6 +22853,9 @@ msgstr "" msgid "Invalid status" msgstr "" +msgid "Invalid tags" +msgstr "" + msgid "Invalid two-factor code." msgstr "" @@ -27142,6 +27151,9 @@ msgstr "" msgid "MlExperimentsEmptyState|No Experiments to Show" msgstr "" +msgid "Modal updated" +msgstr "" + msgid "ModalButton|Add projects" msgstr "" @@ -36272,6 +36284,9 @@ msgstr "" msgid "Runners|Assigned Projects (%{projectCount})" msgstr "" +msgid "Runners|Assigned project runners" +msgstr "" + msgid "Runners|Associated with one or more projects" msgstr "" @@ -36486,6 +36501,9 @@ msgstr "" msgid "Runners|Project" msgstr "" +msgid "Runners|Project runners" +msgstr "" + msgid "Runners|Property Name" msgstr "" @@ -36666,6 +36684,9 @@ msgstr "" msgid "Runners|The runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?" msgstr "" +msgid "Runners|These runners are assigned to this project." +msgstr "" + msgid "Runners|This group currently has 1 stale runner." msgid_plural "Runners|This group currently has %d stale runners." msgstr[0] "" @@ -36755,7 +36776,7 @@ msgstr "" msgid "Runners|Yes, start deleting stale runners" msgstr "" -msgid "Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared or group runner." +msgid "Runners|You can set up a project runner to be used by multiple projects but you cannot make this a shared or group runner." msgstr "" msgid "Runners|You have used %{quotaUsed} out of %{quotaLimit} of your shared Runners pipeline minutes." @@ -36770,10 +36791,10 @@ msgstr "" msgid "Runners|paused" msgstr "" -msgid "Runners|shared" +msgid "Runners|project" msgstr "" -msgid "Runners|specific" +msgid "Runners|shared" msgstr "" msgid "Runner|Owner" @@ -39544,6 +39565,15 @@ msgstr "" msgid "SlackIntegration|You may need to reinstall the GitLab for Slack app when we %{linkStart}make updates or change permissions%{linkEnd}." msgstr "" +msgid "SlackModal|Are you sure you want to change the project?" +msgstr "" + +msgid "SlackModal|If you change the project, you'll lose any text entered in the form." +msgstr "" + +msgid "SlackModal|If you've entered some text, consider saving it somewhere to avoid losing any content." +msgstr "" + msgid "SlackService|1. %{slash_command_link_start}Add a slash command%{slash_command_link_end} in your Slack team using this information:" msgstr "" @@ -39859,6 +39889,9 @@ msgstr "" msgid "Something went wrong while updating assignees" msgstr "" +msgid "Something went wrong while updating the modal." +msgstr "" + msgid "Something went wrong while updating your list settings" msgstr "" @@ -40195,9 +40228,6 @@ msgstr "" msgid "Spam log successfully submitted as ham." msgstr "" -msgid "Specific runners" -msgstr "" - msgid "Specified URL cannot be used: \"%{reason}\"" msgstr "" @@ -42815,9 +42845,6 @@ msgstr "" msgid "These runners are shared across projects in this group." msgstr "" -msgid "These runners are specific to this project." -msgstr "" - msgid "These variables are inherited from the parent group." msgstr "" diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index d0439a18158..55a73796d9d 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe RegistrationsController do +RSpec.describe RegistrationsController, feature_category: :users do include TermsHelper include FullNameHelper @@ -215,11 +215,18 @@ RSpec.describe RegistrationsController do property: member.id.to_s, user: member.reload.user ) + + expect_snowplow_event( + category: 'RegistrationsController', + action: 'create_user', + label: 'invited', + user: member.reload.user + ) end end context 'when member does not exist from the session key value' do - let(:originating_member_id) { -1 } + let(:originating_member_id) { nil } it 'does not track invite acceptance' do subject @@ -229,6 +236,13 @@ RSpec.describe RegistrationsController do action: 'accepted', label: 'invite_email' ) + + expect_snowplow_event( + category: 'RegistrationsController', + action: 'create_user', + label: 'signup', + user: member.reload.user + ) end end end diff --git a/spec/factories/packages/packages.rb b/spec/factories/packages/packages.rb index 1da4f0cedbc..3b7cd8298be 100644 --- a/spec/factories/packages/packages.rb +++ b/spec/factories/packages/packages.rb @@ -72,7 +72,7 @@ FactoryBot.define do transient do without_package_files { false } - file_metadatum_trait { :keep } + file_metadatum_trait { processing? ? :unknown : :keep } published_in { :create } end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 32248ac1af2..c58ea0c8ce0 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -651,7 +651,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do visit edit_admin_runner_path(runner) end - it 'removed specific runner from project' do + it 'removed project runner from project' do within '[data-testid="assigned-projects"]' do click_on 'Disable' end diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 2c5b7d66e2f..069c74dcd2a 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Contributions Calendar', :js, feature_category: :users do +RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do include MobileHelpers let(:user) { create(:user) } diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index edb3dacc2cc..ec6f67a59a8 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Dashboard > Activity', feature_category: :users do +RSpec.describe 'Dashboard > Activity', feature_category: :user_profile do let(:user) { create(:user) } before do diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 34f99765c29..a12772543e6 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Tooltips on .timeago dates', :js, feature_category: :users do +RSpec.describe 'Tooltips on .timeago dates', :js, feature_category: :user_profile do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, name: 'test', namespace: user.namespace) } diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb index 5bf1566fa31..c4dc78bf45b 100644 --- a/spec/features/dashboard/project_member_activity_index_spec.rb +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project member activity', :js, feature_category: :users do +RSpec.describe 'Project member activity', :js, feature_category: :user_profile do let(:user) { create(:user) } let(:project) { create(:project, :public, name: 'x', namespace: user.namespace) } diff --git a/spec/features/explore/topics_spec.rb b/spec/features/explore/topics_spec.rb index b5787a2dba8..dcccaea8c80 100644 --- a/spec/features/explore/topics_spec.rb +++ b/spec/features/explore/topics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Explore Topics', feature_category: :users do +RSpec.describe 'Explore Topics', feature_category: :user_profile do context 'when no topics exist' do it 'renders empty message', :aggregate_failures do visit topics_explore_projects_path diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb index f54a51c9ac9..14fddf5d84c 100644 --- a/spec/features/explore/user_explores_projects_spec.rb +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User explores projects', feature_category: :users do +RSpec.describe 'User explores projects', feature_category: :user_profile do context 'when some projects exist' do let_it_be(:archived_project) { create(:project, :archived) } let_it_be(:internal_project) { create(:project, :internal) } diff --git a/spec/features/file_uploads/user_avatar_spec.rb b/spec/features/file_uploads/user_avatar_spec.rb index 06501e09866..062c47d5310 100644 --- a/spec/features/file_uploads/user_avatar_spec.rb +++ b/spec/features/file_uploads/user_avatar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Upload a user avatar', :js, feature_category: :users do +RSpec.describe 'Upload a user avatar', :js, feature_category: :user_profile do let_it_be(:user, reload: true) { create(:user) } let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') } diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index 3e565dd8eab..d876a5804bd 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User Cluster', :js, feature_category: :users do +RSpec.describe 'User Cluster', :js, feature_category: :user_profile do include GoogleApi::CloudPlatformHelpers let(:group) { create(:group) } diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 87a65438768..e190dfda937 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile account page', :js, feature_category: :users do +RSpec.describe 'Profile account page', :js, feature_category: :user_profile do include Spec::Support::Helpers::ModalHelpers let(:user) { create(:user) } diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb index 82c45862e07..7e4308106be 100644 --- a/spec/features/profiles/account_spec.rb +++ b/spec/features/profiles/account_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Account', :js, feature_category: :users do +RSpec.describe 'Profile > Account', :js, feature_category: :user_profile do let(:user) { create(:user, username: 'foo') } before do diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb index 5c20735cf35..0de4ad47f9a 100644 --- a/spec/features/profiles/active_sessions_spec.rb +++ b/spec/features/profiles/active_sessions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state, feature_category: :users do +RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state, feature_category: :user_profile do include Spec::Support::Helpers::ModalHelpers let(:user) do diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb index 14fdb8ba56f..299ecdb6032 100644 --- a/spec/features/profiles/chat_names_spec.rb +++ b/spec/features/profiles/chat_names_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Chat', feature_category: :users do +RSpec.describe 'Profile > Chat', feature_category: :user_profile do let(:user) { create(:user) } let(:integration) { create(:integration) } diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb index e8ea227c072..d00cef1f6f0 100644 --- a/spec/features/profiles/emails_spec.rb +++ b/spec/features/profiles/emails_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Emails', feature_category: :users do +RSpec.describe 'Profile > Emails', feature_category: :user_profile do let(:user) { create(:user) } let(:other_user) { create(:user) } diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb index 1d014f983e7..0fc59f21489 100644 --- a/spec/features/profiles/gpg_keys_spec.rb +++ b/spec/features/profiles/gpg_keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > GPG Keys', feature_category: :users do +RSpec.describe 'Profile > GPG Keys', feature_category: :user_profile do let(:user) { create(:user, email: GpgHelpers::User2.emails.first) } before do diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb index 7a2a12d8dca..5acc59b190f 100644 --- a/spec/features/profiles/keys_spec.rb +++ b/spec/features/profiles/keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > SSH Keys', feature_category: :users do +RSpec.describe 'Profile > SSH Keys', feature_category: :user_profile do let(:user) { create(:user) } before do diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb index 80d05fd5cc7..d088f73f9df 100644 --- a/spec/features/profiles/oauth_applications_spec.rb +++ b/spec/features/profiles/oauth_applications_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Applications', feature_category: :users do +RSpec.describe 'Profile > Applications', feature_category: :user_profile do include Spec::Support::Helpers::ModalHelpers let(:user) { create(:user) } diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index b324ee17873..c0c573d2f20 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Password', feature_category: :users do +RSpec.describe 'Profile > Password', feature_category: :user_profile do let(:user) { create(:user) } def fill_passwords(password, confirmation) diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 3087d7ff296..a050e87241b 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Profile > Personal Access Tokens', :js, feature_category: :users do +RSpec.describe 'Profile > Personal Access Tokens', :js, feature_category: :user_profile do include Spec::Support::Helpers::ModalHelpers include Spec::Support::Helpers::AccessTokenHelpers diff --git a/spec/features/profiles/two_factor_auths_spec.rb b/spec/features/profiles/two_factor_auths_spec.rb index 8dddaad11c3..e8ff8416722 100644 --- a/spec/features/profiles/two_factor_auths_spec.rb +++ b/spec/features/profiles/two_factor_auths_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Two factor auths', feature_category: :users do +RSpec.describe 'Two factor auths', feature_category: :user_profile do include Spec::Support::Helpers::ModalHelpers context 'when signed in' do diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb index 197a33c355d..89887cb4772 100644 --- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb +++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'Profile > Notifications > User changes notified_of_own_activity setting', :js, -feature_category: :users do +feature_category: :user_profile do let(:user) { create(:user) } before do diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb index 1a231f1d269..f7a9850355a 100644 --- a/spec/features/profiles/user_edit_preferences_spec.rb +++ b/spec/features/profiles/user_edit_preferences_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User edit preferences profile', :js, feature_category: :users do +RSpec.describe 'User edit preferences profile', :js, feature_category: :user_profile do include StubLanguagesTranslationPercentage # Empty value doesn't change the levels diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 67604292090..3819723cc09 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User edit profile', feature_category: :users do +RSpec.describe 'User edit profile', feature_category: :user_profile do include Spec::Support::Helpers::Features::NotesHelpers let_it_be(:user) { create(:user) } diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb index 179da61b8ed..e3c4a797431 100644 --- a/spec/features/profiles/user_manages_applications_spec.rb +++ b/spec/features/profiles/user_manages_applications_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User manages applications', feature_category: :users do +RSpec.describe 'User manages applications', feature_category: :user_profile do let_it_be(:user) { create(:user) } let_it_be(:new_application_path) { applications_profile_path } let_it_be(:index_path) { oauth_applications_path } diff --git a/spec/features/profiles/user_manages_emails_spec.rb b/spec/features/profiles/user_manages_emails_spec.rb index 16a9fbc2f47..b875dfec217 100644 --- a/spec/features/profiles/user_manages_emails_spec.rb +++ b/spec/features/profiles/user_manages_emails_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User manages emails', feature_category: :users do +RSpec.describe 'User manages emails', feature_category: :user_profile do let(:user) { create(:user) } let(:other_user) { create(:user) } diff --git a/spec/features/profiles/user_search_settings_spec.rb b/spec/features/profiles/user_search_settings_spec.rb index 09ee8ddeaab..932ea11075a 100644 --- a/spec/features/profiles/user_search_settings_spec.rb +++ b/spec/features/profiles/user_search_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User searches their settings', :js, feature_category: :users do +RSpec.describe 'User searches their settings', :js, feature_category: :user_profile do let_it_be(:user) { create(:user) } before do diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb index d212982f4e3..1295a0b6150 100644 --- a/spec/features/profiles/user_visits_notifications_tab_spec.rb +++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User visits the notifications tab', :js, feature_category: :users do +RSpec.describe 'User visits the notifications tab', :js, feature_category: :user_profile do let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/features/profiles/user_visits_profile_account_page_spec.rb b/spec/features/profiles/user_visits_profile_account_page_spec.rb index 1cf34478ecf..8ff9cbc242e 100644 --- a/spec/features/profiles/user_visits_profile_account_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_account_page_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User visits the profile account page', feature_category: :users do +RSpec.describe 'User visits the profile account page', feature_category: :user_profile do let(:user) { create(:user) } before do diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb index 726cca4a4bd..90f24c5b866 100644 --- a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb +++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User visits the authentication log', feature_category: :users do +RSpec.describe 'User visits the authentication log', feature_category: :user_profile do let(:user) { create(:user) } context 'when user signed in' do diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index e3940973c46..d690589b893 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User visits the profile preferences page', :js, feature_category: :users do +RSpec.describe 'User visits the profile preferences page', :js, feature_category: :user_profile do include ListboxHelpers let(:user) { create(:user) } diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb index 7fca0f24deb..8ea7ad206c5 100644 --- a/spec/features/profiles/user_visits_profile_spec.rb +++ b/spec/features/profiles/user_visits_profile_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User visits their profile', feature_category: :users do +RSpec.describe 'User visits their profile', feature_category: :user_profile do let_it_be_with_refind(:user) { create(:user) } before do diff --git a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb index 8467e9abeaf..547e47ead77 100644 --- a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User visits the profile SSH keys page', feature_category: :users do +RSpec.describe 'User visits the profile SSH keys page', feature_category: :user_profile do let(:user) { create(:user) } before do diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index e7c2452af93..b2ddf427c0d 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -64,10 +64,10 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do context 'when a project_type runner is activated on the project' do let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project]) } - it 'user sees the specific runner' do + it 'user sees the project runner' do visit project_runners_path(project) - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do expect(page).to have_content(project_runner.display_name) end @@ -76,30 +76,30 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do expect(page).to have_content(project_runner.platform) end - it 'user can pause and resume the specific runner' do + it 'user can pause and resume the project runner' do visit project_runners_path(project) - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do expect(page).to have_link('Pause') end click_on 'Pause' - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do expect(page).to have_link('Resume') end click_on 'Resume' - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do expect(page).to have_link('Pause') end end - it 'user removes an activated specific runner if this is last project for that runners' do + it 'user removes an activated project runner if this is last project for that runners' do visit project_runners_path(project) - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do click_on 'Remove runner' end @@ -109,7 +109,7 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do it 'user edits the runner to be protected' do visit project_runners_path(project) - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do first('[data-testid="edit-runner-link"]').click end @@ -129,7 +129,7 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do it 'user edits runner not to run untagged jobs' do visit project_runners_path(project) - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do first('[data-testid="edit-runner-link"]').click end @@ -189,7 +189,7 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do end end - context 'when a specific runner exists in another project' do + context 'when a project runner exists in another project' do let(:another_project) { create(:project) } let!(:project_runner) { create(:ci_runner, :project, projects: [another_project]) } @@ -197,20 +197,20 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do another_project.add_maintainer(user) end - it 'user enables and disables a specific runner' do + it 'user enables and disables a project runner' do visit project_runners_path(project) - within '.available-specific-runners' do + within '[data-testid="available_project_runners"]' do click_on 'Enable for this project' end - expect(page.find('.activated-specific-runners')).to have_content(project_runner.display_name) + expect(page.find('[data-testid="assigned_project_runners"]')).to have_content(project_runner.display_name) - within '.activated-specific-runners' do + within '[data-testid="assigned_project_runners"]' do click_on 'Disable for this project' end - expect(page.find('.available-specific-runners')).to have_content(project_runner.display_name) + expect(page.find('[data-testid="available_project_runners"]')).to have_content(project_runner.display_name) end end diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index 78cede77fea..02e98905662 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User uploads avatar to group', feature_category: :users do +RSpec.describe 'User uploads avatar to group', feature_category: :user_profile do it 'they see the new avatar' do user = create(:user) group = create(:group) diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index fb62b5eadc5..f1023f17d3e 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User uploads avatar to profile', feature_category: :users do +RSpec.describe 'User uploads avatar to profile', feature_category: :user_profile do let!(:user) { create(:user) } let(:avatar_file_path) { Rails.root.join('spec', 'fixtures', 'dk.png') } diff --git a/spec/features/users/add_email_to_existing_account_spec.rb b/spec/features/users/add_email_to_existing_account_spec.rb index 8c4e68c454f..ea39e5c5a49 100644 --- a/spec/features/users/add_email_to_existing_account_spec.rb +++ b/spec/features/users/add_email_to_existing_account_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'AdditionalEmailToExistingAccount', feature_category: :users do +RSpec.describe 'AdditionalEmailToExistingAccount', feature_category: :user_profile do describe 'add secondary email associated with account' do let_it_be(:user) { create(:user) } let_it_be(:email) { create(:email, user: user) } diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb index 489e7d61ff9..6c6c5ca2b27 100644 --- a/spec/features/users/overview_spec.rb +++ b/spec/features/users/overview_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Overview tab on a user profile', :js, feature_category: :users do +RSpec.describe 'Overview tab on a user profile', :js, feature_category: :user_profile do let(:user) { create(:user) } let(:contributed_project) { create(:project, :public, :repository) } diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb index a2604cd298a..bc37c9941ce 100644 --- a/spec/features/users/rss_spec.rb +++ b/spec/features/users/rss_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User RSS', feature_category: :users do +RSpec.describe 'User RSS', feature_category: :user_profile do let(:user) { create(:user) } let(:path) { user_path(create(:user)) } diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 318dd688fa4..17c33398c0e 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User page', feature_category: :users do +RSpec.describe 'User page', feature_category: :user_profile do include ExternalAuthorizationServiceHelpers let_it_be(:user) { create(:user, bio: '<b>Lorem</b> <i>ipsum</i> dolor sit <a href="https://example.com">amet</a>') } diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 1057ae48c7d..11ff318c346 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -44,7 +44,7 @@ RSpec.shared_examples 'Signup name validation' do |field, max_length, label| end end -RSpec.describe 'Signup', feature_category: :users do +RSpec.describe 'Signup', feature_category: :user_profile do include TermsHelper let(:new_user) { build_stubbed(:user) } diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb index 7d2137b81b8..5c61843e558 100644 --- a/spec/features/users/terms_spec.rb +++ b/spec/features/users/terms_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Users > Terms', :js, feature_category: :users do +RSpec.describe 'Users > Terms', :js, feature_category: :user_profile do include TermsHelper let!(:term) { create(:term, terms: 'By accepting, you promise to be nice!') } diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js index 140609161d4..13c9f206033 100644 --- a/spec/frontend/groups/components/app_spec.js +++ b/spec/frontend/groups/components/app_spec.js @@ -11,6 +11,7 @@ import eventHub from '~/groups/event_hub'; import GroupsService from '~/groups/service/groups_service'; import GroupsStore from '~/groups/store/groups_store'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_FORBIDDEN } from '~/lib/utils/http_status'; import * as urlUtilities from '~/lib/utils/url_utility'; import setWindowLocation from 'helpers/set_window_location_helper'; @@ -336,7 +337,7 @@ describe('AppComponent', () => { it('should show appropriate error flash message if request forbids to leave group', () => { const message = 'Failed to leave the group. Please make sure you are not the only owner.'; - jest.spyOn(vm.service, 'leaveGroup').mockRejectedValue({ status: 403 }); + jest.spyOn(vm.service, 'leaveGroup').mockRejectedValue({ status: HTTP_STATUS_FORBIDDEN }); jest.spyOn(vm.store, 'removeGroup'); vm.leaveGroup(childGroupItem, groupItem); diff --git a/spec/frontend/pages/admin/projects/components/namespace_select_spec.js b/spec/frontend/pages/admin/projects/components/namespace_select_spec.js index 909349569a8..834d14e0fb3 100644 --- a/spec/frontend/pages/admin/projects/components/namespace_select_spec.js +++ b/spec/frontend/pages/admin/projects/components/namespace_select_spec.js @@ -1,99 +1,142 @@ -import { mount } from '@vue/test-utils'; +import { GlCollapsibleListbox } from '@gitlab/ui'; import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; import NamespaceSelect from '~/pages/admin/projects/components/namespace_select.vue'; -describe('Dropdown select component', () => { +const TEST_USER_NAMESPACE = { id: 10, kind: 'user', full_path: 'Administrator' }; +const TEST_GROUP_NAMESPACE = { id: 20, kind: 'group', full_path: 'GitLab Org' }; + +describe('NamespaceSelect', () => { let wrapper; - const mountDropdown = (propsData) => { - wrapper = mount(NamespaceSelect, { propsData }); + const createComponent = (propsData) => { + wrapper = shallowMountExtended(NamespaceSelect, { propsData }); }; - const findDropdownToggle = () => wrapper.find('button.dropdown-toggle'); - const findNamespaceInput = () => wrapper.find('[data-testid="hidden-input"]'); - const findFilterInput = () => wrapper.find('.namespace-search-box input'); - const findDropdownOption = (match) => { - const buttons = wrapper - .findAll('button.dropdown-item') - .filter((node) => node.text().match(match)); - return buttons.length ? buttons.at(0) : buttons; - }; + const findListbox = () => wrapper.findComponent(GlCollapsibleListbox); + const findNamespaceInput = () => wrapper.findByTestId('hidden-input'); - const setFieldValue = async (field, value) => { - await field.setValue(value); - field.trigger('blur'); + const search = async (searchString) => { + findListbox().vm.$emit('search', searchString); + await waitForPromises(); }; beforeEach(() => { setHTMLFixture('<div class="test-container"></div>'); - jest.spyOn(Api, 'namespaces').mockImplementation((_, callback) => - callback([ - { id: 10, kind: 'user', full_path: 'Administrator' }, - { id: 20, kind: 'group', full_path: 'GitLab Org' }, - ]), - ); + jest + .spyOn(Api, 'namespaces') + .mockImplementation((_, callback) => callback([TEST_USER_NAMESPACE, TEST_GROUP_NAMESPACE])); }); afterEach(() => { resetHTMLFixture(); }); - it('creates a hidden input if fieldName is provided', () => { - mountDropdown({ fieldName: 'namespace-input' }); + describe('on mount', () => { + beforeEach(() => { + createComponent(); + }); + + it('does not show hidden input', () => { + expect(findNamespaceInput().exists()).toBe(false); + }); + + it('sets appropriate props', async () => { + expect(findListbox().props()).toMatchObject({ + items: [ + { text: 'user: Administrator', value: '10' }, + { text: 'group: GitLab Org', value: '20' }, + ], + headerText: NamespaceSelect.i18n.headerText, + resetButtonLabel: NamespaceSelect.i18n.reset, + toggleText: 'Namespace', + searchPlaceholder: NamespaceSelect.i18n.searchPlaceholder, + searching: false, + searchable: true, + }); + }); + }); + + it('with fieldName, shows hidden input', () => { + createComponent({ fieldName: 'namespace-input' }); expect(findNamespaceInput().exists()).toBe(true); expect(findNamespaceInput().attributes('name')).toBe('namespace-input'); }); - describe('clicking dropdown options', () => { + describe('select', () => { + describe.each` + selectId | expectToggleText + ${String(TEST_USER_NAMESPACE.id)} | ${`user: ${TEST_USER_NAMESPACE.full_path}`} + ${String(TEST_GROUP_NAMESPACE.id)} | ${`group: ${TEST_GROUP_NAMESPACE.full_path}`} + `('clicking listbox options (selectId=$selectId)', ({ selectId, expectToggleText }) => { + beforeEach(async () => { + createComponent({ fieldName: 'namespace-input' }); + findListbox().vm.$emit('select', selectId); + await nextTick(); + }); + + it('updates hidden field', () => { + expect(findNamespaceInput().attributes('value')).toBe(selectId); + }); + + it('updates the listbox value', async () => { + expect(findListbox().props()).toMatchObject({ + selected: selectId, + toggleText: expectToggleText, + }); + }); + + it('triggers a setNamespace event upon selection', () => { + expect(wrapper.emitted('setNamespace')).toEqual([[selectId]]); + }); + }); + }); + + describe('search', () => { it('retrieves namespaces based on filter query', async () => { - mountDropdown(); + createComponent(); - await setFieldValue(findFilterInput(), 'test'); + // Add space to assert that `?.trim` is called + await search('test '); expect(Api.namespaces).toHaveBeenCalledWith('test', expect.anything()); }); - it('updates the dropdown value based upon selection', async () => { - mountDropdown({ fieldName: 'namespace-input' }); - - // wait for dropdown options to populate - await nextTick(); - - expect(findDropdownOption('user: Administrator').exists()).toBe(true); - expect(findDropdownOption('group: GitLab Org').exists()).toBe(true); - expect(findDropdownOption('group: Foobar').exists()).toBe(false); + it('when not found, does not change the placeholder text', async () => { + createComponent({ + origSelectedId: String(TEST_USER_NAMESPACE.id), + origSelectedText: `user: ${TEST_USER_NAMESPACE.full_path}`, + }); - findDropdownOption('user: Administrator').trigger('click'); - await nextTick(); + await search('not exist'); - expect(findNamespaceInput().attributes('value')).toBe('10'); - expect(findDropdownToggle().text()).toBe('user: Administrator'); + expect(findListbox().props()).toMatchObject({ + selected: String(TEST_USER_NAMESPACE.id), + toggleText: `user: ${TEST_USER_NAMESPACE.full_path}`, + }); }); + }); - it('triggers a setNamespace event upon selection', async () => { - mountDropdown(); - - // wait for dropdown options to populate - await nextTick(); - - findDropdownOption('group: GitLab Org').trigger('click'); - - expect(wrapper.emitted('setNamespace')).toHaveLength(1); - expect(wrapper.emitted('setNamespace')[0][0]).toBe(20); + describe('reset', () => { + beforeEach(() => { + createComponent(); + findListbox().vm.$emit('reset'); }); - it('displays "Any Namespace" option when showAny prop provided', () => { - mountDropdown({ showAny: true }); - expect(wrapper.text()).toContain('Any namespace'); + it('updates the listbox value', () => { + expect(findListbox().props()).toMatchObject({ + selected: null, + toggleText: 'Namespace', + }); }); - it('does not display "Any Namespace" option when showAny prop not provided', () => { - mountDropdown(); - expect(wrapper.text()).not.toContain('Any namespace'); + it('triggers a setNamespace event upon reset', () => { + expect(wrapper.emitted('setNamespace')).toEqual([[null]]); }); }); }); diff --git a/spec/graphql/mutations/achievements/create_spec.rb b/spec/graphql/mutations/achievements/create_spec.rb index 4bad6164314..12b8ff67549 100644 --- a/spec/graphql/mutations/achievements/create_spec.rb +++ b/spec/graphql/mutations/achievements/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::Achievements::Create, feature_category: :users do +RSpec.describe Mutations::Achievements::Create, feature_category: :user_profile do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/graphql/types/achievements/achievement_type_spec.rb b/spec/graphql/types/achievements/achievement_type_spec.rb index 5c98753ac66..d8cd52f78d7 100644 --- a/spec/graphql/types/achievements/achievement_type_spec.rb +++ b/spec/graphql/types/achievements/achievement_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['Achievement'], feature_category: :users do +RSpec.describe GitlabSchema.types['Achievement'], feature_category: :user_profile do include GraphqlHelpers let(:fields) do diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index ce1558c4097..f2a53b373ac 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -32,6 +32,7 @@ RSpec.describe Types::Ci::JobType do pipeline playable previousStageJobsOrNeeds + project queued_at queued_duration refName diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index 45cb960cf20..096341b1ba9 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['User'], feature_category: :users do +RSpec.describe GitlabSchema.types['User'], feature_category: :user_profile do specify { expect(described_class.graphql_name).to eq('User') } specify do diff --git a/spec/graphql/types/users/email_type_spec.rb b/spec/graphql/types/users/email_type_spec.rb index fb484915428..107bbf81e98 100644 --- a/spec/graphql/types/users/email_type_spec.rb +++ b/spec/graphql/types/users/email_type_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe GitlabSchema.types['Email'], feature_category: :users do +RSpec.describe GitlabSchema.types['Email'], feature_category: :user_profile do it 'has the correct fields' do expected_fields = [ :id, diff --git a/spec/graphql/types/users/namespace_commit_email_type_spec.rb b/spec/graphql/types/users/namespace_commit_email_type_spec.rb index ccab881676e..27e50f7285e 100644 --- a/spec/graphql/types/users/namespace_commit_email_type_spec.rb +++ b/spec/graphql/types/users/namespace_commit_email_type_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe GitlabSchema.types['NamespaceCommitEmail'], feature_category: :users do +RSpec.describe GitlabSchema.types['NamespaceCommitEmail'], feature_category: :user_profile do it 'has the correct fields' do expected_fields = [ :id, diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index 3736914669a..326e27fa716 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::ContributionsCalendar, feature_category: :users do +RSpec.describe Gitlab::ContributionsCalendar, feature_category: :user_profile do let_it_be_with_reload(:contributor) { create(:user) } let_it_be_with_reload(:user) { create(:user) } let(:travel_time) { nil } diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb index da5eaf2e4ab..81751511270 100644 --- a/spec/lib/gitlab/etag_caching/middleware_spec.rb +++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb @@ -123,7 +123,15 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state format: :html, method: 'GET', path: enabled_path, - status: status_code + status: status_code, + request_urgency: :low, + target_duration_s: 5, + metadata: a_hash_including( + { + 'meta.caller_id' => 'Projects::NotesController#index', + 'meta.feature_category' => 'team_planning' + } + ) } end @@ -172,10 +180,11 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state expect(headers).to include('X-Gitlab-From-Cache' => 'true') end - it "pushes route's feature category to the context" do + it "pushes expected information in to the context" do expect(Gitlab::ApplicationContext).to receive(:push).with( feature_category: 'team_planning', - caller_id: 'Projects::NotesController#index' + caller_id: 'Projects::NotesController#index', + remote_ip: '127.0.0.1' ) _, _, _ = middleware.call(build_request(path, if_none_match)) @@ -291,7 +300,8 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state { 'PATH_INFO' => path, 'HTTP_IF_NONE_MATCH' => if_none_match, 'rack.input' => '', - 'REQUEST_METHOD' => 'GET' } + 'REQUEST_METHOD' => 'GET', + 'REMOTE_ADDR' => '127.0.0.1' } end def payload_for(event) diff --git a/spec/lib/gitlab/etag_caching/router/graphql_spec.rb b/spec/lib/gitlab/etag_caching/router/graphql_spec.rb index 792f02f8cda..fae468ab84f 100644 --- a/spec/lib/gitlab/etag_caching/router/graphql_spec.rb +++ b/spec/lib/gitlab/etag_caching/router/graphql_spec.rb @@ -18,6 +18,12 @@ RSpec.describe Gitlab::EtagCaching::Router::Graphql do end end + it 'applies the default urgency for every route', :aggregate_failures do + described_class::ROUTES.each do |route| + expect(route.urgency).to be(Gitlab::EndpointAttributes::DEFAULT_URGENCY) + end + end + def match_route(path, header) described_class.match( double(path_info: path, diff --git a/spec/lib/gitlab/etag_caching/router/rails_spec.rb b/spec/lib/gitlab/etag_caching/router/rails_spec.rb index da6c11e3cb1..251f634aac1 100644 --- a/spec/lib/gitlab/etag_caching/router/rails_spec.rb +++ b/spec/lib/gitlab/etag_caching/router/rails_spec.rb @@ -109,17 +109,23 @@ RSpec.describe Gitlab::EtagCaching::Router::Rails do it 'has a valid feature category for every route', :aggregate_failures do feature_categories = Gitlab::FeatureCategories.default.categories - described_class::ROUTES.each do |route| + described_class.all_routes.each do |route| expect(feature_categories).to include(route.feature_category), "#{route.name} has a category of #{route.feature_category}, which is not valid" end end it 'has a caller_id for every route', :aggregate_failures do - described_class::ROUTES.each do |route| + described_class.all_routes.each do |route| expect(route.caller_id).to include('#'), "#{route.name} has caller_id #{route.caller_id}, which is not valid" end end + it 'has an urgency for every route', :aggregate_failures do + described_class.all_routes.each do |route| + expect(route.urgency).to be_an_instance_of(Gitlab::EndpointAttributes::Config::RequestUrgency) + end + end + def match_route(path) described_class.match(double(path_info: path)) end diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb index 8d2183bc03d..bc07f9a99a5 100644 --- a/spec/lib/gitlab/etag_caching/router_spec.rb +++ b/spec/lib/gitlab/etag_caching/router_spec.rb @@ -11,6 +11,7 @@ RSpec.describe Gitlab::EtagCaching::Router do expect(result).to be_present expect(result.name).to eq 'project_pipelines' expect(result.router).to eq Gitlab::EtagCaching::Router::Rails + expect(result.urgency).to eq Projects::PipelinesController.urgency_for_action(:index) end end @@ -21,6 +22,7 @@ RSpec.describe Gitlab::EtagCaching::Router do expect(result).to be_present expect(result.name).to eq 'pipelines_graph' expect(result.router).to eq Gitlab::EtagCaching::Router::Graphql + expect(result.urgency).to eq ::Gitlab::EndpointAttributes::DEFAULT_URGENCY end it 'matches pipeline sha endpoint' do diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb index 61c690b85e9..d6644bc12d2 100644 --- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb @@ -415,6 +415,35 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures, fea end end + context 'A request with urgency set on the env (from ETag-caching)' do + let(:env) do + { described_class::REQUEST_URGENCY_KEY => Gitlab::EndpointAttributes::Config::REQUEST_URGENCIES[:medium], + 'REQUEST_METHOD' => 'GET' } + end + + it 'records the request with the correct urgency' do + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.1) + expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown', + request_urgency: :medium + }, + success: true + ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown', + request_urgency: :medium + }, + error: false + ) + + subject.call(env) + end + end + context 'An unknown request' do let(:env) do { 'REQUEST_METHOD' => 'GET' } diff --git a/spec/lib/gitlab/redis/duplicate_jobs_spec.rb b/spec/lib/gitlab/redis/duplicate_jobs_spec.rb deleted file mode 100644 index 4d46a567032..00000000000 --- a/spec/lib/gitlab/redis/duplicate_jobs_spec.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Redis::DuplicateJobs do - # Note: this is a pseudo-store in front of `SharedState`, meant only as a tool - # to move away from `Sidekiq.redis` for duplicate job data. Thus, we use the - # same store configuration as the former. - let(:instance_specific_config_file) { "config/redis.shared_state.yml" } - let(:environment_config_file_name) { "GITLAB_REDIS_SHARED_STATE_CONFIG_FILE" } - - include_examples "redis_shared_examples" - - describe '#pool' do - subject { described_class.pool } - - around do |example| - clear_pool - example.run - ensure - clear_pool - end - - context 'store connection settings' do - let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" } - let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" } - - before do - allow(Gitlab::Redis::SharedState).to receive(:config_file_name).and_return(config_new_format_host) - allow(Gitlab::Redis::Queues).to receive(:config_file_name).and_return(config_new_format_socket) - end - - it 'instantiates an instance of MultiStore' do - subject.with do |redis_instance| - expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore) - - expect(redis_instance.primary_store.connection[:id]).to eq("redis://test-host:6379/99") - expect(redis_instance.primary_store.connection[:namespace]).to be_nil - expect(redis_instance.secondary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0") - expect(redis_instance.secondary_store.connection[:namespace]).to eq("resque:gitlab") - - expect(redis_instance.instance_name).to eq('DuplicateJobs') - end - end - end - - # Make sure they current namespace is respected for the secondary store but omitted from the primary - context 'key namespaces' do - let(:key) { 'key' } - let(:value) { '123' } - - it 'writes keys to SharedState with no prefix, and to Queues with the "resque:gitlab:" prefix' do - subject.with do |redis_instance| - redis_instance.set(key, value) - end - - Gitlab::Redis::SharedState.with do |redis_instance| - expect(redis_instance.get(key)).to eq(value) - end - - Gitlab::Redis::Queues.with do |redis_instance| - expect(redis_instance.get("resque:gitlab:#{key}")).to eq(value) - end - end - end - - it_behaves_like 'multi store feature flags', :use_primary_and_secondary_stores_for_duplicate_jobs, - :use_primary_store_as_default_for_duplicate_jobs - end - - describe '#raw_config_hash' do - it 'has a legacy default URL' do - expect(subject).to receive(:fetch_config) { false } - - expect(subject.send(:raw_config_hash)).to eq(url: 'redis://localhost:6382') - end - end - - describe '#store_name' do - it 'returns the name of the SharedState store' do - expect(described_class.store_name).to eq('SharedState') - end - end -end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 9532a30144f..0770e7b9f7d 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -608,6 +608,7 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do it { is_expected.to match('1.0') } it { is_expected.to match('1.0~alpha1') } it { is_expected.to match('2:4.9.5+dfsg-5+deb10u1') } + it { is_expected.to match('0.0.0-806aa143-f0bf-4f27-be65-8e4fcb745f37') } end context 'dpkg errors' do diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb index b6748d49739..6a515a2b8a5 100644 --- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb @@ -77,7 +77,11 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gi end end - shared_examples 'with Redis cookies' do + context 'with Redis cookies' do + def with_redis(&block) + Sidekiq.redis(&block) + end + let(:cookie_key) { "#{idempotency_key}:cookie:v2" } let(:cookie) { get_redis_msgpack(cookie_key) } @@ -385,41 +389,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gi end end - context 'with multi-store feature flags turned on' do - def with_redis(&block) - Gitlab::Redis::DuplicateJobs.with(&block) - end - - it 'use Gitlab::Redis::DuplicateJobs.with' do - expect(Gitlab::Redis::DuplicateJobs).to receive(:with).and_call_original - expect(Sidekiq).not_to receive(:redis) - - duplicate_job.check! - end - - it_behaves_like 'with Redis cookies' - end - - context 'when both multi-store feature flags are off' do - def with_redis(&block) - Sidekiq.redis(&block) - end - - before do - stub_feature_flags(use_primary_and_secondary_stores_for_duplicate_jobs: false) - stub_feature_flags(use_primary_store_as_default_for_duplicate_jobs: false) - end - - it 'use Sidekiq.redis' do - expect(Sidekiq).to receive(:redis).and_call_original - expect(Gitlab::Redis::DuplicateJobs).not_to receive(:with) - - duplicate_job.check! - end - - it_behaves_like 'with Redis cookies' - end - describe '#scheduled?' do it 'returns false for non-scheduled jobs' do expect(duplicate_job.scheduled?).to be(false) diff --git a/spec/migrations/20210811122206_update_external_project_bots_spec.rb b/spec/migrations/20210811122206_update_external_project_bots_spec.rb index aa0bce63865..b18239f4fd5 100644 --- a/spec/migrations/20210811122206_update_external_project_bots_spec.rb +++ b/spec/migrations/20210811122206_update_external_project_bots_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe UpdateExternalProjectBots, :migration, feature_category: :users do +RSpec.describe UpdateExternalProjectBots, :migration, feature_category: :user_profile do def create_user(**extra_options) defaults = { projects_limit: 0, email: "#{extra_options[:username]}@example.com" } diff --git a/spec/migrations/20210914095310_cleanup_orphan_project_access_tokens_spec.rb b/spec/migrations/20210914095310_cleanup_orphan_project_access_tokens_spec.rb index a9a814f9a48..a198ae9e473 100644 --- a/spec/migrations/20210914095310_cleanup_orphan_project_access_tokens_spec.rb +++ b/spec/migrations/20210914095310_cleanup_orphan_project_access_tokens_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe CleanupOrphanProjectAccessTokens, :migration, feature_category: :users do +RSpec.describe CleanupOrphanProjectAccessTokens, :migration, feature_category: :user_profile do def create_user(**extra_options) defaults = { state: 'active', projects_limit: 0, email: "#{extra_options[:username]}@example.com" } diff --git a/spec/migrations/20210922082019_drop_int4_column_for_events_spec.rb b/spec/migrations/20210922082019_drop_int4_column_for_events_spec.rb index f615c8bb50e..49cf1a01f2a 100644 --- a/spec/migrations/20210922082019_drop_int4_column_for_events_spec.rb +++ b/spec/migrations/20210922082019_drop_int4_column_for_events_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe DropInt4ColumnForEvents, feature_category: :users do +RSpec.describe DropInt4ColumnForEvents, feature_category: :user_profile do let(:events) { table(:events) } it 'correctly migrates up and down' do diff --git a/spec/migrations/20210922091402_drop_int4_column_for_push_event_payloads_spec.rb b/spec/migrations/20210922091402_drop_int4_column_for_push_event_payloads_spec.rb index 5c39e7530ff..3e241438339 100644 --- a/spec/migrations/20210922091402_drop_int4_column_for_push_event_payloads_spec.rb +++ b/spec/migrations/20210922091402_drop_int4_column_for_push_event_payloads_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe DropInt4ColumnForPushEventPayloads, feature_category: :users do +RSpec.describe DropInt4ColumnForPushEventPayloads, feature_category: :user_profile do let(:push_event_payloads) { table(:push_event_payloads) } it 'correctly migrates up and down' do diff --git a/spec/migrations/20221008032350_add_password_expiration_migration_spec.rb b/spec/migrations/20221008032350_add_password_expiration_migration_spec.rb index 1663966816c..ee6c2aeca9c 100644 --- a/spec/migrations/20221008032350_add_password_expiration_migration_spec.rb +++ b/spec/migrations/20221008032350_add_password_expiration_migration_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' require_migration! -RSpec.describe AddPasswordExpirationMigration, feature_category: :users do +RSpec.describe AddPasswordExpirationMigration, feature_category: :user_profile do let(:application_setting) { table(:application_settings).create! } describe "#up" do diff --git a/spec/migrations/20221012033107_add_password_last_changed_at_to_user_details_spec.rb b/spec/migrations/20221012033107_add_password_last_changed_at_to_user_details_spec.rb index e2c508938fd..5c228381b57 100644 --- a/spec/migrations/20221012033107_add_password_last_changed_at_to_user_details_spec.rb +++ b/spec/migrations/20221012033107_add_password_last_changed_at_to_user_details_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' require_migration! -RSpec.describe AddPasswordLastChangedAtToUserDetails, feature_category: :users do +RSpec.describe AddPasswordLastChangedAtToUserDetails, feature_category: :user_profile do let!(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } let!(:users) { table(:users) } let!(:user) { create_user! } diff --git a/spec/migrations/20221013154159_update_invalid_dormant_user_setting_spec.rb b/spec/migrations/20221013154159_update_invalid_dormant_user_setting_spec.rb index 0686d9ca786..ad644b63060 100644 --- a/spec/migrations/20221013154159_update_invalid_dormant_user_setting_spec.rb +++ b/spec/migrations/20221013154159_update_invalid_dormant_user_setting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe UpdateInvalidDormantUserSetting, :migration, feature_category: :users do +RSpec.describe UpdateInvalidDormantUserSetting, :migration, feature_category: :user_profile do let(:settings) { table(:application_settings) } context 'with no rows in the application_settings table' do diff --git a/spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb b/spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb index d6acf31fdc6..0e5bb419e32 100644 --- a/spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb +++ b/spec/migrations/20221025043930_change_default_value_on_password_last_changed_at_to_user_details_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe ChangeDefaultValueOnPasswordLastChangedAtToUserDetails, :migration, feature_category: :users do +RSpec.describe ChangeDefaultValueOnPasswordLastChangedAtToUserDetails, :migration, feature_category: :user_profile do let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } let(:users) { table(:users) } let(:user_details) { table(:user_details) } diff --git a/spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb b/spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb index 6b6fb553b1f..332b3a5abba 100644 --- a/spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb +++ b/spec/migrations/20221028022627_add_index_on_password_last_changed_at_to_user_details_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe AddIndexOnPasswordLastChangedAtToUserDetails, :migration, feature_category: :users do +RSpec.describe AddIndexOnPasswordLastChangedAtToUserDetails, :migration, feature_category: :user_profile do let(:index_name) { 'index_user_details_on_password_last_changed_at' } it 'correctly migrates up and down' do diff --git a/spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb b/spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb index e8dce46bdbc..7c9d2e3170a 100644 --- a/spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb +++ b/spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe CleanupAfterFixingIssueWhenAdminChangedPrimaryEmail, :sidekiq, feature_category: :users do +RSpec.describe CleanupAfterFixingIssueWhenAdminChangedPrimaryEmail, :sidekiq, feature_category: :user_profile do let(:migration) { described_class.new } let(:users) { table(:users) } let(:emails) { table(:emails) } diff --git a/spec/migrations/cleanup_after_fixing_regression_with_new_users_emails_spec.rb b/spec/migrations/cleanup_after_fixing_regression_with_new_users_emails_spec.rb index 01ceef9f3a1..ce7be6aed73 100644 --- a/spec/migrations/cleanup_after_fixing_regression_with_new_users_emails_spec.rb +++ b/spec/migrations/cleanup_after_fixing_regression_with_new_users_emails_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe CleanupAfterFixingRegressionWithNewUsersEmails, :sidekiq, feature_category: :users do +RSpec.describe CleanupAfterFixingRegressionWithNewUsersEmails, :sidekiq, feature_category: :user_profile do let(:migration) { described_class.new } let(:users) { table(:users) } let(:emails) { table(:emails) } diff --git a/spec/migrations/confirm_security_bot_spec.rb b/spec/migrations/confirm_security_bot_spec.rb index 3761c113692..55053c29233 100644 --- a/spec/migrations/confirm_security_bot_spec.rb +++ b/spec/migrations/confirm_security_bot_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe ConfirmSecurityBot, :migration, feature_category: :users do +RSpec.describe ConfirmSecurityBot, :migration, feature_category: :user_profile do let(:users) { table(:users) } let(:user_type) { 8 } diff --git a/spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb b/spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb index 8def53e1858..7c03ed2c870 100644 --- a/spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb +++ b/spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' require_migration! # rubocop:disable Style/WordArray -RSpec.describe FixBatchedMigrationsOldFormatJobArguments, feature_category: :users do +RSpec.describe FixBatchedMigrationsOldFormatJobArguments, feature_category: :user_profile do let(:batched_background_migrations) { table(:batched_background_migrations) } context 'when migrations with legacy job arguments exists' do diff --git a/spec/migrations/queue_backfill_user_details_fields_spec.rb b/spec/migrations/queue_backfill_user_details_fields_spec.rb index e77a66907de..4613a85be40 100644 --- a/spec/migrations/queue_backfill_user_details_fields_spec.rb +++ b/spec/migrations/queue_backfill_user_details_fields_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe QueueBackfillUserDetailsFields, feature_category: :users do +RSpec.describe QueueBackfillUserDetailsFields, feature_category: :user_profile do let!(:batched_migration) { described_class::MIGRATION } it 'schedules a new batched migration' do diff --git a/spec/migrations/queue_populate_projects_star_count_spec.rb b/spec/migrations/queue_populate_projects_star_count_spec.rb index 84565d14d52..b30bb6a578b 100644 --- a/spec/migrations/queue_populate_projects_star_count_spec.rb +++ b/spec/migrations/queue_populate_projects_star_count_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe QueuePopulateProjectsStarCount, feature_category: :users do +RSpec.describe QueuePopulateProjectsStarCount, feature_category: :user_profile do let!(:batched_migration) { described_class::MIGRATION } it 'schedules a new batched migration' do diff --git a/spec/migrations/set_email_confirmation_setting_before_removing_send_user_confirmation_email_column_spec.rb b/spec/migrations/set_email_confirmation_setting_before_removing_send_user_confirmation_email_column_spec.rb index 4303713744e..8e00fbe4b89 100644 --- a/spec/migrations/set_email_confirmation_setting_before_removing_send_user_confirmation_email_column_spec.rb +++ b/spec/migrations/set_email_confirmation_setting_before_removing_send_user_confirmation_email_column_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' require_migration! -RSpec.describe SetEmailConfirmationSettingBeforeRemovingSendUserConfirmationEmailColumn, feature_category: :users do +RSpec.describe SetEmailConfirmationSettingBeforeRemovingSendUserConfirmationEmailColumn, + feature_category: :user_profile do let(:migration) { described_class.new } let(:application_settings_table) { table(:application_settings) } diff --git a/spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb b/spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb index e08aa8679a1..ef1ced530c9 100644 --- a/spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb +++ b/spec/migrations/set_email_confirmation_setting_from_send_user_confirmation_email_setting_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require_migration! -RSpec.describe SetEmailConfirmationSettingFromSendUserConfirmationEmailSetting, feature_category: :users do +RSpec.describe SetEmailConfirmationSettingFromSendUserConfirmationEmailSetting, feature_category: :user_profile do let(:migration) { described_class.new } let(:application_settings_table) { table(:application_settings) } diff --git a/spec/models/achievements/achievement_spec.rb b/spec/models/achievements/achievement_spec.rb index 9a5f4eee229..d3e3e40fc0c 100644 --- a/spec/models/achievements/achievement_spec.rb +++ b/spec/models/achievements/achievement_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Achievements::Achievement, type: :model, feature_category: :users do +RSpec.describe Achievements::Achievement, type: :model, feature_category: :user_profile do describe 'associations' do it { is_expected.to belong_to(:namespace).required } diff --git a/spec/models/achievements/user_achievement_spec.rb b/spec/models/achievements/user_achievement_spec.rb index a91cba2b5e2..9d88bfdd477 100644 --- a/spec/models/achievements/user_achievement_spec.rb +++ b/spec/models/achievements/user_achievement_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Achievements::UserAchievement, type: :model, feature_category: :users do +RSpec.describe Achievements::UserAchievement, type: :model, feature_category: :user_profile do describe 'associations' do it { is_expected.to belong_to(:achievement).inverse_of(:user_achievements).required } it { is_expected.to belong_to(:user).inverse_of(:user_achievements).required } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index f170eeb5841..931d12b7109 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Event, feature_category: :users do +RSpec.describe Event, feature_category: :user_profile do let_it_be_with_reload(:project) { create(:project) } describe "Associations" do diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index b623d534f29..94c463dfe43 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -214,7 +214,6 @@ RSpec.describe ProtectedBranch do let_it_be(:project) { create(:project, :repository) } let_it_be(:protected_branch) { create(:protected_branch, project: project, name: "“jawn”") } - let(:use_new_cache_implementation) { true } let(:rely_on_new_cache) { true } shared_examples_for 'hash based cache implementation' do @@ -230,7 +229,6 @@ RSpec.describe ProtectedBranch do end before do - stub_feature_flags(hash_based_cache_for_protected_branches: use_new_cache_implementation) stub_feature_flags(rely_on_protected_branches_cache: rely_on_new_cache) allow(described_class).to receive(:matching).and_call_original @@ -296,48 +294,6 @@ RSpec.describe ProtectedBranch do expect(described_class.protected?(project, protected_branch.name)).to eq(true) end end - - context 'when feature flag hash_based_cache_for_protected_branches is off' do - let(:use_new_cache_implementation) { false } - - it 'does not call hash based cache implementation' do - expect(ProtectedBranches::CacheService).not_to receive(:new) - expect(Rails.cache).to receive(:fetch).and_call_original - - described_class.protected?(project, 'missing-branch') - end - - it 'correctly invalidates a cache' do - expect(described_class).to receive(:matching).with(protected_branch.name, protected_refs: anything).once.and_call_original - - create(:protected_branch, project: project, name: "bar") - # the cache is invalidated because the project has been "updated" - expect(described_class.protected?(project, protected_branch.name)).to eq(true) - end - - it 'sets expires_in of 1 hour for the Rails cache key' do - cache_key = described_class.protected_ref_cache_key(project, protected_branch.name) - - expect(Rails.cache).to receive(:fetch).with(cache_key, expires_in: 1.hour) - - described_class.protected?(project, protected_branch.name) - end - - context 'when project is updated' do - it 'invalidates Rails cache' do - expect(described_class).to receive(:matching).with(protected_branch.name, protected_refs: anything).once.and_call_original - - project.touch - - described_class.protected?(project, protected_branch.name) - end - end - - it 'correctly uses the cached version' do - expect(described_class).not_to receive(:matching) - expect(described_class.protected?(project, protected_branch.name)).to eq(true) - end - end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e2e4e4248d8..5570eb36c81 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe User, feature_category: :users do +RSpec.describe User, feature_category: :user_profile do include ProjectForksHelper include TermsHelper include ExclusiveLeaseHelpers diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb index 8affbe6ec2b..fcef5b6ca78 100644 --- a/spec/requests/api/avatar_spec.rb +++ b/spec/requests/api/avatar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Avatar, feature_category: :users do +RSpec.describe API::Avatar, feature_category: :user_profile do let(:gravatar_service) { double('GravatarService') } describe 'GET /avatar' do diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index 5c061a37ff3..f884aaabb53 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Events, feature_category: :users do +RSpec.describe API::Events, feature_category: :user_profile do let(:user) { create(:user) } let(:non_member) { create(:user) } let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index ca08e780758..1d40d8291b5 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -647,6 +647,11 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do icon text } + project { + id + name + webUrl + } shortSha commitPath finishedAt diff --git a/spec/requests/api/graphql/mutations/achievements/create_spec.rb b/spec/requests/api/graphql/mutations/achievements/create_spec.rb index 1713f050540..1e2f0dd777f 100644 --- a/spec/requests/api/graphql/mutations/achievements/create_spec.rb +++ b/spec/requests/api/graphql/mutations/achievements/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::Achievements::Create, feature_category: :users do +RSpec.describe Mutations::Achievements::Create, feature_category: :user_profile do include GraphqlHelpers include WorkhorseHelpers diff --git a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb index 31d17401b9e..967ad75c906 100644 --- a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb +++ b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::UserPreferences::Update, feature_category: :users do +RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profile do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb index 3e82d783a18..c19dfa6f3f3 100644 --- a/spec/requests/api/graphql/user_spec.rb +++ b/spec/requests/api/graphql/user_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User', feature_category: :users do +RSpec.describe 'User', feature_category: :user_profile do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb index 9d3ab269ca1..bb0f557cfee 100644 --- a/spec/requests/api/invitations_spec.rb +++ b/spec/requests/api/invitations_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Invitations, feature_category: :users do +RSpec.describe API::Invitations, feature_category: :user_profile do let_it_be(:maintainer) { create(:user, username: 'maintainer_user') } let_it_be(:maintainer2) { create(:user, username: 'user-with-maintainer-role') } let_it_be(:developer) { create(:user) } diff --git a/spec/requests/api/project_events_spec.rb b/spec/requests/api/project_events_spec.rb index 69d8eb76cf3..f904cd8fd6c 100644 --- a/spec/requests/api/project_events_spec.rb +++ b/spec/requests/api/project_events_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectEvents, feature_category: :users do +RSpec.describe API::ProjectEvents, feature_category: :user_profile do let(:user) { create(:user) } let(:non_member) { create(:user) } let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index d62f8a32453..d817c8fbb59 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -2227,6 +2227,89 @@ RSpec.describe API::Projects do end end + describe 'GET /project/:id/share_locations' do + let_it_be(:root_group) { create(:group, :public, name: 'root group') } + let_it_be(:project_group1) { create(:group, :public, parent: root_group, name: 'group1') } + let_it_be(:project_group2) { create(:group, :public, parent: root_group, name: 'group2') } + let_it_be(:project) { create(:project, :private, group: project_group1) } + + shared_examples_for 'successful groups response' do + it 'returns an array of groups' do + request + + aggregate_failures do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |g| g['name'] }).to match_array(expected_groups.map(&:name)) + end + end + end + + context 'when unauthenticated' do + it 'does not return the groups for the given project' do + get api("/projects/#{project.id}/share_locations") + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when authenticated' do + context 'when user is not the owner of the project' do + it 'does not return the groups' do + get api("/projects/#{project.id}/share_locations", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is the owner of the project' do + let(:request) { get api("/projects/#{project.id}/share_locations", user), params: params } + let(:params) {} + + before do + project.add_owner(user) + project_group1.add_developer(user) + project_group2.add_developer(user) + end + + context 'with default search' do + it_behaves_like 'successful groups response' do + let(:expected_groups) { [project_group1, project_group2] } + end + end + + context 'when searching by group name' do + let(:params) { { search: 'group1' } } + + it_behaves_like 'successful groups response' do + let(:expected_groups) { [project_group1] } + end + end + end + end + + context 'when authenticated as admin' do + let(:request) { get api("/projects/#{project.id}/share_locations", admin), params: {} } + + context 'without share_with_group_lock' do + it_behaves_like 'successful groups response' do + let(:expected_groups) { [root_group, project_group1, project_group2] } + end + end + + context 'with share_with_group_lock' do + before do + project.namespace.update!(share_with_group_lock: true) + end + + it_behaves_like 'successful groups response' do + let(:expected_groups) { [] } + end + end + end + end + describe 'GET /projects/:id' do context 'when unauthenticated' do it 'does not return private projects' do @@ -2297,7 +2380,7 @@ RSpec.describe API::Projects do let(:project_attributes) { YAML.load_file(project_attributes_file) } let(:expected_keys) do - keys = project_attributes.map do |relation, relation_config| + keys = project_attributes.flat_map do |relation, relation_config| begin actual_keys = project.send(relation).attributes.keys rescue NoMethodError @@ -2307,7 +2390,7 @@ RSpec.describe API::Projects do remapped_attributes = relation_config['remapped_attributes'] || {} computed_attributes = relation_config['computed_attributes'] || [] actual_keys - unexposed_attributes - remapped_attributes.keys + remapped_attributes.values + computed_attributes - end.flatten + end unless Gitlab.ee? keys -= %w[ @@ -3662,8 +3745,8 @@ RSpec.describe API::Projects do aggregate_failures "testing response" do expect(response).to have_gitlab_http_status(:ok) - expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\ - '-/system/project/avatar/'\ + expect(json_response['avatar_url']).to eq('http://localhost/uploads/' \ + '-/system/project/avatar/' \ "#{project3.id}/banana_sample.gif") end end @@ -3678,8 +3761,8 @@ RSpec.describe API::Projects do aggregate_failures "testing response" do expect(response).to have_gitlab_http_status(:ok) - expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\ - '-/system/project/avatar/'\ + expect(json_response['avatar_url']).to eq('http://localhost/uploads/' \ + '-/system/project/avatar/' \ "#{project_with_avatar.id}/rails_sample.png") end end @@ -3695,8 +3778,8 @@ RSpec.describe API::Projects do aggregate_failures "testing response" do expect(response).to have_gitlab_http_status(:ok) expect(json_response['description']).to eq('changed description') - expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\ - '-/system/project/avatar/'\ + expect(json_response['avatar_url']).to eq('http://localhost/uploads/' \ + '-/system/project/avatar/' \ "#{project_with_avatar.id}/banana_sample.gif") end end diff --git a/spec/requests/api/users_preferences_spec.rb b/spec/requests/api/users_preferences_spec.rb index 53f366371e5..ef9735fd8b0 100644 --- a/spec/requests/api/users_preferences_spec.rb +++ b/spec/requests/api/users_preferences_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Users, feature_category: :users do +RSpec.describe API::Users, feature_category: :user_profile do let_it_be(:user) { create(:user) } describe 'PUT /user/preferences/' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c063187fdf4..34867b13db2 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Users, feature_category: :users do +RSpec.describe API::Users, feature_category: :user_profile do include WorkhorseHelpers let_it_be(:admin) { create(:admin) } diff --git a/spec/requests/projects/noteable_notes_spec.rb b/spec/requests/projects/noteable_notes_spec.rb index 5699bf17b80..084cf2d4a5c 100644 --- a/spec/requests/projects/noteable_notes_spec.rb +++ b/spec/requests/projects/noteable_notes_spec.rb @@ -36,5 +36,41 @@ RSpec.describe 'Project noteable notes', feature_category: :team_planning do expect(response).to have_gitlab_http_status(:ok) expect(response_etag).to eq(stored_etag) end + + it "instruments cache hits correctly" do + etag_store.touch(notes_path) + + expect(Gitlab::Metrics::RailsSlis.request_apdex).to( + receive(:increment).with( + labels: { + request_urgency: :low, + feature_category: "team_planning", + endpoint_id: "Projects::NotesController#index" + }, + success: be_in([true, false]) + ) + ) + allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original + + expect(ActiveSupport::Notifications).to( + receive(:instrument).with( + 'process_action.action_controller', + a_hash_including( + { + request_urgency: :low, + target_duration_s: 5, + metadata: a_hash_including({ + 'meta.feature_category' => 'team_planning', + 'meta.caller_id' => "Projects::NotesController#index" + }) + } + ) + ) + ) + + get notes_path, headers: { "if-none-match": stored_etag } + + expect(response).to have_gitlab_http_status(:not_modified) + end end end diff --git a/spec/requests/user_activity_spec.rb b/spec/requests/user_activity_spec.rb index f9682d81640..16188ab6a41 100644 --- a/spec/requests/user_activity_spec.rb +++ b/spec/requests/user_activity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Update of user activity', feature_category: :users do +RSpec.describe 'Update of user activity', feature_category: :user_profile do paths_to_visit = [ '/group', '/group/project', diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb index 4e3c2744d56..0a9f3784833 100644 --- a/spec/requests/user_avatar_spec.rb +++ b/spec/requests/user_avatar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Loading a user avatar', feature_category: :users do +RSpec.describe 'Loading a user avatar', feature_category: :user_profile do let(:user) { create(:user, :with_avatar) } context 'when logged in' do diff --git a/spec/services/achievements/create_service_spec.rb b/spec/services/achievements/create_service_spec.rb index f62a45deb50..ac28a88572b 100644 --- a/spec/services/achievements/create_service_spec.rb +++ b/spec/services/achievements/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Achievements::CreateService, feature_category: :users do +RSpec.describe Achievements::CreateService, feature_category: :user_profile do describe '#execute' do let_it_be(:user) { create(:user) } diff --git a/spec/services/chat_names/authorize_user_service_spec.rb b/spec/services/chat_names/authorize_user_service_spec.rb index 4c261ece504..c9b4439202a 100644 --- a/spec/services/chat_names/authorize_user_service_spec.rb +++ b/spec/services/chat_names/authorize_user_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ChatNames::AuthorizeUserService, feature_category: :users do +RSpec.describe ChatNames::AuthorizeUserService, feature_category: :user_profile do describe '#execute' do let(:result) { subject.execute } diff --git a/spec/services/packages/debian/process_package_file_service_spec.rb b/spec/services/packages/debian/process_package_file_service_spec.rb index 571861f42cf..5eb4a7e79ca 100644 --- a/spec/services/packages/debian/process_package_file_service_spec.rb +++ b/spec/services/packages/debian/process_package_file_service_spec.rb @@ -1,20 +1,32 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Debian::ProcessPackageFileService do +RSpec.describe Packages::Debian::ProcessPackageFileService, feature_category: :package_registry do describe '#execute' do - let_it_be(:user) { create(:user) } let_it_be_with_reload(:distribution) { create(:debian_project_distribution, :with_file, codename: 'unstable') } - let!(:incoming) { create(:debian_incoming, project: distribution.project) } - + let!(:package) { create(:debian_package, :processing, project: distribution.project, published_in: nil) } let(:distribution_name) { distribution.codename } let(:debian_file_metadatum) { package_file.debian_file_metadatum } - subject { described_class.new(package_file, user, distribution_name, component_name) } + subject { described_class.new(package_file, distribution_name, component_name) } - RSpec.shared_context 'with Debian package file' do |file_name| - let(:package_file) { incoming.package_files.with_file_name(file_name).first } + shared_examples 'updates package and package file' do + it 'updates package and package file', :aggregate_failures do + expect(::Packages::Debian::GenerateDistributionWorker) + .to receive(:perform_async).with(:project, distribution.id) + expect { subject.execute } + .to not_change(Packages::Package, :count) + .and not_change(Packages::PackageFile, :count) + .and change(Packages::Debian::Publication, :count).by(1) + .and not_change(package.package_files, :count) + .and change { package.reload.name }.to('sample') + .and change { package.reload.version }.to('1.2.3~alpha2') + .and change { package.reload.status }.from('processing').to('default') + .and change { package.reload.debian_publication }.from(nil) + .and change(debian_file_metadatum, :file_type).from('unknown').to(expected_file_type) + .and change(debian_file_metadatum, :component).from(nil).to(component_name) + end end using RSpec::Parameterized::TableSyntax @@ -25,59 +37,62 @@ RSpec.describe Packages::Debian::ProcessPackageFileService do end with_them do - include_context 'with Debian package file', params[:file_name] do - it 'creates package and updates package file', :aggregate_failures do - expect(::Packages::Debian::GenerateDistributionWorker) - .to receive(:perform_async).with(:project, distribution.id) - expect { subject.execute } - .to change(Packages::Package, :count).from(1).to(2) - .and not_change(Packages::PackageFile, :count) - .and change(incoming.package_files, :count).from(7).to(6) - .and change(debian_file_metadatum, :file_type).from('unknown').to(expected_file_type) - .and change(debian_file_metadatum, :component).from(nil).to(component_name) - - created_package = Packages::Package.last - expect(created_package.name).to eq 'sample' - expect(created_package.version).to eq '1.2.3~alpha2' - expect(created_package.creator).to eq user - end + context 'with Debian package file' do + let(:package_file) { package.package_files.with_file_name(file_name).first } - context 'with existing package' do - let_it_be_with_reload(:existing_package) do - create(:debian_package, name: 'sample', version: '1.2.3~alpha2', project: distribution.project) - end + context 'when there is no matching published package' do + it_behaves_like 'updates package and package file' + end - before do - existing_package.update!(debian_distribution: distribution) + context 'when there is a matching published package' do + let!(:matching_package) do + create( + :debian_package, + project: distribution.project, + published_in: distribution, + name: 'sample', + version: '1.2.3~alpha2' + ) end - it 'does not create a package and assigns the package_file to the existing package' do + it 'reuses existing package and update package file', :aggregate_failures do expect(::Packages::Debian::GenerateDistributionWorker) .to receive(:perform_async).with(:project, distribution.id) expect { subject.execute } - .to not_change(Packages::Package, :count) - .and not_change(Packages::PackageFile, :count) - .and change(incoming.package_files, :count).from(7).to(6) - .and change(package_file, :package).from(incoming).to(existing_package) - .and change(debian_file_metadatum, :file_type).from('unknown').to(expected_file_type.to_s) + .to change(Packages::Package, :count).from(2).to(1) + .and change(Packages::PackageFile, :count).from(14).to(8) + .and not_change(Packages::Debian::Publication, :count) + .and change(package.package_files, :count).from(7).to(0) + .and change(package_file, :package).from(package).to(matching_package) + .and not_change(matching_package, :name) + .and not_change(matching_package, :version) + .and change(debian_file_metadatum, :file_type).from('unknown').to(expected_file_type) .and change(debian_file_metadatum, :component).from(nil).to(component_name) - end - context 'when marked as pending_destruction' do - it 'does not re-use the existing package' do - existing_package.pending_destruction! + expect { package.reload } + .to raise_error(ActiveRecord::RecordNotFound) + end + end - expect { subject.execute } - .to change(Packages::Package, :count).by(1) - .and not_change(Packages::PackageFile, :count) - end + context 'when there is a matching published package pending destruction' do + let!(:matching_package) do + create( + :debian_package, + :pending_destruction, + project: distribution.project, + published_in: distribution, + name: 'sample', + version: '1.2.3~alpha2' + ) end + + it_behaves_like 'updates package and package file' end end end context 'without a distribution' do - let(:package_file) { incoming.package_files.with_file_name('libsample0_1.2.3~alpha2_amd64.deb').first } + let(:package_file) { package.package_files.with_file_name('libsample0_1.2.3~alpha2_amd64.deb').first } let(:component_name) { 'main' } before do @@ -89,7 +104,7 @@ RSpec.describe Packages::Debian::ProcessPackageFileService do expect { subject.execute } .to not_change(Packages::Package, :count) .and not_change(Packages::PackageFile, :count) - .and not_change(incoming.package_files, :count) + .and not_change(package.package_files, :count) .and raise_error(ActiveRecord::RecordNotFound) end end @@ -103,7 +118,7 @@ RSpec.describe Packages::Debian::ProcessPackageFileService do expect { subject.execute } .to not_change(Packages::Package, :count) .and not_change(Packages::PackageFile, :count) - .and not_change(incoming.package_files, :count) + .and not_change(package.package_files, :count) .and raise_error(ArgumentError, 'package file without Debian metadata') end end @@ -118,13 +133,13 @@ RSpec.describe Packages::Debian::ProcessPackageFileService do expect { subject.execute } .to not_change(Packages::Package, :count) .and not_change(Packages::PackageFile, :count) - .and not_change(incoming.package_files, :count) + .and not_change(package.package_files, :count) .and raise_error(ArgumentError, 'already processed package file') end end context 'with invalid package file type' do - let(:package_file) { incoming.package_files.with_file_name('sample_1.2.3~alpha2.tar.xz').first } + let(:package_file) { package.package_files.with_file_name('sample_1.2.3~alpha2.tar.xz').first } let(:component_name) { 'main' } it 'raise ArgumentError', :aggregate_failures do @@ -132,30 +147,9 @@ RSpec.describe Packages::Debian::ProcessPackageFileService do expect { subject.execute } .to not_change(Packages::Package, :count) .and not_change(Packages::PackageFile, :count) - .and not_change(incoming.package_files, :count) + .and not_change(package.package_files, :count) .and raise_error(ArgumentError, 'invalid package file type: source') end end - - context 'when creating package fails' do - let(:package_file) { incoming.package_files.with_file_name('libsample0_1.2.3~alpha2_amd64.deb').first } - let(:component_name) { 'main' } - - before do - allow_next_instance_of(::Packages::Debian::FindOrCreatePackageService) do |find_or_create_package_service| - allow(find_or_create_package_service) - .to receive(:execute).and_raise(ActiveRecord::ConnectionTimeoutError, 'connect timeout') - end - end - - it 're-raise error', :aggregate_failures do - expect(::Packages::Debian::GenerateDistributionWorker).not_to receive(:perform_async) - expect { subject.execute } - .to not_change(Packages::Package, :count) - .and not_change(Packages::PackageFile, :count) - .and not_change(incoming.package_files, :count) - .and raise_error(ActiveRecord::ConnectionTimeoutError, 'connect timeout') - end - end end end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index ea918cb3437..6cba9332347 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -10308,7 +10308,6 @@ - './spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb' - './spec/views/projects/pipelines/show.html.haml_spec.rb' - './spec/views/projects/project_members/index.html.haml_spec.rb' -- './spec/views/projects/runners/_specific_runners.html.haml_spec.rb' - './spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb' - './spec/views/projects/settings/integrations/edit.html.haml_spec.rb' - './spec/views/projects/settings/operations/show.html.haml_spec.rb' diff --git a/spec/views/projects/runners/_specific_runners.html.haml_spec.rb b/spec/views/projects/runners/_project_runners.html.haml_spec.rb index ce16e0d5ac6..8a7e693bdeb 100644 --- a/spec/views/projects/runners/_specific_runners.html.haml_spec.rb +++ b/spec/views/projects/runners/_project_runners.html.haml_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -RSpec.describe 'projects/runners/specific_runners.html.haml' do +RSpec.describe 'projects/runners/_project_runners.html.haml', feature_category: :runner do describe 'render' do - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } + let_it_be(:user) { build(:user) } + let_it_be(:project) { build(:project) } before do @project = project @@ -22,7 +22,7 @@ RSpec.describe 'projects/runners/specific_runners.html.haml' do end it 'enables the Remove project button for a project' do - render 'projects/runners/specific_runners', project: project + render 'projects/runners/project_runners', project: project expect(rendered).to have_selector '#js-install-runner' expect(rendered).not_to have_content 'Please contact an admin to register runners.' @@ -35,7 +35,7 @@ RSpec.describe 'projects/runners/specific_runners.html.haml' do end it 'does not enable the Remove project button for a project' do - render 'projects/runners/specific_runners', project: project + render 'projects/runners/project_runners', project: project expect(rendered).to have_content 'Please contact an admin to register runners.' expect(rendered).not_to have_selector '#js-install-runner' diff --git a/spec/views/shared/runners/_runner_details.html.haml_spec.rb b/spec/views/shared/runners/_runner_details.html.haml_spec.rb index 978750c8435..6e95f6e8075 100644 --- a/spec/views/shared/runners/_runner_details.html.haml_spec.rb +++ b/spec/views/shared/runners/_runner_details.html.haml_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'shared/runners/_runner_details.html.haml' do context 'when runner is of type project' do let(:runner) { create(:ci_runner, :project) } - it { is_expected.to have_content("Runner ##{runner.id} specific") } + it { is_expected.to have_content("Runner ##{runner.id} project") } end end diff --git a/spec/workers/packages/debian/process_package_file_worker_spec.rb b/spec/workers/packages/debian/process_package_file_worker_spec.rb index 7da79751697..f45bc07af0e 100644 --- a/spec/workers/packages/debian/process_package_file_worker_spec.rb +++ b/spec/workers/packages/debian/process_package_file_worker_spec.rb @@ -3,18 +3,19 @@ require 'spec_helper' RSpec.describe Packages::Debian::ProcessPackageFileWorker, type: :worker, feature_category: :package_registry do - let_it_be(:user) { create(:user) } let_it_be_with_reload(:distribution) { create(:debian_project_distribution, :with_file, codename: 'unstable') } + let_it_be_with_reload(:package) do + create(:debian_package, :processing, project: distribution.project, published_in: nil) + end - let(:incoming) { create(:debian_incoming, project: distribution.project) } let(:distribution_name) { distribution.codename } + let(:debian_file_metadatum) { package_file.debian_file_metadatum } let(:worker) { described_class.new } describe '#perform' do let(:package_file_id) { package_file.id } - let(:user_id) { user.id } - subject { worker.perform(package_file_id, user_id, distribution_name, component_name) } + subject { worker.perform(package_file_id, distribution_name, component_name) } shared_examples 'returns early without error' do it 'returns early without error' do @@ -34,7 +35,7 @@ RSpec.describe Packages::Debian::ProcessPackageFileWorker, type: :worker, featur with_them do context 'with Debian package file' do - let(:package_file) { incoming.package_files.with_file_name(file_name).first } + let(:package_file) { package.package_files.with_file_name(file_name).first } context 'with mocked service' do it 'calls ProcessPackageFileService' do @@ -48,57 +49,44 @@ RSpec.describe Packages::Debian::ProcessPackageFileWorker, type: :worker, featur end end - context 'with non existing user' do - let(:user_id) { non_existing_record_id } - - it_behaves_like 'returns early without error' - end - - context 'with nil user id' do - let(:user_id) { nil } - - it_behaves_like 'returns early without error' - end - context 'when the service raises an error' do - let(:package_file) { incoming.package_files.with_file_name('sample_1.2.3~alpha2.tar.xz').first } + let(:package_file) { package.package_files.with_file_name('sample_1.2.3~alpha2.tar.xz').first } - it 'removes package file', :aggregate_failures do + it 'marks the package as errored', :aggregate_failures do expect(Gitlab::ErrorTracking).to receive(:log_exception).with( instance_of(ArgumentError), package_file_id: package_file_id, - user_id: user_id, distribution_name: distribution_name, component_name: component_name ) expect { subject } .to not_change(Packages::Package, :count) - .and change { Packages::PackageFile.count }.by(-1) - .and change { incoming.package_files.count }.from(7).to(6) - - expect { package_file.reload }.to raise_error(ActiveRecord::RecordNotFound) + .and not_change { Packages::PackageFile.count } + .and not_change { package.package_files.count } + .and change { package.reload.status }.from('processing').to('error') end end it_behaves_like 'an idempotent worker' do - let(:job_args) { [package_file.id, user.id, distribution_name, component_name] } + let(:job_args) { [package_file.id, distribution_name, component_name] } it 'sets the Debian file type as deb', :aggregate_failures do + expect(::Packages::Debian::GenerateDistributionWorker) + .to receive(:perform_async).with(:project, distribution.id) expect(Gitlab::ErrorTracking).not_to receive(:log_exception) # Using subject inside this block will process the job multiple times expect { subject } - .to change { Packages::Package.count }.from(1).to(2) + .to not_change(Packages::Package, :count) .and not_change(Packages::PackageFile, :count) - .and change { incoming.package_files.count }.from(7).to(6) - .and change { - package_file&.debian_file_metadatum&.reload&.file_type - }.from('unknown').to(expected_file_type) - - created_package = Packages::Package.last - expect(created_package.name).to eq 'sample' - expect(created_package.version).to eq '1.2.3~alpha2' - expect(created_package.creator).to eq user + .and change { Packages::Debian::Publication.count }.by(1) + .and not_change(package.package_files, :count) + .and change { package.reload.name }.to('sample') + .and change { package.version }.to('1.2.3~alpha2') + .and change { package.status }.from('processing').to('default') + .and change { package.debian_publication }.from(nil) + .and change { debian_file_metadatum.reload.file_type }.from('unknown').to(expected_file_type) + .and change { debian_file_metadatum.component }.from(nil).to(component_name) end end end @@ -113,7 +101,7 @@ RSpec.describe Packages::Debian::ProcessPackageFileWorker, type: :worker, featur end context 'with a deb' do - let(:package_file) { incoming.package_files.with_file_name('libsample0_1.2.3~alpha2_amd64.deb').first } + let(:package_file) { package.package_files.with_file_name('libsample0_1.2.3~alpha2_amd64.deb').first } let(:component_name) { 'main' } context 'with non existing package file' do |