Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-09-30 12:16:19 +00:00
parent 76ed6cf575
commit ba00dde24c
111 changed files with 1347 additions and 278 deletions

View File

@ -231,7 +231,7 @@ e2e:test-on-gdk:
SKIP_MESSAGE: Skipping test-on-gdk due to mr containing only quarantine changes!
GDK_IMAGE: "${CI_REGISTRY_IMAGE}/gitlab-qa-gdk:${CI_COMMIT_SHA}"
e2e:test-product-analytics:
.e2e:test-product-analytics:
extends:
- .qa:rules:e2e:test-on-gdk
stage: qa

View File

@ -513,6 +513,21 @@ ai-gateway-no-license:
- if: $QA_SUITES =~ /Test::Integration::AiGatewayNoLicense/
- !reference [.rules:test:manual, rules]
# ========== continuous-vulnerability-scanning ===========
continuous-vulnerabiity-scanning:
extends:
- .qa
- .failure-videos
variables:
QA_SCENARIO: Test::Integration::ContinuousVulnerabilityScanning
QA_MOCK_GITHUB: "true"
rules:
- !reference [.rules:test:ee-only, rules]
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::ContinuousVulnerabilityScanning/
- !reference [.rules:test:manual, rules]
# ------------------------------------------
# Update jobs
# ------------------------------------------

View File

@ -20,24 +20,12 @@ Layout/LineEndStringConcatenationIndentation:
- 'app/graphql/mutations/issues/update.rb'
- 'app/graphql/mutations/members/projects/bulk_update.rb'
- 'app/graphql/mutations/merge_requests/update.rb'
- 'app/graphql/mutations/packages/protection/rule/create.rb'
- 'app/graphql/mutations/packages/protection/rule/delete.rb'
- 'app/graphql/mutations/packages/protection/rule/update.rb'
- 'app/graphql/mutations/todos/mark_all_done.rb'
- 'app/graphql/resolvers/analytics/cycle_analytics/value_streams/stage_metrics_resolver.rb'
- 'app/graphql/resolvers/ci/all_jobs_resolver.rb'
- 'app/graphql/resolvers/ci/config_resolver.rb'
- 'app/graphql/resolvers/ci/runners_resolver.rb'
- 'app/graphql/resolvers/ci/template_resolver.rb'
- 'app/graphql/resolvers/concerns/project_search_arguments.rb'
- 'app/graphql/resolvers/design_management/designs_resolver.rb'
- 'app/graphql/resolvers/groups_resolver.rb'
- 'app/graphql/resolvers/issues/base_resolver.rb'
- 'app/graphql/resolvers/work_items/linked_items_resolver.rb'
- 'app/graphql/types/analytics/cycle_analytics/value_streams/stage_metrics_type.rb'
- 'app/graphql/types/audit_events/definition_type.rb'
- 'app/graphql/types/ci/catalog/resource_type.rb'
- 'app/graphql/types/ci/config/include_type.rb'
- 'app/graphql/types/ci/detailed_status_type.rb'
- 'app/graphql/types/ci/job_token_scope/direction_enum.rb'
- 'app/graphql/types/ci/job_token_scope_type.rb'

View File

@ -4,9 +4,12 @@ import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import { __ } from '~/locale';
import ListSelector from '~/vue_shared/components/list_selector/index.vue';
import { GROUPS_TYPE, PROJECTS_TYPE } from '~/vue_shared/components/list_selector/constants';
export default {
DRAWER_Z_INDEX,
GROUPS_TYPE,
PROJECTS_TYPE,
components: {
GlDrawer,
GlButton,
@ -69,7 +72,7 @@ export default {
<template #default>
<list-selector
type="groups"
:type="$options.GROUPS_TYPE"
class="gl-m-5 !gl-p-0"
autofocus
disable-namespace-dropdown
@ -79,7 +82,7 @@ export default {
/>
<list-selector
type="projects"
:type="$options.PROJECTS_TYPE"
class="gl-m-5 !gl-p-0"
:selected-items="projectExclusions"
@select="handleSelectExclusion"

View File

@ -53,7 +53,7 @@
}
&.content-component-block {
background-color: $body-bg;
@apply gl-bg-default;
}
.title {

View File

@ -46,7 +46,7 @@ $diff-file-header: 41px;
left: -11px;
width: 10px;
height: calc(100% + 1px);
background: $body-bg;
@apply gl-bg-default;
pointer-events: none;
}

View File

@ -4,7 +4,7 @@
.top-bar-fixed {
@apply gl-shadow-inner-b-1-gray-100;
background-color: $body-bg;
@apply gl-bg-default;
position: fixed;
left: var(--application-bar-left);
right: var(--application-bar-right);

View File

@ -767,13 +767,6 @@ pre {
}
}
code {
&.key-fingerprint {
background: $body-bg;
color: $text-color;
}
}
.monospace {
@include gl-font-monospace;
}
@ -895,10 +888,10 @@ wbr {
margin-block-end: 0;
}
}
// prevent backref character from rendering as emoji
.footnote-backref gl-emoji {
font-family: inherit;
font-family: inherit;
font-size: 0.875em;
pointer-events: none;
}

View File

@ -9,7 +9,7 @@
* custom merge request message
*/
.commit-message-container {
background-color: $body-bg;
@apply gl-bg-default;
position: relative;
font-family: $monospace-font;
$left: 12px;

View File

@ -72,7 +72,7 @@ $sticky-header-z-index: 98;
.settings-sticky-footer {
position: sticky;
z-index: $sticky-header-z-index;
background: $body-bg;
@apply gl-bg-default;
}
.settings-sticky-header {
@ -84,7 +84,7 @@ $sticky-header-z-index: 98;
height: $gl-padding-8;
position: sticky;
top: calc(#{$calc-application-header-height} + 36px);
box-shadow: 0 1px 0 $gray-100;
box-shadow: 0 1px 0 var(--gl-border-color-default);
}
}
@ -92,13 +92,13 @@ $sticky-header-z-index: 98;
position: sticky;
padding: $gl-padding-12 $gl-padding $gl-padding-8;
margin: #{-$gl-padding} #{-$gl-padding} 0;
background: $body-bg;
@apply gl-bg-default;
}
.settings-sticky-footer {
bottom: 0;
padding: $gl-padding-8 0;
box-shadow: 0 -1px 0 $gray-100;
box-shadow: 0 -1px 0 var(--gl-border-color-default);
}
// Header shouldn't be sticky if only one section on page

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Groups
class RedirectController < ::ApplicationController
skip_before_action :authenticate_user!
feature_category :groups_and_projects
def redirect_from_id
group = Group.find(group_params[:id])
if can?(current_user, :read_group, group)
redirect_to group
else
render_404
end
end
private
def group_params
params.permit(:id)
end
end
end

View File

@ -12,8 +12,9 @@ module BatchLoaders
def self.load_votes_for(object, vote_type, awardable_class: nil)
awardable_class ||= object.class.name
batch_key = "#{object.class.base_class.name}-#{vote_type}"
BatchLoader::GraphQL.for(object.id).batch(key: "#{object.issuing_parent_id}-#{vote_type}") do |ids, loader, _args|
BatchLoader::GraphQL.for(object.id).batch(key: batch_key) do |ids, loader, _args|
counts = AwardEmoji.votes_for_collection(ids, awardable_class).named(vote_type).index_by(&:awardable_id)
ids.each do |id|

View File

@ -7,7 +7,7 @@ module Mutations
class Create < ::Mutations::BaseMutation
graphql_name 'CreatePackagesProtectionRule'
description 'Creates a protection rule to restrict access to project packages. ' \
'Available only when feature flag `packages_protected_packages` is enabled.'
'Available only when feature flag `packages_protected_packages` is enabled.'
include FindsProject

View File

@ -7,7 +7,7 @@ module Mutations
class Delete < ::Mutations::BaseMutation
graphql_name 'DeletePackagesProtectionRule'
description 'Deletes a protection rule for packages. ' \
'Available only when feature flag `packages_protected_packages` is enabled.'
'Available only when feature flag `packages_protected_packages` is enabled.'
authorize :admin_package

View File

@ -7,8 +7,8 @@ module Mutations
class Update < ::Mutations::BaseMutation
graphql_name 'UpdatePackagesProtectionRule'
description 'Updates a package protection rule to restrict access to project packages. ' \
'You can prevent users without certain permissions from altering packages. ' \
'Available only when feature flag `packages_protected_packages` is enabled.'
'You can prevent users without certain permissions from altering packages. ' \
'Available only when feature flag `packages_protected_packages` is enabled.'
authorize :admin_package
@ -24,7 +24,7 @@ module Mutations
alpha: { milestone: '16.6' },
description:
'Package name protected by the protection rule. For example, `@my-scope/my-package-*`. ' \
'Wildcard character `*` allowed.'
'Wildcard character `*` allowed.'
argument :package_type,
Types::Packages::Protection::RulePackageTypeEnum,

View File

@ -13,7 +13,7 @@ module Mutations
TodoableID,
required: false,
description: "Global ID of the to-do item's parent. Issues, merge requests, designs, and epics are supported. " \
"If argument is omitted, all pending to-do items of the current user are marked as done."
"If argument is omitted, all pending to-do items of the current user are marked as done."
field :todos, [::Types::TodoType],
null: false,

View File

@ -10,7 +10,7 @@ module Resolvers
argument :timeframe, Types::TimeframeInputType,
required: true,
description: 'Aggregation timeframe. Filters the issue or the merge request creation time for FOSS ' \
'projects, and the end event timestamp for licensed projects or groups.'
'projects, and the end event timestamp for licensed projects or groups.'
argument :assignee_usernames, [GraphQL::Types::String],
required: false,

View File

@ -15,7 +15,7 @@ module Resolvers
required: false,
alpha: { milestone: '16.4' },
description: 'Filter jobs by runner type if ' \
'feature flag `:admin_jobs_filter_runner_type` is enabled.'
'feature flag `:admin_jobs_filter_runner_type` is enabled.'
def resolve_with_lookahead(**args)
jobs = ::Ci::JobsFinder.new(current_user: current_user, params: params_data(args)).execute

View File

@ -32,7 +32,7 @@ module Resolvers
GraphQL::Types::Boolean,
required: false,
description: 'Filter for confidential issues. If "false", excludes confidential issues. ' \
'If "true", returns only confidential issues.'
'If "true", returns only confidential issues.'
argument :created_after, Types::TimeType,
required: false,
description: 'Issues created after the date.'
@ -69,7 +69,7 @@ module Resolvers
argument :my_reaction_emoji, GraphQL::Types::String,
required: false,
description: 'Filter by reaction emoji applied by the current user. ' \
'Wildcard values "NONE" and "ANY" are supported.'
'Wildcard values "NONE" and "ANY" are supported.'
argument :not, Types::Issues::NegatedIssueFilterInputType,
description: 'Negated arguments.',
required: false

View File

@ -22,6 +22,7 @@ module Resolvers
Gitlab::IssuablesCountForState.new(
finder(args),
resource_parent,
fast_fail: true,
store_in_redis_cache: true
)
end

View File

@ -10,8 +10,8 @@ module Resolvers
argument :filter, Types::WorkItems::RelatedLinkTypeEnum,
required: false,
description: "Filter by link type. " \
"Supported values: #{Types::WorkItems::RelatedLinkTypeEnum.values.keys.to_sentence}. " \
'Returns all types if omitted.'
"Supported values: #{Types::WorkItems::RelatedLinkTypeEnum.values.keys.to_sentence}. " \
'Returns all types if omitted.'
type Types::WorkItems::LinkedItemType.connection_type, null: true

View File

@ -15,8 +15,8 @@ module Types
field :count,
::Types::Analytics::CycleAnalytics::MetricType,
description: 'Limited item count. The backend counts maximum 1000 items, ' \
'for free projects, and maximum 10,000 items for licensed ' \
'projects or licensed groups.'
'for free projects, and maximum 10,000 items for licensed ' \
'projects or licensed groups.'
field :median,
::Types::Analytics::CycleAnalytics::MetricType,

View File

@ -5,8 +5,8 @@ module Types
class DefinitionType < ::Types::BaseObject
graphql_name 'AuditEventDefinition'
description 'Represents the YAML definitions for audit events defined ' \
'in `ee/config/audit_events/types/<event-type-name>.yml` ' \
'and `config/audit_events/types/<event-type-name>.yml`.'
'in `ee/config/audit_events/types/<event-type-name>.yml` ' \
'and `config/audit_events/types/<event-type-name>.yml`.'
authorize :audit_event_definitions
@ -21,14 +21,14 @@ module Types
field :introduced_by_issue, GraphQL::Types::String,
null: true,
description: 'Link to the issue introducing the event. For older' \
'audit events, it can be a commit URL rather than a' \
'merge request URL.'
'audit events, it can be a commit URL rather than a' \
'merge request URL.'
field :introduced_by_mr, GraphQL::Types::String,
null: true,
description: 'Link to the merge request introducing the event. For' \
'older audit events, it can be a commit URL rather than' \
'a merge request URL.'
'older audit events, it can be a commit URL rather than' \
'a merge request URL.'
field :feature_category, GraphQL::Types::String,
null: false,

View File

@ -26,7 +26,7 @@ module Types
field :versions, Types::Ci::Catalog::Resources::VersionType.connection_type, null: true,
description: 'Versions of the catalog resource. This field can only be ' \
'resolved for one catalog resource in any single request.',
'resolved for one catalog resource in any single request.',
resolver: Resolvers::Ci::Catalog::Resources::VersionsResolver
field :verification_level, Types::Ci::Catalog::Resources::VerificationLevelEnum, null: true,
@ -44,7 +44,7 @@ module Types
field :last_30_day_usage_count, GraphQL::Types::Int, null: false,
description: 'Number of projects that used a component from this catalog resource in a pipeline, by using ' \
'`include:component`, in the last 30 days.',
'`include:component`, in the last 30 days.',
alpha: { milestone: '17.0' }
def web_path

View File

@ -16,27 +16,27 @@ module Types
GraphQL::Types::String,
null: true,
description: 'File location. It can be masked if it contains masked variables. For example, ' \
'`".gitlab/ci/build-images.gitlab-ci.yml"`.'
'`".gitlab/ci/build-images.gitlab-ci.yml"`.'
field :blob,
GraphQL::Types::String,
null: true,
description: 'File blob location. It can be masked if it contains masked variables. For example, ' \
'`"https://gitlab.com/gitlab-org/gitlab/-/blob/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
'`"https://gitlab.com/gitlab-org/gitlab/-/blob/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
field :raw,
GraphQL::Types::String,
null: true,
description: 'File raw location. It can be masked if it contains masked variables. For example, ' \
'`"https://gitlab.com/gitlab-org/gitlab/-/raw/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
'`"https://gitlab.com/gitlab-org/gitlab/-/raw/e52d6d0246d7375291850e61f0abc101fbda9dc2' \
'/.gitlab/ci/build-images.gitlab-ci.yml"`.'
field :extra, # rubocop:disable Graphql/JSONType
GraphQL::Types::JSON,
null: true,
description: 'Extra information for the `include`, which can contain `job_name`, `project`, and `ref`. ' \
'Values can be masked if they contain masked variables.'
'Values can be masked if they contain masked variables.'
field :context_project,
GraphQL::Types::String,

View File

@ -29,7 +29,10 @@ module Ml
delegate :name, to: :model
scope :order_by_model_id_id_desc, -> { order('model_id, id DESC') }
scope :latest_by_model, -> { order_by_model_id_id_desc.select('DISTINCT ON (model_id) *') }
scope :latest_by_model, -> {
order(model_id: :desc, semver_major: :desc, semver_minor: :desc, semver_patch: :desc)
.select('DISTINCT ON (model_id) *')
}
scope :by_version, ->(version) { where("version LIKE ?", "#{sanitize_sql_like(version)}%") } # rubocop:disable GitlabSecurity/SqlInjection -- we are sanitizing
scope :for_model, ->(model) { where(project: model.project, model: model) }
scope :including_relations, -> { includes(:project, :model, :candidate) }

View File

@ -613,6 +613,9 @@ class User < ApplicationRecord
end
scope :by_user_email, ->(emails) { iwhere(email: Array(emails)) }
scope :by_emails, ->(emails) { joins(:emails).where(emails: { email: Array(emails).map(&:downcase) }) }
scope :by_detumbled_emails, ->(detumbled_emails) do
joins(:emails).where(emails: { detumbled_email: Array(detumbled_emails) })
end
scope :for_todos, ->(todos) { where(id: todos.select(:user_id).distinct) }
scope :with_emails, -> { preload(:emails) }
scope :with_dashboard, ->(dashboard) { where(dashboard: dashboard) }
@ -1735,6 +1738,10 @@ class User < ApplicationRecord
verified_emails.uniq
end
def verified_detumbled_emails
emails.distinct.confirmed.pluck(:detumbled_email).compact
end
def public_verified_emails
strong_memoize(:public_verified_emails) do
emails = verified_emails(include_private_email: false)

View File

@ -61,6 +61,10 @@ class UserCustomAttribute < ApplicationRecord
upsert_custom_attribute(user_id: spam_log.user_id, key: AUTO_BANNED_BY_SPAM_LOG_ID, value: spam_log.id)
end
def set_auto_banned_by(user:, reason:)
upsert_custom_attribute(user_id: user.id, key: AUTO_BANNED_BY, value: reason)
end
def set_trusted_by(user:, trusted_by:)
return unless user && trusted_by

View File

@ -55,7 +55,7 @@ module Import
groups_awaiting_placeholder_assignment.collect do |namespace|
placeholders << "%{group_#{namespace.id}_link_start}#{namespace.name}%{group_#{namespace.id}_link_end}"
tag_pairs << tag_pair(
tag.a(href: group_group_members_path(namespace)),
tag.a(href: group_group_members_path(namespace, tab: 'placeholders')),
:"group_#{namespace.id}_link_start",
:"group_#{namespace.id}_link_end"
)

View File

@ -10,6 +10,7 @@ module Users
def execute
if user.ban
record_custom_attribute
ban_duplicate_users
success
else
messages = user.errors.full_messages
@ -20,6 +21,7 @@ module Users
def execute!
user.ban!
record_custom_attribute
ban_duplicate_users
success
end
@ -27,13 +29,12 @@ module Users
attr_reader :user, :reason
def ban_duplicate_users
AntiAbuse::BanDuplicateUsersWorker.perform_async(user.id)
end
def record_custom_attribute
custom_attribute = {
user_id: user.id,
key: UserCustomAttribute::AUTO_BANNED_BY,
value: reason
}
UserCustomAttribute.upsert_custom_attributes([custom_attribute])
UserCustomAttribute.set_auto_banned_by(user: user, reason: reason)
end
end
end

View File

@ -7,7 +7,12 @@ module Users
private
def update_user(user)
user.ban
if user.ban
ban_duplicate_users(user)
true
else
false
end
end
def valid_state?(user)
@ -17,6 +22,10 @@ module Users
def action
:ban
end
def ban_duplicate_users(user)
AntiAbuse::BanDuplicateUsersWorker.perform_async(user.id)
end
end
end

View File

@ -50,8 +50,8 @@
%tbody
%tr
%th= _('MD5')
%td.gl-font-monospace.key-fingerprint= @key.fingerprint
%td.gl-font-monospace= @key.fingerprint
- if @key.fingerprint_sha256.present?
%tr
%th= _('SHA256')
%td.gl-font-monospace.key-fingerprint= @key.fingerprint_sha256
%td.gl-font-monospace= @key.fingerprint_sha256

View File

@ -2424,6 +2424,15 @@
:weight: 1
:idempotent: true
:tags: []
- :name: anti_abuse_ban_duplicate_users
:worker_name: AntiAbuse::BanDuplicateUsersWorker
:feature_category: :instance_resiliency
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: anti_abuse_spam_abuse_events
:worker_name: AntiAbuse::SpamAbuseEventsWorker
:feature_category: :instance_resiliency

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
module AntiAbuse
class BanDuplicateUsersWorker
include ApplicationWorker
data_consistency :delayed
idempotent!
feature_category :instance_resiliency
urgency :low
def perform(banned_user_id)
@banned_user = User.find_by_id(banned_user_id)
return unless banned_user&.banned?
ban_users_with_the_same_detumbled_email!
end
private
attr_reader :banned_user
def ban_users_with_the_same_detumbled_email!
return unless Feature.enabled?(:auto_ban_via_detumbled_email, banned_user, type: :gitlab_com_derisk)
reason = "User #{banned_user.id} was banned with the same detumbled email address"
User.active.by_detumbled_emails(banned_user.verified_detumbled_emails).each do |user|
user.with_lock do
# Check the user state again in case it changed before acquiring the lock
ban_user!(user, reason) if user.active?
end
end
end
def ban_user!(user, reason)
user.ban!
UserCustomAttribute.set_auto_banned_by(user: user, reason: reason)
log_event(user, reason)
end
def log_event(user, reason)
Gitlab::AppLogger.info(
message: "Duplicate user auto-ban",
reason: reason,
username: user.username.to_s,
user_id: user.id,
email: user.email.to_s,
triggered_by_banned_user_id: banned_user.id,
triggered_by_banned_username: banned_user.username
)
end
end
end
AntiAbuse::BanDuplicateUsersWorker.prepend_mod

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user cancels the deletion of a specific package version after initiating the delete process.
action: cancel_delete_package_version
product_group: package_registry
milestone: '16.0'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117497
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user cancels the deletion process for multiple package versions after initiating it.
action: cancel_delete_package_versions
product_group: package_registry
milestone: '15.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110721
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user cancels the deletion process for multiple packages after initiating it.
action: cancel_delete_packages
product_group: package_registry
milestone: '15.8'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104402
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,18 @@
---
description: tracks when a user selects or deselects a checkbox in the filter.
action: checkbox
product_group: global_search
milestone: '16.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121037
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate
additional_properties:
label:
description: label on the checkbox
property:
description: value of checkbox (checked/unchecked)

View File

@ -0,0 +1,13 @@
---
description: tracks when a user deletes the work item.
action: click_delete_work_item
product_group: project_management
milestone: '15.0'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86128
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks when a user toggles the confidentiality status of a work item.
action: click_toggle_work_item_confidentiality
product_group: product_planning
milestone: '15.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94059
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user confirms the deletion of a specific package version.
action: delete_package_version
product_group: package_registry
milestone: '16.0'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117497
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user confirms the deletion of multiple package versions in bulk.
action: delete_package_versions
product_group: package_registry
milestone: '15.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110721
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user confirms the deletion of multiple packages in bulk.
action: delete_packages
product_group: package_registry
milestone: '15.8'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104402
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,18 @@
---
description: tracks when a user collapses the sidebar navigation.
action: nav_hide
product_group: personal_productivity
milestone: '16.2'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124194
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate
additional_properties:
label:
description: Label on how sidebar is hidden with browser resize, toggle button etc.
property:
description: name of the sidebar(nav_sidebar).

View File

@ -0,0 +1,18 @@
---
description: tracks when a user hovers over or partially opens the sidebar to preview its contents without fully expanding it.
action: nav_hover_peek
product_group: personal_productivity
milestone: '16.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131028
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate
additional_properties:
label:
description: nav_sidebar_toggle
property:
description: name of the sidebar(nav_sidebar).

View File

@ -0,0 +1,18 @@
---
description: tracks when a user hovers over or partially opens the sidebar to preview its contents without fully expanding it.
action: nav_peek
product_group: personal_productivity
milestone: '16.2'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124194
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate
additional_properties:
label:
description: nav_hover
property:
description: name of the sidebar(nav_sidebar).

View File

@ -0,0 +1,18 @@
---
description: tracks when a user sees the sidebar navigation.
action: nav_show
product_group: personal_productivity
milestone: '16.2'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124194
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate
additional_properties:
label:
description: Label on how sidebar is shown with browser resize, toggle button etc.
property:
description: name of the sidebar(nav_sidebar).

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user initiates the request to delete a specific package version.
action: request_delete_package_version
product_group: package_registry
milestone: '16.0'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117497
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user initiates the request to delete multiple package versions in bulk.
action: request_delete_package_versions
product_group: package_registry
milestone: '15.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110721
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks an event when a user initiates the request to delete multiple packages in bulk.
action: request_delete_packages
product_group: package_registry
milestone: '15.8'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104402
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks when a user initiates the deletion of selected package files in the package registry.
action: request_delete_selected_package_file
product_group: package_registry
milestone: '15.3'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93169
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks when a user selects "Incident" as the issue type from the dropdown.
action: select_issue_type_incident
product_group: project_management
milestone: '15.11'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115364
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks when a user updates the status of an alert.
action: update_alert_status
product_group: respond
milestone: '13.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32846
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks when a user submits the form to update the package cleanup policy settings for a project.
action: submit_packages_cleanup_form
product_group: package_registry
milestone: '15.2'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90783
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,16 @@
---
description: tracks when a user updates the status of an alert.
action: update_alert_status
product_group: respond
milestone: '13.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32846
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate
additional_properties:
label:
description: status of the alert

View File

@ -0,0 +1,13 @@
---
description: tracks when a user updates the selected group during the subscription process.
action: update_group
product_group: acquisition
milestone: '14.8'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74828
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks when a user updates the number of seats (users) during the subscription process.
action: update_seat_count
product_group: acquisition
milestone: '14.8'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74828
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,13 @@
---
description: tracks when a user views the details of an alert
action: view_alert_details
product_group: respond
milestone: '13.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32846
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,9 @@
---
name: auto_ban_via_detumbled_email
feature_issue_url: https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/814
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166673
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/491796
milestone: '17.5'
group: group::anti-abuse
type: gitlab_com_derisk
default_enabled: false

View File

@ -5,4 +5,4 @@ rollout_issue_url:
milestone: '16.1'
type: ops
group: group::tenant scale
default_enabled: false
default_enabled: true

View File

@ -453,6 +453,10 @@ project_security_exclusions:
- table: projects
column: project_id
on_delete: async_delete
project_security_statistics:
- table: projects
column: project_id
on_delete: async_delete
projects:
- table: organizations
column: organization_id

View File

@ -726,6 +726,9 @@ Settings.cron_jobs['ci_click_house_finished_pipelines_sync_worker']['job_class']
Settings.cron_jobs['deactivate_expired_deployments_cron_worker'] ||= {}
Settings.cron_jobs['deactivate_expired_deployments_cron_worker']['cron'] ||= '*/10 * * * *'
Settings.cron_jobs['deactivate_expired_deployments_cron_worker']['job_class'] ||= 'Pages::DeactivateExpiredDeploymentsCronWorker'
Settings.cron_jobs['database_monitor_locked_tables_cron_worker'] ||= {}
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['cron'] ||= '30 7 */3 * *'
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['job_class'] = 'Database::MonitorLockedTablesWorker'
Gitlab.ee do
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= {}
@ -752,9 +755,6 @@ Gitlab.ee do
Settings.cron_jobs['adjourned_projects_deletion_cron_worker'] ||= {}
Settings.cron_jobs['adjourned_projects_deletion_cron_worker']['cron'] ||= '0 7 * * *'
Settings.cron_jobs['adjourned_projects_deletion_cron_worker']['job_class'] = 'AdjournedProjectsDeletionCronWorker'
Settings.cron_jobs['database_monitor_locked_tables_cron_worker'] ||= {}
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['cron'] ||= '30 7 */3 * *'
Settings.cron_jobs['database_monitor_locked_tables_cron_worker']['job_class'] = 'Database::MonitorLockedTablesWorker'
Settings.cron_jobs['geo_verification_cron_worker'] ||= {}
Settings.cron_jobs['geo_verification_cron_worker']['cron'] ||= '* * * * *'
Settings.cron_jobs['geo_verification_cron_worker']['job_class'] ||= 'Geo::VerificationCronWorker'

View File

@ -270,11 +270,14 @@ InitializerConnections.raise_if_new_database_connection do
resources :groups, only: [:index, :new, :create]
get '/-/g/:id' => 'groups/redirect#redirect_from_id'
draw :group
resources :projects, only: [:index, :new, :create]
get '/projects/:id' => 'projects/redirect#redirect_from_id'
get '/-/p/:id' => 'projects/redirect#redirect_from_id'
draw :git_http
draw :api

View File

@ -45,6 +45,8 @@
- 1
- - analytics_usage_trends_counter_job
- 1
- - anti_abuse_ban_duplicate_users
- 1
- - anti_abuse_new_abuse_report
- 1
- - anti_abuse_spam_abuse_events

View File

@ -0,0 +1,12 @@
---
table_name: project_security_statistics
classes:
- Security::ProjectStatistics
feature_categories:
- vulnerability_management
description: Stores security-related statistics
introduced_by_url:
milestone: '17.5'
gitlab_schema: gitlab_sec
sharding_key:
project_id: projects

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class CreateProjectSecurityStatisticsTable < Gitlab::Database::Migration[2.2]
milestone '17.5'
def change
create_table :project_security_statistics, id: false do |t| # rubocop:disable Migration/EnsureFactoryForTable -- False positive
t.bigint :project_id, primary_key: true, default: nil
t.integer :vulnerability_count, default: 0, null: false
end
end
end

View File

@ -0,0 +1 @@
2baf9eab2929a27d4dfca9d7a811a44e39102e0ac642dc769a008a06c18940a9

View File

@ -16955,6 +16955,11 @@ CREATE SEQUENCE project_security_settings_project_id_seq
ALTER SEQUENCE project_security_settings_project_id_seq OWNED BY project_security_settings.project_id;
CREATE TABLE project_security_statistics (
project_id bigint NOT NULL,
vulnerability_count integer DEFAULT 0 NOT NULL
);
CREATE TABLE project_settings (
project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -25057,6 +25062,9 @@ ALTER TABLE ONLY project_security_exclusions
ALTER TABLE ONLY project_security_settings
ADD CONSTRAINT project_security_settings_pkey PRIMARY KEY (project_id);
ALTER TABLE ONLY project_security_statistics
ADD CONSTRAINT project_security_statistics_pkey PRIMARY KEY (project_id);
ALTER TABLE ONLY project_settings
ADD CONSTRAINT project_settings_pkey PRIMARY KEY (project_id);

View File

@ -473,7 +473,7 @@ you must do so in an `attributes` hash.
| Setting | Description | Examples |
|--------------|-------------|----------|
| `username` | Used in paths for the user's own projects (for example, `gitlab.example.com/username/project`) and when mentioning them in issues, merge request and comments (for example, `@username`). If the attribute specified for `username` contains an email address, the GitLab username is part of the email address before the `@`. Defaults to the LDAP attribute [specified as `uid`](#basic-configuration-settings). | `['uid', 'userid', 'sAMAccountName']` |
| `username` | The `@username` that the GitLab account will be provisioned with. If the value contains an email address, the GitLab username is the part of the email address before the `@`. Defaults to the LDAP attribute [specified as `uid`](#basic-configuration-settings). | `['uid', 'userid', 'sAMAccountName']` |
| `email` | LDAP attribute for user email. Defaults to `['mail', 'email', 'userPrincipalName']` | `['mail', 'email', 'userPrincipalName']` |
| `name` | LDAP attribute for user display name. If `name` is blank, the full name is taken from the `first_name` and `last_name`. Defaults to `'cn'`. | Attributes `'cn'`, or `'displayName'` commonly carry full names. Alternatively, you can force the use of `first_name` and `last_name` by specifying an absent attribute such as `'somethingNonExistent'`. |
| `first_name` | LDAP attribute for user first name. Used when the attribute configured for `name` does not exist. Defaults to `'givenName'`. | `'givenName'` |

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Configure GitLab to access self-hosted models
DETAILS:
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Offering:** Self-managed
**Status:** Beta

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Deploy a self-hosted large language model
DETAILS:
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Offering:** Self-managed
**Status:** Beta

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Set up your self-hosted model infrastructure
DETAILS:
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Offering:** Self-managed
**Status:** Beta

View File

@ -4,6 +4,7 @@ group: Custom Models
description: Self Hosted model setup with an OpenAI Proxy Server (LiteLLM)
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
---
# Set up a self-hosted large language model with LiteLLM
[LiteLLM](https://www.litellm.ai/) is an OpenAI proxy server. You can use LiteLLM to simplify the integration with different large language models (LLMs) by leveraging the OpenAI API spec. Use LiteLLM to easily switch between different LLMs.

View File

@ -8,13 +8,20 @@ info: To determine the technical writer assigned to the Stage/Group associated w
DETAILS:
**Tier:** Ultimate
**Offering:** GitLab.com
**Offering:** GitLab.com, Self-managed
**Status:** Beta
> - Introduced in GitLab 15.4 [with a flag](../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default.
> - `cube_api_proxy` removed and replaced with `product_analytics_internal_preview` in GitLab 15.10.
> - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
> - `product_analytics_dashboards` [enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/398653) by default in GitLab 16.11.
> - Feature flag `product_analytics_dashboards` [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/454059) in GitLab 17.1.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167296) to beta and feature flag `product_analytics_features` added in GitLab 17.5.
FLAG:
The availability of this feature is controlled by a feature flag.
For more information, see the history.
This feature is not ready for production use.
NOTE:
Make sure to define the `cube_api_base_url` and `cube_api_key` application settings first using [the API](settings.md).

View File

@ -184,6 +184,7 @@ You can also enable and disable the setting with the [GraphQL](../../api/graphql
### Git push to your project repository
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/389060) in GitLab 17.2. [with a flag](../../administration/feature_flags.md) named `allow_push_repository_for_job_token`. Disabled by default.
> - **Token Access** setting [renamed to **Job token permissions**](https://gitlab.com/gitlab-org/gitlab/-/issues/415519) in GitLab 17.2.
FLAG:
The availability of this feature is controlled by a feature flag.
@ -208,7 +209,7 @@ To grant permission to job tokens generated in your project to push to the proje
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > CI/CD**.
1. Expand **Token Access**.
1. Expand **Job token permissions**.
1. In the **Permissions** section, select **Allow Git push requests to the repository**.
The job token has the same access permissions as the user that started the job.

View File

@ -45,7 +45,7 @@ configure the settings that are common for all providers.
| Linux package, Docker, and self-compiled | Helm chart | Description | Default value |
| ----------------------------|------------|-------------|-----------|
| `enable` | `enable` | Allows the use of OmniAuth providers. | `false`, which means that signing in using your OmniAuth providers is not allowed, and the OmniAuth provider buttons are not visible in the user interface. |
| `enabled` | `enabled` | Allows the use of OmniAuth providers. | `false`, which means that signing in using your OmniAuth providers is not allowed, and the OmniAuth provider buttons are not visible in the user interface. |
| `allow_single_sign_on` | `allowSingleSignOn` | List of providers that automatically create a GitLab account. The provider names are available in the **OmniAuth provider name** column in the [supported providers table](#supported-providers). | `false`, which means that signing in using your OmniAuth provider account without a pre-existing GitLab account is not allowed. You must create a GitLab account first, and then connect it to your OmniAuth provider account through your profile settings. |
| `auto_link_ldap_user` | `autoLinkLdapUser` | Creates an LDAP identity in GitLab for users that are created through an OmniAuth provider. You can enable this setting if you have [LDAP integration](../administration/auth/ldap/index.md) enabled. Requires the `uid` of the user to be the same in both LDAP and the OmniAuth provider. | `false` |
| `block_auto_created_users` | `blockAutoCreatedUsers` | Places automatically-created users in a [Pending approval](../administration/moderate_users.md#users-pending-approval) state (unable to sign in) until they are approved by an administrator. | `true`. If you set the value to `false`, make sure you define providers that you can control, like SAML or Google. Otherwise, any user on the internet can sign in to GitLab without an administrator's approval. |

View File

@ -153,7 +153,7 @@ DETAILS:
### Self-Hosted Models
DETAILS:
**Tier:** For a limited time, Premium and Ultimate. On October 17, 2024, [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Tier:** For a limited time, Ultimate. On October 17, 2024, Ultimate with [GitLab Duo Enterprise](https://about.gitlab.com/gitlab-duo/#pricing).
**Offering:** Self-managed
**Status:** Beta

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
DETAILS:
**Tier:** Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
**Offering:** GitLab.com, Self-managed
**Status:** Beta
> - Introduced in GitLab 15.4 as an [experiment](../../policy/experiment-beta-support.md#experiment) feature [with a flag](../../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default.
@ -23,6 +23,7 @@ DETAILS:
> - [Enabled on self-managed and GitLab Dedicated](https://gitlab.com/gitlab-org/gitlab/-/issues/444345) in GitLab 16.11.
> - Feature flag `product_analytics_dashboards` [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/454059) in GitLab 17.1.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167192) to beta and feature flag `product_analytics_admin_settings` added in GitLab 17.5.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167296) to beta and feature flag `product_analytics_features` added in GitLab 17.5.
FLAG:
The availability of this feature is controlled by a feature flag.

View File

@ -199,7 +199,7 @@ client.get_latest_versions(model_name)
**Notes**
- Argument `stages` is ignored.
- Versions are ordered by last created.
- Versions are ordered by highest semantic version.
#### Loading a model version

View File

@ -47,6 +47,7 @@ The Repository X-Ray searches a maximum of two directory levels from the reposit
| Java | Gradle | `build.gradle` | 17.4 or later |
| Java | Maven | `pom.xml` | 17.4 or later |
| Kotlin | Gradle | `build.gradle.kts` | 17.5 or later |
| PHP | Composer | `composer.lock` | 17.5 or later |
| Python | Conda | `environment.yml` | 17.5 or later |
| Python | Pip | `requirements.txt` | 17.5 or later |
| Python | Poetry | `poetry.lock`, `pyproject.toml` | 17.5 or later |

View File

@ -143,8 +143,8 @@ module Gitlab
def params_include_filters?
non_filtering_params = %i[
scope state sort group_id include_subgroups namespace_id
attempt_group_search_optimizations non_archived issue_types lookahead
scope state sort group_id include_subgroups include_descendants namespace_id
attempt_group_search_optimizations non_archived issue_types lookahead exclude_projects
]
finder.params.except(*non_filtering_params).values.any?

1
qa/.gitignore vendored
View File

@ -1,5 +1,4 @@
reports/
no_of_examples/
.ruby-version
.tool-versions
.ruby-gemset

View File

@ -1,5 +0,0 @@
--color
--format documentation
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
--require spec_helper

View File

@ -2,7 +2,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa', '~> 14', '>= 14.15.0', require: 'gitlab/qa'
gem 'gitlab-qa', '~> 14', '>= 14.16.0', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 1.38.1', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8.4' # This should stay in sync with the root's Gemfile

View File

@ -116,7 +116,7 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
gitlab-qa (14.15.0)
gitlab-qa (14.16.0)
activesupport (>= 6.1, < 7.2)
gitlab (~> 4.19)
http (~> 5.0)
@ -369,7 +369,7 @@ DEPENDENCIES
fog-core (= 2.1.0)
fog-google (~> 1.24, >= 1.24.1)
gitlab-cng!
gitlab-qa (~> 14, >= 14.15.0)
gitlab-qa (~> 14, >= 14.16.0)
gitlab-utils!
gitlab_quality-test_tooling (~> 1.38.1)
googleauth (~> 1.9.0)

View File

@ -1,4 +1,4 @@
ARG GDK_SHA=25dd408d7727657811bf9fb59b1cbc15d4f1d160
ARG GDK_SHA=a5a1c84f9a0625231b09b03c8dadcc2322a3e2bb
# Use tag prefix when running on 'stable' branch to make sure 'protected' image is used which is not deleted by registry cleanup
ARG GDK_BASE_TAG_PREFIX

View File

@ -16,9 +16,8 @@ module QA
return false
end
QA::Support::Retrier.retry_on_exception do
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
end
# Perform app readiness check before continuing with the whole test suite
Tools::ReadinessCheck.perform(wait: 60)
# Reset admin password if admin token is present but can't be used due to expired password
reset_admin_password!

View File

@ -2,6 +2,7 @@
require 'active_support/deprecation'
require 'uri'
require 'etc'
module QA
module Runtime
@ -734,6 +735,16 @@ module QA
enabled?(ENV['QA_RSPEC_RETRIED'], default: false)
end
def parallel_processes
ENV.fetch('QA_PARALLEL_PROCESSES') do
[Etc.nprocessors / 2, 1].max
end.to_i
end
def parallel_run?
enabled?(ENV["QA_PARALLEL_RUN"], default: false)
end
private
def remote_grid_credentials

View File

@ -10,12 +10,26 @@ module QA
def logger
@logger ||= Gitlab::QA::TestLogger.logger(
level: Gitlab::QA::Runtime::Env.log_level,
source: 'QA Tests',
path: File.expand_path('../../tmp', __dir__)
source: logger_source,
path: log_path
)
end
delegate :debug, :info, :warn, :error, :fatal, :unknown, to: :logger
private
def logger_source
if ENV['TEST_ENV_NUMBER']
"QA Tests ENV-#{ENV['TEST_ENV_NUMBER']}"
else
"QA Tests"
end
end
def log_path
File.expand_path('../../tmp', __dir__)
end
end
end
end

View File

@ -3,7 +3,7 @@
module QA
module Specs
class KnapsackRunner
def self.run(args)
def self.run(args, parallel: false)
QA::Support::KnapsackReport.configure!
allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
@ -14,6 +14,12 @@ module QA
Knapsack.logger.info 'Leftover specs:'
Knapsack.logger.info allocator.leftover_node_tests
if parallel
rspec_args = args.reject { |arg| arg == "--" || arg.start_with?("qa/specs/features") }
run_args = [*rspec_args, '--', *allocator.node_tests]
return ParallelRunner.run(run_args)
end
status = RSpec::Core::Runner.run([*args, '--', *allocator.node_tests])
yield status if block_given?
status

View File

@ -1,31 +1,52 @@
# frozen_string_literal: true
require 'open3'
require "parallel_tests"
require "etc"
module QA
module Specs
module ParallelRunner
module_function
class ParallelRunner
class << self
def run(rspec_args)
used_processes = Runtime::Env.parallel_processes
def run(args)
unless args.include?('--')
index = args.index { |opt| opt.include?('features') }
args = [
"--type", "rspec",
"-n", used_processes.to_s,
"--serialize-stdout",
'--first-is-1',
"--combine-stderr"
]
args.insert(index, '--') if index
unless rspec_args.include?('--')
index = rspec_args.index { |opt| opt.include?("qa/specs/features") }
rspec_args.insert(index, '--') if index
end
args.push("--", *rspec_args) unless rspec_args.empty?
set_environment!
perform_global_setup!
ParallelTests::CLI.new.run(args)
end
env = {}
Runtime::Env::ENV_VARIABLES.each_key do |key|
env[key] = ENV[key] if ENV[key]
private
def perform_global_setup!
Runtime::Browser.configure!
Runtime::Release.perform_before_hooks
end
env['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = Runtime::Scenario.attributes.to_json
env['GITLAB_QA_ACCESS_TOKEN'] = Runtime::API::Client.new(:gitlab).personal_access_token unless env['GITLAB_QA_ACCESS_TOKEN']
cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{args.flatten.join(' ')}"
::Open3.popen2e(env, cmd) do |_, out, wait|
out.each { |line| puts line }
def set_environment!
ENV.store("NO_KNAPSACK", "true")
ENV.store("QA_PARALLEL_RUN", "true")
exit wait.value.exitstatus
return if ENV["QA_GITLAB_URL"].present?
Support::GitlabAddress.define_gitlab_address_attribute!
ENV.store("QA_GITLAB_URL", Support::GitlabAddress.address_with_port(with_default_port: false))
end
end
end

View File

@ -2,6 +2,7 @@
require 'rspec/core'
require 'rspec/expectations'
require 'tempfile'
module QA
module Specs
@ -47,51 +48,57 @@ module QA
end
def perform
args = []
args.push('--tty') if tty
args.push(rspec_tags)
args.push(options)
args = build_initial_args
# use options from default .rspec file for metadata only runs
configure_default_formatters!(args) unless metadata_run?
args.push(DEFAULT_TEST_PATH_ARGS) unless custom_test_paths?
unless Runtime::Env.knapsack? || options.any? { |opt| opt.include?('features') }
args.push(DEFAULT_TEST_PATH_ARGS)
run_rspec(args)
end
private
delegate :rspec_retried?, :parallel_run?, to: Runtime::Env
def build_initial_args
[].tap do |args|
args.push('--tty') if tty
args.push(rspec_tags)
args.push(options)
end
end
if Runtime::Env.knapsack?
KnapsackRunner.run(args.flatten) { |status| abort if status.nonzero? }
elsif Runtime::Scenario.attributes[:parallel]
ParallelRunner.run(args.flatten)
elsif Runtime::Scenario.attributes[:loop]
LoopRunner.run(args.flatten)
elsif Runtime::Scenario.attributes[:count_examples_only]
def run_rspec(args)
if Runtime::Scenario.attributes[:count_examples_only]
count_examples_only(args)
elsif Runtime::Scenario.attributes[:test_metadata_only]
test_metadata_only(args)
elsif Runtime::Env.knapsack?
KnapsackRunner.run(args.flatten, parallel: parallel_run?) { |status| abort if status.nonzero? }
elsif !rspec_retried? && parallel_run?
ParallelRunner.run(args.flatten)
elsif Runtime::Scenario.attributes[:loop]
LoopRunner.run(args.flatten)
else
RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap { |status| abort if status.nonzero? }
end
end
private
def count_examples_only(args)
args.unshift('--dry-run')
out = StringIO.new
err = StringIO.new
RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status|
abort if status.nonzero?
total_examples = Tempfile.open('test-metadata.json') do |file|
RSpec.configure { |config| config.add_formatter(QA::Support::JsonFormatter, file.path) }
RSpec::Core::Runner.run(args.flatten, err, out).tap { |status| abort if status.nonzero? }
JSON.load_file(file, symbolize_names: true).dig(:summary, :example_count)
end
begin
total_examples = out.string.match(/(\d+) examples?,/)[1]
rescue StandardError
raise RegexMismatchError, 'Rspec output did not match regex'
end
filename = build_filename
File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0
$stdout.puts total_examples
puts total_examples
rescue StandardError => e
raise e, "Failed to detect example count, error: '#{e}'.\nOut: '#{out.string}'\nErr: #{err.string}"
end
def test_metadata_only(args)
@ -111,23 +118,28 @@ module QA
$stdout.puts "Saved to file: #{output_file}"
end
def build_filename
filename = Runtime::Scenario.klass.split('::').last(3).join('_').downcase
def configure_default_formatters!(args)
default_formatter_file_name = "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-#{rspec_retried?}"
filename = if parallel_run?
rspec_retried? ? default_formatter_file_name : "#{default_formatter_file_name}-$TEST_ENV_NUMBER"
else
default_formatter_file_name
end
tags = []
tag_opts = %w[--tag -t]
options.reduce do |before, after|
tags << after if tag_opts.include?(before)
after
args.push("--format", "documentation") unless args.flatten.include?("documentation")
{ "QA::Support::JsonFormatter" => "json", "RspecJunitFormatter" => "xml" }.each do |formatter, extension|
next if args.flatten.include?(formatter)
args.push("--format", formatter, "--out", "#{filename}.#{extension}")
end
tags = tags.compact.join('_')
end
filename.concat("_#{tags}") unless tags.empty?
def custom_test_paths?
Runtime::Env.knapsack? || options.any? { |opt| opt.include?('features') }
end
filename.concat('.txt')
FileUtils.mkdir_p('no_of_examples')
File.join('no_of_examples', filename)
def metadata_run?
Runtime::Scenario.attributes[:count_examples_only] || Runtime::Scenario.attributes[:test_metadata_only]
end
end
end

View File

@ -42,7 +42,12 @@ RSpec.configure do |config|
config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', 'tmp/examples.txt')
config.prepend_before do |example|
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
if QA::Runtime::Env.parallel_run?
QA::Runtime::Logger.info("Starting test - PID #{Process.pid}: #{Rainbow(example.full_description).bright}")
else
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
end
QA::Runtime::Example.current = example
visit(QA::Runtime::Scenario.gitlab_address) if QA::Runtime::Env.mobile_layout?
@ -53,8 +58,8 @@ RSpec.configure do |config|
end
config.prepend_before(:suite) do
# Perform before hooks at the very start of the test run
QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run
# Perform before hooks at the very start of the test run, perform once for parallel runs
QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run || QA::Runtime::Env.parallel_run?
end
config.before(:suite) do

View File

@ -97,9 +97,12 @@ module QA
def push_test_metrics_to_gcs
init_gcs_client! # init client and exit early if mandatory configuration is missing
retry_on_exception(sleep_interval: 30, message: 'Failed to push test metrics to GCS') do
gcs_client.put_object(gcs_bucket, metrics_file_name(prefix: 'test',
postfix: "-#{env('CI_PIPELINE_ID') || 'local'}"), execution_data.to_json,
force: true, content_type: 'application/json')
gcs_client.put_object(
gcs_bucket,
metrics_file_name(prefix: 'test', postfix: metrics_filename_postfix),
execution_data.to_json,
force: true, content_type: 'application/json'
)
log(:info, "Pushed #{execution_data.length} test execution entries to GCS")
end
@ -140,10 +143,12 @@ module QA
def push_fabrication_metrics_gcs(data)
init_gcs_client! # init client and exit early if mandatory configuration is missing
retry_on_exception(sleep_interval: 30, message: 'Failed to push resource fabrication metrics to GCS') do
gcs_client.put_object(gcs_bucket,
metrics_file_name(prefix: 'fabrication',
postfix: "-#{env('CI_PIPELINE_ID') || 'local'}"),
data.to_json, force: true, content_type: 'application/json')
gcs_client.put_object(
gcs_bucket,
metrics_file_name(prefix: 'fabrication', postfix: metrics_filename_postfix),
data.to_json, force: true,
content_type: 'application/json'
)
log(:info, "Pushed #{data.length} resource fabrication entries to GCS")
end
@ -186,6 +191,17 @@ module QA
"#{retry_failed_specs? ? "-retry-#{rspec_retried?}" : ''}#{postfix}.json"
end
# Postfix for metrics filenames
#
# @return [String]
def metrics_filename_postfix
@metrics_filename_postfix ||= if QA::Runtime::Env.parallel_run?
"-#{Process.pid}-#{env('CI_PIPELINE_ID') || 'local'}"
else
"-#{env('CI_PIPELINE_ID') || 'local'}"
end
end
# Transform example to influxdb compatible metrics data
# https://github.com/influxdata/influxdb-client-ruby#data-format
#

View File

@ -0,0 +1,124 @@
# frozen_string_literal: true
require "nokogiri"
module QA
module Tools
# Helper to assert GitLab instance readiness without starting a web browser
#
class ReadinessCheck
include Support::API
def self.perform(wait: 60)
new(wait: wait).perform
end
def initialize(wait:)
@wait = wait
end
# Validate gitlab readiness via check for presence of sign-in-form element
#
# @return [void]
def perform
error = nil
info("Waiting for Gitlab to become ready!")
Support::Retrier.retry_until(max_duration: wait, sleep_interval: 1, raise_on_failure: false, log: false) do
result = !required_elements_missing?
error = nil
result
rescue StandardError => e
error = "#{error_base} #{e.message}"
false
end
raise error if error
info("Gitlab is ready!")
end
private
delegate :debug, :info, to: QA::Runtime::Logger
attr_reader :wait
# Sign in page url
#
# @return [String]
def url
@url ||= "#{Support::GitlabAddress.address_with_port(with_default_port: false)}/users/sign_in"
end
# Error message base
#
# @return [String]
def error_base
@error_base ||= "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!"
end
# Required elements css selectors
#
# @return [Array<String>]
def elements_css
@element_css ||= QA::Page::Main::Login.elements.select(&:required?).map(&:selector_css)
end
# Check for missing required elements on sign-in page
#
# @return [Boolean]
def required_elements_missing?
debug("Checking for required element presence on '#{url}'")
# Do not perform headless request on .com due to cloudfare
return rendered_elements_missing? if Runtime::Env.running_on_dot_com?
response = get(url)
unless ok_response?(response)
msg = "Got unsucessfull response code: #{response.code}"
debug(msg) && raise(msg)
end
unless required_elements_present?(response)
msg = "Sign in page missing required elements: '#{elements_css}'"
debug(msg) && raise(msg)
end
debug("Required elements are present!")
false
end
# Perform check for present elements via web browser
#
# @return [Boolean]
def rendered_elements_missing?
debug("Checking for required elements via web browser")
Runtime::Browser.visit(:gitlab, Page::Main::Login)
false
rescue StandardError => e
debug("Sign in page did not render fully")
raise(e)
end
# Validate response code is 200
#
# @param [RestClient::Response] response
# @return [Boolean]
def ok_response?(response)
response.code == Support::API::HTTP_STATUS_OK
end
# Check required elements are present on sign-in page
#
# @param [RestClient::Response] response
# @return [Boolean]
def required_elements_present?(response)
doc = Nokogiri::HTML.parse(response.body)
elements_css.all? { |sel| doc.css(sel).any? }
end
end
end
end

View File

@ -1,58 +1,93 @@
# frozen_string_literal: true
require 'etc'
RSpec.describe QA::Specs::ParallelRunner do
include QA::Support::Helpers::StubEnv
subject(:runner) { described_class }
let(:parallel_tests) { instance_double(ParallelTests::CLI, run: nil) }
before do
allow(QA::Runtime::Scenario).to receive(:attributes).and_return(parallel: true)
stub_env('GITLAB_QA_ACCESS_TOKEN', 'skip_token_creation')
allow(ParallelTests::CLI).to receive(:new).and_return(parallel_tests)
allow(Etc).to receive(:nprocessors).and_return(8)
allow(ENV).to receive(:store)
allow(QA::Runtime::Browser).to receive(:configure!)
allow(QA::Runtime::Release).to receive(:perform_before_hooks)
stub_env("QA_GITLAB_URL", "http://127.0.0.1:3000")
stub_env("QA_PARALLEL_PROCESSES", "8")
end
it 'passes args to parallel_tests' do
expect_cli_arguments(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
it "runs cli without additional rspec args" do
runner.run([])
subject.run(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
expect(parallel_tests).to have_received(:run).with([
"--type", "rspec",
"-n", "8",
"--serialize-stdout",
"--first-is-1",
"--combine-stderr"
])
end
it 'passes a given test path to parallel_tests and adds a separator' do
expect_cli_arguments(%w[-- qa/specs/features/foo])
it "runs cli with additional rspec args" do
runner.run(["--force-color", "qa/specs/features/api"])
subject.run(%w[qa/specs/features/foo])
expect(parallel_tests).to have_received(:run).with([
"--type", "rspec",
"-n", "8",
"--serialize-stdout",
"--first-is-1",
"--combine-stderr",
"--", "--force-color",
"--", "qa/specs/features/api"
])
end
it 'passes tags and test paths to parallel_tests and adds a separator' do
expect_cli_arguments(%w[--tag smoke -- qa/specs/features/foo qa/specs/features/bar])
context "with QA_GITLAB_URL not set" do
before do
stub_env("QA_GITLAB_URL", nil)
subject.run(%w[--tag smoke qa/specs/features/foo qa/specs/features/bar])
end
it 'passes tags and test paths with separators to parallel_tests' do
expect_cli_arguments(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
subject.run(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
end
it 'passes supported environment variables' do
# Test only env vars starting with GITLAB because some of the others
# affect how the runner behaves, and we're not concerned with those
# behaviors in this test
gitlab_env_vars = QA::Runtime::Env::ENV_VARIABLES.reject { |v| !v.start_with?('GITLAB') }
gitlab_env_vars.each do |k, v|
stub_env(k, v)
QA::Support::GitlabAddress.instance_variable_set(:@initialized, nil)
end
gitlab_env_vars['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = '{"parallel":true}'
after do
QA::Support::GitlabAddress.instance_variable_set(:@initialized, nil)
end
expect_cli_arguments([], gitlab_env_vars)
it "sets QA_GITLAB_URL variable for subprocess" do
runner.run([])
subject.run([])
expect(ENV).to have_received(:store).with("QA_GITLAB_URL", "http://127.0.0.1:3000")
end
end
def expect_cli_arguments(arguments, env = { 'QA_RUNTIME_SCENARIO_ATTRIBUTES' => '{"parallel":true}' })
cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{arguments.join(' ')}"
expect(Open3).to receive(:popen2e)
.with(hash_including(env), cmd)
.and_return(0)
context "with QA_PARALLEL_PROCESSES not set" do
before do
stub_env("QA_PARALLEL_PROCESSES", nil)
allow(Etc).to receive(:nprocessors).and_return(8)
end
it "sets number of processes to half of available processors" do
allow(QA::Runtime::Env).to receive(:parallel_processes).and_call_original
runner.run([])
expect(QA::Runtime::Env).to have_received(:parallel_processes)
actual_processes = QA::Runtime::Env.parallel_processes
expect(parallel_tests).to have_received(:run) do |args|
expect(args).to eq([
"--type", "rspec",
"-n", actual_processes.to_s,
"--serialize-stdout",
"--first-is-1",
"--combine-stderr"
])
end
end
end
end

View File

@ -3,7 +3,10 @@
RSpec.describe QA::Specs::Runner do
shared_examples 'excludes default skipped, and geo' do
it 'excludes the default skipped and geo tags, and includes default args' do
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
@ -28,7 +31,10 @@ RSpec.describe QA::Specs::Runner do
it 'sets the `--tty` flag' do
expect_rspec_runner_arguments(
['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]
)
subject.perform
@ -37,63 +43,37 @@ RSpec.describe QA::Specs::Runner do
context 'when count_examples_only is set as an option' do
let(:out) { StringIO.new }
let(:err) { StringIO.new }
before do
QA::Runtime::Scenario.define(:count_examples_only, true)
out.string = '22 examples,'
allow(StringIO).to receive(:new).and_return(out)
allow(StringIO).to receive(:new).and_return(out, err)
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
end
after do
QA::Runtime::Scenario.attributes.delete(:count_examples_only)
end
it 'sets the `--dry-run` flag' do
it 'sets the `--dry-run` flag and minimal arguments' do
expect_rspec_runner_arguments(
['--dry-run'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
[$stderr, anything]
['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
[err, out]
)
subject.perform
end
it 'writes to file when examples are more than zero' do
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
expect(File).to receive(:open).with('no_of_examples/test_instance_all.txt', 'w') { '22' }
subject.perform
end
it 'does not write to file when zero examples' do
out.string = '0 examples,'
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
expect(File).not_to receive(:open)
subject.perform
end
it 'raises error when Rspec output does not match regex' do
out.string = '0'
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
expect { subject.perform }
.to raise_error(QA::Specs::Runner::RegexMismatchError, 'Rspec output did not match regex')
expect { subject.perform }.to raise_error(JSON::ParserError, /Failed to detect example count/)
end
context 'when --tag is specified as an option' do
subject { described_class.new.tap { |runner| runner.options = %w[--tag actioncable] } }
it 'includes the option value in the file name' do
it 'includes the tag in the RSpec arguments' do
expect_rspec_runner_arguments(
['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS],
[$stderr, anything]
[err, out]
)
expect(File).to receive(:open).with('no_of_examples/test_instance_all_actioncable.txt', 'w') { '22' }
subject.perform
expect { subject.perform }.to raise_error(JSON::ParserError, /Failed to detect example count/)
end
end
end
@ -116,24 +96,11 @@ RSpec.describe QA::Specs::Runner do
it 'sets the `--dry-run` flag' do
expect_rspec_runner_arguments(
['--dry-run', *described_class::DEFAULT_TEST_PATH_ARGS],
[$stderr, anything]
[$stderr, $stdout]
)
subject.perform
end
it 'configures json formatted output to file' do
allow(QA::Runtime::Path).to receive(:qa_root).and_return('/root')
expect(rspec_config).to receive(:add_formatter)
.with(QA::Support::JsonFormatter, output_file)
expect(rspec_config).to receive(:fail_if_no_examples=)
.with(true)
allow(RSpec::Core::Runner).to receive(:run).and_return(0)
subject.perform
end
end
context 'when tags are set' do
@ -141,7 +108,10 @@ RSpec.describe QA::Specs::Runner do
it 'focuses on the given tags' do
expect_rspec_runner_arguments(
['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]
)
subject.perform
@ -152,7 +122,10 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke] } }
it 'focuses on the given tag without excluded tags' do
expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke', *described_class::DEFAULT_TEST_PATH_ARGS])
expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
@ -162,7 +135,13 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.options = %w[qa/specs/features/foo] } }
it 'passes the given tests path and excludes the default skipped, and geo tags' do
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', 'qa/specs/features/foo'])
expect_rspec_runner_arguments(
['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo',
'qa/specs/features/foo',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml"]
)
subject.perform
end
@ -172,7 +151,13 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke qa/specs/features/foo] } }
it 'focuses on the given tag and includes the path without excluding the orchestrated or transient tags' do
expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke', 'qa/specs/features/foo'])
expect_rspec_runner_arguments(
['--tag', '~geo', '--tag', 'smoke',
'qa/specs/features/foo',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml"]
)
subject.perform
end
@ -184,7 +169,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes the skip_signup_disabled tag' do
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
@ -196,7 +184,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes the skip_live_env tag' do
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env', *described_class::DEFAULT_TEST_PATH_ARGS])
expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
end
@ -213,7 +204,10 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.tags = %i[geo] } }
it 'includes the geo tag' do
expect_rspec_runner_arguments(['--tag', 'geo', *described_class::DEFAULT_TEST_PATH_ARGS])
expect_rspec_runner_arguments(['--tag', 'geo',
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
end
@ -231,7 +225,9 @@ RSpec.describe QA::Specs::Runner do
it 'includes default args and excludes all unsupported tags' do
expect_rspec_runner_arguments(
DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *excluded_feature_tags_except(feature),
*described_class::DEFAULT_TEST_PATH_ARGS]
'--format', 'documentation', '--format', 'QA::Support::JsonFormatter',
'--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json",
'--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]
)
subject.perform

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
RSpec.describe QA::Tools::ReadinessCheck do
subject(:readiness_check) { described_class.new(wait: wait) }
let(:url) { "example.com" }
let(:wait) { 1 }
let(:msg_base) { "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!" }
let(:dot_com) { false }
let(:response) { instance_double(RestClient::Response, code: code, body: body) }
let(:code) { 200 }
let(:body) { "" }
before do
allow(QA::Runtime::Env).to receive(:running_on_dot_com?).and_return(dot_com)
allow(QA::Support::GitlabAddress).to receive(:address_with_port).with(with_default_port: false).and_return(url)
allow(readiness_check).to receive(:get).with("#{url}/users/sign_in").and_return(response)
end
context "with successfull response" do
let(:body) do
<<~HTML
<!DOCTYPE html>
<body data-testid="login-page">
</body>
</html>
HTML
end
it "validates readiness" do
expect { readiness_check.perform }.not_to raise_error
end
end
context "with missing sign in form" do
let(:body) do
<<~HTML
<!DOCTYPE html>
</html>
HTML
end
it "raises an error on validation" do
expect { readiness_check.perform }.to raise_error(/#{msg_base} Sign in page missing required elements/)
end
end
context "with unsuccessfull response code" do
let(:code) { 500 }
it "raises an error on validation" do
expect { readiness_check.perform }.to raise_error(
"#{msg_base} Got unsucessfull response code: #{code}"
)
end
end
context "with request timeout" do
before do
allow(readiness_check).to receive(:get).and_raise(RestClient::Exceptions::OpenTimeout)
end
it "raises an error on validation" do
expect { readiness_check.perform }.to raise_error("#{msg_base} Timed out connecting to server")
end
end
context "when running on dot com" do
let(:dot_com) { true }
context "with successfull check" do
before do
allow(QA::Runtime::Browser).to receive(:visit).with(:gitlab, QA::Page::Main::Login)
end
it "validates readiness" do
expect { readiness_check.perform }.not_to raise_error
end
end
context "with unsuccessfull check" do
before do
allow(QA::Runtime::Browser).to receive(:visit).with(:gitlab, QA::Page::Main::Login).and_raise("not loaded")
end
it "raises an error on validation" do
expect { readiness_check.perform }.to raise_error("#{msg_base} not loaded")
end
end
end
end

View File

@ -895,6 +895,7 @@ project:
- observability_traces
- observability_logs
- security_exclusions
- security_statistics
award_emoji:
- awardable
- user

View File

@ -233,7 +233,7 @@ RSpec.describe Ml::ModelVersion, feature_category: :mlops do
subject { described_class.latest_by_model }
it 'returns only the latest model version per model id' do
is_expected.to match_array([model_version4, model_version2])
is_expected.to match_array([model_version3, model_version2])
end
end

Some files were not shown because too many files have changed in this diff Show More