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
|
.bundler-patterns: &bundler-patterns
|
||||||
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
|
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
|
||||||
|
- '{Gemfile.next.lock,*/Gemfile.next.lock,*/*/Gemfile.next.lock}'
|
||||||
|
|
||||||
.nodejs-patterns: &nodejs-patterns
|
.nodejs-patterns: &nodejs-patterns
|
||||||
- '{package.json,*/package.json,*/*/package.json}'
|
- '{package.json,*/package.json,*/*/package.json}'
|
||||||
|
|
@ -301,6 +302,7 @@
|
||||||
|
|
||||||
.dependency-patterns: &dependency-patterns
|
.dependency-patterns: &dependency-patterns
|
||||||
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
|
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
|
||||||
|
- '{Gemfile.next.lock,*/Gemfile.next.lock,*/*/Gemfile.next.lock}'
|
||||||
- '{go.sum,*/go.sum,*/*/go.sum}'
|
- '{go.sum,*/go.sum,*/*/go.sum}'
|
||||||
- '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
|
- '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
|
||||||
|
|
||||||
|
|
@ -322,6 +324,7 @@
|
||||||
.assets-compilation-patterns: &assets-compilation-patterns
|
.assets-compilation-patterns: &assets-compilation-patterns
|
||||||
- "{package.json,yarn.lock}"
|
- "{package.json,yarn.lock}"
|
||||||
- "{Gemfile,Gemfile.lock}"
|
- "{Gemfile,Gemfile.lock}"
|
||||||
|
- "{Gemfile.next,Gemfile.next.lock}"
|
||||||
- ".browserslistrc"
|
- ".browserslistrc"
|
||||||
- "babel.config.js"
|
- "babel.config.js"
|
||||||
- "config/webpack.config.js"
|
- "config/webpack.config.js"
|
||||||
|
|
@ -352,6 +355,7 @@
|
||||||
- "**/Rakefile"
|
- "**/Rakefile"
|
||||||
- "**/Dangerfile"
|
- "**/Dangerfile"
|
||||||
- "**/Gemfile"
|
- "**/Gemfile"
|
||||||
|
- "**/Gemfile.next"
|
||||||
- "**/Guardfile"
|
- "**/Guardfile"
|
||||||
- "**/*.rake"
|
- "**/*.rake"
|
||||||
- "**/*.rb"
|
- "**/*.rb"
|
||||||
|
|
@ -360,6 +364,7 @@
|
||||||
# Backend patterns + .ci-patterns
|
# Backend patterns + .ci-patterns
|
||||||
.backend-patterns: &backend-patterns
|
.backend-patterns: &backend-patterns
|
||||||
- "{,jh/}Gemfile{,.lock}"
|
- "{,jh/}Gemfile{,.lock}"
|
||||||
|
- "{,jh/}Gemfile.next{,.lock}"
|
||||||
- "Rakefile"
|
- "Rakefile"
|
||||||
- "config.ru"
|
- "config.ru"
|
||||||
- "keeps/**/*"
|
- "keeps/**/*"
|
||||||
|
|
@ -378,6 +383,7 @@
|
||||||
|
|
||||||
.search-backend-patterns: &search-backend-patterns
|
.search-backend-patterns: &search-backend-patterns
|
||||||
- "{,jh/}Gemfile.lock"
|
- "{,jh/}Gemfile.lock"
|
||||||
|
- "{,jh/}Gemfile.next.lock"
|
||||||
- "GITLAB_ELASTICSEARCH_INDEXER_VERSION"
|
- "GITLAB_ELASTICSEARCH_INDEXER_VERSION"
|
||||||
# List explicitly all the app/ dirs that are backend (i.e. all except app/assets).
|
# 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}/**/*"
|
- "{,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"
|
- ".stylelintrc"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
|
- "{,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{,.lock}"
|
||||||
|
- "{,jh/}Gemfile.next{,.lock}"
|
||||||
- "{package.json,yarn.lock}"
|
- "{package.json,yarn.lock}"
|
||||||
- "*_VERSION"
|
- "*_VERSION"
|
||||||
- "lib/gitlab/redis/*"
|
- "lib/gitlab/redis/*"
|
||||||
|
|
@ -497,6 +504,7 @@
|
||||||
- ".stylelintrc"
|
- ".stylelintrc"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
|
- "{,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{,.lock}"
|
||||||
|
- "{,jh/}Gemfile.next{,.lock}"
|
||||||
- "{package.json,yarn.lock}"
|
- "{package.json,yarn.lock}"
|
||||||
- "*_VERSION"
|
- "*_VERSION"
|
||||||
- "babel.config.js"
|
- "babel.config.js"
|
||||||
|
|
@ -531,6 +539,7 @@
|
||||||
- ".stylelintrc"
|
- ".stylelintrc"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
|
- "{,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{,.lock}"
|
||||||
|
- "{,jh/}Gemfile.next{,.lock}"
|
||||||
- "{package.json,yarn.lock}"
|
- "{package.json,yarn.lock}"
|
||||||
- "*_VERSION"
|
- "*_VERSION"
|
||||||
- "babel.config.js"
|
- "babel.config.js"
|
||||||
|
|
@ -560,6 +569,7 @@
|
||||||
- ".stylelintrc"
|
- ".stylelintrc"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,elastic,generator_templates,gems,haml_lint,lib,locale,public,scripts,sidekiq_cluster,storybook,symbol,vendor}/**/*"
|
- "{,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{,.lock}"
|
||||||
|
- "{,jh/}Gemfile.next{,.lock}"
|
||||||
- "{package.json,yarn.lock}"
|
- "{package.json,yarn.lock}"
|
||||||
- "*_VERSION"
|
- "*_VERSION"
|
||||||
- "babel.config.js"
|
- "babel.config.js"
|
||||||
|
|
@ -605,6 +615,7 @@
|
||||||
- ".{eslintrc.yml,eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
|
- ".{eslintrc.yml,eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
|
||||||
- "*_VERSION"
|
- "*_VERSION"
|
||||||
- "{,jh/}Gemfile{,.lock}"
|
- "{,jh/}Gemfile{,.lock}"
|
||||||
|
- "{,jh/}Gemfile.next{,.lock}"
|
||||||
- "keeps/**/*"
|
- "keeps/**/*"
|
||||||
- "Rakefile"
|
- "Rakefile"
|
||||||
- "tests.yml"
|
- "tests.yml"
|
||||||
|
|
@ -653,6 +664,7 @@
|
||||||
- ".rubocop_todo/**/*.yml"
|
- ".rubocop_todo/**/*.yml"
|
||||||
- "{,ee/,jh/}rubocop/**/*" # We might be changing custom cops
|
- "{,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.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
|
- "lib/gitlab_edition.rb" # This is required in RuboCop::CodeReuseHelpers
|
||||||
- ".gitlab/ci/static-analysis.gitlab-ci.yml"
|
- ".gitlab/ci/static-analysis.gitlab-ci.yml"
|
||||||
- "config/feature_categories.yml" # Used by RSpec/FeatureCategory
|
- "config/feature_categories.yml" # Used by RSpec/FeatureCategory
|
||||||
|
|
@ -664,6 +676,7 @@
|
||||||
|
|
||||||
.core-backend-patterns: &core-backend-patterns
|
.core-backend-patterns: &core-backend-patterns
|
||||||
- "{,jh/}Gemfile{,.lock}"
|
- "{,jh/}Gemfile{,.lock}"
|
||||||
|
- "{,jh/}Gemfile.next{,.lock}"
|
||||||
- "{,ee/,jh/}config/**/*.rb"
|
- "{,ee/,jh/}config/**/*.rb"
|
||||||
|
|
||||||
.core-frontend-patterns: &core-frontend-patterns
|
.core-frontend-patterns: &core-frontend-patterns
|
||||||
|
|
@ -685,6 +698,7 @@
|
||||||
.gdk-component-patterns: &gdk-component-patterns
|
.gdk-component-patterns: &gdk-component-patterns
|
||||||
- qa/gdk/**/*
|
- qa/gdk/**/*
|
||||||
- Gemfile.lock
|
- Gemfile.lock
|
||||||
|
- Gemfile.next.lock
|
||||||
- yarn.lock
|
- yarn.lock
|
||||||
- scripts/build_gdk_image
|
- scripts/build_gdk_image
|
||||||
- scripts/frontend/postinstall.js
|
- scripts/frontend/postinstall.js
|
||||||
|
|
@ -2737,7 +2751,7 @@
|
||||||
when: never
|
when: never
|
||||||
- <<: *if-schedule-maintenance
|
- <<: *if-schedule-maintenance
|
||||||
- <<: *if-merge-request
|
- <<: *if-merge-request
|
||||||
changes: ["Gemfile.lock"]
|
changes: ["Gemfile.lock", "Gemfile.next.lock"]
|
||||||
|
|
||||||
.reports:rules:x-ray:
|
.reports:rules:x-ray:
|
||||||
rules:
|
rules:
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,6 @@ Gitlab/BoundedContexts:
|
||||||
- 'app/finders/personal_access_tokens_finder.rb'
|
- 'app/finders/personal_access_tokens_finder.rb'
|
||||||
- 'app/finders/personal_projects_finder.rb'
|
- 'app/finders/personal_projects_finder.rb'
|
||||||
- 'app/finders/projects_finder.rb'
|
- 'app/finders/projects_finder.rb'
|
||||||
- 'app/finders/prometheus_metrics_finder.rb'
|
|
||||||
- 'app/finders/protected_branches_finder.rb'
|
- 'app/finders/protected_branches_finder.rb'
|
||||||
- 'app/finders/releases_finder.rb'
|
- 'app/finders/releases_finder.rb'
|
||||||
- 'app/finders/resource_milestone_event_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_access_tokens_finder.rb'
|
||||||
- 'app/finders/personal_projects_finder.rb'
|
- 'app/finders/personal_projects_finder.rb'
|
||||||
- 'app/finders/projects_finder.rb'
|
- 'app/finders/projects_finder.rb'
|
||||||
- 'app/finders/prometheus_metrics_finder.rb'
|
|
||||||
- 'app/finders/protected_branches_finder.rb'
|
- 'app/finders/protected_branches_finder.rb'
|
||||||
- 'app/finders/releases_finder.rb'
|
- 'app/finders/releases_finder.rb'
|
||||||
- 'app/finders/resource_milestone_event_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_access_tokens_finder_spec.rb'
|
||||||
- 'spec/finders/personal_projects_finder_spec.rb'
|
- 'spec/finders/personal_projects_finder_spec.rb'
|
||||||
- 'spec/finders/projects/groups_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/projects_finder_spec.rb'
|
||||||
- 'spec/finders/repositories/tree_finder_spec.rb'
|
- 'spec/finders/repositories/tree_finder_spec.rb'
|
||||||
- 'spec/finders/security/security_jobs_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/projects/show/user_sees_setup_shortcut_buttons_spec.rb'
|
||||||
- 'spec/features/snippets/embedded_snippet_spec.rb'
|
- 'spec/features/snippets/embedded_snippet_spec.rb'
|
||||||
- 'spec/features/usage_stats_consent_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/create_alert_issue_spec.rb'
|
||||||
- 'spec/graphql/mutations/alert_management/http_integration/create_spec.rb'
|
- 'spec/graphql/mutations/alert_management/http_integration/create_spec.rb'
|
||||||
- 'spec/graphql/mutations/alert_management/http_integration/destroy_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/groups_finder_spec.rb'
|
||||||
- 'spec/finders/projects/members/effective_access_level_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/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/projects/topics_finder_spec.rb'
|
||||||
- 'spec/finders/prometheus_metrics_finder_spec.rb'
|
|
||||||
- 'spec/finders/protected_branches_finder_spec.rb'
|
- 'spec/finders/protected_branches_finder_spec.rb'
|
||||||
- 'spec/finders/releases/evidence_pipeline_finder_spec.rb'
|
- 'spec/finders/releases/evidence_pipeline_finder_spec.rb'
|
||||||
- 'spec/finders/resource_milestone_event_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/members/effective_access_level_per_user_finder_spec.rb'
|
||||||
- 'spec/finders/projects/ml/candidate_finder_spec.rb'
|
- 'spec/finders/projects/ml/candidate_finder_spec.rb'
|
||||||
- 'spec/finders/projects/ml/model_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/evidence_pipeline_finder_spec.rb'
|
||||||
- 'spec/finders/releases_finder_spec.rb'
|
- 'spec/finders/releases_finder_spec.rb'
|
||||||
- 'spec/finders/repositories/tree_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 ROOT_NAMESPACE = { fullPath: '', id: null };
|
||||||
|
|
||||||
|
export const QUERY_PARAM_FAILED = 'failed';
|
||||||
|
|
||||||
const PLACEHOLDER_STATUS_PENDING_REASSIGNMENT = 'PENDING_REASSIGNMENT';
|
const PLACEHOLDER_STATUS_PENDING_REASSIGNMENT = 'PENDING_REASSIGNMENT';
|
||||||
export const PLACEHOLDER_STATUS_AWAITING_APPROVAL = 'AWAITING_APPROVAL';
|
export const PLACEHOLDER_STATUS_AWAITING_APPROVAL = 'AWAITING_APPROVAL';
|
||||||
const PLACEHOLDER_STATUS_REJECTED = 'REJECTED';
|
const PLACEHOLDER_STATUS_REJECTED = 'REJECTED';
|
||||||
export const PLACEHOLDER_STATUS_REASSIGNING = 'REASSIGNMENT_IN_PROGRESS';
|
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_KEPT_AS_PLACEHOLDER = 'KEEP_AS_PLACEHOLDER';
|
||||||
export const PLACEHOLDER_STATUS_COMPLETED = 'COMPLETED';
|
export const PLACEHOLDER_STATUS_COMPLETED = 'COMPLETED';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@ import { mapState } from 'vuex';
|
||||||
import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
|
import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
|
||||||
import { createAlert } from '~/alert';
|
import { createAlert } from '~/alert';
|
||||||
import { s__, sprintf } from '~/locale';
|
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 importSourceUsersQuery from '../graphql/queries/import_source_users.query.graphql';
|
||||||
import PlaceholdersTable from './placeholders_table.vue';
|
import PlaceholdersTable from './placeholders_table.vue';
|
||||||
|
|
@ -40,6 +45,7 @@ export default {
|
||||||
fullPath: this.group.path,
|
fullPath: this.group.path,
|
||||||
...this.cursor,
|
...this.cursor,
|
||||||
[this.cursor.before ? 'last' : 'first']: DEFAULT_PAGE_SIZE,
|
[this.cursor.before ? 'last' : 'first']: DEFAULT_PAGE_SIZE,
|
||||||
|
statuses: this.queryStatuses,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
update(data) {
|
update(data) {
|
||||||
|
|
@ -64,6 +70,16 @@ export default {
|
||||||
pageInfo() {
|
pageInfo() {
|
||||||
return this.sourceUsers?.pageInfo || {};
|
return this.sourceUsers?.pageInfo || {};
|
||||||
},
|
},
|
||||||
|
statusParamValue() {
|
||||||
|
return getParameterByName('status');
|
||||||
|
},
|
||||||
|
queryStatuses() {
|
||||||
|
if (getParameterByName('status') === QUERY_PARAM_FAILED) {
|
||||||
|
return [PLACEHOLDER_STATUS_FAILED];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
|
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
|
||||||
#import "../fragments/import_source_user.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) {
|
namespace(fullPath: $fullPath) {
|
||||||
id
|
id
|
||||||
importSourceUsers(before: $before, after: $after, first: $first, last: $last) {
|
importSourceUsers(
|
||||||
|
before: $before
|
||||||
|
after: $after
|
||||||
|
first: $first
|
||||||
|
last: $last
|
||||||
|
statuses: $statuses
|
||||||
|
) {
|
||||||
nodes {
|
nodes {
|
||||||
...ImportSourceUser
|
...ImportSourceUser
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { escape } from 'lodash';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||||
|
import { InternalEvents } from '~/tracking';
|
||||||
|
|
||||||
|
const trackingMixin = InternalEvents.mixin();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -12,6 +15,7 @@ export default {
|
||||||
directives: {
|
directives: {
|
||||||
SafeHtml,
|
SafeHtml,
|
||||||
},
|
},
|
||||||
|
mixins: [trackingMixin],
|
||||||
props: {
|
props: {
|
||||||
templates: {
|
templates: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|
@ -54,6 +58,7 @@ export default {
|
||||||
},
|
},
|
||||||
async selectTemplate(templatePath) {
|
async selectTemplate(templatePath) {
|
||||||
const template = await axios.get(templatePath);
|
const template = await axios.get(templatePath);
|
||||||
|
this.trackEvent('apply_wiki_template');
|
||||||
this.$emit('input', template.data);
|
this.$emit('input', template.data);
|
||||||
},
|
},
|
||||||
highlight(text) {
|
highlight(text) {
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,11 @@ export default {
|
||||||
this.search = searchTerm;
|
this.search = searchTerm;
|
||||||
},
|
},
|
||||||
onTokensUpdate(tokens) {
|
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,
|
AVATAR_SHAPE_OPTION_RECT,
|
||||||
|
|
@ -88,6 +92,7 @@ export default {
|
||||||
:dropdown-items="topics"
|
:dropdown-items="topics"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
allow-user-defined-tokens
|
allow-user-defined-tokens
|
||||||
|
show-add-new-always
|
||||||
:placeholder="placeholderText"
|
:placeholder="placeholderText"
|
||||||
@keydown.enter="handleEnter"
|
@keydown.enter="handleEnter"
|
||||||
@text-input="filterTopics"
|
@text-input="filterTopics"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
class SandboxController < ApplicationController # rubocop:disable Gitlab/NamespacedClass
|
class SandboxController < ApplicationController # rubocop:disable Gitlab/NamespacedClass
|
||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
|
skip_before_action :enforce_terms!
|
||||||
|
|
||||||
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
|
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.
|
# public groups instead, even if `all_available` is set to false.
|
||||||
class GroupsFinder < UnionFinder
|
class GroupsFinder < UnionFinder
|
||||||
include CustomAttributesFilter
|
include CustomAttributesFilter
|
||||||
|
include Namespaces::GroupsFilter
|
||||||
|
|
||||||
attr_reader :current_user, :params
|
attr_reader :current_user, :params
|
||||||
|
|
||||||
|
|
@ -115,12 +116,6 @@ class GroupsFinder < UnionFinder
|
||||||
groups.in_organization(organization)
|
groups.in_organization(organization)
|
||||||
end
|
end
|
||||||
|
|
||||||
def by_visibility(groups)
|
|
||||||
return groups unless params[:visibility]
|
|
||||||
|
|
||||||
groups.by_visibility_level(params[:visibility])
|
|
||||||
end
|
|
||||||
|
|
||||||
def by_parent(groups)
|
def by_parent(groups)
|
||||||
return groups unless params[:parent]
|
return groups unless params[:parent]
|
||||||
|
|
||||||
|
|
@ -155,18 +150,6 @@ class GroupsFinder < UnionFinder
|
||||||
groups.id_not_in(params[:exclude_group_ids])
|
groups.id_not_in(params[:exclude_group_ids])
|
||||||
end
|
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?
|
def include_parent_shared_groups?
|
||||||
params.fetch(:include_parent_shared_groups, false)
|
params.fetch(:include_parent_shared_groups, false)
|
||||||
end
|
end
|
||||||
|
|
@ -175,10 +158,6 @@ class GroupsFinder < UnionFinder
|
||||||
params.fetch(:include_parent_descendants, false)
|
params.fetch(:include_parent_descendants, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
def min_access_level?
|
|
||||||
current_user && params[:min_access_level].present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def include_public_groups?
|
def include_public_groups?
|
||||||
current_user.nil? || all_available?
|
current_user.nil? || all_available?
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
#
|
#
|
||||||
module Namespaces
|
module Namespaces
|
||||||
module Groups
|
module Groups
|
||||||
class SharedGroupsFinder < GroupsFinder
|
class SharedGroupsFinder
|
||||||
|
include Namespaces::GroupsFilter
|
||||||
include Gitlab::Allowable
|
include Gitlab::Allowable
|
||||||
|
|
||||||
attr_reader :group, :current_user, :params
|
attr_reader :group, :current_user, :params
|
||||||
|
|
@ -35,6 +36,15 @@ module Namespaces
|
||||||
|
|
||||||
def filter_shared_groups(groups)
|
def filter_shared_groups(groups)
|
||||||
by_visibility(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
|
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_asc, -> { reorder(self.arel_table['path'].asc) }
|
||||||
scope :order_path_desc, -> { reorder(self.arel_table['path'].desc) }
|
scope :order_path_desc, -> { reorder(self.arel_table['path'].desc) }
|
||||||
scope :in_organization, ->(organization) { where(organization: organization) }
|
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
|
class << self
|
||||||
def sort_by_attribute(method)
|
def sort_by_attribute(method)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ module PersonalAccessTokens
|
||||||
# would be updated when using #touch).
|
# would be updated when using #touch).
|
||||||
return unless update?
|
return unless update?
|
||||||
|
|
||||||
with_lease do
|
try_obtain_lease do
|
||||||
::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do
|
::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do
|
||||||
@personal_access_token.update_column(:last_used_at, Time.zone.now)
|
@personal_access_token.update_column(:last_used_at, Time.zone.now)
|
||||||
end
|
end
|
||||||
|
|
@ -35,18 +35,6 @@ module PersonalAccessTokens
|
||||||
@lease_key ||= "pat:last_used_update_lock:#{@personal_access_token.id}"
|
@lease_key ||= "pat:last_used_update_lock:#{@personal_access_token.id}"
|
||||||
end
|
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?
|
def update?
|
||||||
return false if ::Gitlab::Database.read_only?
|
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
|
- table: p_ci_builds
|
||||||
column: build_id
|
column: build_id
|
||||||
on_delete: async_delete
|
on_delete: async_delete
|
||||||
|
security_trainings:
|
||||||
|
- table: projects
|
||||||
|
column: project_id
|
||||||
|
on_delete: async_delete
|
||||||
snippets:
|
snippets:
|
||||||
- table: organizations
|
- table: organizations
|
||||||
column: organization_id
|
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
|
description: Stores information about the available security training providers
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78195
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78195
|
||||||
milestone: '14.7'
|
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
|
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
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78195
|
||||||
milestone: '14.7'
|
milestone: '14.7'
|
||||||
gitlab_schema: gitlab_main_cell
|
gitlab_schema: gitlab_sec
|
||||||
allow_cross_foreign_keys:
|
allow_cross_foreign_keys:
|
||||||
- gitlab_main_clusterwide
|
- gitlab_main_clusterwide
|
||||||
sharding_key:
|
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
|
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;
|
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
|
ALTER TABLE ONLY merge_requests_closing_issues
|
||||||
ADD CONSTRAINT fk_rails_f8540692be FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
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
|
## 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).
|
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 |
|
| 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 |
|
| `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 |
|
| `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` |
|
| `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` |
|
| `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. |
|
| `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) |
|
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (administrators only) |
|
||||||
|
|
||||||
```plaintext
|
```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.
|
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)
|
- [Error Tracking](error_tracking.md)
|
||||||
- [Distributed tracing](tracing.md)
|
- [Distributed tracing](tracing.md)
|
||||||
- [Metrics](metrics.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 |
|
| 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
|
### 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_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 |
|
| [`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 |
|
| [`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 |
|
| [`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_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 |
|
| [`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 |
|
| 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 |
|
| [`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 |
|
| [`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 |
|
| [`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 |
|
| [`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]
|
tags %w[groups]
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
|
optional :skip_groups, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of group ids to exclude from list'
|
||||||
desc: 'Limit by visibility'
|
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 :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)'
|
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/lib/ee/gitlab/background_migration/.rubocop.yml',
|
||||||
'ee/LICENSE',
|
'ee/LICENSE',
|
||||||
'Gemfile.checksum',
|
'Gemfile.checksum',
|
||||||
|
'Gemfile.next.checksum',
|
||||||
'gems/error_tracking_open_api/.openapi-generator/FILES',
|
'gems/error_tracking_open_api/.openapi-generator/FILES',
|
||||||
'gems/error_tracking_open_api/.openapi-generator/VERSION',
|
'gems/error_tracking_open_api/.openapi-generator/VERSION',
|
||||||
'gems/openbao_client/.openapi-generator/FILES',
|
'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(:another_user) { create(:user) }
|
||||||
let_it_be(:current_user) { user }
|
let_it_be(:current_user) { user }
|
||||||
let_it_be(:group) { create(:group, :private, owners: user, name: "group") }
|
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(:shared_group) { create(:group, :private, name: "shared group") }
|
||||||
let_it_be(:other_group) { create(:group, :private, name: "a#{group.name}") }
|
let_it_be(:other_group) { create(:group, :public, name: "other group") }
|
||||||
|
|
||||||
let(:params) { {} }
|
let(:params) { {} }
|
||||||
|
|
||||||
|
|
@ -35,5 +35,34 @@ RSpec.describe Namespaces::Groups::SharedGroupsFinder, feature_category: :groups
|
||||||
expect(results).to be_empty
|
expect(results).to be_empty
|
||||||
end
|
end
|
||||||
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
|
||||||
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 { createAlert } from '~/alert';
|
||||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||||
|
|
||||||
import PlaceholdersTabApp from '~/members/placeholders/components/app.vue';
|
import PlaceholdersTabApp from '~/members/placeholders/components/app.vue';
|
||||||
import PlaceholdersTable from '~/members/placeholders/components/placeholders_table.vue';
|
import PlaceholdersTable from '~/members/placeholders/components/placeholders_table.vue';
|
||||||
import importSourceUsersQuery from '~/members/placeholders/graphql/queries/import_source_users.query.graphql';
|
import importSourceUsersQuery from '~/members/placeholders/graphql/queries/import_source_users.query.graphql';
|
||||||
import { MEMBERS_TAB_TYPES } from '~/members/constants';
|
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(Vuex);
|
||||||
Vue.use(VueApollo);
|
Vue.use(VueApollo);
|
||||||
|
|
@ -140,6 +146,7 @@ describe('PlaceholdersTabApp', () => {
|
||||||
before: null,
|
before: null,
|
||||||
fullPath: mockGroup.path,
|
fullPath: mockGroup.path,
|
||||||
first: 20,
|
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 = {
|
export const mockReassignMutationResponse = {
|
||||||
data: {
|
data: {
|
||||||
importSourceUserReassign: {
|
importSourceUserReassign: {
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,14 @@ const mockTopics = [
|
||||||
{ id: 2, name: 'GitLab', title: 'GitLab', avatarUrl: 'avatar.com/GitLab.png' },
|
{ id: 2, name: 'GitLab', title: 'GitLab', avatarUrl: 'avatar.com/GitLab.png' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const USER_DEFINED_TOKEN = 'user defined token';
|
||||||
|
|
||||||
describe('TopicsTokenSelector', () => {
|
describe('TopicsTokenSelector', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let div;
|
let div;
|
||||||
let input;
|
let input;
|
||||||
|
|
||||||
const createComponent = (selected) => {
|
const createComponent = ({ selected, topics = mockTopics } = {}) => {
|
||||||
wrapper = mount(TopicsTokenSelector, {
|
wrapper = mount(TopicsTokenSelector, {
|
||||||
attachTo: div,
|
attachTo: div,
|
||||||
propsData: {
|
propsData: {
|
||||||
|
|
@ -21,7 +23,7 @@ describe('TopicsTokenSelector', () => {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
topics: mockTopics,
|
topics,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mocks: {
|
mocks: {
|
||||||
|
|
@ -40,6 +42,9 @@ describe('TopicsTokenSelector', () => {
|
||||||
|
|
||||||
const findAllAvatars = () => wrapper.findAllComponents(GlAvatarLabeled).wrappers;
|
const findAllAvatars = () => wrapper.findAllComponents(GlAvatarLabeled).wrappers;
|
||||||
|
|
||||||
|
const findSelectedTokensText = () =>
|
||||||
|
wrapper.findAllComponents(GlToken).wrappers.map((w) => w.text());
|
||||||
|
|
||||||
const setTokenSelectorInputValue = (value) => {
|
const setTokenSelectorInputValue = (value) => {
|
||||||
const tokenSelectorInput = findTokenSelectorInput();
|
const tokenSelectorInput = findTokenSelectorInput();
|
||||||
|
|
||||||
|
|
@ -75,7 +80,7 @@ describe('TopicsTokenSelector', () => {
|
||||||
{ id: 12, name: 'topic2' },
|
{ id: 12, name: 'topic2' },
|
||||||
{ id: 13, name: 'topic3' },
|
{ id: 13, name: 'topic3' },
|
||||||
];
|
];
|
||||||
createComponent(selected);
|
createComponent({ selected });
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
wrapper.findAllComponents(GlToken).wrappers.forEach((tokenWrapper, index) => {
|
wrapper.findAllComponents(GlToken).wrappers.forEach((tokenWrapper, index) => {
|
||||||
|
|
@ -103,4 +108,58 @@ describe('TopicsTokenSelector', () => {
|
||||||
expect(event.preventDefault).toHaveBeenCalled();
|
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) }
|
it { is_expected.to match_array(groups) }
|
||||||
end
|
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
|
describe 'descendants_with_shared_with_groups' do
|
||||||
subject { described_class.descendants_with_shared_with_groups(parent_group) }
|
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
|
end
|
||||||
|
|
||||||
describe '.has_oversized_filelists?' do
|
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) }
|
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
|
it_behaves_like 'rate limited endpoint', rate_limit_key: :group_shared_groups_api do
|
||||||
def request
|
def request
|
||||||
get api("/groups/#{main_group.id}/groups/shared")
|
get api(path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1940,7 +1940,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
||||||
it_behaves_like 'unthrottled endpoint'
|
it_behaves_like 'unthrottled endpoint'
|
||||||
|
|
||||||
def request
|
def request
|
||||||
get api("/groups/#{main_group.id}/groups/shared")
|
get api(path)
|
||||||
end
|
end
|
||||||
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_1) { create(:group, :public, owners: user1) }
|
||||||
let_it_be(:shared_group_2) { create(:group, :private, owners: user1) }
|
let_it_be(:shared_group_2) { create(:group, :private, owners: user1) }
|
||||||
|
|
||||||
let(:path) { "/groups/#{main_group.id}/groups/shared" }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
create(:group_group_link, shared_group: shared_group_1, shared_with_group: main_group)
|
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)
|
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
|
||||||
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
|
context "when using sorting" do
|
||||||
let(:response_groups) { json_response.map { |group| group['name'] } }
|
let(:response_groups) { json_response.map { |group| group['name'] } }
|
||||||
let(:response_group_paths) { json_response.map { |group| group['path'] } }
|
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] }
|
let(:shared_group_ids) { [shared_group1.id, shared_group2.id, other_group.id] }
|
||||||
|
|
||||||
it "sorts by name ascending by default", :aggregate_failures do
|
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 have_gitlab_http_status(:ok)
|
||||||
expect(response).to include_pagination_headers
|
expect(response).to include_pagination_headers
|
||||||
|
|
@ -2010,7 +2072,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sorts in descending order when passed", :aggregate_failures do
|
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 have_gitlab_http_status(:ok)
|
||||||
expect(response).to include_pagination_headers
|
expect(response).to include_pagination_headers
|
||||||
|
|
@ -2020,7 +2082,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sorts by path in order_by param", :aggregate_failures do
|
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 have_gitlab_http_status(:ok)
|
||||||
expect(response).to include_pagination_headers
|
expect(response).to include_pagination_headers
|
||||||
|
|
@ -2029,7 +2091,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sorts by id in the order_by param", :aggregate_failures do
|
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 have_gitlab_http_status(:ok)
|
||||||
expect(response).to include_pagination_headers
|
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 have_gitlab_http_status(:ok)
|
||||||
expect(response).to include_pagination_headers
|
expect(response).to include_pagination_headers
|
||||||
expect(json_response.length).to eq(3)
|
expect(json_response.length).to eq(2)
|
||||||
expect(response_groups).to eq(['same-name shared', 'same-name shared_other', 'other-name'])
|
expect(response_groups).to eq(['same-name shared', 'same-name shared_other'])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `search` parameter is not given' do
|
context 'when `search` parameter is not given' do
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,39 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe SandboxController, feature_category: :shared do
|
RSpec.describe SandboxController, feature_category: :shared do
|
||||||
describe 'GET #mermaid' do
|
describe 'GET #mermaid' do
|
||||||
|
subject(:get_mermaid) { get sandbox_mermaid_path }
|
||||||
|
|
||||||
it 'renders page without template' do
|
it 'renders page without template' do
|
||||||
get sandbox_mermaid_path
|
get_mermaid
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(response).to render_template(layout: nil)
|
expect(response).to render_template(layout: nil)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -46,25 +46,6 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_
|
||||||
end
|
end
|
||||||
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
|
context 'when database load balancing is configured' do
|
||||||
let!(:service) { described_class.new(personal_access_token) }
|
let!(:service) { described_class.new(personal_access_token) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3790,9 +3790,7 @@
|
||||||
- './spec/finders/projects/groups_finder_spec.rb'
|
- './spec/finders/projects/groups_finder_spec.rb'
|
||||||
- './spec/finders/projects/members/effective_access_level_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/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/projects/topics_finder_spec.rb'
|
||||||
- './spec/finders/prometheus_metrics_finder_spec.rb'
|
|
||||||
- './spec/finders/protected_branches_finder_spec.rb'
|
- './spec/finders/protected_branches_finder_spec.rb'
|
||||||
- './spec/finders/releases/evidence_pipeline_finder_spec.rb'
|
- './spec/finders/releases/evidence_pipeline_finder_spec.rb'
|
||||||
- './spec/finders/releases_finder_spec.rb'
|
- './spec/finders/releases_finder_spec.rb'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue