Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-08 09:11:58 +00:00
parent 2551ad5119
commit eb76ded04a
76 changed files with 1989 additions and 1117 deletions

View File

@ -2,7 +2,6 @@
# Cop supports --autocorrect.
Lint/AssignmentInCondition:
Exclude:
- 'app/controllers/concerns/uploads_actions.rb'
- 'app/controllers/concerns/verifies_with_email.rb'
- 'app/controllers/omniauth_callbacks_controller.rb'
- 'app/controllers/projects/commit_controller.rb'

View File

@ -381,7 +381,6 @@ RSpec/ExampleWithoutDescription:
- 'spec/models/alert_management/alert_spec.rb'
- 'spec/models/alert_management/alert_user_mention_spec.rb'
- 'spec/models/alert_management/http_integration_spec.rb'
- 'spec/models/application_setting_spec.rb'
- 'spec/models/board_spec.rb'
- 'spec/models/bulk_imports/entity_spec.rb'
- 'spec/models/bulk_imports/tracker_spec.rb'

View File

@ -2304,7 +2304,6 @@ RSpec/NamedSubject:
- 'spec/models/alert_management/alert_spec.rb'
- 'spec/models/alerting/project_alerting_setting_spec.rb'
- 'spec/models/analytics/usage_trends/measurement_spec.rb'
- 'spec/models/application_setting_spec.rb'
- 'spec/models/audit_event_spec.rb'
- 'spec/models/aws/role_spec.rb'
- 'spec/models/blob_spec.rb'

View File

@ -1,10 +1,11 @@
<script>
import { GlAvatar, GlBadge, GlLink } from '@gitlab/ui';
import { GlAvatar, GlAvatarLink, GlBadge, GlLink } from '@gitlab/ui';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
export default {
components: {
GlAvatar,
GlAvatarLink,
GlBadge,
GlLink,
},
@ -45,7 +46,7 @@ export default {
entityName: this.name,
alt: this.name,
src: this.avatarUrl,
size: 48,
size: 32,
};
},
},
@ -54,21 +55,23 @@ export default {
</script>
<template>
<div class="gl-flex gl-items-center gl-py-5">
<gl-link v-if="href" :href="href" class="gl-mr-3 !gl-no-underline">
<li class="!gl-flex gl-items-center gl-gap-3">
<gl-avatar-link
v-if="href"
:href="href"
:data-user-id="name"
:data-username="name"
class="!gl-no-underline"
>
<gl-avatar v-bind="avatarProps" />
</gl-link>
<gl-avatar v-else v-bind="avatarProps" class="gl-mr-3" />
</gl-avatar-link>
<gl-avatar v-else v-bind="avatarProps" />
<div>
<div class="gl-mb-1">
<gl-link v-if="href" :href="href" class="gl-font-bold !gl-text-default">{{
fullName
}}</gl-link>
<span v-else class="gl-font-bold !gl-text-default">{{ fullName }}</span>
<div class="gl-flex gl-items-center gl-gap-2">
<gl-link :href="href" class="gl-font-bold !gl-text-default">{{ fullName }}</gl-link>
<gl-badge v-if="isOwner" variant="info">{{ s__('Runners|Owner') }}</gl-badge>
</div>
<div v-if="description">{{ description }}</div>
<p v-if="description" class="gl-mb-0 gl-text-sm gl-text-subtle">{{ description }}</p>
</div>
</div>
</li>
</template>

View File

@ -1,8 +1,10 @@
<script>
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import RunnerAssignedItem from './runner_assigned_item.vue';
export default {
components: {
CrudComponent,
RunnerAssignedItem,
},
props: {
@ -20,9 +22,12 @@ export default {
</script>
<template>
<div class="gl-border-t-1 gl-border-t-default gl-border-t-solid">
<h3 class="gl-mb-0 gl-mt-5 gl-text-lg">{{ s__('Runners|Assigned Group') }}</h3>
<template v-if="groups.length">
<crud-component
v-if="groups.length"
:title="s__('Runners|Assigned Group')"
data-testid="runner-groups"
>
<ul class="content-list">
<runner-assigned-item
v-for="group in groups"
:key="group.id"
@ -31,7 +36,6 @@ export default {
:full-name="group.fullName"
:avatar-url="group.avatarUrl"
/>
</template>
<span v-else class="gl-text-subtle">{{ __('None') }}</span>
</div>
</ul>
</crud-component>
</template>

View File

@ -1,10 +1,9 @@
<script>
import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { sprintf, formatNumber } from '~/locale';
import { createAlert } from '~/alert';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import runnerProjectsQuery from '../graphql/show/runner_projects.query.graphql';
import {
I18N_ASSIGNED_PROJECTS,
I18N_CLEAR_FILTER_PROJECTS,
I18N_FILTER_PROJECTS,
I18N_NO_PROJECTS_FOUND,
@ -25,6 +24,7 @@ export default {
GlSkeletonLoader,
RunnerAssignedItem,
RunnerPagination,
CrudComponent,
},
props: {
runner: {
@ -78,11 +78,6 @@ export default {
loading() {
return this.$apollo.queries.projects.loading;
},
heading() {
return sprintf(I18N_ASSIGNED_PROJECTS, {
projectCount: formatNumber(this.projects.count),
});
},
},
methods: {
isOwner(projectId) {
@ -104,42 +99,47 @@ export default {
</script>
<template>
<div class="gl-border-t-1 gl-border-t-default gl-border-t-solid">
<h3 class="gl-mt-5 gl-text-lg">
{{ heading }}
</h3>
<crud-component
:title="s__('Runner|Assigned Projects')"
:count="projects.count"
icon="project"
body-class="!gl-mx-0"
>
<gl-search-box-by-type
:is-loading="loading"
:clear-button-title="$options.I18N_CLEAR_FILTER_PROJECTS"
:placeholder="$options.I18N_FILTER_PROJECTS"
debounce="500"
class="gl-w-28"
class="gl-m-5"
:value="search"
@input="onSearchInput"
/>
<div v-if="!projects.items.length && loading" class="gl-py-5">
<div v-if="!projects.items.length && loading" class="gl-p-5">
<gl-skeleton-loader v-for="i in $options.RUNNER_DETAILS_PROJECTS_PAGE_SIZE" :key="i" />
</div>
<template v-else-if="projects.items.length">
<runner-assigned-item
v-for="(project, i) in projects.items"
:key="project.id"
:class="{ 'gl-border-t-1 gl-border-t-default gl-border-t-solid': i !== 0 }"
:href="project.webUrl"
:name="project.name"
:full-name="project.nameWithNamespace"
:avatar-url="project.avatarUrl"
:description="project.description"
:is-owner="isOwner(project.id)"
<ul class="content-list gl-border-t gl-border-t-section">
<runner-assigned-item
v-for="project in projects.items"
:key="project.id"
:href="project.webUrl"
:name="project.name"
:full-name="project.nameWithNamespace"
:avatar-url="project.avatarUrl"
:description="project.description"
:is-owner="isOwner(project.id)"
/>
</ul>
</template>
<div v-else class="gl-mt-4 gl-px-5 gl-text-subtle">{{ $options.I18N_NO_PROJECTS_FOUND }}</div>
<template #pagination>
<runner-pagination
:disabled="loading"
:page-info="projects.pageInfo"
@input="onPaginationInput"
/>
</template>
<div v-else class="gl-py-5 gl-text-subtle">{{ $options.I18N_NO_PROJECTS_FOUND }}</div>
<runner-pagination
:disabled="loading"
:page-info="projects.pageInfo"
@input="onPaginationInput"
/>
</div>
</crud-component>
</template>

View File

@ -1,3 +1,8 @@
import initCompareSelector from '~/projects/compare';
import { createRapidDiffsApp } from '~/rapid_diffs/app';
initCompareSelector();
const app = createRapidDiffsApp();
app.init();
app.reloadDiffs();

View File

@ -0,0 +1,6 @@
@import 'mixins_and_variables_and_functions';
@import 'components/rapid_diffs';
.rd-app {
--rd-app-sticky-top: calc(#{$calc-application-header-height});
}

View File

@ -80,8 +80,8 @@ module UploadsActions
# behavior when serving uploads.
def set_request_format_from_path_extension
path = request.headers['action_dispatch.original_path'] || request.headers['PATH_INFO']
return unless match = path&.match(/\.(\w+)\z/)
match = path&.match(/\.(\w+)\z/)
return unless match
format = Mime[match.captures.first]
@ -120,7 +120,8 @@ module UploadsActions
# rubocop: disable CodeReuse/ActiveRecord
def build_uploader_from_upload
return unless uploader = build_uploader
uploader = build_uploader
return unless uploader
upload_paths = uploader.upload_paths(params[:filename])
upload = Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths)

View File

@ -80,6 +80,11 @@ class Projects::CompareController < Projects::ApplicationController
def rapid_diffs
return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip)
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
@reload_stream_url = diffs_stream_namespace_project_compare_index_path(**compare_params)
@diff_files_endpoint = diff_files_metadata_namespace_project_compare_index_path(**compare_params)
@update_current_user_path = expose_path(api_v4_user_preferences_path)
show
end

View File

@ -21,6 +21,7 @@ class TodosFinder
include FinderMethods
include Gitlab::Utils::StrongMemoize
include SafeFormatHelper
include Gitlab::InternalEventsTracking
requires_cross_project_access unless: -> { project? }
@ -48,6 +49,8 @@ class TodosFinder
return Todo.none if current_user.nil?
raise ArgumentError, invalid_type_message unless valid_types?
track_bot_user if current_user.bot?
items = current_user.todos
items = without_hidden(items)
items = by_action_id(items)
@ -261,6 +264,17 @@ class TodosFinder
def filter_done_only?
Array.wrap(params[:state]).map(&:to_sym) == [:done]
end
def track_bot_user
track_internal_event(
"request_todos_by_bot_user",
user: current_user,
additional_properties: {
label: 'user_type',
property: current_user.user_type
}
)
end
end
TodosFinder.prepend_mod_with('TodosFinder')

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module WorkItems
module Glql
class WorkItemsFinder
def initialize(current_user, context, resource_parent, params = {})
@current_user = current_user
@context = context
@resource_parent = resource_parent
@params = params
end
# Overwritten in ee/app/finders/ee/work_items/glql/work_items_finder.rb
def use_elasticsearch_finder?
false
end
end
end
end
WorkItems::Glql::WorkItemsFinder.prepend_mod

View File

@ -21,14 +21,31 @@ module Resolvers
def resolve_with_lookahead(**args)
return WorkItem.none if resource_parent.nil?
Gitlab::Graphql::Loaders::IssuableLoader.new(
resource_parent,
finder(prepare_finder_params(args))
).batching_find_all { |q| apply_lookahead(q) }
finder = choose_finder(args)
Gitlab::Graphql::Loaders::IssuableLoader
.new(resource_parent, finder)
.batching_find_all { |q| apply_lookahead(q) }
end
private
def choose_finder(args)
if ::Feature.enabled?(:glql_es_integration, current_user)
glql_finder = glql_finder(args)
return glql_finder if glql_finder.use_elasticsearch_finder?
end
finder(prepare_finder_params(args))
end
def glql_finder(args)
::WorkItems::Glql::WorkItemsFinder.new(current_user, context, resource_parent, args)
end
# When we search on a group level, this finder is being overwritten in
# app/graphql/resolvers/namespaces/work_items_resolver.rb:32
def finder(args)
::WorkItems::WorkItemsFinder.new(current_user, args)
end

View File

@ -179,7 +179,6 @@ module Ci
scope :with_pipeline_locked_artifacts, -> { joins(:pipeline).where('pipeline.locked': Ci::Pipeline.lockeds[:artifacts_locked]) }
scope :last_month, -> { where('created_at > ?', Date.today - 1.month) }
scope :scheduled_actions, -> { where(when: :delayed, status: COMPLETED_STATUSES + %i[scheduled]) }
scope :ref_protected, -> { where(protected: true) }
scope :with_live_trace, -> { where_exists(Ci::BuildTraceChunk.scoped_build) }
scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) }
scope :finished_before, ->(date) { finished.where('finished_at < ?', date) }

View File

@ -70,6 +70,7 @@ class CommitStatus < Ci::ApplicationRecord
scope :after_stage, ->(index) { where('stage_idx > ?', index) }
scope :for_project, ->(project_id) { where(project_id: project_id) }
scope :for_ref, ->(ref) { where(ref: ref) }
scope :for_user, ->(user) { where(user: user) }
scope :by_name, ->(name) { where(name: name) }
scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) }
scope :with_pipeline, -> { joins(:pipeline) }
@ -80,6 +81,7 @@ class CommitStatus < Ci::ApplicationRecord
}
scope :with_when_executed, ->(when_executed) { where(when: when_executed) }
scope :with_type, ->(type) { where(type: type) }
scope :ref_protected, -> { where(protected: true) }
# The scope applies `pluck` to split the queries. Use with care.
scope :for_project_paths, ->(paths) do

View File

@ -26,7 +26,7 @@ module Ci
result = validate
return result if result&.error?
@pipeline = first_matching_pipeline || create_pipeline
@pipeline = find_or_create_pipeline
return forbidden unless ::Ability.allowed?(current_user, :update_pipeline, pipeline)
@stage = find_or_create_external_stage
@ -46,9 +46,13 @@ module Ci
return bad_request('State is required') if params[:state].blank?
return not_found('References for commit') if ref.blank?
return unless params[:pipeline_id] && !first_matching_pipeline
return unless params[:pipeline_id]
not_found("Pipeline for pipeline_id, sha and ref")
return not_found("Pipeline for pipeline_id, sha and ref") unless first_matching_pipeline
return if can_append_jobs_to_existing_pipeline?
error("The number of jobs has exceeded the limit", :unprocessable_entity)
end
def ref
@ -62,6 +66,23 @@ module Ci
end
strong_memoize_attr :commit
def find_or_create_pipeline
return create_pipeline unless first_matching_pipeline
return first_matching_pipeline if can_append_jobs_to_existing_pipeline?
create_log_entry
enforce_jobs_limit? ? create_pipeline : first_matching_pipeline
end
def can_append_jobs_to_existing_pipeline?
return true unless first_matching_pipeline_size_exceeded?
return true if external_commit_status_exists?
false
end
strong_memoize_attr :can_append_jobs_to_existing_pipeline?
def first_matching_pipeline
limit = params[:pipeline_id] ? nil : DEFAULT_LIMIT_PIPELINES
pipelines = project.ci_pipelines.newest_first(sha: sha, limit: limit)
@ -71,6 +92,13 @@ module Ci
end
strong_memoize_attr :first_matching_pipeline
def first_matching_pipeline_size_exceeded?
project
.actual_limits
.exceeded?(:ci_pipeline_size, first_matching_pipeline.all_jobs)
end
strong_memoize_attr :first_matching_pipeline_size_exceeded?
def name
params[:name] || params[:context] || 'default'
end
@ -99,22 +127,32 @@ module Ci
end
end
def external_commit_status_exists?
external_commit_status_scope(first_matching_pipeline).any?
end
def find_or_build_external_commit_status
::GenericCommitStatus.running_or_pending.find_or_initialize_by( # rubocop:disable CodeReuse/ActiveRecord
project: project,
pipeline: pipeline,
name: name,
ref: ref,
user: current_user,
protected: project.protected_for?(ref),
external_commit_status_scope(pipeline).find_or_initialize_by( # rubocop:disable CodeReuse/ActiveRecord
ci_stage: stage,
stage_idx: stage.position,
partition_id: pipeline.partition_id
stage_idx: stage.position
).tap do |new_commit_status|
new_commit_status.assign_attributes(optional_commit_status_params)
end
end
def external_commit_status_scope(pipeline)
scope = ::GenericCommitStatus
.running_or_pending
.for_project(project.id)
.in_pipelines(pipeline)
.in_partition(pipeline.partition_id)
.for_ref(ref)
.by_name(name)
.for_user(current_user)
scope = scope.ref_protected if project.protected_for?(ref)
scope
end
def add_or_update_external_job
::Ci::Pipelines::AddJobService.new(pipeline).execute!(commit_status) do |job|
apply_job_state!(job)
@ -153,6 +191,22 @@ module Ci
}
end
def create_log_entry
Gitlab::AppJsonLogger.info(
class: self.class.name,
namespace_id: project.namespace_id,
project_id: project.id,
current_user_id: current_user.id,
subscription_plan: project.actual_plan_name,
message: 'Project tried to create more jobs than the quota allowed',
limit_enforced: enforce_jobs_limit?
)
end
def enforce_jobs_limit?
Feature.enabled?(:ci_limit_commit_statuses, project)
end
def not_found(message)
error("404 #{message} Not Found", :not_found)
end

View File

@ -10,7 +10,7 @@
= render ::Layouts::SettingsSectionComponent.new(_('Restrict projects for this runner'), options: { class: 'gl-mt-7 gl-pt-6 gl-border-t' }) do |c|
- c.with_body do
.gl-flex.gl-flex-col.gl-gap-5
= render ::Layouts::CrudComponent.new(_('Assigned projects')) do |c|
= render ::Layouts::CrudComponent.new(_('Assigned projects'), icon: 'project', count: @runner.runner_projects.count) do |c|
- c.with_body do
- if @runner.runner_projects.any?
%ul.content-list{ data: { testid: 'assigned-projects' } }

View File

@ -14,7 +14,7 @@
-# Broadcast messages
.broadcast-wrapper
= dispensable_render_if_exists "shared/token_expiration_banner"
= dispensable_render "layouts/broadcast"
= dispensable_render "layouts/broadcast" unless content_for(:hide_broadcast_messages).present?
= yield :group_invite_members_banner
-# Alerts

View File

@ -26,6 +26,6 @@
= dispensable_render_if_exists "projects/importing_alert", project: @project
= dispensable_render_if_exists "shared/web_hooks/web_hook_disabled_alert"
= dispensable_render_if_exists "projects/free_user_cap_alert", project: @project
= dispensable_render_if_exists 'shared/unlimited_members_during_trial_alert', resource: @project
= dispensable_render_if_exists 'shared/unlimited_members_during_trial_alert', resource: @project unless content_for(:hide_unlimited_members_during_trial_alert).present?
= render template: "layouts/application"

View File

@ -5,6 +5,8 @@
- has_diff = @commits.present? || @diffs.present? && @diffs.diff_files.present?
-# Only show commit list in the first page
- hide_commit_list = params[:page].present? && params[:page] != '1'
- add_page_specific_style 'page_bundles/merge_requests'
- add_page_specific_style 'page_bundles/compare_rapid_diffs'
.container-fluid{ class: [container_class] }
.gl-border-b-0.gl-mb-0.gl-pt-4
@ -15,7 +17,8 @@
.container-fluid{ class: [container_class] }
= render "projects/commits/commit_list" unless hide_commit_list
.container-fluid
= render RapidDiffs::DiffFileComponent.with_collection(@diffs.diff_files, parallel_view: diff_view == :parallel)
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: nil, show_whitespace: @show_whitespace_default, diff_view: diff_view, metadata_endpoint: nil, update_user_endpoint: @update_current_user_path, diff_files_endpoint: @diff_files_endpoint, lazy: true }
= render ::RapidDiffs::AppComponent.new(**args)
- else
.container-fluid
= render Pajamas::CardComponent.new(card_options: { class: "gl-bg-subtle" }) do |c|

View File

@ -0,0 +1,24 @@
---
description: Using inputs when creating a pipeline
internal_events: true
action: create_pipeline_with_inputs
identifiers:
- project
- namespace
- user
additional_properties:
label:
description: The source of the pipeline, e.g. push, merge_request, schedule, etc.
property:
description: The source of the config, e.g. repository_source, auto_devops_source, etc.
value:
description: Number of inputs passed from the user
product_group: pipeline_authoring
product_categories:
- pipeline_composition
milestone: '17.11'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186735
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
description: todos accessed by user who is a bot
internal_events: true
action: request_todos_by_bot_user
identifiers:
- user
additional_properties:
label:
description: type of user
property:
description: type of user who requested todos
product_group: personal_productivity
product_categories:
- notifications
milestone: '17.11'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185261
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,9 @@
---
name: ci_limit_commit_statuses
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/436361
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185807
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/526901
milestone: '17.11'
group: group::ci platform
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: glql_es_integration
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/524326
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185103
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/527280
milestone: '17.11'
group: group::knowledge
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,23 @@
---
key_path: redis_hll_counters.count_distinct_project_id_from_create_pipeline_with_inputs
description: Count of unique projects that created a CI pipeline with inputs via any source.
product_group: pipeline_authoring
product_categories:
- pipeline_composition
performance_indicator_type: []
value_type: number
status: active
milestone: '17.11'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186735
time_frame:
- 28d
- 7d
data_source: internal_events
data_category: optional
tiers:
- free
- premium
- ultimate
events:
- name: create_pipeline_with_inputs
unique: project.id

View File

@ -0,0 +1,23 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_request_todos_by_bot_user
description: Count of unique users who are bots and who are accessing todos through webui/api endpoint.
product_group: personal_productivity
product_categories:
- notifications
performance_indicator_type: []
value_type: number
status: active
milestone: '17.11'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185261
time_frame:
- 28d
- 7d
data_source: internal_events
data_category: optional
tiers:
- free
- premium
- ultimate
events:
- name: request_todos_by_bot_user
unique: user.id

View File

@ -8,9 +8,9 @@
window: 1
impact: high # Can be one of: [critical, high, medium, low]
scope: project # Can be one or a combination of: [instance, group, project]
reporter: jheimbuck_gl # (required) GitLab username of the person reporting the deprecation
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/395708 # (required) Link to the deprecation issue in GitLab
reporter: jocelynjane # (required) GitLab username of the person reporting the deprecation
stage: Software Supply Chain Security # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383084 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
In GitLab 14.4, we introduced a setting to [limit access _from_ your project's CI/CD job tokens (`CI_JOB_TOKEN`)](https://docs.gitlab.com/ci/jobs/ci_job_token/#limit-your-projects-job-token-access) to make it more secure.
This setting was called **Limit CI_JOB_TOKEN access**. In GitLab 16.3, we renamed this setting to **Limit access _from_ this project** for clarity.
@ -18,7 +18,7 @@
In GitLab 15.9, we introduced an alternative setting called
[**Authorized groups and projects**](https://docs.gitlab.com/ci/jobs/ci_job_token/#add-a-group-or-project-to-the-job-token-allowlist).
This setting controls job token access _to_ your project by using an allowlist.
This new setting is a large improvement over the original. The first iteration is deprecated
This new setting is a large improvement over the original. The first iteration was deprecated
in GitLab 16.0 and scheduled for removal in GitLab 18.0.
The **Limit access _from_ this project** setting is disabled by default for all new projects.

View File

@ -7,7 +7,7 @@
breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
window: 1
reporter: jocelynjane # (required) GitLab username of the person reporting the deprecation
stage: govern # (required) String value of the stage that the feature was created in. e.g., Growth
stage: Software Supply Chain Security # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383084 # (required) Link to the deprecation issue in GitLab
check_impact: Use the [Authentication Log](../ci/jobs/ci_job_token.md#job-token-authentication-log) to verify which projects are authenticating with your project.
impact: high # Can be one of: [critical, high, medium, low]

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class IncreaseDescriptionLimitOnComplianceRequirements < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.11'
def up
remove_text_limit :compliance_requirements, :description
add_text_limit :compliance_requirements, :description, 500
end
def down
remove_text_limit :compliance_requirements, :description
add_text_limit :compliance_requirements, :description, 255
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class RemoveSubscriptionSeatAssignmentOrganizationIdDefault < Gitlab::Database::Migration[2.2]
milestone '17.11'
def change
change_column_default(:subscription_seat_assignments, :organization_id, from: 1, to: nil)
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
class RemoveBrokenFkForPCiStagesAndPCiPipelinesAttempt3 < Gitlab::Database::Migration[2.2]
include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
include Gitlab::Database::MigrationHelpers::WraparoundAutovacuum
milestone '17.11'
disable_ddl_transaction!
SOURCE_TABLE_NAME = :p_ci_stages
TARGET_TABLE_NAME = :p_ci_pipelines
FK_NAME = :fk_fb57e6cc56_p
def up
return unless can_execute_on?(:ci_pipelines, :ci_stages)
with_lock_retries do
remove_foreign_key_if_exists(
SOURCE_TABLE_NAME,
TARGET_TABLE_NAME,
name: FK_NAME,
reverse_lock_order: true
)
end
end
def down
# no-op
# Since we recreate the fk in db/post_migrate/20250326204934_remove_broken_fk_for_p_ci_stages_and_p_ci_pipelines.rb
end
end

View File

@ -0,0 +1 @@
85210d36999484fafe57284bc5c68985d68f48061530ff79429c84875a6420f2

View File

@ -0,0 +1 @@
b062d670e1b5da5de8bca3c4082a3a6cd76f873e33098cf3873fcee6ba1b128c

View File

@ -0,0 +1 @@
6dc9a76b5e4b712f670b18cc23721aa673b6db01c2a1806de0a0defb2019e39e

View File

@ -0,0 +1 @@
e4400de67e39e1bd6934ff41c05de276c7bf26294f15fb9ac6ac62e293e47ffa

View File

@ -12651,7 +12651,7 @@ CREATE TABLE compliance_requirements (
description text NOT NULL,
control_expression text,
requirement_type smallint DEFAULT 0 NOT NULL,
CONSTRAINT check_71d7c59197 CHECK ((char_length(description) <= 255)),
CONSTRAINT check_71d7c59197 CHECK ((char_length(description) <= 500)),
CONSTRAINT check_be5c3cfc16 CHECK ((char_length(control_expression) <= 2048)),
CONSTRAINT check_f1fb6fdd81 CHECK ((char_length(name) <= 255))
);
@ -23215,7 +23215,7 @@ CREATE TABLE subscription_seat_assignments (
last_activity_on timestamp with time zone,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
organization_id bigint DEFAULT 1 NOT NULL,
organization_id bigint NOT NULL,
seat_type smallint
);

View File

@ -20,7 +20,7 @@ title: Broadcast Messages API
{{< /history >}}
Broadcast messages API operates on [broadcast messages](../administration/broadcast_messages.md).
Use this API to interact with banners and notifications displayed in the UI. For more information, see [broadcast messages](../administration/broadcast_messages.md).
GET requests do not require authentication. All other broadcast message API endpoints are accessible only to administrators. Non-GET requests by:

View File

@ -18,9 +18,11 @@ title: Database migrations API
{{< /history >}}
This API is for managing database migrations used in the development of GitLab.
Use this API to manage GitLab database migrations.
All methods require administrator authorization.
Prerequisites:
- You must have administrator access to the instance.
## Mark a migration as successful

View File

@ -12,6 +12,8 @@ title: Events API
{{< /details >}}
Use this API to view user and project events.
## Filter parameters
### Actions
@ -137,7 +139,7 @@ Example response:
]
```
### Get user contribution events
## Get user contribution events
Get the contribution events for the specified user, sorted from newest to oldest. Scope `read_user` or `api` is required.
Events associated with epics are not available using API.

View File

@ -2,7 +2,7 @@
stage: Growth
group: Acquisition
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Experiments API (GitLab team only)
title: Experiments API
---
{{< details >}}
@ -12,9 +12,11 @@ title: Experiments API (GitLab team only)
{{< /details >}}
This API is for listing A/B experiments [defined in GitLab](../development/experiment_guide/_index.md).
Use this API to interact with A/B experiments. For more information, see the [experiment guide](../development/experiment_guide/_index.md).
The user must be a [GitLab team member](https://gitlab.com/groups/gitlab-com/-/group_members) to access the API.
Prerequisites:
- You must be a [GitLab team member](https://gitlab.com/groups/gitlab-com/-/group_members).
## List all experiments

View File

@ -12,6 +12,8 @@ title: Model registry API
{{< /details >}}
Use this API to interact with the machine learning model registry. For more information, see [model registry](../user/project/ml/model_registry/_index.md).
## Download a machine learning model package
Returns the file.

View File

@ -15033,6 +15033,58 @@ paths:
tags:
- conan_packages
operationId: getApiV4ProjectsIdPackagesConanV2ConansPackageNamePackageVersionPackageUsernamePackageChannelLatest
"/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions":
get:
summary: Get the list of revisions
description: This feature was introduced in GitLab 17.11
produces:
- application/json
parameters:
- in: path
name: id
description: The ID or URL-encoded path of the project
type: string
required: true
- in: path
name: package_name
description: Package name
type: string
required: true
example: my-package
- in: path
name: package_version
description: Package version
type: string
required: true
example: '1.0'
- in: path
name: package_username
description: Package username
type: string
required: true
example: my-group+my-project
- in: path
name: package_channel
description: Package channel
type: string
required: true
example: stable
responses:
'200':
description: Get the list of revisions
schema:
"$ref": "#/definitions/API_Entities_Packages_Conan_RecipeRevision"
'400':
description: Bad Request
'401':
description: Unauthorized
'403':
description: Forbidden
'404':
description: Not Found
tags:
- conan_packages
operationId: getApiV4ProjectsIdPackagesConanV2ConansPackageNamePackageVersionPackageUsernamePackageChannelRevisions
? "/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/files"
: get:
summary: List recipe files

View File

@ -12,6 +12,12 @@ title: Conan v2 API
{{< /details >}}
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/519741) in GitLab 17.11 [with a flag](../../administration/feature_flags.md) named `conan_package_revisions_support`. Disabled by default.
{{< /history >}}
{{< alert type="note" >}}
For Conan v1 operations, see [Conan v1 API](conan_v1.md).
@ -20,6 +26,12 @@ For Conan v1 operations, see [Conan v1 API](conan_v1.md).
Use this API to interact with the Conan v2 package manager. For more information, see [Conan packages in the package registry](../../user/packages/conan_repository/_index.md).
{{< alert type="flag" >}}
The availability of this feature is controlled by a feature flag. For more information, see the history.
{{< /alert >}}
Generally, these endpoints are used by the [Conan 2 package manager client](https://docs.conan.io/2/index.html)
and are not meant for manual consumption.
@ -53,18 +65,6 @@ The project-level prefix is used to make requests in a single project's scope. T
## Get latest recipe revision
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/519741) in GitLab 17.11 [with a flag](../../administration/feature_flags.md) named `conan_package_revisions_support`. Disabled by default.
{{< /history >}}
{{< alert type="flag" >}}
The availability of this feature is controlled by a feature flag. For more information, see the history.
{{< /alert >}}
Get the revision hash and creation date of the latest package recipe.
```plaintext
@ -118,3 +118,40 @@ curl --request PUT \
--upload-file path/to/conaninfo.txt \
"https://gitlab.example.com/api/v4/projects/9/packages/conan/v2/conans/my-package/1.0/my-group+my-project/stable/revisions/75151329520e7685dcf5da49ded2fec0/packages/103f6067a947f366ef91fc1b7da351c588d1827f/revisions/3bdd2d8c8e76c876ebd1ac0469a4e72c/files/conaninfo.txt"
```
## List all recipe revisions
List all revisions for a package recipe.
```plaintext
GET <route_prefix>/v2/conans/:package_name/:package_version/:package_username/:package_channel/revisions
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `package_name` | string | yes | Name of a package. |
| `package_version` | string | yes | Version of a package. |
| `package_username` | string | yes | Conan username of a package. This attribute is the `+`-separated full path of your project. |
| `package_channel` | string | yes | Channel of a package. |
```shell
curl --header "Authorization: Bearer <authenticate_token>" "https://gitlab.example.com/api/v4/projects/9/packages/conan/v2/conans/my-package/1.0/my-group+my-project/stable/revisions"
```
Example response:
```json
{
"reference": "my-package/1.0@my-group+my-project/stable",
"revisions": [
{
"revision": "75151329520e7685dcf5da49ded2fec0",
"time": "2024-12-17T09:16:40.334+0000"
},
{
"revision": "df28fd816be3a119de5ce4d374436b25",
"time": "2024-12-17T09:15:30.123+0000"
}
]
}
```

View File

@ -12,7 +12,7 @@ title: Service Ping API
{{< /details >}}
The Service Ping API is associated with [Service Ping](../development/internal_analytics/service_ping/_index.md).
Use this API to interact with the GitLab Service Ping process. For more information, see the [Service Ping development guidelines](../development/internal_analytics/service_ping/_index.md).
## Export Service Ping data

View File

@ -190,6 +190,7 @@ The following endpoints are available for CI/CD job tokens.
| Packages: Read | `READ_PACKAGES` | `GET /projects/:id/packages/conan/v2/conans/:package_name/:package_version/:package_username/:package_channel/latest` | Get the latest revision |
| Packages: Read | `READ_PACKAGES` | `GET /projects/:id/packages/conan/v2/conans/:package_name/:package_version/:package_username/:package_channel/revisions/:recipe_revision/files/:file_name` | Download recipe files |
| Packages: Read | `READ_PACKAGES` | `GET /projects/:id/packages/conan/v2/conans/:package_name/:package_version/:package_username/:package_channel/revisions/:recipe_revision/files` | List recipe files |
| Packages: Read | `READ_PACKAGES` | `GET /projects/:id/packages/conan/v2/conans/:package_name/:package_version/:package_username/:package_channel/revisions` | Get the list of revisions |
| Packages: Read | `READ_PACKAGES` | `GET /projects/:id/packages/generic/:package_name/*package_version/(*path/):file_name` | Download package file |
| Packages: Read | `READ_PACKAGES` | `GET /projects/:id/packages/go/*module_name/@v/:module_version.info` | Version metadata |
| Packages: Read | `READ_PACKAGES` | `GET /projects/:id/packages/go/*module_name/@v/:module_version.mod` | Download module file |

View File

@ -24,8 +24,8 @@ This window takes place on April 21 - 23, 2025 from 09:00 UTC to 22:00 UTC.
| Deprecation | Impact | Stage | Scope | Check potential impact |
|-------------|--------|-------|-------|------------------------|
| [CI/CD job token - **Limit access from your project** setting removal](https://gitlab.com/gitlab-org/gitlab/-/issues/395708) | High | Verify | Project | |
| [CI/CD job token - **Authorized groups and projects** allowlist enforcement](https://gitlab.com/gitlab-org/gitlab/-/issues/383084) | High | Govern | Project | Use the [Authentication Log](../ci/jobs/ci_job_token.md#job-token-authentication-log) to verify which projects are authenticating with your project. |
| [CI/CD job token - **Limit access from your project** setting removal](https://gitlab.com/gitlab-org/gitlab/-/issues/383084) | High | Software supply chain security | Project | |
| [CI/CD job token - **Authorized groups and projects** allowlist enforcement](https://gitlab.com/gitlab-org/gitlab/-/issues/383084) | High | Software supply chain security | Project | Use the [Authentication Log](../ci/jobs/ci_job_token.md#job-token-authentication-log) to verify which projects are authenticating with your project. |
| [Deprecate License Scanning CI/CD artifact report type](https://gitlab.com/gitlab-org/gitlab/-/issues/439301) | Low | Secure | Project | |
| [OpenTofu CI/CD template](https://gitlab.com/components/opentofu/-/issues/43#note_1913822299) | Low | Deploy | Project | |
| [CodeClimate-based Code Quality scanning will be removed](https://gitlab.com/gitlab-org/gitlab/-/issues/471677) | High | Secure | | |

View File

@ -956,7 +956,7 @@ This migration tool will be removed in GitLab 18.3.
- Announced in GitLab <span class="milestone">15.9</span>
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/update/terminology/#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/395708).
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/383084).
</div>
@ -966,7 +966,7 @@ This setting was called **Limit CI_JOB_TOKEN access**. In GitLab 16.3, we rename
In GitLab 15.9, we introduced an alternative setting called
[**Authorized groups and projects**](https://docs.gitlab.com/ci/jobs/ci_job_token/#add-a-group-or-project-to-the-job-token-allowlist).
This setting controls job token access _to_ your project by using an allowlist.
This new setting is a large improvement over the original. The first iteration is deprecated
This new setting is a large improvement over the original. The first iteration was deprecated
in GitLab 16.0 and scheduled for removal in GitLab 18.0.
The **Limit access _from_ this project** setting is disabled by default for all new projects.

View File

@ -296,6 +296,19 @@ However, some proxy services, such as Cloudflare, [alter this header, causing a
If you see [SignatureDoesNotMatch errors](https://repost.aws/knowledge-center/s3-presigned-url-signature-mismatch)
ensure that your proxy server does not alter or remove signed HTTP headers.
### 17.5 to 17.8 upgrade
- When upgrading from GitLab 17.5 to 17.8, background migration fails with insert or update on the `group_type_ci_runner_machines` table.
In the UI, the migration path `BackfillCiRunnerMachinesPartitionedTable: ci_runner_machines` shows progress `0.00%` and status `failed`.
The corresponding error in the logs include a foreign key constraint error.
```shell
ERROR: insert or update on table "group_type_ci_runner_machines_" violates foreign key constraint "fk_rails_"
```
To resolve this error, initialize the migration from the UI.
## 17.7.0
- Git 2.47.0 and later is required by Gitaly. For self-compiled installations, you should use the [Git version provided by Gitaly](../../install/installation.md#git).

View File

@ -267,6 +267,7 @@ that explain what you want to build:
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/514124) in GitLab 17.9 [with a flag](../../../../administration/feature_flags.md) named `code_suggestions_include_context_imports`. Disabled by default.
- [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/514124) in GitLab 17.11.
{{< /history >}}

View File

@ -63,6 +63,25 @@ module API
end
end
namespace 'revisions' do
desc 'Get the list of revisions' do
detail 'This feature was introduced in GitLab 17.11'
success code: 200, model: ::API::Entities::Packages::Conan::RecipeRevision
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authorization, job_token_policies: :read_packages,
allow_public_access_for_enabled_project_features: :package_registry
get urgency: :low do
not_found!('Package') unless package
present package, with: ::API::Entities::Packages::Conan::RecipeRevisions
end
params do
requires :recipe_revision, type: String, regexp: Gitlab::Regex.conan_revision_regex_v2,
desc: 'Recipe revision', documentation: { example: 'df28fd816be3a119de5ce4d374436b25' }

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module API
module Entities
module Packages
module Conan
class RecipeRevisions < Grape::Entity
MAX_REVISIONS_COUNT = 1000
expose :conan_recipe, as: :reference, documentation: {
type: String,
desc: 'The Conan package reference',
example: 'packageTest/1.2.3@gitlab-org+conan/stable'
}
expose :conan_recipe_revisions, as: :revisions, using:
::API::Entities::Packages::Conan::RecipeRevision, documentation: {
type: Array,
desc: 'List of recipe revisions',
is_array: true
} do |package|
package.conan_recipe_revisions.order_by_id_desc.limit(MAX_REVISIONS_COUNT)
end
end
end
end
end
end

View File

@ -5,9 +5,12 @@ module Gitlab
module Pipeline
module Chain
class Metrics < Chain::Base
include Gitlab::InternalEventsTracking
def perform!
increment_pipeline_created_counter
create_snowplow_event_for_pipeline_name
track_inputs_usage
end
def break?
@ -35,6 +38,21 @@ module Gitlab
user: @pipeline.user,
namespace: @pipeline.project.namespace)
end
def track_inputs_usage
return unless command.inputs.present?
track_internal_event(
'create_pipeline_with_inputs',
project: @pipeline.project,
user: @pipeline.user,
additional_properties: {
label: @pipeline.source,
property: @pipeline.config_source,
value: command.inputs.size
}
)
end
end
end
end

View File

@ -51899,6 +51899,9 @@ msgid_plural "Runner|%d days selected"
msgstr[0] ""
msgstr[1] ""
msgid "Runner|Assigned Projects"
msgstr ""
msgid "Runner|Export runner usage"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -1,289 +1,289 @@
{
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 36.748689889000005,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 100.77959961300002,
"qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb": 96.87526989899999,
"qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb": 102.907303401,
"qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb": 107.81779989099999,
"qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb": 8.881418332,
"qa/specs/features/api/1_manage/import/import_github_repo_spec.rb": 132.936269333,
"qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb": 56.816598805999995,
"qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb": 61.162899051,
"qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb": 209.70132820999999,
"qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb": 96.173647769,
"qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb": 89.778076775,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 21.815154841,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 15.03905998,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 39.579143645,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 67.817038612,
"qa/specs/features/api/3_create/merge_request/push_options_spec.rb": 39.877210432,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 1.339750631,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 30.421427255,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 24.981516762,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 14.678412409,
"qa/specs/features/api/3_create/repository/files_spec.rb": 6.441263556,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 14.791804148,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 30.467549086,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 27.377157513,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 9.147974599,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 73.730185215,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 17.649168932,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 105.35572889100001,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 40.292875856,
"qa/specs/features/api/8_monitor/metrics_spec.rb": 4.589572055,
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 39.176505588,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 91.05536891599999,
"qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb": 98.52159591,
"qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb": 104.551483562,
"qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb": 100.324842921,
"qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb": 14.807430715,
"qa/specs/features/api/1_manage/import/import_github_repo_spec.rb": 130.784218543,
"qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb": 64.662730943,
"qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb": 54.421507083,
"qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb": 191.824245544,
"qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb": 100.22250081,
"qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb": 104.033227667,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 19.836200967,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 18.60979307,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 14.490345451,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 36.466250826,
"qa/specs/features/api/3_create/merge_request/push_options_spec.rb": 42.988665077,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 3.085092701,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 18.731098203,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 31.756471593,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 9.176217719,
"qa/specs/features/api/3_create/repository/files_spec.rb": 9.100291720000001,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 19.789176191,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 14.127716535,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 17.738230164,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 9.718849433,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 93.993469578,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 27.827293353,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 81.614643212,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 37.030485183,
"qa/specs/features/api/8_monitor/metrics_spec.rb": 4.303853109,
"qa/specs/features/api/9_data_stores/users_spec.rb": 0.690633541,
"qa/specs/features/api/9_tenant_scale/user_inherited_access_spec.rb": 148.778119399,
"qa/specs/features/api/9_tenant_scale/users_spec.rb": 7.7769207080000005,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 27.588405841,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 46.231992708,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 57.553888476,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 13.458955311,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 86.727141342,
"qa/specs/features/browser_ui/10_govern/login/log_into_gitlab_via_ldap_spec.rb": 4.970114166,
"qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb": 27.669525792,
"qa/specs/features/browser_ui/10_govern/login/login_via_instance_wide_saml_sso_spec.rb": 16.169293578,
"qa/specs/features/browser_ui/10_govern/login/oauth_login_with_github_spec.rb": 42.577628715,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 87.952950578,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 25.181579702,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 41.360734357,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 53.499264494,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 34.572808644,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 8.464928637,
"qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb": 12.230266086,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 94.820041243,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb": 70.163923479,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_issue_import_spec.rb": 58.96069371,
"qa/specs/features/browser_ui/1_manage/integrations/pipeline_status_emails_spec.rb": 68.29266376,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb": 52.941611866,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_user_contribution_reassignment_spec.rb": 144.893616525,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 27.34963433,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 36.015767611,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 37.963573242,
"qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb": 12.927938319,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 19.811932895,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 20.828166886,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 23.007679044,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 240.205702511,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 32.378738655,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 38.887409131,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 26.782780208,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 23.678412682,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 29.57041069,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 24.674775355,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 17.228493134,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 106.950786121,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 13.440053257,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 31.145231421,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 88.787501135,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 34.937814825000004,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 26.17470458,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 32.885310584,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 54.64427657900001,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 40.031222661,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 30.079835836,
"qa/specs/features/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 91.78022778600001,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 63.644932612,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 30.368519293,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 90.45895233499999,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 145.387593325,
"qa/specs/features/browser_ui/3_create/merge_request/merge_request_set_to_auto_merge_spec.rb": 85.207053341,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 141.369671603,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 37.068462598,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 79.385398953,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 54.326760602,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 74.449561397,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 65.822850265,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 33.774430334,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 44.553965054,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 22.782698586,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 68.20510106900001,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 35.565775844,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 97.024146938,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 36.136286218,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 23.553872932,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 31.752627947,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 73.068276571,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 71.790220715,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 52.284437836,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 38.343658607,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_file_size_spec.rb": 75.989601491,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 50.77147622,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 15.306413479,
"qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 23.180083188,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 21.218950143,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 20.068239239,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 44.18048070099999,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 42.654819145,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 35.333107803999994,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 48.41153438,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 55.41003247,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 39.550213226,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 17.272513407,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 8.439073346,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 29.506290837,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 25.578753549,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 41.861474035,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 26.689954233,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 67.633111071,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 14.42981476,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 38.984309245999995,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 70.768198214,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 23.048958336,
"qa/specs/features/browser_ui/3_create/web_ide/settings_sync_web_ide_spec.rb": 178.750633299,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 72.450729728,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 101.058665466,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_glab_spec.rb": 129.793125333,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb": 122.625565137,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 65.538729527,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 97.80482527,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 351.74379074399997,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 366.752637057,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 70.714951878,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 131.576637143,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 39.675315354,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 117.389873128,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 27.84489054,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 43.507904949,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 55.177185025,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 93.248063191,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 58.761583905,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 84.806505638,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 73.702687013,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 180.233371283,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 34.903017314,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_registration_token_spec.rb": 19.747268518,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_unregister_runner_spec.rb": 32.949971249,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_counts_spec.rb": 23.765970791,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_status_counts_spec.rb": 17.981969578,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 19.530808179,
"qa/specs/features/browser_ui/4_verify/runner/register_project_runner_spec.rb": 37.176360232,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 47.515444821,
"qa/specs/features/browser_ui/5_package/container_registry/self_managed/container_registry_spec.rb": 329.501627163,
"qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb": 166.640687758,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 73.44111263,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 90.026104761,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 70.888136062,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 325.030709762,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 494.877632239,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 299.726158061,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 355.894419948,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 294.872375748,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 378.511772524,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 87.854051053,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 25.377972394,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 248.82869324400002,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 6.531194724,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 62.926271454,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 62.389787705,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 59.156780717000004,
"qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb": 71.949245833,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 26.572676154,
"qa/specs/features/api/9_tenant_scale/user_inherited_access_spec.rb": 113.342388754,
"qa/specs/features/api/9_tenant_scale/users_spec.rb": 7.576689275,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 25.491246615,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 54.40607851,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 53.929611186,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 13.785073181,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 105.09600871,
"qa/specs/features/browser_ui/10_govern/login/log_into_gitlab_via_ldap_spec.rb": 5.853250944,
"qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb": 31.138112638,
"qa/specs/features/browser_ui/10_govern/login/login_via_instance_wide_saml_sso_spec.rb": 15.655818167,
"qa/specs/features/browser_ui/10_govern/login/oauth_login_with_github_spec.rb": 41.830935048,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 92.216633301,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 29.087858797,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 36.609030039,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 32.063652859,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 30.140470085,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 14.898678269,
"qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb": 11.457741844,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 80.956942756,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb": 61.587456981,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_issue_import_spec.rb": 49.783134806,
"qa/specs/features/browser_ui/1_manage/integrations/pipeline_status_emails_spec.rb": 75.398187787,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb": 62.551203658,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_user_contribution_reassignment_spec.rb": 179.079921323,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 24.699574178,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 31.446366729,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 23.345811836,
"qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb": 11.585723651,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 13.430481753,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 20.990459605,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 26.300396238,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 228.58185635399997,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 27.159370728,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 46.197502022,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 39.438472973,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 28.561117183,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 34.849981602,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 30.922639705,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 11.334030194,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 103.748631788,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 16.530162796,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 29.868662989,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 62.549955809,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 51.971791847,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 23.423750975,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 40.495311468,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 52.48287242,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 44.723394458,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 27.279122592,
"qa/specs/features/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 96.01619219,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 47.946711635,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 37.292467574,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 87.951634137,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 69.095170681,
"qa/specs/features/browser_ui/3_create/merge_request/merge_request_set_to_auto_merge_spec.rb": 89.353301884,
"qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 82.455191487,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 37.496633679,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 43.344028066,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 61.870465045,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 99.357846168,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 108.65662449999999,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 28.127477243,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 19.554299625,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 18.53950606,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 37.138855639,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 41.695306587000005,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 95.087173198,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 15.482273838,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 17.088130169,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 21.634184929,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 63.780286729,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 72.25449942,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 45.47578834,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 34.0460304,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_file_size_spec.rb": 75.586404426,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 51.755314742,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 15.57759222,
"qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 25.965786077,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 26.613981945,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 23.486877928,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 35.437819741,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 40.400395469,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 27.052214895,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 57.653632449,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 69.516559777,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 34.510858952,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 15.504011263,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 10.092901067,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 14.182606942,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 18.591736551,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 33.235969554,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 15.210947993000001,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 57.79741400499999,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 21.582307971,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 52.897086774,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 57.706077656000005,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 30.677331388,
"qa/specs/features/browser_ui/3_create/web_ide/settings_sync_web_ide_spec.rb": 162.41345672699998,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 143.641517368,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 80.40537081299999,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_glab_spec.rb": 163.169890989,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb": 121.73887833,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 46.964068505,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 55.897837024,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 310.10969266899997,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 319.92834888100003,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 86.907390959,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 121.411431674,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 36.921699422,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 105.475351609,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 30.960499654,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 69.279146518,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 56.21386151,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 110.636965525,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 64.510124169,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 139.018442123,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 56.130873145,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 62.838020438,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 20.270429665,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_registration_token_spec.rb": 23.662636469,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_unregister_runner_spec.rb": 30.993274153,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_counts_spec.rb": 26.663382279,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_status_counts_spec.rb": 20.681835993,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 16.421210592,
"qa/specs/features/browser_ui/4_verify/runner/register_project_runner_spec.rb": 68.496165287,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 44.026361299,
"qa/specs/features/browser_ui/5_package/container_registry/self_managed/container_registry_spec.rb": 346.051414333,
"qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb": 165.907375645,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 54.621088382,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 89.727124535,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 56.272736953,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 232.53489357,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 531.994169464,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 301.031649504,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 256.45871393100003,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 317.930835549,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 299.929926871,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 93.123572624,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 40.923164628,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 152.50910212,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 14.747934885,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 51.273995219,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 65.405826745,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 53.807391923,
"qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb": 58.381913755999996,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 18.612875487,
"qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 22.367142461,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 27.810751683,
"qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 15.037094806,
"qa/specs/features/browser_ui/9_tenant_scale/group/create_group_with_mattermost_team_spec.rb": 7.637430428,
"qa/specs/features/browser_ui/9_tenant_scale/group/group_member_access_request_spec.rb": 66.67361918099999,
"qa/specs/features/browser_ui/9_tenant_scale/group/transfer_project_spec.rb": 28.54705019,
"qa/specs/features/browser_ui/9_tenant_scale/project/add_project_member_spec.rb": 36.495004679,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_badge_spec.rb": 22.77783493,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_spec.rb": 62.51984216700001,
"qa/specs/features/browser_ui/9_tenant_scale/project/dashboard_images_spec.rb": 17.873880698,
"qa/specs/features/browser_ui/9_tenant_scale/project/invite_group_to_project_spec.rb": 55.330400639,
"qa/specs/features/browser_ui/9_tenant_scale/project/project_owner_permissions_spec.rb": 181.22851424699996,
"qa/specs/features/browser_ui/9_tenant_scale/project/view_project_activity_spec.rb": 28.142072277,
"qa/specs/features/browser_ui/9_tenant_scale/user/follow_user_activity_spec.rb": 49.140768645,
"qa/specs/features/browser_ui/9_tenant_scale/user/parent_group_access_termination_spec.rb": 31.675526669,
"qa/specs/features/browser_ui/9_tenant_scale/user/user_inherited_access_spec.rb": 37.448731564,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 40.151562624,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 52.779530648999994,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 57.994923916000005,
"qa/specs/features/ee/api/1_manage/import/import_github_repo_spec.rb": 26.798864946,
"qa/specs/features/ee/api/1_manage/integrations/group_webhook_events_spec.rb": 11.316165249,
"qa/specs/features/ee/api/1_manage/migration/gitlab_migration_group_spec.rb": 78.565359616,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 56.606363925000004,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 41.586085397000005,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/advanced_global_advanced_syntax_search_spec.rb": 121.07962714,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/elasticsearch_api_spec.rb": 39.074068445,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/commit_index/commit_index_spec.rb": 20.538624526,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/issues_index/issue_index_spec.rb": 69.628096222,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/main_index/blob_index_spec.rb": 98.944347351,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/merge_request_index/merge_request_index_spec.rb": 59.333038368,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/notes_index/note_index_spec.rb": 45.730371219,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/user_index/user_index_spec.rb": 55.23396789,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 120.410017676,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 69.981076146,
"qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 87.439770461,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 28.91454899,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 130.455638878,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 54.41057111,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 152.096139383,
"qa/specs/features/ee/browser_ui/10_govern/group/group_ldap_sync_spec.rb": 154.72142316800003,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 110.55021925499999,
"qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb": 199.766808513,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 104.092144427,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 183.637753485,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 69.200602261,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 151.241116954,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 117.80368880200001,
"qa/specs/features/ee/browser_ui/10_govern/security_policies_spec.rb": 85.433710471,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 355.557962348,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 20.289002323,
"qa/specs/features/ee/browser_ui/10_govern/vulnerabilities_jira_integration_spec.rb": 24.600869353,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 326.775724988,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb": 22.447537764,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 8.110863641,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 18.01465773,
"qa/specs/features/ee/browser_ui/13_secure/cvs_dependency_scanning_spec.rb": 69.873956932,
"qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 118.375459768,
"qa/specs/features/ee/browser_ui/13_secure/secret_push_protection_spec.rb": 101.183865816,
"qa/specs/features/ee/browser_ui/16_ai_powered/duo_chat/duo_chat_spec.rb": 14.192048308,
"qa/specs/features/ee/browser_ui/1_manage/integrations/jira_issues_list_spec.rb": 58.441882131999996,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 188.68364248,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 61.557919679,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 39.130500766,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 11.328566694,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 10.165153364,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 197.628648779,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 37.649970935,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 11.0974183,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 31.755721733,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 11.462498838,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 25.760340238,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 22.595121894000002,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 30.187060882,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 11.557951333,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 32.387186465,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 21.612366417,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 22.71344926,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 53.568658903,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 17.672093606,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 18.395458779,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 30.256064316999996,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 26.065709995,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 14.315851726,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 39.155969526999996,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 48.642485804,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 41.465057812,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 30.501364072,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 91.529703764,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 39.586881532,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 120.609385585,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 30.602658116,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 197.365108699,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 34.340679891,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 167.332642931,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 222.35023873900002,
"qa/specs/features/ee/browser_ui/3_create/repository/prevent_forking_outside_group_spec.rb": 38.080491187,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 81.403262162,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 34.385134201,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 37.732446262,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 297.550874342,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 206.89696229799998,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 173.42842018,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 62.8214752,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 135.254123274,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_for_merged_result_spec.rb": 42.272271643,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 56.186165218,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 21.83266261,
"qa/specs/features/ee/browser_ui/9_tenant_scale/elasticsearch/elasticsearch_reindexing_spec.rb": 89.766408195,
"qa/specs/features/ee/browser_ui/9_tenant_scale/group/share_group_with_group_spec.rb": 23.115335836
"qa/specs/features/browser_ui/9_tenant_scale/group/create_group_with_mattermost_team_spec.rb": 7.104243912,
"qa/specs/features/browser_ui/9_tenant_scale/group/group_member_access_request_spec.rb": 55.710686376999995,
"qa/specs/features/browser_ui/9_tenant_scale/group/transfer_project_spec.rb": 25.724117626,
"qa/specs/features/browser_ui/9_tenant_scale/project/add_project_member_spec.rb": 34.548432906,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_badge_spec.rb": 22.612643177,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_spec.rb": 59.292128477,
"qa/specs/features/browser_ui/9_tenant_scale/project/dashboard_images_spec.rb": 20.043397138,
"qa/specs/features/browser_ui/9_tenant_scale/project/invite_group_to_project_spec.rb": 44.533386718,
"qa/specs/features/browser_ui/9_tenant_scale/project/project_owner_permissions_spec.rb": 181.053261051,
"qa/specs/features/browser_ui/9_tenant_scale/project/view_project_activity_spec.rb": 29.277003431,
"qa/specs/features/browser_ui/9_tenant_scale/user/follow_user_activity_spec.rb": 29.6309072,
"qa/specs/features/browser_ui/9_tenant_scale/user/parent_group_access_termination_spec.rb": 24.421995476,
"qa/specs/features/browser_ui/9_tenant_scale/user/user_inherited_access_spec.rb": 41.211764101,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 46.413296588,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 44.23899489,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 73.638566544,
"qa/specs/features/ee/api/1_manage/import/import_github_repo_spec.rb": 29.562953975,
"qa/specs/features/ee/api/1_manage/integrations/group_webhook_events_spec.rb": 5.940498168,
"qa/specs/features/ee/api/1_manage/migration/gitlab_migration_group_spec.rb": 78.366718749,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 71.121441171,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 45.753250745,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/advanced_global_advanced_syntax_search_spec.rb": 112.58514259500001,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/elasticsearch_api_spec.rb": 36.918142567,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/commit_index/commit_index_spec.rb": 101.979260884,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/issues_index/issue_index_spec.rb": 61.369233457,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/main_index/blob_index_spec.rb": 20.746524222,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/merge_request_index/merge_request_index_spec.rb": 69.662616855,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/notes_index/note_index_spec.rb": 26.684938838,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/user_index/user_index_spec.rb": 53.445630676,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 118.780288993,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 71.145434648,
"qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 82.468044455,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 17.585545491,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 133.700659851,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 70.29304784300001,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 117.720710441,
"qa/specs/features/ee/browser_ui/10_govern/group/group_ldap_sync_spec.rb": 132.299217487,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 129.528995354,
"qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb": 240.083432332,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 94.99492927099999,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 165.601342272,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 68.88300071100001,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 165.567898701,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 127.783166739,
"qa/specs/features/ee/browser_ui/10_govern/security_policies_spec.rb": 81.713224871,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 433.892545852,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 19.419528764,
"qa/specs/features/ee/browser_ui/10_govern/vulnerabilities_jira_integration_spec.rb": 39.387180892,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 339.32426497299997,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb": 24.957714262,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 5.396372707,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 13.24383258,
"qa/specs/features/ee/browser_ui/13_secure/cvs_dependency_scanning_spec.rb": 47.132167116,
"qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 121.623449753,
"qa/specs/features/ee/browser_ui/13_secure/secret_push_protection_spec.rb": 99.22665656,
"qa/specs/features/ee/browser_ui/16_ai_powered/duo_chat/duo_chat_spec.rb": 9.987338635,
"qa/specs/features/ee/browser_ui/1_manage/integrations/jira_issues_list_spec.rb": 57.542533293,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 90.962175234,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 67.089455546,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 45.024412004,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 13.988895666,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 12.150748481,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 198.618111315,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 54.053503038,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 9.031209059,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 29.767775438,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 13.233084817,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 27.14804958,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 25.855073277,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 34.027625922,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 21.303439779,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 25.468812878,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 14.568871698,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 26.677815288,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 49.016745142,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 26.511320585,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 14.111546655,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 23.170020004999998,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 36.953367027,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 25.536112939,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 48.151708761,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 37.730140177,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 48.698282177,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 35.147782681,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 80.363806285,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 36.886591042,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 96.127670207,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 29.63738232,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 150.60685605,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 39.389905743,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 173.26336489300002,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 168.009649525,
"qa/specs/features/ee/browser_ui/3_create/repository/prevent_forking_outside_group_spec.rb": 53.316478516000004,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 89.532745654,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 31.234434872,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 40.536372702,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 368.356913276,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 227.02807481,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 173.56518959599998,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 63.285966995,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 98.510400815,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_for_merged_result_spec.rb": 45.99535396,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 55.500998288,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 13.888654807,
"qa/specs/features/ee/browser_ui/9_tenant_scale/elasticsearch/elasticsearch_reindexing_spec.rb": 123.75672789800001,
"qa/specs/features/ee/browser_ui/9_tenant_scale/group/share_group_with_group_spec.rb": 26.039019041
}

View File

@ -139,7 +139,6 @@ spec/frontend/members/components/table/members_pagination_spec.js
spec/frontend/members/components/table/members_table_spec.js
spec/frontend/ml/model_registry/apps/show_ml_model_spec.js
spec/frontend/ml/model_registry/components/model_edit_spec.js
spec/frontend/ml/model_registry/components/model_version_create_spec.js
spec/frontend/notes/components/discussion_notes_spec.js
spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
spec/frontend/packages_and_registries/dependency_proxy/app_spec.js

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Usage Quotas > Storage tab', :js, feature_category: :consumables_cost_management do
let_it_be_with_reload(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project) { create(:project, group: group) }
let_it_be_with_reload(:statistics) { create(:project_statistics, project: project, repository_size: 12.megabytes) }
before_all do
group.add_owner(user)
end
before do
sign_in(user)
end
context 'when directly accessed via a url' do
before do
visit project_usage_quotas_path(project, anchor: 'storage-quota-tab')
end
it 'displays the tab header' do
within_testid 'storage-tab-app' do
expect(page).to have_text('Usage breakdown')
end
end
it 'displays the total project storage size' do
within_testid 'total-usage' do
expect(page).to have_text('12.0 MiB')
end
end
end
end

View File

@ -22,6 +22,17 @@ RSpec.describe TodosFinder, feature_category: :notifications do
expect(described_class.new(nil, {}).execute).to be_empty
end
context 'when user is a bot' do
it_behaves_like 'internal event tracking' do
let(:event) { 'request_todos_by_bot_user' }
let(:category) { described_class.name }
let(:user) { create(:user, :bot) }
let(:additional_properties) { { label: 'user_type', property: user.user_type } }
let(:event_attribute_overrides) { { project: nil, namespace: nil } }
subject(:finder_service) { finder.new(user).execute }
end
end
context 'filtering' do
let!(:todo1) { create(:todo, user: user, project: project, target: issue) }
let!(:todo2) { create(:todo, user: user, group: group, target: merge_request) }

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::Glql::WorkItemsFinder, feature_category: :markdown do
let_it_be(:resource_parent) { create(:group) }
let(:current_user) { create(:user) }
let(:context) { instance_double(GraphQL::Query::Context) }
let(:params) { {} }
let(:dummy_request) do
instance_double(ActionDispatch::Request, params: {}, referer: 'http://localhost')
end
subject(:finder) { described_class.new(current_user, context, resource_parent, params) }
describe '#use_elasticsearch_finder?' do
before do
allow(context).to receive(:[]).with(:request).and_return(dummy_request)
end
it 'returns false by default' do
expect(finder.use_elasticsearch_finder?).to be_falsey
end
end
end

View File

@ -1,4 +1,4 @@
import { GlAvatar, GlBadge, GlLink } from '@gitlab/ui';
import { GlAvatar, GlAvatarLink, GlBadge } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerAssignedItem from '~/ci/runner/components/runner_assigned_item.vue';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
@ -37,11 +37,11 @@ describe('RunnerAssignedItem', () => {
entityName: mockName,
src: mockAvatarUrl,
shape: AVATAR_SHAPE_OPTION_RECT,
size: 48,
size: 32,
};
it('Shows an avatar as a link', () => {
const avatarLink = wrapper.findAllComponents(GlLink).at(0);
const avatarLink = wrapper.findAllComponents(GlAvatarLink).at(0);
expect(avatarLink.attributes('href')).toBe(mockHref);
expect(avatarLink.findComponent(GlAvatar).props()).toMatchObject(avatarProps);
@ -53,7 +53,7 @@ describe('RunnerAssignedItem', () => {
});
it('does not display avatar as a link', () => {
expect(wrapper.findComponent(GlLink).exists()).toBe(false);
expect(wrapper.findComponent(GlAvatarLink).exists()).toBe(false);
expect(wrapper.findComponent(GlAvatar).props()).toMatchObject(avatarProps);
});
});
@ -72,7 +72,7 @@ describe('RunnerAssignedItem', () => {
});
it('does not display item as a link', () => {
expect(wrapper.findComponent(GlLink).exists()).toBe(false);
expect(wrapper.findComponent(GlAvatarLink).exists()).toBe(false);
expect(wrapper.findByText(mockFullName).exists()).toBe(true);
});

View File

@ -2,6 +2,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerGroups from '~/ci/runner/components/runner_groups.vue';
import RunnerAssignedItem from '~/ci/runner/components/runner_assigned_item.vue';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { runnerData, runnerWithGroupData } from '../mock_data';
@ -12,7 +13,7 @@ const mockGroup = mockGroupRunner.groups.nodes[0];
describe('RunnerGroups', () => {
let wrapper;
const findHeading = () => wrapper.find('h3');
const findHeading = () => wrapper.findByTestId('crud-title');
const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem);
const createComponent = ({ runner = mockGroupRunner, mountFn = shallowMountExtended } = {}) => {
@ -20,6 +21,9 @@ describe('RunnerGroups', () => {
propsData: {
runner,
},
stubs: {
CrudComponent,
},
});
};
@ -40,6 +44,8 @@ describe('RunnerGroups', () => {
const item = findRunnerAssignedItems().at(0);
const { webUrl, name, fullName, avatarUrl } = mockGroup;
expect(wrapper.findByTestId('runner-groups').exists()).toBe(true);
expect(item.props()).toMatchObject({
href: webUrl,
name,
@ -56,8 +62,8 @@ describe('RunnerGroups', () => {
});
});
it('Shows a "None" label', () => {
expect(wrapper.findByText('None').exists()).toBe(true);
it('Hides component', () => {
expect(wrapper.findByTestId('runner-groups').exists()).toBe(false);
});
});
});

View File

@ -1,13 +1,11 @@
import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { GlSearchBoxByType } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { sprintf } from '~/locale';
import {
I18N_ASSIGNED_PROJECTS,
I18N_CLEAR_FILTER_PROJECTS,
I18N_FILTER_PROJECTS,
I18N_NO_PROJECTS_FOUND,
@ -16,6 +14,7 @@ import {
import RunnerProjects from '~/ci/runner/components/runner_projects.vue';
import RunnerAssignedItem from '~/ci/runner/components/runner_assigned_item.vue';
import RunnerPagination from '~/ci/runner/components/runner_pagination.vue';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { captureException } from '~/ci/runner/sentry_utils';
import runnerProjectsQuery from '~/ci/runner/graphql/show/runner_projects.query.graphql';
@ -35,8 +34,7 @@ describe('RunnerProjects', () => {
let wrapper;
let mockRunnerProjectsQuery;
const findHeading = () => wrapper.find('h3');
const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader);
const findCrud = () => wrapper.findComponent(CrudComponent);
const findGlSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType);
const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem);
const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);
@ -47,6 +45,9 @@ describe('RunnerProjects', () => {
propsData: {
runner: mockRunner,
},
stubs: {
CrudComponent,
},
});
};
@ -91,9 +92,8 @@ describe('RunnerProjects', () => {
});
it('Shows a heading', () => {
const expected = sprintf(I18N_ASSIGNED_PROJECTS, { projectCount: mockProjects.length });
expect(findHeading().text()).toBe(expected);
expect(findCrud().props('title')).toContain('Assigned Projects');
expect(findCrud().props('count')).toBe(2);
});
it('Shows projects', () => {
@ -193,12 +193,9 @@ describe('RunnerProjects', () => {
it('shows loading indicator and no other content', () => {
createComponent();
expect(findGlSkeletonLoading().exists()).toBe(true);
expect(wrapper.findByText(I18N_NO_PROJECTS_FOUND).exists()).toBe(false);
expect(findRunnerAssignedItems().length).toBe(0);
expect(findRunnerPagination().attributes('disabled')).toBeDefined();
expect(findGlSearchBoxByType().props('isLoading')).toBe(true);
});
});

View File

@ -65,6 +65,7 @@ describe('ModelVersionCreate', () => {
stubs: {
PageHeading,
UploadDropzone,
ImportArtifactZone,
},
});
};

View File

@ -182,7 +182,45 @@ RSpec.describe Resolvers::WorkItemsResolver, feature_category: :team_planning do
end
end
def resolve_items(args = {}, context = { current_user: current_user })
context 'when searching for work items in ES for GLQL request' do
let(:request_params) { { 'operationName' => 'GLQL' } }
let(:glql_ctx) do
{ request: instance_double(ActionDispatch::Request, params: request_params, referer: 'http://localhost') }
end
before do
allow(Gitlab::CurrentSettings).to receive(:elasticsearch_search?).and_return(true)
allow(project).to receive(:use_elasticsearch?).and_return(true)
end
context 'when feature flag is enabled' do
before do
stub_feature_flags(glql_es_integration: true)
end
it 'uses GLQL WorkItemsFinder' do
expect(::WorkItems::Glql::WorkItemsFinder).to receive(:new).and_call_original
batch_sync { resolve_items({ label_name: item1.labels }, glql_ctx).to_a }
end
end
context 'when feature flag is not enabled' do
before do
stub_feature_flags(glql_es_integration: false)
end
it 'falls back to old WorkItemsFinder' do
expect(::WorkItems::Glql::WorkItemsFinder).not_to receive(:new)
batch_sync { resolve_items({ label_name: item1.labels }, glql_ctx).to_a }
end
end
end
def resolve_items(args = {}, context = {})
context[:current_user] = current_user
resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal)
end
end

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ::API::Entities::Packages::Conan::RecipeRevision, feature_category: :package_registry do
let(:recipe_revision) { build(:conan_recipe_revision) }
let(:recipe_revision) { build_stubbed(:conan_recipe_revision) }
let(:entity) { described_class.new(recipe_revision) }
subject { entity.as_json }

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Entities::Packages::Conan::RecipeRevisions, feature_category: :package_registry do
let_it_be(:package) { create(:conan_package, without_package_files: true) }
let_it_be(:revision1) { package.conan_recipe_revisions.first }
let_it_be(:revision2) { create(:conan_recipe_revision, package: package) }
let(:entity) { described_class.new(package) }
describe '#as_json' do
subject(:json) { entity.as_json }
it 'exposes the reference and revisions', :aggregate_failures do
expect(json[:reference]).to eq(package.conan_recipe)
expect(json[:revisions].map(&:as_json)).to eq([
{ 'revision' => revision2.revision, 'time' => revision2.created_at.iso8601(3) },
{ 'revision' => revision1.revision, 'time' => revision1.created_at.iso8601(3) }
])
end
context 'when the limit is reached' do
before do
stub_const("#{described_class}::MAX_REVISIONS_COUNT", 1)
end
it 'limits the number of revisions to MAX_REVISIONS_COUNT' do
expect(json[:revisions].map(&:as_json)).to eq([
'revision' => revision2.revision, 'time' => revision2.created_at.iso8601(3)
])
end
end
end
end

View File

@ -60,4 +60,35 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Metrics, feature_category: :continuo
expect_no_snowplow_event
end
end
context 'with inputs' do
let(:inputs) do
{
deploy_strategy: 'manual',
job_stage: 'deploy',
test_script: 'echo "test"'
}
end
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
current_user: user,
origin_ref: 'master',
inputs: inputs
)
end
it 'tracks the usage of inputs' do
expect { run_chain }.to trigger_internal_events('create_pipeline_with_inputs').with(
project: pipeline.project,
user: pipeline.user,
additional_properties: {
label: 'push',
property: 'unknown_source',
value: 3
}
)
end
end
end

View File

@ -178,7 +178,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.not_to allow_value(nil).for(:protected_paths_for_get_request) }
it { is_expected.to allow_value([]).for(:protected_paths_for_get_request) }
it do
it 'validates wiki_page_max_content_bytes is an integer not less than 1KB' do
is_expected.to validate_numericality_of(:wiki_page_max_content_bytes)
.only_integer.is_greater_than_or_equal_to(1024)
end
@ -204,17 +204,17 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value('default' => '90').for(:repository_storages_weighted) }
it { is_expected.to allow_value('default' => nil).for(:repository_storages_weighted) }
it do
it 'rejects negative repository storage weights' do
is_expected.not_to allow_value('default' => -1).for(:repository_storages_weighted)
.with_message("value for 'default' must be between 0 and 100")
end
it do
it 'rejects repository storage weights over 100' do
is_expected.not_to allow_value('default' => 101).for(:repository_storages_weighted)
.with_message("value for 'default' must be between 0 and 100")
end
it do
it 'rejects repository storage weights with invalid keys' do
is_expected.not_to allow_value('default' => 100,
shouldntexist: 50).for(:repository_storages_weighted).with_message("can't include: shouldntexist")
end
@ -263,7 +263,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.not_to allow_value(apdex_slo: '10').for(:prometheus_alert_db_indicators_settings) }
it { is_expected.to allow_value(nil).for(:prometheus_alert_db_indicators_settings) }
it do
it 'accepts valid prometheus alert db indicators settings' do
is_expected.to allow_value(valid_prometheus_alert_db_indicators_settings)
.for(:prometheus_alert_db_indicators_settings)
end
@ -484,7 +484,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'for grafana_url validations' do
before do
subject.instance_variable_set(:@parsed_grafana_url, nil)
setting.instance_variable_set(:@parsed_grafana_url, nil)
end
it { is_expected.to allow_value(http).for(:grafana_url) }
@ -506,10 +506,10 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'with invalid grafana URL' do
it 'adds an error' do
subject.grafana_url = " #{http}"
expect(subject.save).to be false
setting.grafana_url = " #{http}"
expect(setting.save).to be false
expect(subject.errors[:grafana_url]).to eq(
expect(setting.errors[:grafana_url]).to eq(
[
'must be a valid relative or absolute URL. ' \
'Please check your Grafana URL setting in ' \
@ -520,10 +520,10 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'with blocked grafana URL' do
it 'adds an error' do
subject.grafana_url = javascript
expect(subject.save).to be false
setting.grafana_url = javascript
expect(setting.save).to be false
expect(subject.errors[:grafana_url]).to eq(
expect(setting.errors[:grafana_url]).to eq(
[
'is blocked: Only allowed schemes are http, https. Please check your ' \
'Grafana URL setting in ' \
@ -581,7 +581,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value("snowplow.gitlab.com").for(:snowplow_collector_hostname) }
it { is_expected.to allow_value("db-snowplow.gitlab.com").for(:snowplow_database_collector_hostname) }
it do
it 'rejects snowplow database collector hostnames that exceed maximum length' do
is_expected.not_to allow_value("#{'a' * 256}db-snowplow.gitlab.com")
.for(:snowplow_database_collector_hostname)
end
@ -734,14 +734,14 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
end
end
it do
it 'validates local_markdown_version is an integer between 0 and 65535' do
is_expected.to validate_numericality_of(:local_markdown_version)
.only_integer
.is_greater_than_or_equal_to(0)
.is_less_than(65536)
end
it do
it 'validates archive_builds_in_seconds is at least 1 day' do
is_expected.to validate_numericality_of(:archive_builds_in_seconds)
.only_integer
.is_greater_than_or_equal_to(1.day.seconds.to_i)
@ -750,14 +750,14 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
describe 'usage_ping_enabled setting' do
shared_examples 'usage ping enabled' do
it do
it 'properly reflects enabled status' do
expect(setting.usage_ping_enabled).to be(true)
expect(setting.usage_ping_enabled?).to be(true)
end
end
shared_examples 'usage ping disabled' do
it do
it 'properly reflects disabled status' do
expect(setting.usage_ping_enabled).to be(false)
expect(setting.usage_ping_enabled?).to be(false)
end
@ -985,72 +985,72 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
[:gitaly_timeout_medium, :gitaly_timeout_fast].each do |timeout_name|
it "validates that #{timeout_name} is lower than timeout_default" do
subject[:gitaly_timeout_default] = 50
subject[timeout_name] = 100
setting[:gitaly_timeout_default] = 50
setting[timeout_name] = 100
expect(subject).to be_invalid
expect(setting).to be_invalid
end
end
it 'accepts all timeouts equal' do
subject.gitaly_timeout_default = 0
subject.gitaly_timeout_medium = 0
subject.gitaly_timeout_fast = 0
setting.gitaly_timeout_default = 0
setting.gitaly_timeout_medium = 0
setting.gitaly_timeout_fast = 0
expect(subject).to be_valid
expect(setting).to be_valid
end
it 'accepts timeouts in descending order' do
subject.gitaly_timeout_default = 50
subject.gitaly_timeout_medium = 30
subject.gitaly_timeout_fast = 20
setting.gitaly_timeout_default = 50
setting.gitaly_timeout_medium = 30
setting.gitaly_timeout_fast = 20
expect(subject).to be_valid
expect(setting).to be_valid
end
it 'rejects timeouts in ascending order' do
subject.gitaly_timeout_default = 20
subject.gitaly_timeout_medium = 30
subject.gitaly_timeout_fast = 50
setting.gitaly_timeout_default = 20
setting.gitaly_timeout_medium = 30
setting.gitaly_timeout_fast = 50
expect(subject).to be_invalid
expect(setting).to be_invalid
end
it 'rejects medium timeout larger than default' do
subject.gitaly_timeout_default = 30
subject.gitaly_timeout_medium = 50
subject.gitaly_timeout_fast = 20
setting.gitaly_timeout_default = 30
setting.gitaly_timeout_medium = 50
setting.gitaly_timeout_fast = 20
expect(subject).to be_invalid
expect(setting).to be_invalid
end
it 'rejects medium timeout smaller than fast' do
subject.gitaly_timeout_default = 30
subject.gitaly_timeout_medium = 15
subject.gitaly_timeout_fast = 20
setting.gitaly_timeout_default = 30
setting.gitaly_timeout_medium = 15
setting.gitaly_timeout_fast = 20
expect(subject).to be_invalid
expect(setting).to be_invalid
end
it 'does not prevent from saving when gitaly timeouts were previously invalid' do
subject.update_column(:gitaly_timeout_default, Settings.gitlab.max_request_duration_seconds + 1)
setting.update_column(:gitaly_timeout_default, Settings.gitlab.max_request_duration_seconds + 1)
expect(subject.reload).to be_valid
expect(setting.reload).to be_valid
end
end
describe 'enforcing terms' do
it 'requires the terms to present when enforcing users to accept' do
subject.enforce_terms = true
setting.enforce_terms = true
expect(subject).to be_invalid
expect(setting).to be_invalid
end
it 'is valid when terms are created' do
create(:term)
subject.enforce_terms = true
setting.enforce_terms = true
expect(subject).to be_valid
expect(setting).to be_valid
end
end
@ -1102,7 +1102,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'with asset proxy settings' do
before do
subject.asset_proxy_enabled = true
setting.asset_proxy_enabled = true
end
describe '#asset_proxy_url' do
@ -1112,10 +1112,10 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.not_to allow_value(ftp).for(:asset_proxy_url) }
it 'is not required when asset proxy is disabled' do
subject.asset_proxy_enabled = false
subject.asset_proxy_url = ''
setting.asset_proxy_enabled = false
setting.asset_proxy_url = ''
expect(subject).to be_valid
expect(setting).to be_valid
end
end
@ -1124,17 +1124,17 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value('anything').for(:asset_proxy_secret_key) }
it 'is not required when asset proxy is disabled' do
subject.asset_proxy_enabled = false
subject.asset_proxy_secret_key = ''
setting.asset_proxy_enabled = false
setting.asset_proxy_secret_key = ''
expect(subject).to be_valid
expect(setting).to be_valid
end
it 'is encrypted' do
subject.asset_proxy_secret_key = 'shared secret'
setting.asset_proxy_secret_key = 'shared secret'
expect(subject.encrypted_asset_proxy_secret_key).to be_present
expect(subject.encrypted_asset_proxy_secret_key).not_to eq(subject.asset_proxy_secret_key)
expect(setting.encrypted_asset_proxy_secret_key).to be_present
expect(setting.encrypted_asset_proxy_secret_key).not_to eq(setting.asset_proxy_secret_key)
end
end
@ -1197,12 +1197,12 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value(OpenSSL::PKey::RSA.new(1024).to_pem).for(:ci_jwt_signing_key) }
it 'is encrypted' do
subject.ci_jwt_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
setting.ci_jwt_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
aggregate_failures do
expect(subject.encrypted_ci_jwt_signing_key).to be_present
expect(subject.encrypted_ci_jwt_signing_key_iv).to be_present
expect(subject.encrypted_ci_jwt_signing_key).not_to eq(subject.ci_jwt_signing_key)
expect(setting.encrypted_ci_jwt_signing_key).to be_present
expect(setting.encrypted_ci_jwt_signing_key_iv).to be_present
expect(setting.encrypted_ci_jwt_signing_key).not_to eq(setting.ci_jwt_signing_key)
end
end
end
@ -1214,12 +1214,12 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value(OpenSSL::PKey::RSA.new(1024).to_pem).for(:ci_job_token_signing_key) }
it 'is encrypted' do
subject.ci_job_token_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
setting.ci_job_token_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
aggregate_failures do
expect(subject.encrypted_ci_job_token_signing_key).to be_present
expect(subject.encrypted_ci_job_token_signing_key_iv).to be_present
expect(subject.encrypted_ci_job_token_signing_key).not_to eq(subject.ci_job_token_signing_key)
expect(setting.encrypted_ci_job_token_signing_key).to be_present
expect(setting.encrypted_ci_job_token_signing_key_iv).to be_present
expect(setting.encrypted_ci_job_token_signing_key).not_to eq(setting.ci_job_token_signing_key)
end
end
end
@ -1231,12 +1231,12 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value(OpenSSL::PKey::RSA.new(1024).to_pem).for(:customers_dot_jwt_signing_key) }
it 'is encrypted' do
subject.customers_dot_jwt_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
setting.customers_dot_jwt_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
aggregate_failures do
expect(subject.encrypted_customers_dot_jwt_signing_key).to be_present
expect(subject.encrypted_customers_dot_jwt_signing_key_iv).to be_present
expect(subject.encrypted_customers_dot_jwt_signing_key).not_to eq(subject.customers_dot_jwt_signing_key)
expect(setting.encrypted_customers_dot_jwt_signing_key).to be_present
expect(setting.encrypted_customers_dot_jwt_signing_key_iv).to be_present
expect(setting.encrypted_customers_dot_jwt_signing_key).not_to eq(setting.customers_dot_jwt_signing_key)
end
end
end
@ -1245,12 +1245,12 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value(nil).for(:cloud_license_auth_token) }
it 'is encrypted' do
subject.cloud_license_auth_token = 'token-from-customers-dot'
setting.cloud_license_auth_token = 'token-from-customers-dot'
aggregate_failures do
expect(subject.encrypted_cloud_license_auth_token).to be_present
expect(subject.encrypted_cloud_license_auth_token_iv).to be_present
expect(subject.encrypted_cloud_license_auth_token).not_to eq(subject.cloud_license_auth_token)
expect(setting.encrypted_cloud_license_auth_token).to be_present
expect(setting.encrypted_cloud_license_auth_token_iv).to be_present
expect(setting.encrypted_cloud_license_auth_token).not_to eq(setting.cloud_license_auth_token)
end
end
end
@ -1259,7 +1259,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'for static objects external storage' do
context 'when URL is set' do
before do
subject.static_objects_external_storage_url = http
setting.static_objects_external_storage_url = http
end
it { is_expected.not_to allow_value(nil).for(:static_objects_external_storage_auth_token) }
@ -1268,28 +1268,28 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'with sourcegraph settings' do
it 'is invalid if sourcegraph is enabled and no url is provided' do
allow(subject).to receive(:sourcegraph_enabled).and_return(true)
allow(setting).to receive(:sourcegraph_enabled).and_return(true)
expect(subject.sourcegraph_url).to be_nil
expect(setting.sourcegraph_url).to be_nil
is_expected.to be_invalid
end
end
context 'with gitpod settings' do
it 'is invalid if gitpod is enabled and no url is provided' do
allow(subject).to receive_messages(gitpod_enabled: true, gitpod_url: nil)
allow(setting).to receive_messages(gitpod_enabled: true, gitpod_url: nil)
is_expected.to be_invalid
end
it 'is invalid if gitpod is enabled and an empty url is provided' do
allow(subject).to receive_messages(gitpod_enabled: true, gitpod_url: '')
allow(setting).to receive_messages(gitpod_enabled: true, gitpod_url: '')
is_expected.to be_invalid
end
it 'is invalid if gitpod is enabled and an invalid url is provided' do
allow(subject).to receive_messages(gitpod_enabled: true, gitpod_url: 'javascript:alert("test")//')
allow(setting).to receive_messages(gitpod_enabled: true, gitpod_url: 'javascript:alert("test")//')
is_expected.to be_invalid
end
@ -1329,7 +1329,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'with prometheus settings' do
it 'validates metrics_method_call_threshold' do
allow(subject).to receive(:prometheus_metrics_enabled).and_return(true)
allow(setting).to receive(:prometheus_metrics_enabled).and_return(true)
is_expected.to validate_numericality_of(:metrics_method_call_threshold).is_greater_than_or_equal_to(0)
end
@ -1364,7 +1364,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'for default_syntax_highlighting_theme' do
it { is_expected.to allow_value(*Gitlab::ColorSchemes.valid_ids).for(:default_syntax_highlighting_theme) }
it do
it 'rejects invalid values for default syntax highlighting theme' do
is_expected.not_to allow_value(nil, 0,
Gitlab::ColorSchemes.available_schemes.size + 1).for(:default_syntax_highlighting_theme)
end
@ -1389,7 +1389,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'for default_project_visibility, default_group_visibility and restricted_visibility_levels validations' do
before do
subject.restricted_visibility_levels = [10]
setting.restricted_visibility_levels = [10]
end
it { is_expected.not_to allow_value(10).for(:default_group_visibility) }
@ -1398,19 +1398,19 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to allow_value(20).for(:default_project_visibility) }
it 'sets error messages when default visibility settings are not valid' do
subject.default_group_visibility = 10
subject.default_project_visibility = 10
setting.default_group_visibility = 10
setting.default_project_visibility = 10
expect(subject).not_to be_valid
expect(subject.errors.messages[:default_group_visibility].first)
expect(setting).not_to be_valid
expect(setting.errors.messages[:default_group_visibility].first)
.to eq("cannot be set to a restricted visibility level")
expect(subject.errors.messages[:default_project_visibility].first)
expect(setting.errors.messages[:default_project_visibility].first)
.to eq("cannot be set to a restricted visibility level")
end
end
describe 'sentry_clientside_traces_sample_rate' do
it do
it 'validates sentry_clientside_traces_sample_rate is between 0 and 1' do
is_expected.to validate_numericality_of(:sentry_clientside_traces_sample_rate)
.is_greater_than_or_equal_to(0)
.is_less_than_or_equal_to(1)
@ -1575,24 +1575,24 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
end
it 'removes unknown sources (as strings) from the array' do
subject.disabled_oauth_sign_in_sources = %w[github test]
setting.disabled_oauth_sign_in_sources = %w[github test]
expect(subject).to be_valid
expect(subject.disabled_oauth_sign_in_sources).to eq ['github']
expect(setting).to be_valid
expect(setting.disabled_oauth_sign_in_sources).to eq ['github']
end
it 'removes unknown sources (as symbols) from the array' do
subject.disabled_oauth_sign_in_sources = %i[github test]
setting.disabled_oauth_sign_in_sources = %i[github test]
expect(subject).to be_valid
expect(subject.disabled_oauth_sign_in_sources).to eq ['github']
expect(setting).to be_valid
expect(setting.disabled_oauth_sign_in_sources).to eq ['github']
end
it 'ignores nil' do
subject.disabled_oauth_sign_in_sources = nil
setting.disabled_oauth_sign_in_sources = nil
expect(subject).to be_valid
expect(subject.disabled_oauth_sign_in_sources).to be_empty
expect(setting).to be_valid
expect(setting.disabled_oauth_sign_in_sources).to be_empty
end
end
@ -1637,7 +1637,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'for validations' do
it { is_expected.to validate_presence_of(:diff_max_patch_bytes) }
it do
it 'validates diff_max_patch_bytes is an integer within defined bounds' do
is_expected.to validate_numericality_of(:diff_max_patch_bytes)
.only_integer
.is_greater_than_or_equal_to(Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES)
@ -1650,7 +1650,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'for validations' do
it { is_expected.to validate_presence_of(:diff_max_files) }
it do
it 'validates diff_max_files is an integer within allowed bounds' do
is_expected
.to validate_numericality_of(:diff_max_files)
.only_integer
@ -1664,7 +1664,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
context 'for validations' do
it { is_expected.to validate_presence_of(:diff_max_lines) }
it do
it 'validates diff_max_lines is an integer within allowed bounds' do
is_expected
.to validate_numericality_of(:diff_max_lines)
.only_integer
@ -1704,7 +1704,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
where(users_over_minimum: [-1, 0, 1])
with_them do
it do
it 'permits instance review when user count meets minimum requirement' do
expect(Rails.cache).to receive(:fetch).with('limited_users_count', anything)
.and_return(::ApplicationSetting::INSTANCE_REVIEW_MIN_USERS + users_over_minimum)
is_expected.to be(users_over_minimum >= 0)
@ -1715,51 +1715,51 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
describe 'email_restrictions' do
context 'when email restrictions are enabled' do
before do
subject.email_restrictions_enabled = true
setting.email_restrictions_enabled = true
end
it 'allows empty email restrictions' do
subject.email_restrictions = ''
setting.email_restrictions = ''
expect(subject).to be_valid
expect(setting).to be_valid
end
it 'accepts valid email restrictions regex' do
subject.email_restrictions = '\+'
setting.email_restrictions = '\+'
expect(subject).to be_valid
expect(setting).to be_valid
end
it 'does not accept invalid email restrictions regex' do
subject.email_restrictions = '+'
setting.email_restrictions = '+'
expect(subject).not_to be_valid
expect(setting).not_to be_valid
end
it 'sets an error when regex is not valid' do
subject.email_restrictions = '+'
setting.email_restrictions = '+'
expect(subject).not_to be_valid
expect(subject.errors.messages[:email_restrictions].first)
expect(setting).not_to be_valid
expect(setting.errors.messages[:email_restrictions].first)
.to eq(_('not valid RE2 syntax: no argument for repetition operator: +'))
end
end
context 'when email restrictions are disabled' do
before do
subject.email_restrictions_enabled = false
setting.email_restrictions_enabled = false
end
it 'allows empty email restrictions' do
subject.email_restrictions = ''
setting.email_restrictions = ''
expect(subject).to be_valid
expect(setting).to be_valid
end
it 'invalid regex is not valid' do
subject.email_restrictions = '+'
setting.email_restrictions = '+'
expect(subject).not_to be_valid
expect(setting).not_to be_valid
end
end
end
@ -1768,45 +1768,45 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
describe 'kroki_format_supported?' do
it 'returns true when Excalidraw is enabled' do
subject.kroki_formats_excalidraw = true
expect(subject.kroki_format_supported?('excalidraw')).to be(true)
setting.kroki_formats_excalidraw = true
expect(setting.kroki_format_supported?('excalidraw')).to be(true)
end
it 'returns true when BlockDiag is enabled' do
subject.kroki_formats_blockdiag = true
setting.kroki_formats_blockdiag = true
# format "blockdiag" aggregates multiple diagram types: actdiag, blockdiag, nwdiag...
expect(subject.kroki_format_supported?('actdiag')).to be(true)
expect(subject.kroki_format_supported?('blockdiag')).to be(true)
expect(setting.kroki_format_supported?('actdiag')).to be(true)
expect(setting.kroki_format_supported?('blockdiag')).to be(true)
end
it 'returns false when BlockDiag is disabled' do
subject.kroki_formats_blockdiag = false
setting.kroki_formats_blockdiag = false
# format "blockdiag" aggregates multiple diagram types: actdiag, blockdiag, nwdiag...
expect(subject.kroki_format_supported?('actdiag')).to be(false)
expect(subject.kroki_format_supported?('blockdiag')).to be(false)
expect(setting.kroki_format_supported?('actdiag')).to be(false)
expect(setting.kroki_format_supported?('blockdiag')).to be(false)
end
it 'returns false when the diagram type is optional and not enabled' do
expect(subject.kroki_format_supported?('bpmn')).to be(false)
expect(setting.kroki_format_supported?('bpmn')).to be(false)
end
it 'returns true when the diagram type is enabled by default' do
expect(subject.kroki_format_supported?('vegalite')).to be(true)
expect(subject.kroki_format_supported?('nomnoml')).to be(true)
expect(subject.kroki_format_supported?('unknown-diagram-type')).to be(false)
expect(setting.kroki_format_supported?('vegalite')).to be(true)
expect(setting.kroki_format_supported?('nomnoml')).to be(true)
expect(setting.kroki_format_supported?('unknown-diagram-type')).to be(false)
end
it 'returns false when the diagram type is unknown' do
expect(subject.kroki_format_supported?('unknown-diagram-type')).to be(false)
expect(setting.kroki_format_supported?('unknown-diagram-type')).to be(false)
end
end
describe 'kroki_formats' do
it 'returns the value for kroki_formats' do
subject.kroki_formats = { blockdiag: true, bpmn: false, excalidraw: true }
expect(subject.kroki_formats_blockdiag).to be(true)
expect(subject.kroki_formats_bpmn).to be(false)
expect(subject.kroki_formats_excalidraw).to be(true)
setting.kroki_formats = { blockdiag: true, bpmn: false, excalidraw: true }
expect(setting.kroki_formats_blockdiag).to be(true)
expect(setting.kroki_formats_bpmn).to be(false)
expect(setting.kroki_formats_excalidraw).to be(true)
end
end
@ -1814,11 +1814,11 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
let(:defaults) { { name: 'main', push_access_level: 30, merge_access_level: 30, unprotect_access_level: 40 } }
it 'returns the value for default_branch_protection_defaults' do
subject.default_branch_protection_defaults = defaults
expect(subject.default_branch_protection_defaults['name']).to eq('main')
expect(subject.default_branch_protection_defaults['push_access_level']).to eq(30)
expect(subject.default_branch_protection_defaults['merge_access_level']).to eq(30)
expect(subject.default_branch_protection_defaults['unprotect_access_level']).to eq(40)
setting.default_branch_protection_defaults = defaults
expect(setting.default_branch_protection_defaults['name']).to eq('main')
expect(setting.default_branch_protection_defaults['push_access_level']).to eq(30)
expect(setting.default_branch_protection_defaults['merge_access_level']).to eq(30)
expect(setting.default_branch_protection_defaults['unprotect_access_level']).to eq(40)
end
context 'when provided with content that does not match the JSON schema' do
@ -1891,12 +1891,12 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
end
describe '#static_objects_external_storage_auth_token=', :aggregate_failures do
subject { setting.static_objects_external_storage_auth_token = token }
subject(:set_auth_token) { setting.static_objects_external_storage_auth_token = token }
let(:token) { 'Test' }
it 'stores an encrypted version of the token' do
subject
set_auth_token
expect(setting[:static_objects_external_storage_auth_token]).to be_nil
expect(setting[:static_objects_external_storage_auth_token_encrypted]).to be_present
@ -1907,7 +1907,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
let(:token) { '' }
it 'removes an encrypted version of the token' do
subject
set_auth_token
expect(setting[:static_objects_external_storage_auth_token]).to be_nil
expect(setting[:static_objects_external_storage_auth_token_encrypted]).to be_nil
@ -1919,7 +1919,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
let(:plaintext_token) { Devise.friendly_token(20) }
it 'encrypts the plaintext token' do
subject
set_auth_token
described_class.update!(static_objects_external_storage_auth_token: plaintext_token)
@ -1933,25 +1933,25 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
describe '#database_grafana_api_key' do
it 'is encrypted' do
subject.database_grafana_api_key = 'somesecret'
setting.database_grafana_api_key = 'somesecret'
aggregate_failures do
expect(subject.encrypted_database_grafana_api_key).to be_present
expect(subject.encrypted_database_grafana_api_key_iv).to be_present
expect(subject.encrypted_database_grafana_api_key).not_to eq(subject.database_grafana_api_key)
expect(setting.encrypted_database_grafana_api_key).to be_present
expect(setting.encrypted_database_grafana_api_key_iv).to be_present
expect(setting.encrypted_database_grafana_api_key).not_to eq(setting.database_grafana_api_key)
end
end
end
context "when inactive project deletion" do
it "validates warning email after months is less than delete after months" do
subject[:inactive_projects_delete_after_months] = 3
subject[:inactive_projects_send_warning_email_after_months] = 6
setting[:inactive_projects_delete_after_months] = 3
setting[:inactive_projects_send_warning_email_after_months] = 6
expect(subject).to be_invalid
expect(setting).to be_invalid
end
it do
it 'validates inactive project warning email period is greater than zero' do
is_expected.to validate_numericality_of(:inactive_projects_send_warning_email_after_months).is_greater_than(0)
end

View File

@ -198,32 +198,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
it_behaves_like 'a triggerable processable', :ci_build
describe '.ref_protected' do
subject { described_class.ref_protected }
context 'when protected is true' do
let!(:job) { create(:ci_build, :protected, pipeline: pipeline) }
it { is_expected.to include(job) }
end
context 'when protected is false' do
let!(:job) { create(:ci_build, pipeline: pipeline) }
it { is_expected.not_to include(job) }
end
context 'when protected is nil' do
let!(:job) { create(:ci_build, pipeline: pipeline) }
before do
job.update_attribute(:protected, nil)
end
it { is_expected.not_to include(job) }
end
end
describe '.with_downloadable_artifacts' do
subject { described_class.with_downloadable_artifacts }

View File

@ -6,7 +6,7 @@ RSpec.describe CommitStatus, feature_category: :continuous_integration do
let_it_be(:project) { create(:project, :repository) }
let_it_be_with_reload(:pipeline) do
create(:ci_pipeline, project: project, sha: project.commit.id)
create(:ci_pipeline, project: project, sha: project.commit.id, user: create(:user))
end
let(:commit_status) { create_status }
@ -611,6 +611,44 @@ RSpec.describe CommitStatus, feature_category: :continuous_integration do
end
end
describe '.ref_protected' do
subject { described_class.ref_protected }
context 'when protected is true' do
let!(:job) { create_status(protected: true) }
it { is_expected.to include(job) }
end
context 'when protected is false' do
let!(:job) { create_status(protected: false) }
it { is_expected.not_to include(job) }
end
context 'when protected is nil' do
let!(:job) { create_status }
before do
job.update_attribute(:protected, nil)
end
it { is_expected.not_to include(job) }
end
end
describe '.for_user' do
subject { described_class.for_user(pipeline.user).order(:id) }
let(:statuses) do
[create_status(user: create(:user)), create_status(user: pipeline.user)]
end
it 'returns statuses for the specified user' do
is_expected.to eq(statuses.values_at(1))
end
end
describe '#before_sha' do
subject { commit_status.before_sha }

View File

@ -247,4 +247,42 @@ RSpec.describe API::Conan::V2::ProjectPackages, feature_category: :package_regis
it_behaves_like 'package not found'
it_behaves_like 'project not found by project id'
end
describe 'GET /api/v4/projects/:id/packages/conan/v2/conans/:package_name/:package_version/:package_username' \
'/:package_channel/revisions' do
include_context 'for conan recipe endpoints'
let(:recipe_path) { package.conan_recipe_path }
let(:url_suffix) { "#{recipe_path}/revisions" }
let_it_be(:revision1) { package.conan_recipe_revisions.first }
let_it_be(:revision2) { create(:conan_recipe_revision, package: package) }
subject(:request) { get api(url), headers: headers }
it 'returns the reference and a list of revisions in descending order' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['reference']).to eq(package.conan_recipe)
expect(json_response['revisions']).to eq([
{
'revision' => revision2.revision,
'time' => revision2.created_at.iso8601(3)
},
{
'revision' => revision1.revision,
'time' => revision1.created_at.iso8601(3)
}
])
end
it_behaves_like 'conan package revisions feature flag check'
it_behaves_like 'packages feature check'
it_behaves_like 'enforcing read_packages job token policy'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
it_behaves_like 'conan FIPS mode'
it_behaves_like 'package not found'
it_behaves_like 'project not found by project id'
end
end

View File

@ -12,6 +12,7 @@ RSpec.describe Ci::CreateCommitStatusService, :clean_gitlab_redis_cache, feature
let_it_be(:guest) { create_user(:guest) }
let_it_be(:reporter) { create_user(:reporter) }
let_it_be(:developer) { create_user(:developer) }
let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
let(:user) { developer }
let(:sha) { commit.id }
@ -68,6 +69,46 @@ RSpec.describe Ci::CreateCommitStatusService, :clean_gitlab_redis_cache, feature
expect(response.message).to eq('403 Forbidden')
end
end
context 'when the pipeline size is exceeded' do
before do
plan_limits.update!(ci_pipeline_size: 1)
create(:ci_build, pipeline: pipeline)
end
it 'creates commit status on a new pipeline' do
expect(response).to be_success
expect(job.sha).to eq(commit.id)
expect(job.status).to eq(status)
expect(job.name).to eq('default')
expect(job.ref).not_to be_empty
expect(job.pipeline_id).to be_present
expect(job.pipeline_id).not_to eq(pipeline.id)
end
context 'when the flag is disabled' do
before do
stub_feature_flags(ci_limit_commit_statuses: false)
end
it 'creates commit status on the pipeline and logs a message' do
expect(Gitlab::AppJsonLogger).to receive(:info).with(
a_hash_including(
class: described_class.name,
project_id: project.id,
subscription_plan: project.actual_plan_name
)
)
expect(response).to be_success
expect(job.sha).to eq(commit.id)
expect(job.status).to eq(status)
expect(job.name).to eq('default')
expect(job.ref).not_to be_empty
expect(job.pipeline_id).to eq(pipeline.id)
end
end
end
end
end
end
@ -90,6 +131,23 @@ RSpec.describe Ci::CreateCommitStatusService, :clean_gitlab_redis_cache, feature
expect(response).to be_success
expect(job.status).to eq(status)
end
context 'when the pipeline size is exceeded' do
before do
plan_limits.update!(ci_pipeline_size: 1)
create(:ci_build, pipeline: ::Ci::Pipeline.last)
end
it "changes existing job to #{status}" do
expect { response }
.to not_change { ::Ci::Pipeline.count }.from(1)
.and not_change { ::Ci::Stage.count }.from(2)
.and not_change { ::CommitStatus.count }.from(2)
expect(response).to be_success
expect(job.status).to eq(status)
end
end
end
end
@ -368,6 +426,24 @@ RSpec.describe Ci::CreateCommitStatusService, :clean_gitlab_redis_cache, feature
expect(response.message).to eq("404 Pipeline for pipeline_id, sha and ref Not Found")
end
end
context 'when the specified pipeline has too many jobs' do
before do
plan_limits.update!(ci_pipeline_size: 1)
create(:ci_build, pipeline: other_pipeline)
end
it 'returns a service error' do
expect { response }
.to not_change { ::Ci::Pipeline.count }.from(2)
.and not_change { ::Ci::Stage.count }.from(1)
.and not_change { ::CommitStatus.count }.from(1)
expect(response).to be_error
expect(response.http_status).to eq(:unprocessable_entity)
expect(response.message).to eq("The number of jobs has exceeded the limit")
end
end
end
end

View File

@ -196,13 +196,11 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute', feature_category
let(:trigger) do
{
trigger: {
include: {
project: downstream_project.full_path,
file: '.gitlab-ci.yml',
inputs: {
stage: 'deploy',
suffix: 'build'
}
project: downstream_project.full_path,
file: '.gitlab-ci.yml',
inputs: {
stage: 'deploy',
suffix: 'build'
}
}
}
@ -214,6 +212,15 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute', feature_category
end
it_behaves_like 'creates a downstream pipeline with the inputs provided'
it 'tracks the usage of inputs' do
expect { subject }.to trigger_internal_events('create_pipeline_with_inputs').with(
category: 'Gitlab::Ci::Pipeline::Chain::Metrics',
additional_properties: { value: 2, label: 'pipeline', property: 'repository_source' },
project: downstream_project,
user: user
)
end
end
context 'when the downstream pipeline is for the same project' do
@ -238,6 +245,12 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute', feature_category
end
it_behaves_like 'creates a downstream pipeline with the inputs provided'
it 'does not track the usage of inputs because the inputs are in the internal include' do
expect { subject }.not_to trigger_internal_events('create_pipeline_with_inputs')
subject
end
end
end

View File

@ -117,6 +117,15 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
expect(my_job_deploy.options[:script]).to eq(['echo "Deploying to staging using blue-green strategy"'])
end
it 'tracks the usage of inputs' do
expect { execute }.to trigger_internal_events('create_pipeline_with_inputs').with(
category: 'Gitlab::Ci::Pipeline::Chain::Metrics',
additional_properties: { value: 5, label: 'push', property: config_source },
project: project,
user: user
)
end
context 'when the FF ci_inputs_for_pipelines is disabled' do
before do
stub_feature_flags(ci_inputs_for_pipelines: false)
@ -136,6 +145,8 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
end
context 'when the project CI config is in the current repository file' do
let(:config_source) { 'repository_source' }
let(:project_files) do
{ '.gitlab-ci.yml' => complex_inputs_example_yaml }
end
@ -150,6 +161,8 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
end
context 'when the project CI config is in another project repository file' do
let(:config_source) { 'external_project_source' }
let_it_be(:project_2) do
create(:project, :custom_repo, files: { 'a_config_file.yml' => complex_inputs_example_yaml })
end
@ -164,6 +177,8 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
end
context 'when the project CI config is a remote file' do
let(:config_source) { 'remote_source' }
let(:project_ci_config_path) { 'https://gitlab.example.com/something/.gitlab-ci.yml' }
before do
@ -175,6 +190,8 @@ RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_compositio
end
context 'when the CI config is passed as content' do
let(:config_source) { 'parameter_source' }
let(:content) { complex_inputs_example_yaml }
it_behaves_like 'testing invalid and valid cases'

View File

@ -3,14 +3,14 @@
require 'spec_helper'
RSpec.describe 'layouts/_page', feature_category: :geo_replication do
let_it_be(:user) { build_stubbed(:user) }
let(:user) { build_stubbed(:user) }
before do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
end
describe '_silent_mode_banner' do
before do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
end
describe 'when ::Gitlab::SilentMode.enabled? is true' do
before do
allow(::Gitlab::SilentMode).to receive(:enabled?).and_return(true)
@ -35,4 +35,26 @@ RSpec.describe 'layouts/_page', feature_category: :geo_replication do
end
end
end
describe '_broadcast' do
context 'when broadcast messages are hidden' do
before do
view.content_for(:hide_broadcast_messages, true)
end
it 'does not render broadcast messages' do
render
expect(rendered).not_to render_template('layouts/_broadcast')
end
end
context 'when `:hide_broadcast_messages` is not present' do
it 'renders broadcast messages' do
render
expect(rendered).to render_template('layouts/_broadcast')
end
end
end
end

View File

@ -57,6 +57,10 @@ RSpec.describe RunPipelineScheduleWorker, feature_category: :pipeline_compositio
let(:service_response) { instance_double(ServiceResponse, payload: pipeline, error?: false) }
let(:pipeline) { instance_double(Ci::Pipeline, persisted?: true) }
before_all do
project.add_maintainer(user)
end
context 'when pipeline can be created' do
before do
expect(Ci::CreatePipelineService).to receive(:new)
@ -134,6 +138,27 @@ RSpec.describe RunPipelineScheduleWorker, feature_category: :pipeline_compositio
end
before_all do
project.repository.create_file(
user,
'.gitlab-ci.yml',
<<~YAML,
spec:
inputs:
input1:
default: v1
input2:
default: v2
---
build:
stage: build
script: echo "build"
YAML
message: 'test',
branch_name: 'master'
)
create(:ci_pipeline_schedule_input, pipeline_schedule: pipeline_schedule, name: 'input1', value: 'value1')
create(:ci_pipeline_schedule_input, pipeline_schedule: pipeline_schedule, name: 'input2', value: 'value2')
end
@ -147,6 +172,17 @@ RSpec.describe RunPipelineScheduleWorker, feature_category: :pipeline_compositio
expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response)
end
it 'tracks the usage of inputs' do
expect do
worker.perform(pipeline_schedule.id, user.id)
end.to trigger_internal_events('create_pipeline_with_inputs').with(
category: 'Gitlab::Ci::Pipeline::Chain::Metrics',
additional_properties: { value: 2, label: 'schedule', property: 'repository_source' },
project: project,
user: user
)
end
end
end