Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-07-31 21:10:25 +00:00
parent 11c99a1a7a
commit 9a7adb84bf
56 changed files with 851 additions and 622 deletions

View File

@ -291,6 +291,7 @@
.bundler-patterns: &bundler-patterns
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
- '{Gemfile.next.lock,*/Gemfile.next.lock,*/*/Gemfile.next.lock}'
.nodejs-patterns: &nodejs-patterns
- '{package.json,*/package.json,*/*/package.json}'
@ -301,6 +302,7 @@
.dependency-patterns: &dependency-patterns
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
- '{Gemfile.next.lock,*/Gemfile.next.lock,*/*/Gemfile.next.lock}'
- '{go.sum,*/go.sum,*/*/go.sum}'
- '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
@ -322,6 +324,7 @@
.assets-compilation-patterns: &assets-compilation-patterns
- "{package.json,yarn.lock}"
- "{Gemfile,Gemfile.lock}"
- "{Gemfile.next,Gemfile.next.lock}"
- ".browserslistrc"
- "babel.config.js"
- "config/webpack.config.js"
@ -352,6 +355,7 @@
- "**/Rakefile"
- "**/Dangerfile"
- "**/Gemfile"
- "**/Gemfile.next"
- "**/Guardfile"
- "**/*.rake"
- "**/*.rb"
@ -360,6 +364,7 @@
# Backend patterns + .ci-patterns
.backend-patterns: &backend-patterns
- "{,jh/}Gemfile{,.lock}"
- "{,jh/}Gemfile.next{,.lock}"
- "Rakefile"
- "config.ru"
- "keeps/**/*"
@ -378,6 +383,7 @@
.search-backend-patterns: &search-backend-patterns
- "{,jh/}Gemfile.lock"
- "{,jh/}Gemfile.next.lock"
- "GITLAB_ELASTICSEARCH_INDEXER_VERSION"
# List explicitly all the app/ dirs that are backend (i.e. all except app/assets).
- "{,ee/,jh/}{app/channels,app/components,app/controllers,app/finders,app/graphql,app/helpers,app/mailers,app/models,app/policies,app/presenters,app/serializers,app/services,app/uploaders,app/validators,app/views,app/workers}/**/*"
@ -470,6 +476,7 @@
- ".stylelintrc"
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
- "{,jh/}Gemfile{,.lock}"
- "{,jh/}Gemfile.next{,.lock}"
- "{package.json,yarn.lock}"
- "*_VERSION"
- "lib/gitlab/redis/*"
@ -497,6 +504,7 @@
- ".stylelintrc"
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
- "{,jh/}Gemfile{,.lock}"
- "{,jh/}Gemfile.next{,.lock}"
- "{package.json,yarn.lock}"
- "*_VERSION"
- "babel.config.js"
@ -531,6 +539,7 @@
- ".stylelintrc"
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
- "{,jh/}Gemfile{,.lock}"
- "{,jh/}Gemfile.next{,.lock}"
- "{package.json,yarn.lock}"
- "*_VERSION"
- "babel.config.js"
@ -560,6 +569,7 @@
- ".stylelintrc"
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
- "{,jh/}Gemfile{,.lock}"
- "{,jh/}Gemfile.next{,.lock}"
- "{package.json,yarn.lock}"
- "*_VERSION"
- "babel.config.js"
@ -605,6 +615,7 @@
- ".{eslintrc.yml,eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "{,jh/}Gemfile.next{,.lock}"
- "keeps/**/*"
- "Rakefile"
- "tests.yml"
@ -653,6 +664,7 @@
- ".rubocop_todo/**/*.yml"
- "{,ee/,jh/}rubocop/**/*" # We might be changing custom cops
- "{,ee/,jh/}Gemfile.lock" # This should include gitlab-styles, rubocop itself, and any plugins we might be using
- "{,ee/,jh/}Gemfile.next.lock" # This should include gitlab-styles, rubocop itself, and any plugins we might be using
- "lib/gitlab_edition.rb" # This is required in RuboCop::CodeReuseHelpers
- ".gitlab/ci/static-analysis.gitlab-ci.yml"
- "config/feature_categories.yml" # Used by RSpec/FeatureCategory
@ -664,6 +676,7 @@
.core-backend-patterns: &core-backend-patterns
- "{,jh/}Gemfile{,.lock}"
- "{,jh/}Gemfile.next{,.lock}"
- "{,ee/,jh/}config/**/*.rb"
.core-frontend-patterns: &core-frontend-patterns
@ -685,6 +698,7 @@
.gdk-component-patterns: &gdk-component-patterns
- qa/gdk/**/*
- Gemfile.lock
- Gemfile.next.lock
- yarn.lock
- scripts/build_gdk_image
- scripts/frontend/postinstall.js
@ -2737,7 +2751,7 @@
when: never
- <<: *if-schedule-maintenance
- <<: *if-merge-request
changes: ["Gemfile.lock"]
changes: ["Gemfile.lock", "Gemfile.next.lock"]
.reports:rules:x-ray:
rules:

View File

@ -102,7 +102,6 @@ Gitlab/BoundedContexts:
- 'app/finders/personal_access_tokens_finder.rb'
- 'app/finders/personal_projects_finder.rb'
- 'app/finders/projects_finder.rb'
- 'app/finders/prometheus_metrics_finder.rb'
- 'app/finders/protected_branches_finder.rb'
- 'app/finders/releases_finder.rb'
- 'app/finders/resource_milestone_event_finder.rb'

View File

@ -73,7 +73,6 @@ Gitlab/NamespacedClass:
- 'app/finders/personal_access_tokens_finder.rb'
- 'app/finders/personal_projects_finder.rb'
- 'app/finders/projects_finder.rb'
- 'app/finders/prometheus_metrics_finder.rb'
- 'app/finders/protected_branches_finder.rb'
- 'app/finders/releases_finder.rb'
- 'app/finders/resource_milestone_event_finder.rb'

View File

@ -3108,7 +3108,6 @@ Layout/LineLength:
- 'spec/finders/personal_access_tokens_finder_spec.rb'
- 'spec/finders/personal_projects_finder_spec.rb'
- 'spec/finders/projects/groups_finder_spec.rb'
- 'spec/finders/projects/prometheus/alerts_finder_spec.rb'
- 'spec/finders/projects_finder_spec.rb'
- 'spec/finders/repositories/tree_finder_spec.rb'
- 'spec/finders/security/security_jobs_finder_spec.rb'

View File

@ -100,7 +100,6 @@ RSpec/AnyInstanceOf:
- 'spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb'
- 'spec/features/snippets/embedded_snippet_spec.rb'
- 'spec/features/usage_stats_consent_spec.rb'
- 'spec/finders/prometheus_metrics_finder_spec.rb'
- 'spec/graphql/mutations/alert_management/create_alert_issue_spec.rb'
- 'spec/graphql/mutations/alert_management/http_integration/create_spec.rb'
- 'spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb'

View File

@ -1431,9 +1431,7 @@ RSpec/FeatureCategory:
- 'spec/finders/projects/groups_finder_spec.rb'
- 'spec/finders/projects/members/effective_access_level_finder_spec.rb'
- 'spec/finders/projects/members/effective_access_level_per_user_finder_spec.rb'
- 'spec/finders/projects/prometheus/alerts_finder_spec.rb'
- 'spec/finders/projects/topics_finder_spec.rb'
- 'spec/finders/prometheus_metrics_finder_spec.rb'
- 'spec/finders/protected_branches_finder_spec.rb'
- 'spec/finders/releases/evidence_pipeline_finder_spec.rb'
- 'spec/finders/resource_milestone_event_finder_spec.rb'

View File

@ -1382,8 +1382,6 @@ RSpec/NamedSubject:
- 'spec/finders/projects/members/effective_access_level_per_user_finder_spec.rb'
- 'spec/finders/projects/ml/candidate_finder_spec.rb'
- 'spec/finders/projects/ml/model_finder_spec.rb'
- 'spec/finders/projects/prometheus/alerts_finder_spec.rb'
- 'spec/finders/prometheus_metrics_finder_spec.rb'
- 'spec/finders/releases/evidence_pipeline_finder_spec.rb'
- 'spec/finders/releases_finder_spec.rb'
- 'spec/finders/repositories/tree_finder_spec.rb'

View File

@ -29,11 +29,13 @@ export const TARGET_NAMESPACE_FIELD = 'targetNamespace';
export const ROOT_NAMESPACE = { fullPath: '', id: null };
export const QUERY_PARAM_FAILED = 'failed';
const PLACEHOLDER_STATUS_PENDING_REASSIGNMENT = 'PENDING_REASSIGNMENT';
export const PLACEHOLDER_STATUS_AWAITING_APPROVAL = 'AWAITING_APPROVAL';
const PLACEHOLDER_STATUS_REJECTED = 'REJECTED';
export const PLACEHOLDER_STATUS_REASSIGNING = 'REASSIGNMENT_IN_PROGRESS';
const PLACEHOLDER_STATUS_FAILED = 'FAILED';
export const PLACEHOLDER_STATUS_FAILED = 'FAILED';
export const PLACEHOLDER_STATUS_KEPT_AS_PLACEHOLDER = 'KEEP_AS_PLACEHOLDER';
export const PLACEHOLDER_STATUS_COMPLETED = 'COMPLETED';

View File

@ -4,6 +4,11 @@ import { mapState } from 'vuex';
import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { s__, sprintf } from '~/locale';
import { getParameterByName } from '~/lib/utils/url_utility';
import {
PLACEHOLDER_STATUS_FAILED,
QUERY_PARAM_FAILED,
} from '~/import_entities/import_groups/constants';
import importSourceUsersQuery from '../graphql/queries/import_source_users.query.graphql';
import PlaceholdersTable from './placeholders_table.vue';
@ -40,6 +45,7 @@ export default {
fullPath: this.group.path,
...this.cursor,
[this.cursor.before ? 'last' : 'first']: DEFAULT_PAGE_SIZE,
statuses: this.queryStatuses,
};
},
update(data) {
@ -64,6 +70,16 @@ export default {
pageInfo() {
return this.sourceUsers?.pageInfo || {};
},
statusParamValue() {
return getParameterByName('status');
},
queryStatuses() {
if (getParameterByName('status') === QUERY_PARAM_FAILED) {
return [PLACEHOLDER_STATUS_FAILED];
}
return [];
},
},
mounted() {

View File

@ -1,10 +1,23 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "../fragments/import_source_user.fragment.graphql"
query importSourceUsers($fullPath: ID!, $before: String, $after: String, $first: Int, $last: Int) {
query importSourceUsers(
$fullPath: ID!
$before: String
$after: String
$first: Int
$last: Int
$statuses: [ImportSourceUserStatus!]
) {
namespace(fullPath: $fullPath) {
id
importSourceUsers(before: $before, after: $after, first: $first, last: $last) {
importSourceUsers(
before: $before
after: $after
first: $first
last: $last
statuses: $statuses
) {
nodes {
...ImportSourceUser
}

View File

@ -4,6 +4,9 @@ import { escape } from 'lodash';
import { __, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { InternalEvents } from '~/tracking';
const trackingMixin = InternalEvents.mixin();
export default {
components: {
@ -12,6 +15,7 @@ export default {
directives: {
SafeHtml,
},
mixins: [trackingMixin],
props: {
templates: {
type: Array,
@ -54,6 +58,7 @@ export default {
},
async selectTemplate(templatePath) {
const template = await axios.get(templatePath);
this.trackEvent('apply_wiki_template');
this.$emit('input', template.data);
},
highlight(text) {

View File

@ -74,7 +74,11 @@ export default {
this.search = searchTerm;
},
onTokensUpdate(tokens) {
this.$emit('update', tokens);
const uniqueTokens = Array.from(new Map(tokens.map((item) => [item.name, item])).values());
this.selectedTokens = uniqueTokens;
this.$emit('update', this.selectedTokens);
},
},
AVATAR_SHAPE_OPTION_RECT,
@ -88,6 +92,7 @@ export default {
:dropdown-items="topics"
:loading="loading"
allow-user-defined-tokens
show-add-new-always
:placeholder="placeholderText"
@keydown.enter="handleEnter"
@text-input="filterTopics"

View File

@ -2,6 +2,7 @@
class SandboxController < ApplicationController # rubocop:disable Gitlab/NamespacedClass
skip_before_action :authenticate_user!
skip_before_action :enforce_terms!
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Namespaces
module GroupsFilter
private
def by_search(groups)
return groups unless params[:search].present?
groups.search(params[:search], include_parents: params[:parent].blank?)
end
def skip_groups(groups)
return groups unless params[:skip_groups].present?
groups.id_not_in(params[:skip_groups])
end
def min_access_level?
current_user && params[:min_access_level].present?
end
def sort(groups)
return groups.order_id_desc unless params[:sort]
groups.sort_by_attribute(params[:sort])
end
def by_visibility(groups)
return groups unless params[:visibility]
groups.by_visibility_level(params[:visibility])
end
end
end

View File

@ -29,6 +29,7 @@
# public groups instead, even if `all_available` is set to false.
class GroupsFinder < UnionFinder
include CustomAttributesFilter
include Namespaces::GroupsFilter
attr_reader :current_user, :params
@ -115,12 +116,6 @@ class GroupsFinder < UnionFinder
groups.in_organization(organization)
end
def by_visibility(groups)
return groups unless params[:visibility]
groups.by_visibility_level(params[:visibility])
end
def by_parent(groups)
return groups unless params[:parent]
@ -155,18 +150,6 @@ class GroupsFinder < UnionFinder
groups.id_not_in(params[:exclude_group_ids])
end
def by_search(groups)
return groups unless params[:search].present?
groups.search(params[:search], include_parents: params[:parent].blank?)
end
def sort(groups)
return groups.order_id_desc unless params[:sort]
groups.sort_by_attribute(params[:sort])
end
def include_parent_shared_groups?
params.fetch(:include_parent_shared_groups, false)
end
@ -175,10 +158,6 @@ class GroupsFinder < UnionFinder
params.fetch(:include_parent_descendants, false)
end
def min_access_level?
current_user && params[:min_access_level].present?
end
def include_public_groups?
current_user.nil? || all_available?
end

View File

@ -12,7 +12,8 @@
#
module Namespaces
module Groups
class SharedGroupsFinder < GroupsFinder
class SharedGroupsFinder
include Namespaces::GroupsFilter
include Gitlab::Allowable
attr_reader :group, :current_user, :params
@ -35,6 +36,15 @@ module Namespaces
def filter_shared_groups(groups)
by_visibility(groups)
.then { |filtered_groups| skip_groups(filtered_groups) }
.then { |filtered_groups| by_search(filtered_groups) }
.then { |filtered_groups| by_min_access_level(filtered_groups) }
end
def by_min_access_level(groups)
return groups unless min_access_level?
groups.by_min_access_level(current_user, params[:min_access_level])
end
end
end

View File

@ -1,69 +0,0 @@
# frozen_string_literal: true
module Projects
module Prometheus
# Find Prometheus alerts by +project+, +environment+, +id+,
# or any combo thereof.
#
# Optionally filter by +metric+.
#
# Arguments:
# params:
# project: Project | integer
# environment: Environment | integer
# metric: PrometheusMetric | integer
class AlertsFinder
def initialize(params = {})
unless params[:project] || params[:environment] || params[:id]
raise ArgumentError,
'Please provide one or more of the following params: :project, :environment, :id'
end
@params = params
end
# Find all matching alerts
#
# @return [ActiveRecord::Relation<PrometheusAlert>]
def execute
relation = by_project(PrometheusAlert)
relation = by_environment(relation)
relation = by_metric(relation)
relation = by_id(relation)
ordered(relation)
end
private
attr_reader :params
def by_project(relation)
return relation unless params[:project]
relation.for_project(params[:project])
end
def by_environment(relation)
return relation unless params[:environment]
relation.for_environment(params[:environment])
end
def by_metric(relation)
return relation unless params[:metric]
relation.for_metric(params[:metric])
end
def by_id(relation)
return relation unless params[:id]
relation.id_in(params[:id])
end
def ordered(relation)
relation.order_by('id_asc')
end
end
end
end

View File

@ -1,132 +0,0 @@
# frozen_string_literal: true
class PrometheusMetricsFinder
ACCEPTED_PARAMS = [
:project,
:group,
:title,
:y_label,
:identifier,
:id,
:common,
:ordered
].freeze
# Cautiously preferring a memoized class method over a constant
# so that the DB connection is accessed after the class is loaded.
def self.indexes
@indexes ||= PrometheusMetric
.connection
.indexes(:prometheus_metrics)
.map { |index| index.columns.map(&:to_sym) }
end
def initialize(params = {})
@params = params.slice(*ACCEPTED_PARAMS)
end
# @return [PrometheusMetric, PrometheusMetric::ActiveRecord_Relation]
def execute
validate_params!
metrics = by_project(::PrometheusMetric.all)
metrics = by_group(metrics)
metrics = by_title(metrics)
metrics = by_y_label(metrics)
metrics = by_common(metrics)
metrics = by_ordered(metrics)
metrics = by_identifier(metrics)
by_id(metrics)
end
private
attr_reader :params
def by_project(metrics)
return metrics unless params[:project]
metrics.for_project(params[:project])
end
def by_group(metrics)
return metrics unless params[:group]
metrics.for_group(params[:group])
end
def by_title(metrics)
return metrics unless params[:title]
metrics.for_title(params[:title])
end
def by_y_label(metrics)
return metrics unless params[:y_label]
metrics.for_y_label(params[:y_label])
end
def by_common(metrics)
return metrics unless params[:common]
metrics.common
end
def by_ordered(metrics)
return metrics unless params[:ordered]
metrics.ordered
end
def by_identifier(metrics)
return metrics unless params[:identifier]
metrics.for_identifier(params[:identifier])
end
def by_id(metrics)
return metrics unless params[:id]
metrics.id_in(params[:id])
end
def validate_params!
validate_params_present!
validate_id_params!
validate_indexes!
end
# Ensure all provided params are supported
def validate_params_present!
raise ArgumentError, "Please provide one or more of: #{ACCEPTED_PARAMS}" if params.blank?
end
# Protect against the caller "finding" the wrong metric
def validate_id_params!
raise ArgumentError, 'Only one of :identifier, :id is permitted' if params[:identifier] && params[:id]
return unless params[:identifier] && !(params[:project] || params[:common])
raise ArgumentError, ':identifier must be scoped to a :project or :common'
end
# Protect against unaccounted-for, complex/slow queries.
# This is not a hard and fast rule, but is meant to encourage
# mindful inclusion of new queries.
def validate_indexes!
indexable_params = params.except(:ordered, :id, :project).keys
indexable_params << :project_id if params[:project]
indexable_params.sort!
return if appropriate_index?(indexable_params)
raise ArgumentError, "An index should exist for params: #{indexable_params}"
end
def appropriate_index?(indexable_params)
return true if indexable_params.blank?
self.class.indexes.any? { |index| (index - indexable_params).empty? }
end
end

View File

@ -301,6 +301,7 @@ class Group < Namespace
scope :order_path_asc, -> { reorder(self.arel_table['path'].asc) }
scope :order_path_desc, -> { reorder(self.arel_table['path'].desc) }
scope :in_organization, ->(organization) { where(organization: organization) }
scope :by_min_access_level, ->(user, access_level) { joins(:group_members).where(members: { user: user }).where('members.access_level >= ?', access_level) }
class << self
def sort_by_attribute(method)

View File

@ -18,7 +18,7 @@ module PersonalAccessTokens
# would be updated when using #touch).
return unless update?
with_lease do
try_obtain_lease do
::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do
@personal_access_token.update_column(:last_used_at, Time.zone.now)
end
@ -35,18 +35,6 @@ module PersonalAccessTokens
@lease_key ||= "pat:last_used_update_lock:#{@personal_access_token.id}"
end
def with_lease
return yield unless Feature.enabled?(
:use_lease_for_pat_last_used_update,
Feature.current_request,
type: :gitlab_com_derisk
)
try_obtain_lease do
yield
end
end
def update?
return false if ::Gitlab::Database.read_only?

View File

@ -0,0 +1,18 @@
---
description: Wiki template is applied to a page
internal_events: true
action: apply_wiki_template
identifiers:
- project
- namespace
- user
product_group: knowledge
milestone: '17.3'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161130
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -1,9 +0,0 @@
---
name: use_lease_for_pat_last_used_update
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468851
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158577
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/471276
milestone: '17.2'
group: group::scalability
type: gitlab_com_derisk
default_enabled: false

View File

@ -397,6 +397,10 @@ security_scans:
- table: p_ci_builds
column: build_id
on_delete: async_delete
security_trainings:
- table: projects
column: project_id
on_delete: async_delete
snippets:
- table: organizations
column: organization_id

View File

@ -0,0 +1,22 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_apply_wiki_template_monthly
description: Monthly count of unique users who applied a template to wiki page
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.3'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161130
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: apply_wiki_template
unique: user.id

View File

@ -0,0 +1,22 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_apply_wiki_template_weekly
description: Weekly count of unique users who applied a template to wiki page
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.3'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161130
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: apply_wiki_template
unique: user.id

View File

@ -0,0 +1,21 @@
---
key_path: counts.count_total_apply_wiki_template
description: Total count of applied wiki templates
product_group: knowledge
performance_indicator_type: []
value_type: number
status: active
milestone: '17.3'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161130
time_frame: all
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: apply_wiki_template

View File

@ -0,0 +1,9 @@
---
migration_job_name: NullifyOrganizationIdForSnippets
description: Nullfies organization_id for ProjectSnippets
feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160838
milestone: '17.3'
queued_migration_version: 20240726081618
finalize_after: '2024-08-26'
finalized_by: # version of the migration that finalized this BBM

View File

@ -7,4 +7,4 @@ feature_categories:
description: Stores information about the available security training providers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78195
milestone: '14.7'
gitlab_schema: gitlab_main_clusterwide
gitlab_schema: gitlab_sec

View File

@ -7,7 +7,7 @@ feature_categories:
description: Stores information about the primary security training provider for a given project
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78195
milestone: '14.7'
gitlab_schema: gitlab_main_cell
gitlab_schema: gitlab_sec
allow_cross_foreign_keys:
- gitlab_main_clusterwide
sharding_key:

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class RemoveProjectsSecurityTrainingsProjectIdFk < Gitlab::Database::Migration[2.2]
milestone '17.3'
disable_ddl_transaction!
FOREIGN_KEY_NAME = "fk_rails_f80240fae0"
def up
with_lock_retries do
remove_foreign_key_if_exists(:security_trainings, :projects,
name: FOREIGN_KEY_NAME, reverse_lock_order: true)
end
end
def down
add_concurrent_foreign_key(:security_trainings, :projects,
name: FOREIGN_KEY_NAME, column: :project_id,
target_column: :id, on_delete: :cascade)
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class QueueNullifyOrganizationIdForSnippets < Gitlab::Database::Migration[2.2]
milestone '17.3'
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "NullifyOrganizationIdForSnippets"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 50
def up
queue_batched_background_migration(
MIGRATION,
:snippets,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :snippets, :id, [])
end
end

View File

@ -0,0 +1 @@
c09bb2913ad4fa49a39694a040e65dbe28506611267e499ad3ad4aa464bd9ab0

View File

@ -0,0 +1 @@
2f550894b53a31cafe392b25b8927d66279b04bcd44a2315d2f716e7cd975313

View File

@ -35573,9 +35573,6 @@ ALTER TABLE ONLY internal_ids
ALTER TABLE ONLY issues_self_managed_prometheus_alert_events
ADD CONSTRAINT fk_rails_f7db2d72eb FOREIGN KEY (self_managed_prometheus_alert_event_id) REFERENCES self_managed_prometheus_alert_events(id) ON DELETE CASCADE;
ALTER TABLE ONLY security_trainings
ADD CONSTRAINT fk_rails_f80240fae0 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY merge_requests_closing_issues
ADD CONSTRAINT fk_rails_f8540692be FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;

View File

@ -454,7 +454,7 @@ To distinguish between a project in the group and a project shared to the group,
## List a group's shared groups
Get a list of groups shared to this group. When accessed without authentication, only public shared groups are returned.
Get a list of groups where the given group has been invited. When accessed without authentication, only public shared groups are returned.
By default, this request returns 20 results at a time because the API results [are paginated](rest/index.md#pagination).
@ -463,10 +463,12 @@ Parameters:
| Attribute | Type | Required | Description |
| ------------------------------------- | ----------------- | -------- | ---------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
| `skip_groups` | array of integers | no | Skip the specified group IDs |
| `search` | string | no | Return the list of authorized groups matching the search criteria |
| `order_by` | string | no | Order groups by `name`, `path`, `id`, or `similarity`. Default is `name` |
| `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` |
| `visibility` | string | no | Limit to groups with `public`, `internal`, or `private` visibility. |
| `min_access_level` | integer | no | Limit to groups where current user has at least the specified [role (`access_level`)](members.md#roles) |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (administrators only) |
```plaintext

View File

@ -9,6 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Visualize and analyze errors, traces, metrics and logs collected from your application and its infrastructure. Monitor, identify and resolve performance issues and incidents collaboratively.
- [Getting started](../user/get_started/get_started_monitoring.md)
- [Error Tracking](error_tracking.md)
- [Distributed tracing](tracing.md)
- [Metrics](metrics.md)

View File

@ -96,7 +96,7 @@ Audit event types belong to the following product categories.
| Name | Description | Saved to database | Streamed | Introduced in | Scope |
|:------------|:------------|:------------------|:---------|:--------------|:--------------|
| [`job_artifact_downloaded`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129608) | Triggered when a user download a job artifact from a project | **{dotted-circle}** No | **{check-circle}** Yes | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/250663) | Project |
| [`job_artifact_downloaded`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129608) | Triggered when a user downloads a job artifact from a project | **{dotted-circle}** No | **{check-circle}** Yes | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/250663) | Project |
### Code review
@ -156,7 +156,7 @@ Audit event types belong to the following product categories.
| [`member_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109711) | Event triggered when a membership is destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/374112) | Group, Project |
| [`member_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109711) | Event triggered when a membership is updated | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/374112) | Group, Project |
| [`merge_request_create`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90911) | Event triggered when a Merge Request is created | **{dotted-circle}** No | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/367239) | Project |
| [`omniauth_login_failed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123080) | Event triggered when an OmniAuth login fails | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User |
| [`omniauth_login_failed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123080) | Triggered when an OmniAuth login fails | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User |
| [`password_reset_requested`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114548) | Event triggered when a user requests a password reset using a registered email address | **{check-circle}** Yes | **{dotted-circle}** No | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User |
| [`personal_access_token_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108952) | Event triggered when a user creates a personal access token | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/374113) | User |
| [`personal_access_token_revoked`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108952) | Event triggered when a personal access token is revoked | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/374113) | User |
@ -520,7 +520,7 @@ Audit event types belong to the following product categories.
| Name | Description | Saved to database | Streamed | Introduced in | Scope |
|:------------|:------------|:------------------|:---------|:--------------|:--------------|
| [`authenticated_with_group_saml`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28575) | Triggered when successfully signing in with SAML authentication. | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/35710) | Group |
| [`ban_user`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116103) | Event triggered on user ban action | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/377620) | User |
| [`ban_user`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116103) | Triggered when a user is banned, unbanned, blocked, or unblocked | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/377620) | User |
| [`change_membership_state`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87924) | Event triggered on a users membership is updated | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/362200) | Group |
| [`password_reset_failed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129079) | Event triggered when a password reset fails for a user | **{dotted-circle}** No | **{check-circle}** Yes | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/377762) | User |
| [`unban_user`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116221) | Event triggered on user unban action | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/377620) | User |

View File

@ -0,0 +1,204 @@
---
stage: Monitor
group: Observability
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
description: "Get Started monitoring your application"
---
# Get started with monitoring your application in GitLab
Monitoring is a crucial part of maintaining and optimizing your applications.
GitLab observability features help you track errors, analyze application performance, and respond to incidents.
These capabilities are part of the larger DevOps workflow:
![Workflow](img/get_started_monitor_app_v17_3.png)
All of these features can be used independently. For example, you can use
tracing or incidents without using error tracking. However, for the best experience,
use all of these features together.
## Step 1: Determine which project to use
You can use the same project for monitoring that you already use to store your application's source code.
For large applications with multiple services and repositories, you should create a dedicated project
to centralize all telemetry data collected from the different components of the system.
This approach offers several benefits:
- Data is accessible to all development and operations teams, which facilitates collaboration.
- Data from different sources can be queried and correlated in one place, which accelerates investigations.
- It provides a single source of truth for all observability data, making it easier to maintain and update.
- It simplifies access management for administrators by centralizing user permissions in a single project.
To enable observability features, you need administrator or the Owner role for the project.
For more information, see:
- [Create a project](../project/index.md)
## Step 2: Track application errors with error tracking
Error tracking helps you identify, prioritize, and debug errors in your application.
Errors generated by your application are collected by the Sentry SDK,
then stored on either GitLab or Sentry back ends.
For more information, see:
- [How error tracking works](../../operations/error_tracking.md#how-error-tracking-works)
## Step 3: Monitor application performance with tracing, metrics, and logs
### Enable beta features
The following features are available in closed beta:
- [Distributed tracing](../../operations/tracing.md): Follow application requests across multiple services.
- [Metrics](../../operations/metrics.md): Monitor application and infrastructure performance metrics,
like request latency, traffic, error rate, or saturation.
- [Logs](../../operations/logs.md): Centralize and analyze application and infrastructure logs.
To make these features available, an administrator must [enable the feature flag](../../administration/feature_flags.md)
named `observability_features` for your project or group. After these features are enabled, you can set up data collection.
### Instrument your application with OpenTelemetry
Traces, metrics, and logs are generated from your application and collected
by OpenTelemetry, then stored on the GitLab back end.
[OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/) is an open-source
observability framework that provides a collection of tools, APIs, and SDKs for generating,
collecting, and exporting telemetry data. The OpenTelemetry Collector is a key component of this framework.
You can collect and send telemetry data to GitLab using either direct instrumentation
or the OpenTelemetry Collector. This table compares the two methods:
| Method | Pros | Cons |
|--------|------|------|
| Direct instrumentation | - Simpler setup<br>- No infrastructure changes| - Less flexible<br>- No data sampling or processing<br>- Can generate high volume of data |
| OpenTelemetry Collector | - Centralized configuration<br>- Enables data sampling and processing<br>- Controlled volume of data | - More complex setup<br>- Requires infrastructure changes |
You should use the OpenTelemetry Collector for most setups, especially if your application
is likely to grow in complexity. However, direct instrumentation can be simpler for testing purposes and small applications.
#### Direct instrumentation
You can instrument your application code to send telemetry data directly to GitLab without using a collector.
Choose a guide based on your programming language or framework:
- [Ruby on Rails](../../tutorials/observability/observability_rails_tutorial.md)
- [Node JS](../../tutorials/observability/observability_nodejs_tutorial.md)
For other languages, use the appropriate [OpenTelemetry API or SDK](https://opentelemetry.io/docs/languages/).
#### Using the OpenTelemetry Collector (recommended)
For complex application setups, you should use the OpenTelemetry Collector.
**What is the OpenTelemetry Collector?**
The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) acts like proxy that receives, processes, and exports telemetry data from your application to your monitoring tools such as GitLab Observability. It is opensource and vendor-neutral, which means you can use with any compatible tools and avoid vendor lock-in.
Benefits of using the Collector:
- Simplicity: Application services send data to only one destination (the Collector)
- Flexibility: Add or change data destinations from a single place (if you use multiple vendors)
- Advanced features: Sampling, batching and compression of data
- Consistency: Uniform data processing
- Governance: Centralized configuration
**Configure the OpenTelemetry Collector**
1. [Quick start installation](https://opentelemetry.io/docs/collector/quick-start/)
1. [Choose a deployment method](https://opentelemetry.io/docs/collector/deployment/) (agent or gateway)
1. [Configure data collection](https://opentelemetry.io/docs/collector/configuration/)
Add the GitLab endpoint as an exporter in the Collector `config.yaml` file:
```yaml
exporters:
otlphttp/gitlab:
endpoint: https://observe.gitlab.com/v3/<group_id>/<project_id>/ingest/
headers:
"private-token": "<your_token>"
service:
pipelines:
traces:
exporters: [spanmetrics, otlphttp/gitlab]
metrics:
exporters: [otlphttp/gitlab]
logs:
exporters: [otlphttp/gitlab]
```
Replace the placeholders with the following values:
- `<group_id>`: The top-level group ID for your project.
On the group homepage, in the upper-right corner,
select the vertical ellipsis (**{ellipsis_v}**), then **Copy group ID**.
- `<project_id>`: The project ID. On the project homepage,
in the upper-right corner, select the vertical ellipsis (**{ellipsis_v}**), then **Copy project ID**.
- `<your_token>`: An access token created in the project with the `Owner` role and
`read_api` and `write_observability` scopes. Create a token at the project's **Settings** > **Access tokens**.
1. Instrument your application to send data to the Collector.
Use the language-specific guides above, but point to your Collector instead of GitLab.
For example, if your application and your Collector are on the same host, send your application to this URL:
```plaintext
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
```
### Test your setup
After setting up data collection, you can visualize the collected data in your project by viewing the **Monitor** navigation menu.
Use the **Tracing**, **Metrics**, and **Logs** pagesto access this information. These features work together to provide a comprehensive view of your application's health and performance, helping you troubleshoot detected issues.
For more information, see:
- [Distributed tracing](../../operations/tracing.md)
- [Metrics](../../operations/metrics.md)
- [Logs](../../operations/logs.md)
## Step 4: Monitor infrastructure with metrics and logs
To monitor your applications' infrastructure performance and availability
first install the OpenTelemetry Collector as described previously. Then,
based on your setup, you can use various methods to gather metrics and logs data:
- For host-level, OS metrics: Use the OpenTelemetry Collector with a receiver like
[Host Metrics](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/hostmetricsreceiver).
This receiver collects CPU, memory, disk, and network metrics from the host system.
- For cloud-based infrastructure: Use your provider's monitoring solution integrated with OpenTelemetry.
For example, receivers like [AWS CloudWatch](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/awscloudwatchreceiver) or [Azure Monitor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/azuremonitorreceiver).
- For containerized applications: Use receivers like
[Docker stats](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/dockerstatsreceiver/) or
[Kubelet stats](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/kubeletstatsreceiver).
- For Kubernetes clusters: Follow [this external guide](https://opentelemetry.io/docs/kubernetes/getting-started/).
## Step 5: Manage alerts and incidents
Set up incident management features to troubleshoot issues and resolve incidents collaboratively.
For more information, see:
- [Incident Management](../../operations/incident_management/index.md)
## Step 6: Analyze and improve
Use the data and insights gathered to continuously improve your application and the monitoring process:
1. Create insight dashboards to analyze issues
or incidents created and closed, and assess the performance of your incident response.
1. Create executable runbooks to help engineers on-call remediate incidents autonomously.
1. Regularly review your monitoring setup and adjust sampling thresholds or add new metrics as your application evolves.
1. Conduct post-incident reviews to identify areas for improvement in both your application and your incident response process.
1. Use the insights gained from monitoring to inform your development priorities and technical debt reduction efforts.
For more information, see:
- [Insight dashboards](../project/insights/index.md)
- [Executable runbooks](../project/clusters/runbooks/index.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -349,8 +349,10 @@ module API
tags %w[groups]
end
params do
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
desc: 'Limit by visibility'
optional :skip_groups, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of group ids to exclude from list'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'Limit by visibility'
optional :search, type: String, desc: 'Search for a specific group'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user'
optional :order_by, type: String, values: %w[name path id similarity], default: 'name', desc: 'Order by name, path, id or similarity if searching'
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Remove organization_id from project snippets
# their organization_id will be calculated from the project relation
class NullifyOrganizationIdForSnippets < BatchedMigrationJob
feature_category :source_code_management
operation_name :nullify_organization_id_for_snippets
def perform
each_sub_batch do |sub_batch|
sub_batch.where(type: 'ProjectSnippet').update_all(organization_id: nil)
end
end
end
end
end

View File

@ -226,6 +226,7 @@ RSpec.describe '.gitlab/ci/rules.gitlab-ci.yml', feature_category: :tooling do
'ee/lib/ee/gitlab/background_migration/.rubocop.yml',
'ee/LICENSE',
'Gemfile.checksum',
'Gemfile.next.checksum',
'gems/error_tracking_open_api/.openapi-generator/FILES',
'gems/error_tracking_open_api/.openapi-generator/VERSION',
'gems/openbao_client/.openapi-generator/FILES',

View File

@ -7,8 +7,8 @@ RSpec.describe Namespaces::Groups::SharedGroupsFinder, feature_category: :groups
let_it_be(:another_user) { create(:user) }
let_it_be(:current_user) { user }
let_it_be(:group) { create(:group, :private, owners: user, name: "group") }
let_it_be(:shared_group) { create(:group, :private, name: "b#{group.name}") }
let_it_be(:other_group) { create(:group, :private, name: "a#{group.name}") }
let_it_be(:shared_group) { create(:group, :private, name: "shared group") }
let_it_be(:other_group) { create(:group, :public, name: "other group") }
let(:params) { {} }
@ -35,5 +35,34 @@ RSpec.describe Namespaces::Groups::SharedGroupsFinder, feature_category: :groups
expect(results).to be_empty
end
end
context 'with search filter' do
let(:params) { { search: "other group" } }
it 'filters by search term' do
expect(results).to contain_exactly(other_group)
end
end
context 'with visibility filter' do
let(:params) { { visibility: 'private' } }
it 'filters by visibility' do
expect(results).to contain_exactly(shared_group)
end
end
context 'with min_access_level filter' do
before_all do
shared_group.add_owner(current_user)
other_group.add_owner(current_user)
end
let(:params) { { min_access_level: Gitlab::Access::OWNER } }
it 'filters by minimum access level' do
expect(results).to contain_exactly(shared_group, other_group)
end
end
end
end

View File

@ -1,169 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Prometheus::AlertsFinder do
let(:finder) { described_class.new(params) }
let(:params) { {} }
describe 'with params' do
let_it_be(:project) { create(:project) }
let_it_be(:other_project) { create(:project) }
let_it_be(:other_env) { create(:environment, project: other_project) }
let_it_be(:production) { create(:environment, project: project) }
let_it_be(:staging) { create(:environment, project: project) }
let_it_be(:alert) { create_alert(project, production) }
let_it_be(:alert2) { create_alert(project, production) }
let_it_be(:stg_alert) { create_alert(project, staging) }
let_it_be(:other_alert) { create_alert(other_project, other_env) }
describe '#execute' do
subject { finder.execute }
context 'with project' do
before do
params[:project] = project
end
it { is_expected.to eq([alert, alert2, stg_alert]) }
context 'with matching metric' do
before do
params[:metric] = alert.prometheus_metric
end
it { is_expected.to eq([alert]) }
end
context 'with matching metric id' do
before do
params[:metric] = alert.prometheus_metric_id
end
it { is_expected.to eq([alert]) }
end
context 'with project non-specific metric' do
before do
params[:metric] = other_alert.prometheus_metric
end
it { is_expected.to be_empty }
end
end
context 'with environment' do
before do
params[:environment] = production
end
it { is_expected.to eq([alert, alert2]) }
context 'with matching metric' do
before do
params[:metric] = alert.prometheus_metric
end
it { is_expected.to eq([alert]) }
end
context 'with environment non-specific metric' do
before do
params[:metric] = stg_alert.prometheus_metric
end
it { is_expected.to be_empty }
end
end
context 'with matching project and environment' do
before do
params[:project] = project
params[:environment] = production
end
it { is_expected.to eq([alert, alert2]) }
context 'with matching metric' do
before do
params[:metric] = alert.prometheus_metric
end
it { is_expected.to eq([alert]) }
end
context 'with environment non-specific metric' do
before do
params[:metric] = stg_alert.prometheus_metric
end
it { is_expected.to be_empty }
end
context 'with matching id' do
before do
params[:id] = alert.id
end
it { is_expected.to eq([alert]) }
end
context 'with a nil id' do
before do
params[:id] = nil
end
it { is_expected.to eq([alert, alert2]) }
end
end
context 'with non-matching project-environment pair' do
before do
params[:project] = project
params[:environment] = other_env
end
it { is_expected.to be_empty }
end
context 'with id' do
before do
params[:id] = alert.id
end
it { is_expected.to eq([alert]) }
end
context 'with multiple ids' do
before do
params[:id] = [alert.id, other_alert.id]
end
it { is_expected.to eq([alert, other_alert]) }
end
context 'with non-matching id' do
before do
params[:id] = -5
end
it { is_expected.to be_empty }
end
end
private
def create_alert(project, environment)
create(:prometheus_alert, project: project, environment: environment)
end
end
describe 'without params' do
subject { finder }
it 'raises an error' do
expect { subject }
.to raise_error(ArgumentError, 'Please provide one or more of the following params: :project, :environment, :id')
end
end
end

View File

@ -1,144 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe PrometheusMetricsFinder do
describe '#execute' do
let(:finder) { described_class.new(params) }
let(:params) { {} }
subject { finder.execute }
context 'with params' do
let_it_be(:project) { create(:project) }
let_it_be(:project_metric) { create(:prometheus_metric, project: project) }
let_it_be(:common_metric) { create(:prometheus_metric, :common) }
let_it_be(:unique_metric) do
create(
:prometheus_metric,
:common,
title: 'Unique title',
y_label: 'Unique y_label',
group: :kubernetes,
identifier: 'identifier',
created_at: 5.minutes.ago
)
end
context 'with appropriate indexes' do
before do
allow_any_instance_of(described_class).to receive(:appropriate_index?).and_return(true)
end
context 'with project' do
let(:params) { { project: project } }
it { is_expected.to eq([project_metric]) }
end
context 'with group' do
let(:params) { { group: project_metric.group } }
it { is_expected.to contain_exactly(common_metric, project_metric) }
end
context 'with title' do
let(:params) { { title: project_metric.title } }
it { is_expected.to contain_exactly(project_metric, common_metric) }
end
context 'with y_label' do
let(:params) { { y_label: project_metric.y_label } }
it { is_expected.to contain_exactly(project_metric, common_metric) }
end
context 'with common' do
let(:params) { { common: true } }
it { is_expected.to contain_exactly(common_metric, unique_metric) }
end
context 'with ordered' do
let(:params) { { ordered: true } }
it { is_expected.to eq([unique_metric, project_metric, common_metric]) }
end
context 'with indentifier' do
let(:params) { { identifier: unique_metric.identifier } }
it 'raises an error' do
expect { subject }.to raise_error(
ArgumentError,
':identifier must be scoped to a :project or :common'
)
end
context 'with common' do
let(:params) { { identifier: unique_metric.identifier, common: true } }
it { is_expected.to contain_exactly(unique_metric) }
end
context 'with id' do
let(:params) { { id: 14, identifier: 'string' } }
it 'raises an error' do
expect { subject }.to raise_error(
ArgumentError,
'Only one of :identifier, :id is permitted'
)
end
end
end
context 'with id' do
let(:params) { { id: common_metric.id } }
it { is_expected.to contain_exactly(common_metric) }
end
context 'with multiple params' do
let(:params) do
{
group: project_metric.group,
title: project_metric.title,
y_label: project_metric.y_label,
common: true,
ordered: true
}
end
it { is_expected.to contain_exactly(common_metric) }
end
end
context 'without an appropriate index' do
let(:params) do
{
title: project_metric.title,
ordered: true
}
end
it 'raises an error' do
expect { subject }.to raise_error(
ArgumentError,
'An index should exist for params: [:title]'
)
end
end
end
context 'without params' do
it 'raises an error' do
expect { subject }.to raise_error(
ArgumentError,
'Please provide one or more of: [:project, :group, :title, :y_label, :identifier, :id, :common, :ordered]'
)
end
end
end
end

View File

@ -7,12 +7,18 @@ import { GlTab, GlTabs } from '@gitlab/ui';
import { createAlert } from '~/alert';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import setWindowLocation from 'helpers/set_window_location_helper';
import PlaceholdersTabApp from '~/members/placeholders/components/app.vue';
import PlaceholdersTable from '~/members/placeholders/components/placeholders_table.vue';
import importSourceUsersQuery from '~/members/placeholders/graphql/queries/import_source_users.query.graphql';
import { MEMBERS_TAB_TYPES } from '~/members/constants';
import { mockSourceUsersQueryResponse, mockSourceUsers, pagination } from '../mock_data';
import {
mockSourceUsersQueryResponse,
mockSourceUsersFailedStatusResponse,
mockSourceUsers,
pagination,
} from '../mock_data';
Vue.use(Vuex);
Vue.use(VueApollo);
@ -140,6 +146,7 @@ describe('PlaceholdersTabApp', () => {
before: null,
fullPath: mockGroup.path,
first: 20,
statuses: [],
});
});
@ -208,4 +215,39 @@ describe('PlaceholdersTabApp', () => {
});
});
});
describe('correctly filters users', () => {
const sourceUsersFailureQueryHandler = jest
.fn()
.mockResolvedValue(mockSourceUsersFailedStatusResponse);
beforeEach(async () => {
setWindowLocation('?status=failed');
createComponent({ queryHandler: sourceUsersFailureQueryHandler });
await waitForPromises();
});
it('when the url includes the query param failed', () => {
const sourceUsersWithFailedStatus =
mockSourceUsersFailedStatusResponse.data.namespace.importSourceUsers;
const tableProps = findPlaceholdersTable().props();
expect(findPlaceholdersTable().props()).toMatchObject({
isLoading: false,
items: sourceUsersWithFailedStatus.nodes,
pageInfo: sourceUsersWithFailedStatus.pageInfo,
});
expect(tableProps.items.length).toBe(1);
expect(tableProps.items[0].status).toBe('FAILED');
expect(sourceUsersFailureQueryHandler).toHaveBeenCalledTimes(1);
expect(sourceUsersFailureQueryHandler).toHaveBeenCalledWith({
after: null,
before: null,
fullPath: mockGroup.path,
first: 20,
statuses: ['FAILED'],
});
});
});
});

View File

@ -84,6 +84,26 @@ export const mockSourceUsersQueryResponse = ({ pageInfo = {} } = {}) => ({
},
});
export const mockSourceUsersFailedStatusResponse = {
data: {
namespace: {
__typename: 'Namespace',
id: 'gid://gitlab/Group/1',
importSourceUsers: {
__typename: 'ImportSourceUserConnection',
nodes: [mockSourceUsers[4]],
pageInfo: {
__typename: 'PageInfo',
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
},
},
},
};
export const mockReassignMutationResponse = {
data: {
importSourceUserReassign: {

View File

@ -8,12 +8,14 @@ const mockTopics = [
{ id: 2, name: 'GitLab', title: 'GitLab', avatarUrl: 'avatar.com/GitLab.png' },
];
const USER_DEFINED_TOKEN = 'user defined token';
describe('TopicsTokenSelector', () => {
let wrapper;
let div;
let input;
const createComponent = (selected) => {
const createComponent = ({ selected, topics = mockTopics } = {}) => {
wrapper = mount(TopicsTokenSelector, {
attachTo: div,
propsData: {
@ -21,7 +23,7 @@ describe('TopicsTokenSelector', () => {
},
data() {
return {
topics: mockTopics,
topics,
};
},
mocks: {
@ -40,6 +42,9 @@ describe('TopicsTokenSelector', () => {
const findAllAvatars = () => wrapper.findAllComponents(GlAvatarLabeled).wrappers;
const findSelectedTokensText = () =>
wrapper.findAllComponents(GlToken).wrappers.map((w) => w.text());
const setTokenSelectorInputValue = (value) => {
const tokenSelectorInput = findTokenSelectorInput();
@ -75,7 +80,7 @@ describe('TopicsTokenSelector', () => {
{ id: 12, name: 'topic2' },
{ id: 13, name: 'topic3' },
];
createComponent(selected);
createComponent({ selected });
await nextTick();
wrapper.findAllComponents(GlToken).wrappers.forEach((tokenWrapper, index) => {
@ -103,4 +108,58 @@ describe('TopicsTokenSelector', () => {
expect(event.preventDefault).toHaveBeenCalled();
});
});
describe('when tokens are added', () => {
it('properly updates selectedTokens and emits `update` with existing token', async () => {
createComponent();
await setTokenSelectorInputValue(mockTopics[0].name);
await tokenSelectorTriggerEnter();
expect(findSelectedTokensText()).toStrictEqual([mockTopics[0].name]);
expect(wrapper.emitted('update')[0][0]).toStrictEqual([mockTopics[0]]);
});
it('properly updates selectedTokens and emits `update` with user defined token', async () => {
createComponent({ topics: [] });
await setTokenSelectorInputValue(USER_DEFINED_TOKEN);
await tokenSelectorTriggerEnter();
expect(findSelectedTokensText()).toStrictEqual([USER_DEFINED_TOKEN]);
expect(wrapper.emitted('update')[0][0]).toStrictEqual([
expect.objectContaining({ name: USER_DEFINED_TOKEN }),
]);
});
it('properly omits duplicate tokens, updates selectedTokens, and emits `update`', async () => {
createComponent({ selected: mockTopics });
await setTokenSelectorInputValue(USER_DEFINED_TOKEN);
await tokenSelectorTriggerEnter();
expect(findSelectedTokensText()).toStrictEqual([
mockTopics[0].name,
mockTopics[1].name,
USER_DEFINED_TOKEN,
]);
expect(wrapper.emitted('update')[0][0]).toStrictEqual([
...mockTopics,
expect.objectContaining({ name: USER_DEFINED_TOKEN }),
]);
await setTokenSelectorInputValue(USER_DEFINED_TOKEN);
await tokenSelectorTriggerEnter();
expect(findSelectedTokensText()).toStrictEqual([
mockTopics[0].name,
mockTopics[1].name,
USER_DEFINED_TOKEN,
]);
expect(wrapper.emitted('update')[0][0]).toStrictEqual([
...mockTopics,
expect.objectContaining({ name: USER_DEFINED_TOKEN }),
]);
});
});
});

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::NullifyOrganizationIdForSnippets, feature_category: :source_code_management do
let(:snippets) { table(:snippets) }
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
let!(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
let!(:personal_snippet) do
snippets.create!(
type: 'PersonalSnippet', author_id: 1, project_id: nil, title: 'Snippet1', organization_id: 1
)
end
let!(:project_snippet_with_organization) do
snippets.create!(
type: 'ProjectSnippet', author_id: 1, project_id: project.id, title: 'Snippet2', organization_id: 1
)
end
let!(:project_snippet_without_organization) do
snippets.create!(
type: 'ProjectSnippet', author_id: 1, project_id: project.id, title: 'Snippet3', organization_id: nil
)
end
let(:migration_attrs) do
{
start_id: snippets.minimum(:id),
end_id: snippets.maximum(:id),
batch_table: :snippets,
batch_column: :id,
sub_batch_size: 2,
pause_ms: 0,
connection: ApplicationRecord.connection
}
end
it 'nullfies organization_id for project snippets' do
expect do
described_class.new(**migration_attrs).perform
end.to change { project_snippet_with_organization.reload.organization_id }.from(1).to(nil)
.and not_change { personal_snippet.reload.organization_id }.from(1)
.and not_change { project_snippet_without_organization.reload.organization_id }.from(nil)
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueNullifyOrganizationIdForSnippets, feature_category: :source_code_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :snippets,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -1368,6 +1368,32 @@ RSpec.describe Group, feature_category: :groups_and_projects do
it { is_expected.to match_array(groups) }
end
describe '.by_min_access_level' do
let_it_be(:user) { create(:user) }
let_it_be(:group1) { create(:group) }
let_it_be(:group2) { create(:group) }
let(:owner_access_level) { Gitlab::Access::OWNER }
let(:developer_access_level) { Gitlab::Access::DEVELOPER }
before do
create(:group_member, user: user, group: group1, access_level: owner_access_level)
create(:group_member, user: user, group: group2, access_level: developer_access_level)
end
it 'returns groups where the user has the specified access level' do
result = described_class.by_min_access_level(user, owner_access_level)
expect(result).to contain_exactly(group1)
end
it 'returns groups if the user has greater or equal specified access level' do
result = described_class.by_min_access_level(user, developer_access_level)
expect(result).to contain_exactly(group1, group2)
end
end
describe 'descendants_with_shared_with_groups' do
subject { described_class.descendants_with_shared_with_groups(parent_group) }

View File

@ -17,7 +17,7 @@ RSpec.describe Packages::Rpm::RepositoryFile, type: :model, feature_category: :p
end
describe '.has_oversized_filelists?' do
let_it_be(:filelists) { create(:rpm_repository_file, :filelists, size: 21.megabytes) }
let!(:filelists) { create(:rpm_repository_file, :filelists, size: 21.megabytes) }
subject { described_class.has_oversized_filelists?(project_id: filelists.project_id) }

View File

@ -1928,7 +1928,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
it_behaves_like 'rate limited endpoint', rate_limit_key: :group_shared_groups_api do
def request
get api("/groups/#{main_group.id}/groups/shared")
get api(path)
end
end
@ -1940,7 +1940,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
it_behaves_like 'unthrottled endpoint'
def request
get api("/groups/#{main_group.id}/groups/shared")
get api(path)
end
end
@ -1973,8 +1973,6 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
let_it_be(:shared_group_1) { create(:group, :public, owners: user1) }
let_it_be(:shared_group_2) { create(:group, :private, owners: user1) }
let(:path) { "/groups/#{main_group.id}/groups/shared" }
before do
create(:group_group_link, shared_group: shared_group_1, shared_with_group: main_group)
create(:group_group_link, shared_group: shared_group_2, shared_with_group: main_group)
@ -1991,6 +1989,70 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
end
end
context "when using skip_groups in request" do
it "returns all shared groups excluding skipped groups", :aggregate_failures do
get api(path, user1), params: { skip_groups: [shared_group1.id] }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.map { |group| group['id'] }).to contain_exactly(shared_group2.id, other_group.id)
end
end
context "when search is present in request" do
let_it_be(:new_shared_group) { create(:group, :public, name: "new search group", owners: user1) }
let_it_be(:other_shared_group) { create(:group, :private, name: "other group", owners: user1) }
before do
create(:group_group_link, shared_group: new_shared_group, shared_with_group: main_group)
create(:group_group_link, shared_group: other_shared_group, shared_with_group: main_group)
end
it 'filters the shared projects in the group based on search params', :aggregate_failures do
get api(path, user1), params: { search: 'new' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(new_shared_group.id)
end
end
context 'when using min_access_level in the request' do
let_it_be(:new_main_group) do
create(:group, :private, owners: user1)
end
let_it_be(:shared_group1) do
create(:group, :private)
end
let_it_be(:shared_group2) do
create(:group, :private)
end
before do
shared_group1.add_developer(user1)
shared_group2.add_reporter(user1)
create(:group_group_link, shared_group: shared_group1, shared_with_group: new_main_group)
create(:group_group_link, shared_group: shared_group2, shared_with_group: new_main_group)
end
context 'with min_access_level parameter' do
it 'returns an array of groups the user has at least reporter access', :aggregate_failures do
get api("/groups/#{new_main_group.id}/groups/shared", user1), params: { min_access_level: Gitlab::Access::REPORTER }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |group| group['id'] }).to contain_exactly(shared_group1.id, shared_group2.id)
end
end
end
context "when using sorting" do
let(:response_groups) { json_response.map { |group| group['name'] } }
let(:response_group_paths) { json_response.map { |group| group['path'] } }
@ -2000,7 +2062,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
let(:shared_group_ids) { [shared_group1.id, shared_group2.id, other_group.id] }
it "sorts by name ascending by default", :aggregate_failures do
get api("/groups/#{main_group.id}/groups/shared", user1)
get api(path, user1)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@ -2010,7 +2072,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
end
it "sorts in descending order when passed", :aggregate_failures do
get api("/groups/#{main_group.id}/groups/shared", user1), params: { sort: "desc" }
get api(path, user1), params: { sort: "desc" }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@ -2020,7 +2082,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
end
it "sorts by path in order_by param", :aggregate_failures do
get api("/groups/#{main_group.id}/groups/shared", user1), params: { order_by: "path" }
get api(path, user1), params: { order_by: "path" }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@ -2029,7 +2091,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
end
it "sorts by id in the order_by param", :aggregate_failures do
get api("/groups/#{main_group.id}/groups/shared", user1), params: { order_by: "id" }
get api(path, user1), params: { order_by: "id" }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@ -2062,8 +2124,8 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response.length).to eq(3)
expect(response_groups).to eq(['same-name shared', 'same-name shared_other', 'other-name'])
expect(json_response.length).to eq(2)
expect(response_groups).to eq(['same-name shared', 'same-name shared_other'])
end
context 'when `search` parameter is not given' do

View File

@ -4,11 +4,39 @@ require 'spec_helper'
RSpec.describe SandboxController, feature_category: :shared do
describe 'GET #mermaid' do
subject(:get_mermaid) { get sandbox_mermaid_path }
it 'renders page without template' do
get sandbox_mermaid_path
get_mermaid
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(layout: nil)
end
context 'with a signed-in user' do
let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
it 'renders page' do
get_mermaid
expect(response).to have_gitlab_http_status(:ok)
end
context 'when enforce_terms setting is enabled' do
before do
stub_application_setting(enforce_terms: true)
end
it 'does not enforce terms for rendering Mermaid markdown' do
get_mermaid
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
end

View File

@ -46,25 +46,6 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_
end
end
context 'when use_lease_for_pat_last_used_update flag is disabled' do
before do
stub_feature_flags(use_lease_for_pat_last_used_update: false)
end
it 'does not obtain an exclusive lease before updating' do
Gitlab::Redis::SharedState.with do |redis|
expect(redis).not_to receive(:set).with(
"#{Gitlab::ExclusiveLease::PREFIX}:pat:last_used_update_lock:#{personal_access_token.id}",
anything,
nx: true,
ex: described_class::LEASE_TIMEOUT
)
end
expect { subject }.to change { personal_access_token.last_used_at }
end
end
context 'when database load balancing is configured' do
let!(:service) { described_class.new(personal_access_token) }

View File

@ -3790,9 +3790,7 @@
- './spec/finders/projects/groups_finder_spec.rb'
- './spec/finders/projects/members/effective_access_level_finder_spec.rb'
- './spec/finders/projects/members/effective_access_level_per_user_finder_spec.rb'
- './spec/finders/projects/prometheus/alerts_finder_spec.rb'
- './spec/finders/projects/topics_finder_spec.rb'
- './spec/finders/prometheus_metrics_finder_spec.rb'
- './spec/finders/protected_branches_finder_spec.rb'
- './spec/finders/releases/evidence_pipeline_finder_spec.rb'
- './spec/finders/releases_finder_spec.rb'