Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-03-18 21:12:38 +00:00
parent c13c8ff01f
commit aa720e5357
66 changed files with 2149 additions and 1059 deletions

View File

@ -595,6 +595,21 @@ Gitlab/BoundedContexts:
- 'lib/**/*'
- 'ee/lib/**/*'
Gitlab/HardDeleteCalls:
Enabled: true
Exclude:
- 'spec/**/*'
- 'ee/spec/**/*'
- 'ee/db/fixtures/**/*'
- 'lib/tasks/**/*.rake'
- 'ee/lib/tasks/**/*.rake'
- 'app/services/groups/destroy_service.rb'
- 'app/services/projects/destroy_service.rb'
- 'app/workers/group_destroy_worker.rb'
- 'app/workers/project_destroy_worker.rb'
- 'ee/app/services/namespaces/groups/adjourned_deletion_service.rb'
- 'ee/app/services/projects/adjourned_deletion_service.rb'
Gitlab/PolicyRuleBoolean:
Enabled: true
Include:

View File

@ -0,0 +1,18 @@
---
Gitlab/HardDeleteCalls:
Details: grace period
Exclude:
- 'app/controllers/admin/groups_controller.rb'
- 'app/controllers/admin/projects_controller.rb'
- 'app/controllers/groups_controller.rb'
- 'app/controllers/organizations/groups_controller.rb'
- 'app/controllers/projects_controller.rb'
- 'app/services/groups/mark_for_deletion_service.rb'
- 'app/services/projects/mark_for_deletion_service.rb'
- 'app/services/projects/overwrite_project_service.rb'
- 'app/services/users/destroy_service.rb'
- 'app/workers/anti_abuse/banned_user_project_deletion_worker.rb'
- 'app/workers/projects/inactive_projects_deletion_cron_worker.rb'
- 'lib/api/groups.rb'
- 'lib/api/projects.rb'
- 'lib/gitlab/background_migration/delete_orphaned_groups.rb'

View File

@ -53,7 +53,6 @@ Gitlab/Rails/AttrEncrypted:
- 'ee/app/models/dependency_proxy/packages/setting.rb'
- 'ee/app/models/geo_node.rb'
- 'ee/app/models/merge_requests/external_status_check.rb'
- 'ee/app/models/remote_development/workspace_variable.rb'
- 'ee/app/models/status_page/project_setting.rb'
- 'ee/app/models/system_access/group_microsoft_application.rb'
- 'ee/app/models/system_access/group_microsoft_graph_access_token.rb'

View File

@ -27,7 +27,6 @@ Gitlab/ServiceResponse:
- 'app/services/packages/debian/create_distribution_service.rb'
- 'app/services/packages/mark_package_for_destruction_service.rb'
- 'app/services/packages/rubygems/dependency_resolver_service.rb'
- 'app/services/snippets/base_service.rb'
- 'app/services/timelogs/base_service.rb'
- 'app/services/work_items/create_and_link_service.rb'
- 'app/services/work_items/create_from_task_service.rb'

View File

@ -522,7 +522,7 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4', feature_category: :shared
gem 'gitlab-styles', '~> 13.1.0', feature_category: :tooling, require: false
gem 'haml_lint', '~> 0.58', feature_category: :tooling
gem 'haml_lint', '~> 0.58', feature_category: :tooling, require: false
# Benchmarking & profiling
gem 'benchmark-ips', '~> 2.14.0', require: false, feature_category: :shared

View File

@ -1,5 +1,6 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
import { getSkeletonRectProps } from './utils';
export default {
name: 'InputsTableSkeletonLoader',
@ -7,16 +8,7 @@ export default {
GlSkeletonLoader,
},
methods: {
getSkeletonRectProps(columnIndex, rowIndex) {
return {
x: `${columnIndex * 25.5}%`,
y: rowIndex * 10,
width: '23%',
height: 6,
rx: 2,
ry: 2,
};
},
getSkeletonRectProps,
},
};
</script>

View File

@ -20,6 +20,11 @@ export default {
type: String,
required: true,
},
savedInputs: {
type: Array,
required: false,
default: () => [],
},
},
emits: ['update-inputs'],
data() {
@ -36,8 +41,20 @@ export default {
ref: this.queryRef,
};
},
skip() {
return !this.projectPath;
},
update({ project }) {
return project?.ciPipelineCreationInputs || [];
const queryInputs = project?.ciPipelineCreationInputs || [];
const savedInputsMap = Object.fromEntries(
this.savedInputs.map(({ name, value }) => [name, value]),
);
// if there are any saved inputs, overwrite the values
return queryInputs.map((input) => ({
...input,
default: savedInputsMap[input.name] ?? input.default,
}));
},
error(error) {
createAlert({
@ -83,7 +100,7 @@ export default {
<template v-else>
<pipeline-inputs-table v-if="hasInputs" :inputs="inputs" @update="handleInputsUpdated" />
<div v-else class="gl-flex gl-justify-center gl-text-subtle">
{{ __('There are no inputs for this configuration.') }}
{{ s__('Pipelines|There are no inputs for this configuration.') }}
</div>
</template>
</crud-component>

View File

@ -52,9 +52,8 @@ export default {
</script>
<template>
<!-- Using inline style for max-height as gl-max-h-* utilities are insufficient for our needs.
Will replace with pagination or better utilities in the future. -->
<div class="gl-overflow-y-auto" style="max-height: 50rem">
<!-- Will replace with pagination in the future. -->
<div class="gl-overflow-y-auto md:gl-max-h-[50rem]">
<gl-table-lite
class="gl-mb-0"
:items="inputs"

View File

@ -0,0 +1,17 @@
/**
* Generates skeleton rect props for the skeleton loader based on column and row indices
*
* @param {Number} columnIndex - The column index (0-based)
* @param {Number} rowIndex - The row index (0-based)
* @returns {Object} - The props for the skeleton rect
*/
export const getSkeletonRectProps = (columnIndex, rowIndex) => {
return {
x: `${columnIndex * 25.5}%`,
y: rowIndex * 10,
width: '23%',
height: 6,
rx: 2,
ry: 2,
};
};

View File

@ -12,8 +12,10 @@ import { createAlert } from '~/alert';
import { visitUrl, queryToObject } from '~/lib/utils/url_utility';
import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants';
import RefSelector from '~/ref/components/ref_selector.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue';
import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
import PipelineInputsForm from '~/ci/common/pipeline_inputs/pipeline_inputs_form.vue';
import PipelineVariablesPermissionsMixin from '~/ci/mixins/pipeline_variables_permissions_mixin';
import createPipelineScheduleMutation from '../graphql/mutations/create_pipeline_schedule.mutation.graphql';
import updatePipelineScheduleMutation from '../graphql/mutations/update_pipeline_schedule.mutation.graphql';
@ -30,12 +32,13 @@ export default {
GlFormGroup,
GlFormInput,
GlLoadingIcon,
IntervalPatternInput,
PipelineInputsForm,
PipelineVariablesFormGroup,
RefSelector,
TimezoneDropdown,
IntervalPatternInput,
PipelineVariablesFormGroup,
},
mixins: [PipelineVariablesPermissionsMixin],
mixins: [glFeatureFlagsMixin(), PipelineVariablesPermissionsMixin],
inject: [
'projectPath',
'projectId',
@ -86,6 +89,7 @@ export default {
this.description = schedule.description;
this.cron = schedule.cron;
this.cronTimezone = schedule.cronTimezone;
this.savedInputs = schedule.inputs?.nodes || [];
this.scheduleRef = schedule.ref || this.defaultBranch;
this.variables = variables.map((variable) => {
return {
@ -109,14 +113,16 @@ export default {
},
data() {
return {
cron: '',
description: '',
scheduleRef: this.defaultBranch,
activated: true,
cron: '',
cronTimezone: '',
variables: [],
description: '',
pipelineInputs: [],
savedInputs: [],
schedule: {},
scheduleRef: this.defaultBranch,
updatedVariables: [],
variables: [],
};
},
i18n: {
@ -154,6 +160,9 @@ export default {
filledVariables() {
return this.updatedVariables.filter((variable) => variable.key !== '' && !variable.empty);
},
isPipelineInputsFeatureAvailable() {
return this.glFeatures.ciInputsForPipelines;
},
preparedVariablesUpdate() {
return this.filledVariables.map((variable) => {
return {
@ -203,6 +212,7 @@ export default {
variables: this.preparedVariablesCreate,
active: this.activated,
projectPath: this.projectPath,
...(this.isPipelineInputsFeatureAvailable && { inputs: this.pipelineInputs }),
},
},
});
@ -233,6 +243,7 @@ export default {
ref: this.scheduleRef,
variables: this.preparedVariablesUpdate,
active: this.activated,
...(this.isPipelineInputsFeatureAvailable && { inputs: this.pipelineInputs }),
},
},
});
@ -311,6 +322,14 @@ export default {
class="gl-w-full"
/>
</gl-form-group>
<!--Pipeline inputs-->
<pipeline-inputs-form
v-if="isPipelineInputsFeatureAvailable"
:saved-inputs="savedInputs"
:query-ref="scheduleRef"
class="gl-mb-6"
@update-inputs="pipelineInputs = $event"
/>
<!--Variable List-->
<pipeline-variables-form-group
v-if="canViewPipelineVariables"

View File

@ -60,6 +60,12 @@ query getPipelineSchedulesQuery(
name
webPath
}
inputs {
nodes {
name
value
}
}
variables {
nodes {
id

View File

@ -309,6 +309,23 @@ class ContainerRepository < ApplicationRecord
self.find_by(project: path.repository_project, name: path.repository_name)
end
def has_protected_tag_rules_for_delete?(user)
return false if Feature.disabled?(:container_registry_protected_tags, project)
return true if user.nil?
return false if user.can_admin_all_resources?
return false unless project.has_container_registry_protected_tag_rules?(
action: 'delete',
access_level: user.max_member_access_for_project(project.id)
)
# This is an API call so we put it last
return false unless has_tags?
true
end
private
def transform_tags_page(tags_response_body)

View File

@ -2,6 +2,8 @@
class ForkNetwork < ApplicationRecord
belongs_to :root_project, class_name: 'Project'
belongs_to :organization, class_name: 'Organizations::Organization', optional: true
has_many :fork_network_members
has_many :projects, through: :fork_network_members

View File

@ -427,6 +427,7 @@ class Project < ApplicationRecord
has_many :container_registry_protection_rules, class_name: 'ContainerRegistry::Protection::Rule', inverse_of: :project
has_many :container_registry_protection_tag_rules, class_name: 'ContainerRegistry::Protection::TagRule', inverse_of: :project
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
# here.
@ -3478,6 +3479,12 @@ class Project < ApplicationRecord
end
end
def has_container_registry_protected_tag_rules?(action:, access_level:)
strong_memoize_with(:has_container_registry_protected_tag_rules, action, access_level) do
container_registry_protection_tag_rules.for_actions_and_access([action], access_level).exists?
end
end
private
def with_redis(&block)

View File

@ -2,4 +2,10 @@
class ContainerRepositoryPolicy < BasePolicy
delegate { @subject.project }
condition(:protected_for_delete) { @subject.has_protected_tag_rules_for_delete?(@user) }
rule { protected_for_delete }.policy do
prevent :destroy_container_image
end
end

View File

@ -109,7 +109,13 @@ module Projects
end
def fork_network
@fork_network ||= @project.fork_network || @project.build_root_of_fork_network
@fork_network ||= @project.fork_network || build_fork_network
end
def build_fork_network
@project.build_root_of_fork_network.tap do |fork_network|
fork_network.organization = @project.organization
end
end
def build_fork_network_member(fork_to_project)

View File

@ -4,6 +4,11 @@ module Snippets
class BaseService < ::BaseProjectService
UPDATE_COMMIT_MSG = 'Update snippet'
INITIAL_COMMIT_MSG = 'Initial commit'
INVALID_PARAMS_ERROR = :invalid_params_error
INVALID_PARAMS_MESSAGES = {
cannot_be_used_together: 'and snippet files cannot be used together',
invalid_data: 'have invalid data'
}.freeze
CreateRepositoryError = Class.new(StandardError)
@ -39,19 +44,20 @@ module Snippets
def invalid_params_error(snippet)
if snippet_actions.valid?
[:content, :file_name].each do |key|
snippet.errors.add(key, 'and snippet files cannot be used together') if params.key?(key)
snippet.errors.add(key, INVALID_PARAMS_MESSAGES[:cannot_be_used_together]) if params.key?(key)
end
else
snippet.errors.add(:snippet_actions, 'have invalid data')
snippet.errors.add(:snippet_actions, INVALID_PARAMS_MESSAGES[:invalid_data])
end
snippet_error_response(snippet, 422)
# Callers need to interpret into 422
snippet_error_response(snippet, INVALID_PARAMS_ERROR)
end
def snippet_error_response(snippet, http_status)
def snippet_error_response(snippet, reason)
ServiceResponse.error(
message: snippet.errors.full_messages.to_sentence,
http_status: http_status,
reason: reason,
payload: { snippet: snippet }
)
end

View File

@ -4,6 +4,8 @@ module Snippets
class CreateService < Snippets::BaseService
include Gitlab::InternalEventsTracking
FAILED_TO_CREATE_ERROR = :failed_to_create_error
def initialize(project:, current_user: nil, params: {}, perform_spam_check: true)
super(project: project, current_user: current_user, params: params)
@perform_spam_check = perform_spam_check
@ -32,7 +34,7 @@ module Snippets
ServiceResponse.success(payload: { snippet: @snippet })
else
snippet_error_response(@snippet, 400)
snippet_error_response(@snippet, FAILED_TO_CREATE_ERROR)
end
end

View File

@ -5,6 +5,7 @@ module Snippets
include Gitlab::InternalEventsTracking
COMMITTABLE_ATTRIBUTES = %w[file_name content].freeze
FAILED_TO_UPDATE_ERROR = :failed_to_update_error
UpdateError = Class.new(StandardError)
@ -33,7 +34,7 @@ module Snippets
ServiceResponse.success(payload: { snippet: snippet })
else
snippet_error_response(snippet, 400)
snippet_error_response(snippet, FAILED_TO_UPDATE_ERROR)
end
end

View File

@ -11,6 +11,8 @@ and isn't the most recent one).
MSG
DB_MESSAGE = <<~MSG
## Database review
This merge request requires a database review. To make sure these
changes are reviewed, take the following steps:

View File

@ -8,14 +8,6 @@ description: The relationships between Epics and Issues
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3302
milestone: '10.2'
gitlab_schema: gitlab_main_cell
desired_sharding_key:
namespace_id:
references: namespaces
backfill_via:
parent:
foreign_key: issue_id
table: issues
sharding_key: namespace_id
belongs_to: issue
desired_sharding_key_migration_job_name: BackfillEpicIssuesNamespaceId
table_size: small
sharding_key:
namespace_id: namespaces

View File

@ -0,0 +1,13 @@
---
table_name: secret_detection_token_statuses
classes:
- Vulnerabilities::FindingTokenStatus
feature_categories:
- secret_detection
description: Stores the status of token detected by a secret detection scan
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182489
milestone: '17.11'
gitlab_schema: gitlab_sec
table_size: small
sharding_key:
project_id: projects

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class CreateSecretDetectionTokenStatus < Gitlab::Database::Migration[2.2]
milestone '17.11'
def change
create_table :secret_detection_token_statuses, id: false do |t|
t.bigint :vulnerability_occurrence_id, primary_key: true, default: nil
t.bigint :project_id, null: false
t.timestamps_with_timezone null: false
t.integer :status, limit: 2, null: false, default: 0
t.index :project_id, name: 'idx_secret_detect_token_on_project_id'
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddForeignKeyToVulnerabilityOccurrencesOnSecretDetectionTokenStatus < Gitlab::Database::Migration[2.2]
milestone '17.11'
disable_ddl_transaction!
def up
add_concurrent_foreign_key :secret_detection_token_statuses, :vulnerability_occurrences,
column: :vulnerability_occurrence_id, on_delete: :cascade, reverse_lock_order: true
end
def down
with_lock_retries do
remove_foreign_key_if_exists :secret_detection_token_statuses, column: :vulnerability_occurrence_id,
reverse_lock_order: true
end
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddEpicIssuesNamespaceIdNotNull < Gitlab::Database::Migration[2.2]
milestone '17.11'
disable_ddl_transaction!
def up
add_not_null_constraint :epic_issues, :namespace_id
end
def down
remove_not_null_constraint :epic_issues, :namespace_id
end
end

View File

@ -0,0 +1 @@
774b323376d9227eb65ec49bfb7b55d418c86fd73d01a023794e93ce1a911492

View File

@ -0,0 +1 @@
57df94cf5617553e2bba34da01fd617030fa638fbe8970e29a82e9698b9207b3

View File

@ -0,0 +1 @@
cd19ac58831db5ae7fc67a6b5d8b5e832da4153a11aeb24fca30a0bc27cfee57

View File

@ -13827,7 +13827,8 @@ CREATE TABLE epic_issues (
issue_id bigint NOT NULL,
relative_position integer,
namespace_id bigint,
work_item_parent_link_id bigint
work_item_parent_link_id bigint,
CONSTRAINT check_885d672eec CHECK ((namespace_id IS NOT NULL))
);
CREATE SEQUENCE epic_issues_id_seq
@ -21764,6 +21765,14 @@ CREATE SEQUENCE scim_oauth_access_tokens_id_seq
ALTER SEQUENCE scim_oauth_access_tokens_id_seq OWNED BY scim_oauth_access_tokens.id;
CREATE TABLE secret_detection_token_statuses (
vulnerability_occurrence_id bigint NOT NULL,
project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
status smallint DEFAULT 0 NOT NULL
);
CREATE SEQUENCE security_findings_id_seq
START WITH 1
INCREMENT BY 1
@ -29620,6 +29629,9 @@ ALTER TABLE ONLY scim_identities
ALTER TABLE ONLY scim_oauth_access_tokens
ADD CONSTRAINT scim_oauth_access_tokens_pkey PRIMARY KEY (id);
ALTER TABLE ONLY secret_detection_token_statuses
ADD CONSTRAINT secret_detection_token_statuses_pkey PRIMARY KEY (vulnerability_occurrence_id);
ALTER TABLE ONLY security_findings
ADD CONSTRAINT security_findings_pkey PRIMARY KEY (id, partition_number);
@ -31995,6 +32007,8 @@ CREATE INDEX idx_scan_result_policies_on_configuration_id_id_updated_at ON scan_
CREATE INDEX idx_scan_result_policy_violations_on_policy_id_and_id ON scan_result_policy_violations USING btree (scan_result_policy_id, id);
CREATE INDEX idx_secret_detect_token_on_project_id ON secret_detection_token_statuses USING btree (project_id);
CREATE INDEX idx_security_pipeline_execution_project_schedules_next_run_at ON security_pipeline_execution_project_schedules USING btree (next_run_at, id);
CREATE INDEX idx_security_policies_config_id_policy_index ON security_policies USING btree (security_orchestration_policy_configuration_id, policy_index);
@ -40753,6 +40767,9 @@ ALTER TABLE ONLY todos
ALTER TABLE ONLY packages_debian_group_architectures
ADD CONSTRAINT fk_92714bcab1 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY secret_detection_token_statuses
ADD CONSTRAINT fk_928017ddbc FOREIGN KEY (vulnerability_occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;
ALTER TABLE ONLY workspaces_agent_configs
ADD CONSTRAINT fk_94660551c8 FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;

View File

@ -1,9 +1,13 @@
---
stage: AI-powered
group: AI Framework
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
title: Set up Duo on your GitLab.com staging account
redirect_to: '../ai_features/ai_development_license.md#setting-up-duo-on-your-gitlabcom-staging-account'
redirect_to: 'ai_development_license.md'
remove_date: '2025-04-18'
---
This document has been consolidated into [GitLab Duo licensing for local development](ai_development_license.md#setting-up-duo-on-your-gitlabcom-staging-account).
<!-- markdownlint-disable -->
This document was moved to [another location](ai_development_license.md).
<!-- This redirect file can be deleted after <2025-04-18>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/development/documentation/redirects -->

View File

@ -474,7 +474,6 @@ Merge requests enforce these maximums:
The availability of this feature is controlled by a feature flag.
For more information, see the history.
This feature is available for testing, but not ready for production use.
{{< /alert >}}

View File

@ -118,22 +118,22 @@ To improve your security, try these features:
| Feature | Tier | Add-on | Offering | Status |
| ------- | ---- | ------ | -------- | ------ |
| [GitLab Duo Chat](../gitlab_duo_chat/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/_index.md) | Ultimate | GitLab Duo Enterprise | Self-managed | General availability |
| [GitLab Duo Chat](../gitlab_duo_chat/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/_index.md) | Ultimate | GitLab Duo Enterprise | GitLab Self-Managed | General availability |
| [GitLab Duo Workflow](../duo_workflow/_index.md) | Ultimate | - | GitLab.com | Experiment |
| [Issue Description Generation](../project/issues/managing_issues.md#populate-an-issue-with-issue-description-generation) | Ultimate | GitLab Duo Enterprise | GitLab.com | Experiment |
| [Discussion Summary](../discussions/_index.md#summarize-issue-discussions-with-duo-chat) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Code Suggestions](../project/repository/code_suggestions/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Code Explanation](../project/repository/code_explain.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Test Generation](../gitlab_duo_chat/examples.md#write-tests-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Refactor Code](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Fix Code](../gitlab_duo_chat/examples.md#fix-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/_index.md#gitlab-duo-for-the-cli) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Discussion Summary](../discussions/_index.md#summarize-issue-discussions-with-duo-chat) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Code Suggestions](../project/repository/code_suggestions/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Code Explanation](../project/repository/code_explain.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Test Generation](../gitlab_duo_chat/examples.md#write-tests-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Refactor Code](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Fix Code](../gitlab_duo_chat/examples.md#fix-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/_index.md#gitlab-duo-for-the-cli) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Merge Request Summary](../project/merge_requests/duo_in_merge_requests.md#generate-a-description-by-summarizing-code-changes) | Ultimate | GitLab Duo Enterprise | GitLab.com | Beta |
| [Code Review](../project/merge_requests/duo_in_merge_requests.md#have-gitlab-duo-review-your-code) | Ultimate | GitLab Duo Enterprise | GitLab.com | Beta |
| [Code Review Summary](../project/merge_requests/duo_in_merge_requests.md#summarize-a-code-review) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed | Experiment |
| [Merge Commit Message Generation](../project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Root Cause Analysis](../gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Vulnerability Explanation](../application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [Vulnerability Resolution](../application_security/vulnerabilities/_index.md#vulnerability-resolution) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed, GitLab Dedicated | General availability |
| [AI Impact Dashboard](../analytics/ai_impact_analytics.md) | Ultimate | GitLab Duo Enterprise | GitLab.com, Self-managed | General availability |
| [Code Review](../project/merge_requests/duo_in_merge_requests.md#have-gitlab-duo-review-your-code) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | Beta |
| [Code Review Summary](../project/merge_requests/duo_in_merge_requests.md#summarize-a-code-review) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed | Experiment |
| [Merge Commit Message Generation](../project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Root Cause Analysis](../gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Vulnerability Explanation](../application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Vulnerability Resolution](../application_security/vulnerabilities/_index.md#vulnerability-resolution) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [AI Impact Dashboard](../analytics/ai_impact_analytics.md) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed | General availability |

View File

@ -68,43 +68,58 @@ To create a GitLab agent for Kubernetes:
Set up your AWS credentials when you want to authenticate AWS with GitLab.
1. Create an [IAM User](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) or [IAM Role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html).
1. Make sure that your IAM user or role has the appropriate permissions for your project. For this example project, you must have the permissions shown below. You can expand this when you set up your own project.
1. Make sure that your IAM user or role has the appropriate permissions for your project. For this example project, you must have the permissions listed in the following JSON block. You can expand these permissions when you set up your own project.
```json
// IAM custom Policy definition
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ec2:*",
"eks:*",
"elasticloadbalancing:*",
"autoscaling:*",
"cloudwatch:*",
"logs:*",
"kms:DescribeKey",
"iam:AddRoleToInstanceProfile",
"iam:AttachRolePolicy",
"iam:CreateInstanceProfile",
"iam:CreateRole",
"iam:CreateServiceLinkedRole",
"iam:GetRole",
"iam:ListAttachedRolePolicies",
"iam:ListRolePolicies",
"iam:ListRoles",
"iam:PassRole",
// required for destroy step
"iam:DetachRolePolicy",
"iam:ListInstanceProfilesForRole",
"iam:DeleteRole"
],
"Resource": "*"
}
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ec2:*",
"eks:*",
"elasticloadbalancing:*",
"autoscaling:*",
"cloudwatch:*",
"logs:*",
"kms:DescribeKey",
"kms:TagResource",
"kms:UntagResource",
"kms:ListResourceTags",
"kms:CreateKey",
"kms:CreateAlias",
"kms:ListAliases",
"kms:DeleteAlias",
"iam:AddRoleToInstanceProfile",
"iam:AttachRolePolicy",
"iam:CreateInstanceProfile",
"iam:CreateRole",
"iam:CreateServiceLinkedRole",
"iam:GetRole",
"iam:ListAttachedRolePolicies",
"iam:ListRolePolicies",
"iam:ListRoles",
"iam:PassRole",
"iam:DetachRolePolicy",
"iam:ListInstanceProfilesForRole",
"iam:DeleteRole",
"iam:CreateOpenIDConnectProvider",
"iam:CreatePolicy",
"iam:TagOpenIDConnectProvider",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:GetOpenIDConnectProvider",
"iam:DeleteOpenIDConnectProvider",
"iam:ListPolicyVersions",
"iam:DeletePolicy"
],
"Resource": "*"
}
]
}
}
```
1. [Create an access key for the user or role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html).

View File

@ -115,3 +115,13 @@ To ensure tag protection, direct manifest deletion requests are only allowed whe
- Tag protection is disabled
- The user has permission to delete any protected tags
## Deleting container images
You cannot [delete container images](../../packages/container_registry/delete_container_registry_images.md) if all the following conditions are true:
- The container image has tags.
- The project has container registry tag protection rules.
- Your access level is lower than the `minimum_access_delete_level` defined in any of the rules.
This restriction applies regardless of whether the rule patterns match the container image tags.

View File

@ -55,6 +55,7 @@ Provide feedback on this feature in [issue 443236](https://gitlab.com/gitlab-org
- Status: Beta
- LLM: Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module API
module Helpers
module Snippets
# Maps service response reasons to HTTP status codes.
# See design discussion: https://gitlab.com/gitlab-org/gitlab/-/issues/356036
class HttpResponseMap
REASON_TO_HTTP_STATUS = {
success: 200,
error: 400,
invalid_params_error: 422,
failed_to_create_error: 400,
failed_to_update_error: 400
}.freeze
UNHANDLED = 'Unhandled service reason'
def self.status_for(reason)
REASON_TO_HTTP_STATUS[reason] || unhandled_reason_error(reason)
end
def self.unhandled_reason_error(reason)
Gitlab::AppLogger.warn(message: UNHANDLED, reason: reason.inspect)
500
end
private_class_method :unhandled_reason_error
end
end
end
end

View File

@ -97,7 +97,8 @@ module API
present snippet, with: Entities::ProjectSnippet, current_user: current_user
else
with_captcha_check_rest_api(spammable: snippet) do
render_api_error!({ error: service_response.message }, service_response.http_status)
http_status = Helpers::Snippets::HttpResponseMap.status_for(service_response.reason)
render_api_error!({ error: service_response.message }, http_status)
end
end
end
@ -143,7 +144,8 @@ module API
present snippet, with: Entities::ProjectSnippet, current_user: current_user
else
with_captcha_check_rest_api(spammable: snippet) do
render_api_error!({ error: service_response.message }, service_response.http_status)
http_status = Helpers::Snippets::HttpResponseMap.status_for(service_response.reason)
render_api_error!({ error: service_response.message }, http_status)
end
end
end

View File

@ -148,7 +148,8 @@ module API
present snippet, with: Entities::PersonalSnippet, current_user: current_user
else
with_captcha_check_rest_api(spammable: snippet) do
render_api_error!({ error: service_response.message }, service_response.http_status)
http_status = Helpers::Snippets::HttpResponseMap.status_for(service_response.reason)
render_api_error!({ error: service_response.message }, http_status)
end
end
end
@ -195,7 +196,8 @@ module API
present snippet, with: Entities::PersonalSnippet, current_user: current_user
else
with_captcha_check_rest_api(spammable: snippet) do
render_api_error!({ error: service_response.message }, service_response.http_status)
http_status = Helpers::Snippets::HttpResponseMap.status_for(service_response.reason)
render_api_error!({ error: service_response.message }, http_status)
end
end
end

View File

@ -13,6 +13,7 @@ module Gitlab
].freeze
LONG_RUNNING_TASKS = [
Sos::PgStatStatements,
Sos::DbLoopStatsActivity
].freeze

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Gitlab
module Database
module Sos
class PgStatStatements < BaseDbStatsHandler
include Gitlab::Utils::StrongMemoize
QUERY = <<~SQL
SELECT now() AS timestamp, *
FROM pg_stat_statements;
SQL
def run
return unless pg_stat_statements_installed?
result = execute_query(QUERY)
write_to_csv("pg_stat_statements", result, include_timestamp: true)
end
def pg_stat_statements_installed?
query = "select exists(select 1 from pg_extension where extname = 'pg_stat_statements');"
result = execute_query(query)
result.first['exists']
end
strong_memoize_attr :pg_stat_statements_installed?
end
end
end
end

View File

@ -2916,6 +2916,12 @@ msgstr ""
msgid "AccessTokens|Add a %{type}"
msgstr ""
msgid "AccessTokens|Add new token"
msgstr ""
msgid "AccessTokens|An error occurred while creating the token."
msgstr ""
msgid "AccessTokens|An error occurred while fetching the tokens."
msgstr ""
@ -2946,6 +2952,9 @@ msgstr ""
msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working."
msgstr ""
msgid "AccessTokens|At least one scope is required."
msgstr ""
msgid "AccessTokens|Copy feed token"
msgstr ""
@ -2961,18 +2970,27 @@ msgstr ""
msgid "AccessTokens|Create %{type}"
msgstr ""
msgid "AccessTokens|Create token"
msgstr ""
msgid "AccessTokens|Created"
msgstr ""
msgid "AccessTokens|Created date"
msgstr ""
msgid "AccessTokens|Description"
msgstr ""
msgid "AccessTokens|Enable DPoP"
msgstr ""
msgid "AccessTokens|Expiration date"
msgstr ""
msgid "AccessTokens|Expiration date is required."
msgstr ""
msgid "AccessTokens|Expired"
msgstr ""
@ -2994,6 +3012,45 @@ msgstr ""
msgid "AccessTokens|For example, the application using the token or the purpose of the token. Do not give sensitive information for the name of the token, as it will be visible to all %{resource_type} members."
msgstr ""
msgid "AccessTokens|Grant access to download Service Ping payload via API when authenticated as an admin user."
msgstr ""
msgid "AccessTokens|Grants access to GitLab Duo related API endpoints."
msgstr ""
msgid "AccessTokens|Grants access to manage the runners."
msgstr ""
msgid "AccessTokens|Grants complete read/write access to the API, including all groups and projects, the container registry, the dependency proxy, and the package registry."
msgstr ""
msgid "AccessTokens|Grants create access to the runners."
msgstr ""
msgid "AccessTokens|Grants permission for token to rotate itself."
msgstr ""
msgid "AccessTokens|Grants permission to perform API actions as an administrator, when Admin Mode is enabled."
msgstr ""
msgid "AccessTokens|Grants permission to perform API actions as any user in the system, when authenticated as an admin user."
msgstr ""
msgid "AccessTokens|Grants permission to perform Kubernetes API calls using the agent for Kubernetes."
msgstr ""
msgid "AccessTokens|Grants read access to the API, including all groups and projects, the container registry, and the package registry."
msgstr ""
msgid "AccessTokens|Grants read-only access to repositories on private projects using Git-over-HTTP or the Repository Files API."
msgstr ""
msgid "AccessTokens|Grants read-only access to your profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users."
msgstr ""
msgid "AccessTokens|Grants read-write access to repositories on private projects using Git-over-HTTP (not using the API)."
msgstr ""
msgid "AccessTokens|How do I use DPoP headers?"
msgstr ""
@ -3080,6 +3137,9 @@ msgstr ""
msgid "AccessTokens|Scopes set the permission levels granted to the token."
msgstr ""
msgid "AccessTokens|Scopes set the permission levels granted to the token. %{linkStart}Learn more%{linkEnd}."
msgstr ""
msgid "AccessTokens|Search or filter access tokens..."
msgstr ""
@ -3122,6 +3182,9 @@ msgstr ""
msgid "AccessTokens|Token name"
msgstr ""
msgid "AccessTokens|Token name is required."
msgstr ""
msgid "AccessTokens|Usage"
msgstr ""
@ -3137,6 +3200,9 @@ msgstr ""
msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API."
msgstr ""
msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API. You can also use personal access tokens to authenticate against Git over HTTP. They are the only accepted password when you have Two-Factor Authentication (2FA) enabled."
msgstr ""
msgid "AccessTokens|You can only have one active project access token with a trial license. You cannot generate a new token until the existing token is deleted, or you upgrade your subscription."
msgstr ""
@ -43395,6 +43461,9 @@ msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr ""
msgid "Pipelines|There are no inputs for this configuration."
msgstr ""
msgid "Pipelines|There are no merge trains for the %{branch} target branch in %{projectName}. Merge requests added to a merge train are displayed on this page. Go to a merge request to %{linkStart}start a merge train.%{linkEnd}"
msgstr ""
@ -59316,9 +59385,6 @@ msgstr ""
msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin area. Contact your GitLab instance administrator to setup custom project templates."
msgstr ""
msgid "There are no inputs for this configuration."
msgstr ""
msgid "There are no issues to show"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -1,297 +1,294 @@
{
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 31.312703777000003,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 85.723811025,
"qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb": 108.48175637,
"qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb": 111.364926809,
"qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb": 117.373596391,
"qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb": 20.240661693,
"qa/specs/features/api/1_manage/import/import_github_repo_spec.rb": 100.604753928,
"qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb": 51.773151303000006,
"qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb": 57.164676432,
"qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb": 209.66836027,
"qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb": 95.145731268,
"qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb": 96.669735449,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 22.777700688,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 12.804441871,
"qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 43.539234226000005,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 14.737558998,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 36.495285002,
"qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 53.455758953,
"qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 32.191657311,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 3.163201342,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 24.429672997,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 24.674972863,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 17.603209331,
"qa/specs/features/api/3_create/repository/files_spec.rb": 6.074523577,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 7.039305525,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 18.893561881,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 18.331837077,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 7.25170929,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 138.732236624,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 14.713357095,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 106.199417264,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 33.370348565,
"qa/specs/features/api/8_monitor/metrics_spec.rb": 4.672106511,
"qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb": 124.106589226,
"qa/specs/features/api/9_data_stores/users_spec.rb": 7.531413161,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 20.421148217,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 47.29602662,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 57.590253872,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 12.904743836,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 103.6354147,
"qa/specs/features/browser_ui/10_govern/login/log_into_gitlab_via_ldap_spec.rb": 4.187795071,
"qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb": 32.433976297,
"qa/specs/features/browser_ui/10_govern/login/login_via_instance_wide_saml_sso_spec.rb": 18.72828949,
"qa/specs/features/browser_ui/10_govern/login/oauth_login_with_github_spec.rb": 41.157460572,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 211.56071979799998,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 24.15947311,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 34.264426186,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 40.190177983,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 30.26643493,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 9.481520661,
"qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb": 12.117321519,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 101.140834264,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb": 49.602512911999995,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_issue_import_spec.rb": 36.148346744,
"qa/specs/features/browser_ui/1_manage/integrations/pipeline_status_emails_spec.rb": 66.365016218,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb": 72.170178938,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_user_contribution_reassignment_spec.rb": 170.633368898,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 27.361170958,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 32.810219511,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 22.414812761,
"qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb": 21.731724809,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 26.39903421,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 28.530292849,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 23.803294166,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 68.543888982,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 24.533760912,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 20.111584639,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 29.837648576,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 15.112964234,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 27.0954566,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 23.190903783,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 16.2804108,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 94.742394875,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 12.337184544,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 25.72992505,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 72.432054287,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 44.995716559,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 20.641570945,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 36.423745517,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 58.292547928,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 44.441698066,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 24.15771234,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 59.042601764,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 35.607056678,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 89.090076795,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb": 36.630282281,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 81.166348019,
"qa/specs/features/browser_ui/3_create/merge_request/merge_request_set_to_auto_merge_spec.rb": 64.703022855,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 79.737368209,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 39.219961205,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 102.978471064,
"qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 92.169994349,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 61.334739567,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 52.573579617,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 57.251906456,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 67.625249287,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 20.018602586,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 20.632802902,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 53.986348589,
"qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 19.492480119,
"qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 11.101996261,
"qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 17.542070237,
"qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 29.047117234,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 53.045863252000004,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 85.73131555399999,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 33.625171262,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 18.588071995,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 30.672816907,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 56.033679471,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 53.30382817,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 47.768821505,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 31.065604238,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 53.456585986,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 9.483635175,
"qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 23.004905801,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 24.234086567,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 21.222221601,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 35.108021069,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 32.508396036,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 36.330483318,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 42.982747187,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 66.373316612,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 27.828975043,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 14.292804146,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 8.376301189,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 19.310346212,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 18.103331834,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 27.902920605,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 18.601709575,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 58.335178918,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 20.684676419,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 46.25020741,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 68.623372252,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 15.533937005,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 64.056241196,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 75.73989959400001,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_glab_spec.rb": 154.711315543,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb": 132.788845052,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 53.999267897,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 64.325558272,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 342.39437633,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 248.005338145,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 83.139581136,
"qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb": 40.203053418,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 112.091917162,
"qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb": 51.780327903,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 59.266756507,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 126.555352978,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 26.146649165,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 88.597246375,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 61.090969346,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 96.955356456,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 83.837600245,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 107.868942601,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 93.38860749,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 65.814947628,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 24.370662572,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_registration_token_spec.rb": 19.696668852,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_unregister_runner_spec.rb": 32.783702631,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_counts_spec.rb": 23.285444497,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_status_counts_spec.rb": 16.425156745,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 12.010111147,
"qa/specs/features/browser_ui/4_verify/runner/register_project_runner_spec.rb": 56.395677587,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 56.948236212,
"qa/specs/features/browser_ui/5_package/container_registry/self_managed/container_registry_spec.rb": 336.46897839,
"qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb": 170.558130572,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 44.871103009,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 99.561866722,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 71.396030986,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 272.46680776,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 513.4305678439999,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 324.59947701400006,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 290.930515917,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 348.315553671,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 363.461743166,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 99.501268752,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 25.534309915,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 171.074830804,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 11.747934645,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 43.58996528,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 57.292421387000005,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 46.445470523,
"qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb": 61.596451156,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 26.865431941,
"qa/specs/features/browser_ui/9_data_stores/group/create_group_with_mattermost_team_spec.rb": 7.62795593,
"qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb": 65.166631909,
"qa/specs/features/browser_ui/9_data_stores/group/transfer_project_spec.rb": 25.912590716,
"qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 30.677051466,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_badge_spec.rb": 23.754947808,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 57.371518925000004,
"qa/specs/features/browser_ui/9_data_stores/project/dashboard_images_spec.rb": 17.48816339,
"qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb": 72.565639924,
"qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb": 105.512590394,
"qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 26.944971584,
"qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb": 36.464417609,
"qa/specs/features/browser_ui/9_data_stores/user/parent_group_access_termination_spec.rb": 27.87712138,
"qa/specs/features/browser_ui/9_data_stores/user/user_inherited_access_spec.rb": 35.758359558,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 55.897473623,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 25.263763109000003,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 65.364787871,
"qa/specs/features/ee/api/1_manage/import/import_github_repo_spec.rb": 31.153499632,
"qa/specs/features/ee/api/1_manage/integrations/group_webhook_events_spec.rb": 12.094830499,
"qa/specs/features/ee/api/1_manage/migration/gitlab_migration_group_spec.rb": 78.648789815,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 60.794241526,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 45.19848707500001,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/advanced_global_advanced_syntax_search_spec.rb": 117.250437922,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/elasticsearch_api_spec.rb": 55.743935538,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/commit_index/commit_index_spec.rb": 115.61303047,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/issues_index/issue_index_spec.rb": 56.709287471,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/main_index/blob_index_spec.rb": 21.207008245,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/merge_request_index/merge_request_index_spec.rb": 70.431202498,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/notes_index/note_index_spec.rb": 37.811999641,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/user_index/user_index_spec.rb": 132.069291484,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/nightly_elasticsearch_test_spec.rb": 19.928258532,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 122.81131311300001,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 98.919022938,
"qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 86.80656613,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 28.758285522,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 153.487471293,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 30.506825360000004,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 102.202442549,
"qa/specs/features/ee/browser_ui/10_govern/group/group_ldap_sync_spec.rb": 126.297446367,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 114.62520591999998,
"qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb": 322.994291718,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 100.8588692,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 164.72135849900002,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 54.989027421,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 200.66570050400003,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 166.07628630800002,
"qa/specs/features/ee/browser_ui/10_govern/security_policies_spec.rb": 80.963582403,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 304.313640466,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 21.265823402,
"qa/specs/features/ee/browser_ui/10_govern/vulnerabilities_jira_integration_spec.rb": 33.402676897,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 345.326474276,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_security_training_spec.rb": 134.357049619,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb": 20.569162075999998,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 4.843355821,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 25.728816186,
"qa/specs/features/ee/browser_ui/13_secure/cvs_dependency_scanning_spec.rb": 52.345787099,
"qa/specs/features/ee/browser_ui/13_secure/enable_advanced_sast_spec.rb": 137.126234557,
"qa/specs/features/ee/browser_ui/13_secure/enable_scanning_from_configuration_spec.rb": 82.742356453,
"qa/specs/features/ee/browser_ui/13_secure/secret_push_protection_spec.rb": 105.390265021,
"qa/specs/features/ee/browser_ui/16_ai_powered/duo_chat/duo_chat_spec.rb": 15.931974517,
"qa/specs/features/ee/browser_ui/1_manage/integrations/jira_issues_list_spec.rb": 57.699235578,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 73.229403091,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 105.594426127,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 38.868889772,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 11.316103986,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 10.099606894,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 126.592772512,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 20.105267349,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 7.653830696,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 20.633041741,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 10.264453209,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 26.240314396,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 28.107581157,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 29.075443314,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 10.271708079,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 26.342305449,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 16.226553964,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 21.541011214,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 49.58064934400001,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 31.174940456,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 24.526747347,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 19.823153894,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 22.978240583,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 20.568377114,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 56.270250495999996,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 25.845107059,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 39.395453035,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 20.343913591,
"qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 103.80653801,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 78.648992686,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 27.28980532,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 44.96693451,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 25.658054484,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 28.023039462,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 219.367284116,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 118.93965357600001,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 134.931337431,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 253.95235804499998,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 146.06331843499999,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 29.286008566,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 47.92659186,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 320.59896564599995,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 198.83719065100001,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 171.82875241199997,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 107.684678228,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 171.792003449,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_for_merged_result_spec.rb": 93.729740876,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 51.093958903,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 23.608011262,
"qa/specs/features/ee/browser_ui/9_data_stores/elasticsearch/elasticsearch_reindexing_spec.rb": 138.36814982599998,
"qa/specs/features/ee/browser_ui/9_data_stores/group/prevent_forking_outside_group_spec.rb": 43.633666222,
"qa/specs/features/ee/browser_ui/9_data_stores/group/share_group_with_group_spec.rb": 45.645946482
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 39.015416951000006,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 106.368852176,
"qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb": 105.049180592,
"qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb": 114.117016781,
"qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb": 112.215821194,
"qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb": 20.070271725,
"qa/specs/features/api/1_manage/import/import_github_repo_spec.rb": 62.003969309,
"qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb": 55.825349622,
"qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb": 62.935936429,
"qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb": 202.493898823,
"qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb": 87.499751174,
"qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb": 88.508076714,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 13.277984319,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 24.708294559,
"qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 19.257215062,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 23.549206831,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 17.103644498,
"qa/specs/features/api/3_create/merge_request/push_options_spec.rb": 48.787745737,
"qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 14.30835218,
"qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 13.037028017,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 3.362543138,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 22.521983931,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 16.986733483,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 10.438538951,
"qa/specs/features/api/3_create/repository/files_spec.rb": 8.292723854,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 14.309379313,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 46.82348699,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 18.842996995,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 4.836340896,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 130.826120863,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 17.87065088,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 59.64439644,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 32.128043063,
"qa/specs/features/api/8_monitor/metrics_spec.rb": 4.933892169,
"qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb": 101.195678975,
"qa/specs/features/api/9_data_stores/users_spec.rb": 7.995516989,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 22.527453243,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 53.999705944,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 53.908889334,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 13.911981617,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 106.007041931,
"qa/specs/features/browser_ui/10_govern/login/log_into_gitlab_via_ldap_spec.rb": 3.283060791,
"qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb": 29.144384337,
"qa/specs/features/browser_ui/10_govern/login/login_via_instance_wide_saml_sso_spec.rb": 15.951114865,
"qa/specs/features/browser_ui/10_govern/login/oauth_login_with_github_spec.rb": 47.003167955,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 208.003737035,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 25.351553058,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 33.319005124,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 40.696586075,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 30.745564205,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 11.25534958,
"qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb": 12.456176334,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 78.935532839,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb": 53.867358392,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_issue_import_spec.rb": 44.402761204,
"qa/specs/features/browser_ui/1_manage/integrations/pipeline_status_emails_spec.rb": 74.402252836,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb": 61.509075739,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_user_contribution_reassignment_spec.rb": 130.406320304,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 22.456073862,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 30.704792421,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 23.416268612,
"qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb": 19.650408505,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 39.904237402,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 23.676053478,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 28.733551729,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 160.463164561,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 19.1935964,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 42.444434445,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 38.610917306,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 22.115639337,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 30.853094396,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 24.081250961,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 15.645366466,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 135.79523272199998,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 12.480264716,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 34.270604428,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 77.81674518,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 48.630988975,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 18.598626078,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 37.093153591,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 37.76284062,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 48.864911859,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 25.087867893,
"qa/specs/features/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 79.635242401,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 45.435782761,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 31.730203301,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 82.792721693,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 79.294377152,
"qa/specs/features/browser_ui/3_create/merge_request/merge_request_set_to_auto_merge_spec.rb": 75.962346477,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 78.547733484,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 30.715780741,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 72.845907065,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 55.065707066,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 56.127548556,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 48.691703135,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 72.095818685,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 24.217319974,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 22.145803562,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 41.870368975000005,
"qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 15.722445173,
"qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 20.471180894,
"qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 20.460344621,
"qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 23.14264736,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 39.458224772,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 112.11682745499999,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 11.899885153,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 27.750587168,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 23.152083494,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 58.475345381,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 33.741464532,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 47.433937904000004,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 35.559179209999996,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_file_size_spec.rb": 52.099899994,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 49.290506297,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 13.125618596,
"qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 27.779947735,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 29.195662745,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 25.826480199,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 42.173013838,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 37.395484355,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 39.528317903,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 59.304316883,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 53.150999789,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 30.702030026,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 14.854521748,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 10.754807591,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 17.408281231,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 20.837560765,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 37.344303433,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 17.954152331000003,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 75.552891701,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 20.457936094,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 47.844030061,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 59.437038702,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 17.528917038,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 65.344498643,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 82.170487484,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_glab_spec.rb": 128.799186741,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb": 132.329964178,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 47.076370568,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 83.300867242,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 295.80982683700006,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 330.727897538,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 71.050121225,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 160.175345309,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 38.954995341,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 118.092139808,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 22.696241489,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 54.407585417,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 68.481154055,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 117.82635571899999,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 50.944508088,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 85.520066019,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 38.289494734,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 76.068754019,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 27.411302724,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_registration_token_spec.rb": 17.972469624,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_unregister_runner_spec.rb": 27.839527679,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_counts_spec.rb": 26.191812661,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_status_counts_spec.rb": 20.972690884,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 16.967509613,
"qa/specs/features/browser_ui/4_verify/runner/register_project_runner_spec.rb": 44.441871718,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 52.074608676,
"qa/specs/features/browser_ui/5_package/container_registry/self_managed/container_registry_spec.rb": 339.815146561,
"qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb": 158.892163116,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 67.898079823,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 79.948760081,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 76.56463333,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 261.917341999,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 537.798583543,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 312.88760401900004,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 270.221598764,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 301.388367709,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 275.612222368,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 105.725227548,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 27.660887847,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 139.250853836,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 6.653796947,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 61.254122884,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 63.371906673,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 60.674454635000004,
"qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb": 76.543664971,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 18.743305802,
"qa/specs/features/browser_ui/9_data_stores/group/create_group_with_mattermost_team_spec.rb": 8.860879939,
"qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb": 56.79010400199999,
"qa/specs/features/browser_ui/9_data_stores/group/transfer_project_spec.rb": 27.603434121,
"qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 37.518034725,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_badge_spec.rb": 20.474208584,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 57.698194097,
"qa/specs/features/browser_ui/9_data_stores/project/dashboard_images_spec.rb": 14.717029229,
"qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb": 52.531947523,
"qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb": 101.944178823,
"qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 31.945936,
"qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb": 40.756750728,
"qa/specs/features/browser_ui/9_data_stores/user/parent_group_access_termination_spec.rb": 31.607358063,
"qa/specs/features/browser_ui/9_data_stores/user/user_inherited_access_spec.rb": 34.836026899000004,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 39.535526732,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 35.68952704,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 60.760585273000004,
"qa/specs/features/ee/api/1_manage/import/import_github_repo_spec.rb": 63.724848364,
"qa/specs/features/ee/api/1_manage/integrations/group_webhook_events_spec.rb": 5.841713835,
"qa/specs/features/ee/api/1_manage/migration/gitlab_migration_group_spec.rb": 84.330011071,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 67.734790242,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 40.88584918,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/advanced_global_advanced_syntax_search_spec.rb": 107.88748357200001,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/elasticsearch_api_spec.rb": 30.462039465999997,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/commit_index/commit_index_spec.rb": 21.92566804,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/issues_index/issue_index_spec.rb": 21.179437801,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/main_index/blob_index_spec.rb": 113.333326954,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/merge_request_index/merge_request_index_spec.rb": 72.845728048,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/notes_index/note_index_spec.rb": 48.397926046,
"qa/specs/features/ee/api/9_data_stores/elasticsearch/index_tests/user_index/user_index_spec.rb": 66.84477839,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 110.74005522899999,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 74.717975852,
"qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 111.91902516,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 31.138067809,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 131.344681354,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 59.072728833999996,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 118.707501791,
"qa/specs/features/ee/browser_ui/10_govern/group/group_ldap_sync_spec.rb": 115.93010979799999,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 118.218510615,
"qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb": 224.736325594,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 123.880161656,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 154.513786391,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 73.222131825,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 196.305962014,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 145.00942302,
"qa/specs/features/ee/browser_ui/10_govern/security_policies_spec.rb": 90.637912799,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 382.386519925,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 11.842035565,
"qa/specs/features/ee/browser_ui/10_govern/vulnerabilities_jira_integration_spec.rb": 23.163571571,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 377.698799393,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb": 21.309370363,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 6.025411135,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 22.143682348,
"qa/specs/features/ee/browser_ui/13_secure/cvs_dependency_scanning_spec.rb": 43.336889577,
"qa/specs/features/ee/browser_ui/13_secure/enable_advanced_sast_spec.rb": 131.519278129,
"qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 129.325574593,
"qa/specs/features/ee/browser_ui/13_secure/secret_push_protection_spec.rb": 98.04171613899999,
"qa/specs/features/ee/browser_ui/16_ai_powered/duo_chat/duo_chat_spec.rb": 11.579960114,
"qa/specs/features/ee/browser_ui/1_manage/integrations/jira_issues_list_spec.rb": 58.073892832000006,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 54.644641075,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 46.295190376,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 39.414439645,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 19.304536865,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 10.286369479,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 197.91125446900003,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 42.211732379,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 13.477497003,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 19.156923591,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 11.53903459,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 33.608027282,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 22.345140744,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 40.64401603,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 16.258654052,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 30.093744852,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 20.979680426,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 20.356119157,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 46.448732539999995,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 23.489636985,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 14.499187043,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 21.178604938,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 28.992488871,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 21.227470595,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 35.585879116,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 47.009384816,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 46.469674276,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 18.064138468,
"qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 57.508876344,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 88.699750432,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 36.469501829,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 40.069966681,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 28.517757514,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 24.368511089,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 181.395316981,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 105.0553455,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 104.534100176,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 170.577859332,
"qa/specs/features/ee/browser_ui/3_create/repository/prevent_forking_outside_group_spec.rb": 49.567739792,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 74.894425885,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 53.353582035,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 43.11710376,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 343.07040934199995,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 209.229670413,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 177.14888967,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 38.15616983,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 109.025755969,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_for_merged_result_spec.rb": 35.021199942,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 48.187999655,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 21.398259859,
"qa/specs/features/ee/browser_ui/9_data_stores/elasticsearch/elasticsearch_reindexing_spec.rb": 139.892187759,
"qa/specs/features/ee/browser_ui/9_data_stores/group/share_group_with_group_spec.rb": 30.381249007
}

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
# This cop identifies direct calls to hard delete classes that could lead to data loss.
class HardDeleteCalls < RuboCop::Cop::Base
MSG = 'Avoid the use of `%{observed_class}`. Use `%{preferred_class}` instead. ' \
'See https://docs.gitlab.com/development/deleting_data/ '
HARD_DELETE_CLASSES = {
'Projects::DestroyService' => 'Projects::MarkForDeletionService',
'ProjectDestroyWorker' => 'Projects::MarkForDeletionService',
'Groups::DestroyService' => 'Groups::MarkForDeletionService',
'GroupDestroyWorker' => 'Groups::MarkForDeletionService'
}.freeze
def on_send(node)
check_node(node)
end
def on_csend(node)
check_node(node)
end
private
def check_node(node)
receiver = node.receiver
return unless receiver && receiver.const_type?
preferred_class = HARD_DELETE_CLASSES[receiver.const_name]
return unless preferred_class
add_offense(node, message: message(receiver.const_name, preferred_class))
end
def message(observed_class, preferred_class)
format(MSG, observed_class: observed_class, preferred_class: preferred_class)
end
end
end
end
end

View File

@ -225,6 +225,7 @@ RSpec.describe 'Database schema',
instance_integrations: %w[project_id group_id inherit_from_id], # these columns are not used in instance integrations
group_scim_identities: %w[temp_source_id], # temporary column that is not a foreign key
group_scim_auth_access_tokens: %w[temp_source_id], # temporary column that is not a foreign key
secret_detection_token_statuses: %w[project_id],
system_access_group_microsoft_graph_access_tokens: %w[temp_source_id], # temporary column that is not a foreign key
system_access_group_microsoft_applications: %w[temp_source_id], # temporary column that is not a foreign key
subscription_user_add_on_assignment_versions: %w[item_id user_id purchase_id], # Managed by paper_trail gem, no need for FK on the historical data

View File

@ -3,5 +3,9 @@
FactoryBot.define do
factory :fork_network do
association :root_project, factory: :project
before(:create) do |network|
network.organization_id = network.root_project.organization_id
end
end
end

View File

@ -14,6 +14,7 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :continuous_integrat
before do
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
stub_feature_flags(ci_inputs_for_pipelines: false)
end
context 'logged in as the pipeline schedule owner' do

View File

@ -181,8 +181,9 @@ describe('DynamicValueRenderer', () => {
${'NUMBER'} | ${'42'} | ${42} | ${false}
${'BOOLEAN'} | ${'true'} | ${true} | ${true}
${'BOOLEAN'} | ${'false'} | ${false} | ${true}
${'ARRAY'} | ${'a,b,c'} | ${'a,b,c'} | ${false}
`(
'converts input value "$inputValue" to $type typed value',
'handles input value "$inputValue" for $type type appropriately',
async ({ type, inputValue, expectedTypedValue, usesDropdown }) => {
createComponent({
props: { item: { ...defaultProps.item, type } },

View File

@ -31,16 +31,17 @@ describe('PipelineInputsForm', () => {
let wrapper;
let pipelineInputsHandler;
const createComponent = ({ props = {} } = {}) => {
const createComponent = ({ props = {}, provide = {} } = {}) => {
const handlers = [[getPipelineInputsQuery, pipelineInputsHandler]];
const mockApollo = createMockApollo(handlers);
wrapper = shallowMountExtended(PipelineInputsForm, {
propsData: {
...props,
...defaultProps,
...props,
},
provide: {
...defaultProvide,
...provide,
},
apolloProvider: mockApollo,
});
@ -49,6 +50,7 @@ describe('PipelineInputsForm', () => {
const findSkeletonLoader = () => wrapper.findComponent(InputsTableSkeletonLoader);
const findInputsTable = () => wrapper.findComponent(PipelineInputsTable);
const findCrudComponent = () => wrapper.findComponent(CrudComponent);
const findEmptyState = () => wrapper.findByText('There are no inputs for this configuration.');
describe('mounted', () => {
beforeEach(() => {
@ -83,13 +85,31 @@ describe('PipelineInputsForm', () => {
});
it('sends the correct props to the table', () => {
const expectedInputs = mockPipelineInputsResponse.data.project.ciPipelineCreationInputs;
const expectedInputs = [
{
name: 'deploy_environment',
description: 'Specify deployment environment',
default: 'staging',
type: 'text',
required: false,
options: ['staging', 'production'],
regex: '^(staging|production)$',
},
{
name: 'api_token',
description: 'API token for deployment',
default: '',
type: 'text',
required: true,
options: [],
regex: null,
},
];
expect(findInputsTable().props('inputs')).toEqual(expectedInputs);
});
it('updates the count in the crud component', () => {
const count = mockPipelineInputsResponse.data.project.ciPipelineCreationInputs.length;
expect(findCrudComponent().props('count')).toBe(count);
expect(findCrudComponent().props('count')).toBe(2);
});
});
@ -104,9 +124,7 @@ describe('PipelineInputsForm', () => {
});
it('displays the empty state message when there are no inputs', () => {
expect(wrapper.findByText('There are no inputs for this configuration.').exists()).toBe(
true,
);
expect(findEmptyState().exists()).toBe(true);
});
});
@ -124,6 +142,32 @@ describe('PipelineInputsForm', () => {
});
});
});
describe('when projectPath is not provided', () => {
beforeEach(async () => {
pipelineInputsHandler = jest.fn();
await createComponent({ provide: { projectPath: '' } });
});
it('does not execute the query', () => {
expect(pipelineInputsHandler).not.toHaveBeenCalled();
expect(findEmptyState().exists()).toBe(true);
});
});
});
describe('savedInputs prop', () => {
it('overwrites default values if saved input values are provided', async () => {
pipelineInputsHandler = jest.fn().mockResolvedValue(mockPipelineInputsResponse);
const savedInputs = [{ name: 'deploy_environment', value: 'saved-value' }];
await createComponent({ props: { savedInputs } });
await waitForPromises();
const updatedInput = findInputsTable()
.props('inputs')
.find((i) => i.name === 'deploy_environment');
expect(updatedInput.default).toBe('saved-value');
});
});
describe('event handling', () => {

View File

@ -0,0 +1,27 @@
import { getSkeletonRectProps } from '~/ci/common/pipeline_inputs/utils';
describe('Skeleton utils', () => {
describe('getSkeletonRectProps', () => {
it.each`
columnIndex | rowIndex | expectedX | expectedY
${0} | ${0} | ${'0%'} | ${0}
${1} | ${0} | ${'25.5%'} | ${0}
${2} | ${0} | ${'51%'} | ${0}
${3} | ${0} | ${'76.5%'} | ${0}
${0} | ${1} | ${'0%'} | ${10}
${2} | ${3} | ${'51%'} | ${30}
`(
'calculates correct position for col $columnIndex, row $rowIndex',
({ columnIndex, rowIndex, expectedX, expectedY }) => {
const result = getSkeletonRectProps(columnIndex, rowIndex);
expect(result.x).toBe(expectedX);
expect(result.y).toBe(expectedY);
expect(result.width).toBe('23%');
expect(result.height).toBe(6);
expect(result.rx).toBe(2);
expect(result.ry).toBe(2);
},
);
});
});

View File

@ -6,10 +6,12 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { visitUrl } from '~/lib/utils/url_utility';
import { createAlert } from '~/alert';
import PipelineInputsForm from '~/ci/common/pipeline_inputs/pipeline_inputs_form.vue';
import PipelineSchedulesForm from '~/ci/pipeline_schedules/components/pipeline_schedules_form.vue';
import PipelineVariablesFormGroup from '~/ci/pipeline_schedules/components/pipeline_variables_form_group.vue';
import RefSelector from '~/ref/components/ref_selector.vue';
import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue';
import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
import createPipelineScheduleMutation from '~/ci/pipeline_schedules/graphql/mutations/create_pipeline_schedule.mutation.graphql';
@ -87,6 +89,7 @@ describe('Pipeline schedules form', () => {
editing = false,
pipelineVariablesPermissionsMixin = mockPipelineVariablesPermissions(true),
requestHandlers,
ciInputsForPipelines = false,
} = {}) => {
wrapper = shallowMountExtended(PipelineSchedulesForm, {
propsData: {
@ -96,8 +99,11 @@ describe('Pipeline schedules form', () => {
},
provide: {
...defaultProvide,
glFeatures: {
ciInputsForPipelines,
},
},
mixins: [pipelineVariablesPermissionsMixin],
mixins: [glFeatureFlagMixin(), pipelineVariablesPermissionsMixin],
apolloProvider: createMockApolloProvider(requestHandlers),
});
};
@ -110,6 +116,7 @@ describe('Pipeline schedules form', () => {
const findSubmitButton = () => wrapper.findByTestId('schedule-submit-button');
const findCancelButton = () => wrapper.findByTestId('schedule-cancel-button');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPipelineInputsForm = () => wrapper.findComponent(PipelineInputsForm);
const findPipelineVariables = () => wrapper.findComponent(PipelineVariablesFormGroup);
describe('Form elements', () => {
@ -168,6 +175,22 @@ describe('Pipeline schedules form', () => {
});
});
it('does not display inputs form when feature flag is disabled', () => {
createComponent();
expect(findPipelineInputsForm().exists()).toBe(false);
});
it('displays inputs form when feature flag is enabled', () => {
createComponent({ ciInputsForPipelines: true });
expect(findPipelineInputsForm().exists()).toBe(true);
expect(findPipelineInputsForm().props()).toMatchObject({
queryRef: 'main',
savedInputs: [],
});
});
it('displays variable list when the user has permissions', () => {
createComponent();
@ -237,71 +260,82 @@ describe('Pipeline schedules form', () => {
});
});
describe('schedule creation success', () => {
beforeEach(() => {
createComponent();
it('creates pipeline schedule successfully', async () => {
createComponent({ ciInputsForPipelines: true });
const updatedInputs = [
{ name: 'input1', value: 'value1' },
{ name: 'input2', value: 'value2' },
];
const updatedVariables = [
{
key: 'test_var_2',
value: 'value_2',
variableType: 'ENV_VAR',
},
];
findDescription().vm.$emit('input', 'My schedule');
findTimezoneDropdown().vm.$emit('input', {
formattedTimezone: '[UTC-4] Eastern Time (US & Canada)',
identifier: 'America/New_York',
});
it('creates pipeline schedule', async () => {
findDescription().vm.$emit('input', 'My schedule');
findIntervalComponent().vm.$emit('cronValue', '0 16 * * *');
findTimezoneDropdown().vm.$emit('input', {
formattedTimezone: '[UTC-4] Eastern Time (US & Canada)',
identifier: 'America/New_York',
});
findPipelineVariables().vm.$emit('update-variables', updatedVariables);
findPipelineInputsForm().vm.$emit('update-inputs', updatedInputs);
findIntervalComponent().vm.$emit('cronValue', '0 16 * * *');
findSubmitButton().vm.$emit('click');
findPipelineVariables().vm.$emit('update-variables', [
{
key: 'test_var_2',
value: 'value_2',
variableType: 'ENV_VAR',
},
]);
await waitForPromises();
findSubmitButton().vm.$emit('click');
expect(createMutationHandlerSuccess).toHaveBeenCalledWith({
input: {
active: true,
cron: '0 16 * * *',
cronTimezone: 'America/New_York',
description: 'My schedule',
projectPath: 'gitlab-org/gitlab',
ref: 'main',
variables: updatedVariables,
inputs: updatedInputs,
},
});
expect(visitUrl).toHaveBeenCalledWith('/root/ci-project/-/pipeline_schedules');
expect(createAlert).not.toHaveBeenCalled();
});
await waitForPromises();
it('shows error for failed pipeline schedule creation', async () => {
createComponent({
requestHandlers: [[createPipelineScheduleMutation, createMutationHandlerFailed]],
});
findSubmitButton().vm.$emit('click');
expect(createMutationHandlerSuccess).toHaveBeenCalledWith({
input: {
active: true,
cron: '0 16 * * *',
cronTimezone: 'America/New_York',
description: 'My schedule',
projectPath: 'gitlab-org/gitlab',
ref: 'main',
variables: [
{
key: 'test_var_2',
value: 'value_2',
variableType: 'ENV_VAR',
},
],
},
});
expect(visitUrl).toHaveBeenCalledWith('/root/ci-project/-/pipeline_schedules');
expect(createAlert).not.toHaveBeenCalled();
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred while creating the pipeline schedule.',
});
});
describe('schedule creation failure', () => {
beforeEach(() => {
createComponent({
requestHandlers: [[createPipelineScheduleMutation, createMutationHandlerFailed]],
});
it('does not include inputs in mutation if feature flag is disabled', async () => {
createComponent({
requestHandlers: [[createPipelineScheduleMutation, createMutationHandlerSuccess]],
});
it('shows error for failed pipeline schedule creation', async () => {
findSubmitButton().vm.$emit('click');
await waitForPromises();
await waitForPromises();
await findSubmitButton().vm.$emit('click');
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred while creating the pipeline schedule.',
});
});
expect(createMutationHandlerSuccess).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.not.objectContaining({
inputs: expect.anything(),
}),
}),
);
});
});
@ -330,25 +364,23 @@ describe('Pipeline schedules form', () => {
expect(findPipelineVariables().props('initialVariables')).toHaveLength(variables.length);
});
describe('schedule fetch success', () => {
it('fetches schedule and sets form data correctly', async () => {
createComponent({
editing: true,
requestHandlers: [[getPipelineSchedulesQuery, querySuccessHandler]],
});
expect(querySuccessHandler).toHaveBeenCalled();
await waitForPromises();
expect(findDescription().props('value')).toBe(schedule.description);
expect(findIntervalComponent().props('initialCronInterval')).toBe(schedule.cron);
expect(findTimezoneDropdown().props('value')).toBe(schedule.cronTimezone);
expect(findRefSelector().props('value')).toBe(schedule.ref);
expect(findPipelineVariables().props('initialVariables')).toHaveLength(2);
expect(findPipelineVariables().props('initialVariables')[0].key).toBe(variables[0].key);
expect(findPipelineVariables().props('initialVariables')[1].key).toBe(variables[1].key);
it('fetches schedule and sets form data correctly', async () => {
createComponent({
editing: true,
requestHandlers: [[getPipelineSchedulesQuery, querySuccessHandler]],
});
expect(querySuccessHandler).toHaveBeenCalled();
await waitForPromises();
expect(findDescription().props('value')).toBe(schedule.description);
expect(findIntervalComponent().props('initialCronInterval')).toBe(schedule.cron);
expect(findTimezoneDropdown().props('value')).toBe(schedule.cronTimezone);
expect(findRefSelector().props('value')).toBe(schedule.ref);
expect(findPipelineVariables().props('initialVariables')).toHaveLength(2);
expect(findPipelineVariables().props('initialVariables')[0].key).toBe(variables[0].key);
expect(findPipelineVariables().props('initialVariables')[1].key).toBe(variables[1].key);
});
it('schedule fetch failure', async () => {
@ -365,22 +397,11 @@ describe('Pipeline schedules form', () => {
});
it('edit schedule success', async () => {
createComponent({
editing: true,
requestHandlers: [
[getPipelineSchedulesQuery, querySuccessHandler],
[updatePipelineScheduleMutation, updateMutationHandlerSuccess],
],
});
await waitForPromises();
findDescription().vm.$emit('input', 'Updated schedule');
findIntervalComponent().vm.$emit('cronValue', '0 22 16 * *');
// Ensures variable is sent with destroy property set true
findPipelineVariables().vm.$emit('update-variables', [
const updatedInputs = [
{ name: 'input1', value: 'value1' },
{ name: 'input2', value: 'value2' },
];
const updatedVariables = [
{
id: variables[0].id,
key: variables[0].key,
@ -395,7 +416,26 @@ describe('Pipeline schedules form', () => {
variableType: variables[1].variableType,
destroy: false,
},
]);
];
createComponent({
ciInputsForPipelines: true,
editing: true,
requestHandlers: [
[getPipelineSchedulesQuery, querySuccessHandler],
[updatePipelineScheduleMutation, updateMutationHandlerSuccess],
],
});
await waitForPromises();
findDescription().vm.$emit('input', 'Updated schedule');
findIntervalComponent().vm.$emit('cronValue', '0 22 16 * *');
// Ensures variable is sent with destroy property set true
findPipelineVariables().vm.$emit('update-variables', updatedVariables);
findPipelineInputsForm().vm.$emit('update-inputs', updatedInputs);
findSubmitButton().vm.$emit('click');
@ -409,22 +449,8 @@ describe('Pipeline schedules form', () => {
id: schedule.id,
ref: schedule.ref,
description: 'Updated schedule',
variables: [
{
destroy: true,
id: variables[0].id,
key: variables[0].key,
value: variables[0].value,
variableType: variables[0].variableType,
},
{
destroy: false,
id: variables[1].id,
key: variables[1].key,
value: variables[1].value,
variableType: variables[1].variableType,
},
],
variables: updatedVariables,
inputs: updatedInputs,
},
});
});
@ -448,5 +474,24 @@ describe('Pipeline schedules form', () => {
message: 'An error occurred while updating the pipeline schedule.',
});
});
it('does not include inputs in mutation if feature flag is disabled', async () => {
createComponent({
editing: true,
requestHandlers: [[updatePipelineScheduleMutation, updateMutationHandlerSuccess]],
});
await waitForPromises();
await findSubmitButton().vm.$emit('click');
expect(updateMutationHandlerSuccess).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.not.objectContaining({
inputs: expect.anything(),
}),
}),
);
});
});
});

View File

@ -9,12 +9,19 @@ RSpec.describe Mutations::ContainerRepositories::Destroy, feature_category: :con
let_it_be_with_reload(:container_repository) { create(:container_repository) }
let_it_be(:current_user) { create(:user) }
let(:project) { container_repository.project }
let_it_be(:project) { container_repository.project }
let(:id) { container_repository.to_global_id }
specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
describe '#resolve' do
let(:tags) { %w[a b c] }
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(tags: tags)
end
subject do
described_class.new(object: nil, context: query_context, field: nil)
.resolve(id: id)
@ -30,7 +37,7 @@ RSpec.describe Mutations::ContainerRepositories::Destroy, feature_category: :con
end
end
shared_examples 'denying access to container respository' do
shared_examples 'denying access to container repository' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
@ -40,9 +47,9 @@ RSpec.describe Mutations::ContainerRepositories::Destroy, feature_category: :con
where(:user_role, :shared_examples_name) do
:maintainer | 'destroying the container repository'
:developer | 'destroying the container repository'
:reporter | 'denying access to container respository'
:guest | 'denying access to container respository'
:anonymous | 'denying access to container respository'
:reporter | 'denying access to container repository'
:guest | 'denying access to container repository'
:anonymous | 'denying access to container repository'
end
with_them do
@ -53,5 +60,64 @@ RSpec.describe Mutations::ContainerRepositories::Destroy, feature_category: :con
it_behaves_like params[:shared_examples_name]
end
end
context 'when the project has tag protection rules' do
before_all do
create(
:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_delete: :owner
)
end
context 'when the container repository has tags' do
where(:user_role, :shared_examples_name) do
:owner | 'destroying the container repository'
:maintainer | 'denying access to container repository'
:developer | 'denying access to container repository'
end
with_them do
before do
project.send("add_#{user_role}", current_user)
end
it_behaves_like params[:shared_examples_name]
end
context 'when the current user is an admin', :enable_admin_mode do
let(:current_user) { build_stubbed(:admin) }
it_behaves_like 'destroying the container repository'
end
context 'when the feature container_registry_protected_tags is disabled' do
%i[owner maintainer developer].each do |user_role|
context "with the role of #{user_role}" do
before do
stub_feature_flags(container_registry_protected_tags: false)
project.send("add_#{user_role}", current_user)
end
it_behaves_like 'destroying the container repository'
end
end
end
end
context 'when the container repository does not have tags' do
let(:tags) { [] }
%i[owner maintainer developer].each do |user_role|
context "with the role of #{user_role}" do
before do
project.send("add_#{user_role}", current_user)
end
it_behaves_like 'destroying the container repository'
end
end
end
end
end
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Helpers::Snippets::HttpResponseMap, feature_category: :source_code_management do
describe '.status_for' do
context 'when reason is in the map' do
it 'returns the corresponding HTTP status', :aggregate_failures do
expect(described_class.status_for(:success)).to eq(200)
expect(described_class.status_for(:error)).to eq(400)
expect(described_class.status_for(:invalid_params_error)).to eq(422)
expect(described_class.status_for(:failed_to_create_error)).to eq(400)
expect(described_class.status_for(:failed_to_update_error)).to eq(400)
end
end
context 'when reason is not in the map' do
it 'returns 500 and logs a structured warning' do
some_unknown_reason = :some_unknown_reason
expect(Gitlab::AppLogger).to receive(:warn).with(
message: described_class::UNHANDLED,
reason: some_unknown_reason.inspect
)
expect(described_class.status_for(some_unknown_reason)).to eq(500)
end
end
end
end

View File

@ -70,6 +70,8 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do
'dast_profiles_pipelines.project_id', # LFK already present on dast_profiles and will cascade delete
'dast_scanner_profiles_builds.project_id', # LFK already present on dast_scanner_profiles and will cascade delete
'vulnerability_finding_links.project_id', # LFK already present on vulnerability_occurrence with cascade delete
'secret_detection_token_statuses.project_id',
# LFK already present on vulnerability_occurrence with cascade delete.
'ldap_group_links.group_id',
'namespace_descendants.namespace_id',
'p_batched_git_ref_updates_deletions.project_id',

View File

@ -4,14 +4,10 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::Sos::DbLoopStatsActivity, feature_category: :database do
let(:temp_directory) { Dir.mktmpdir }
let(:output_file_path) { temp_directory }
let(:output) { Gitlab::Database::Sos::Output.new(temp_directory, mode: :directory) }
let(:db_name) { 'test_db' }
let(:connection) { ApplicationRecord.connection }
let(:handler) { described_class.new(connection, db_name, output) }
let(:query) { { pg_stat_user_indexes: "SELECT * FROM pg_stat_user_indexes;" } }
let(:result) { ApplicationRecord.connection.execute(query[:pg_stat_user_indexes]) }
let(:timestamp) { Time.zone.now.strftime("%Y%m%d_%H%M%S") }
after do
FileUtils.remove_entry(temp_directory)

View File

@ -14,7 +14,7 @@ RSpec.describe Gitlab::Database::Sos::DbStatsActivity, feature_category: :databa
end
describe '#run' do
it 'executes each query successfully and writes results to CSV' do
it 'successfully writes each query result to csv' do
handler.run
described_class::QUERIES.each_key do |name|

View File

@ -0,0 +1,78 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::Sos::PgStatStatements, feature_category: :database do
include Database::DatabaseHelpers
let(:temp_directory) { Dir.mktmpdir }
let(:output) { Gitlab::Database::Sos::Output.new(temp_directory, mode: :directory) }
let(:db_name) { 'test_db' }
let(:connection) { ApplicationRecord.connection }
let(:handler) { described_class.new(connection, db_name, output) }
after do
FileUtils.remove_entry(temp_directory)
end
describe '#run' do
context 'when pg_stat_statements is installed' do
before do
connection.execute(<<~SQL)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE TABLE _test_pg_stat_statements_copy (LIKE pg_stat_statements);
CREATE OR REPLACE VIEW pg_stat_statements AS ( SELECT * FROM _test_pg_stat_statements_copy );
SQL
connection.execute(<<~SQL.squish)
INSERT INTO pg_stat_statements (
userid, dbid, queryid, query,
plans, total_plan_time, min_plan_time, max_plan_time, mean_plan_time)
VALUES (
1, 2727493, 23234938938, 'ALTER TABLE "table" DISABLE TRIGGER ALL', 0,
0.0, 0.0, 0.0, 0.0)
SQL
end
it "successfully writes the executed query results to CSV" do
allow(handler).to receive(:pg_stat_statements_installed?).and_return(true)
handler.run
file_path = File.join(temp_directory, db_name, "pg_stat_statements", "*.csv")
expect(Dir.glob(file_path).any?).to be true
end
end
context 'when pg_stat_statements is not installed' do
it "skips executing and writing to csv" do
allow(handler).to receive(:pg_stat_statements_installed?).and_return(false)
handler.run
file_path = File.join(temp_directory, db_name, "pg_stat_statements", "*.csv")
expect(Dir.glob(file_path).any?).to be false
end
end
end
describe '#pg_stats_statements_installed?' do
context 'when the pg_stat_statements is installed' do
before do
connection.execute('CREATE EXTENSION IF NOT EXISTS pg_stat_statements;')
end
it 'returns true' do
expect(handler.pg_stat_statements_installed?).to be true
end
end
context 'when pg_stat_statments is not installed' do
before do
connection.execute('DROP EXTENSION IF EXISTS pg_stat_statements;')
end
it 'returns false' do
expect(handler.pg_stat_statements_installed?).to be false
end
end
end
end

View File

@ -1238,6 +1238,7 @@ vulnerability_finding:
- feedbacks
- finding_evidence
- security_findings
- finding_token_status
scanner:
- findings
- security_findings

View File

@ -1256,4 +1256,75 @@ RSpec.describe ContainerRepository, :aggregate_failures, feature_category: :cont
expect(registry2.object_id).not_to be(registry.object_id)
end
end
describe '#has_protected_tag_rules_for_delete?' do
let_it_be(:user) { create(:user) }
let(:has_tags) { true }
subject { repository.has_protected_tag_rules_for_delete?(user) }
before do
allow(repository).to receive(:has_tags?).and_return(has_tags)
end
context 'when the feature container_registry_protected_tags is disabled' do
before do
stub_feature_flags(container_registry_protected_tags: false)
end
it { is_expected.to be_falsey }
end
context 'when the project does not have tag protection rules' do
it { is_expected.to be_falsey }
end
context 'when the user is nil' do
let(:user) { nil }
it { is_expected.to be_truthy }
end
context 'when the project has tag protection rules' do
let_it_be(:project) { create(:project, path: 'test') }
before_all do
create(
:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_delete: Gitlab::Access::OWNER
)
end
context 'for admin' do
before do
allow(user).to receive(:can_admin_all_resources?).and_return(true)
end
it { is_expected.to be_falsey }
end
context 'when user has lower access level' do
before_all do
project.add_maintainer(user)
end
it { is_expected.to be_truthy }
context 'when the container repository does not have tags' do
let(:has_tags) { false }
it { is_expected.to be_falsey }
end
end
context 'when user has the same or higher access level' do
before_all do
project.add_owner(user)
end
it { is_expected.to be_falsey }
end
end
end
end

View File

@ -9956,4 +9956,41 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
end
end
end
describe '#has_container_registry_protected_tag_rules?' do
let_it_be_with_refind(:project) { create(:project) }
subject { project.has_container_registry_protected_tag_rules?(action: 'delete', access_level: Gitlab::Access::OWNER) }
it 'returns false when there is no matching tag protection rule' do
create(:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_push: :admin,
minimum_access_level_for_delete: :maintainer
)
expect(subject).to eq(false)
end
it 'returns true when there exists a matching tag protection rule' do
create(
:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_push: :maintainer,
minimum_access_level_for_delete: :admin
)
expect(subject).to eq(true)
end
it 'memoizes the call' do
allow(project.container_registry_protection_tag_rules).to receive(:for_actions_and_access).and_call_original
2.times do
project.has_container_registry_protected_tag_rules?(action: 'push', access_level: :maintainer)
end
expect(project.container_registry_protection_tag_rules).to have_received(:for_actions_and_access).with(%w[push], :maintainer).once
end
end
end

View File

@ -0,0 +1,110 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ContainerRepositoryPolicy, feature_category: :container_registry do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:project) { create(:project, creator: user) }
let_it_be_with_reload(:container_repository) { create(:container_repository, project: project) }
subject { described_class.new(user, container_repository) }
shared_examples 'not allowing anonymous user' do
context 'when the current user is anonymous' do
let(:user) { nil }
it { is_expected.to be_disallowed(:destroy_container_image) }
end
end
describe 'destroy_container_image' do
using RSpec::Parameterized::TableSyntax
before do
allow(container_repository).to receive(:has_tags?).and_return(has_tags)
end
context 'when the project has tag protection rules' do
before_all do
create(
:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_delete: :owner
)
end
context 'when the container repository has tags' do
let(:has_tags) { true }
where(:user_role, :expected_result) do
:owner | :be_allowed
:maintainer | :be_disallowed
:developer | :be_disallowed
end
with_them do
before do
project.send(:"add_#{user_role}", user)
end
it { is_expected.to send(expected_result, :destroy_container_image) }
end
context 'when the current user is an admin', :enable_admin_mode do
let(:user) { build_stubbed(:admin) }
it { expect_allowed(:destroy_container_image) }
end
it_behaves_like 'not allowing anonymous user'
context 'when the feature container_registry_protected_tags is disabled' do
%i[owner maintainer developer].each do |user_role|
context "with the role of #{user_role}" do
before do
stub_feature_flags(container_registry_protected_tags: false)
project.send(:"add_#{user_role}", user)
end
it { expect_allowed(:destroy_container_image) }
end
end
it_behaves_like 'not allowing anonymous user'
end
end
context 'when the container repository does not have tags' do
let(:has_tags) { false }
%i[owner maintainer developer].each do |user_role|
context "with the role of #{user_role}" do
before do
project.send(:"add_#{user_role}", user)
end
it { expect_allowed(:destroy_container_image) }
end
end
it_behaves_like 'not allowing anonymous user'
end
end
context 'when the project does not have tag protection rules' do
let(:has_tags) { true }
%i[owner maintainer developer].each do |user_role|
context "with the role of #{user_role}" do
before do
project.send(:"add_#{user_role}", user)
end
it { expect_allowed(:destroy_container_image) }
end
end
it_behaves_like 'not allowing anonymous user'
end
end
end

View File

@ -8,9 +8,9 @@ RSpec.describe 'Destroying a container repository', feature_category: :container
include GraphqlHelpers
let_it_be_with_reload(:container_repository) { create(:container_repository) }
let_it_be(:user) { create(:user) }
let_it_be(:current_user) { create(:user) }
let(:project) { container_repository.project }
let_it_be(:project) { container_repository.project }
let(:id) { container_repository.to_global_id.to_s }
let(:query) do
@ -27,15 +27,17 @@ RSpec.describe 'Destroying a container repository', feature_category: :container
let(:mutation_response) { graphql_mutation_response(:destroyContainerRepository) }
let(:container_repository_mutation_response) { mutation_response['containerRepository'] }
let(:tags) { %w[a b c] }
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(tags: %w[a b c])
stub_container_registry_tags(tags: tags)
end
shared_examples 'destroying the container repository' do
it 'marks the container repository as delete_scheduled' do
expect(::Packages::CreateEventService)
.to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original
.to receive(:new).with(nil, current_user, event_name: :delete_repository, scope: :container).and_call_original
subject
@ -56,8 +58,19 @@ RSpec.describe 'Destroying a container repository', feature_category: :container
it_behaves_like 'returning response status', :success
end
shared_examples 'returning an error' do
it 'returns an error' do
subject
expect_graphql_errors_to_include(
'The resource that you are attempting to access does not exist ' \
'or you don\'t have permission to perform this action'
)
end
end
describe 'post graphql mutation' do
subject { post_graphql_mutation(mutation, current_user: user) }
subject { post_graphql_mutation(mutation, current_user:) }
context 'with valid id' do
where(:user_role, :shared_examples_name) do
@ -70,7 +83,7 @@ RSpec.describe 'Destroying a container repository', feature_category: :container
with_them do
before do
project.send("add_#{user_role}", user) unless user_role == :anonymous
project.send("add_#{user_role}", current_user) unless user_role == :anonymous
end
it_behaves_like params[:shared_examples_name]
@ -82,5 +95,62 @@ RSpec.describe 'Destroying a container repository', feature_category: :container
it_behaves_like 'denying the mutation request'
end
context 'when the project has tag protection rules' do
before_all do
create(
:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_delete: :owner
)
end
where(:user_role, :shared_examples_name) do
:owner | 'destroying the container repository'
:maintainer | 'returning an error'
:developer | 'returning an error'
end
with_them do
before do
project.send("add_#{user_role}", current_user)
end
it_behaves_like params[:shared_examples_name]
end
context 'when the container repository does not have tags' do
let(:tags) { [] }
%i[owner maintainer developer].each do |user_role|
context "with the role of #{user_role}" do
before do
project.send("add_#{user_role}", current_user)
end
it_behaves_like 'destroying the container repository'
end
end
end
context 'when the current user is an admin', :enable_admin_mode do
let(:current_user) { create(:admin) }
it_behaves_like 'destroying the container repository'
end
context 'when the feature container_registry_protected_tags is disabled' do
%i[owner maintainer developer].each do |user_role|
context "with the role of #{user_role}" do
before do
stub_feature_flags(container_registry_protected_tags: false)
project.send("add_#{user_role}", current_user)
end
it_behaves_like 'destroying the container repository'
end
end
end
end
end
end

View File

@ -285,4 +285,55 @@ RSpec.describe 'getting container repositories in a project', feature_category:
end
end
end
describe 'destroyContainerRepository' do
describe 'efficient database queries' do
let_it_be(:project) { create(:project, :private) }
let_it_be(:project_container_repositories) { create_list(:container_repository, 2, project: project) }
let(:fields) do
<<~GQL
containerRepositories {
nodes {
userPermissions {
destroyContainerRepository
}
}
}
GQL
end
before_all do
create(:container_registry_protection_tag_rule,
project: project,
tag_name_pattern: 'x'
)
end
before do
project_container_repositories.each do |repository|
stub_container_registry_tags(repository: repository.path, tags: %w[tag1 tag2 tag3], with_manifest: false)
end
end
it 'avoids N+1 database queries', :use_sql_query_cache do
query = graphql_query_for('project', { 'fullPath' => project.full_path }, fields)
first_user = create(:user, developer_of: project)
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: first_user)
end
second_user = create(:user, developer_of: project)
new_repositories = create_list(:container_repository, 2, project: project)
new_repositories.each do |repository|
stub_container_registry_tags(repository: repository.path, tags: %w[tag1 tag2 tag3], with_manifest: false)
end
expect do
post_graphql(query, current_user: second_user)
end.to issue_same_number_of_queries_as(control)
end
end
end
end

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/gitlab/hard_delete_calls'
RSpec.describe RuboCop::Cop::Gitlab::HardDeleteCalls, feature_category: :incident_management do
it 'registers an offense when using Projects::DestroyService' do
expect_offense(<<~RUBY)
Projects::DestroyService.new(project, user).execute
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid the use of `Projects::DestroyService`. Use `Projects::MarkForDeletionService` instead. [...]
RUBY
end
it 'registers an offense when using Projects::DestroyWorker' do
expect_offense(<<~RUBY)
ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid the use of `ProjectDestroyWorker`. Use `Projects::MarkForDeletionService` instead. [...]
RUBY
end
it 'registers an offense when using Groups::DestroyService' do
expect_offense(<<~RUBY)
Groups::DestroyService.new(group, user).execute
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid the use of `Groups::DestroyService`. Use `Groups::MarkForDeletionService` instead. [...]
RUBY
end
it 'registers an offense when using GroupDestroyWorker' do
expect_offense(<<~RUBY)
GroupDestroyWorker.perform_async(group.id, user.id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid the use of `GroupDestroyWorker`. Use `Groups::MarkForDeletionService` instead. [...]
RUBY
end
context 'when hard delete classes are called with safe navigation' do
it 'registers an offense for Projects::DestroyService with safe navigation' do
expect_offense(<<~RUBY)
def delete_project(project)
Projects::DestroyService&.new(project, user)&.execute
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid the use of `Projects::DestroyService`. Use `Projects::MarkForDeletionService` instead. [...]
end
RUBY
end
end
end

View File

@ -91,6 +91,7 @@ RSpec.describe Projects::ForkService, feature_category: :source_code_management
expect(fork_network).not_to be_nil
expect(fork_network.root_project).to eq(project)
expect(fork_network.projects).to contain_exactly(project, fork_of_project)
expect(fork_network.organization).to eq(project.organization)
end
it 'imports the repository of the forked project', :sidekiq_might_not_need_inline do

View File

@ -36,7 +36,7 @@ RSpec.shared_examples 'invalid params error response' do
aggregate_failures do
expect(response).to be_error
expect(response.http_status).to eq 422
expect(response.reason).to eq(described_class::INVALID_PARAMS_ERROR)
end
end
end