Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-10-25 21:10:58 +00:00
parent 62866a623e
commit 098444d917
80 changed files with 1084 additions and 171 deletions

View File

@ -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'

View File

@ -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">

View File

@ -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>

View File

@ -40,7 +40,7 @@ export default {
showAiActions() {
return (
this.resourceGlobalId &&
this.glFeatures.openaiExperimentation &&
(this.glFeatures.openaiExperimentation || this.glFeatures.aiGlobalSwitch) &&
this.glFeatures.summarizeNotes
);
},

View File

@ -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: {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -53,10 +53,6 @@ module PolicyActor
false
end
def security_policy_bot?
false
end
def automation_bot?
false
end

View File

@ -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

View File

@ -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? }

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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')

View File

@ -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 }

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
0018bc2180eeb632d75132b6d82e959e772ff1e7d8966310858e304d07d4ec34

View File

@ -0,0 +1 @@
e2b4cdafd6147740ad43c286d90f7feec9f70d66a510d58a3cc3c33b0d703b49

View File

@ -0,0 +1 @@
d3d90178100e92cffe263715cdfc3c9ddcb47ce804f3ffd92d5bc4326de0244c

View File

@ -0,0 +1 @@
840bc159c277271b66f4348c31d912485c04b8ee1b15227c96dcc690f6b93311

View File

@ -0,0 +1 @@
9a560649866367e556cf841e20f981b6c09fe03d1054f0db37cb510fbfbaef13

View File

@ -0,0 +1 @@
eba011de5a174a93e5159c765c093d3a6519111769a1ac09b2f996322cf3973e

View File

@ -0,0 +1 @@
f9659a07b4c7b2d4508f1de231e759cf4e15e684ecaa4231ff6069b4ba203e20

View File

@ -0,0 +1 @@
9df85930f78c6fa9e02252877d136aab3167a8ac1134cbd321c26f5958899f06

View File

@ -0,0 +1 @@
0be5d3565d71dc9656fd90dbd404ea0314ff29f6da9ca9ef2d100bcc9515308b

View File

@ -0,0 +1 @@
021dbeb0a8c5ebecfa647344b1e99dd1698ae3fb72a8857409551070b23f9f49

View File

@ -0,0 +1 @@
71e2f63bf9a327f62d21c2407b9ccebe779e0fd881266467f180cf285edc326f

View File

@ -0,0 +1 @@
1e9bf34cc708dd8637e4e636894fb9b7894c6d54832b3b42c88af17c4ed87532

View File

@ -0,0 +1 @@
e2ee8bcb49b470bbea1874f6a63c9b7a2fd67ef4223cf5d358de1fca4e3f36be

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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}/)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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) }

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"