Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
26d28ba159
commit
1c359370b3
|
|
@ -8,3 +8,6 @@ include:
|
|||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "gitlab-utils"
|
||||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "click_house-client"
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ stages:
|
|||
STATUS_SYM: ☠️
|
||||
STATUS: failed
|
||||
TYPE: "($QA_RUN_TYPE) "
|
||||
ALLURE_JOB_NAME: $QA_RUN_TYPE
|
||||
when: always
|
||||
script:
|
||||
- |
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ notify-slack:
|
|||
variables:
|
||||
QA_RSPEC_XML_FILE_PATTERN: ${CI_PROJECT_DIR}/qa/tmp/rspec-*.xml
|
||||
RUN_WITH_BUNDLE: "true"
|
||||
ALLURE_JOB_NAME: $QA_RUN_TYPE
|
||||
when: on_failure
|
||||
|
||||
export-test-metrics:
|
||||
|
|
|
|||
|
|
@ -193,10 +193,13 @@
|
|||
- "lib/gitlab/setup_helper.rb"
|
||||
|
||||
.workhorse-patterns: &workhorse-patterns
|
||||
- ".gitlab/ci/workhorse.gitlab-ci.yml"
|
||||
- "GITLAB_WORKHORSE_VERSION"
|
||||
- "workhorse/**/*"
|
||||
- ".gitlab/ci/workhorse.gitlab-ci.yml"
|
||||
- "scripts/gitaly-test-build"
|
||||
- "scripts/gitaly-test-spawn"
|
||||
- "spec/support/gitlab-git-test.git/**/*"
|
||||
- "spec/support/helpers/gitaly_setup.rb"
|
||||
|
||||
.yaml-lint-patterns: &yaml-lint-patterns
|
||||
- "**/*.{yml,yaml}{,.*}"
|
||||
|
|
|
|||
|
|
@ -984,11 +984,6 @@ Cop/SidekiqApiUsage:
|
|||
- 'config/initializers/sidekiq.rb'
|
||||
- 'config/initializers/forbid_sidekiq_in_transactions.rb'
|
||||
|
||||
Rake/Require:
|
||||
Include:
|
||||
- '{,ee/,jh/}lib/**/*.rake'
|
||||
- 'qa/tasks/**/*.rake'
|
||||
|
||||
Cop/FeatureFlagUsage:
|
||||
Include:
|
||||
- 'lib/gitlab/redis/**/*.rb'
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ Rails/OutputSafety:
|
|||
- 'lib/gitlab/observability.rb'
|
||||
- 'lib/gitlab/other_markup.rb'
|
||||
- 'lib/gitlab/string_range_marker.rb'
|
||||
- 'lib/gitlab/utils.rb'
|
||||
- 'spec/helpers/application_helper_spec.rb'
|
||||
- 'spec/helpers/notify_helper_spec.rb'
|
||||
- 'spec/helpers/profiles_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
25fbd040740121576144a38413229d2cc571aca6
|
||||
c9160a6d435d904bb72f3627c702192706ad4642
|
||||
|
|
|
|||
1
Gemfile
1
Gemfile
|
|
@ -326,6 +326,7 @@ gem 'sassc-rails', '~> 2.1.0'
|
|||
gem 'autoprefixer-rails', '10.2.5.1'
|
||||
gem 'terser', '1.0.2'
|
||||
|
||||
gem 'click_house-client', path: 'gems/click_house-client', require: 'click_house/client'
|
||||
gem 'addressable', '~> 2.8'
|
||||
gem 'tanuki_emoji', '~> 0.6'
|
||||
gem 'gon', '~> 6.4.0'
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ PATH
|
|||
activerecord-gitlab (0.1.0)
|
||||
activerecord (>= 6.1.7.3)
|
||||
|
||||
PATH
|
||||
remote: gems/click_house-client
|
||||
specs:
|
||||
click_house-client (0.1.0)
|
||||
activesupport (< 8)
|
||||
addressable (~> 2.8)
|
||||
json (~> 2.6.3)
|
||||
|
||||
PATH
|
||||
remote: gems/gitlab-rspec
|
||||
specs:
|
||||
|
|
@ -1736,6 +1744,7 @@ DEPENDENCIES
|
|||
carrierwave (~> 1.3)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
circuitbox (= 2.0.0)
|
||||
click_house-client!
|
||||
cloud_profiler_agent (~> 0.0.0)!
|
||||
commonmarker (~> 0.23.9)
|
||||
concurrent-ruby (~> 1.1)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import $ from 'jquery';
|
|||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
import { createAlert } from '~/alert';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { addMarkdownListeners, removeMarkdownListeners } from '~/lib/utils/text_markdown';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
// MarkdownPreview
|
||||
|
|
@ -128,6 +129,8 @@ $(document).on('markdown-preview:show', (e, $form) => {
|
|||
return;
|
||||
}
|
||||
|
||||
removeMarkdownListeners($form);
|
||||
|
||||
lastTextareaPreviewed = $form.find('textarea.markdown-area');
|
||||
lastTextareaHeight = lastTextareaPreviewed.height();
|
||||
|
||||
|
|
@ -165,6 +168,7 @@ $(document).on('markdown-preview:hide', (e, $form) => {
|
|||
$form.find('.haml-markdown-button, .js-zen-enter').removeClass('gl-display-none!');
|
||||
|
||||
markdownPreview.hideReferencedCommands($form);
|
||||
addMarkdownListeners($form);
|
||||
});
|
||||
|
||||
$(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
|
||||
|
|
|
|||
|
|
@ -194,9 +194,8 @@ export default {
|
|||
},
|
||||
showFileDiscussions() {
|
||||
return (
|
||||
this.glFeatures.commentOnFiles &&
|
||||
!this.file.viewer?.manuallyCollapsed &&
|
||||
(this.fileDiscussions.length || this.file.drafts.length || this.file.hasCommentForm)
|
||||
(this.fileDiscussions.length || this.file.drafts?.length || this.file.hasCommentForm)
|
||||
);
|
||||
},
|
||||
diffFileHash() {
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ export default {
|
|||
return this.expanded ? __('Hide file contents') : __('Show file contents');
|
||||
},
|
||||
showCommentButton() {
|
||||
return this.getNoteableData.current_user.can_create_note && this.glFeatures.commentOnFiles;
|
||||
return this.getNoteableData.current_user.can_create_note;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
|
|||
|
|
@ -597,6 +597,7 @@ export function compositionEndNoteText() {
|
|||
|
||||
export function updateTextForToolbarBtn($toolbarBtn) {
|
||||
const $textArea = $toolbarBtn.closest('.md-area').find('textarea');
|
||||
if (!$textArea.length) return;
|
||||
|
||||
switch ($toolbarBtn.data('mdCommand')) {
|
||||
case 'indentLines':
|
||||
|
|
|
|||
|
|
@ -75,3 +75,43 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
$sticky-header-z-index: 98;
|
||||
|
||||
.settings-sticky-header,
|
||||
.settings-sticky-footer {
|
||||
position: sticky;
|
||||
z-index: $sticky-header-z-index;
|
||||
background: $body-bg;
|
||||
}
|
||||
|
||||
.settings-sticky-header {
|
||||
top: $calc-application-header-height;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: $gl-padding-8;
|
||||
position: sticky;
|
||||
top: calc(#{$calc-application-header-height} + 40px);
|
||||
box-shadow: 0 1px 1px $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-sticky-header-inner {
|
||||
position: sticky;
|
||||
padding: $gl-padding $gl-padding $gl-padding-12;
|
||||
margin: #{-$gl-padding} #{-$gl-padding} 0;
|
||||
background: $body-bg;
|
||||
}
|
||||
|
||||
.settings-sticky-footer {
|
||||
bottom: 0;
|
||||
padding-top: $gl-padding-8;
|
||||
padding-bottom: $gl-padding-8;
|
||||
box-shadow: 0 #{-$gl-padding-4} $gl-padding-12 $gl-padding-4 $body-bg;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?)
|
||||
push_frontend_feature_flag(:mr_activity_filters, current_user)
|
||||
push_frontend_feature_flag(:review_apps_redeploy_mr_widget, project)
|
||||
push_frontend_feature_flag(:comment_on_files, current_user)
|
||||
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ class WebHookService
|
|||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
|
||||
Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name),
|
||||
Gitlab::WebHooks::GITLAB_UUID_HEADER => SecureRandom.uuid,
|
||||
Gitlab::WebHooks::GITLAB_INSTANCE_HEADER => Gitlab.config.gitlab.base_url
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,24 +8,25 @@
|
|||
.js-user-profile
|
||||
- else
|
||||
= gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f|
|
||||
.js-search-settings-section.gl-pb-6
|
||||
.profile-settings-sidebar
|
||||
%h4.gl-my-0
|
||||
= s_("Profiles|Public avatar")
|
||||
%p.gl-text-secondary
|
||||
- if @user.avatar?
|
||||
- if gravatar_enabled?
|
||||
= s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
|
||||
- else
|
||||
= s_("Profiles|You can change your avatar here")
|
||||
.settings-section.js-search-settings-section
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0
|
||||
= s_("Profiles|Public avatar")
|
||||
%p.gl-text-secondary
|
||||
- if @user.avatar?
|
||||
- if gravatar_enabled?
|
||||
= s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
|
||||
- else
|
||||
- if gravatar_enabled?
|
||||
= s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
|
||||
- else
|
||||
= s_("Profiles|You can upload your avatar here")
|
||||
- if current_appearance&.profile_image_guidelines?
|
||||
.md
|
||||
= brand_profile_image_guidelines
|
||||
= s_("Profiles|You can change your avatar here")
|
||||
- else
|
||||
- if gravatar_enabled?
|
||||
= s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
|
||||
- else
|
||||
= s_("Profiles|You can upload your avatar here")
|
||||
- if current_appearance&.profile_image_guidelines?
|
||||
.md
|
||||
= brand_profile_image_guidelines
|
||||
.avatar-image
|
||||
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
|
||||
= render Pajamas::AvatarComponent.new(@user, size: 96, alt: "", class: 'gl-float-left gl-mr-5')
|
||||
|
|
@ -43,11 +44,13 @@
|
|||
button_options: { class: 'gl-mt-3', data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") } },
|
||||
method: :delete) do
|
||||
= s_("Profiles|Remove avatar")
|
||||
.gl-pb-8
|
||||
|
||||
.js-search-settings-section.gl-border-t.gl-py-6
|
||||
.profile-settings-sidebar
|
||||
%h4.gl-my-0= s_("Profiles|Current status")
|
||||
%p.gl-text-secondary= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
|
||||
.settings-section.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0= s_("Profiles|Current status")
|
||||
%p.gl-text-secondary= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
|
||||
.gl-max-w-80
|
||||
#js-user-profile-set-status-form
|
||||
= f.fields_for :status, @user.status do |status_form|
|
||||
|
|
@ -57,22 +60,26 @@
|
|||
= status_form.hidden_field :clear_status_after,
|
||||
value: user_clear_status_at(@user),
|
||||
data: { js_name: 'clearStatusAfter' }
|
||||
.gl-pb-7
|
||||
|
||||
.user-time-preferences.js-search-settings-section.gl-border-t.gl-py-6
|
||||
.profile-settings-sidebar
|
||||
%h4.gl-my-0= s_("Profiles|Time settings")
|
||||
%p.gl-text-secondary= s_("Profiles|Set your local time zone.")
|
||||
.settings-section.user-time-preferences.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0= s_("Profiles|Time settings")
|
||||
%p.gl-text-secondary= s_("Profiles|Set your local time zone.")
|
||||
= f.label :user_timezone, _("Time zone")
|
||||
.js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } }
|
||||
.gl-pb-7
|
||||
|
||||
.js-search-settings-section.gl-border-t.gl-py-6
|
||||
.profile-settings-sidebar
|
||||
%h4.gl-my-0
|
||||
= s_("Profiles|Main settings")
|
||||
%p.gl-text-secondary
|
||||
= s_("Profiles|This information will appear on your profile.")
|
||||
- if current_user.ldap_user?
|
||||
= s_("Profiles|Some options are unavailable for LDAP accounts")
|
||||
.settings-section.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0
|
||||
= s_("Profiles|Main settings")
|
||||
%p.gl-text-secondary
|
||||
= s_("Profiles|This information will appear on your profile.")
|
||||
- if current_user.ldap_user?
|
||||
= s_("Profiles|Some options are unavailable for LDAP accounts")
|
||||
.form-group.gl-form-group.rspec-full-name.gl-max-w-80
|
||||
= render 'profiles/name', form: f, user: @user
|
||||
.form-group.gl-form-group.gl-md-form-input-lg
|
||||
|
|
@ -161,7 +168,9 @@
|
|||
= s_("Profiles|Achievements")
|
||||
= f.gitlab_ui_checkbox_component :achievements_enabled,
|
||||
s_('Profiles|Display achievements on your profile')
|
||||
.js-hide-when-nothing-matches-search.gl-border-t.gl-py-6
|
||||
.gl-pb-7
|
||||
|
||||
.js-hide-when-nothing-matches-search.settings-sticky-footer
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-mr-3 js-password-prompt-btn', pajamas_button: true
|
||||
= render Pajamas::ButtonComponent.new(href: user_path(current_user)) do
|
||||
= s_('TagsPage|Cancel')
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: comment_on_files
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121429
|
||||
rollout_issue_url:
|
||||
milestone: '16.1'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: true
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
return unless File.exist?(Rails.root.join('config/click_house.yml'))
|
||||
|
||||
raw_config = Rails.application.config_for(:click_house)
|
||||
return if raw_config.blank?
|
||||
|
||||
ClickHouse::Client.configure do |config|
|
||||
raw_config.each do |database_identifier, db_config|
|
||||
config.register_database(database_identifier,
|
||||
database: db_config[:database],
|
||||
url: db_config[:url],
|
||||
username: db_config[:username],
|
||||
password: db_config[:password],
|
||||
variables: db_config[:variables] || {}
|
||||
)
|
||||
end
|
||||
|
||||
config.json_parser = Gitlab::Json
|
||||
config.http_post_proc = ->(url, headers, body) do
|
||||
options = {
|
||||
headers: headers,
|
||||
body: body,
|
||||
allow_local_requests: Rails.env.development? || Rails.env.test?
|
||||
}
|
||||
|
||||
response = Gitlab::HTTP.post(url, options)
|
||||
ClickHouse::Client::Response.new(response.body, response.code)
|
||||
end
|
||||
end
|
||||
|
|
@ -9934,6 +9934,29 @@ The connection type for [`MergeRequest`](#mergerequest).
|
|||
| <a id="mergerequestconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
| <a id="mergerequestconnectiontotaltimetomerge"></a>`totalTimeToMerge` | [`Float`](#float) | Total sum of time to merge, in seconds, for the collection of merge requests. |
|
||||
|
||||
#### `MergeRequestDiffConnection`
|
||||
|
||||
The connection type for [`MergeRequestDiff`](#mergerequestdiff).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestdiffconnectionedges"></a>`edges` | [`[MergeRequestDiffEdge]`](#mergerequestdiffedge) | A list of edges. |
|
||||
| <a id="mergerequestdiffconnectionnodes"></a>`nodes` | [`[MergeRequestDiff]`](#mergerequestdiff) | A list of nodes. |
|
||||
| <a id="mergerequestdiffconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `MergeRequestDiffEdge`
|
||||
|
||||
The edge type for [`MergeRequestDiff`](#mergerequestdiff).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestdiffedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="mergerequestdiffedgenode"></a>`node` | [`MergeRequestDiff`](#mergerequestdiff) | The item at the end of the edge. |
|
||||
|
||||
#### `MergeRequestDiffLlmSummaryConnection`
|
||||
|
||||
The connection type for [`MergeRequestDiffLlmSummary`](#mergerequestdiffllmsummary).
|
||||
|
|
@ -10014,6 +10037,29 @@ The edge type for [`MergeRequestParticipant`](#mergerequestparticipant).
|
|||
| <a id="mergerequestparticipantedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="mergerequestparticipantedgenode"></a>`node` | [`MergeRequestParticipant`](#mergerequestparticipant) | The item at the end of the edge. |
|
||||
|
||||
#### `MergeRequestReviewLlmSummaryConnection`
|
||||
|
||||
The connection type for [`MergeRequestReviewLlmSummary`](#mergerequestreviewllmsummary).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestreviewllmsummaryconnectionedges"></a>`edges` | [`[MergeRequestReviewLlmSummaryEdge]`](#mergerequestreviewllmsummaryedge) | A list of edges. |
|
||||
| <a id="mergerequestreviewllmsummaryconnectionnodes"></a>`nodes` | [`[MergeRequestReviewLlmSummary]`](#mergerequestreviewllmsummary) | A list of nodes. |
|
||||
| <a id="mergerequestreviewllmsummaryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `MergeRequestReviewLlmSummaryEdge`
|
||||
|
||||
The edge type for [`MergeRequestReviewLlmSummary`](#mergerequestreviewllmsummary).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestreviewllmsummaryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="mergerequestreviewllmsummaryedgenode"></a>`node` | [`MergeRequestReviewLlmSummary`](#mergerequestreviewllmsummary) | The item at the end of the edge. |
|
||||
|
||||
#### `MergeRequestReviewerConnection`
|
||||
|
||||
The connection type for [`MergeRequestReviewer`](#mergerequestreviewer).
|
||||
|
|
@ -17575,6 +17621,7 @@ Defines which user roles, users, or groups can merge into a protected branch.
|
|||
| <a id="mergerequestmergecommitsha"></a>`mergeCommitSha` | [`String`](#string) | SHA of the merge request commit (set once merged). |
|
||||
| <a id="mergerequestmergeerror"></a>`mergeError` | [`String`](#string) | Error message due to a merge error. |
|
||||
| <a id="mergerequestmergeongoing"></a>`mergeOngoing` | [`Boolean!`](#boolean) | Indicates if a merge is currently occurring. |
|
||||
| <a id="mergerequestmergerequestdiffs"></a>`mergeRequestDiffs` **{warning-solid}** | [`MergeRequestDiffConnection`](#mergerequestdiffconnection) | **Introduced** in 16.2. This feature is an Experiment. It can be changed or removed at any time. Diff versions of a merge request. |
|
||||
| <a id="mergerequestmergestatus"></a>`mergeStatus` **{warning-solid}** | [`String`](#string) | **Deprecated** in 14.0. This was renamed. Use: [`MergeRequest.mergeStatusEnum`](#mergerequestmergestatusenum). |
|
||||
| <a id="mergerequestmergestatusenum"></a>`mergeStatusEnum` | [`MergeStatus`](#mergestatus) | Merge status of the merge request. |
|
||||
| <a id="mergerequestmergetrainscount"></a>`mergeTrainsCount` | [`Int`](#int) | Number of merge requests in the merge train. |
|
||||
|
|
@ -18267,6 +18314,19 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="mergerequestauthorworkspacesincludeactualstates"></a>`includeActualStates` | [`[String!]`](#string) | Includes all workspaces that match any of the actual states. |
|
||||
| <a id="mergerequestauthorworkspacesprojectids"></a>`projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project id. |
|
||||
|
||||
### `MergeRequestDiff`
|
||||
|
||||
A diff version of a merge request.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestdiffcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the diff was created. |
|
||||
| <a id="mergerequestdiffdiffllmsummary"></a>`diffLlmSummary` | [`MergeRequestDiffLlmSummary`](#mergerequestdiffllmsummary) | Diff summary generated by AI. |
|
||||
| <a id="mergerequestdiffreviewllmsummaries"></a>`reviewLlmSummaries` | [`MergeRequestReviewLlmSummaryConnection`](#mergerequestreviewllmsummaryconnection) | Review summaries generated by AI. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestdiffupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the diff was updated. |
|
||||
|
||||
### `MergeRequestDiffLlmSummary`
|
||||
|
||||
A diff summary generated by AI.
|
||||
|
|
@ -18595,6 +18655,22 @@ Check permissions for the current user on a merge request.
|
|||
| <a id="mergerequestpermissionsrevertoncurrentmergerequest"></a>`revertOnCurrentMergeRequest` | [`Boolean!`](#boolean) | Indicates the user can perform `revert_on_current_merge_request` on this resource. |
|
||||
| <a id="mergerequestpermissionsupdatemergerequest"></a>`updateMergeRequest` | [`Boolean!`](#boolean) | Indicates the user can perform `update_merge_request` on this resource. |
|
||||
|
||||
### `MergeRequestReviewLlmSummary`
|
||||
|
||||
A review summary generated by AI.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mergerequestreviewllmsummarycontent"></a>`content` | [`String!`](#string) | Content of the review summary. |
|
||||
| <a id="mergerequestreviewllmsummarycreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the review summary was created. |
|
||||
| <a id="mergerequestreviewllmsummarymergerequestdiffid"></a>`mergeRequestDiffId` | [`ID!`](#id) | ID of the Merge Request diff associated with the review summary. |
|
||||
| <a id="mergerequestreviewllmsummaryprovider"></a>`provider` | [`String!`](#string) | AI provider that generated the summary. |
|
||||
| <a id="mergerequestreviewllmsummaryreviewer"></a>`reviewer` | [`UserCore`](#usercore) | User who authored the review associated with the review summary. |
|
||||
| <a id="mergerequestreviewllmsummaryupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the review summary was updated. |
|
||||
| <a id="mergerequestreviewllmsummaryuser"></a>`user` | [`UserCore`](#usercore) | User associated with the review summary. |
|
||||
|
||||
### `MergeRequestReviewer`
|
||||
|
||||
A user assigned to a merge request as a reviewer.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ in the following table.
|
|||
| Scope | Description |
|
||||
| ----- | ----------- |
|
||||
| `api` | Allows read-write access to the repository files. |
|
||||
| `read_api` | Allows read access to the repository files. |
|
||||
| `read_repository` | Allows read-access to the repository files. |
|
||||
|
||||
## Get file from repository
|
||||
|
|
|
|||
|
|
@ -22,6 +22,21 @@ This function takes pagination parameters `page` and `per_page` to restrict the
|
|||
GET /users
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `username` | string | no | Get a single user with a specific username. |
|
||||
| `extern_uid` | string | no | Get a single user with a specific external authentication provider UID. |
|
||||
| `provider` | string | no | The external provider. |
|
||||
| `search` | string | no | Search for a username. |
|
||||
| `active` | boolean | no | Filters only active users. Default is `false`. |
|
||||
| `external` | boolean | no | Filters only external users. Default is `false`. |
|
||||
| `exclude_external` | boolean | no | Filters only non external users. Default is `false`. |
|
||||
| `blocked` | boolean | no | Filters only blocked users. Default is `false`. |
|
||||
| `created_after` | DateTime| no | Returns users created after specified time. |
|
||||
| `created_before` | DateTime| no | Returns users created before specified time. |
|
||||
| `exclude_internal` | boolean | no | Filters only non internal users. Default is `false`. |
|
||||
| `without_projects_bots`| boolean | no | Filters user without project bots. Default is `false`. |
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
|
|
@ -121,6 +136,8 @@ GET /users?without_project_bots=true
|
|||
GET /users
|
||||
```
|
||||
|
||||
You can use all [parameters available for everyone](#for-non-administrator-users) plus these additional parameters available only for administrators.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `order_by` | string | no | Return users ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
status: proposed
|
||||
creation-date: "2023-06-21"
|
||||
authors: [ "@fabiopitino" ]
|
||||
coach: [ ]
|
||||
approvers: [ ]
|
||||
owning-stage: ""
|
||||
---
|
||||
|
||||
# Defining bounded contexts
|
||||
|
||||
## Status quo
|
||||
|
||||
Today the GitLab codebase doesn't have a clear domain structure.
|
||||
We have [forced the creation of some modules](https://gitlab.com/gitlab-org/gitlab/-/issues/212156)
|
||||
as a first step but we don't have a well defined strategy for doing it consistently.
|
||||
|
||||
The majority of the code is not properly namespaced and organized:
|
||||
|
||||
- Ruby namespaces used don't always represent the SSoT. We have overlapping concepts spread across multiple
|
||||
namespaces. For example: `Abuse::` and `Spam::` or `Security::Orchestration::` and `Security::SecurityOrchestration`.
|
||||
- Domain code related to the same bounded context is scattered across multiple directories.
|
||||
- Domain code is present in `lib/` directory under namespaces that differ from the same domain under `app/`.
|
||||
- Some namespaces are very shallow, containing a few classes while other namespaces are very deep and large.
|
||||
- A lot of the old code is not namespaced, making it difficult to understand the context where it's used.
|
||||
|
||||
## Goal
|
||||
|
||||
1. Define a list of characteristics that bounded contexts should have. For example: must relate to at least 1 product category.
|
||||
1. Have a list of top-level bounded contexts where all domain code is broken down into.
|
||||
1. Engineers can clearly see the list of available bounded contexts and can make an easy decision where to add
|
||||
new classes and modules.
|
||||
1. Define a process for adding a new bounded context to the application. This should occur quite infrequently
|
||||
and new bounded contexts need to adhere to the characteristics defined previously.
|
||||
1. Enforce the list of bounded contexts so that no new top-level namespaces can be used aside from the authorized ones.
|
||||
|
||||
## Iterations
|
||||
|
||||
### 0. Extract libraries out of the codebase
|
||||
|
||||
In June 2023 we've started extracing gems out of the main codebase, into
|
||||
[`gems/` directory inside the monorepo](https://gitlab.com/gitlab-org/gitlab/-/blob/4c6e120069abe751d3128c05ade45ea749a033df/doc/development/gems.md).
|
||||
|
||||
This is our first step towards modularization: externalize code that can be
|
||||
extracted to prevent coupling from being introduced into modules that have been
|
||||
designed as separate components.
|
||||
|
||||
These gems as still part of the monorepo.
|
||||
|
||||
### 1. What makes a bounded context?
|
||||
|
||||
From the research in [Proposal: split GitLab monolith into components](https://gitlab.com/gitlab-org/gitlab/-/issues/365293)
|
||||
it seems that following [product categories](https://about.gitlab.com/handbook/product/categories/#hierarchy), as a guideline,
|
||||
would be much better than translating organization structure into folder structure (e.g. `app/modules/verify/pipeline-execution/...`).
|
||||
|
||||
However, this guideline alone is not sufficient and we need a more specific strategy:
|
||||
|
||||
- Product categories can change ownership and we have seen some pretty frequent changes, even back and forth.
|
||||
Moving code every time a product category changes ownership adds too much maintenance overhead.
|
||||
- Teams and organization changes should just mean relabelling the ownership of specific modules.
|
||||
- Bounded contexts (top level modules) should be [sufficiently deep](../../../development/software_design.md#use-namespaces-to-define-bounded-contexts)
|
||||
to encapsulate implementation details and provide a smaller interface.
|
||||
- Some product categories, such as Browser Performance Testing, are just too small to represent a bounded context on their own.
|
||||
We should have a strategy for grouping product categories together when makes sense.
|
||||
- Product categories don't necessarily translate into clean boundaries.
|
||||
`Category:Pipeline Composition` and `Category:Continuous Integration` are some examples where Pipeline Authoring team
|
||||
and Pipeline Execution team share a lot of code.
|
||||
- Some parts of the code might not have a clear product category associated to it.
|
||||
|
||||
Despite the above, product categories provide a rough view of the bounded contexts at play in the application.
|
||||
|
||||
One idea could be to use product categories to sketch the initial set of bounded contexts.
|
||||
Then, group related or strongly coupled categories under the same bounded context and create new bounded contexts if missing.
|
||||
|
||||
### 2. Identify existing bounded contexts
|
||||
|
||||
Start with listing all the Ruby files in a spreadsheet and categorize them into components following the guidelines above.
|
||||
Some of them are already pretty explicit like Ci::, Packages::, etc. Components should follow our
|
||||
[existing naming guide](../../../development/software_design.md#use-namespaces-to-define-bounded-contexts).
|
||||
|
||||
This could be a short-lived Working Group with representative members of each DevOps stage (e.g. Senior+ engineers).
|
||||
The WG would help defining high-level components and will be the DRIs for driving the changes in their respective DevOps stage.
|
||||
|
||||
### 3. Publish the list of bounded contexts
|
||||
|
||||
The list of bounded contexts (top-level namespaces) extracted from the codebase should be defined statically so it can be
|
||||
used programmatically.
|
||||
|
||||
```yaml
|
||||
# file: config/bounded_contexts.yml
|
||||
bounded_contexts:
|
||||
continuous_integration:
|
||||
dir: modules/ci
|
||||
namespace: 'Ci::'
|
||||
packages: ...
|
||||
merge_requests: ...
|
||||
git: ...
|
||||
```
|
||||
|
||||
With this static list we could:
|
||||
|
||||
- Document the existing bounded contexts for engineers to see the big picture.
|
||||
- Understand where to place new classes and modules.
|
||||
- Enforce if any top-level namespaces are used that are not in the list of bounded contexts.
|
||||
- Autoload non-standard Rails directories based on the given list.
|
||||
|
||||
## Glossary
|
||||
|
||||
- `modules` are Ruby modules and can be used to nest code hierarchically.
|
||||
- `namespaces` are unique hierarchies of Ruby constants. E.g. `Ci::` but also `Ci::JobArtifacts::` or `Ci::Pipeline::Chain::`.
|
||||
- `packages` are Packwerk packages to group together related functionalities. These packages can be big or small depending on the design and architecture. Inside a package all constants (classes and modules) have the same namespace. For example:
|
||||
- In a package `ci`, all the classes would be nested under `Ci::` namespace. There can be also nested namespaces like `Ci::PipelineProcessing::`.
|
||||
- In a package `ci-pipeline_creation` all classes are nested under `Ci::PipelineCreation`, like `Ci::PipelineCreation::Chain::Command`.
|
||||
- In a package `ci` a class named `MergeRequests::UpdateHeadPipelineService` would not be allowed because it would not match the package's namespace.
|
||||
- This can be enforced easily with [Packwerk's based Rubocop Cops](https://github.com/rubyatscale/rubocop-packs/blob/main/lib/rubocop/cop/packs/root_namespace_is_pack_name.rb).
|
||||
- `bounded context` is a top-level Packwerk package that represents a macro aspect of the domain. For example: `Ci::`, `MergeRequests::`, `Packages::`, etc.
|
||||
- A bounded context is represented by a single Ruby module/namespace. E.g. `Ci::` and not `Ci::JobArtifacts::`.
|
||||
- A bounded context can be made of 1 or multiple Packwerk packages. Nested packages would be recommended if the domain is quite complex and we want to enforce privacy among all the implementation details. For example: `Ci::PipelineProcessing::` and `Ci::PipelineCreation::` could be separate packages of the same bounded context and expose their public API while keeping implementation details private.
|
||||
- A new bounded context like `RemoteDevelopment::` can be represented a single package while large and complex bounded contexts like `Ci::` would need to be organized into smaller/nested packages.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
status: proposed
|
||||
creation-date: "2023-05-22"
|
||||
authors: [ "@fabiopitino" ]
|
||||
coach: [ ]
|
||||
approvers: [ ]
|
||||
owning-stage: ""
|
||||
---
|
||||
|
||||
# Hexagonal Rails Monolith
|
||||
|
||||
## Summary
|
||||
|
||||
**TL;DR:** Change the Rails monolith from a [big ball of mud](https://en.wikipedia.org/wiki/Big_ball_of_mud) state to
|
||||
a [modular monolith](https://www.thereformedprogrammer.net/my-experience-of-using-modular-monolith-and-ddd-architectures)
|
||||
that uses an [Hexagonal architecture](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)) (or ports and adapters architecture).
|
||||
Extract cohesive functional domains into separate directory structure using Domain-Driven Design practices.
|
||||
Extract infrastructure code (logging, database tools, instrumentation, etc.) into gems, essentially remove the need for `lib/` directory.
|
||||
Define what parts of the functional domains (for example application services) are of public use for integration (the ports)
|
||||
and what parts are instead private encapsulated details.
|
||||
Define Web, Sidekiq, REST, GraphQL, and Action Cable as the adapters in the external layer of the architecture.
|
||||
Use [Packwerk](https://github.com/Shopify/packwerk) to enforce privacy and dependency between modules of the monolith.
|
||||
|
||||

|
||||
|
||||
## Details
|
||||
|
||||
### Application domain
|
||||
|
||||
The application core (functional domains) is divided into separate top-level bounded contexts called after the
|
||||
[feature category](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml) they represent.
|
||||
A bounded-context is represented in the form of a Ruby module.
|
||||
This follows the existing [guideline on naming namespaces](../../../../development/software_design.md#use-namespaces-to-define-bounded-contexts) but puts more structure to it.
|
||||
|
||||
Modules should:
|
||||
|
||||
- Be deep enough to encapsulate a lot of the internal logic, state and data.
|
||||
- Have a public interface that is as small as possible, safe to use by other bounded contexts and well documented.
|
||||
- Be cohesive and represent the SSoT (single source of truth) of the feature it describes.
|
||||
|
||||
Feature categories represent a product area that is large enough for the module to be deep, so we don't have a proliferation
|
||||
of small top-level modules. It also helps the codebase to follow the
|
||||
[ubiquitous language](../../../../development/software_design.md#use-ubiquitous-language-instead-of-crud-terminology).
|
||||
A team can be responsible for multiple feature categories, hence owning the vision for multiple bounded contexts.
|
||||
While feature categories can sometimes change ownership, this change of mapping the bounded context to new owners
|
||||
is very cheap.
|
||||
Using feature categories also helps new contributors, either as GitLab team members of members of the wider community,
|
||||
to navigate the codebase.
|
||||
|
||||
If multiple feature categories are strongly related, they may be grouped under a single bounded context.
|
||||
If a feature category is only relevant in the context of a parent feature category, it may be included in the
|
||||
parent's bounded context. For example: Build artifacts existing in the context of Continuous Integration feature category
|
||||
and they may be merged under a single bounded context.
|
||||
|
||||
### Application adapters
|
||||
|
||||
>>>
|
||||
_Adapters are the glue between components and the outside world._
|
||||
_They tailor the exchanges between the external world and the ports that represent the requirements of the inside_
|
||||
_of the application component. There can be several adapters for one port, for example, data can be provided by_
|
||||
_a user through a GUI or a command-line interface, by an automated data source, or by test scripts._ -
|
||||
[Wikipedia](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)#Principle)
|
||||
>>>
|
||||
|
||||
Application adapters would be:
|
||||
|
||||
- Web UI (Rails controllers, view, JS and Vue client)
|
||||
- REST API endpoints
|
||||
- GraphQL Endpoints
|
||||
- Action Cable
|
||||
|
||||
TODO: continue describing how adapters are organized and why they are separate from the domain code.
|
||||
|
||||
### Platform code
|
||||
|
||||
For platform code we consider any classes and modules that are required by the application domain and/or application
|
||||
adapters to work.
|
||||
|
||||
The Rails' `lib/` directory today contains multiple categories of code that could live somewhere else,
|
||||
most of which is platform code:
|
||||
|
||||
- REST API endpoints could be part of the [application adapters](#application-adapters).
|
||||
- domain code (both large domain code such as `Gitlab::Ci` and small such as `Gitlab::JiraImport`) should be
|
||||
moved inside the [application domain](#application-domain).
|
||||
- The rest could be extracted as separate single-purpose gems under the `gems/` directory inside the monolith.
|
||||
This can include utilities such as logging, error reporting and metrics, rate limiters,
|
||||
infrastructure code like `Gitlab::ApplicationRateLimiter`, `Gitlab::Redis`, `Gitlab::Database`
|
||||
and generic subdomains like `Banzai`.
|
||||
|
||||
Base classes to extend Rails framework such as `ApplicationRecord` or `ApplicationWorker` as well as GitLab base classes
|
||||
such as `BaseService` could be implemented as gem extensions.
|
||||
|
||||
This means that aside from the Rails framework code, the rest of the platform code resides in `gems/`.
|
||||
|
||||
Eventually all code inside `gems/` could potentially be extracted in a separate repository or open sourced.
|
||||
Placing platform code inside `gems/` makes it clear that its purpose is to serve the application code.
|
||||
|
||||
### Why Packwerk?
|
||||
|
||||
TODO:
|
||||
|
||||
- boundaries not enforced at runtime. Ruby code will still work as being all loaded in the same memory space.
|
||||
- can be introduced incrementally. Not everything requires to be moved to packs for the Rails autoloader to work.
|
||||
|
||||
Companies like Gusto have been developing and maintaining a list of [development and engineering tools](https://github.com/rubyatscale)
|
||||
for organizations that want to move to using a Rails modular monolith around Packwerk.
|
||||
|
||||
### EE and JH extensions
|
||||
|
||||
TODO:
|
||||
|
||||
## Challenges
|
||||
|
||||
- Such changes require a shift in the development mindset to understand the benefits of the modular
|
||||
architecture and not fallback into legacy practices.
|
||||
- Changing the application architecture is a challenging task. It takes time, resources and commitment
|
||||
but most importantly it requires buy-in from engineers.
|
||||
- This may require us to have a medium-long term team of engineers or a Working Group that makes progresses
|
||||
on the architecture evolution plan, foster discussions in various engineering channels and resolve adoption challenges.
|
||||
- We need to ensure we build standards and guidelines and not silos.
|
||||
- We need to ensure we have clear guidelines on where new code should be placed. We must not recreate junk drawer folders like `lib/`.
|
||||
|
||||
## Opportunities
|
||||
|
||||
The move to a modular monolith architecture enables a lot of opportunities that we could explore in the future:
|
||||
|
||||
- We could align the concept of domain expert with explicitly owning specific modules of the monolith.
|
||||
- The use of static analysis tool (such as Packwerk, Rubocop) can catch design violations in development and CI, ensuring
|
||||
that best practices are honored.
|
||||
- By defining dependencies between modules explicitly we could speed up CI by testing only the parts that are affected by
|
||||
the changes.
|
||||
- Such modular architecture could help to further decompose modules into separate services if needed.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
status: proposed
|
||||
creation-date: "2023-05-22"
|
||||
authors: [ "@grzesiek", "@fabiopitino" ]
|
||||
coach: [ ]
|
||||
approvers: [ ]
|
||||
owning-stage: ""
|
||||
participating-stages: []
|
||||
---
|
||||
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
|
||||
# GitLab Modular Monolith
|
||||
|
||||
## Summary
|
||||
|
||||
The main [GitLab Rails](https://gitlab.com/gitlab-org/gitlab)
|
||||
project has been implemented as a large monolithic application, using
|
||||
[Ruby on Rails](https://rubyonrails.org/) framework. It has over 2.2 million
|
||||
lines of Ruby code and hundreds of engineers contributing to it every day.
|
||||
|
||||
The application has been growing in complexity for more than a decade. The
|
||||
monolithic architecture has served us well during this time, making it possible
|
||||
to keep high development velocity and great engineering productivity.
|
||||
|
||||
Even though we strive for having [an approachable open-core architecture](https://about.gitlab.com/blog/2022/07/14/open-core-is-worse-than-plugins/)
|
||||
we need to strengthen the boundaries between domains to retain velocity and
|
||||
increase development predictability.
|
||||
|
||||
As we grow as an engineering organization, we want to explore a slightly
|
||||
different, but related, architectural paradigm:
|
||||
[a modular monolith design](https://en.wikipedia.org/wiki/Modular_programming),
|
||||
while still using a [monolithic architecture](https://en.wikipedia.org/wiki/Monolithic_application)
|
||||
with satellite services.
|
||||
|
||||
This should allow us to increase engineering efficiency, reduce the cognitive
|
||||
load, and eventually decouple internal components to the extend that will allow
|
||||
us to deploy and run them separately if needed.
|
||||
|
||||
## Motivation
|
||||
|
||||
Working with a large and tightly coupled monolithic application is challenging:
|
||||
|
||||
Engineering:
|
||||
|
||||
- Onboarding engineers takes time. It takes a while before engineers feel
|
||||
productive due to the size of the context and the amount of coupling.
|
||||
- We need to use `CODEOWNERS` file feature for several domains but
|
||||
[these rules are complex](https://gitlab.com/gitlab-org/gitlab/-/blob/409228f064a950af8ff2cecdd138fc9da41c8e63/.gitlab/CODEOWNERS#L1396-1457).
|
||||
- It is difficult for engineers to build a mental map of the application due to its size.
|
||||
Even apparently isolated changes can have [far-reaching repercussions](https://about.gitlab.com/handbook/engineering/development/#reducing-the-impact-of-far-reaching-work)
|
||||
on other parts of the monolith.
|
||||
- Attrition/retention of engineering talent. It is fatiguing and demoralizing for
|
||||
engineers to constantly deal with the obstacles to productivity.
|
||||
|
||||
Architecture:
|
||||
|
||||
- There is little structure inside the monolith. We have attempted to enforce
|
||||
the creation [of some modules](https://gitlab.com/gitlab-org/gitlab/-/issues/212156)
|
||||
but have no company-wide strategy on what the functional parts of the
|
||||
monolith should be, and how code should be organized.
|
||||
- There is no isolation between existing modules. Ruby does not provide
|
||||
out-of-the-box tools to effectively enforce boundaries. Everything lives
|
||||
under the same memory space.
|
||||
- We rarely build abstractions that can boost our efficiency.
|
||||
- Moving stable parts of the application into separate services is impossible
|
||||
due to high coupling.
|
||||
- We are unable to deploy changes to specific domains separately and isolate
|
||||
failures that are happening inside them.
|
||||
|
||||
Productivity:
|
||||
|
||||
- High median-time-to-production for complex changes.
|
||||
- It can be overwhelming for the wider-community members to contribute.
|
||||
- Reducing testing times requires diligent and persistent efforts.
|
||||
|
||||
## Goals
|
||||
|
||||
- Increase the development velocity and predicability through separation of concerns.
|
||||
- Improve code quality by reducing coupling and introducing useful abstractions.
|
||||
- Build abstractions required to deploy and run GitLab components separately.
|
||||
|
||||
## How do we get there?
|
||||
|
||||
While we do recognize that modularization is a significant technical endeavor,
|
||||
we believe that the main challenge is organizational, rather than technical. We
|
||||
not only need to design separation in a way that modules are decoupled in a
|
||||
pragmatic way which works well on GitLab.com but also on self-managed
|
||||
instances, but we need to align modularization with the way in which we want to
|
||||
work at GitLab.
|
||||
|
||||
There are many aspects and details required to make modularization of our
|
||||
monolith successful. We will work on the aspects listed below, refine them, and
|
||||
add more important details as we move forward towards the goal:
|
||||
|
||||
1. [Deliver modularization proof-of-concepts that will deliver key insights](proof_of_concepts.md)
|
||||
1. [Align modularization plans to the organizational structure](bounded_contexts.md)
|
||||
1. Start a training program for team members on how to work with decoupled domains (TODO)
|
||||
1. Build tools that will make it easier to build decoupled domains through inversion of control (TODO)
|
||||
1. Separate domains into modules that will reflect organizational structure (TODO)
|
||||
1. Build necessary services to align frontend and backend modularization (TODO)
|
||||
1. [Introduce hexagonal architecture within the monolith](hexagonal_monolith/index.md)
|
||||
1. Introduce clean architecture with one-way-dependencies and host application (TODO)
|
||||
1. Build abstractions that will make it possible to run and deploy domains separately (TODO)
|
||||
|
||||
## Status
|
||||
|
||||
In progress.
|
||||
|
||||
## References
|
||||
|
||||
[List of references](references.md)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
status: proposed
|
||||
creation-date: "2023-07-05"
|
||||
authors: [ "@grzesiek" ]
|
||||
coach: [ ]
|
||||
owners: [ ]
|
||||
---
|
||||
|
||||
# Modular Monolithk: PoCs
|
||||
|
||||
Modularization of our monolith is a complex project. There will be many
|
||||
unknowns. One thing that can help us mitigate the risks and deliver key
|
||||
insights are Proof-of-Concepts that we could deliver early on, to better
|
||||
understand what will need to be done.
|
||||
|
||||
## Inter-module communicaton PoC
|
||||
|
||||
First PoC that we plan to deliver is a PoC of inter-module communication. We do
|
||||
recognize the need to separate modules, but still allow them to communicate
|
||||
together using a well defined interface. Modules can communicate through a
|
||||
facade classes (like libraries usually do), or through evening system. Both
|
||||
ways are important.
|
||||
|
||||
The main question is: how do we want to define the interface and how to design
|
||||
the communication channels.
|
||||
|
||||
It is one of our goals to make it possible to plug modules out, and operate
|
||||
some of them as separate services. This will make it easier deploy GitLab.com
|
||||
in the future and scale key domains. One possible way to achieve this goal
|
||||
would be to design the inter-module communication using a protobuf as an
|
||||
interface and gRPC as a communication channel. When modules are plugged-in, we
|
||||
would bypass gRPC and serialization and use in-process communication primitives
|
||||
(while still using protobuf as an interface). When a module gets plugged-out,
|
||||
gRPC would carry messages between modules.
|
||||
|
||||
## Frontend sorting hat PoC
|
||||
|
||||
Frontend sorting-hat is a PoC for combining multiple domains to render a full
|
||||
page of GitLab (with menus, and items that come from multiple separate
|
||||
domains).
|
||||
|
||||
## Frontend assets aggregation PoC
|
||||
|
||||
Frontend assets aggregation is a PoC for a possible separation of micro-frontends.
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
status: proposed
|
||||
creation-date: "2023-06-21"
|
||||
authors: [ "@fabiopitino" ]
|
||||
coach: [ ]
|
||||
approvers: [ ]
|
||||
owning-stage: ""
|
||||
---
|
||||
|
||||
# References
|
||||
|
||||
## Related design docs
|
||||
|
||||
- [Composable codebase design doc](../composable_codebase_using_rails_engines/index.md)
|
||||
|
||||
## Related Issues
|
||||
|
||||
- [Split GitLab monolith into components](https://gitlab.com/gitlab-org/gitlab/-/issues/365293)
|
||||
- [Make it simple to build and use "Decoupled Services"](https://gitlab.com/gitlab-org/gitlab/-/issues/31121)
|
||||
- [Use nested structure to organize CI classes](https://gitlab.com/gitlab-org/gitlab/-/issues/209745)
|
||||
- [Create new models / classes within a module / namespace](https://gitlab.com/gitlab-org/gitlab/-/issues/212156)
|
||||
- [Make teams to be maintainers of their code](https://gitlab.com/gitlab-org/gitlab/-/issues/25872)
|
||||
- [Add backend guide for Dependency Injection](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73644)
|
||||
|
||||
## Internal Slack Channels
|
||||
|
||||
- [`#modular_monolith`](https://gitlab.slack.com/archives/C03NTK6HZBM)
|
||||
- [`#architecture`](https://gitlab.slack.com/archives/CJ4DB7517)
|
||||
|
||||
## Reference Implementations / Guides
|
||||
|
||||
Gusto / RubyAtScale:
|
||||
|
||||
- [RubyAtScale toolchain for modularization](https://github.com/rubyatscale)
|
||||
- [Gusto's engineering blog](https://engineering.gusto.com/laying-the-cultural-and-technical-foundation-for-big-rails/)
|
||||
- [Gradual modularization](https://gradualmodularization.com/) (successor to CBRA)
|
||||
- [Component-Based Rails Applications](https://cbra.info) ("deprecated")
|
||||
|
||||
Shopify:
|
||||
|
||||
- [Packwerk](https://github.com/Shopify/packwerk)
|
||||
- [Shopify's jurney to modularization](https://shopify.engineering/shopify-monolith)
|
||||
- [Internal GitLab doc transcript of an AMA with a Shopify engineer](https://docs.google.com/document/d/1uZbcaK8Aqs-D_n7_uQ5XE295r5UWDJEBwA6g5bTjcwc/edit#heading=h.d1tml5rlzrpa)
|
||||
|
||||
Domain-Driven Rails / Rails Event Store:
|
||||
|
||||
Rails Event Store is relevant because it is a mechanism to achieve many
|
||||
of the goals discussed here, and is based upon patterns used by Arkency
|
||||
to build production applications.
|
||||
|
||||
This doesn't mean we need to use this specific framework or approach.
|
||||
|
||||
However, the general concepts of DDD/ES/CQRS are important and in some
|
||||
cases maybe necessary to achieve the goals of this blueprint, so it's
|
||||
useful to have concrete production-proven implementations of those
|
||||
concepts to look at as an example.
|
||||
|
||||
- [Arkency's domain-driven Rails](https://products.arkency.com/domain-driven-rails/)
|
||||
- [Arkency's Rails Event Store](https://railseventstore.org)
|
||||
|
||||
App Continuum:
|
||||
|
||||
An illustration of how an application can evolve from a small, unstructed app, through various
|
||||
stages including a modular well-structured, monolith, all the way to a microservices architecture.
|
||||
|
||||
Includes discussion of why you might want to stop at various stages, and specifically the
|
||||
challenges/concerns with making the jump to microservices, and why sticking with a
|
||||
well-structured monolith may be preferable in many cases.
|
||||
|
||||
- [App Continuum](https://www.appcontinuum.io)
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Pipeline Security
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: tutorial
|
||||
---
|
||||
|
||||
# Tutorial: Update HashiCorp Vault configuration to use ID Tokens **(PREMIUM)**
|
||||
|
||||
This tutorial demonstrates how to convert your existing CI/CI secrets configuration to use [ID Tokens](../secrets/id_token_authentication.md).
|
||||
|
||||
The `CI_JOB_JWT` variables are deprecated, but updating to ID tokens requires some important configuration changes to work with Vault. If you have more than a handful of jobs, converting everything at once is a daunting task.
|
||||
|
||||
From GitLab 15.9 to 15.11, [enable the automatic ID token authentication](../secrets/id_token_authentication.md#enable-automatic-id-token-authentication-deprecated)
|
||||
setting to enable ID Tokens and disable `CI_JOB_JWT` tokens.
|
||||
|
||||
In GitLab 16.0 and later you can use ID tokens without any settings changes.
|
||||
Jobs that use `secrets:vault` automatically do not have `CI_JOB_JWT` tokens available,
|
||||
Jobs that don't use `secrets:vault` can still use `CI_JOB_JWT` tokens.
|
||||
|
||||
This tutorial will focus on v16 onwards, if you are running a slightly older version you will need to toggle the `Limit JSON Web Token (JWT) access` setting as appropriate.
|
||||
|
||||
To update your vault configuration to use ID tokens:
|
||||
|
||||
1. [Create a second JWT authentication path in Vault](#create-a-second-jwt-authentication-path-in-vault)
|
||||
1. [Recreate roles to use the new authentication path](#recreate-roles-to-use-the-new-authentication-path)
|
||||
1. [Update your CI/CD Jobs](#update-your-cicd-jobs)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This tutorial assumes you are familiar with GitLab CI/CD and Vault.
|
||||
|
||||
To follow along, you must have:
|
||||
|
||||
- An instance running GitLab 15.9 or later, or be on GitLab.com.
|
||||
- A Vault server that you are already using.
|
||||
- CI/CD jobs retrieving secrets from Vault with `CI_JOB_JWT`.
|
||||
|
||||
In the examples below, replace `vault.example.com` with the URL of your Vault server,
|
||||
and `gitlab.example.com` with the URL of your GitLab instance.
|
||||
|
||||
## Create a second JWT authentication path in Vault
|
||||
|
||||
As part of the transition from `CI_JOB_JWT` to ID tokens, you must update the `bound_issuer` in Vault to include `https://`:
|
||||
|
||||
```shell
|
||||
$ vault write auth/jwt/config \
|
||||
jwks_url="https://gitlab.example.com/-/jwks" \
|
||||
bound_issuer="https://gitlab.example.com"
|
||||
```
|
||||
|
||||
After you make this change, jobs that use `CI_JOB_JWT` start to fail.
|
||||
|
||||
You can create multiple authentication paths in Vault, which enable you to transition to IT Tokens on a project by job basis without disruption.
|
||||
|
||||
1. Configure a new authentication path with the name `jwt_v2`, run:
|
||||
|
||||
```shell
|
||||
vault auth enable -path jwt_v2 jwt
|
||||
```
|
||||
|
||||
You can choose a different name, but the rest of these examples assume you used `jwt_v2`, so update the examples as needed.
|
||||
|
||||
1. Configure the new authentication path for your instance:
|
||||
|
||||
```shell
|
||||
$ vault write auth/jwt_v2/config \
|
||||
jwks_url="https://gitlab.example.com/-/jwks" \
|
||||
bound_issuer="https://gitlab.example.com"
|
||||
```
|
||||
|
||||
## Recreate roles to use the new authentication path
|
||||
|
||||
Roles are bound to a specific authentication path so you need to add new roles for each job.
|
||||
|
||||
1. Recreate the role for staging named `myproject-staging`:
|
||||
|
||||
```shell
|
||||
$ vault write auth/jwt_v2/role/myproject-staging - <<EOF
|
||||
{
|
||||
"role_type": "jwt",
|
||||
"policies": ["myproject-staging"],
|
||||
"token_explicit_max_ttl": 60,
|
||||
"user_claim": "user_email",
|
||||
"bound_claims": {
|
||||
"project_id": "22",
|
||||
"ref": "master",
|
||||
"ref_type": "branch"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
1. Recreate the role for production named `myproject-production`:
|
||||
|
||||
```shell
|
||||
$ vault write auth/jwt_v2/role/myproject-production - <<EOF
|
||||
{
|
||||
"role_type": "jwt",
|
||||
"policies": ["myproject-production"],
|
||||
"token_explicit_max_ttl": 60,
|
||||
"user_claim": "user_email",
|
||||
"bound_claims_type": "glob",
|
||||
"bound_claims": {
|
||||
"project_id": "22",
|
||||
"ref_protected": "true",
|
||||
"ref_type": "branch",
|
||||
"ref": "auto-deploy-*"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
You only need to update `jwt` to `jwt_v2` in the `vault` command, do not change the `role_type` inside the role.
|
||||
|
||||
## Update your CI/CD Jobs
|
||||
|
||||
Vault has two different [KV Secrets Engines](https://developer.hashicorp.com/vault/docs/secrets/kv) and the version you are using impacts how you define secrets in CI/CD.
|
||||
|
||||
Check the [Which Version is my Vault KV Mount?](https://support.hashicorp.com/hc/en-us/articles/4404288741139-Which-Version-is-my-Vault-KV-Mount-) article on HashiCorp's support portal to check your Vault server.
|
||||
|
||||
Also, if needed you can review the CI/CD documentation for:
|
||||
|
||||
- [`secrets:`](../yaml/index.md#secrets)
|
||||
- [`id_tokens:`](../yaml/index.md#id_tokens)
|
||||
|
||||
The following examples show how to obtain the staging database password written to the `password` field in `secret/myproject/staging/db`
|
||||
|
||||
### KV Secrets Engine v1
|
||||
|
||||
The [`secrets:vault`](../yaml/index.md#secretsvault) keyword defaults to v2 of the KV Mount, so you need to explicitly configure the job to use the v1 engine:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
variables:
|
||||
VAULT_SERVER_URL: https://vault.example.com
|
||||
VAULT_AUTH_PATH: jwt_v2
|
||||
VAULT_AUTH_ROLE: myproject-staging
|
||||
id_tokens:
|
||||
VAULT_ID_TOKEN:
|
||||
aud: https://gitlab.example.com
|
||||
secrets:
|
||||
PASSWORD:
|
||||
vault:
|
||||
engine:
|
||||
name: kv-v1
|
||||
path: secret
|
||||
field: password
|
||||
path: myproject/staging/db
|
||||
file: false
|
||||
```
|
||||
|
||||
Both `VAULT_SERVER_URL` and `VAULT_AUTH_PATH` can be [defined as project or group CI/CD variables](../../ci/variables/index.md#define-a-cicd-variable-in-the-ui),
|
||||
if preferred.
|
||||
|
||||
We use [`secrets:file:false`](../../ci/yaml/index.md#secretsfile) because ID tokens place secrets in a file by default, but we need it to work as a regular variable to match the old behavior.
|
||||
|
||||
### KV Secrets Engine v2
|
||||
|
||||
There are two formats you can use for the v2 engine.
|
||||
|
||||
Long format:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
variables:
|
||||
VAULT_SERVER_URL: https://vault.example.com
|
||||
VAULT_AUTH_PATH: jwt_v2
|
||||
VAULT_AUTH_ROLE: myproject-staging
|
||||
id_tokens:
|
||||
VAULT_ID_TOKEN:
|
||||
aud: https://gitlab.example.com
|
||||
secrets:
|
||||
PASSWORD:
|
||||
vault:
|
||||
engine:
|
||||
name: kv-v2
|
||||
path: secret
|
||||
field: password
|
||||
path: myproject/staging/db
|
||||
file: false
|
||||
```
|
||||
|
||||
This is the same as the example for the v1 engine but `secrets:vault:engine:name:` is set to `kv-v2` to match the engine.
|
||||
|
||||
You can also use a short format:
|
||||
|
||||
```yaml
|
||||
job:
|
||||
variables:
|
||||
VAULT_SERVER_URL: https://vault.example.com
|
||||
VAULT_AUTH_PATH: jwt_v2
|
||||
VAULT_AUTH_ROLE: myproject-staging
|
||||
id_tokens:
|
||||
VAULT_ID_TOKEN:
|
||||
aud: https://gitlab.example.com
|
||||
secrets:
|
||||
PASSWORD:
|
||||
vault: myproject/staging/db/password@secret
|
||||
file: false
|
||||
```
|
||||
|
||||
After you commit the updated CI/CD configuration, your jobs will be fetching secrets with ID Tokens, congratulations!
|
||||
|
|
@ -139,6 +139,9 @@ manual_authentication:
|
|||
You can use ID tokens to automatically fetch secrets from HashiCorp Vault with the
|
||||
[`secrets`](../yaml/index.md#secrets) keyword.
|
||||
|
||||
If you previously used `CI_JOB_JWT` to fetch secrets from Vault, learn how to switch
|
||||
to ID tokens with the [Update HashiCorp Vault configuration to use ID Tokens](convert-to-id-tokens.md) tutorial.
|
||||
|
||||
### Configure automatic ID Token authentication
|
||||
|
||||
If one ID token is defined, the `secrets` keyword automatically uses it to authenticate with Vault. For example:
|
||||
|
|
|
|||
|
|
@ -97,17 +97,7 @@ You can see example adding a new gem: [!121676](https://gitlab.com/gitlab-org/gi
|
|||
|
||||
```yaml
|
||||
inherit_from:
|
||||
- ../../.rubocop.yml
|
||||
|
||||
CodeReuse/ActiveRecord:
|
||||
Enabled: false
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.0
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- ../config/rubocop.yml
|
||||
```
|
||||
|
||||
1. Configure CI for a newly added Gem:
|
||||
|
|
|
|||
|
|
@ -53,8 +53,26 @@ For more details, see [Secure your application](../user/application_security/ind
|
|||
|
||||
## Security partners
|
||||
|
||||
You can integrate GitLab with several security partners. For more information, see
|
||||
[Security partner integrations](security_partners/index.md).
|
||||
You can integrate GitLab with the following security partners:
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
||||
- [Anchore](https://docs.anchore.com/current/docs/configuration/integration/ci_cd/gitlab/)
|
||||
- [Bridgecrew](https://docs.bridgecrew.io/docs/integrate-with-gitlab-self-managed)
|
||||
- [Checkmarx](https://checkmarx.atlassian.net/wiki/spaces/SD/pages/1929937052/GitLab+Integration)
|
||||
- [Deepfactor](https://www.deepfactor.io/docs/integrate-deepfactor-scanner-in-your-ci-cd-pipelines/#gitlab)
|
||||
- [Fortify](https://www.microfocus.com/en-us/fortify-integrations/gitlab)
|
||||
- [GrammaTech](https://www.grammatech.com/codesonar-gitlab-integration)
|
||||
- [Indeni](https://docs.cloudrail.app/#/integrations/gitlab)
|
||||
- [Jscrambler](https://docs.jscrambler.com/code-integrity/documentation/gitlab-ci-integration)
|
||||
- [Mend](https://www.mend.io/gitlab/)
|
||||
- [Semgrep](https://semgrep.dev/for/gitlab)
|
||||
- [StackHawk](https://docs.stackhawk.com/continuous-integration/gitlab.html)
|
||||
- [Tenable](https://docs.tenable.com/tenableio/Content/ContainerSecurity/GetStarted.htm)
|
||||
- [Venafi](https://marketplace.venafi.com/xchange/620d2d6ed419fb06a5c5bd36/solution/6292c2ef7550f2ee553cf223)
|
||||
- [Veracode](https://community.veracode.com/s/knowledgeitem/gitlab-ci-MCEKSYPRWL35BRTGOVI55SK5RI4A)
|
||||
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
|
||||
## Continuous integration
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ For more information about how Smart Commits work and what commands are availabl
|
|||
- [Process issues with Smart Commits](https://support.atlassian.com/jira-software-cloud/docs/process-issues-with-smart-commits/)
|
||||
- [Using Smart Commits](https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html)
|
||||
|
||||
## Troubleshooting
|
||||
## Related topics
|
||||
|
||||
To troubleshoot the Jira development panel on your own server, see the
|
||||
[Atlassian documentation](https://confluence.atlassian.com/jirakb/troubleshoot-the-development-panel-in-jira-server-574685212.html).
|
||||
- [Troubleshoot the development panel in Jira Server](https://confluence.atlassian.com/jirakb/troubleshoot-the-development-panel-in-jira-server-574685212.html)
|
||||
|
|
|
|||
|
|
@ -1,30 +1,11 @@
|
|||
---
|
||||
stage: Secure
|
||||
group: Static Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
type: index
|
||||
redirect_to: '../index.md#security-partners'
|
||||
remove_date: '2023-10-05'
|
||||
---
|
||||
|
||||
# Security partners **(FREE)**
|
||||
This document was moved to [another location](../index.md#security-partners).
|
||||
|
||||
You can integrate GitLab with its security partners. This page has information on how do this with
|
||||
each security partner:
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
||||
- [Anchore](https://docs.anchore.com/current/docs/configuration/integration/ci_cd/gitlab/)
|
||||
- [Bridgecrew](https://docs.bridgecrew.io/docs/integrate-with-gitlab-self-managed)
|
||||
- [Checkmarx](https://checkmarx.atlassian.net/wiki/spaces/SD/pages/1929937052/GitLab+Integration)
|
||||
- [Deepfactor](https://www.deepfactor.io/docs/integrate-deepfactor-scanner-in-your-ci-cd-pipelines/#gitlab)
|
||||
- [GrammaTech](https://www.grammatech.com/codesonar-gitlab-integration)
|
||||
- [Indeni](https://docs.cloudrail.app/#/integrations/gitlab)
|
||||
- [JScrambler](https://docs.jscrambler.com/code-integrity/documentation/gitlab-ci-integration)
|
||||
- [Mend](https://www.mend.io/gitlab/)
|
||||
- [Semgrep](https://semgrep.dev/for/gitlab)
|
||||
- [StackHawk](https://docs.stackhawk.com/continuous-integration/gitlab.html)
|
||||
- [Tenable](https://docs.tenable.com/tenableio/Content/ContainerSecurity/GetStarted.htm)
|
||||
- [Venafi](https://marketplace.venafi.com/xchange/620d2d6ed419fb06a5c5bd36/solution/6292c2ef7550f2ee553cf223)
|
||||
- [Veracode](https://community.veracode.com/s/knowledgeitem/gitlab-ci-MCEKSYPRWL35BRTGOVI55SK5RI4A)
|
||||
- [Fortify](https://www.microfocus.com/en-us/fortify-integrations/gitlab)
|
||||
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
<!-- This redirect file can be deleted after <2023-10-05>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -360,6 +360,33 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
|
|||
|
||||
1. [Re-run database migrations](../administration/raketasks/maintenance.md#run-incomplete-database-migrations).
|
||||
|
||||
- You might also encounter the following error while upgrading to GitLab 15.10 or later:
|
||||
|
||||
```shell
|
||||
"exception.class": "ActiveRecord::StatementInvalid",
|
||||
"exception.message": "PG::SyntaxError: ERROR: zero-length delimited identifier at or near \"\"\"\"\nLINE 1: ...COALESCE(\"lock_version\", 0) + 1 WHERE \"ci_builds\".\"\" IN (SEL...\n
|
||||
```
|
||||
|
||||
This error is caused by a [batched background migration introduced in GitLab 14.9](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81410)
|
||||
not being finalized before upgrading to GitLab 15.10 or later. To resolve this error, it is safe to [mark the migration as complete](background_migrations.md#mark-a-batched-migration-finished):
|
||||
|
||||
```ruby
|
||||
# Start the rails console
|
||||
|
||||
connection = Ci::ApplicationRecord.connection
|
||||
|
||||
Gitlab::Database::SharedModel.using_connection(connection) do
|
||||
migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(
|
||||
Gitlab::Database.gitlab_schemas_for_connection(connection), 'NullifyOrphanRunnerIdOnCiBuilds', :ci_builds, :id, [])
|
||||
|
||||
# mark all jobs completed
|
||||
migration.batched_jobs.update_all(status: Gitlab::Database::BackgroundMigration::BatchedJob.state_machine.states[:succeeded].value)
|
||||
migration.update_attribute(:status, Gitlab::Database::BackgroundMigration::BatchedMigration.state_machine.states[:finished].value)
|
||||
end
|
||||
```
|
||||
|
||||
For more information, see [issue 415724](https://gitlab.com/gitlab-org/gitlab/-/issues/415724).
|
||||
|
||||
### 15.9.0
|
||||
|
||||
- **Upgrade to patch release 15.9.3 or later**. This provides fixes for two database migration bugs:
|
||||
|
|
|
|||
|
|
@ -148,6 +148,9 @@ Getting help has never been easier. If you have a question about how the GitLab
|
|||
|
||||
To give feedback, select the **Give Feedback** link.
|
||||
|
||||
NOTE:
|
||||
Only the last 50 messages in the chat history are retained. The chat history expires 3 days after last use.
|
||||
|
||||
### Summarize merge request changes **(ULTIMATE SAAS)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10400) in GitLab 16.0 as an [Experiment](../policy/experiment-beta-support.md#experiment).
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ To view project insights:
|
|||
1. Select **Analyze > Insights**.
|
||||
1. To view a report, select the **Select report** dropdown list.
|
||||
|
||||
### Access Insights reports with deep links
|
||||
|
||||
You can direct users to a specific report in Insights by using the deep-linked URL.
|
||||
|
||||
To create a deep link, append the report key to the end of the Insights report URL.
|
||||
For example, a GitLab report with the key `bugsCharts` has the deep link URL `https://gitlab.com/gitlab-org/gitlab/insights/#/bugsCharts`.
|
||||
|
||||
## Configure project insights
|
||||
|
||||
Prerequisites:
|
||||
|
|
|
|||
|
|
@ -279,6 +279,7 @@ For more information about supported events for webhooks, see [webhook events](w
|
|||
|
||||
> - `X-Gitlab-Event-UUID` header [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329743) in GitLab 14.8.
|
||||
> - `X-Gitlab-Instance` header [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31333) in GitLab 15.5.
|
||||
> - `X-Gitlab-Webhook-UUID` header [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230830) in GitLab 16.2.
|
||||
|
||||
Webhook requests to your endpoint include the following headers:
|
||||
|
||||
|
|
@ -286,6 +287,7 @@ Webhook requests to your endpoint include the following headers:
|
|||
| ------ | ------ | ------ |
|
||||
| `User-Agent` | In the format `"Gitlab/<VERSION>"`. | `"GitLab/15.5.0-pre"` |
|
||||
| `X-Gitlab-Instance` | Hostname of the GitLab instance that sent the webhook. | `"https://gitlab.com"` |
|
||||
| `X-Gitlab-Webhook-UUID` | Unique ID per webhook. If a webhook request fails and retries, the second request has a new ID. | `"02affd2d-2cba-4033-917d-ec22d5dc4b38"` |
|
||||
| `X-Gitlab-Event` | Name of the webhook type. Corresponds to [event types](webhook_events.md) but in the format `"<EVENT> Hook"`. | `"Push Hook"` |
|
||||
| `X-Gitlab-Event-UUID` | Unique ID per webhook that is not recursive. A hook is recursive if triggered by an earlier webhook that hit the GitLab instance. Recursive webhooks have the same value for this header. | `"13792a34-cac6-4fda-95a8-c58e00a3954e"` |
|
||||
|
||||
|
|
|
|||
|
|
@ -146,11 +146,8 @@ per conflicted file on the merge request diff:
|
|||
|
||||
## Add a comment to a merge request file
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123515) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `comment_on_files`. Enabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is available. To disable it, ask an administrator to [disable the feature flag](../../../administration/feature_flags.md) named `comment_on_files`.
|
||||
On GitLab.com, this feature is available.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123515) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `comment_on_files`. Enabled by default.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125130) in GitLab 16.2.
|
||||
|
||||
You can add comments to a merge request diff file. These comments persist across
|
||||
rebases and file changes.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
inherit_from:
|
||||
- ../../.rubocop.yml
|
||||
- ../config/rubocop.yml
|
||||
|
||||
CodeReuse/ActiveRecord:
|
||||
# FIXME
|
||||
Gitlab/RSpec/AvoidSetup:
|
||||
Enabled: false
|
||||
|
||||
Rails/ApplicationRecord:
|
||||
Enabled: false
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.7
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
|
|
|
|||
|
|
@ -21,24 +21,24 @@ GEM
|
|||
ast (2.4.2)
|
||||
concurrent-ruby (1.2.2)
|
||||
diff-lcs (1.5.0)
|
||||
gitlab-styles (10.0.0)
|
||||
rubocop (~> 1.43.0)
|
||||
gitlab-styles (10.1.0)
|
||||
rubocop (~> 1.50.2)
|
||||
rubocop-graphql (~> 0.18)
|
||||
rubocop-performance (~> 1.15)
|
||||
rubocop-rails (~> 2.17)
|
||||
rubocop-rspec (~> 2.18)
|
||||
rubocop-rspec (~> 2.22)
|
||||
i18n (1.13.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.6.3)
|
||||
minitest (5.18.0)
|
||||
parallel (1.22.1)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.3)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
racc (1.6.2)
|
||||
rack (3.0.4.1)
|
||||
racc (1.7.1)
|
||||
rack (3.0.8)
|
||||
rainbow (3.1.1)
|
||||
regexp_parser (2.8.0)
|
||||
regexp_parser (2.8.1)
|
||||
rexml (3.2.5)
|
||||
rspec (3.12.0)
|
||||
rspec-core (~> 3.12.0)
|
||||
|
|
@ -53,33 +53,36 @@ GEM
|
|||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-support (3.12.0)
|
||||
rubocop (1.43.0)
|
||||
rubocop (1.50.2)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.24.1, < 2.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.26.0)
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.17.0)
|
||||
rubocop-capybara (2.18.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.23.1)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-graphql (0.19.0)
|
||||
rubocop (>= 0.87, < 2)
|
||||
rubocop-performance (1.16.0)
|
||||
rubocop-performance (1.18.0)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.17.4)
|
||||
rubocop-rails (2.20.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-rspec (2.18.1)
|
||||
rubocop-rspec (2.22.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
ruby-progressbar (1.11.0)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-progressbar (1.13.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.4.2)
|
||||
|
|
@ -90,9 +93,10 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
activerecord-gitlab!
|
||||
gitlab-styles (~> 10.0.0)
|
||||
rspec (~> 3.0)
|
||||
rubocop (~> 1.21)
|
||||
gitlab-styles (~> 10.1.0)
|
||||
rspec (~> 3.12)
|
||||
rubocop (~> 1.50)
|
||||
rubocop-rspec (~> 2.22)
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.14
|
||||
|
|
|
|||
|
|
@ -8,19 +8,20 @@ Gem::Specification.new do |spec|
|
|||
spec.authors = ["group::tenant-scale"]
|
||||
spec.email = ["engineering@gitlab.com"]
|
||||
|
||||
spec.summary = "GitLab's ActiveRecord patches"
|
||||
spec.summary = "GitLab ActiveRecord patches"
|
||||
spec.description = "GitLab stores any patches relating to ActiveRecord here"
|
||||
spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/activerecord-gitlab"
|
||||
spec.license = "MIT"
|
||||
spec.required_ruby_version = ">= 2.7"
|
||||
spec.required_ruby_version = ">= 3.0"
|
||||
spec.metadata["rubygems_mfa_required"] = "true"
|
||||
|
||||
spec.files = Dir['lib/**/*.rb']
|
||||
spec.test_files = Dir['spec/**/*']
|
||||
spec.files = Dir["lib/**/*.rb"]
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_runtime_dependency "activerecord", ">= 6.1.7.3"
|
||||
|
||||
spec.add_development_dependency "gitlab-styles", "~> 10.0.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.0"
|
||||
spec.add_development_dependency "rubocop", "~> 1.21"
|
||||
spec.add_development_dependency "gitlab-styles", "~> 10.1.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.12"
|
||||
spec.add_development_dependency "rubocop", "~> 1.50"
|
||||
spec.add_development_dependency "rubocop-rspec", "~> 2.22"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
include:
|
||||
- local: gems/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "click_house-client"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
--format documentation
|
||||
--color
|
||||
--require spec_helper
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
inherit_from:
|
||||
- ../../.rubocop.yml
|
||||
|
||||
CodeReuse/ActiveRecord:
|
||||
Enabled: false
|
||||
|
||||
Rails/ApplicationRecord:
|
||||
Enabled: false
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.0
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- lib/gitlab/rspec.rb
|
||||
- lib/gitlab/rspec/all.rb
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
gemspec
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
click_house-client (0.1.0)
|
||||
activesupport (< 8)
|
||||
addressable (~> 2.8)
|
||||
json (~> 2.6.3)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (7.0.6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
concurrent-ruby (1.2.2)
|
||||
diff-lcs (1.5.0)
|
||||
gitlab-styles (10.1.0)
|
||||
rubocop (~> 1.50.2)
|
||||
rubocop-graphql (~> 0.18)
|
||||
rubocop-performance (~> 1.15)
|
||||
rubocop-rails (~> 2.17)
|
||||
rubocop-rspec (~> 2.22)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.6.3)
|
||||
minitest (5.18.1)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.3)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
public_suffix (5.0.1)
|
||||
racc (1.7.1)
|
||||
rack (3.0.8)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
regexp_parser (2.8.1)
|
||||
rexml (3.2.5)
|
||||
rspec (3.12.0)
|
||||
rspec-core (~> 3.12.0)
|
||||
rspec-expectations (~> 3.12.0)
|
||||
rspec-mocks (~> 3.12.0)
|
||||
rspec-core (3.12.2)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-support (3.12.1)
|
||||
rubocop (1.50.2)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.18.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.23.1)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-graphql (0.19.0)
|
||||
rubocop (>= 0.87, < 2)
|
||||
rubocop-performance (1.18.0)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.20.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-rspec (2.22.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-progressbar (1.13.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.4.2)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
click_house-client!
|
||||
gitlab-styles (~> 10.1.0)
|
||||
rake (~> 13.0)
|
||||
rspec (~> 3.0)
|
||||
rubocop
|
||||
rubocop-rspec
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.15
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ClickHouse::Client
|
||||
|
||||
This Gem provides a simple way to query ClickHouse databases using the HTTP interface.
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "click_house-client"
|
||||
spec.version = "0.1.0"
|
||||
spec.authors = ["group::optimize"]
|
||||
spec.email = ["engineering@gitlab.com"]
|
||||
|
||||
spec.summary = "GitLab's client to interact with ClickHouse"
|
||||
spec.description = "This Gem provides a simple way to query ClickHouse databases using the HTTP interface."
|
||||
spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/click_house-client"
|
||||
spec.license = "MIT"
|
||||
spec.required_ruby_version = ">= 3.0"
|
||||
|
||||
spec.add_runtime_dependency "activesupport", "< 8"
|
||||
spec.add_runtime_dependency "addressable", "~> 2.8"
|
||||
spec.add_runtime_dependency 'json', '~> 2.6.3'
|
||||
|
||||
spec.add_development_dependency 'gitlab-styles', '~> 10.1.0'
|
||||
spec.add_development_dependency "rake", "~> 13.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.0"
|
||||
spec.add_development_dependency 'rubocop'
|
||||
spec.add_development_dependency 'rubocop-rspec'
|
||||
end
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'addressable'
|
||||
require 'json'
|
||||
require 'active_support/time'
|
||||
require_relative "client/database"
|
||||
require_relative "client/configuration"
|
||||
require_relative "client/formatter"
|
||||
require_relative "client/response"
|
||||
|
||||
module ClickHouse
|
||||
module Client
|
||||
class << self
|
||||
def configuration
|
||||
@configuration ||= Configuration.new
|
||||
end
|
||||
|
||||
def configure
|
||||
yield(configuration)
|
||||
configuration.validate!
|
||||
end
|
||||
end
|
||||
|
||||
Error = Class.new(StandardError)
|
||||
ConfigurationError = Class.new(Error)
|
||||
DatabaseError = Class.new(Error)
|
||||
|
||||
def self.execute(query, database, configuration = self.configuration)
|
||||
db = configuration.databases[database]
|
||||
raise ConfigurationError, "The database '#{database}' is not configured" unless db
|
||||
|
||||
response = configuration.http_post_proc.call(
|
||||
db.uri.to_s,
|
||||
db.headers,
|
||||
"#{query} FORMAT JSON" # always return JSON
|
||||
)
|
||||
|
||||
raise DatabaseError, response.body unless response.success?
|
||||
|
||||
Formatter.format(configuration.json_parser.parse(response.body))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ClickHouse
|
||||
module Client
|
||||
class Configuration
|
||||
# Configuration options:
|
||||
#
|
||||
# *register_database* (method): registers a database, the following arguments are required:
|
||||
# - database: database name
|
||||
# - url: URL and port to the HTTP interface
|
||||
# - username
|
||||
# - password
|
||||
# - variables (optional): configuration for the client
|
||||
#
|
||||
# *http_post_proc*: A callable object for invoking the HTTP request.
|
||||
# The object must handle the following parameters: url, headers, body
|
||||
# and return a Gitlab::ClickHouse::Client::Response object.
|
||||
#
|
||||
# *json_parser*: object for parsing JSON strings, it should respond to the "parse" method
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Gitlab::ClickHouse::Client.configure do |c|
|
||||
# c.register_database(:main,
|
||||
# database: 'gitlab_clickhouse_test',
|
||||
# url: 'http://localhost:8123',
|
||||
# username: 'default',
|
||||
# password: 'clickhouse',
|
||||
# variables: {
|
||||
# join_use_nulls: 1 # treat JOINs as per SQL standard
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# c.http_post_proc = lambda do |url, headers, body|
|
||||
# options = {
|
||||
# headers: headers,
|
||||
# body: body,
|
||||
# allow_local_requests: false
|
||||
# }
|
||||
#
|
||||
# response = Gitlab::HTTP.post(url, options)
|
||||
# Gitlab::ClickHouse::Client::Response.new(response.body, response.code)
|
||||
# end
|
||||
#
|
||||
# c.json_parser = JSON
|
||||
# end
|
||||
attr_accessor :http_post_proc, :json_parser
|
||||
attr_reader :databases
|
||||
|
||||
def initialize
|
||||
@databases = {}
|
||||
@http_post_proc = nil
|
||||
@json_parser = JSON
|
||||
end
|
||||
|
||||
def register_database(name, **args)
|
||||
raise ConfigurationError, "The database '#{name}' is already registered" if @databases.key?(name)
|
||||
|
||||
@databases[name] = Database.new(**args)
|
||||
end
|
||||
|
||||
def validate!
|
||||
raise ConfigurationError, "The 'http_post_proc' option is not configured" unless @http_post_proc
|
||||
raise ConfigurationError, "The 'json_parser' option is not configured" unless @json_parser
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ClickHouse
|
||||
module Client
|
||||
class Database
|
||||
attr_reader :database
|
||||
|
||||
def initialize(database:, url:, username:, password:, variables: {})
|
||||
@database = database
|
||||
@url = url
|
||||
@username = username
|
||||
@password = password
|
||||
@variables = variables.merge(database: database).freeze
|
||||
end
|
||||
|
||||
def uri
|
||||
@uri ||= begin
|
||||
parsed = Addressable::URI.parse(@url)
|
||||
parsed.query_values = @variables
|
||||
parsed
|
||||
end
|
||||
end
|
||||
|
||||
def headers
|
||||
@headers ||= {
|
||||
'X-ClickHouse-User' => @username,
|
||||
'X-ClickHouse-Key' => @password
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ClickHouse
|
||||
module Client
|
||||
class Formatter
|
||||
DEFAULT = ->(value) { value }
|
||||
|
||||
TYPE_CASTERS = {
|
||||
'UInt64' => ->(value) { Integer(value) },
|
||||
"DateTime64(6, 'UTC')" => ->(value) { ActiveSupport::TimeZone["UTC"].parse(value) }
|
||||
}.freeze
|
||||
|
||||
def self.format(result)
|
||||
name_type_mapping = result['meta'].each_with_object({}) do |column, hash|
|
||||
hash[column['name']] = column['type']
|
||||
end
|
||||
|
||||
result['data'].map do |row|
|
||||
row.each_with_object({}) do |(column, value), casted_row|
|
||||
caster = TYPE_CASTERS.fetch(name_type_mapping[column], DEFAULT)
|
||||
|
||||
casted_row[column] = caster.call(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ClickHouse
|
||||
module Client
|
||||
class Response
|
||||
attr_reader :body
|
||||
|
||||
def initialize(body, http_status_code)
|
||||
@body = body
|
||||
@http_status_code = http_status_code
|
||||
end
|
||||
|
||||
def success?
|
||||
@http_status_code == 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ClickHouse::Client::Configuration do
|
||||
subject(:configuration) do
|
||||
config = described_class.new
|
||||
config.http_post_proc = -> {}
|
||||
config.json_parser = Object
|
||||
config
|
||||
end
|
||||
|
||||
describe '#register_database' do
|
||||
let(:database_options) do
|
||||
{
|
||||
database: 'test_db',
|
||||
url: 'http://localhost:3333',
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
variables: {
|
||||
join_use_nulls: 1
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'registers a database' do
|
||||
configuration.register_database(:my_db, **database_options)
|
||||
|
||||
expect(configuration.databases.size).to eq(1)
|
||||
database = configuration.databases[:my_db]
|
||||
|
||||
expect(database.uri.to_s).to eq('http://localhost:3333?database=test_db&join_use_nulls=1')
|
||||
end
|
||||
|
||||
context 'when adding the same DB multiple times' do
|
||||
it 'raises error' do
|
||||
configuration.register_database(:my_db, **database_options)
|
||||
expect do
|
||||
configuration.register_database(:my_db, **database_options)
|
||||
end.to raise_error(ClickHouse::Client::ConfigurationError, /database 'my_db' is already registered/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#validate!' do
|
||||
context 'when `http_post_proc` option is not configured' do
|
||||
it 'raises error' do
|
||||
configuration.http_post_proc = nil
|
||||
|
||||
expect do
|
||||
configuration.validate!
|
||||
end.to raise_error(ClickHouse::Client::ConfigurationError, /'http_post_proc' option is not configured/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `json_parser` option is not configured' do
|
||||
it 'raises error' do
|
||||
configuration.json_parser = nil
|
||||
|
||||
expect do
|
||||
configuration.validate!
|
||||
end.to raise_error(ClickHouse::Client::ConfigurationError, /'json_parser' option is not configured/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ClickHouse::Client::Database do
|
||||
subject(:database) do
|
||||
described_class.new(
|
||||
database: 'test_db',
|
||||
url: 'http://localhost:3333',
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
variables: {
|
||||
join_use_nulls: 1
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
describe '#uri' do
|
||||
it 'builds the correct URL' do
|
||||
expect(database.uri.to_s).to eq('http://localhost:3333?database=test_db&join_use_nulls=1')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#headers' do
|
||||
it 'returns the correct headers' do
|
||||
expect(database.headers).to eq({
|
||||
'X-ClickHouse-User' => 'user',
|
||||
'X-ClickHouse-Key' => 'pass'
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe ClickHouse::Client do
|
||||
describe '#execute' do
|
||||
# Assuming we have a DB table with the following schema
|
||||
#
|
||||
# CREATE TABLE issues (
|
||||
# `id` UInt64,
|
||||
# `title` String DEFAULT '',
|
||||
# `description` Nullable(String),
|
||||
# `created_at` DateTime64(6, 'UTC') DEFAULT now(),
|
||||
# `updated_at` DateTime64(6, 'UTC') DEFAULT now()
|
||||
# )
|
||||
# ENGINE = ReplacingMergeTree(updated_at)
|
||||
# ORDER BY (id)
|
||||
|
||||
let(:query_result_fixture) { File.expand_path('../fixtures/query_result.json', __dir__) }
|
||||
|
||||
let(:database_config) do
|
||||
{
|
||||
database: 'test_db',
|
||||
url: 'http://localhost:3333',
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
variables: {
|
||||
join_use_nulls: 1
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:configuration) do
|
||||
ClickHouse::Client::Configuration.new.tap do |config|
|
||||
config.register_database(:test_db, **database_config)
|
||||
config.http_post_proc = ->(_url, _headers, _query) {
|
||||
body = File.read(query_result_fixture)
|
||||
ClickHouse::Client::Response.new(body, 200)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
it 'parses the results and returns the data as array of hashes' do
|
||||
result = described_class.execute('SELECT * FROM issues', :test_db, configuration)
|
||||
|
||||
timestamp1 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:44')
|
||||
timestamp2 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:50')
|
||||
timestamp3 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:40')
|
||||
|
||||
expect(result).to eq([
|
||||
{
|
||||
'id' => 2,
|
||||
'title' => 'Title 2',
|
||||
'description' => 'description',
|
||||
'created_at' => timestamp1,
|
||||
'updated_at' => timestamp1
|
||||
},
|
||||
{
|
||||
'id' => 3,
|
||||
'title' => 'Title 3',
|
||||
'description' => nil,
|
||||
'created_at' => timestamp2,
|
||||
'updated_at' => timestamp2
|
||||
},
|
||||
{
|
||||
'id' => 1,
|
||||
'title' => 'Title 1',
|
||||
'description' => 'description',
|
||||
'created_at' => timestamp3,
|
||||
'updated_at' => timestamp3
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
context 'when the DB is not configured' do
|
||||
it 'raises erro' do
|
||||
expect do
|
||||
described_class.execute('SELECT * FROM issues', :different_db, configuration)
|
||||
end.to raise_error(ClickHouse::Client::ConfigurationError, /not configured/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when error response is returned' do
|
||||
let(:configuration) do
|
||||
ClickHouse::Client::Configuration.new.tap do |config|
|
||||
config.register_database(:test_db, **database_config)
|
||||
config.http_post_proc = ->(_url, _headers, _query) {
|
||||
ClickHouse::Client::Response.new('some error', 404)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises error' do
|
||||
expect do
|
||||
described_class.execute('SELECT * FROM issues', :test_db, configuration)
|
||||
end.to raise_error(ClickHouse::Client::DatabaseError, 'some error')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"meta": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "UInt64"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"type:": "String"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "Nullable(String)"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"type": "DateTime64(6, 'UTC')"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"type": "DateTime64(6, 'UTC')"
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
{
|
||||
"id": "2",
|
||||
"title": "Title 2",
|
||||
"description": "description",
|
||||
"created_at": "2023-06-21 13:33:44.000000",
|
||||
"updated_at": "2023-06-21 13:33:44.000000"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"title": "Title 3",
|
||||
"description": null,
|
||||
"created_at": "2023-06-21 13:33:50.000000",
|
||||
"updated_at": "2023-06-21 13:33:50.000000"
|
||||
},
|
||||
{
|
||||
"id": "1",
|
||||
"title": "Title 1",
|
||||
"description": "description",
|
||||
"created_at": "2023-06-21 13:33:40.000000",
|
||||
"updated_at": "2023-06-21 13:33:40.000000"
|
||||
}
|
||||
],
|
||||
"rows": 3,
|
||||
"statistics": {
|
||||
"elapsed": 0.001581789,
|
||||
"rows_read": 3,
|
||||
"bytes_read": 172
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "click_house/client"
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Enable flags like --only-failures and --next-failure
|
||||
config.example_status_persistence_file_path = ".rspec_status"
|
||||
|
||||
# Disable RSpec exposing methods globally on `Module` and `main`
|
||||
config.disable_monkey_patching!
|
||||
|
||||
config.expect_with :rspec do |c|
|
||||
c.syntax = :expect
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
inherit_gem:
|
||||
gitlab-styles:
|
||||
- rubocop-default.yml
|
||||
|
||||
require:
|
||||
- ../../rubocop/rubocop
|
||||
- rubocop-rspec
|
||||
|
||||
inherit_mode:
|
||||
merge:
|
||||
- Include
|
||||
- Exclude
|
||||
- AllowedPatterns
|
||||
|
||||
AllCops:
|
||||
# Target the current Ruby version. For example, "3.0" or "3.1".
|
||||
TargetRubyVersion: <%= RUBY_VERSION[/^\d+\.\d+/, 0] %>
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
CodeReuse/ActiveRecord:
|
||||
Enabled: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
Cop/PutGroupRoutesUnderScope:
|
||||
Enabled: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
Cop/PutProjectRoutesUnderScope:
|
||||
Enabled: false
|
||||
|
||||
Gemspec/AvoidExecutingGit:
|
||||
Enabled: true
|
||||
|
||||
# We disable this since we support multiple Ruby versions
|
||||
Gemspec/RequiredRubyVersion:
|
||||
Enabled: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
Gitlab/DocUrl:
|
||||
Enabled: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
Gitlab/NamespacedClass:
|
||||
Enabled: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
Graphql/AuthorizeTypes:
|
||||
Enabled: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
Graphql/Descriptions:
|
||||
Enabled: false
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
RSpec/MissingFeatureCategory:
|
||||
Enabled: false
|
||||
|
||||
# Enable once we drop 3.0 support
|
||||
Style/HashSyntax:
|
||||
Enabled: false
|
||||
|
||||
Style/RegexpLiteralMixedPreserve:
|
||||
Enabled: true
|
||||
SupportedStyles:
|
||||
- slashes
|
||||
- percent_r
|
||||
- mixed
|
||||
- mixed_preserve
|
||||
EnforcedStyle: mixed_preserve
|
||||
|
|
@ -1,14 +1,2 @@
|
|||
inherit_from:
|
||||
- ../../.rubocop.yml
|
||||
|
||||
CodeReuse/ActiveRecord:
|
||||
Enabled: false
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.0
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- lib/gitlab/rspec.rb
|
||||
- lib/gitlab/rspec/all.rb
|
||||
- ../config/rubocop.yml
|
||||
|
|
|
|||
|
|
@ -43,12 +43,12 @@ GEM
|
|||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
gitlab-styles (10.0.0)
|
||||
rubocop (~> 1.43.0)
|
||||
gitlab-styles (10.1.0)
|
||||
rubocop (~> 1.50.2)
|
||||
rubocop-graphql (~> 0.18)
|
||||
rubocop-performance (~> 1.15)
|
||||
rubocop-rails (~> 2.17)
|
||||
rubocop-rspec (~> 2.18)
|
||||
rubocop-rspec (~> 2.22)
|
||||
i18n (1.13.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.6.3)
|
||||
|
|
@ -86,7 +86,7 @@ GEM
|
|||
zeitwerk (~> 2.5)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
regexp_parser (2.8.0)
|
||||
regexp_parser (2.8.1)
|
||||
rexml (3.2.5)
|
||||
rspec (3.12.0)
|
||||
rspec-core (~> 3.12.0)
|
||||
|
|
@ -125,32 +125,35 @@ GEM
|
|||
rspec-mocks (~> 3.12)
|
||||
rspec-support (~> 3.12)
|
||||
rspec-support (3.12.0)
|
||||
rubocop (1.43.0)
|
||||
rubocop (1.50.2)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.24.1, < 2.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.28.1)
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.18.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.23.1)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-graphql (0.19.0)
|
||||
rubocop (>= 0.87, < 2)
|
||||
rubocop-performance (1.18.0)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.19.1)
|
||||
rubocop-rails (2.20.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-rspec (2.18.1)
|
||||
rubocop-rspec (2.22.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-progressbar (1.13.0)
|
||||
thor (1.2.2)
|
||||
tzinfo (2.0.6)
|
||||
|
|
@ -167,12 +170,12 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
factory_bot_rails (~> 6.2.0)
|
||||
gitlab-rspec!
|
||||
gitlab-styles (~> 10.0.0)
|
||||
gitlab-styles (~> 10.1.0)
|
||||
rspec-benchmark (~> 0.6.0)
|
||||
rspec-parameterized (~> 1.0)
|
||||
rspec-rails (~> 6.0.1)
|
||||
rubocop (~> 1.21)
|
||||
rubocop-rspec (~> 2.18.1)
|
||||
rubocop (~> 1.50)
|
||||
rubocop-rspec (~> 2.22)
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.4
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ Gem::Specification.new do |spec|
|
|||
spec.add_runtime_dependency "rspec", "~> 3.0"
|
||||
|
||||
spec.add_development_dependency "factory_bot_rails", "~> 6.2.0"
|
||||
spec.add_development_dependency "gitlab-styles", "~> 10.0.0"
|
||||
spec.add_development_dependency "gitlab-styles", "~> 10.1.0"
|
||||
spec.add_development_dependency "rspec-benchmark", "~> 0.6.0"
|
||||
spec.add_development_dependency "rspec-parameterized", "~> 1.0"
|
||||
spec.add_development_dependency "rspec-rails", "~> 6.0.1"
|
||||
spec.add_development_dependency "rubocop", "~> 1.21"
|
||||
spec.add_development_dependency "rubocop-rspec", "~> 2.18.1"
|
||||
spec.add_development_dependency "rubocop", "~> 1.50"
|
||||
spec.add_development_dependency "rubocop-rspec", "~> 2.22"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,3 +2,8 @@
|
|||
|
||||
require "rspec"
|
||||
require_relative "rspec/version"
|
||||
|
||||
module Gitlab
|
||||
module Rspec
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ module StubENV
|
|||
end
|
||||
|
||||
def env_stubbed?
|
||||
ENV[STUBBED_KEY]
|
||||
ENV.fetch(STUBBED_KEY, false)
|
||||
end
|
||||
|
||||
def init_stub
|
||||
|
|
|
|||
|
|
@ -1,26 +1,5 @@
|
|||
inherit_from:
|
||||
- ../../.rubocop.yml
|
||||
|
||||
CodeReuse/ActiveRecord:
|
||||
Enabled: false
|
||||
|
||||
Gitlab/DocUrl:
|
||||
Enabled: false
|
||||
|
||||
Gitlab/NamespacedClass:
|
||||
Enabled: false
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.0
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- lib/gitlab/utils/all.rb
|
||||
|
||||
Lint/AmbiguousRegexpLiteral:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- ../config/rubocop.yml
|
||||
|
||||
RSpec/InstanceVariable:
|
||||
Exclude:
|
||||
|
|
@ -29,3 +8,24 @@ RSpec/InstanceVariable:
|
|||
Lint/BinaryOperatorWithIdenticalOperands:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
|
||||
# We use EnforcedStyle of comparison here due to it being better
|
||||
# performing code as seen in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36221#note_375659681
|
||||
Style/NumericPredicate:
|
||||
EnforcedStyle: comparison
|
||||
|
||||
# FIXME
|
||||
Gitlab/RSpec/AvoidSetup:
|
||||
Enabled: false
|
||||
|
||||
# FIXME: When enabled, there's a spec failure in ee/spec/requests/api/graphql/mutations/merge_requests/update_approval_rule_spec.rb:51,
|
||||
# due to the `default_value` of `remove_hidden_groups` set to `[]`, most probably instead of `false`, in ee/app/graphql/mutations/merge_requests/update_approval_rule.rb.
|
||||
# The problem is that `Object#=~` exists (even though it's deprecated), hence calling it on an `Array` doesn't blow up, but `Array#match?` doesn't exist.
|
||||
Performance/RegexpMatch:
|
||||
Exclude:
|
||||
- lib/gitlab/utils.rb
|
||||
|
||||
Rails/OutputSafety:
|
||||
Details: grace period
|
||||
Exclude:
|
||||
- 'lib/gitlab/utils.rb'
|
||||
|
|
|
|||
|
|
@ -55,12 +55,12 @@ GEM
|
|||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
gitlab-styles (10.0.0)
|
||||
rubocop (~> 1.43.0)
|
||||
gitlab-styles (10.1.0)
|
||||
rubocop (~> 1.50.2)
|
||||
rubocop-graphql (~> 0.18)
|
||||
rubocop-performance (~> 1.15)
|
||||
rubocop-rails (~> 2.17)
|
||||
rubocop-rspec (~> 2.18)
|
||||
rubocop-rspec (~> 2.22)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.6.3)
|
||||
|
|
@ -73,9 +73,10 @@ GEM
|
|||
nokogiri (1.15.2)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
parallel (1.22.1)
|
||||
parser (3.2.0.0)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.3)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
proc_to_ast (0.1.0)
|
||||
coderay
|
||||
parser
|
||||
|
|
@ -100,7 +101,7 @@ GEM
|
|||
zeitwerk (~> 2.5)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
regexp_parser (2.6.0)
|
||||
regexp_parser (2.8.1)
|
||||
rexml (3.2.5)
|
||||
rspec (3.12.0)
|
||||
rspec-core (~> 3.12.0)
|
||||
|
|
@ -139,33 +140,36 @@ GEM
|
|||
rspec-mocks (~> 3.11)
|
||||
rspec-support (~> 3.11)
|
||||
rspec-support (3.12.0)
|
||||
rubocop (1.43.0)
|
||||
rubocop (1.50.2)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.24.1, < 2.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.24.1)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.18.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.23.1)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-graphql (0.19.0)
|
||||
rubocop (>= 0.87, < 2)
|
||||
rubocop-performance (1.18.0)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.19.1)
|
||||
rubocop-rails (2.20.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-rspec (2.18.1)
|
||||
rubocop-rspec (2.22.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
ruby-progressbar (1.11.0)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-progressbar (1.13.0)
|
||||
thor (1.2.2)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
|
|
@ -181,13 +185,14 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
factory_bot_rails (~> 6.2.0)
|
||||
gitlab-rspec!
|
||||
gitlab-styles (~> 10.0.0)
|
||||
gitlab-styles (~> 10.1.0)
|
||||
gitlab-utils!
|
||||
rspec (~> 3.12)
|
||||
rspec-benchmark (~> 0.6.0)
|
||||
rspec-parameterized (~> 1.0)
|
||||
rspec-rails (~> 6.0.1)
|
||||
rubocop (~> 1.21)
|
||||
rubocop-rspec (~> 2.18.1)
|
||||
rubocop (~> 1.50)
|
||||
rubocop-rspec (~> 2.22)
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.4
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ Gem::Specification.new do |spec|
|
|||
spec.add_runtime_dependency "rake", "~> 13.0"
|
||||
|
||||
spec.add_development_dependency "factory_bot_rails", "~> 6.2.0"
|
||||
spec.add_development_dependency "gitlab-styles", "~> 10.0.0"
|
||||
spec.add_development_dependency "gitlab-styles", "~> 10.1.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.12"
|
||||
spec.add_development_dependency "rspec-benchmark", "~> 0.6.0"
|
||||
spec.add_development_dependency "rspec-parameterized", "~> 1.0"
|
||||
spec.add_development_dependency "rspec-rails", "~> 6.0.1"
|
||||
spec.add_development_dependency "rubocop", "~> 1.21"
|
||||
spec.add_development_dependency "rubocop-rspec", "~> 2.18.1"
|
||||
spec.add_development_dependency "rubocop", "~> 1.50"
|
||||
spec.add_development_dependency "rubocop-rspec", "~> 2.22"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,13 +12,16 @@ module Gitlab
|
|||
MAX_VERSION_LENGTH = 128
|
||||
|
||||
def self.parse(str, parse_suffix: false)
|
||||
if str.is_a?(self)
|
||||
str
|
||||
elsif str && str.length <= MAX_VERSION_LENGTH && m = str.match(VERSION_REGEX)
|
||||
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i, parse_suffix ? m.post_match : nil)
|
||||
else
|
||||
VersionInfo.new
|
||||
return str if str.is_a?(self)
|
||||
|
||||
if str && str.length <= MAX_VERSION_LENGTH
|
||||
match = str.match(VERSION_REGEX)
|
||||
if match
|
||||
return VersionInfo.new(match[1].to_i, match[2].to_i, match[3].to_i, parse_suffix ? match.post_match : nil)
|
||||
end
|
||||
end
|
||||
|
||||
VersionInfo.new
|
||||
end
|
||||
|
||||
def initialize(major = 0, minor = 0, patch = 0, suffix = nil) # rubocop:disable Metrics/ParameterLists
|
||||
|
|
@ -71,7 +74,7 @@ module Gitlab
|
|||
|
||||
def suffix
|
||||
@suffix ||= @suffix_s.strip.gsub('-', '.pre.').scan(/\d+|[a-z]+/i).map do |s|
|
||||
/^\d+$/ =~ s ? s.to_i : s
|
||||
/^\d+$/.match?(s) ? s.to_i : s
|
||||
end.freeze
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable GitlabSecurity/PublicSend
|
||||
|
||||
require 'spec_helper'
|
||||
require 'active_support/testing/time_helpers'
|
||||
|
||||
|
|
@ -23,7 +21,7 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
end
|
||||
|
||||
def method_name
|
||||
strong_memoize(:method_name) do # rubocop: disable Gitlab/StrongMemoizeAttr
|
||||
strong_memoize(:method_name) do # rubocop:disable Gitlab/StrongMemoizeAttr
|
||||
trace << value
|
||||
value
|
||||
end
|
||||
|
|
@ -111,12 +109,12 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
it_behaves_like 'caching the value'
|
||||
|
||||
it 'raises exception for invalid type as key' do
|
||||
expect { object.strong_memoize(10) { 20 } }.to raise_error /Invalid type of '10'/
|
||||
expect { object.strong_memoize(10) { 20 } }.to raise_error(/Invalid type of '10'/)
|
||||
end
|
||||
|
||||
it 'raises exception for invalid characters in key' do
|
||||
expect { object.strong_memoize(:enabled?) { 20 } }
|
||||
.to raise_error /is not allowed as an instance variable name/
|
||||
.to raise_error(/is not allowed as an instance variable name/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -129,7 +127,7 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
end
|
||||
|
||||
[:method_name, "method_name"].each do |argument|
|
||||
context "for #{argument.class}" do
|
||||
context "when argument is a #{argument.class}" do
|
||||
it 'does allocate exactly one string when fetching value' do
|
||||
expect do
|
||||
object.strong_memoize(argument) { 10 }
|
||||
|
|
@ -157,12 +155,12 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
it_behaves_like 'caching the value'
|
||||
|
||||
it 'raises exception for invalid type as key' do
|
||||
expect { object.strong_memoize_with_expiration(10, 1) { 20 } }.to raise_error /Invalid type of '10'/
|
||||
expect { object.strong_memoize_with_expiration(10, 1) { 20 } }.to raise_error(/Invalid type of '10'/)
|
||||
end
|
||||
|
||||
it 'raises exception for invalid characters in key' do
|
||||
expect { object.strong_memoize_with_expiration(:enabled?, 1) { 20 } }
|
||||
.to raise_error /is not allowed as an instance variable name/
|
||||
.to raise_error(/is not allowed as an instance variable name/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -217,19 +215,19 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
|
||||
describe '#strong_memoized?' do
|
||||
shared_examples 'memoization check' do |method_name|
|
||||
context "for #{method_name}" do
|
||||
context "when method is :#{method_name}" do
|
||||
let(:value) { :anything }
|
||||
|
||||
subject { object.strong_memoized?(method_name) }
|
||||
|
||||
it 'returns false if the value is uncached' do
|
||||
is_expected.to be(false)
|
||||
expect(subject).to be(false)
|
||||
end
|
||||
|
||||
it 'returns true if the value is cached' do
|
||||
object.public_send(method_name)
|
||||
|
||||
is_expected.to be(true)
|
||||
expect(subject).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -309,7 +307,7 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
subject { klass.strong_memoize_attr(:nonexistent_method) }
|
||||
|
||||
it 'fails when strong-memoizing a nonexistent method' do
|
||||
expect { subject }.to raise_error(NameError, %r{undefined method `nonexistent_method' for class})
|
||||
expect { subject }.to raise_error(NameError, /undefined method `nonexistent_method' for class/)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -355,7 +353,7 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
|
||||
it { is_expected.to eq(output) }
|
||||
|
||||
if params[:valid]
|
||||
if params[:valid] # rubocop:disable RSpec/AvoidConditionalStatements
|
||||
it 'is a valid ivar name' do
|
||||
expect { instance_variable_defined?(ivar) }.not_to raise_error
|
||||
end
|
||||
|
|
@ -368,5 +366,3 @@ RSpec.describe Gitlab::Utils::StrongMemoize, feature_category: :shared do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable GitlabSecurity/PublicSend
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@ module Gitlab
|
|||
module WebHooks
|
||||
GITLAB_EVENT_HEADER = 'X-Gitlab-Event'
|
||||
GITLAB_INSTANCE_HEADER = 'X-Gitlab-Instance'
|
||||
GITLAB_UUID_HEADER = 'X-Gitlab-Webhook-UUID'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5398,6 +5398,9 @@ msgstr ""
|
|||
msgid "Analytics|Visualization Type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Visualization designer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Visualization was saved successfully"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -34785,6 +34788,9 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Page Views"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Product analytics onboarding"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Repeat Visit Percentage"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module RuboCop
|
|||
class FeatureCategory < RuboCop::Cop::Base
|
||||
include MigrationHelpers
|
||||
|
||||
FEATURE_CATEGORIES_FILE_PATH = "config/feature_categories.yml"
|
||||
FEATURE_CATEGORIES_FILE_PATH = File.expand_path("../../../config/feature_categories.yml", __dir__)
|
||||
|
||||
MSG = "'feature_category' should be defined to better assign the ownership for batched migration jobs. " \
|
||||
"For more details refer: " \
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ module RuboCop
|
|||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless in_rake_file?(node)
|
||||
|
||||
method, file = require_method(node)
|
||||
return unless method
|
||||
|
||||
|
|
@ -70,6 +72,14 @@ module RuboCop
|
|||
|
||||
private
|
||||
|
||||
def in_rake_file?(node)
|
||||
File.extname(filepath(node)) == '.rake'
|
||||
end
|
||||
|
||||
def filepath(node)
|
||||
node.location.expression.source_buffer.name
|
||||
end
|
||||
|
||||
# Allow `require "foo/rake_task"`
|
||||
def requires_task?(file)
|
||||
file.source.include?('task')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../usage_data_helpers'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module UsageData
|
||||
|
|
@ -13,6 +15,8 @@ module RuboCop
|
|||
# distinct_count(Ci::Build, :commit_id)
|
||||
#
|
||||
class DistinctCountByLargeForeignKey < RuboCop::Cop::Base
|
||||
include UsageDataHelpers
|
||||
|
||||
MSG = 'Avoid doing `%s` on foreign keys for large tables having above 100 million rows.'
|
||||
|
||||
def_node_matcher :distinct_count?, <<-PATTERN
|
||||
|
|
@ -20,6 +24,8 @@ module RuboCop
|
|||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless in_usage_data_file?(node)
|
||||
|
||||
distinct_count?(node) do |method_name, method_arguments|
|
||||
next unless method_arguments && method_arguments.length >= 2
|
||||
next if batch_set_to_false?(method_arguments[2])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../usage_data_helpers'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module UsageData
|
||||
|
|
@ -12,6 +14,8 @@ module RuboCop
|
|||
# histogram(Issue, buckets: 1..100)
|
||||
# histogram(User.active, buckets: 1..100)
|
||||
class HistogramWithLargeTable < RuboCop::Cop::Base
|
||||
include UsageDataHelpers
|
||||
|
||||
MSG = 'Avoid histogram method on %{model_name}'
|
||||
|
||||
# Match one level const as Issue, Gitlab
|
||||
|
|
@ -31,6 +35,8 @@ module RuboCop
|
|||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless in_usage_data_file?(node)
|
||||
|
||||
one_level_matches = one_level_node(node)
|
||||
two_level_matches = two_level_node(node)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../usage_data_helpers'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module UsageData
|
||||
|
|
@ -17,6 +19,8 @@ module RuboCop
|
|||
# # ...
|
||||
# end
|
||||
class InstrumentationSuperclass < RuboCop::Cop::Base
|
||||
include UsageDataHelpers
|
||||
|
||||
MSG = "Instrumentation classes should subclass one of the following: %{allowed_classes}."
|
||||
|
||||
BASE_PATTERN = "(const nil? !#allowed_class?)"
|
||||
|
|
@ -32,12 +36,16 @@ module RuboCop
|
|||
PATTERN
|
||||
|
||||
def on_class(node)
|
||||
return unless in_instrumentation_file?(node)
|
||||
|
||||
class_definition(node) do
|
||||
register_offense(node.children[1])
|
||||
end
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
return unless in_instrumentation_file?(node)
|
||||
|
||||
class_new_definition(node) do
|
||||
register_offense(node.children.last)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../usage_data_helpers'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module UsageData
|
||||
class LargeTable < RuboCop::Cop::Base
|
||||
include UsageDataHelpers
|
||||
|
||||
# This cop checks that batch count and distinct_count are used in usage_data.rb files in metrics based on ActiveRecord models.
|
||||
#
|
||||
# @example
|
||||
|
|
@ -38,6 +42,8 @@ module RuboCop
|
|||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless in_usage_data_file?(node)
|
||||
|
||||
one_level_matches = one_level_node(node)
|
||||
two_level_matches = two_level_node(node)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
UsageData/LargeTable:
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'lib/gitlab/usage_data.rb'
|
||||
- 'ee/lib/ee/gitlab/usage_data.rb'
|
||||
NonRelatedClasses:
|
||||
- :ActionMailer::Base
|
||||
- :Date
|
||||
|
|
@ -41,9 +38,6 @@ UsageData/LargeTable:
|
|||
- :maximum
|
||||
UsageData/HistogramWithLargeTable:
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'lib/gitlab/usage_data.rb'
|
||||
- 'ee/lib/ee/gitlab/usage_data.rb'
|
||||
HighTrafficModels: &high_traffic_models # models for all high traffic tables in Migration/UpdateLargeTable
|
||||
- 'AuditEvent'
|
||||
- 'CommitStatus'
|
||||
|
|
@ -98,9 +92,6 @@ UsageData/HistogramWithLargeTable:
|
|||
- 'WebHookLog'
|
||||
UsageData/DistinctCountByLargeForeignKey:
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'lib/gitlab/usage_data.rb'
|
||||
- 'ee/lib/ee/gitlab/usage_data.rb'
|
||||
AllowedForeignKeys:
|
||||
- 'agent_id'
|
||||
- 'author_id'
|
||||
|
|
@ -116,8 +107,6 @@ UsageData/DistinctCountByLargeForeignKey:
|
|||
- 'requirement_id'
|
||||
UsageData/InstrumentationSuperclass:
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'lib/gitlab/usage/metrics/instrumentations/**/*.rb'
|
||||
AllowedClasses:
|
||||
- :DatabaseMetric
|
||||
- :GenericMetric
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module UsageDataHelpers
|
||||
def in_usage_data_file?(node)
|
||||
filepath(node).end_with?('gitlab/usage_data.rb')
|
||||
end
|
||||
|
||||
def in_instrumentation_file?(node)
|
||||
filepath(node).start_with?('lib/gitlab/usage/metrics/instrumentations') && File.extname(filepath(node)) == '.rb'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filepath(node)
|
||||
node.location.expression.source_buffer.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -34,7 +34,7 @@ if ([ "${CI_PROJECT_NAME}" = "gitlab" ] && [ "${FOSS_ONLY}" != "1" ]) || ([ "${C
|
|||
fi
|
||||
|
||||
# Generate this image for https://jihulab.com/gitlab-cn/gitlab and https://gitlab.com/gitlab-jh/jh-team/gitlab
|
||||
if (["${CI_PROJECT_NAMESPACE}" = "gitlab-cn"] || ["${CI_PROJECT_NAMESPACE}" = "gitlab-jh"]); then
|
||||
if ([ "${CI_PROJECT_NAMESPACE}" = "gitlab-cn" ] || [ "${CI_PROJECT_NAMESPACE}" = "gitlab-jh" ]); then
|
||||
ASSETS_IMAGE_NAME="gitlab-assets-jh"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Rubocop doesn't have a good way to run excluded files without a separate invocation:
|
||||
# https://github.com/rubocop/rubocop/issues/6323
|
||||
find gems vendor/gems -name \*.gemspec | xargs bundle exec rubocop --only Gemspec/AvoidExecutingGit
|
||||
|
|
@ -51,8 +51,7 @@ class StaticAnalysis
|
|||
Task.new(%w[yarn run block-dependencies], 1),
|
||||
Task.new(%w[yarn run check-dependencies], 1),
|
||||
Task.new(%w[scripts/lint-rugged], 1),
|
||||
Task.new(%w[scripts/gemfile_lock_changed.sh], 1),
|
||||
Task.new(%w[scripts/lint-vendored-gems.sh], 1)
|
||||
Task.new(%w[scripts/gemfile_lock_changed.sh], 1)
|
||||
].compact.freeze
|
||||
|
||||
def run_tasks!(options = {})
|
||||
|
|
|
|||
|
|
@ -102,15 +102,16 @@ RSpec.describe 'Markdown keyboard shortcuts', :js, feature_category: :team_plann
|
|||
|
||||
it_behaves_like 'keyboard shortcuts'
|
||||
it_behaves_like 'no side effects'
|
||||
end
|
||||
|
||||
context 'Haml markdown editor' do
|
||||
let(:path_to_visit) { new_project_issue_path(project) }
|
||||
let(:markdown_field) { find_field('Description') }
|
||||
let(:non_markdown_field) { find_field('Title') }
|
||||
context 'if preview is toggled before shortcuts' do
|
||||
before do
|
||||
click_button "Preview"
|
||||
click_button "Continue editing"
|
||||
end
|
||||
|
||||
it_behaves_like 'keyboard shortcuts'
|
||||
it_behaves_like 'no side effects'
|
||||
it_behaves_like 'keyboard shortcuts'
|
||||
it_behaves_like 'no side effects'
|
||||
end
|
||||
end
|
||||
|
||||
def type_and_select(text)
|
||||
|
|
|
|||
|
|
@ -644,22 +644,14 @@ describe('DiffFileHeader component', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it.each`
|
||||
commentOnFiles | exists | existsText
|
||||
${false} | ${false} | ${'does not'}
|
||||
${true} | ${true} | ${'does'}
|
||||
`(
|
||||
'$existsText render comment on files button when commentOnFiles is $commentOnFiles',
|
||||
({ commentOnFiles, exists }) => {
|
||||
window.gon = { current_user_id: 1 };
|
||||
createComponent({
|
||||
props: {
|
||||
addMergeRequestButtons: true,
|
||||
},
|
||||
options: { provide: { glFeatures: { commentOnFiles } } },
|
||||
});
|
||||
it('should render the comment on files button', () => {
|
||||
window.gon = { current_user_id: 1 };
|
||||
createComponent({
|
||||
props: {
|
||||
addMergeRequestButtons: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="comment-files-button"]').exists()).toEqual(exists);
|
||||
},
|
||||
);
|
||||
expect(wrapper.find('[data-testid="comment-files-button"]').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -572,7 +572,6 @@ describe('DiffFile', () => {
|
|||
|
||||
({ wrapper, store } = createComponent({
|
||||
file,
|
||||
options: { provide: { glFeatures: { commentOnFiles: true } } },
|
||||
}));
|
||||
|
||||
expect(wrapper.find('[data-testid="file-discussions"]').exists()).toEqual(exists);
|
||||
|
|
@ -593,7 +592,6 @@ describe('DiffFile', () => {
|
|||
|
||||
({ wrapper, store } = createComponent({
|
||||
file,
|
||||
options: { provide: { glFeatures: { commentOnFiles: true } } },
|
||||
}));
|
||||
|
||||
expect(wrapper.find('[data-testid="file-note-form"]').exists()).toEqual(exists);
|
||||
|
|
@ -612,7 +610,6 @@ describe('DiffFile', () => {
|
|||
|
||||
({ wrapper, store } = createComponent({
|
||||
file,
|
||||
options: { provide: { glFeatures: { commentOnFiles: true } } },
|
||||
}));
|
||||
|
||||
expect(wrapper.find('[data-testid="diff-file-discussions"]').exists()).toEqual(exists);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { nextTick } from 'vue';
|
||||
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import WidgetWrapper from '~/work_items/components/widget_wrapper.vue';
|
||||
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
|
||||
import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/work_item_children_wrapper.vue';
|
||||
import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
|
||||
import OkrActionsSplitButton from '~/work_items/components/work_item_links/okr_actions_split_button.vue';
|
||||
|
||||
import {
|
||||
FORM_TYPES,
|
||||
WORK_ITEM_TYPE_ENUM_OBJECTIVE,
|
||||
|
|
@ -42,9 +40,8 @@ describe('WorkItemTree', () => {
|
|||
children,
|
||||
canUpdate,
|
||||
},
|
||||
stubs: { WidgetWrapper },
|
||||
});
|
||||
|
||||
wrapper.vm.$refs.wrapper.show = jest.fn();
|
||||
};
|
||||
|
||||
it('displays Add button', () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'ClickHouse::Client', feature_category: :database do
|
||||
it 'does not have any databases configured' do
|
||||
databases = ClickHouse::Client.configuration.databases
|
||||
|
||||
expect(databases).to be_empty
|
||||
end
|
||||
end
|
||||
|
|
@ -250,6 +250,7 @@ merge_request_diff:
|
|||
- merge_request_diff_detail
|
||||
- merge_request_diff_files
|
||||
- merge_request_diff_llm_summary
|
||||
- merge_request_review_llm_summaries
|
||||
merge_request_diff_commits:
|
||||
- merge_request_diff
|
||||
- commit_author
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ RSpec.describe RuboCop::Cop::QA::ElementWithPattern do
|
|||
end
|
||||
end
|
||||
|
||||
context 'outside of a migration spec file' do
|
||||
context 'when outside of a QA spec file' do
|
||||
it "does not register an offense" do
|
||||
expect_no_offenses(<<-RUBY)
|
||||
describe 'foo' do
|
||||
|
|
|
|||
|
|
@ -7,54 +7,84 @@ require_relative '../../../../rubocop/cop/rake/require'
|
|||
RSpec.describe RuboCop::Cop::Rake::Require do
|
||||
let(:msg) { described_class::MSG }
|
||||
|
||||
it 'registers an offenses for require methods' do
|
||||
expect_offense(<<~RUBY)
|
||||
require 'json'
|
||||
^^^^^^^^^^^^^^ #{msg}
|
||||
require_relative 'gitlab/json'
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg}
|
||||
RUBY
|
||||
describe '#in_rake_file?' do
|
||||
context 'in a Rake file' do
|
||||
let(:node) { double(location: double(expression: double(source_buffer: double(name: 'foo/bar.rake')))) } # rubocop:disable RSpec/VerifiedDoubles
|
||||
|
||||
it { expect(subject.__send__(:in_rake_file?, node)).to be(true) }
|
||||
end
|
||||
|
||||
context 'when outside of a Rake file' do
|
||||
let(:node) { double(location: double(expression: double(source_buffer: double(name: 'foo/bar.rb')))) } # rubocop:disable RSpec/VerifiedDoubles
|
||||
|
||||
it { expect(subject.__send__(:in_rake_file?, node)).to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not register offense inside `task` definition' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
task :parse do
|
||||
require 'json'
|
||||
end
|
||||
context 'in a Rake file' do
|
||||
before do
|
||||
allow(cop).to receive(:in_rake_file?).and_return(true)
|
||||
end
|
||||
|
||||
namespace :some do
|
||||
task parse: :env do
|
||||
require_relative 'gitlab/json'
|
||||
it 'registers an offenses for require methods' do
|
||||
expect_offense(<<~RUBY)
|
||||
require 'json'
|
||||
^^^^^^^^^^^^^^ #{msg}
|
||||
require_relative 'gitlab/json'
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg}
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register offense inside `task` definition' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
task :parse do
|
||||
require 'json'
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
namespace :some do
|
||||
task parse: :env do
|
||||
require_relative 'gitlab/json'
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register offense inside a block definition' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
RSpec::Core::RakeTask.new(:parse_json) do |t, args|
|
||||
require 'json'
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register offense inside a method definition' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
def load_deps
|
||||
require 'json'
|
||||
end
|
||||
|
||||
task :parse do
|
||||
load_deps
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register offense when require task related files' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
require 'rubocop/rake_tasks'
|
||||
require 'gettext_i18n_rails/tasks'
|
||||
require_relative '../../rubocop/check_graceful_task'
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not register offense inside a block definition' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
RSpec::Core::RakeTask.new(:parse_json) do |t, args|
|
||||
require 'json'
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
context 'when outside of a Rake file' do
|
||||
before do
|
||||
allow(cop).to receive(:in_rake_file?).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not register offense inside a method definition' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
def load_deps
|
||||
require 'json'
|
||||
end
|
||||
|
||||
task :parse do
|
||||
load_deps
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not register offense when require task related files' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
require 'rubocop/rake_tasks'
|
||||
require 'gettext_i18n_rails/tasks'
|
||||
require_relative '../../rubocop/check_graceful_task'
|
||||
RUBY
|
||||
it 'registers an offenses for require methods' do
|
||||
expect_no_offenses("require 'json'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,33 +13,45 @@ RSpec.describe RuboCop::Cop::UsageData::DistinctCountByLargeForeignKey do
|
|||
})
|
||||
end
|
||||
|
||||
context 'when counting by disallowed key' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
distinct_count(Issue, :creator_id)
|
||||
^^^^^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
context 'in an usage data file' do
|
||||
before do
|
||||
allow(cop).to receive(:in_usage_data_file?).and_return(true)
|
||||
end
|
||||
|
||||
it 'does not register an offense when batch is false' do
|
||||
expect_no_offenses('distinct_count(Issue, :creator_id, batch: false)')
|
||||
context 'when counting by disallowed key' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
distinct_count(Issue, :creator_id)
|
||||
^^^^^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
end
|
||||
|
||||
it 'does not register an offense when batch is false' do
|
||||
expect_no_offenses('distinct_count(Issue, :creator_id, batch: false)')
|
||||
end
|
||||
|
||||
it 'registers an offense when batch is true' do
|
||||
expect_offense(<<~CODE)
|
||||
distinct_count(Issue, :creator_id, batch: true)
|
||||
^^^^^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
it 'registers an offense when batch is true' do
|
||||
expect_offense(<<~CODE)
|
||||
distinct_count(Issue, :creator_id, batch: true)
|
||||
^^^^^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
context 'when calling by allowed key' do
|
||||
it 'does not register an offense with symbol' do
|
||||
expect_no_offenses('distinct_count(Issue, :author_id)')
|
||||
end
|
||||
|
||||
it 'does not register an offense with string' do
|
||||
expect_no_offenses("distinct_count(Issue, 'merge_requests.target_project_id')")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling by allowed key' do
|
||||
it 'does not register an offense with symbol' do
|
||||
expect_no_offenses('distinct_count(Issue, :author_id)')
|
||||
end
|
||||
|
||||
it 'does not register an offense with string' do
|
||||
expect_no_offenses("distinct_count(Issue, 'merge_requests.target_project_id')")
|
||||
context 'when outside of an usage data file' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('distinct_count(Issue, :creator_id)')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,93 +14,105 @@ RSpec.describe RuboCop::Cop::UsageData::HistogramWithLargeTable do
|
|||
})
|
||||
end
|
||||
|
||||
context 'with large tables' do
|
||||
context 'with one-level constants' do
|
||||
context 'when calling histogram(Issue)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Issue, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
context 'in an usage data file' do
|
||||
before do
|
||||
allow(cop).to receive(:in_usage_data_file?).and_return(true)
|
||||
end
|
||||
|
||||
context 'with large tables' do
|
||||
context 'with one-level constants' do
|
||||
context 'when calling histogram(Issue)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Issue, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(::Issue)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Issue, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(Issue.closed)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Issue.closed, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(::Issue.closed)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Issue.closed, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(::Issue)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Issue, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
context 'with two-level constants' do
|
||||
context 'when calling histogram(::Ci::Build)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Ci::Build, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(Issue.closed)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Issue.closed, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
context 'when calling histogram(::Ci::Build.active)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Ci::Build.active, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(::Issue.closed)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Issue.closed, :project_id, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Issue
|
||||
CODE
|
||||
context 'when calling histogram(Ci::Build)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Ci::Build, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(Ci::Build.active)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Ci::Build.active, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with two-level constants' do
|
||||
context 'when calling histogram(::Ci::Build)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Ci::Build, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
context 'with non related class' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('histogram(MergeRequest, buckets: 1..100)')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(::Ci::Build.active)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(::Ci::Build.active, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(Ci::Build)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Ci::Build, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when calling histogram(Ci::Build.active)' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
histogram(Ci::Build.active, buckets: 1..100)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{msg} Ci::Build
|
||||
CODE
|
||||
end
|
||||
context 'with non related method' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('count(Issue, buckets: 1..100)')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non related class' do
|
||||
context 'when outside of an usage data file' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('histogram(MergeRequest, buckets: 1..100)')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non related method' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('count(Issue, buckets: 1..100)')
|
||||
expect_no_offenses('histogram(Issue, :project_id, buckets: 1..100)')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,49 +14,63 @@ RSpec.describe RuboCop::Cop::UsageData::InstrumentationSuperclass do
|
|||
})
|
||||
end
|
||||
|
||||
context 'with class definition' do
|
||||
context 'when inheriting from allowed superclass' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('class NewMetric < GenericMetric; end')
|
||||
context 'when in an instrumentation file' do
|
||||
before do
|
||||
allow(cop).to receive(:in_instrumentation_file?).and_return(true)
|
||||
end
|
||||
|
||||
context 'with class definition' do
|
||||
context 'when inheriting from allowed superclass' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('class NewMetric < GenericMetric; end')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inheriting from some other superclass' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
class NewMetric < BaseMetric; end
|
||||
^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not inheriting' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('class NewMetric; end')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inheriting from some other superclass' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
class NewMetric < BaseMetric; end
|
||||
^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
context 'with dynamic class definition' do
|
||||
context 'when inheriting from allowed superclass' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('NewMetric = Class.new(GenericMetric)')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not inheriting' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('class NewMetric; end')
|
||||
context 'when inheriting from some other superclass' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
NewMetric = Class.new(BaseMetric)
|
||||
^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not inheriting' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('NewMetric = Class.new')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with dynamic class definition' do
|
||||
context 'when inheriting from allowed superclass' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('NewMetric = Class.new(GenericMetric)')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inheriting from some other superclass' do
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~CODE)
|
||||
NewMetric = Class.new(BaseMetric)
|
||||
^^^^^^^^^^ #{msg}
|
||||
CODE
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not inheriting' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('NewMetric = Class.new')
|
||||
end
|
||||
context 'when outside of an instrumentation file' do
|
||||
it "does not register an offense" do
|
||||
expect_no_offenses(<<-RUBY)
|
||||
class NewMetric < BaseMetric; end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ RSpec.describe RuboCop::Cop::UsageData::LargeTable do
|
|||
})
|
||||
end
|
||||
|
||||
context 'when in usage_data files' do
|
||||
context 'in an usage data file' do
|
||||
before do
|
||||
allow(cop).to receive(:usage_data_files?).and_return(true)
|
||||
allow(cop).to receive(:in_usage_data_file?).and_return(true)
|
||||
end
|
||||
|
||||
context 'with large tables' do
|
||||
|
|
@ -76,4 +76,10 @@ RSpec.describe RuboCop::Cop::UsageData::LargeTable do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when outside of an usage data file' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses('Issue.active.count')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -69,20 +69,23 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state,
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
let!(:uuid) { SecureRandom.uuid }
|
||||
let(:uuid) { SecureRandom.uuid }
|
||||
let!(:recursion_uuid) { SecureRandom.uuid }
|
||||
let(:headers) do
|
||||
{
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
|
||||
'X-Gitlab-Webhook-UUID' => uuid,
|
||||
'X-Gitlab-Event' => 'Push Hook',
|
||||
'X-Gitlab-Event-UUID' => uuid,
|
||||
'X-Gitlab-Event-UUID' => recursion_uuid,
|
||||
'X-Gitlab-Instance' => Gitlab.config.gitlab.base_url
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
# Set a stable value for the `X-Gitlab-Event-UUID` header.
|
||||
Gitlab::WebHooks::RecursionDetection.set_request_uuid(uuid)
|
||||
# Set stable values for the `X-Gitlab-Webhook-UUID` and `X-Gitlab-Event-UUID` headers.
|
||||
allow(SecureRandom).to receive(:uuid).and_return(uuid)
|
||||
Gitlab::WebHooks::RecursionDetection.set_request_uuid(recursion_uuid)
|
||||
end
|
||||
|
||||
context 'when there is an interpolation error' do
|
||||
|
|
|
|||
|
|
@ -54,39 +54,40 @@ module SimpleCovEnv
|
|||
SimpleCov.configure do
|
||||
load_profile 'test_frameworks'
|
||||
|
||||
add_filter '/bin/'
|
||||
add_filter 'db/fixtures/development/' # Matches EE files as well
|
||||
add_filter %r|db/migrate/\d{14}_init_schema\.rb\z| # Matches EE files as well
|
||||
add_filter '/gems/'
|
||||
add_filter '/vendor/'
|
||||
add_filter %r{^/(ee/)?(bin|gems|vendor)}
|
||||
add_filter %r{^/(ee/)?db/fixtures/development}
|
||||
add_filter %r{^/(ee/)?db/migrate/\d{14}_init_schema\.rb\z}
|
||||
|
||||
add_group 'Channels', 'app/channels' # Matches EE files as well
|
||||
add_group 'Components', 'app/components' # Matches EE files as well
|
||||
add_group 'Config', %w[/config /ee/config]
|
||||
add_group 'Controllers', 'app/controllers' # Matches EE files as well
|
||||
add_group 'Elastic migrations', 'ee/elastic'
|
||||
add_group 'Enums', 'app/enums' # Matches EE files as well
|
||||
add_group 'Events', 'app/events' # Matches EE files as well
|
||||
add_group 'Experiments', 'app/experiments' # Matches EE files as well
|
||||
add_group 'Finders', 'app/finders' # Matches EE files as well
|
||||
add_group 'Fixtures', 'db/fixtures' # Matches EE files as well
|
||||
add_group 'GraphQL', 'app/graphql' # Matches EE files as well
|
||||
add_group 'Helpers', 'app/helpers' # Matches EE files as well
|
||||
add_group 'Mailers', 'app/mailers' # Matches EE files as well
|
||||
add_group 'Models', 'app/models' # Matches EE files as well
|
||||
add_group 'Policies', 'app/policies' # Matches EE files as well
|
||||
add_group 'Presenters', 'app/presenters' # Matches EE files as well
|
||||
add_group 'Replicators', 'app/replicators' # Matches EE files as well
|
||||
add_group 'Serializers', 'app/serializers' # Matches EE files as well
|
||||
add_group 'Services', 'app/services' # Matches EE files as well
|
||||
add_group 'Uploaders', 'app/uploaders' # Matches EE files as well
|
||||
add_group 'Validators', 'app/validators' # Matches EE files as well
|
||||
add_group 'Views', 'app/views' # Matches EE files as well
|
||||
add_group 'Workers', 'app/workers' # Matches EE files as well
|
||||
add_group 'Initializers', %w[config/initializers config/initializers_before_autoloader] # Matches EE files as well
|
||||
add_group 'Migrations', %w[db/migrate db/optional_migrations db/post_migrate db/geo/migrate db/geo/post_migrate] # Matches EE files as well
|
||||
add_group 'Libraries', %w[/lib /ee/lib]
|
||||
add_group 'Tooling', %w[/haml_lint /rubocop /scripts /tooling]
|
||||
add_group 'Channels', %r{^/(ee/)?app/channels}
|
||||
add_group 'Components', %r{^/(ee/)?app/components}
|
||||
add_group 'Config', %r{^/(ee/)?config}
|
||||
add_group 'Controllers', %r{^/(ee/)?app/controllers}
|
||||
add_group 'Elastic migrations', %r{^/(ee/)?elastic}
|
||||
add_group 'Enums', %r{^/(ee/)?app/enums}
|
||||
add_group 'Events', %r{^/(ee/)?app/events}
|
||||
add_group 'Experiments', %r{^/(ee/)?app/experiments}
|
||||
add_group 'Finders', %r{^/(ee/)?app/finders}
|
||||
add_group 'Fixtures', %r{^/(ee/)?db/fixtures}
|
||||
add_group 'GraphQL', %r{^/(ee/)?app/graphql}
|
||||
add_group 'Helpers', %r{^/(ee/)?app/helpers}
|
||||
add_group 'Initializers', %r{^/(ee/)?config/(initializers|initializers_before_autoloader)}
|
||||
add_group 'Libraries', %r{^/(ee/)?lib}
|
||||
add_group 'Mailers', %r{^/(ee/)?app/mailers}
|
||||
add_group 'Metrics server', %r{^/(ee/)?metrics_server}
|
||||
add_group 'Migrations', %r{^/(ee/)?db/(geo/)?(migrate|optional_migrations|post_migrate)}
|
||||
add_group 'Models', %r{^/(ee/)?app/models}
|
||||
add_group 'Policies', %r{^/(ee/)?app/policies}
|
||||
add_group 'Presenters', %r{^/(ee/)?app/presenters}
|
||||
add_group 'Replicators', %r{^/(ee/)?app/replicators}
|
||||
add_group 'Seeds', %r{^/(ee/)?db/seeds}
|
||||
add_group 'Serializers', %r{^/(ee/)?app/serializers}
|
||||
add_group 'Services', %r{^/(ee/)?app/services}
|
||||
add_group 'Sidekiq cluster', %r{^/(ee/)?sidekiq_cluster}
|
||||
add_group 'Tooling', %r{^/(ee/)?(danger|haml_lint|rubocop|scripts|tooling)}
|
||||
add_group 'Uploaders', %r{^/(ee/)?app/uploaders}
|
||||
add_group 'Validators', %r{^/(ee/)?app/validators}
|
||||
add_group 'Views', %r{^/(ee/)?app/views}
|
||||
add_group 'Workers', %r{^/(ee/)?app/workers}
|
||||
|
||||
merge_timeout 365 * 24 * 3600
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue