Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
11c99a1a7a
commit
9a7adb84bf
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
c09bb2913ad4fa49a39694a040e65dbe28506611267e499ad3ad4aa464bd9ab0
|
||||
|
|
@ -0,0 +1 @@
|
|||
2f550894b53a31cafe392b25b8927d66279b04bcd44a2315d2f716e7cd975313
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
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 |
|
|
@ -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)'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in New Issue