diff options
80 files changed, 1084 insertions, 171 deletions
diff --git a/.rubocop_todo/style/format_string.yml b/.rubocop_todo/style/format_string.yml index 002d1964d3f..101b16802f7 100644 --- a/.rubocop_todo/style/format_string.yml +++ b/.rubocop_todo/style/format_string.yml @@ -256,7 +256,6 @@ Style/FormatString: - 'lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb' - 'lib/gitlab/github_import/issuable_finder.rb' - 'lib/gitlab/github_import/label_finder.rb' - - 'lib/gitlab/github_import/milestone_finder.rb' - 'lib/gitlab/github_import/object_counter.rb' - 'lib/gitlab/github_import/page_counter.rb' - 'lib/gitlab/github_import/parallel_scheduling.rb' diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue index 85dfa12c756..f494a3bd224 100644 --- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue +++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue @@ -1,11 +1,12 @@ <script> -import { GlLoadingIcon, GlTableLite } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon, GlTableLite } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { __, s__ } from '~/locale'; import getCiCatalogResourceComponents from '../../graphql/queries/get_ci_catalog_resource_components.query.graphql'; export default { components: { + GlButton, GlLoadingIcon, GlTableLite, }, @@ -70,6 +71,8 @@ export default { }, ], i18n: { + copyText: __('Copy value'), + copyAriaText: __('Copy to clipboard'), inputTitle: s__('CiCatalogComponent|Inputs'), fetchError: s__("CiCatalogComponent|There was an error fetching this resource's components"), }, @@ -88,7 +91,24 @@ export default { > <h3 class="gl-font-size-h2" data-testid="component-name">{{ component.name }}</h3> <p class="gl-mt-5">{{ component.description }}</p> - <pre class="gl-w-85p gl-py-4">{{ generateSnippet(component.path) }}</pre> + <div class="gl-display-flex"> + <pre + class="gl-w-85p gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0 gl-border-r-none" + ><span>{{ generateSnippet(component.path) }}</span> + </pre> + <div class="gl--flex-center gl-bg-gray-10 gl-border gl-border-l-none"> + <gl-button + class="gl-p-4! gl-mr-3!" + category="tertiary" + icon="copy-to-clipboard" + size="small" + :title="$options.i18n.copyText" + :data-clipboard-text="generateSnippet(component.path)" + data-testid="copy-to-clipboard" + :aria-label="$options.i18n.copyAriaText" + /> + </div> + </div> <div class="gl-mt-5"> <b class="gl-display-block gl-mb-4"> {{ $options.i18n.inputTitle }}</b> <gl-table-lite :items="component.inputs.nodes" :fields="$options.fields"> diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue index dd1cca0a05c..1f61e878eb0 100644 --- a/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue +++ b/app/assets/javascripts/ci/runner/components/search_tokens/tag_token.vue @@ -70,7 +70,7 @@ export default { @fetch-suggestions="fetchTags" v-on="$listeners" > - <template #view-token="{ viewTokenProps: { listeners, inputValue, activeTokenValue } }"> + <template #view-token="{ viewTokenProps: { listeners = {}, inputValue, activeTokenValue } }"> <gl-token variant="search-value" :class="$options.RUNNER_TAG_BG_CLASS" v-on="listeners"> {{ activeTokenValue ? activeTokenValue.text : inputValue }} </gl-token> diff --git a/app/assets/javascripts/notes/components/notes_activity_header.vue b/app/assets/javascripts/notes/components/notes_activity_header.vue index ce642733396..b4eeea8db02 100644 --- a/app/assets/javascripts/notes/components/notes_activity_header.vue +++ b/app/assets/javascripts/notes/components/notes_activity_header.vue @@ -40,7 +40,7 @@ export default { showAiActions() { return ( this.resourceGlobalId && - this.glFeatures.openaiExperimentation && + (this.glFeatures.openaiExperimentation || this.glFeatures.aiGlobalSwitch) && this.glFeatures.summarizeNotes ); }, diff --git a/app/assets/javascripts/sidebar/components/toggle/toggle_sidebar.vue b/app/assets/javascripts/sidebar/components/toggle/toggle_sidebar.vue index ba0bf783315..7ce1ceb4bb8 100644 --- a/app/assets/javascripts/sidebar/components/toggle/toggle_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/toggle/toggle_sidebar.vue @@ -1,6 +1,7 @@ <script> import { GlButton, GlTooltipDirective } from '@gitlab/ui'; import { __ } from '~/locale'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { name: 'ToggleSidebar', @@ -10,6 +11,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagsMixin()], props: { collapsed: { type: Boolean, @@ -29,7 +31,13 @@ export default { return this.collapsed ? 'chevron-double-lg-left' : 'chevron-double-lg-right'; }, allCssClasses() { - return [this.cssClasses, { 'js-sidebar-collapsed': this.collapsed }]; + return [ + this.cssClasses, + { + 'js-sidebar-collapsed': this.collapsed, + 'gl-mt-2': this.glFeatures.notificationsTodosButtons, + }, + ]; }, }, watch: { diff --git a/app/graphql/mutations/packages/protection/rule/delete.rb b/app/graphql/mutations/packages/protection/rule/delete.rb new file mode 100644 index 00000000000..bd0159d3c23 --- /dev/null +++ b/app/graphql/mutations/packages/protection/rule/delete.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Mutations + module Packages + module Protection + module Rule + class Delete < ::Mutations::BaseMutation + graphql_name 'DeletePackagesProtectionRule' + description 'Deletes a protection rule for packages. ' \ + 'Available only when feature flag `packages_protected_packages` is enabled.' + + authorize :admin_package + + argument :id, + ::Types::GlobalIDType[::Packages::Protection::Rule], + required: true, + description: 'Global ID of the package protection rule to delete.' + + field :package_protection_rule, + Types::Packages::Protection::RuleType, + null: true, + description: 'Packages protection rule that was deleted successfully.' + + def resolve(id:, **_kwargs) + if Feature.disabled?(:packages_protected_packages) + raise_resource_not_available_error!("'packages_protected_packages' feature flag is disabled") + end + + package_protection_rule = authorized_find!(id: id) + + response = ::Packages::Protection::DeleteRuleService.new(package_protection_rule, + current_user: current_user).execute + + { package_protection_rule: response.payload[:package_protection_rule], errors: response.errors } + end + end + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 9c292404aed..475066e7279 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -173,6 +173,7 @@ module Types extensions: [::Gitlab::Graphql::Limit::FieldCallCount => { limit: 1 }] mount_mutation Mutations::Packages::DestroyFile mount_mutation Mutations::Packages::Protection::Rule::Create, alpha: { milestone: '16.5' } + mount_mutation Mutations::Packages::Protection::Rule::Delete, alpha: { milestone: '16.6' } mount_mutation Mutations::Packages::DestroyFiles mount_mutation Mutations::Packages::Cleanup::Policy::Update mount_mutation Mutations::Echo diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index bd63381e9d1..eda789d5e55 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -68,14 +68,6 @@ module WikiHelper render Pajamas::ButtonComponent.new(href: wiki_path(wiki, **link_options), icon: "sort-#{icon_class}", button_options: { class: link_class, title: title }) end - def wiki_sort_title(key) - if key == Wiki::CREATED_AT_ORDER - s_("Wiki|Created date") - else - s_("Wiki|Title") - end - end - def wiki_empty_state_messages(wiki) case wiki.container when Project diff --git a/app/models/packages/nuget/symbol.rb b/app/models/packages/nuget/symbol.rb index 643b5552d84..3315f11b974 100644 --- a/app/models/packages/nuget/symbol.rb +++ b/app/models/packages/nuget/symbol.rb @@ -4,6 +4,7 @@ module Packages module Nuget class Symbol < ApplicationRecord include FileStoreMounter + include ShaAttribute belongs_to :package, -> { where(package_type: :nuget) }, inverse_of: :nuget_symbols @@ -13,6 +14,8 @@ module Packages validates :signature, uniqueness: { scope: :file_path } validates :object_storage_key, uniqueness: true + sha256_attribute :file_sha256 + mount_file_store_uploader SymbolUploader before_validation :set_object_storage_key, on: :create diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 1ec2495a661..462afbaa475 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -37,7 +37,7 @@ class BasePolicy < DeclarativePolicy::Base desc "User is security policy bot" with_options scope: :user, score: 0 - condition(:security_policy_bot) { @user&.security_policy_bot? } + condition(:security_policy_bot) { false } desc "User is automation bot" with_options scope: :user, score: 0 diff --git a/app/policies/concerns/policy_actor.rb b/app/policies/concerns/policy_actor.rb index e000f1514e5..8fa09683b06 100644 --- a/app/policies/concerns/policy_actor.rb +++ b/app/policies/concerns/policy_actor.rb @@ -53,10 +53,6 @@ module PolicyActor false end - def security_policy_bot? - false - end - def automation_bot? false end diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 7594360a91c..175f86c9673 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -63,10 +63,6 @@ class GlobalPolicy < BasePolicy prevent :access_git end - rule { security_policy_bot }.policy do - enable :access_git - end - rule { project_bot | service_account }.policy do prevent :log_in prevent :receive_notifications diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 0a93dcdff9f..28ba819cb60 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -38,9 +38,6 @@ class ProjectPolicy < BasePolicy desc "User is a project bot" condition(:project_bot) { user.project_bot? && team_member? } - desc "User is a security policy bot on the project" - condition(:security_policy_bot) { user&.security_policy_bot? && team_member? } - desc "Project is public" condition(:public_project, scope: :subject, score: 0) { project.public? } diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb index f6720546fab..0858fad1e1a 100644 --- a/app/presenters/commit_status_presenter.rb +++ b/app/presenters/commit_status_presenter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + class CommitStatusPresenter < Gitlab::View::Presenter::Delegated CALLOUT_FAILURE_MESSAGES = { unknown_failure: 'There is an unknown failure, please try again', diff --git a/app/services/packages/nuget/symbols/create_symbol_files_service.rb b/app/services/packages/nuget/symbols/create_symbol_files_service.rb index 03e14ba00e1..5f0b8762054 100644 --- a/app/services/packages/nuget/symbols/create_symbol_files_service.rb +++ b/app/services/packages/nuget/symbols/create_symbol_files_service.rb @@ -18,7 +18,7 @@ module Packages process_symbol_entries rescue ExtractionError => e - Gitlab::ErrorTracking.log_exception(e, class: self.class.name, package_id: package.id) + Gitlab::ErrorTracking.track_exception(e, class: self.class.name, package_id: package.id) end private @@ -31,7 +31,7 @@ module Packages raise ExtractionError, 'too many symbol entries' if index >= SYMBOL_ENTRIES_LIMIT entry.extract(tmp_file.path) { true } - File.open(tmp_file.path) do |file| + File.open(tmp_file.path, 'rb') do |file| create_symbol(entry.name, file) end end @@ -43,25 +43,27 @@ module Packages end def create_symbol(path, file) - signature = extract_signature(file.read(1.kilobyte)) - return if signature.blank? + signature, checksum = extract_signature_and_checksum(file) + return if signature.blank? || checksum.blank? ::Packages::Nuget::Symbol.create!( package: package, file: { tempfile: file, filename: path.downcase, content_type: CONTENT_TYPE }, file_path: path, signature: signature, - size: file.size + size: file.size, + file_sha256: checksum ) rescue StandardError => e - Gitlab::ErrorTracking.log_exception(e, class: self.class.name, package_id: package.id) + Gitlab::ErrorTracking.track_exception(e, class: self.class.name, package_id: package.id) end - def extract_signature(content_fragment) - ExtractSymbolSignatureService - .new(content_fragment) + def extract_signature_and_checksum(file) + ::Packages::Nuget::Symbols::ExtractSignatureAndChecksumService + .new(file) .execute .payload + .values_at(:signature, :checksum) end end end diff --git a/app/services/packages/nuget/symbols/extract_symbol_signature_service.rb b/app/services/packages/nuget/symbols/extract_signature_and_checksum_service.rb index c2ccdb517b5..fd37d139145 100644 --- a/app/services/packages/nuget/symbols/extract_symbol_signature_service.rb +++ b/app/services/packages/nuget/symbols/extract_signature_and_checksum_service.rb @@ -3,45 +3,43 @@ module Packages module Nuget module Symbols - class ExtractSymbolSignatureService + class ExtractSignatureAndChecksumService include Gitlab::Utils::StrongMemoize # More information about the GUID format can be found here: # https://github.com/dotnet/symstore/blob/main/docs/specs/SSQP_Key_Conventions.md#key-formatting-basic-rules GUID_START_INDEX = 7 - GUID_END_INDEX = 22 + GUID_END_INDEX = 26 + SIGNATURE_LENGTH = 16 + TWENTY_ZEROED_BYTES = "\u0000" * 20 GUID_PARTS_LENGTHS = [4, 2, 2, 8].freeze GUID_AGE_PART = 'FFFFFFFF' TWO_CHARACTER_HEX_REGEX = /\h{2}/ + GUID_CHUNK_SIZE = 256.bytes + SHA_CHUNK_SIZE = 16.kilobytes # The extraction of the signature in this service is based on the following documentation: # https://github.com/dotnet/symstore/blob/main/docs/specs/SSQP_Key_Conventions.md#portable-pdb-signature - def initialize(symbol_content) - @symbol_content = symbol_content + def initialize(file) + @file = file end def execute return error_response unless signature - ServiceResponse.success(payload: signature) + ServiceResponse.success(payload: { signature: signature, checksum: checksum }) end private - attr_reader :symbol_content + attr_reader :file def signature - # Find the index of the first occurrence of 'Blob' - guid_index = symbol_content.index('Blob') - return if guid_index.nil? - - # Extract the binary GUID from the symbol content - guid = symbol_content[(guid_index + GUID_START_INDEX)..(guid_index + GUID_END_INDEX)] - return if guid.nil? + return unless pdb_id # Convert the GUID into an array of two-character hex strings - guid = guid.unpack('H*').flat_map { |el| el.scan(TWO_CHARACTER_HEX_REGEX) } + guid = pdb_id.first(SIGNATURE_LENGTH).unpack('H*').flat_map { |el| el.scan(TWO_CHARACTER_HEX_REGEX) } # Reorder the GUID parts based on arbitrary lengths guid = GUID_PARTS_LENGTHS.map { |length| guid.shift(length) } @@ -54,6 +52,36 @@ module Packages end strong_memoize_attr :signature + # https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md#portable-pdb-checksum + def checksum + sha = OpenSSL::Digest.new('SHA256') + count = 0 + chunk = (+'').force_encoding(Encoding::BINARY) + file.rewind + + while file.read(SHA_CHUNK_SIZE, chunk) + count += 1 + chunk[pdb_id] = TWENTY_ZEROED_BYTES if count == 1 + sha.update(chunk) + end + + sha.hexdigest + end + + def pdb_id + # The ID is located in the first 256 bytes of the symbol `.pdb` file + chunk = file.read(GUID_CHUNK_SIZE) + return unless chunk + + # Find the index of the first occurrence of 'Blob' + guid_index = chunk.index('Blob') + return unless guid_index + + # Extract the binary GUID from the symbol content + chunk[(guid_index + GUID_START_INDEX)..(guid_index + GUID_END_INDEX)] + end + strong_memoize_attr :pdb_id + def error_response ServiceResponse.error(message: 'Could not find the signature in the symbol file') end diff --git a/app/services/packages/protection/delete_rule_service.rb b/app/services/packages/protection/delete_rule_service.rb new file mode 100644 index 00000000000..a1fa111b57b --- /dev/null +++ b/app/services/packages/protection/delete_rule_service.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Packages + module Protection + class DeleteRuleService + include Gitlab::Allowable + + def initialize(package_protection_rule, current_user:) + if package_protection_rule.blank? || current_user.blank? + raise ArgumentError, + 'package_protection_rule and current_user must be set' + end + + @package_protection_rule = package_protection_rule + @current_user = current_user + end + + def execute + unless can?(current_user, :admin_package, package_protection_rule.project) + error_message = _('Unauthorized to delete a package protection rule') + return service_response_error(message: error_message) + end + + deleted_package_protection_rule = package_protection_rule.destroy! + + ServiceResponse.success(payload: { package_protection_rule: deleted_package_protection_rule }) + rescue StandardError => e + service_response_error(message: e.message) + end + + private + + attr_reader :package_protection_rule, :current_user + + def service_response_error(message:) + ServiceResponse.error( + message: message, + payload: { package_protection_rule: nil } + ) + end + end + end +end diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml index ed1342be1fc..12b9df6719d 100644 --- a/app/views/admin/application_settings/metrics_and_profiling.html.haml +++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml @@ -41,7 +41,7 @@ .settings-content = render 'performance_bar' -%section.settings.as-usage.no-animate#js-usage-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'usage_statistics_settings_content' } } +%section.settings.as-usage.no-animate#js-usage-settings{ class: ('expanded' if expanded_by_default?), data: { testid: 'usage-statistics-settings-content' } } .settings-header#usage-statistics %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _('Usage statistics') diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index 079123e989e..8e51eafd84c 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -21,7 +21,7 @@ = file_field_tag :file, class: '' .row .form-actions.col-sm-12 - = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { class: 'gl-mr-2', data: { qa_selector: 'import_project_button' }}) do + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { class: 'gl-mr-2', data: { testid: 'import-project-button' }}) do = _('Import project') = render Pajamas::ButtonComponent.new(href: new_project_path) do = _('Cancel') diff --git a/app/views/import/shared/_new_project_form.html.haml b/app/views/import/shared/_new_project_form.html.haml index 6000612a285..042d94ad1b6 100644 --- a/app/views/import/shared/_new_project_form.html.haml +++ b/app/views/import/shared/_new_project_form.html.haml @@ -1,7 +1,7 @@ .row .form-group.project-name.col-sm-12 = label_tag :name, _('Project name'), class: 'label-bold' - = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { qa_selector: 'project_name_field' } + = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { testid: 'project-name-field' } .form-group.col-12.col-sm-6.gl-pr-0 = label_tag :namespace_id, _('Project URL'), class: 'label-bold' .input-group.gl-flex-nowrap @@ -21,4 +21,4 @@ .gl-align-self-center.gl-pl-5 / .form-group.col-12.col-sm-6.project-path = label_tag :path, _('Project slug'), class: 'label-bold' - = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control gl-form-input", required: true, aria: { required: true }, data: { qa_selector: 'project_slug_field' } + = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control gl-form-input", required: true, aria: { required: true } diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 6315c6dc52d..3e92ef25552 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -17,7 +17,7 @@ = html_escape(_("Importing GitLab projects? Migrating GitLab projects when migrating groups by direct transfer is in Beta. %{link_start}Learn more.%{link_end}")) % { link_start: docs_link, link_end: '</a>'.html_safe } .import-buttons - if gitlab_project_import_enabled? - .import_gitlab_project.has-tooltip{ data: { container: 'body', qa_selector: 'gitlab_import_button' } } + .import_gitlab_project.has-tooltip{ data: { container: 'body', testid: 'gitlab-import-button' } } = render Pajamas::ButtonComponent.new(href: '#', icon: 'tanuki', button_options: { class: 'btn_import_gitlab_project js-import-project-btn', data: { href: new_import_gitlab_project_path, platform: 'gitlab_export', **tracking_attrs_data(track_label, 'click_button', 'gitlab_export') } }) do = _('GitLab export') diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 1392c7ab89f..f018e4f122e 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -16,7 +16,7 @@ %aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { always_show_toggle: true, signed: { in: signed_in }, issuable_type: issuable_type }, class: "#{sidebar_gutter_collapsed_class(is_merge_request_with_flag)} #{'right-sidebar-merge-requests' if is_merge_request_with_flag}", 'aria-live' => 'polite', 'aria-label': issuable_type } .issuable-sidebar{ class: "#{'is-merge-request' if is_merge_request_with_flag}" } .issuable-sidebar-header{ class: "gl-pb-4! #{'gl-pb-2! gl-md-display-flex gl-justify-content-end gl-lg-display-none!' if is_merge_request_with_flag}" } - = render Pajamas::ButtonComponent.new(button_options: { class: "gutter-toggle float-right js-sidebar-toggle has-tooltip gl-shadow-none! #{'gl-display-block' if moved_sidebar_enabled}", type: 'button', 'aria-label' => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }) do + = render Pajamas::ButtonComponent.new(button_options: { class: "gutter-toggle float-right js-sidebar-toggle has-tooltip gl-shadow-none! #{'gl-display-block' if moved_sidebar_enabled} #{'gl-mt-2' if notifications_todos_buttons_enabled?}" , type: 'button', 'aria-label' => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }) do = sidebar_gutter_toggle_icon - if signed_in - if !is_merge_request_with_flag diff --git a/config/feature_flags/development/ai_self_discover.yml b/config/feature_flags/development/ai_self_discover.yml deleted file mode 100644 index ef5e2bc8926..00000000000 --- a/config/feature_flags/development/ai_self_discover.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ai_self_discover -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132267 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/425908 -milestone: '16.4' -type: development -group: group::ai framework -default_enabled: false diff --git a/db/docs/approval_group_rules.yml b/db/docs/approval_group_rules.yml new file mode 100644 index 00000000000..35f75338480 --- /dev/null +++ b/db/docs/approval_group_rules.yml @@ -0,0 +1,10 @@ +--- +table_name: approval_group_rules +classes: + - ApprovalRules::ApprovalGroupRule +feature_categories: + - source_code_management +description: Keeps approval group rules +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132651 +milestone: '16.5' +gitlab_schema: gitlab_main diff --git a/db/docs/approval_group_rules_groups.yml b/db/docs/approval_group_rules_groups.yml new file mode 100644 index 00000000000..0599af6ac15 --- /dev/null +++ b/db/docs/approval_group_rules_groups.yml @@ -0,0 +1,9 @@ +--- +table_name: approval_group_rules_groups +classes: [] +feature_categories: + - source_code_management +description: Keeps connection between group and a group approval rule +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132651 +milestone: '16.5' +gitlab_schema: gitlab_main diff --git a/db/docs/approval_group_rules_protected_branches.yml b/db/docs/approval_group_rules_protected_branches.yml new file mode 100644 index 00000000000..ac55f0980be --- /dev/null +++ b/db/docs/approval_group_rules_protected_branches.yml @@ -0,0 +1,9 @@ +--- +table_name: approval_group_rules_protected_branches +classes: [] +feature_categories: + - source_code_management +description: Keeps relation between approval group rules and protected branches. +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132651 +milestone: '16.5' +gitlab_schema: gitlab_main diff --git a/db/docs/approval_group_rules_users.yml b/db/docs/approval_group_rules_users.yml new file mode 100644 index 00000000000..67271d2a35d --- /dev/null +++ b/db/docs/approval_group_rules_users.yml @@ -0,0 +1,9 @@ +--- +table_name: approval_group_rules_users +classes: [] +feature_categories: + - source_code_management +description: Keeps connection between user and a group approval rule +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132651 +milestone: '16.5' +gitlab_schema: gitlab_main diff --git a/db/migrate/20230926092914_add_approval_group_rules.rb b/db/migrate/20230926092914_add_approval_group_rules.rb new file mode 100644 index 00000000000..c5f4a356df1 --- /dev/null +++ b/db/migrate/20230926092914_add_approval_group_rules.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class AddApprovalGroupRules < Gitlab::Database::Migration[2.1] + INDEX_GROUP_ID_TYPE_NAME = 'idx_on_approval_group_rules_group_id_type_name' + INDEX_ANY_APPROVER_TYPE = 'idx_on_approval_group_rules_any_approver_type' + INDEX_SECURITY_ORCHESTRATION_POLICY_CONFURATION = 'idx_on_approval_group_rules_security_orch_policy' + disable_ddl_transaction! + + def up + create_table :approval_group_rules do |t| + t.references :group, references: :namespaces, null: false, + foreign_key: { to_table: :namespaces, on_delete: :cascade }, index: false + t.timestamps_with_timezone + t.integer :approvals_required, limit: 2, null: false, default: 0 + t.integer :report_type, limit: 2, null: true, default: nil + t.integer :rule_type, limit: 2, null: false, default: 1 + t.integer :security_orchestration_policy_configuration_id, limit: 5 + t.integer :scan_result_policy_id, limit: 5, index: true + t.text :name, null: false, limit: 255 + + t.index [:group_id, :rule_type, :name], unique: true, name: INDEX_GROUP_ID_TYPE_NAME + t.index [:group_id, :rule_type], where: 'rule_type = 4', unique: true, name: INDEX_ANY_APPROVER_TYPE + t.index :security_orchestration_policy_configuration_id, name: INDEX_SECURITY_ORCHESTRATION_POLICY_CONFURATION + end + + add_text_limit :approval_group_rules, :name, 255 + end + + def down + with_lock_retries do + drop_table :approval_group_rules + end + end +end diff --git a/db/migrate/20230926092944_add_approval_group_rules_groups.rb b/db/migrate/20230926092944_add_approval_group_rules_groups.rb new file mode 100644 index 00000000000..52ac86737e6 --- /dev/null +++ b/db/migrate/20230926092944_add_approval_group_rules_groups.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddApprovalGroupRulesGroups < Gitlab::Database::Migration[2.1] + INDEX_RULE_GROUP = 'idx_on_approval_group_rules_groups_rule_group' + + def up + create_table :approval_group_rules_groups do |t| + t.bigint :approval_group_rule_id, null: false + t.bigint :group_id, null: false, index: true + + t.index [:approval_group_rule_id, :group_id], unique: true, name: INDEX_RULE_GROUP + end + end + + def down + drop_table :approval_group_rules_groups + end +end diff --git a/db/migrate/20230926093004_add_approval_group_rules_users.rb b/db/migrate/20230926093004_add_approval_group_rules_users.rb new file mode 100644 index 00000000000..8c6d14ce9ac --- /dev/null +++ b/db/migrate/20230926093004_add_approval_group_rules_users.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddApprovalGroupRulesUsers < Gitlab::Database::Migration[2.1] + INDEX_RULE_USER = 'idx_on_approval_group_rules_users_rule_user' + + def up + create_table :approval_group_rules_users do |t| + t.bigint :approval_group_rule_id, null: false + t.bigint :user_id, null: false, index: true + + t.index [:approval_group_rule_id, :user_id], unique: true, name: INDEX_RULE_USER + end + end + + def down + drop_table :approval_group_rules_users + end +end diff --git a/db/migrate/20230926093025_add_approval_group_rules_protected_branches.rb b/db/migrate/20230926093025_add_approval_group_rules_protected_branches.rb new file mode 100644 index 00000000000..5f623ec9edb --- /dev/null +++ b/db/migrate/20230926093025_add_approval_group_rules_protected_branches.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class AddApprovalGroupRulesProtectedBranches < Gitlab::Database::Migration[2.1] + INDEX_RULE_PROTECTED_BRANCH = 'idx_on_approval_group_rules_protected_branch' + INDEX_APPROVAL_GROUP_RULE = 'idx_on_approval_group_rules' + INDEX_PROTECTED_BRANCH = 'idx_on_protected_branch' + + def up + create_table :approval_group_rules_protected_branches do |t| + t.bigint :approval_group_rule_id, null: false + t.bigint :protected_branch_id, null: false + + t.index :protected_branch_id, name: INDEX_PROTECTED_BRANCH + t.index [:approval_group_rule_id, :protected_branch_id], unique: true, name: INDEX_RULE_PROTECTED_BRANCH + end + end + + def down + drop_table :approval_group_rules_protected_branches + end +end diff --git a/db/migrate/20230926093101_add_fk_to_approval_rule_on_approval_group_rules_users.rb b/db/migrate/20230926093101_add_fk_to_approval_rule_on_approval_group_rules_users.rb new file mode 100644 index 00000000000..4c11542e9e6 --- /dev/null +++ b/db/migrate/20230926093101_add_fk_to_approval_rule_on_approval_group_rules_users.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddFkToApprovalRuleOnApprovalGroupRulesUsers < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules_users, + :approval_group_rules, + column: :approval_group_rule_id, + on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules_users, column: :approval_group_rule_id + end + end +end diff --git a/db/migrate/20230926093144_add_fk_to_user_on_approval_group_rules_users.rb b/db/migrate/20230926093144_add_fk_to_user_on_approval_group_rules_users.rb new file mode 100644 index 00000000000..30c08c8966d --- /dev/null +++ b/db/migrate/20230926093144_add_fk_to_user_on_approval_group_rules_users.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddFkToUserOnApprovalGroupRulesUsers < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules_users, :users, column: :user_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules_users, column: :user_id + end + end +end diff --git a/db/migrate/20230926093211_add_fk_to_approval_rule_on_approval_group_rules_groups.rb b/db/migrate/20230926093211_add_fk_to_approval_rule_on_approval_group_rules_groups.rb new file mode 100644 index 00000000000..44526150266 --- /dev/null +++ b/db/migrate/20230926093211_add_fk_to_approval_rule_on_approval_group_rules_groups.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddFkToApprovalRuleOnApprovalGroupRulesGroups < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules_groups, :approval_group_rules, column: :approval_group_rule_id, + on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules_groups, column: :approval_group_rule_id + end + end +end diff --git a/db/migrate/20230926093251_add_fk_to_group_on_approval_group_rules_groups.rb b/db/migrate/20230926093251_add_fk_to_group_on_approval_group_rules_groups.rb new file mode 100644 index 00000000000..2052993af05 --- /dev/null +++ b/db/migrate/20230926093251_add_fk_to_group_on_approval_group_rules_groups.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddFkToGroupOnApprovalGroupRulesGroups < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules_groups, :namespaces, column: :group_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules_groups, column: :group_id + end + end +end diff --git a/db/migrate/20230926105440_add_fk_to_approval_rule_on_approval_group_rules_protected_branches.rb b/db/migrate/20230926105440_add_fk_to_approval_rule_on_approval_group_rules_protected_branches.rb new file mode 100644 index 00000000000..cd799656ac9 --- /dev/null +++ b/db/migrate/20230926105440_add_fk_to_approval_rule_on_approval_group_rules_protected_branches.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddFkToApprovalRuleOnApprovalGroupRulesProtectedBranches < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules_protected_branches, + :approval_group_rules, + column: :approval_group_rule_id, + on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules_protected_branches, column: :approval_group_rule_id + end + end +end diff --git a/db/migrate/20230926105931_add_fk_to_protected_branch_on_approval_group_rules_protected_branches.rb b/db/migrate/20230926105931_add_fk_to_protected_branch_on_approval_group_rules_protected_branches.rb new file mode 100644 index 00000000000..5804a8da4d8 --- /dev/null +++ b/db/migrate/20230926105931_add_fk_to_protected_branch_on_approval_group_rules_protected_branches.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddFkToProtectedBranchOnApprovalGroupRulesProtectedBranches < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules_protected_branches, :protected_branches, + column: :protected_branch_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules_protected_branches, column: :protected_branch_id + end + end +end diff --git a/db/migrate/20230928145555_add_fk_to_security_orchestration_policy_configuration_on_approval_group_rules.rb b/db/migrate/20230928145555_add_fk_to_security_orchestration_policy_configuration_on_approval_group_rules.rb new file mode 100644 index 00000000000..2630adcf81f --- /dev/null +++ b/db/migrate/20230928145555_add_fk_to_security_orchestration_policy_configuration_on_approval_group_rules.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddFkToSecurityOrchestrationPolicyConfigurationOnApprovalGroupRules < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules, :security_orchestration_policy_configurations, + column: :security_orchestration_policy_configuration_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules, + column: :security_orchestration_policy_configuration_id + end + end +end diff --git a/db/migrate/20230928145637_add_fk_to_scan_result_policy_on_approval_group_rules.rb b/db/migrate/20230928145637_add_fk_to_scan_result_policy_on_approval_group_rules.rb new file mode 100644 index 00000000000..f30d03e0f62 --- /dev/null +++ b/db/migrate/20230928145637_add_fk_to_scan_result_policy_on_approval_group_rules.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddFkToScanResultPolicyOnApprovalGroupRules < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :approval_group_rules, :scan_result_policies, + column: :scan_result_policy_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :approval_group_rules, column: :scan_result_policy_id + end + end +end diff --git a/db/migrate/20231019104211_add_file_sha256_to_packages_nuget_symbols.rb b/db/migrate/20231019104211_add_file_sha256_to_packages_nuget_symbols.rb new file mode 100644 index 00000000000..374fa91000d --- /dev/null +++ b/db/migrate/20231019104211_add_file_sha256_to_packages_nuget_symbols.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddFileSha256ToPackagesNugetSymbols < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def up + add_column :packages_nuget_symbols, :file_sha256, :binary + end + + def down + remove_column :packages_nuget_symbols, :file_sha256 + end +end diff --git a/db/schema_migrations/20230926092914 b/db/schema_migrations/20230926092914 new file mode 100644 index 00000000000..391ade3aab0 --- /dev/null +++ b/db/schema_migrations/20230926092914 @@ -0,0 +1 @@ +0018bc2180eeb632d75132b6d82e959e772ff1e7d8966310858e304d07d4ec34
\ No newline at end of file diff --git a/db/schema_migrations/20230926092944 b/db/schema_migrations/20230926092944 new file mode 100644 index 00000000000..1fe6c64634a --- /dev/null +++ b/db/schema_migrations/20230926092944 @@ -0,0 +1 @@ +e2b4cdafd6147740ad43c286d90f7feec9f70d66a510d58a3cc3c33b0d703b49
\ No newline at end of file diff --git a/db/schema_migrations/20230926093004 b/db/schema_migrations/20230926093004 new file mode 100644 index 00000000000..30407142a30 --- /dev/null +++ b/db/schema_migrations/20230926093004 @@ -0,0 +1 @@ +d3d90178100e92cffe263715cdfc3c9ddcb47ce804f3ffd92d5bc4326de0244c
\ No newline at end of file diff --git a/db/schema_migrations/20230926093025 b/db/schema_migrations/20230926093025 new file mode 100644 index 00000000000..6a5ef092c69 --- /dev/null +++ b/db/schema_migrations/20230926093025 @@ -0,0 +1 @@ +840bc159c277271b66f4348c31d912485c04b8ee1b15227c96dcc690f6b93311
\ No newline at end of file diff --git a/db/schema_migrations/20230926093101 b/db/schema_migrations/20230926093101 new file mode 100644 index 00000000000..2ce67dab37f --- /dev/null +++ b/db/schema_migrations/20230926093101 @@ -0,0 +1 @@ +9a560649866367e556cf841e20f981b6c09fe03d1054f0db37cb510fbfbaef13
\ No newline at end of file diff --git a/db/schema_migrations/20230926093144 b/db/schema_migrations/20230926093144 new file mode 100644 index 00000000000..b383692607f --- /dev/null +++ b/db/schema_migrations/20230926093144 @@ -0,0 +1 @@ +eba011de5a174a93e5159c765c093d3a6519111769a1ac09b2f996322cf3973e
\ No newline at end of file diff --git a/db/schema_migrations/20230926093211 b/db/schema_migrations/20230926093211 new file mode 100644 index 00000000000..9befd202129 --- /dev/null +++ b/db/schema_migrations/20230926093211 @@ -0,0 +1 @@ +f9659a07b4c7b2d4508f1de231e759cf4e15e684ecaa4231ff6069b4ba203e20
\ No newline at end of file diff --git a/db/schema_migrations/20230926093251 b/db/schema_migrations/20230926093251 new file mode 100644 index 00000000000..63bb045e437 --- /dev/null +++ b/db/schema_migrations/20230926093251 @@ -0,0 +1 @@ +9df85930f78c6fa9e02252877d136aab3167a8ac1134cbd321c26f5958899f06
\ No newline at end of file diff --git a/db/schema_migrations/20230926105440 b/db/schema_migrations/20230926105440 new file mode 100644 index 00000000000..957b7cbbbac --- /dev/null +++ b/db/schema_migrations/20230926105440 @@ -0,0 +1 @@ +0be5d3565d71dc9656fd90dbd404ea0314ff29f6da9ca9ef2d100bcc9515308b
\ No newline at end of file diff --git a/db/schema_migrations/20230926105931 b/db/schema_migrations/20230926105931 new file mode 100644 index 00000000000..f158665e529 --- /dev/null +++ b/db/schema_migrations/20230926105931 @@ -0,0 +1 @@ +021dbeb0a8c5ebecfa647344b1e99dd1698ae3fb72a8857409551070b23f9f49
\ No newline at end of file diff --git a/db/schema_migrations/20230928145555 b/db/schema_migrations/20230928145555 new file mode 100644 index 00000000000..860364e57cc --- /dev/null +++ b/db/schema_migrations/20230928145555 @@ -0,0 +1 @@ +71e2f63bf9a327f62d21c2407b9ccebe779e0fd881266467f180cf285edc326f
\ No newline at end of file diff --git a/db/schema_migrations/20230928145637 b/db/schema_migrations/20230928145637 new file mode 100644 index 00000000000..47d4c9b0593 --- /dev/null +++ b/db/schema_migrations/20230928145637 @@ -0,0 +1 @@ +1e9bf34cc708dd8637e4e636894fb9b7894c6d54832b3b42c88af17c4ed87532
\ No newline at end of file diff --git a/db/schema_migrations/20231019104211 b/db/schema_migrations/20231019104211 new file mode 100644 index 00000000000..cce6cc892da --- /dev/null +++ b/db/schema_migrations/20231019104211 @@ -0,0 +1 @@ +e2ee8bcb49b470bbea1874f6a63c9b7a2fd67ef4223cf5d358de1fca4e3f36be
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index da04d20ff84..043dd201d5a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12000,6 +12000,74 @@ CREATE SEQUENCE application_settings_id_seq ALTER SEQUENCE application_settings_id_seq OWNED BY application_settings.id; +CREATE TABLE approval_group_rules ( + id bigint NOT NULL, + group_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + approvals_required smallint DEFAULT 0 NOT NULL, + report_type smallint, + rule_type smallint DEFAULT 1 NOT NULL, + security_orchestration_policy_configuration_id bigint, + scan_result_policy_id bigint, + name text NOT NULL, + CONSTRAINT check_25d42add43 CHECK ((char_length(name) <= 255)) +); + +CREATE TABLE approval_group_rules_groups ( + id bigint NOT NULL, + approval_group_rule_id bigint NOT NULL, + group_id bigint NOT NULL +); + +CREATE SEQUENCE approval_group_rules_groups_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE approval_group_rules_groups_id_seq OWNED BY approval_group_rules_groups.id; + +CREATE SEQUENCE approval_group_rules_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE approval_group_rules_id_seq OWNED BY approval_group_rules.id; + +CREATE TABLE approval_group_rules_protected_branches ( + id bigint NOT NULL, + approval_group_rule_id bigint NOT NULL, + protected_branch_id bigint NOT NULL +); + +CREATE SEQUENCE approval_group_rules_protected_branches_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE approval_group_rules_protected_branches_id_seq OWNED BY approval_group_rules_protected_branches.id; + +CREATE TABLE approval_group_rules_users ( + id bigint NOT NULL, + approval_group_rule_id bigint NOT NULL, + user_id bigint NOT NULL +); + +CREATE SEQUENCE approval_group_rules_users_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE approval_group_rules_users_id_seq OWNED BY approval_group_rules_users.id; + CREATE TABLE approval_merge_request_rule_sources ( id bigint NOT NULL, approval_merge_request_rule_id bigint NOT NULL, @@ -20148,6 +20216,7 @@ CREATE TABLE packages_nuget_symbols ( file_path text NOT NULL, signature text NOT NULL, object_storage_key text NOT NULL, + file_sha256 bytea, CONSTRAINT check_0e93ca58b7 CHECK ((char_length(file) <= 255)), CONSTRAINT check_28b82b08fa CHECK ((char_length(object_storage_key) <= 255)), CONSTRAINT check_30b0ef2ca2 CHECK ((char_length(file_path) <= 255)), @@ -25924,6 +25993,14 @@ ALTER TABLE ONLY application_setting_terms ALTER COLUMN id SET DEFAULT nextval(' ALTER TABLE ONLY application_settings ALTER COLUMN id SET DEFAULT nextval('application_settings_id_seq'::regclass); +ALTER TABLE ONLY approval_group_rules ALTER COLUMN id SET DEFAULT nextval('approval_group_rules_id_seq'::regclass); + +ALTER TABLE ONLY approval_group_rules_groups ALTER COLUMN id SET DEFAULT nextval('approval_group_rules_groups_id_seq'::regclass); + +ALTER TABLE ONLY approval_group_rules_protected_branches ALTER COLUMN id SET DEFAULT nextval('approval_group_rules_protected_branches_id_seq'::regclass); + +ALTER TABLE ONLY approval_group_rules_users ALTER COLUMN id SET DEFAULT nextval('approval_group_rules_users_id_seq'::regclass); + ALTER TABLE ONLY approval_merge_request_rule_sources ALTER COLUMN id SET DEFAULT nextval('approval_merge_request_rule_sources_id_seq'::regclass); ALTER TABLE ONLY approval_merge_request_rules ALTER COLUMN id SET DEFAULT nextval('approval_merge_request_rules_id_seq'::regclass); @@ -27725,6 +27802,18 @@ ALTER TABLE ONLY application_setting_terms ALTER TABLE ONLY application_settings ADD CONSTRAINT application_settings_pkey PRIMARY KEY (id); +ALTER TABLE ONLY approval_group_rules_groups + ADD CONSTRAINT approval_group_rules_groups_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY approval_group_rules + ADD CONSTRAINT approval_group_rules_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY approval_group_rules_protected_branches + ADD CONSTRAINT approval_group_rules_protected_branches_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY approval_group_rules_users + ADD CONSTRAINT approval_group_rules_users_pkey PRIMARY KEY (id); + ALTER TABLE ONLY approval_merge_request_rule_sources ADD CONSTRAINT approval_merge_request_rule_sources_pkey PRIMARY KEY (id); @@ -31100,6 +31189,18 @@ CREATE INDEX idx_mrs_on_target_id_and_created_at_and_state_id ON merge_requests CREATE UNIQUE INDEX idx_namespace_settings_on_default_compliance_framework_id ON namespace_settings USING btree (default_compliance_framework_id); +CREATE UNIQUE INDEX idx_on_approval_group_rules_any_approver_type ON approval_group_rules USING btree (group_id, rule_type) WHERE (rule_type = 4); + +CREATE UNIQUE INDEX idx_on_approval_group_rules_group_id_type_name ON approval_group_rules USING btree (group_id, rule_type, name); + +CREATE UNIQUE INDEX idx_on_approval_group_rules_groups_rule_group ON approval_group_rules_groups USING btree (approval_group_rule_id, group_id); + +CREATE UNIQUE INDEX idx_on_approval_group_rules_protected_branch ON approval_group_rules_protected_branches USING btree (approval_group_rule_id, protected_branch_id); + +CREATE INDEX idx_on_approval_group_rules_security_orch_policy ON approval_group_rules USING btree (security_orchestration_policy_configuration_id); + +CREATE UNIQUE INDEX idx_on_approval_group_rules_users_rule_user ON approval_group_rules_users USING btree (approval_group_rule_id, user_id); + CREATE UNIQUE INDEX idx_on_compliance_management_frameworks_namespace_id_name ON compliance_management_frameworks USING btree (namespace_id, name); CREATE UNIQUE INDEX idx_on_external_approval_rules_project_id_external_url ON external_approval_rules USING btree (project_id, external_url); @@ -31110,6 +31211,8 @@ CREATE UNIQUE INDEX idx_on_external_status_checks_project_id_external_url ON ext CREATE UNIQUE INDEX idx_on_external_status_checks_project_id_name ON external_status_checks USING btree (project_id, name); +CREATE INDEX idx_on_protected_branch ON approval_group_rules_protected_branches USING btree (protected_branch_id); + CREATE INDEX idx_open_issues_on_project_and_confidential_and_author_and_id ON issues USING btree (project_id, confidential, author_id, id) WHERE (state_id = 1); CREATE INDEX idx_packages_debian_group_component_files_on_architecture_id ON packages_debian_group_component_files USING btree (architecture_id); @@ -31338,6 +31441,12 @@ CREATE INDEX index_application_settings_on_usage_stats_set_by_user_id ON applica CREATE INDEX index_applicationsettings_on_instance_administration_project_id ON application_settings USING btree (instance_administration_project_id); +CREATE INDEX index_approval_group_rules_groups_on_group_id ON approval_group_rules_groups USING btree (group_id); + +CREATE INDEX index_approval_group_rules_on_scan_result_policy_id ON approval_group_rules USING btree (scan_result_policy_id); + +CREATE INDEX index_approval_group_rules_users_on_user_id ON approval_group_rules_users USING btree (user_id); + CREATE UNIQUE INDEX index_approval_merge_request_rule_sources_1 ON approval_merge_request_rule_sources USING btree (approval_merge_request_rule_id); CREATE INDEX index_approval_merge_request_rule_sources_2 ON approval_merge_request_rule_sources USING btree (approval_project_rule_id); @@ -36775,6 +36884,9 @@ ALTER TABLE ONLY remote_development_agent_configs ALTER TABLE ONLY dast_sites ADD CONSTRAINT fk_0a57f2271b FOREIGN KEY (dast_site_validation_id) REFERENCES dast_site_validations(id) ON DELETE SET NULL; +ALTER TABLE ONLY approval_group_rules_protected_branches + ADD CONSTRAINT fk_0b85e6c388 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE; + ALTER TABLE ONLY issue_customer_relations_contacts ADD CONSTRAINT fk_0c0037f723 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE; @@ -36805,6 +36917,9 @@ ALTER TABLE ONLY vulnerabilities ALTER TABLE ONLY vulnerabilities ADD CONSTRAINT fk_131d289c65 FOREIGN KEY (milestone_id) REFERENCES milestones(id) ON DELETE SET NULL; +ALTER TABLE ONLY approval_group_rules + ADD CONSTRAINT fk_1485c451e3 FOREIGN KEY (scan_result_policy_id) REFERENCES scan_result_policies(id) ON DELETE CASCADE; + ALTER TABLE ONLY catalog_resource_versions ADD CONSTRAINT fk_15376d917e FOREIGN KEY (release_id) REFERENCES releases(id) ON DELETE CASCADE; @@ -37054,12 +37169,18 @@ ALTER TABLE ONLY user_achievements ALTER TABLE ONLY vulnerability_reads ADD CONSTRAINT fk_4f593f6c62 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; +ALTER TABLE ONLY approval_group_rules_protected_branches + ADD CONSTRAINT fk_4f85f13b20 FOREIGN KEY (approval_group_rule_id) REFERENCES approval_group_rules(id) ON DELETE CASCADE; + ALTER TABLE ONLY project_compliance_standards_adherence ADD CONSTRAINT fk_4fd1d9d9b0 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE SET NULL; ALTER TABLE ONLY vulnerability_reads ADD CONSTRAINT fk_5001652292 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY approval_group_rules_groups + ADD CONSTRAINT fk_50edc8134e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY alert_management_alerts ADD CONSTRAINT fk_51ab4b6089 FOREIGN KEY (prometheus_alert_id) REFERENCES prometheus_alerts(id) ON DELETE CASCADE; @@ -37135,6 +37256,9 @@ ALTER TABLE ONLY vulnerability_reads ALTER TABLE ONLY merge_requests ADD CONSTRAINT fk_641731faff FOREIGN KEY (updated_by_id) REFERENCES users(id) ON DELETE SET NULL; +ALTER TABLE ONLY approval_group_rules + ADD CONSTRAINT fk_64450bea52 FOREIGN KEY (security_orchestration_policy_configuration_id) REFERENCES security_orchestration_policy_configurations(id) ON DELETE CASCADE; + ALTER TABLE ONLY ci_pipeline_chat_data ADD CONSTRAINT fk_64ebfab6b3 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE; @@ -37285,6 +37409,9 @@ ALTER TABLE ONLY packages_package_files ALTER TABLE p_ci_builds ADD CONSTRAINT fk_87f4cefcda FOREIGN KEY (upstream_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE; +ALTER TABLE ONLY approval_group_rules_users + ADD CONSTRAINT fk_888a0df3b7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ALTER TABLE ONLY vulnerabilities ADD CONSTRAINT fk_88b4d546ef FOREIGN KEY (start_date_sourcing_milestone_id) REFERENCES milestones(id) ON DELETE SET NULL; @@ -37354,6 +37481,9 @@ ALTER TABLE ONLY protected_branch_merge_access_levels ALTER TABLE ONLY notes ADD CONSTRAINT fk_99e097b079 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY approval_group_rules_users + ADD CONSTRAINT fk_9a4b673183 FOREIGN KEY (approval_group_rule_id) REFERENCES approval_group_rules(id) ON DELETE CASCADE; + ALTER TABLE ONLY import_failures ADD CONSTRAINT fk_9a9b9ba21c FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; @@ -37759,6 +37889,9 @@ ALTER TABLE ONLY approval_project_rules ALTER TABLE ONLY vulnerabilities ADD CONSTRAINT fk_efb96ab1e2 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY approval_group_rules_groups + ADD CONSTRAINT fk_efff219a48 FOREIGN KEY (approval_group_rule_id) REFERENCES approval_group_rules(id) ON DELETE CASCADE; + ALTER TABLE ONLY emails ADD CONSTRAINT fk_emails_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; @@ -38584,6 +38717,9 @@ ALTER TABLE ONLY namespace_admin_notes ALTER TABLE ONLY ci_runner_machines ADD CONSTRAINT fk_rails_666b61f04f FOREIGN KEY (runner_id) REFERENCES ci_runners(id) ON DELETE CASCADE; +ALTER TABLE ONLY approval_group_rules + ADD CONSTRAINT fk_rails_6727675176 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY jira_imports ADD CONSTRAINT fk_rails_675d38c03b FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE SET NULL; diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 247a1c18992..af314f0c0df 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -3034,6 +3034,31 @@ Input type: `DeleteAnnotationInput` | <a id="mutationdeleteannotationclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationdeleteannotationerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +### `Mutation.deletePackagesProtectionRule` + +Deletes a protection rule for packages. Available only when feature flag `packages_protected_packages` is enabled. + +WARNING: +**Introduced** in 16.6. +This feature is an Experiment. It can be changed or removed at any time. + +Input type: `DeletePackagesProtectionRuleInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationdeletepackagesprotectionruleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationdeletepackagesprotectionruleid"></a>`id` | [`PackagesProtectionRuleID!`](#packagesprotectionruleid) | Global ID of the package protection rule to delete. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationdeletepackagesprotectionruleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationdeletepackagesprotectionruleerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| <a id="mutationdeletepackagesprotectionrulepackageprotectionrule"></a>`packageProtectionRule` | [`PackagesProtectionRule`](#packagesprotectionrule) | Packages protection rule that was deleted successfully. | + ### `Mutation.designManagementDelete` Input type: `DesignManagementDeleteInput` @@ -30746,6 +30771,12 @@ A `PackagesPackageID` is a global ID. It is encoded as a string. An example `PackagesPackageID` is: `"gid://gitlab/Packages::Package/1"`. +### `PackagesProtectionRuleID` + +A `PackagesProtectionRuleID` is a global ID. It is encoded as a string. + +An example `PackagesProtectionRuleID` is: `"gid://gitlab/Packages::Protection::Rule/1"`. + ### `PackagesPypiMetadatumID` A `PackagesPypiMetadatumID` is a global ID. It is encoded as a string. diff --git a/doc/user/ai_features.md b/doc/user/ai_features.md index 0033ecd2949..de081a7f620 100644 --- a/doc/user/ai_features.md +++ b/doc/user/ai_features.md @@ -12,19 +12,19 @@ GitLab is creating AI-assisted features across our DevSecOps platform. These fea | Feature | Purpose | Large Language Model | Current availability | Maturity | |-|-|-|-|-| | [Suggested Reviewers](project/merge_requests/reviews/index.md#gitlab-duo-suggested-reviewers) | Assists in creating faster and higher-quality reviews by automatically suggesting reviewers for your merge request. | GitLab creates a machine learning model for each project, which is used to generate reviewers <br><br> [View the issue](https://gitlab.com/gitlab-org/modelops/applied-ml/applied-ml-updates/-/issues/10) | SaaS only <br><br> Ultimate tier | [Generally Available (GA)](../policy/experiment-beta-support.md#generally-available-ga) | -| [Code Suggestions](project/repository/code_suggestions/index.md) | Helps you write code more efficiently by viewing code suggestions as you type. | [`code-gecko`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-completion) and [`code-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-generation) <br><br> [Anthropic's Claude](https://www.anthropic.com/product) model | SaaS <br> Self-managed <br><br> All tiers | [Beta](../policy/experiment-beta-support.md#beta) | -| [Vulnerability summary](application_security/vulnerabilities/index.md#explaining-a-vulnerability) | Helps you remediate vulnerabilities more efficiently, uplevel your skills, and write more secure code. | [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) <br><br> Anthropic's claude model if degraded performance | SaaS only <br><br> Ultimate tier | [Beta](../policy/experiment-beta-support.md#beta) | -| [Code explanation](#explain-code-in-the-web-ui-with-code-explanation) | Helps you understand code by explaining it in English language. | [`codechat-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-chat) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [GitLab Duo Chat](gitlab_duo_chat.md) | Process and generate text and code in a conversational manner. Helps you quickly identify useful information in large volumes of text in issues, epics, code, and GitLab documentation. | [Anthropic's claude model](https://www.anthropic.com/product) <br><br> [`textembedding-gecko`](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Code Suggestions](project/repository/code_suggestions/index.md) | Helps you write code more efficiently by viewing code suggestions as you type. | For Code Completion: Vertext AI Codey [`code-gecko`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-completion) and Anthropic [`Claude-instant-1.2`](https://docs.anthropic.com/claude/reference/selecting-a-model) <br><br> For Code Generation: Vertext AI Codey [`code-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-generation) and Anthropic [`Claude-2`](https://docs.anthropic.com/claude/reference/selecting-a-model)| [SaaS: All tiers](project/repository/code_suggestions/saas.md) <br><br> [Self-managed: Premium and Ultimate with Cloud Licensing](project/repository/code_suggestions/self_managed.md) | [Beta](../policy/experiment-beta-support.md#beta) | +| [Vulnerability summary](application_security/vulnerabilities/index.md#explaining-a-vulnerability) | Helps you remediate vulnerabilities more efficiently, boost your skills, and write more secure code. | Vertext AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) <br><br> Anthropic [`Claude-2`](https://docs.anthropic.com/claude/reference/selecting-a-model) if degraded performance | SaaS only <br><br> Ultimate tier | [Beta](../policy/experiment-beta-support.md#beta) | +| [Code explanation](#explain-code-in-the-web-ui-with-code-explanation) | Helps you understand code by explaining it in English language. | Vertext AI Codey [`codechat-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-chat) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [GitLab Duo Chat](gitlab_duo_chat.md) | Process and generate text and code in a conversational manner. Helps you quickly identify useful information in large volumes of text in issues, epics, code, and GitLab documentation. | Anthropic [`Claude-2`](https://docs.anthropic.com/claude/reference/selecting-a-model) <br><br> Vertext AI Codey [`textembedding-gecko`](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | | [Value stream forecasting](#forecast-deployment-frequency-with-value-stream-forecasting) | Assists you with predicting productivity metrics and identifying anomalies across your software development lifecycle. | Statistical forecasting | SaaS only <br> Self-managed <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Discussion summary](#summarize-issue-discussions-with-discussion-summary) | Assists with quickly getting everyone up to speed on lengthy conversations to help ensure you are all on the same page. | [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Merge request summary](project/merge_requests/ai_in_merge_requests.md#summarize-merge-request-changes) | Efficiently communicate the impact of your merge request changes. | [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Code review summary](project/merge_requests/ai_in_merge_requests.md#summarize-my-merge-request-review) | Helps ease merge request handoff between authors and reviewers and help reviewers efficiently understand suggestions. | [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Merge request template population](project/merge_requests/ai_in_merge_requests.md#fill-in-merge-request-templates) | Generate a description for the merge request based on the contents of the template. | [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Test generation](project/merge_requests/ai_in_merge_requests.md#generate-suggested-tests-in-merge-requests) | Automates repetitive tasks and helps catch bugs early. | [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Git suggestions](https://gitlab.com/gitlab-org/gitlab/-/issues/409636) | Helps you discover or recall Git commands when and where you need them. | [Google Vertex Codey APIs](https://cloud.google.com/vertex-ai/docs/generative-ai/code/code-models-overview) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Root cause analysis](#root-cause-analysis) | Assists you in determining the root cause for a pipeline failure and failed CI/CD build. | [Google Vertex Codey APIs](https://cloud.google.com/vertex-ai/docs/generative-ai/code/code-models-overview) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | -| [Issue description generation](#summarize-an-issue-with-issue-description-generation) | Generate issue descriptions. | OpenAI's GPT-3 | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Discussion summary](#summarize-issue-discussions-with-discussion-summary) | Assists with quickly getting everyone up to speed on lengthy conversations to help ensure you are all on the same page. | Vertext AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Merge request summary](project/merge_requests/ai_in_merge_requests.md#summarize-merge-request-changes) | Efficiently communicate the impact of your merge request changes. | Vertext AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Code review summary](project/merge_requests/ai_in_merge_requests.md#summarize-my-merge-request-review) | Helps ease merge request handoff between authors and reviewers and help reviewers efficiently understand suggestions. | Vertext AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Merge request template population](project/merge_requests/ai_in_merge_requests.md#fill-in-merge-request-templates) | Generate a description for the merge request based on the contents of the template. | Vertext AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Test generation](project/merge_requests/ai_in_merge_requests.md#generate-suggested-tests-in-merge-requests) | Automates repetitive tasks and helps catch bugs early. | Vertext AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Git suggestions](https://gitlab.com/gitlab-org/gitlab/-/issues/409636) | Helps you discover or recall Git commands when and where you need them. | Vertext AI Codey [`codechat-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-chat) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Root cause analysis](#root-cause-analysis) | Assists you in determining the root cause for a pipeline failure and failed CI/CD build. | Vertext AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | +| [Issue description generation](#summarize-an-issue-with-issue-description-generation) | Generate issue descriptions. | OpenAI's [`GPT-3`](https://platform.openai.com/docs/models/gpt-3) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) | ## Enable AI/ML features diff --git a/lib/gitlab/github_import/milestone_finder.rb b/lib/gitlab/github_import/milestone_finder.rb index d9290e36ea1..dcb679fda6d 100644 --- a/lib/gitlab/github_import/milestone_finder.rb +++ b/lib/gitlab/github_import/milestone_finder.rb @@ -7,6 +7,7 @@ module Gitlab # The base cache key to use for storing/retrieving milestone IDs. CACHE_KEY = 'github-import/milestone-finder/%{project}/%{iid}' + CACHE_OBJECT_NOT_FOUND = -1 # project - An instance of `Project` def initialize(project) @@ -18,7 +19,20 @@ module Gitlab def id_for(issuable) return unless issuable.milestone_number - Gitlab::Cache::Import::Caching.read_integer(cache_key_for(issuable.milestone_number)) + milestone_iid = issuable.milestone_number + cache_key = cache_key_for(milestone_iid) + + val = Gitlab::Cache::Import::Caching.read_integer(cache_key) + + return val if Feature.disabled?(:import_fallback_to_db_empty_cache, project) + + return if val == CACHE_OBJECT_NOT_FOUND + return val if val.present? + + object_id = project.milestones.by_iid(milestone_iid).pick(:id) || CACHE_OBJECT_NOT_FOUND + + Gitlab::Cache::Import::Caching.write(cache_key, object_id) + object_id == CACHE_OBJECT_NOT_FOUND ? nil : object_id end # rubocop: disable CodeReuse/ActiveRecord @@ -35,7 +49,7 @@ module Gitlab # rubocop: enable CodeReuse/ActiveRecord def cache_key_for(iid) - CACHE_KEY % { project: project.id, iid: iid } + format(CACHE_KEY, project: project.id, iid: iid) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6f1a247ba6c..685abd710b6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -50704,6 +50704,9 @@ msgstr "" msgid "Unauthorized to create an environment" msgstr "" +msgid "Unauthorized to delete a package protection rule" +msgstr "" + msgid "Unauthorized to update the environment" msgstr "" @@ -53824,9 +53827,6 @@ msgstr "" msgid "Wiki|Create New Page" msgstr "" -msgid "Wiki|Created date" -msgstr "" - msgid "Wiki|Edit Page" msgstr "" @@ -53845,9 +53845,6 @@ msgstr "" msgid "Wiki|The sidebar failed to load. You can reload the page to try again." msgstr "" -msgid "Wiki|Title" -msgstr "" - msgid "Wiki|View All Pages" msgstr "" diff --git a/package.json b/package.json index 6ab5e20903f..ef9148f2489 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@gitlab/cluster-client": "^2.0.0", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/fonts": "^1.3.0", - "@gitlab/svgs": "3.66.0", + "@gitlab/svgs": "3.67.0", "@gitlab/ui": "66.36.1", "@gitlab/visual-review-tools": "1.7.3", "@gitlab/web-ide": "0.0.1-dev-20231004090414", diff --git a/qa/qa/page/admin/settings/metrics_and_profiling.rb b/qa/qa/page/admin/settings/metrics_and_profiling.rb index aa2399ba810..2e919dd171a 100644 --- a/qa/qa/page/admin/settings/metrics_and_profiling.rb +++ b/qa/qa/page/admin/settings/metrics_and_profiling.rb @@ -9,7 +9,7 @@ module QA view 'app/views/admin/application_settings/metrics_and_profiling.html.haml' do element 'performance-bar-settings-content' - element :usage_statistics_settings_content + element 'usage-statistics-settings-content' end def expand_performance_bar(&block) @@ -19,7 +19,7 @@ module QA end def expand_usage_statistics(&block) - expand_content(:usage_statistics_settings_content) do + expand_content('usage-statistics-settings-content') do Component::UsageStatistics.perform(&block) end end diff --git a/qa/qa/page/component/import/gitlab.rb b/qa/qa/page/component/import/gitlab.rb index 1cb8a099a70..6c5516dd2e2 100644 --- a/qa/qa/page/component/import/gitlab.rb +++ b/qa/qa/page/component/import/gitlab.rb @@ -11,17 +11,16 @@ module QA super base.view 'app/views/import/gitlab_projects/new.html.haml' do - element :import_project_button + element 'import-project-button' end base.view 'app/views/import/shared/_new_project_form.html.haml' do - element :project_name_field - element :project_slug_field + element 'project-name-field' end end def set_imported_project_name(name) - fill_element(:project_name_field, name) + fill_element('project-name-field', name) end def attach_exported_file(path) @@ -29,7 +28,7 @@ module QA end def click_import_gitlab_project - click_element(:import_project_button) + click_element('import-project-button') wait_until(reload: false) do has_notice?("The project was successfully imported.") || has_element?('project-name-content') diff --git a/qa/qa/page/component/import/selection.rb b/qa/qa/page/component/import/selection.rb index db2ff74e0f8..bd3268c3b16 100644 --- a/qa/qa/page/component/import/selection.rb +++ b/qa/qa/page/component/import/selection.rb @@ -9,16 +9,16 @@ module QA super base.view 'app/views/projects/_import_project_pane.html.haml' do - element :gitlab_import_button + element 'gitlab-import-button' end end def click_gitlab retry_until(reload: true, max_attempts: 10, message: 'Waiting for import source to be enabled') do - has_element?(:gitlab_import_button) + has_element?('gitlab-import-button') end - click_element(:gitlab_import_button) + click_element('gitlab-import-button') end end end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 2413166e120..9900343600a 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -112,7 +112,7 @@ module QA end def signed_in_as_user?(user) - return false unless has_personal_area? + return false unless signed_in? within_user_menu do has_element?('user-profile-link', text: /#{user.username}/) diff --git a/qa/qa/page/project/web_ide/vscode.rb b/qa/qa/page/project/web_ide/vscode.rb index 74194b85ebe..0997064c19f 100644 --- a/qa/qa/page/project/web_ide/vscode.rb +++ b/qa/qa/page/project/web_ide/vscode.rb @@ -40,6 +40,10 @@ module QA click_element('span[aria-label="New Folder..."]') end + def has_committed_and_pushed_successfully? + page.has_css?('.span[title="Success! Your changes have been committed."]') + end + def click_upload_menu_item click_element('span[aria-label="Upload..."]') end @@ -88,6 +92,10 @@ module QA click_element('.monaco-button[title="Create new branch"]') end + def click_continue_with_existing_branch + page.find('.monaco-button[title="Continue"]').click + end + def has_branch_input_field? has_element?('input[aria-label="input"]') end @@ -103,6 +111,32 @@ module QA page.within_frame(iframe, &block) end + def click_new_file_menu_item + page.find('[aria-label="New File..."]').click + end + + def switch_to_original_window + page.driver.browser.switch_to.window(page.driver.browser.window_handles.first) + end + + def create_new_file_from_template(filename, template) + within_vscode_editor do + Support::Waiter.wait_until(max_duration: 20, retry_on_exception: true) do + click_new_file_menu_item + enter_new_file_text_input(filename) + page.within('div.editor-container') do + page.find('textarea.inputarea.monaco-mouse-cursor-text').send_keys(template) + end + page.has_content?(filename) + end + end + end + + def enter_new_file_text_input(name) + page.find('.explorer-item-edited', visible: true) + send_keys(name, :enter) + end + # Used for stablility, due to feature_caching of vscode_web_ide def wait_for_ide_to_load page.driver.browser.switch_to.window(page.driver.browser.window_handles.last) @@ -157,6 +191,13 @@ module QA end end + def push_to_existing_branch + within_vscode_editor do + click_continue_with_existing_branch + has_committed_and_pushed_successfully? + end + end + def push_to_new_branch within_vscode_editor do click_new_branch diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb index 9e56d000070..f9e5e97b96f 100644 --- a/qa/qa/page/search/results.rb +++ b/qa/qa/page/search/results.rb @@ -20,21 +20,26 @@ module QA end def switch_to_code - switch_to_tab(:code_tab) + click_element(:nav_item_link, submenu_item: 'Code') end def switch_to_projects switch_to_tab(:projects_tab) end - def has_file_in_project?(file_name, project_name) - has_element?(:result_item_content, text: "#{project_name}: #{file_name}") + def has_project_in_search_result?(project_name) + has_element?(:result_item_content, text: project_name) end - def has_file_with_content?(file_name, file_text) - within_element_by_index(:result_item_content, 0) do - break false unless has_element?(:file_title_content, text: file_name) + def has_file_in_project?(file_name, project_name) + within_element(:result_item_content, text: project_name) do + has_element?(:file_title_content, text: file_name) + end + end + def has_file_in_project_with_content?(file_text, file_path) + within_element(:result_item_content, + text: file_path) do has_element?(:file_text_content, text: file_text) end end diff --git a/spec/factories/packages/nuget/symbol.rb b/spec/factories/packages/nuget/symbol.rb index 7ab1e026cda..665535de939 100644 --- a/spec/factories/packages/nuget/symbol.rb +++ b/spec/factories/packages/nuget/symbol.rb @@ -7,5 +7,6 @@ FactoryBot.define do file_path { 'lib/net7.0/package.pdb' } size { 100.bytes } sequence(:signature) { |n| "b91a152048fc4b3883bf3cf73fbc03f#{n}FFFFFFFF" } + file_sha256 { 'dd1aaf26c557685cc37f93f53a2b6befb2c2e679f5ace6ec7a26d12086f358be' } end end diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js index a41996d20b3..93f9f0b7e23 100644 --- a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js +++ b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js @@ -38,6 +38,7 @@ describe('CiResourceComponents', () => { }; const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findCopyToClipboardButton = (i) => wrapper.findAllByTestId('copy-to-clipboard').at(i); const findComponents = () => wrapper.findAllByTestId('component-section'); beforeEach(() => { @@ -98,6 +99,15 @@ describe('CiResourceComponents', () => { }); }); + it('adds a copy-to-clipboard button', () => { + components.forEach((component, i) => { + const button = findCopyToClipboardButton(i); + + expect(button.props().icon).toBe('copy-to-clipboard'); + expect(button.attributes('data-clipboard-text')).toContain(component.path); + }); + }); + describe('inputs', () => { it('renders the component parameter attributes', () => { const [firstComponent] = components; diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb index 6eaa603a43d..31bbd394ac1 100644 --- a/spec/helpers/wiki_helper_spec.rb +++ b/spec/helpers/wiki_helper_spec.rb @@ -115,17 +115,6 @@ RSpec.describe WikiHelper, feature_category: :wiki do end end - describe '#wiki_sort_title' do - it 'returns a title corresponding to a key' do - expect(helper.wiki_sort_title('created_at')).to eq('Created date') - expect(helper.wiki_sort_title('title')).to eq('Title') - end - - it 'defaults to Title if a key is unknown' do - expect(helper.wiki_sort_title('unknown')).to eq('Title') - end - end - describe '#wiki_page_tracking_context' do let_it_be(:page) { create(:wiki_page, title: 'path/to/page 💩', content: '💩', format: :markdown) } diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb index e7f47d334e8..62886981de1 100644 --- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb +++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do +RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:milestone) { create(:milestone, project: project) } @@ -20,23 +20,72 @@ RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache expect(finder.id_for(issuable)).to eq(milestone.id) end - it 'returns nil for an empty cache key' do + it 'returns nil if object does not exist' do + missing_issuable = double(:issuable, milestone_number: 999) + + expect(finder.id_for(missing_issuable)).to be_nil + end + + it 'fetches object id from database if not in cache' do key = finder.cache_key_for(milestone.iid) Gitlab::Cache::Import::Caching.write(key, '') - expect(finder.id_for(issuable)).to be_nil + expect(finder.id_for(issuable)).to eq(milestone.id) end it 'returns nil for an issuable with a non-existing milestone' do expect(finder.id_for(double(:issuable, milestone_number: 5))).to be_nil end + + it 'returns nil and skips database read if cache has no record' do + key = finder.cache_key_for(milestone.iid) + + Gitlab::Cache::Import::Caching.write(key, -1) + + expect(finder.id_for(issuable)).to be_nil + end end context 'without a cache in place' do - it 'returns nil' do + it 'caches the ID of a database row and returns the ID' do + expect(Gitlab::Cache::Import::Caching) + .to receive(:write) + .with("github-import/milestone-finder/#{project.id}/1", milestone.id) + .and_call_original + + expect(finder.id_for(issuable)).to eq(milestone.id) + end + end + + context 'with FF import_fallback_to_db_empty_cache disabled' do + before do + stub_feature_flags(import_fallback_to_db_empty_cache: false) + end + + it 'returns nil if object does not exist' do + missing_issuable = double(:issuable, milestone_number: 999) + + expect(finder.id_for(missing_issuable)).to be_nil + end + + it 'does not fetch object id from database if not in cache' do expect(finder.id_for(issuable)).to be_nil end + + it 'fetches object id from cache if present' do + finder.build_cache + + expect(finder.id_for(issuable)).to eq(milestone.id) + end + + it 'returns -1 if cache is -1' do + key = finder.cache_key_for(milestone.iid) + + Gitlab::Cache::Import::Caching.write(key, -1) + + expect(finder.id_for(issuable)).to eq(-1) + end end end diff --git a/spec/models/packages/nuget/symbol_spec.rb b/spec/models/packages/nuget/symbol_spec.rb index 52e95c11939..f43f3a3bdeb 100644 --- a/spec/models/packages/nuget/symbol_spec.rb +++ b/spec/models/packages/nuget/symbol_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Packages::Nuget::Symbol, type: :model, feature_category: :package subject(:symbol) { create(:nuget_symbol) } it { is_expected.to be_a FileStoreMounter } + it { is_expected.to be_a ShaAttribute } describe 'relationships' do it { is_expected.to belong_to(:package).inverse_of(:nuget_symbols) } diff --git a/spec/policies/concerns/policy_actor_spec.rb b/spec/policies/concerns/policy_actor_spec.rb index 7fd9db67032..a38725d73d6 100644 --- a/spec/policies/concerns/policy_actor_spec.rb +++ b/spec/policies/concerns/policy_actor_spec.rb @@ -20,10 +20,4 @@ RSpec.describe PolicyActor, feature_category: :shared do # initialized. So here we just use an instance expect(build(:user).methods).to include(*methods) end - - describe '#security_policy_bot?' do - subject { PolicyActorTestClass.new.security_policy_bot? } - - it { is_expected.to eq(false) } - end end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index 475e8f981dd..52fea8d782e 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -10,7 +10,6 @@ RSpec.describe GlobalPolicy, feature_category: :shared do let_it_be(:service_account) { create(:user, :service_account) } let_it_be(:migration_bot) { create(:user, :migration_bot) } let_it_be(:security_bot) { create(:user, :security_bot) } - let_it_be(:security_policy_bot) { create(:user, :security_policy_bot) } let_it_be(:llm_bot) { create(:user, :llm_bot) } let_it_be_with_reload(:current_user) { create(:user) } let_it_be(:user) { create(:user) } @@ -411,12 +410,6 @@ RSpec.describe GlobalPolicy, feature_category: :shared do it { is_expected.to be_allowed(:access_git) } end - context 'security policy bot' do - let(:current_user) { security_policy_bot } - - it { is_expected.to be_allowed(:access_git) } - end - describe 'deactivated user' do before do current_user.deactivate diff --git a/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb new file mode 100644 index 00000000000..d5b602d4664 --- /dev/null +++ b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Deleting a package protection rule', :aggregate_failures, feature_category: :package_registry do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be_with_refind(:package_protection_rule) { create(:package_protection_rule, project: project) } + let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } + + let(:mutation) { graphql_mutation(:delete_packages_protection_rule, input) } + let(:mutation_response) { graphql_mutation_response(:delete_packages_protection_rule) } + let(:input) { { id: package_protection_rule.to_global_id } } + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + shared_examples 'an erroneous reponse' do + it { subject.tap { expect(mutation_response).to be_blank } } + it { expect { subject }.not_to change { ::Packages::Protection::Rule.count } } + end + + it_behaves_like 'a working GraphQL mutation' + + it 'responds with deleted package protection rule' do + subject + + expect(mutation_response).to include( + 'packageProtectionRule' => { + 'packageNamePattern' => package_protection_rule.package_name_pattern, + 'packageType' => package_protection_rule.package_type.upcase, + 'pushProtectedUpToAccessLevel' => package_protection_rule.push_protected_up_to_access_level.upcase + }, 'errors' => be_blank + ) + end + + it { is_expected.tap { expect_graphql_errors_to_be_empty } } + it { expect { subject }.to change { ::Packages::Protection::Rule.count }.from(1).to(0) } + + context 'with existing package protection rule belonging to other project' do + let_it_be(:package_protection_rule) do + create(:package_protection_rule, package_name_pattern: 'protection_rule_other_project') + end + + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + + context 'with deleted package protection rule' do + let!(:package_protection_rule) do + create(:package_protection_rule, project: project, package_name_pattern: 'protection_rule_deleted').destroy! + end + + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + + context 'when current_user does not have permission' do + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:anonymous) { create(:user) } + + where(:current_user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + end + + context "when feature flag ':packages_protected_packages' disabled" do + before do + stub_feature_flags(packages_protected_packages: false) + end + + it_behaves_like 'an erroneous reponse' + + it { subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) } } + end +end diff --git a/spec/services/packages/nuget/symbols/create_symbol_files_service_spec.rb b/spec/services/packages/nuget/symbols/create_symbol_files_service_spec.rb index 97bfc3e06a8..96cc46884af 100644 --- a/spec/services/packages/nuget/symbols/create_symbol_files_service_spec.rb +++ b/spec/services/packages/nuget/symbols/create_symbol_files_service_spec.rb @@ -15,9 +15,9 @@ RSpec.describe Packages::Nuget::Symbols::CreateSymbolFilesService, feature_categ describe '#execute' do subject { service.execute } - shared_examples 'logs an error' do |error_class| - it 'logs an error' do - expect(Gitlab::ErrorTracking).to receive(:log_exception).with( + shared_examples 'logging an error' do |error_class| + it 'logs the error' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( an_instance_of(error_class), class: described_class.name, package_id: package.id @@ -29,8 +29,8 @@ RSpec.describe Packages::Nuget::Symbols::CreateSymbolFilesService, feature_categ context 'when symbol files are found' do it 'creates a symbol record and extracts the signature' do - expect_next_instance_of(Packages::Nuget::Symbols::ExtractSymbolSignatureService, - instance_of(String)) do |service| + expect_next_instance_of(Packages::Nuget::Symbols::ExtractSignatureAndChecksumService, + instance_of(File)) do |service| expect(service).to receive(:execute).and_call_original end @@ -47,13 +47,13 @@ RSpec.describe Packages::Nuget::Symbols::CreateSymbolFilesService, feature_categ expect { subject }.not_to change { package.nuget_symbols.count } end - it_behaves_like 'logs an error', described_class::ExtractionError + it_behaves_like 'logging an error', described_class::ExtractionError end - context 'when creating a symbol record without a signature' do + context 'without a signature' do before do - allow_next_instance_of(Packages::Nuget::Symbols::ExtractSymbolSignatureService) do |instance| - allow(instance).to receive(:execute).and_return(ServiceResponse.success(payload: nil)) + allow_next_instance_of(Packages::Nuget::Symbols::ExtractSignatureAndChecksumService) do |instance| + allow(instance).to receive(:execute).and_return(ServiceResponse.success(payload: { signature: nil })) end end @@ -64,12 +64,28 @@ RSpec.describe Packages::Nuget::Symbols::CreateSymbolFilesService, feature_categ end end - context 'when creating duplicate symbol records' do + context 'without a checksum' do + before do + allow_next_instance_of(Packages::Nuget::Symbols::ExtractSignatureAndChecksumService) do |instance| + allow(instance).to receive(:execute).and_return(ServiceResponse.success(payload: { checksum: nil })) + end + end + + it 'does not call create! on the symbol record' do + expect(::Packages::Nuget::Symbol).not_to receive(:create!) + + subject + end + end + + context 'with existing duplicate symbol records' do let_it_be(:symbol) { create(:nuget_symbol, package: package) } before do - allow_next_instance_of(Packages::Nuget::Symbols::ExtractSymbolSignatureService) do |instance| - allow(instance).to receive(:execute).and_return(ServiceResponse.success(payload: symbol.signature)) + allow_next_instance_of(Packages::Nuget::Symbols::ExtractSignatureAndChecksumService) do |instance| + allow(instance).to receive(:execute).and_return( + ServiceResponse.success(payload: { signature: symbol.signature, checksum: symbol.file_sha256 }) + ) end end @@ -77,7 +93,7 @@ RSpec.describe Packages::Nuget::Symbols::CreateSymbolFilesService, feature_categ expect { subject }.not_to change { package.nuget_symbols.count } end - it_behaves_like 'logs an error', ActiveRecord::RecordInvalid + it_behaves_like 'logging an error', ActiveRecord::RecordInvalid end context 'when a symbol file has the wrong entry size' do @@ -87,7 +103,7 @@ RSpec.describe Packages::Nuget::Symbols::CreateSymbolFilesService, feature_categ end end - it_behaves_like 'logs an error', described_class::ExtractionError + it_behaves_like 'logging an error', described_class::ExtractionError end context 'when a symbol file has the wrong entry name' do @@ -97,7 +113,7 @@ RSpec.describe Packages::Nuget::Symbols::CreateSymbolFilesService, feature_categ end end - it_behaves_like 'logs an error', described_class::ExtractionError + it_behaves_like 'logging an error', described_class::ExtractionError end end end diff --git a/spec/services/packages/nuget/symbols/extract_signature_and_checksum_service_spec.rb b/spec/services/packages/nuget/symbols/extract_signature_and_checksum_service_spec.rb new file mode 100644 index 00000000000..210b92dfce5 --- /dev/null +++ b/spec/services/packages/nuget/symbols/extract_signature_and_checksum_service_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Nuget::Symbols::ExtractSignatureAndChecksumService, feature_category: :package_registry do + let_it_be(:symbol_file_path) { expand_fixture_path('packages/nuget/symbol/package.pdb') } + let(:symbol_file) { File.new(symbol_file_path) } + + let(:service) { described_class.new(symbol_file) } + + after do + symbol_file.close + end + + describe '#execute' do + subject { service.execute } + + context 'with a valid symbol file' do + it 'returns the signature and checksum' do + payload = subject.payload + + expect(payload[:signature]).to eq('b91a152048fc4b3883bf3cf73fbc03f1FFFFFFFF') + expect(payload[:checksum]).to eq('20151ab9fc48384b83bf3cf73fbc03f1d49166cc356139845f290d1d315256c0') + end + + it 'reads the file in chunks' do + expect(symbol_file).to receive(:read).with(described_class::GUID_CHUNK_SIZE).and_call_original + expect(symbol_file).to receive(:read).with(described_class::SHA_CHUNK_SIZE, instance_of(String)) + .at_least(:once).and_call_original + + subject + end + end + + context 'with an invalid symbol file' do + before do + allow(symbol_file).to receive(:read).and_return('invalid') + end + + it 'returns an error' do + expect(subject).to be_error + expect(subject.message).to eq('Could not find the signature in the symbol file') + end + end + end +end diff --git a/spec/services/packages/nuget/symbols/extract_symbol_signature_service_spec.rb b/spec/services/packages/nuget/symbols/extract_symbol_signature_service_spec.rb deleted file mode 100644 index 87b0d00a0a7..00000000000 --- a/spec/services/packages/nuget/symbols/extract_symbol_signature_service_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Packages::Nuget::Symbols::ExtractSymbolSignatureService, feature_category: :package_registry do - let_it_be(:symbol_file) { fixture_file('packages/nuget/symbol/package.pdb') } - - let(:service) { described_class.new(symbol_file) } - - describe '#execute' do - subject { service.execute } - - context 'with a valid symbol file' do - it { expect(subject.payload).to eq('b91a152048fc4b3883bf3cf73fbc03f1FFFFFFFF') } - end - - context 'with corrupted data' do - let(:symbol_file) { 'corrupted data' } - - it { expect(subject).to be_error } - end - end -end diff --git a/spec/services/packages/protection/delete_rule_service_spec.rb b/spec/services/packages/protection/delete_rule_service_spec.rb new file mode 100644 index 00000000000..d64609d4df1 --- /dev/null +++ b/spec/services/packages/protection/delete_rule_service_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Protection::DeleteRuleService, '#execute', feature_category: :package_registry do + let_it_be(:project) { create(:project) } + let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } + let_it_be_with_refind(:package_protection_rule) { create(:package_protection_rule, project: project) } + + subject { described_class.new(package_protection_rule, current_user: current_user).execute } + + shared_examples 'a successful service response' do + it { is_expected.to be_success } + + it { + is_expected.to have_attributes( + errors: be_blank, + message: be_blank, + payload: { package_protection_rule: package_protection_rule } + ) + } + + it { subject.tap { expect { package_protection_rule.reload }.to raise_error ActiveRecord::RecordNotFound } } + end + + shared_examples 'an erroneous service response' do + it { is_expected.to be_error } + it { is_expected.to have_attributes(message: be_present, payload: { package_protection_rule: be_blank }) } + + it do + expect { subject }.not_to change { Packages::Protection::Rule.count } + + expect { package_protection_rule.reload }.not_to raise_error + end + end + + it_behaves_like 'a successful service response' + + it 'deletes the package protection rule in the database' do + expect { subject } + .to change { project.reload.package_protection_rules }.from([package_protection_rule]).to([]) + .and change { ::Packages::Protection::Rule.count }.from(1).to(0) + end + + context 'with deleted package protection rule' do + let!(:package_protection_rule) do + create(:package_protection_rule, project: project, package_name_pattern: 'protection_rule_deleted').destroy! + end + + it_behaves_like 'a successful service response' + end + + context 'when error occurs during delete operation' do + before do + allow(package_protection_rule).to receive(:destroy!).and_raise(StandardError.new('Some error')) + end + + it_behaves_like 'an erroneous service response' + + it { is_expected.to have_attributes message: /Some error/ } + end + + context 'when current_user does not have permission' do + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:anonymous) { create(:user) } + + where(:current_user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it_behaves_like 'an erroneous service response' + + it { is_expected.to have_attributes message: /Unauthorized to delete a package protection rule/ } + end + end + + context 'without package protection rule' do + let(:package_protection_rule) { nil } + + it { expect { subject }.to raise_error(ArgumentError) } + end + + context 'without current_user' do + let(:current_user) { nil } + let(:package_protection_rule) { build_stubbed(:package_protection_rule, project: project) } + + it { expect { subject }.to raise_error(ArgumentError) } + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d08f0f2e2ba..e16fbc5cefd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -301,6 +301,8 @@ RSpec.configure do |config| # https://gitlab.com/gitlab-org/gitlab/-/issues/385453 stub_feature_flags(vscode_web_ide: false) + stub_feature_flags(ai_global_switch: false) + enable_rugged = example.metadata[:enable_rugged].present? # Disable Rugged features by default diff --git a/spec/support/shared_examples/services/protected_branches_shared_examples.rb b/spec/support/shared_examples/services/protected_branches_shared_examples.rb index 15c63865720..51653a7460a 100644 --- a/spec/support/shared_examples/services/protected_branches_shared_examples.rb +++ b/spec/support/shared_examples/services/protected_branches_shared_examples.rb @@ -23,3 +23,34 @@ RSpec.shared_context 'with scan result policy blocking protected branches' do stub_licensed_features(security_orchestration_policies: true) end end + +RSpec.shared_context 'with scan result policy preventing force pushing' do + include RepoHelpers + + let(:policy_path) { Security::OrchestrationPolicyConfiguration::POLICY_PATH } + let(:default_branch) { policy_project.default_branch } + let(:prevent_force_pushing) { true } + + let(:scan_result_policy) do + build(:scan_result_policy, branches: [branch_name], + approval_settings: { prevent_force_pushing: prevent_force_pushing }) + end + + let(:policy_yaml) do + build(:orchestration_policy_yaml, scan_result_policy: [scan_result_policy]) + end + + before do + create_file_in_repo(policy_project, default_branch, default_branch, policy_path, policy_yaml) + stub_licensed_features(security_orchestration_policies: true) + end + + after do + policy_project.repository.delete_file( + policy_project.creator, + policy_path, + message: 'Automatically deleted policy', + branch_name: default_branch + ) + end +end diff --git a/yarn.lock b/yarn.lock index 31156439fea..2c0fe97b749 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1269,10 +1269,10 @@ stylelint-declaration-strict-value "1.9.2" stylelint-scss "5.1.0" -"@gitlab/svgs@3.66.0": - version "3.66.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.66.0.tgz#5dbe98f9811001942d78395756b9d7c588300c01" - integrity sha512-FdkoMAprxjJJnl90GJYoCMeIpvCaYPNAnRkrlsmo7NY3Ce8fpRb/XE/ZakqULeadj82S7R1IRuTHYfWB06vVtA== +"@gitlab/svgs@3.67.0": + version "3.67.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.67.0.tgz#72dc27e6b611acf6bdae2d7074fc413f24da6f1f" + integrity sha512-N2otHyWJqnUpJcP/lz//KdAD6VV6za7zv7QQqpD9v/p/dzCneyEc4t52g09ERHV71kv9/pWOVPqqwk9eQ2iz9A== "@gitlab/ui@66.36.1": version "66.36.1" |