Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
14b5bf2629
commit
07e0fae35c
|
|
@ -393,8 +393,7 @@ db:migrate-from-previous-major-version:
|
|||
- sed -i -e "s/gem 'google-protobuf', '~> 3.8.0'/gem 'google-protobuf', '~> 3.12'/" Gemfile
|
||||
- sed -i -e "s/gem 'nokogiri', '~> 1.10.5'/gem 'nokogiri', '~> 1.11.0'/" Gemfile
|
||||
- sed -i -e "s/gem 'mimemagic', '~> 0.3.2'/gem 'ruby-magic', '~> 0.4.0'/" Gemfile
|
||||
- run_timed_command "gem install bundler:1.17.3"
|
||||
- run_timed_command "bundle update google-protobuf nokogiri grpc mimemagic bootsnap"
|
||||
- run_timed_command "bundle update --bundler google-protobuf nokogiri grpc mimemagic bootsnap"
|
||||
- SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh
|
||||
- run_timed_command "bundle exec rake db:drop db:create db:structure:load db:migrate db:seed_fu"
|
||||
- git checkout -f $CI_COMMIT_SHA
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { GlAlert } from '@gitlab/ui';
|
||||
import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2';
|
||||
import { ContentEditor } from '../services/content_editor';
|
||||
import FormattingBubbleMenu from './formatting_bubble_menu.vue';
|
||||
import TopToolbar from './top_toolbar.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -9,6 +10,7 @@ export default {
|
|||
GlAlert,
|
||||
TiptapEditorContent,
|
||||
TopToolbar,
|
||||
FormattingBubbleMenu,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
|
|
@ -44,6 +46,7 @@ export default {
|
|||
:class="{ 'is-focused': contentEditor.tiptapEditor.isFocused }"
|
||||
>
|
||||
<top-toolbar ref="toolbar" class="gl-mb-4" />
|
||||
<formatting-bubble-menu />
|
||||
<tiptap-editor-content class="md" :editor="contentEditor.tiptapEditor" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<script>
|
||||
import { GlButtonGroup } from '@gitlab/ui';
|
||||
import { BubbleMenu } from '@tiptap/vue-2';
|
||||
import { BUBBLE_MENU_TRACKING_ACTION } from '../constants';
|
||||
import trackUIControl from '../services/track_ui_control';
|
||||
import ToolbarButton from './toolbar_button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BubbleMenu,
|
||||
GlButtonGroup,
|
||||
ToolbarButton,
|
||||
},
|
||||
inject: ['tiptapEditor'],
|
||||
methods: {
|
||||
trackToolbarControlExecution({ contentType, value }) {
|
||||
trackUIControl({ action: BUBBLE_MENU_TRACKING_ACTION, property: contentType, value });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<bubble-menu class="gl-shadow gl-rounded-base" :editor="tiptapEditor">
|
||||
<gl-button-group>
|
||||
<toolbar-button
|
||||
data-testid="bold"
|
||||
content-type="bold"
|
||||
icon-name="bold"
|
||||
editor-command="toggleBold"
|
||||
category="primary"
|
||||
size="medium"
|
||||
:label="__('Bold text')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
/>
|
||||
<toolbar-button
|
||||
data-testid="italic"
|
||||
content-type="italic"
|
||||
icon-name="italic"
|
||||
editor-command="toggleItalic"
|
||||
category="primary"
|
||||
size="medium"
|
||||
:label="__('Italic text')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
/>
|
||||
<toolbar-button
|
||||
data-testid="strike"
|
||||
content-type="strike"
|
||||
icon-name="strikethrough"
|
||||
editor-command="toggleStrike"
|
||||
category="primary"
|
||||
size="medium"
|
||||
:label="__('Strikethrough')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
/>
|
||||
<toolbar-button
|
||||
data-testid="code"
|
||||
content-type="code"
|
||||
icon-name="code"
|
||||
editor-command="toggleCode"
|
||||
category="primary"
|
||||
size="medium"
|
||||
:label="__('Code')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
/>
|
||||
</gl-button-group>
|
||||
</bubble-menu>
|
||||
</template>
|
||||
|
|
@ -29,6 +29,21 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'default',
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'tertiary',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'small',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -55,9 +70,9 @@ export default {
|
|||
<editor-state-observer @transaction="updateActive">
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
category="tertiary"
|
||||
size="small"
|
||||
class="gl-mx-2"
|
||||
:variant="variant"
|
||||
:category="category"
|
||||
:size="size"
|
||||
:class="{ active: isActive }"
|
||||
:aria-label="label"
|
||||
:title="label"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import Tracking from '~/tracking';
|
||||
import { CONTENT_EDITOR_TRACKING_LABEL, TOOLBAR_CONTROL_TRACKING_ACTION } from '../constants';
|
||||
import trackUIControl from '../services/track_ui_control';
|
||||
import Divider from './divider.vue';
|
||||
import ToolbarButton from './toolbar_button.vue';
|
||||
import ToolbarImageButton from './toolbar_image_button.vue';
|
||||
|
|
@ -8,10 +7,6 @@ import ToolbarLinkButton from './toolbar_link_button.vue';
|
|||
import ToolbarTableButton from './toolbar_table_button.vue';
|
||||
import ToolbarTextStyleDropdown from './toolbar_text_style_dropdown.vue';
|
||||
|
||||
const trackingMixin = Tracking.mixin({
|
||||
label: CONTENT_EDITOR_TRACKING_LABEL,
|
||||
});
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ToolbarButton,
|
||||
|
|
@ -21,13 +16,9 @@ export default {
|
|||
ToolbarImageButton,
|
||||
Divider,
|
||||
},
|
||||
mixins: [trackingMixin],
|
||||
methods: {
|
||||
trackToolbarControlExecution({ contentType: property, value }) {
|
||||
this.track(TOOLBAR_CONTROL_TRACKING_ACTION, {
|
||||
property,
|
||||
value,
|
||||
});
|
||||
trackToolbarControlExecution({ contentType, value }) {
|
||||
trackUIControl({ property: contentType, value });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -45,6 +36,7 @@ export default {
|
|||
data-testid="bold"
|
||||
content-type="bold"
|
||||
icon-name="bold"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleBold"
|
||||
:label="__('Bold text')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -53,6 +45,7 @@ export default {
|
|||
data-testid="italic"
|
||||
content-type="italic"
|
||||
icon-name="italic"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleItalic"
|
||||
:label="__('Italic text')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -61,6 +54,7 @@ export default {
|
|||
data-testid="strike"
|
||||
content-type="strike"
|
||||
icon-name="strikethrough"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleStrike"
|
||||
:label="__('Strikethrough')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -69,6 +63,7 @@ export default {
|
|||
data-testid="code"
|
||||
content-type="code"
|
||||
icon-name="code"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleCode"
|
||||
:label="__('Code')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -84,6 +79,7 @@ export default {
|
|||
data-testid="blockquote"
|
||||
content-type="blockquote"
|
||||
icon-name="quote"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleBlockquote"
|
||||
:label="__('Insert a quote')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -92,6 +88,7 @@ export default {
|
|||
data-testid="code-block"
|
||||
content-type="codeBlock"
|
||||
icon-name="doc-code"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleCodeBlock"
|
||||
:label="__('Insert a code block')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -100,6 +97,7 @@ export default {
|
|||
data-testid="bullet-list"
|
||||
content-type="bulletList"
|
||||
icon-name="list-bulleted"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleBulletList"
|
||||
:label="__('Add a bullet list')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -108,6 +106,7 @@ export default {
|
|||
data-testid="ordered-list"
|
||||
content-type="orderedList"
|
||||
icon-name="list-numbered"
|
||||
class="gl-mx-2"
|
||||
editor-command="toggleOrderedList"
|
||||
:label="__('Add a numbered list')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
@ -116,6 +115,7 @@ export default {
|
|||
data-testid="horizontal-rule"
|
||||
content-type="horizontalRule"
|
||||
icon-name="dash"
|
||||
class="gl-mx-2"
|
||||
editor-command="setHorizontalRule"
|
||||
:label="__('Add a horizontal rule')"
|
||||
@execute="trackToolbarControlExecution"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const PROVIDE_SERIALIZER_OR_RENDERER_ERROR = s__(
|
|||
|
||||
export const CONTENT_EDITOR_TRACKING_LABEL = 'content_editor';
|
||||
export const TOOLBAR_CONTROL_TRACKING_ACTION = 'execute_toolbar_control';
|
||||
export const BUBBLE_MENU_TRACKING_ACTION = 'execute_bubble_menu_control';
|
||||
export const KEYBOARD_SHORTCUT_TRACKING_ACTION = 'execute_keyboard_shortcut';
|
||||
export const INPUT_RULE_TRACKING_ACTION = 'execute_input_rule';
|
||||
|
||||
|
|
@ -40,3 +41,7 @@ export const TEXT_STYLE_DROPDOWN_ITEMS = [
|
|||
label: __('Normal text'),
|
||||
},
|
||||
];
|
||||
|
||||
export const LOADING_CONTENT_EVENT = 'loadingContent';
|
||||
export const LOADING_SUCCESS_EVENT = 'loadingSuccess';
|
||||
export const LOADING_ERROR_EVENT = 'loadingError';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import eventHubFactory from '~/helpers/event_hub_factory';
|
||||
import { LOADING_CONTENT_EVENT, LOADING_SUCCESS_EVENT, LOADING_ERROR_EVENT } from '../constants';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export class ContentEditor {
|
||||
constructor({ tiptapEditor, serializer }) {
|
||||
this._tiptapEditor = tiptapEditor;
|
||||
this._serializer = serializer;
|
||||
this._eventHub = eventHubFactory();
|
||||
}
|
||||
|
||||
get tiptapEditor() {
|
||||
|
|
@ -16,12 +19,41 @@ export class ContentEditor {
|
|||
return doc.childCount === 0 || (doc.childCount === 1 && doc.child(0).childCount === 0);
|
||||
}
|
||||
|
||||
once(type, handler) {
|
||||
this._eventHub.$once(type, handler);
|
||||
}
|
||||
|
||||
on(type, handler) {
|
||||
this._eventHub.$on(type, handler);
|
||||
}
|
||||
|
||||
emit(type, params = {}) {
|
||||
this._eventHub.$emit(type, params);
|
||||
}
|
||||
|
||||
off(type, handler) {
|
||||
this._eventHub.$off(type, handler);
|
||||
}
|
||||
|
||||
disposeAllEvents() {
|
||||
this._eventHub.dispose();
|
||||
}
|
||||
|
||||
async setSerializedContent(serializedContent) {
|
||||
const { _tiptapEditor: editor, _serializer: serializer } = this;
|
||||
|
||||
editor.commands.setContent(
|
||||
await serializer.deserialize({ schema: editor.schema, content: serializedContent }),
|
||||
);
|
||||
try {
|
||||
this._eventHub.$emit(LOADING_CONTENT_EVENT);
|
||||
const document = await serializer.deserialize({
|
||||
schema: editor.schema,
|
||||
content: serializedContent,
|
||||
});
|
||||
editor.commands.setContent(document);
|
||||
this._eventHub.$emit(LOADING_SUCCESS_EVENT);
|
||||
} catch (e) {
|
||||
this._eventHub.$emit(LOADING_ERROR_EVENT, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
getSerializedContent() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import Tracking from '~/tracking';
|
||||
import { CONTENT_EDITOR_TRACKING_LABEL, TOOLBAR_CONTROL_TRACKING_ACTION } from '../constants';
|
||||
|
||||
export default ({ action = TOOLBAR_CONTROL_TRACKING_ACTION, property, value } = {}) =>
|
||||
Tracking.event(undefined, action, {
|
||||
label: CONTENT_EDITOR_TRACKING_LABEL,
|
||||
property,
|
||||
value,
|
||||
});
|
||||
|
|
@ -18,23 +18,14 @@ module Groups
|
|||
def authenticate_user_from_jwt_token!
|
||||
return unless dependency_proxy_for_private_groups?
|
||||
|
||||
if Feature.enabled?(:dependency_proxy_deploy_tokens)
|
||||
authenticate_with_http_token do |token, _|
|
||||
@authentication_result = EMPTY_AUTH_RESULT
|
||||
authenticate_with_http_token do |token, _|
|
||||
@authentication_result = EMPTY_AUTH_RESULT
|
||||
|
||||
found_user = user_from_token(token)
|
||||
sign_in(found_user) if found_user.is_a?(User)
|
||||
end
|
||||
|
||||
request_bearer_token! unless authenticated_user
|
||||
else
|
||||
authenticate_with_http_token do |token, _|
|
||||
user = user_from_token(token)
|
||||
sign_in(user) if user
|
||||
end
|
||||
|
||||
request_bearer_token! unless current_user
|
||||
found_user = user_from_token(token)
|
||||
sign_in(found_user) if found_user.is_a?(User)
|
||||
end
|
||||
|
||||
request_bearer_token! unless authenticated_user
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -51,7 +42,6 @@ module Groups
|
|||
|
||||
def user_from_token(token)
|
||||
token_payload = ::DependencyProxy::AuthTokenService.decoded_token_payload(token)
|
||||
return User.find(token_payload['user_id']) unless Feature.enabled?(:dependency_proxy_deploy_tokens)
|
||||
|
||||
if token_payload['user_id']
|
||||
token_user = User.find(token_payload['user_id'])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
module Packages
|
||||
module Npm
|
||||
# from "@scope/package-name" return "scope" or nil
|
||||
def self.scope_of(package_name)
|
||||
return unless package_name
|
||||
return unless package_name.starts_with?('@')
|
||||
return unless package_name.include?('/')
|
||||
|
||||
package_name.match(Gitlab::Regex.npm_package_name_regex)&.captures&.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -62,7 +62,7 @@ class Packages::Package < ApplicationRecord
|
|||
|
||||
validate :valid_conan_package_recipe, if: :conan?
|
||||
validate :valid_composer_global_name, if: :composer?
|
||||
validate :package_already_taken, if: :npm?
|
||||
validate :npm_package_already_taken, if: :npm?
|
||||
validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
|
||||
validates :name, format: { with: Gitlab::Regex.generic_package_name_regex }, if: :generic?
|
||||
validates :name, format: { with: Gitlab::Regex.helm_package_regex }, if: :helm?
|
||||
|
|
@ -320,14 +320,22 @@ class Packages::Package < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def package_already_taken
|
||||
def npm_package_already_taken
|
||||
return unless project
|
||||
return unless follows_npm_naming_convention?
|
||||
|
||||
if project.package_already_taken?(name)
|
||||
if project.package_already_taken?(name, package_type: :npm)
|
||||
errors.add(:base, _('Package already exists'))
|
||||
end
|
||||
end
|
||||
|
||||
# https://docs.gitlab.com/ee/user/packages/npm_registry/#package-naming-convention
|
||||
def follows_npm_naming_convention?
|
||||
return false unless project&.root_namespace&.path
|
||||
|
||||
project.root_namespace.path == ::Packages::Npm.scope_of(name)
|
||||
end
|
||||
|
||||
def unique_debian_package_name
|
||||
return unless debian_publication&.distribution
|
||||
|
||||
|
|
|
|||
|
|
@ -2566,12 +2566,15 @@ class Project < ApplicationRecord
|
|||
[project&.id, root_group&.id]
|
||||
end
|
||||
|
||||
def package_already_taken?(package_name)
|
||||
def package_already_taken?(package_name, package_type:)
|
||||
namespace.root_ancestor.all_projects
|
||||
.joins(:packages)
|
||||
.where.not(id: id)
|
||||
.merge(Packages::Package.default_scoped.with_name(package_name))
|
||||
.exists?
|
||||
.merge(
|
||||
Packages::Package.default_scoped
|
||||
.with_name(package_name)
|
||||
.with_package_type(package_type)
|
||||
).exists?
|
||||
end
|
||||
|
||||
def default_branch_or_main
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: dependency_proxy_deploy_tokens
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64363
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334565
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::package
|
||||
default_enabled: false
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: multiple_database_metrics
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63490
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333227
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::sharding
|
||||
default_enabled: false
|
||||
|
|
@ -11,6 +11,10 @@ value_type: number
|
|||
status: data_available
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_testing_test_case_parsed
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -10,6 +10,20 @@ value_type: number
|
|||
status: removed
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_testing_test_case_parsed
|
||||
- i_testing_metrics_report_widget_total
|
||||
- i_testing_group_code_coverage_visit_total
|
||||
- i_testing_full_code_quality_report_total
|
||||
- i_testing_web_performance_widget_total
|
||||
- i_testing_group_code_coverage_project_click_total
|
||||
- i_testing_load_performance_widget_total
|
||||
- i_testing_metrics_report_artifact_uploaders
|
||||
- i_testing_summary_widget_total
|
||||
- users_expanding_testing_code_quality_report
|
||||
- users_expanding_testing_accessibility_report
|
||||
distribution:
|
||||
- ce
|
||||
tier:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ milestone: '13.11'
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57133
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- users_expanding_testing_code_quality_report
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ milestone: '13.11'
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57133
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- users_expanding_testing_accessibility_report
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ milestone: "13.11"
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59316
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_testing_summary_widget_total
|
||||
distribution:
|
||||
- ee
|
||||
- ce
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ value_type: number
|
|||
status: data_available
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_testing_test_case_parsed
|
||||
distribution:
|
||||
- ee
|
||||
- ce
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ milestone: '13.11'
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57133
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- users_expanding_testing_code_quality_report
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ milestone: '13.11'
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57133
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- users_expanding_testing_accessibility_report
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ milestone: "13.11"
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59316
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_testing_summary_widget_total
|
||||
distribution:
|
||||
- ee
|
||||
- ce
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ From left to right, the performance bar displays:
|
|||
was sent to the read/write primary server. "Replica" means it was sent
|
||||
to a read-only replica.
|
||||
- **Config name**: shows up only when the
|
||||
`multiple_database_metrics` feature flag is enabled. This is used to
|
||||
distinguish between different databases configured for different GitLab
|
||||
features. The name shown is the same name used to configure database
|
||||
`GITLAB_MULTIPLE_DATABASE_METRICS` environment variable is set. This is
|
||||
used to distinguish between different databases configured for different
|
||||
GitLab features. The name shown is the same name used to configure database
|
||||
connections in GitLab.
|
||||
- **Gitaly calls**: the time taken (in milliseconds) and the total number of
|
||||
[Gitaly](../../gitaly/index.md) calls. Click to display a modal window with more
|
||||
|
|
|
|||
|
|
@ -49,28 +49,20 @@ module API
|
|||
when :project
|
||||
params[:id]
|
||||
when :instance
|
||||
namespace_path = namespace_path_from_package_name
|
||||
package_name = params[:package_name]
|
||||
namespace_path = ::Packages::Npm.scope_of(package_name)
|
||||
next unless namespace_path
|
||||
|
||||
namespace = Namespace.top_most
|
||||
.by_path(namespace_path)
|
||||
next unless namespace
|
||||
|
||||
finder = ::Packages::Npm::PackageFinder.new(params[:package_name], namespace: namespace)
|
||||
finder = ::Packages::Npm::PackageFinder.new(package_name, namespace: namespace)
|
||||
|
||||
finder.last&.project_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# from "@scope/package-name" return "scope" or nil
|
||||
def namespace_path_from_package_name
|
||||
package_name = params[:package_name]
|
||||
return unless package_name.starts_with?('@')
|
||||
return unless package_name.include?('/')
|
||||
|
||||
package_name.match(Gitlab::Regex.npm_package_name_regex)&.captures&.first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ code_quality:
|
|||
}
|
||||
- docker pull --quiet "$CODE_QUALITY_IMAGE"
|
||||
- |
|
||||
docker run \
|
||||
docker run --rm \
|
||||
$(propagate_env_vars \
|
||||
SOURCE_CODE \
|
||||
TIMEOUT_SECONDS \
|
||||
|
|
|
|||
|
|
@ -163,6 +163,10 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def self.db_config_names
|
||||
::ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).map(&:name)
|
||||
end
|
||||
|
||||
def self.db_config_name(ar_connection)
|
||||
if ar_connection.respond_to?(:pool) &&
|
||||
ar_connection.pool.respond_to?(:db_config) &&
|
||||
|
|
|
|||
|
|
@ -42,8 +42,13 @@ module Gitlab
|
|||
tracking_category: 'Growth::Conversion::Experiment::ContactSalesInApp',
|
||||
use_backwards_compatible_subject_index: true
|
||||
},
|
||||
remove_known_trial_form_fields: {
|
||||
tracking_category: 'Growth::Conversion::Experiment::RemoveKnownTrialFormFields'
|
||||
remove_known_trial_form_fields_welcoming: {
|
||||
tracking_category: 'Growth::Conversion::Experiment::RemoveKnownTrialFormFieldsWelcoming',
|
||||
rollout_strategy: :user
|
||||
},
|
||||
remove_known_trial_form_fields_noneditable: {
|
||||
tracking_category: 'Growth::Conversion::Experiment::RemoveKnownTrialFormFieldsNoneditable',
|
||||
rollout_strategy: :user
|
||||
},
|
||||
invite_members_new_dropdown: {
|
||||
tracking_category: 'Growth::Expansion::Experiment::InviteMembersNewDropdown'
|
||||
|
|
|
|||
|
|
@ -14,20 +14,22 @@ module Gitlab
|
|||
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/323
|
||||
def initialize(repo, commit)
|
||||
@id = commit.id
|
||||
@additions = 0
|
||||
@deletions = 0
|
||||
@total = 0
|
||||
|
||||
wrapped_gitaly_errors do
|
||||
gitaly_stats(repo, commit)
|
||||
end
|
||||
additions, deletions = fetch_stats(repo, commit)
|
||||
|
||||
@additions = additions.to_i
|
||||
@deletions = deletions.to_i
|
||||
@total = @additions + @deletions
|
||||
end
|
||||
|
||||
def gitaly_stats(repo, commit)
|
||||
stats = repo.gitaly_commit_client.commit_stats(@id)
|
||||
@additions = stats.additions
|
||||
@deletions = stats.deletions
|
||||
@total = @additions + @deletions
|
||||
def fetch_stats(repo, commit)
|
||||
Rails.cache.fetch("commit_stats:#{repo.gl_project_path}:#{@id}") do
|
||||
stats = wrapped_gitaly_errors do
|
||||
repo.gitaly_commit_client.commit_stats(@id)
|
||||
end
|
||||
|
||||
[stats.additions, stats.deletions]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,17 +8,15 @@ module Gitlab
|
|||
attach_to :active_record
|
||||
|
||||
IGNORABLE_SQL = %w{BEGIN COMMIT}.freeze
|
||||
DB_COUNTERS = %i{db_count db_write_count db_cached_count}.freeze
|
||||
DB_COUNTERS = %i{count write_count cached_count}.freeze
|
||||
SQL_COMMANDS_WITH_COMMENTS_REGEX = %r{\A(/\*.*\*/\s)?((?!(.*[^\w'"](DELETE|UPDATE|INSERT INTO)[^\w'"])))(WITH.*)?(SELECT)((?!(FOR UPDATE|FOR SHARE)).)*$}i.freeze
|
||||
|
||||
SQL_DURATION_BUCKET = [0.05, 0.1, 0.25].freeze
|
||||
TRANSACTION_DURATION_BUCKET = [0.1, 0.25, 1].freeze
|
||||
|
||||
DB_LOAD_BALANCING_COUNTERS = %i{
|
||||
db_replica_count db_replica_cached_count db_replica_wal_count db_replica_wal_cached_count
|
||||
db_primary_count db_primary_cached_count db_primary_wal_count db_primary_wal_cached_count
|
||||
}.freeze
|
||||
DB_LOAD_BALANCING_DURATIONS = %i{db_primary_duration_s db_replica_duration_s}.freeze
|
||||
DB_LOAD_BALANCING_ROLES = %i{replica primary}.freeze
|
||||
DB_LOAD_BALANCING_COUNTERS = %i{count cached_count wal_count wal_cached_count}.freeze
|
||||
DB_LOAD_BALANCING_DURATIONS = %i{duration_s}.freeze
|
||||
|
||||
SQL_WAL_LOCATION_REGEX = /(pg_current_wal_insert_lsn\(\)::text|pg_last_wal_replay_lsn\(\)::text)/.freeze
|
||||
|
||||
|
|
@ -40,9 +38,10 @@ module Gitlab
|
|||
payload = event.payload
|
||||
return if ignored_query?(payload)
|
||||
|
||||
increment(:db_count)
|
||||
increment(:db_cached_count) if cached_query?(payload)
|
||||
increment(:db_write_count) unless select_sql_command?(payload)
|
||||
db_config_name = db_config_name(event.payload)
|
||||
increment(:count, db_config_name: db_config_name)
|
||||
increment(:cached_count, db_config_name: db_config_name) if cached_query?(payload)
|
||||
increment(:write_count, db_config_name: db_config_name) unless select_sql_command?(payload)
|
||||
|
||||
observe(:gitlab_sql_duration_seconds, event) do
|
||||
buckets SQL_DURATION_BUCKET
|
||||
|
|
@ -61,24 +60,17 @@ module Gitlab
|
|||
return {} unless Gitlab::SafeRequestStore.active?
|
||||
|
||||
{}.tap do |payload|
|
||||
DB_COUNTERS.each do |counter|
|
||||
payload[counter] = Gitlab::SafeRequestStore[counter].to_i
|
||||
db_counter_keys.each do |key|
|
||||
payload[key] = Gitlab::SafeRequestStore[key].to_i
|
||||
end
|
||||
|
||||
if ::Gitlab::SafeRequestStore.active? && ::Gitlab::Database::LoadBalancing.enable?
|
||||
DB_LOAD_BALANCING_COUNTERS.each do |counter|
|
||||
load_balancing_metric_counter_keys.each do |counter|
|
||||
payload[counter] = ::Gitlab::SafeRequestStore[counter].to_i
|
||||
end
|
||||
DB_LOAD_BALANCING_DURATIONS.each do |duration|
|
||||
payload[duration] = ::Gitlab::SafeRequestStore[duration].to_f.round(3)
|
||||
end
|
||||
|
||||
if Feature.enabled?(:multiple_database_metrics, default_enabled: :yaml)
|
||||
::Gitlab::SafeRequestStore[:duration_by_database]&.each do |name, duration_by_role|
|
||||
duration_by_role.each do |db_role, duration|
|
||||
payload[:"db_#{db_role}_#{name}_duration_s"] = duration.to_f.round(3)
|
||||
end
|
||||
end
|
||||
load_balancing_metric_duration_keys.each do |duration|
|
||||
payload[duration] = ::Gitlab::SafeRequestStore[duration].to_f.round(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -92,12 +84,15 @@ module Gitlab
|
|||
|
||||
def increment_db_role_counters(db_role, payload)
|
||||
cached = cached_query?(payload)
|
||||
increment("db_#{db_role}_count".to_sym)
|
||||
increment("db_#{db_role}_cached_count".to_sym) if cached
|
||||
|
||||
db_config_name = db_config_name(payload)
|
||||
|
||||
increment(:count, db_role: db_role, db_config_name: db_config_name)
|
||||
increment(:cached_count, db_role: db_role, db_config_name: db_config_name) if cached
|
||||
|
||||
if wal_command?(payload)
|
||||
increment("db_#{db_role}_wal_count".to_sym)
|
||||
increment("db_#{db_role}_wal_cached_count".to_sym) if cached
|
||||
increment(:wal_count, db_role: db_role, db_config_name: db_config_name)
|
||||
increment(:wal_cached_count, db_role: db_role, db_config_name: db_config_name) if cached
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -109,15 +104,13 @@ module Gitlab
|
|||
return unless ::Gitlab::SafeRequestStore.active?
|
||||
|
||||
duration = event.duration / 1000.0
|
||||
duration_key = "db_#{db_role}_duration_s".to_sym
|
||||
duration_key = compose_metric_key(:duration_s, db_role)
|
||||
::Gitlab::SafeRequestStore[duration_key] = (::Gitlab::SafeRequestStore[duration_key].presence || 0) + duration
|
||||
|
||||
# Per database metrics
|
||||
name = ::Gitlab::Database.db_config_name(event.payload[:connection])
|
||||
::Gitlab::SafeRequestStore[:duration_by_database] ||= {}
|
||||
::Gitlab::SafeRequestStore[:duration_by_database][name] ||= {}
|
||||
::Gitlab::SafeRequestStore[:duration_by_database][name][db_role] ||= 0
|
||||
::Gitlab::SafeRequestStore[:duration_by_database][name][db_role] += duration
|
||||
db_config_name = db_config_name(event.payload)
|
||||
duration_key = compose_metric_key(:duration_s, db_role, db_config_name)
|
||||
::Gitlab::SafeRequestStore[duration_key] = (::Gitlab::SafeRequestStore[duration_key].presence || 0) + duration
|
||||
end
|
||||
|
||||
def ignored_query?(payload)
|
||||
|
|
@ -132,10 +125,25 @@ module Gitlab
|
|||
payload[:sql].match(SQL_COMMANDS_WITH_COMMENTS_REGEX)
|
||||
end
|
||||
|
||||
def increment(counter)
|
||||
current_transaction&.increment("gitlab_transaction_#{counter}_total".to_sym, 1)
|
||||
def increment(counter, db_config_name:, db_role: nil)
|
||||
log_key = compose_metric_key(counter, db_role)
|
||||
|
||||
Gitlab::SafeRequestStore[counter] = Gitlab::SafeRequestStore[counter].to_i + 1
|
||||
prometheus_key = if db_role
|
||||
:"gitlab_transaction_db_#{db_role}_#{counter}_total"
|
||||
else
|
||||
:"gitlab_transaction_db_#{counter}_total"
|
||||
end
|
||||
|
||||
current_transaction&.increment(prometheus_key, 1)
|
||||
Gitlab::SafeRequestStore[log_key] = Gitlab::SafeRequestStore[log_key].to_i + 1
|
||||
|
||||
# To avoid confusing log keys we only log the db_config_name metrics
|
||||
# when we are also logging the db_role. Otherwise it will be hard to
|
||||
# tell if the log key is referring to a db_role OR a db_config_name.
|
||||
if db_role.present? && db_config_name.present?
|
||||
log_key = compose_metric_key(counter, db_role, db_config_name)
|
||||
Gitlab::SafeRequestStore[log_key] = Gitlab::SafeRequestStore[log_key].to_i + 1
|
||||
end
|
||||
end
|
||||
|
||||
def observe(histogram, event, &block)
|
||||
|
|
@ -145,6 +153,45 @@ module Gitlab
|
|||
def current_transaction
|
||||
::Gitlab::Metrics::WebTransaction.current || ::Gitlab::Metrics::BackgroundTransaction.current
|
||||
end
|
||||
|
||||
def db_config_name(payload)
|
||||
::Gitlab::Database.db_config_name(payload[:connection])
|
||||
end
|
||||
|
||||
def self.db_counter_keys
|
||||
DB_COUNTERS.map { |c| compose_metric_key(c) }
|
||||
end
|
||||
|
||||
def self.load_balancing_metric_counter_keys
|
||||
load_balancing_metric_keys(DB_LOAD_BALANCING_COUNTERS)
|
||||
end
|
||||
|
||||
def self.load_balancing_metric_duration_keys
|
||||
load_balancing_metric_keys(DB_LOAD_BALANCING_DURATIONS)
|
||||
end
|
||||
|
||||
def self.load_balancing_metric_keys(metrics)
|
||||
[].tap do |counters|
|
||||
DB_LOAD_BALANCING_ROLES.each do |role|
|
||||
metrics.each do |metric|
|
||||
counters << compose_metric_key(metric, role)
|
||||
next unless ENV['GITLAB_MULTIPLE_DATABASE_METRICS']
|
||||
|
||||
::Gitlab::Database.db_config_names.each do |config_name|
|
||||
counters << compose_metric_key(metric, role, config_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def compose_metric_key(metric, db_role = nil, db_config_name = nil)
|
||||
self.class.compose_metric_key(metric, db_role, db_config_name)
|
||||
end
|
||||
|
||||
def self.compose_metric_key(metric, db_role = nil, db_config_name = nil)
|
||||
[:db, db_role, db_config_name, metric].compact.join("_").to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ module Peek
|
|||
end
|
||||
|
||||
def format_call_details(call)
|
||||
if Feature.enabled?(:multiple_database_metrics, default_enabled: :yaml)
|
||||
if ENV['GITLAB_MULTIPLE_DATABASE_METRICS']
|
||||
super
|
||||
else
|
||||
super.except(:db_config_name)
|
||||
|
|
|
|||
|
|
@ -16337,6 +16337,9 @@ msgstr ""
|
|||
msgid "Hi %{username}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi%{salutation}, your GitLab Ultimate trial lasts for 30 days, but you can keep your free GitLab account forever. We just need some additional information about %{company} to activate your trial."
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38248,7 +38251,7 @@ msgstr ""
|
|||
msgid "Your GPG keys (%{count})"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your GitLab Ultimate trial will last 30 days after which point you can keep your free GitLab account forever. We just need some additional information to activate your trial."
|
||||
msgid "Your GitLab Ultimate trial lasts for 30 days, but you can keep your free GitLab account forever. We just need some additional information to activate your trial."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your GitLab account has been locked due to an excessive amount of unsuccessful sign in attempts. Your account will automatically unlock in %{duration} or you may click the link below to unlock now."
|
||||
|
|
@ -40195,5 +40198,8 @@ msgstr ""
|
|||
msgid "yaml invalid"
|
||||
msgstr ""
|
||||
|
||||
msgid "your company"
|
||||
msgstr ""
|
||||
|
||||
msgid "your settings"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -65,82 +65,39 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'deploy tokens with dependency_proxy_deploy_tokens disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
context 'with deploy token from a different group,' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
|
||||
context 'with deploy token from a different group,' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with revoked deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :revoked, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with expired deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :expired, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with deploy token with insufficient scopes' do
|
||||
let_it_be(:user) { create(:deploy_token, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when a group is not found' do
|
||||
before do
|
||||
expect(Group).to receive(:find_by_full_path).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'deploy tokens with dependency_proxy_deploy_tokens enabled' do
|
||||
context 'with deploy token from a different group,' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
context 'with revoked deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :revoked, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'with expired deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :expired, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'with deploy token with insufficient scopes' do
|
||||
let_it_be(:user) { create(:deploy_token, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when a group is not found' do
|
||||
before do
|
||||
expect(Group).to receive(:find_by_full_path).and_return(nil)
|
||||
end
|
||||
|
||||
context 'with revoked deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :revoked, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'with expired deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :expired, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'with deploy token with insufficient scopes' do
|
||||
let_it_be(:user) { create(:deploy_token, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when a group is not found' do
|
||||
before do
|
||||
expect(Group).to receive(:find_by_full_path).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when user is not found' do
|
||||
|
|
@ -274,25 +231,6 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
it_behaves_like 'returning response status', :success
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest_from_cache'
|
||||
end
|
||||
|
||||
context 'with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
end
|
||||
end
|
||||
|
||||
context 'a valid deploy token with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
let_it_be(:user) { create(:deploy_token, :dependency_proxy_scopes, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'a valid deploy token' do
|
||||
|
|
@ -395,25 +333,6 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
it_behaves_like 'returning response status', :success
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
|
||||
end
|
||||
|
||||
context 'with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
end
|
||||
end
|
||||
|
||||
context 'a valid deploy token with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'a valid deploy token' do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`content_editor/components/toolbar_button displays tertiary, small button with a provided label and icon 1`] = `
|
||||
"<b-button-stub size=\\"sm\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-mx-2 gl-button btn-default-tertiary btn-icon\\">
|
||||
"<b-button-stub size=\\"sm\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-button btn-default-tertiary btn-icon\\">
|
||||
<!---->
|
||||
<gl-icon-stub name=\\"bold\\" size=\\"16\\" class=\\"gl-button-icon\\"></gl-icon-stub>
|
||||
<!---->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
import { BubbleMenu } from '@tiptap/vue-2';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import FormattingBubbleMenu from '~/content_editor/components/formatting_bubble_menu.vue';
|
||||
|
||||
import {
|
||||
BUBBLE_MENU_TRACKING_ACTION,
|
||||
CONTENT_EDITOR_TRACKING_LABEL,
|
||||
} from '~/content_editor/constants';
|
||||
import { createTestEditor } from '../test_utils';
|
||||
|
||||
describe('content_editor/components/top_toolbar', () => {
|
||||
let wrapper;
|
||||
let trackingSpy;
|
||||
let tiptapEditor;
|
||||
|
||||
const buildEditor = () => {
|
||||
tiptapEditor = createTestEditor();
|
||||
|
||||
jest.spyOn(tiptapEditor, 'isActive');
|
||||
};
|
||||
|
||||
const buildWrapper = () => {
|
||||
wrapper = shallowMountExtended(FormattingBubbleMenu, {
|
||||
provide: {
|
||||
tiptapEditor,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, null, jest.spyOn);
|
||||
buildEditor();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders bubble menu component', () => {
|
||||
buildWrapper();
|
||||
const bubbleMenu = wrapper.findComponent(BubbleMenu);
|
||||
|
||||
expect(bubbleMenu.props().editor).toBe(tiptapEditor);
|
||||
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base']);
|
||||
});
|
||||
|
||||
describe.each`
|
||||
testId | controlProps
|
||||
${'bold'} | ${{ contentType: 'bold', iconName: 'bold', label: 'Bold text', editorCommand: 'toggleBold', size: 'medium', category: 'primary' }}
|
||||
${'italic'} | ${{ contentType: 'italic', iconName: 'italic', label: 'Italic text', editorCommand: 'toggleItalic', size: 'medium', category: 'primary' }}
|
||||
${'strike'} | ${{ contentType: 'strike', iconName: 'strikethrough', label: 'Strikethrough', editorCommand: 'toggleStrike', size: 'medium', category: 'primary' }}
|
||||
${'code'} | ${{ contentType: 'code', iconName: 'code', label: 'Code', editorCommand: 'toggleCode', size: 'medium', category: 'primary' }}
|
||||
`('given a $testId toolbar control', ({ testId, controlProps }) => {
|
||||
beforeEach(() => {
|
||||
buildWrapper();
|
||||
});
|
||||
|
||||
it('renders the toolbar control with the provided properties', () => {
|
||||
expect(wrapper.findByTestId(testId).exists()).toBe(true);
|
||||
|
||||
Object.keys(controlProps).forEach((propName) => {
|
||||
expect(wrapper.findByTestId(testId).props(propName)).toBe(controlProps[propName]);
|
||||
});
|
||||
});
|
||||
|
||||
it('tracks the execution of toolbar controls', () => {
|
||||
const eventData = { contentType: 'italic', value: 1 };
|
||||
const { contentType, value } = eventData;
|
||||
|
||||
wrapper.findByTestId(testId).vm.$emit('execute', eventData);
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, BUBBLE_MENU_TRACKING_ACTION, {
|
||||
label: CONTENT_EDITOR_TRACKING_LABEL,
|
||||
property: contentType,
|
||||
value,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -50,6 +50,24 @@ describe('content_editor/components/toolbar_button', () => {
|
|||
expect(findButton().html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('allows customizing the variant, category, size of the button', () => {
|
||||
const variant = 'danger';
|
||||
const category = 'secondary';
|
||||
const size = 'medium';
|
||||
|
||||
buildWrapper({
|
||||
variant,
|
||||
category,
|
||||
size,
|
||||
});
|
||||
|
||||
expect(findButton().props()).toMatchObject({
|
||||
variant,
|
||||
category,
|
||||
size,
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
editorState | outcomeDescription | outcome
|
||||
${{ isActive: true, isFocused: true }} | ${'button is active'} | ${true}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import TopToolbar from '~/content_editor/components/top_toolbar.vue';
|
||||
import {
|
||||
TOOLBAR_CONTROL_TRACKING_ACTION,
|
||||
|
|
@ -12,7 +11,7 @@ describe('content_editor/components/top_toolbar', () => {
|
|||
let trackingSpy;
|
||||
|
||||
const buildWrapper = () => {
|
||||
wrapper = extendedWrapper(shallowMount(TopToolbar));
|
||||
wrapper = shallowMountExtended(TopToolbar);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -43,17 +42,17 @@ describe('content_editor/components/top_toolbar', () => {
|
|||
});
|
||||
|
||||
it('renders the toolbar control with the provided properties', () => {
|
||||
expect(wrapper.findByTestId(testId).props()).toEqual({
|
||||
...controlProps,
|
||||
expect(wrapper.findByTestId(testId).exists()).toBe(true);
|
||||
|
||||
Object.keys(controlProps).forEach((propName) => {
|
||||
expect(wrapper.findByTestId(testId).props(propName)).toBe(controlProps[propName]);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
eventData
|
||||
${{ contentType: 'bold' }}
|
||||
${{ contentType: 'blockquote', value: 1 }}
|
||||
`('tracks the execution of toolbar controls', ({ eventData }) => {
|
||||
it('tracks the execution of toolbar controls', () => {
|
||||
const eventData = { contentType: 'blockquote', value: 1 };
|
||||
const { contentType, value } = eventData;
|
||||
|
||||
wrapper.findByTestId(testId).vm.$emit('execute', eventData);
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, TOOLBAR_CONTROL_TRACKING_ACTION, {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import {
|
||||
LOADING_CONTENT_EVENT,
|
||||
LOADING_SUCCESS_EVENT,
|
||||
LOADING_ERROR_EVENT,
|
||||
} from '~/content_editor/constants';
|
||||
import { ContentEditor } from '~/content_editor/services/content_editor';
|
||||
|
||||
import { createTestEditor } from '../test_utils';
|
||||
|
||||
describe('content_editor/services/content_editor', () => {
|
||||
let contentEditor;
|
||||
let serializer;
|
||||
|
||||
beforeEach(() => {
|
||||
const tiptapEditor = createTestEditor();
|
||||
serializer = { deserialize: jest.fn() };
|
||||
contentEditor = new ContentEditor({ tiptapEditor, serializer });
|
||||
});
|
||||
|
||||
describe('when setSerializedContent succeeds', () => {
|
||||
beforeEach(() => {
|
||||
serializer.deserialize.mockResolvedValueOnce('');
|
||||
});
|
||||
|
||||
it('emits loadingContent and loadingSuccess event', () => {
|
||||
let loadingContentEmitted = false;
|
||||
|
||||
contentEditor.on(LOADING_CONTENT_EVENT, () => {
|
||||
loadingContentEmitted = true;
|
||||
});
|
||||
contentEditor.on(LOADING_SUCCESS_EVENT, () => {
|
||||
expect(loadingContentEmitted).toBe(true);
|
||||
});
|
||||
|
||||
contentEditor.setSerializedContent('**bold text**');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setSerializedContent fails', () => {
|
||||
const error = 'error';
|
||||
|
||||
beforeEach(() => {
|
||||
serializer.deserialize.mockRejectedValueOnce(error);
|
||||
});
|
||||
|
||||
it('emits loadingError event', async () => {
|
||||
contentEditor.on(LOADING_ERROR_EVENT, (e) => {
|
||||
expect(e).toBe('error');
|
||||
});
|
||||
|
||||
await expect(() => contentEditor.setSerializedContent('**bold text**')).rejects.toEqual(
|
||||
error,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Gitlab::Git::CommitStats, :seed_helper do
|
||||
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
|
||||
let(:commit) { Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID) }
|
||||
|
||||
def verify_stats!
|
||||
stats = described_class.new(repository, commit)
|
||||
|
||||
expect(stats).to have_attributes(
|
||||
additions: eq(11),
|
||||
deletions: eq(6),
|
||||
total: eq(17)
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns commit stats and caches them', :use_clean_rails_redis_caching do
|
||||
expect(repository.gitaly_commit_client).to receive(:commit_stats).with(commit.id).and_call_original
|
||||
|
||||
verify_stats!
|
||||
|
||||
expect(Rails.cache.fetch("commit_stats:group/project:#{commit.id}")).to eq([11, 6])
|
||||
|
||||
expect(repository.gitaly_commit_client).not_to receive(:commit_stats)
|
||||
|
||||
verify_stats!
|
||||
end
|
||||
end
|
||||
|
|
@ -294,47 +294,38 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
|
|||
context 'when load balancing is enabled', :db_load_balancing do
|
||||
let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.connection) }
|
||||
|
||||
let(:expected_db_payload_defaults) do
|
||||
metrics =
|
||||
::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_counter_keys +
|
||||
::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_duration_keys +
|
||||
::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_keys +
|
||||
[:db_duration_s]
|
||||
|
||||
metrics.each_with_object({}) do |key, result|
|
||||
result[key.to_s] = 0
|
||||
end
|
||||
end
|
||||
|
||||
let(:expected_end_payload_with_db) do
|
||||
expected_end_payload.merge(
|
||||
expected_end_payload.merge(expected_db_payload_defaults).merge(
|
||||
'db_duration_s' => a_value >= 0.1,
|
||||
'db_count' => a_value >= 1,
|
||||
'db_cached_count' => 0,
|
||||
'db_write_count' => 0,
|
||||
'db_replica_count' => 0,
|
||||
'db_replica_cached_count' => 0,
|
||||
'db_replica_wal_count' => 0,
|
||||
"db_replica_#{db_config_name}_count" => 0,
|
||||
'db_replica_duration_s' => a_value >= 0,
|
||||
'db_primary_count' => a_value >= 1,
|
||||
'db_primary_cached_count' => 0,
|
||||
'db_primary_wal_count' => 0,
|
||||
"db_primary_#{db_config_name}_count" => a_value >= 1,
|
||||
'db_primary_duration_s' => a_value > 0,
|
||||
"db_primary_#{db_config_name}_duration_s" => a_value > 0,
|
||||
'db_primary_wal_cached_count' => 0,
|
||||
'db_replica_wal_cached_count' => 0
|
||||
"db_primary_#{db_config_name}_duration_s" => a_value > 0
|
||||
)
|
||||
end
|
||||
|
||||
let(:end_payload) do
|
||||
start_payload.merge(
|
||||
start_payload.merge(expected_db_payload_defaults).merge(
|
||||
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
|
||||
'job_status' => 'done',
|
||||
'duration_s' => 0.0,
|
||||
'completed_at' => timestamp.to_f,
|
||||
'cpu_s' => 1.111112,
|
||||
'db_duration_s' => 0.0,
|
||||
'db_cached_count' => 0,
|
||||
'db_count' => 0,
|
||||
'db_write_count' => 0,
|
||||
'db_replica_count' => 0,
|
||||
'db_replica_cached_count' => 0,
|
||||
'db_replica_wal_count' => 0,
|
||||
'db_replica_duration_s' => 0,
|
||||
'db_primary_count' => 0,
|
||||
'db_primary_cached_count' => 0,
|
||||
'db_primary_wal_count' => 0,
|
||||
'db_primary_wal_cached_count' => 0,
|
||||
'db_replica_wal_cached_count' => 0,
|
||||
'db_primary_duration_s' => 0
|
||||
'cpu_s' => 1.111112
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,16 @@ RSpec.describe Gitlab::UsageDataMetrics do
|
|||
)
|
||||
end
|
||||
|
||||
it 'includes testing monthly and weekly keys' do
|
||||
expect(subject[:redis_hll_counters][:testing]).to include(
|
||||
:i_testing_test_case_parsed_monthly, :i_testing_test_case_parsed_weekly,
|
||||
:users_expanding_testing_code_quality_report_monthly, :users_expanding_testing_code_quality_report_weekly,
|
||||
:users_expanding_testing_accessibility_report_monthly, :users_expanding_testing_accessibility_report_weekly,
|
||||
:i_testing_summary_widget_total_monthly, :i_testing_summary_widget_total_weekly,
|
||||
:testing_total_unique_counts_monthly
|
||||
)
|
||||
end
|
||||
|
||||
it 'includes source_code monthly and weekly keys' do
|
||||
expect(subject[:redis_hll_counters][:source_code].keys).to contain_exactly(*[
|
||||
:wiki_action_monthly, :wiki_action_weekly,
|
||||
|
|
|
|||
|
|
@ -109,9 +109,9 @@ RSpec.describe Peek::Views::ActiveRecord, :request_store do
|
|||
)
|
||||
end
|
||||
|
||||
context 'when the multiple_database_metrics feature flag is disabled' do
|
||||
context 'when the GITLAB_MULTIPLE_DATABASE_METRICS env var is disabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_database_metrics: false)
|
||||
stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', nil)
|
||||
end
|
||||
|
||||
it 'does not include db_config_name field' do
|
||||
|
|
@ -191,9 +191,9 @@ RSpec.describe Peek::Views::ActiveRecord, :request_store do
|
|||
)
|
||||
end
|
||||
|
||||
context 'when the multiple_database_metrics feature flag is disabled' do
|
||||
context 'when the GITLAB_MULTIPLE_DATABASE_METRICS env var is disabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_database_metrics: false)
|
||||
stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', nil)
|
||||
end
|
||||
|
||||
it 'does not include db_config_name field' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Npm do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
describe '.scope_of' do
|
||||
subject { described_class.scope_of(package_name) }
|
||||
|
||||
where(:package_name, :expected_result) do
|
||||
nil | nil
|
||||
'test' | nil
|
||||
'@test' | nil
|
||||
'test/package' | nil
|
||||
'@/package' | nil
|
||||
'@test/package' | 'test'
|
||||
'@test/' | nil
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(expected_result) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -418,7 +418,7 @@ RSpec.describe Packages::Package, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#package_already_taken' do
|
||||
describe '#npm_package_already_taken' do
|
||||
context 'maven package' do
|
||||
let!(:package) { create(:maven_package) }
|
||||
|
||||
|
|
@ -428,6 +428,43 @@ RSpec.describe Packages::Package, type: :model do
|
|||
expect(new_package).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'npm package' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, namespace: group) }
|
||||
let_it_be(:second_project) { create(:project, namespace: group)}
|
||||
|
||||
let(:package) { build(:npm_package, project: project, name: name) }
|
||||
let(:second_package) { build(:npm_package, project: second_project, name: name, version: '5.0.0') }
|
||||
|
||||
context 'following the naming convention' do
|
||||
let(:name) { "@#{group.path}/test" }
|
||||
|
||||
it 'will allow the first package' do
|
||||
expect(package).to be_valid
|
||||
end
|
||||
|
||||
it 'will not allow npm package with duplicate name' do
|
||||
package.save!
|
||||
|
||||
expect(second_package).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'not following the naming convention' do
|
||||
let(:name) { '@foobar/test' }
|
||||
|
||||
it 'will allow the first package' do
|
||||
expect(package).to be_valid
|
||||
end
|
||||
|
||||
it 'will allow npm package with duplicate name' do
|
||||
package.save!
|
||||
|
||||
expect(second_package).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "recipe uniqueness for conan packages" do
|
||||
|
|
|
|||
|
|
@ -6839,29 +6839,36 @@ RSpec.describe Project, factory_default: :keep do
|
|||
end
|
||||
|
||||
describe '#package_already_taken?' do
|
||||
let(:namespace) { create(:namespace) }
|
||||
let(:project) { create(:project, :public, namespace: namespace) }
|
||||
let!(:package) { create(:npm_package, project: project, name: "@#{namespace.path}/foo") }
|
||||
let_it_be(:namespace) { create(:namespace) }
|
||||
let_it_be(:project) { create(:project, :public, namespace: namespace) }
|
||||
let_it_be(:package) { create(:npm_package, project: project, name: "@#{namespace.path}/foo") }
|
||||
|
||||
context 'no package exists with the same name' do
|
||||
it 'returns false' do
|
||||
result = project.package_already_taken?("@#{namespace.path}/bar")
|
||||
result = project.package_already_taken?("@#{namespace.path}/bar", package_type: :npm)
|
||||
expect(result).to be false
|
||||
end
|
||||
|
||||
it 'returns false if it is the project that the package belongs to' do
|
||||
result = project.package_already_taken?("@#{namespace.path}/foo")
|
||||
result = project.package_already_taken?("@#{namespace.path}/foo", package_type: :npm)
|
||||
expect(result).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'a package already exists with the same name' do
|
||||
let(:alt_project) { create(:project, :public, namespace: namespace) }
|
||||
let_it_be(:alt_project) { create(:project, :public, namespace: namespace) }
|
||||
|
||||
it 'returns true' do
|
||||
result = alt_project.package_already_taken?("@#{namespace.path}/foo")
|
||||
result = alt_project.package_already_taken?(package.name, package_type: :npm)
|
||||
expect(result).to be true
|
||||
end
|
||||
|
||||
context 'for a different package type' do
|
||||
it 'returns false' do
|
||||
result = alt_project.package_already_taken?(package.name, package_type: :nuget)
|
||||
expect(result).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -228,6 +228,31 @@ RSpec.describe API::NpmProjectPackages do
|
|||
|
||||
it_behaves_like 'handling upload with different authentications'
|
||||
end
|
||||
|
||||
context 'with an existing package' do
|
||||
let_it_be(:second_project) { create(:project, namespace: namespace) }
|
||||
|
||||
context 'following the naming convention' do
|
||||
let_it_be(:second_package) { create(:npm_package, project: second_project, name: "@#{group.path}/test") }
|
||||
|
||||
let(:package_name) { "@#{group.path}/test" }
|
||||
|
||||
it_behaves_like 'handling invalid record with 400 error'
|
||||
end
|
||||
|
||||
context 'not following the naming convention' do
|
||||
let_it_be(:second_package) { create(:npm_package, project: second_project, name: "@any_scope/test") }
|
||||
|
||||
let(:package_name) { "@any_scope/test" }
|
||||
|
||||
it "uploads the package" do
|
||||
expect { upload_package_with_token(package_name, params) }
|
||||
.to change { project.packages.count }.by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'package creation fails' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.before do
|
||||
# Enable this by default in all tests so it behaves like a FF
|
||||
stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', '1')
|
||||
end
|
||||
end
|
||||
|
|
@ -1,70 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
|
||||
let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.connection) }
|
||||
|
||||
let(:expected_payload_defaults) do
|
||||
metrics =
|
||||
::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_counter_keys +
|
||||
::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_duration_keys +
|
||||
::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_keys
|
||||
|
||||
metrics.each_with_object({}) do |key, result|
|
||||
result[key] = 0
|
||||
end
|
||||
end
|
||||
|
||||
it 'prevents db counters from leaking to the next transaction' do
|
||||
2.times do
|
||||
Gitlab::WithRequestStore.with_request_store do
|
||||
subscriber.sql(event)
|
||||
connection = event.payload[:connection]
|
||||
|
||||
if db_role == :primary
|
||||
expected = {
|
||||
db_count: record_query ? 1 : 0,
|
||||
db_write_count: record_write_query ? 1 : 0,
|
||||
db_cached_count: record_cached_query ? 1 : 0,
|
||||
db_primary_cached_count: record_cached_query ? 1 : 0,
|
||||
db_primary_count: record_query ? 1 : 0,
|
||||
db_primary_duration_s: record_query ? 0.002 : 0,
|
||||
db_replica_cached_count: 0,
|
||||
db_replica_count: 0,
|
||||
db_replica_duration_s: 0.0,
|
||||
db_primary_wal_count: record_wal_query ? 1 : 0,
|
||||
db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
|
||||
db_replica_wal_cached_count: 0,
|
||||
db_replica_wal_count: 0
|
||||
}
|
||||
expected[:"db_primary_#{::Gitlab::Database.db_config_name(connection)}_duration_s"] = 0.002 if record_query
|
||||
elsif db_role == :replica
|
||||
expected = {
|
||||
db_count: record_query ? 1 : 0,
|
||||
db_write_count: record_write_query ? 1 : 0,
|
||||
db_cached_count: record_cached_query ? 1 : 0,
|
||||
db_primary_cached_count: 0,
|
||||
db_primary_count: 0,
|
||||
db_primary_duration_s: 0.0,
|
||||
db_replica_cached_count: record_cached_query ? 1 : 0,
|
||||
db_replica_count: record_query ? 1 : 0,
|
||||
db_replica_duration_s: record_query ? 0.002 : 0,
|
||||
db_replica_wal_count: record_wal_query ? 1 : 0,
|
||||
db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
|
||||
db_primary_wal_cached_count: 0,
|
||||
db_primary_wal_count: 0
|
||||
}
|
||||
expected[:"db_replica_#{::Gitlab::Database.db_config_name(connection)}_duration_s"] = 0.002 if record_query
|
||||
else
|
||||
expected = {
|
||||
db_count: record_query ? 1 : 0,
|
||||
db_write_count: record_write_query ? 1 : 0,
|
||||
db_cached_count: record_cached_query ? 1 : 0
|
||||
}
|
||||
end
|
||||
expected = if db_role == :primary
|
||||
expected_payload_defaults.merge({
|
||||
db_count: record_query ? 1 : 0,
|
||||
db_write_count: record_write_query ? 1 : 0,
|
||||
db_cached_count: record_cached_query ? 1 : 0,
|
||||
db_primary_cached_count: record_cached_query ? 1 : 0,
|
||||
"db_primary_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
|
||||
db_primary_count: record_query ? 1 : 0,
|
||||
"db_primary_#{db_config_name}_count": record_query ? 1 : 0,
|
||||
db_primary_duration_s: record_query ? 0.002 : 0,
|
||||
"db_primary_#{db_config_name}_duration_s": record_query ? 0.002 : 0,
|
||||
db_primary_wal_count: record_wal_query ? 1 : 0,
|
||||
"db_primary_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
|
||||
db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
|
||||
"db_primary_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
|
||||
})
|
||||
elsif db_role == :replica
|
||||
expected_payload_defaults.merge({
|
||||
db_count: record_query ? 1 : 0,
|
||||
db_write_count: record_write_query ? 1 : 0,
|
||||
db_cached_count: record_cached_query ? 1 : 0,
|
||||
db_replica_cached_count: record_cached_query ? 1 : 0,
|
||||
"db_replica_#{db_config_name}_cached_count": record_cached_query ? 1 : 0,
|
||||
db_replica_count: record_query ? 1 : 0,
|
||||
"db_replica_#{db_config_name}_count": record_query ? 1 : 0,
|
||||
db_replica_duration_s: record_query ? 0.002 : 0,
|
||||
"db_replica_#{db_config_name}_duration_s": record_query ? 0.002 : 0,
|
||||
db_replica_wal_count: record_wal_query ? 1 : 0,
|
||||
"db_replica_#{db_config_name}_wal_count": record_wal_query ? 1 : 0,
|
||||
db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0,
|
||||
"db_replica_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0
|
||||
})
|
||||
else
|
||||
{
|
||||
db_count: record_query ? 1 : 0,
|
||||
db_write_count: record_write_query ? 1 : 0,
|
||||
db_cached_count: record_cached_query ? 1 : 0
|
||||
}
|
||||
end
|
||||
|
||||
expect(described_class.db_counter_payload).to eq(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple_database_metrics is disabled' do
|
||||
context 'when the GITLAB_MULTIPLE_DATABASE_METRICS env var is disabled' do
|
||||
before do
|
||||
stub_feature_flags(multiple_database_metrics: false)
|
||||
stub_env('GITLAB_MULTIPLE_DATABASE_METRICS', nil)
|
||||
end
|
||||
|
||||
it 'does not include per database metrics' do
|
||||
Gitlab::WithRequestStore.with_request_store do
|
||||
subscriber.sql(event)
|
||||
connection = event.payload[:connection]
|
||||
|
||||
expect(described_class.db_counter_payload).not_to include(:"db_replica_#{::Gitlab::Database.db_config_name(connection)}_duration_s")
|
||||
expect(described_class.db_counter_payload).not_to include(:"db_replica_#{db_config_name}_duration_s")
|
||||
expect(described_class.db_counter_payload).not_to include(:"db_replica_#{db_config_name}_count")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue