Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-11-14 18:09:21 +00:00
parent 15c1cc886c
commit c3ccd2fdf1
74 changed files with 719 additions and 228 deletions

View File

@ -5,10 +5,7 @@ Gitlab/AvoidGitlabInstanceChecks:
- 'app/controllers/admin/runners_controller.rb'
- 'app/controllers/explore/groups_controller.rb'
- 'app/controllers/jira_connect/oauth_application_ids_controller.rb'
- 'app/helpers/application_helper.rb'
- 'app/helpers/application_settings_helper.rb'
- 'app/helpers/auth_helper.rb'
- 'app/helpers/ci/pipelines_helper.rb'
- 'app/helpers/groups_helper.rb'
- 'app/helpers/integrations_helper.rb'
- 'app/helpers/packages_helper.rb'

View File

@ -2,6 +2,15 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 16.5.2 (2023-11-14)
### Fixed (4 changes)
- [Fix assign security check permission checks](gitlab-org/gitlab@41d0d45a2abd8621ef55042eb0bc54343a48f2da) ([merge request](gitlab-org/gitlab!136434)) **GitLab Enterprise Edition**
- [Create group wiki repo if absent when verifying on primary](gitlab-org/gitlab@0b490bdbda06febdda47b2e0e0cea1d9e52dd381) ([merge request](gitlab-org/gitlab!136243)) **GitLab Enterprise Edition**
- [Fix broken issue rendering when initial ID is null](gitlab-org/gitlab@9f9fafbe202c5f5f591f689e295f62b01b8d40fc) ([merge request](gitlab-org/gitlab!136065))
- [Backport artifacts page breadcrumb fixes](gitlab-org/gitlab@4f15baf84a8de9068fa5c2c6af47fc74d2a46df8) ([merge request](gitlab-org/gitlab!135195))
## 16.5.1 (2023-10-30)
### Fixed (1 change)

View File

@ -504,7 +504,7 @@ group :test do
# Moved in `test` because https://gitlab.com/gitlab-org/gitlab/-/issues/217527
gem 'derailed_benchmarks', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'gitlab_quality-test_tooling', '~> 1.5.0', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'gitlab_quality-test_tooling', '~> 1.5.2', require: false, feature_category: :tooling
end
gem 'octokit', '~> 6.0' # rubocop:todo Gemfile/MissingFeatureCategory

View File

@ -119,7 +119,6 @@
{"name":"devise","version":"4.9.3","platform":"ruby","checksum":"480638d6c51b97f56da6e28d4f3e2a1b8e606681b316aa594b87c6ab94923488"},
{"name":"devise-two-factor","version":"4.1.1","platform":"ruby","checksum":"c95f5b07533e62217aaed3c386874d94e2d472fb5f2b6598afe8600fc17a8b95"},
{"name":"diff-lcs","version":"1.5.0","platform":"ruby","checksum":"49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67"},
{"name":"diff_match_patch","version":"0.1.0","platform":"ruby","checksum":"b36057bfcfeaedf19dcb7b2c28c19ee625bd6ec6d0d182717d3ef22b3879c40e"},
{"name":"diffy","version":"3.4.2","platform":"ruby","checksum":"36b42ffbe5138ddc56182107c24ad8d6b066ecfd2876829f391e3a4993d89ae1"},
{"name":"digest-crc","version":"0.6.4","platform":"ruby","checksum":"a283a60ad466f621b130c516a664df744207ee1b0283ec821fc1223eca54eab3"},
{"name":"discordrb-webhooks","version":"3.4.2","platform":"ruby","checksum":"cfdba8a4b28236b6ab34e37389f881a59c241aeb5be0a4447249efd4e4383c6e"},
@ -222,7 +221,7 @@
{"name":"gitlab-styles","version":"11.0.0","platform":"ruby","checksum":"0dd8ec066ce9955ac51d3616c6bfded30f75bb526f39ff392ece6f43d5b9406b"},
{"name":"gitlab_chronic_duration","version":"0.12.0","platform":"ruby","checksum":"0d766944d415b5c831f176871ee8625783fc0c5bfbef2d79a3a616f207ffc16d"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"gitlab_quality-test_tooling","version":"1.5.0","platform":"ruby","checksum":"7ce31d48462290f39c2c9bf8ae99b39b31e3a5eba0546bac058cdb6f7f88afd3"},
{"name":"gitlab_quality-test_tooling","version":"1.5.2","platform":"ruby","checksum":"1fe87d513f005fa2ad6c35ca2bef10a262e6ab3cca3754ff5ccf121622eb45f9"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},

View File

@ -716,7 +716,7 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
gitlab_quality-test_tooling (1.5.0)
gitlab_quality-test_tooling (1.5.2)
activesupport (>= 6.1, < 7.2)
amatch (~> 0.4.1)
gitlab (~> 4.19)
@ -1886,7 +1886,7 @@ DEPENDENCIES
gitlab-utils!
gitlab_chronic_duration (~> 0.12)
gitlab_omniauth-ldap (~> 2.2.0)
gitlab_quality-test_tooling (~> 1.5.0)
gitlab_quality-test_tooling (~> 1.5.2)
gon (~> 6.4.0)
google-apis-androidpublisher_v3 (~> 0.34.0)
google-apis-cloudbilling_v1 (~> 0.21.0)

View File

@ -112,11 +112,9 @@ export default {
</div>
</div>
<div
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-justify-content-space-between"
class="gl-display-flex gl-sm-flex-direction-column gl-justify-content-space-between gl-font-sm"
>
<span class="gl-display-flex gl-flex-basis-two-thirds gl-font-sm">{{
resource.description
}}</span>
<span class="gl-display-flex gl-flex-basis-two-thirds">{{ resource.description }}</span>
<div class="gl-display-flex gl-justify-content-end">
<span v-if="hasReleasedVersion">
<gl-sprintf :message="$options.i18n.releasedMessage">

View File

@ -36,7 +36,7 @@ export default function initDiffsApp(store = notesStore) {
iid: dataset.iid || '',
endpointCoverage: dataset.endpointCoverage || '',
endpointCodequality: dataset.endpointCodequality || '',
sastReportAvailable: dataset.endpointSast,
sastReportAvailable: parseBoolean(dataset.sastReportAvailable),
helpPagePath: dataset.helpPagePath,
currentUser: JSON.parse(dataset.currentUserData) || {},
changesEmptyStateIllustration: dataset.changesEmptyStateIllustration,

View File

@ -4,6 +4,7 @@ import DeleteEnvironmentModal from '../components/delete_environment_modal.vue';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
import environmentsMixin from '../mixins/environments_mixin';
import EnvironmentsPaginationApiMixin from '../mixins/environments_pagination_api_mixin';
import ConfirmRollbackModal from '../components/confirm_rollback_modal.vue';
export default {
components: {
@ -12,6 +13,7 @@ export default {
GlTab,
GlTabs,
StopEnvironmentModal,
ConfirmRollbackModal,
},
mixins: [environmentsMixin, EnvironmentsPaginationApiMixin],
@ -42,6 +44,7 @@ export default {
<div :class="cssContainerClass">
<stop-environment-modal :environment="environmentInStopModal" />
<delete-environment-modal :environment="environmentInDeleteModal" />
<confirm-rollback-modal :environment="environmentInRollbackModal" />
<h4 class="gl-font-weight-normal" data-testid="folder-name">
{{ s__('Environments|Environments') }} /

View File

@ -6,6 +6,7 @@ export const STATUS_MERGED = 'merged';
export const STATUS_OPEN = 'opened';
export const STATUS_REOPENED = 'reopened';
export const STATUS_LOCKED = 'locked';
export const STATUS_EMPTY = 'empty';
export const TITLE_LENGTH_MAX = 255;

View File

@ -8,6 +8,11 @@ export default {
RefSelector,
},
props: {
disabled: {
type: Boolean,
required: false,
default: false,
},
persistedDefaultBranch: {
type: String,
required: true,
@ -26,6 +31,7 @@ export default {
</script>
<template>
<ref-selector
:disabled="disabled"
:value="persistedDefaultBranch"
class="gl-w-full"
:project-id="projectId"

View File

@ -1,4 +1,5 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import DefaultBranchSelector from './components/default_branch_selector.vue';
export default (el) => {
@ -6,13 +7,14 @@ export default (el) => {
return null;
}
const { projectId, defaultBranch } = el.dataset;
const { projectId, defaultBranch, disabled } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(DefaultBranchSelector, {
props: {
disabled: parseBoolean(disabled),
persistedDefaultBranch: defaultBranch,
projectId,
},

View File

@ -28,12 +28,17 @@ export default {
},
inheritAttrs: false,
props: {
disabled: {
type: Boolean,
required: false,
default: false,
},
enabledRefTypes: {
type: Array,
required: false,
default: () => ALL_REF_TYPES,
validator: (val) =>
// It has to be an arrray
// It has to be an array
isArray(val) &&
// with at least one item
val.length > 0 &&
@ -234,6 +239,10 @@ export default {
this.debouncedSearch();
},
selectRef(ref) {
if (this.disabled) {
return;
}
this.setSelectedRef(ref);
this.$emit('input', this.selectedRef);
},
@ -262,6 +271,7 @@ export default {
:toggle-class="extendedToggleButtonClass"
:toggle-text="buttonText"
:icon="dropdownIcon"
:disabled="disabled"
v-bind="$attrs"
v-on="$listeners"
@hidden="$emit('hide')"

View File

@ -1,6 +1,6 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import { STATUS_CLOSED, STATUS_MERGED, STATUS_EMPTY } from '~/issues/constants';
import StatusIcon from './extensions/status_icon.vue';
export default {
@ -24,6 +24,9 @@ export default {
isMerged() {
return this.status === STATUS_MERGED;
},
isEmpty() {
return this.status === STATUS_EMPTY;
},
},
};
</script>
@ -33,6 +36,7 @@ export default {
<gl-icon v-if="isMerged" name="merge" :size="16" class="gl-text-blue-500" />
<gl-icon v-else-if="isClosed" name="merge-request-close" :size="16" class="gl-text-red-500" />
<gl-icon v-else-if="status === 'approval'" name="approval" :size="16" />
<status-icon v-else-if="isEmpty" icon-name="neutral" :level="1" class="gl-m-0!" />
<status-icon v-else :is-loading="isLoading" :icon-name="status" :level="1" class="gl-m-0!" />
</div>
</div>

View File

@ -37,7 +37,7 @@ export default {
<template>
<div class="mr-widget-body media">
<status-icon status="success" />
<p class="media-body gl-m-0! gl-font-weight-bold gl-text-gray-900!">
<p class="media-body gl-mt-1 gl-mb-0! gl-font-weight-bold gl-text-gray-900!">
<template v-if="canMerge">
{{ __('Ready to merge!') }}
</template>

View File

@ -1,48 +1,48 @@
<script>
import { GlSprintf, GlLink } from '@gitlab/ui';
import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-merge-requests-md.svg?url';
import { STATUS_EMPTY } from '~/issues/constants';
import { helpPagePath } from '~/helpers/help_page_helper';
import StatusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetNothingToMerge',
components: {
GlSprintf,
GlLink,
StatusIcon,
},
computed: {
statusEmpty() {
return STATUS_EMPTY;
},
},
ciHelpPage: helpPagePath('ci/quick_start/index.html'),
EMPTY_STATE_SVG_URL,
};
</script>
<template>
<div class="mr-widget-body mr-widget-empty-state">
<div class="row">
<div
class="col-md-3 col-12 text-center d-flex justify-content-center align-items-center svg-content svg-130 pb-0 pt-0"
>
<img :src="$options.EMPTY_STATE_SVG_URL" :alt="''" />
</div>
<div class="text col-md-9 col-12">
<p class="highlight mt-3">
{{ s__('mrWidgetNothingToMerge|Merge request contains no changes') }}
</p>
<p data-testid="nothing-to-merge-body">
<gl-sprintf
:message="
s__(
'mrWidgetNothingToMerge|Use merge requests to propose changes to your project and discuss them with your team. To make changes, use the %{boldStart}Code%{boldEnd} dropdown list above, then test them with %{linkStart}CI/CD%{linkEnd} before merging.',
)
"
>
<template #bold="{ content }">
<b>{{ content }}</b>
</template>
<template #link="{ content }">
<gl-link :href="$options.ciHelpPage" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<div class="mr-widget-body media">
<status-icon :status="statusEmpty" />
<div>
<p class="media-body gl-mt-1 gl-mb-1 gl-font-weight-bold gl-text-gray-900!">
{{ s__('mrWidgetNothingToMerge|Merge request contains no changes') }}
</p>
<p class="gl-m-0! gl-text-secondary" data-testid="nothing-to-merge-body">
<gl-sprintf
:message="
s__(
'mrWidgetNothingToMerge|Use merge requests to propose changes to your project and discuss them with your team. To make changes, use the %{boldStart}Code%{boldEnd} dropdown list above, then test them with %{linkStart}CI/CD%{linkEnd} before merging.',
)
"
>
<template #bold="{ content }">
<b>{{ content }}</b>
</template>
<template #link="{ content }">
<gl-link :href="$options.ciHelpPage" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
</div>
</template>

View File

@ -7,6 +7,7 @@ module Emails
include ::ServiceDesk::CustomEmails::Logger
EMAIL_ATTACHMENTS_SIZE_LIMIT = 10.megabytes.freeze
VERIFICATION_EMAIL_TIMEOUT = 7
included do
layout 'service_desk', only: [:service_desk_thank_you_email, :service_desk_new_note_email]
@ -146,7 +147,15 @@ module Emails
log_info(project: @project)
mail.delivery_method(::Mail::SMTP, @service_desk_setting.custom_email_credential.delivery_options)
delivery_options = @service_desk_setting.custom_email_credential.delivery_options
# We force the use of custom email settings when sending out the verification email.
# If the credentials aren't correct some servers tend to take a while to answer
# which leads to some Net::ReadTimeout errors which disguises the
# real configuration issue.
# We increase the timeout for verification emails only.
delivery_options[:read_timeout] = VERIFICATION_EMAIL_TIMEOUT if force
mail.delivery_method(::Mail::SMTP, delivery_options)
end
def service_desk_custom_email_enabled?

View File

@ -11,8 +11,6 @@ class ProjectSetting < ApplicationRecord
scope :for_projects, ->(projects) { where(project_id: projects) }
ignore_column :jitsu_key, remove_with: '16.7', remove_after: '2023-11-17'
attr_encrypted :cube_api_key,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_32,

View File

@ -5,6 +5,8 @@ class Vulnerability < ApplicationRecord
include EachBatch
include IgnorableColumns
ignore_column :milestone_id, remove_with: '16.9', remove_after: '2023-01-13'
alias_attribute :vulnerability_id, :id
scope :with_projects, -> { includes(:project) }

View File

@ -32,7 +32,9 @@ module BulkImports
entity.start!
BulkImports::ExportRequestWorker.perform_async(entity.id)
Gitlab::ApplicationContext.with_context(bulk_import_entity_id: entity.id) do
BulkImports::ExportRequestWorker.perform_async(entity.id)
end
end
end

View File

@ -1,3 +1,13 @@
- change_default_disabled = @default_branch_blocked_by_security_policy
- popover_data = {}
- if change_default_disabled
- tag_pair_security_policies_page = tag_pair(link_to('', namespace_project_security_policies_path, target: '_blank', rel: 'noopener noreferrer'), :security_policies_link_start, :security_policies_link_end)
- tag_pair_security_policies_docs = tag_pair(link_to('', help_page_path('user/application_security/policies/scan-result-policies'), target: '_blank', rel: 'noopener noreferrer'), :learn_more_link_start, :learn_more_link_end)
- popover_content = safe_format(s_("SecurityOrchestration|You can't change the default branch because its protection is enforced by one or more %{security_policies_link_start}security policies%{security_policies_link_end}. %{learn_more_link_start}Learn more%{learn_more_link_end}."), tag_pair_security_policies_docs, tag_pair_security_policies_page)
- popover_title = s_("SecurityOrchestration|Security policy overwrites this setting")
- popover_data = { container: 'body', toggle: 'popover', html: 'true', triggers: 'hover', title: popover_title, content: popover_content }
%fieldset#default-branch-settings
- if @project.empty_repo?
.text-secondary
@ -6,8 +16,8 @@
.form-group
= f.label :default_branch, _("Default branch"), class: 'label-bold'
%p= s_('ProjectSettings|All merge requests and commits are made against this branch unless you specify a different one.')
.gl-form-input-xl
.js-select-default-branch{ data: { default_branch: @project.default_branch, project_id: @project.id } }
.gl-form-input-xl{ data: { **popover_data } }
.js-select-default-branch{ data: { default_branch: @project.default_branch, project_id: @project.id, disabled: change_default_disabled.to_s } }
.form-group
- help_text = _("When merge requests and commits in the default branch close, any issues they reference also close.")

View File

@ -49,7 +49,9 @@ module BulkImports
attr_reader :entity
def re_enqueue
BulkImports::EntityWorker.perform_in(PERFORM_DELAY, entity.id)
with_context(bulk_import_entity_id: entity.id) do
BulkImports::EntityWorker.perform_in(PERFORM_DELAY, entity.id)
end
end
def running_tracker
@ -66,11 +68,13 @@ module BulkImports
next_pipeline_trackers.each_with_index do |pipeline_tracker, index|
log_info(message: 'Stage starting', entity_stage: pipeline_tracker.stage) if index == 0
BulkImports::PipelineWorker.perform_async(
pipeline_tracker.id,
pipeline_tracker.stage,
entity.id
)
with_context(bulk_import_entity_id: entity.id) do
BulkImports::PipelineWorker.perform_async(
pipeline_tracker.id,
pipeline_tracker.stage,
entity.id
)
end
end
end

View File

@ -20,7 +20,9 @@ module BulkImports
set_source_xid
request_export
BulkImports::EntityWorker.perform_async(entity_id)
with_context(bulk_import_entity_id: entity_id) do
BulkImports::EntityWorker.perform_async(entity_id)
end
end
def perform_failure(exception, entity_id)

View File

@ -38,7 +38,9 @@ module BulkImports
attr_reader :tracker
def re_enqueue
self.class.perform_in(REQUEUE_DELAY, tracker.id)
with_context(bulk_import_entity_id: tracker.entity.id) do
self.class.perform_in(REQUEUE_DELAY, tracker.id)
end
end
def import_in_progress?

View File

@ -42,6 +42,7 @@ module BulkImports
@batch = ::BulkImports::BatchTracker.find(batch_id)
@tracker = @batch.tracker
@entity = @tracker.entity
@pending_retry = false
return unless process_batch?
@ -50,7 +51,11 @@ module BulkImports
try_obtain_lease { run }
ensure
::BulkImports::FinishBatchedPipelineWorker.perform_async(tracker.id) unless pending_retry
unless pending_retry
with_context(bulk_import_entity_id: entity.id) do
::BulkImports::FinishBatchedPipelineWorker.perform_async(tracker.id)
end
end
end
def perform_failure(batch_id, exception)
@ -62,7 +67,7 @@ module BulkImports
private
attr_reader :batch, :tracker, :pending_retry
attr_reader :batch, :tracker, :pending_retry, :entity
def run
return batch.skip! if tracker.failed? || tracker.finished?
@ -83,7 +88,7 @@ module BulkImports
Gitlab::ErrorTracking.track_exception(exception, log_attributes(message: 'Batch tracker failed'))
BulkImports::Failure.create(
bulk_import_entity_id: batch.tracker.entity.id,
bulk_import_entity_id: tracker.entity.id,
pipeline_class: tracker.pipeline_name,
pipeline_step: 'pipeline_batch_worker_run',
exception_class: exception.class.to_s,
@ -91,7 +96,9 @@ module BulkImports
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
)
::BulkImports::FinishBatchedPipelineWorker.perform_async(tracker.id)
with_context(bulk_import_entity_id: tracker.entity.id) do
::BulkImports::FinishBatchedPipelineWorker.perform_async(tracker.id)
end
end
def context
@ -115,7 +122,9 @@ module BulkImports
def re_enqueue(delay = FILE_EXTRACTION_PIPELINE_PERFORM_DELAY)
log_extra_metadata_on_done(:re_enqueue, true)
self.class.perform_in(delay, batch.id)
with_context(bulk_import_entity_id: entity.id) do
self.class.perform_in(delay, batch.id)
end
end
def process_batch?

View File

@ -124,12 +124,14 @@ module BulkImports
def re_enqueue(delay = FILE_EXTRACTION_PIPELINE_PERFORM_DELAY)
log_extra_metadata_on_done(:re_enqueue, true)
self.class.perform_in(
delay,
pipeline_tracker.id,
pipeline_tracker.stage,
entity.id
)
with_context(bulk_import_entity_id: entity.id) do
self.class.perform_in(
delay,
pipeline_tracker.id,
pipeline_tracker.stage,
entity.id
)
end
end
def context
@ -218,7 +220,9 @@ module BulkImports
1.upto(export_status.batches_count) do |batch_number|
batch = pipeline_tracker.batches.find_or_create_by!(batch_number: batch_number) # rubocop:disable CodeReuse/ActiveRecord
::BulkImports::PipelineBatchWorker.perform_async(batch.id)
with_context(bulk_import_entity_id: entity.id) do
::BulkImports::PipelineBatchWorker.perform_async(batch.id)
end
end
end
end

View File

@ -4,6 +4,7 @@ module ClickHouse
class EventsSyncWorker
include ApplicationWorker
include Gitlab::ExclusiveLeaseHelpers
include Gitlab::Utils::StrongMemoize
idempotent!
queue_namespace :cronjob
@ -91,6 +92,11 @@ module ClickHouse
)
end
def last_event_id_in_postgresql
Event.maximum(:id)
end
strong_memoize_attr :last_event_id_in_postgresql
def enabled?
ClickHouse::Client.configuration.databases[:main].present? && Feature.enabled?(:event_sync_worker_for_click_house)
end
@ -110,24 +116,34 @@ module ClickHouse
def process_batch(context)
Enumerator.new do |yielder|
has_data = false
# rubocop: disable CodeReuse/ActiveRecord
Event.where(Event.arel_table[:id].gt(context.last_record_id)).each_batch(of: BATCH_SIZE) do |relation|
has_data = true
relation.select(*EVENT_PROJECTIONS).each do |row|
has_more_data = false
batching_scope.each_batch(of: BATCH_SIZE) do |relation|
records = relation.select(*EVENT_PROJECTIONS).to_a
has_more_data = records.size == BATCH_SIZE
records.each do |row|
yielder << row
context.last_processed_id = row.id
break if context.record_limit_reached?
end
break if context.over_time? || context.record_limit_reached?
break if context.over_time? || context.record_limit_reached? || !has_more_data
end
context.no_more_records! if has_data == false
# rubocop: enable CodeReuse/ActiveRecord
context.no_more_records! unless has_more_data
end
end
# rubocop: disable CodeReuse/ActiveRecord
def batching_scope
return Event.none unless last_event_id_in_postgresql
table = Event.arel_table
Event
.where(table[:id].gt(context.last_record_id))
.where(table[:id].lteq(last_event_id_in_postgresql))
end
# rubocop: enable CodeReuse/ActiveRecord
end
end

View File

@ -0,0 +1,8 @@
---
name: skip_code_generation_instruction_extraction
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136343
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431246
milestone: '16.7'
type: development
group: group::code creation
default_enabled: false

View File

@ -9,6 +9,10 @@ value_type: boolean
status: active
time_frame: none
data_source: system
instrumentation_class: GitlabConfigMetric
options:
config:
registry: enabled
distribution:
- ee
- ce

View File

@ -9,6 +9,10 @@ value_type: boolean
status: active
time_frame: none
data_source: system
instrumentation_class: GitlabConfigMetric
options:
config:
dependency_proxy: enabled
distribution:
- ee
- ce

View File

@ -9,6 +9,10 @@ value_type: boolean
status: active
time_frame: none
data_source: system
instrumentation_class: GitlabConfigMetric
options:
config:
gitlab_ci: shared_runners_enabled
distribution:
- ce
- ee

View File

@ -9,6 +9,10 @@ value_type: boolean
status: active
time_frame: none
data_source: system
instrumentation_class: GitlabConfigMetric
options:
config:
ldap: enabled
distribution:
- ce
- ee

View File

@ -9,6 +9,10 @@ value_type: boolean
status: active
time_frame: none
data_source: system
instrumentation_class: GitlabConfigMetric
options:
config:
mattermost: enabled
distribution:
- ce
- ee

View File

@ -5,7 +5,11 @@ require_relative '../../tooling/danger/outdated_todo'
module Danger
class Todos < ::Danger::Plugin
def check_outdated_todos(filenames)
Tooling::Danger::OutdatedTodo.new(filenames, context: self).check
Tooling::Danger::OutdatedTodo.new(filenames, context: self, allow_fail: from_lefthook?).check
end
def from_lefthook?
%w[1 true].include?(ENV['FROM_LEFTHOOK'])
end
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPreReceiveSecretDetectionEnabledToApplicationSettings < Gitlab::Database::Migration[2.2]
milestone '16.7'
def change
add_column :application_settings, :pre_receive_secret_detection_enabled, :boolean, null: false, default: false
end
end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
class CleanupCiPipelineVariablesPipelineIdBigint < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
TABLE = :ci_pipeline_variables
REFERENCING_TABLE = :ci_pipelines
COLUMNS = [:pipeline_id]
INDEX_NAME = :index_ci_pipeline_variables_on_pipeline_id_bigint_and_key
FK_NAME = :temp_fk_rails_8d3b04e3e1
def up
with_lock_retries(raise_on_exhaustion: true) do
lock_tables(:ci_pipelines, TABLE)
cleanup_conversion_of_integer_to_bigint(TABLE, COLUMNS)
end
end
def down
restore_conversion_of_integer_to_bigint(TABLE, COLUMNS)
add_concurrent_index(
TABLE, [:pipeline_id_convert_to_bigint, :key],
name: INDEX_NAME, unique: true
)
add_concurrent_foreign_key(
TABLE, REFERENCING_TABLE,
column: :pipeline_id_convert_to_bigint, name: FK_NAME,
on_delete: :cascade, validate: true, reverse_lock_order: true
)
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class DropIndexNamespacesOnRequireTwoFactorAuthentication < Gitlab::Database::Migration[2.2]
milestone '16.7'
disable_ddl_transaction!
TABLE_NAME = :namespaces
INDEX_NAME = :index_namespaces_on_require_two_factor_authentication
def up
remove_concurrent_index_by_name TABLE_NAME, INDEX_NAME
end
def down
add_concurrent_index TABLE_NAME, :require_two_factor_authentication,
name: INDEX_NAME
end
end

View File

@ -0,0 +1 @@
842fcd7c485ec4757810444172f2a0b7ec69ea0eda14662e0418ee3befdcaadc

View File

@ -0,0 +1 @@
057503cc1306afe9dea3a3d01a2fd8eeb240c33d292a6e3f2bd8ba52b38cfa62

View File

@ -0,0 +1 @@
ade28e4300509e182d6ab751b4412382c8e00803b102fc4f25bb0a0d049bcc30

View File

@ -565,15 +565,6 @@ BEGIN
END;
$$;
CREATE FUNCTION trigger_7f3d66a7d7f5() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW."pipeline_id_convert_to_bigint" := NEW."pipeline_id";
RETURN NEW;
END;
$$;
CREATE FUNCTION trigger_b2d852e1e2cb() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -12122,6 +12113,7 @@ CREATE TABLE application_settings (
enable_artifact_external_redirect_warning_page boolean DEFAULT true NOT NULL,
allow_project_creation_for_guest_and_below boolean DEFAULT true NOT NULL,
update_namespace_name_rate_limit smallint DEFAULT 120 NOT NULL,
pre_receive_secret_detection_enabled boolean DEFAULT false NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
@ -14242,7 +14234,6 @@ CREATE TABLE ci_pipeline_variables (
encrypted_value text,
encrypted_value_salt character varying,
encrypted_value_iv character varying,
pipeline_id_convert_to_bigint integer DEFAULT 0 NOT NULL,
variable_type smallint DEFAULT 1 NOT NULL,
partition_id bigint NOT NULL,
raw boolean DEFAULT false NOT NULL,
@ -32041,8 +32032,6 @@ CREATE INDEX index_ci_pipeline_schedules_on_project_id ON ci_pipeline_schedules
CREATE UNIQUE INDEX index_ci_pipeline_variables_on_pipeline_id_and_key ON ci_pipeline_variables USING btree (pipeline_id, key);
CREATE UNIQUE INDEX index_ci_pipeline_variables_on_pipeline_id_bigint_and_key ON ci_pipeline_variables USING btree (pipeline_id_convert_to_bigint, key);
CREATE INDEX index_ci_pipelines_config_on_pipeline_id ON ci_pipelines_config USING btree (pipeline_id);
CREATE INDEX index_ci_pipelines_for_ondemand_dast_scans ON ci_pipelines USING btree (id) WHERE (source = 13);
@ -33467,8 +33456,6 @@ CREATE INDEX index_namespaces_on_path_trigram ON namespaces USING gin (path gin_
CREATE UNIQUE INDEX index_namespaces_on_push_rule_id ON namespaces USING btree (push_rule_id);
CREATE INDEX index_namespaces_on_require_two_factor_authentication ON namespaces USING btree (require_two_factor_authentication);
CREATE UNIQUE INDEX index_namespaces_on_runners_token_encrypted ON namespaces USING btree (runners_token_encrypted);
CREATE INDEX index_namespaces_on_traversal_ids ON namespaces USING gin (traversal_ids);
@ -36927,8 +36914,6 @@ CREATE TRIGGER trigger_10ee1357e825 BEFORE INSERT OR UPDATE ON p_ci_builds FOR E
CREATE TRIGGER trigger_1bd97da9c1a4 BEFORE INSERT OR UPDATE ON ci_pipelines FOR EACH ROW EXECUTE FUNCTION trigger_1bd97da9c1a4();
CREATE TRIGGER trigger_7f3d66a7d7f5 BEFORE INSERT OR UPDATE ON ci_pipeline_variables FOR EACH ROW EXECUTE FUNCTION trigger_7f3d66a7d7f5();
CREATE TRIGGER trigger_b2d852e1e2cb BEFORE INSERT OR UPDATE ON ci_pipelines FOR EACH ROW EXECUTE FUNCTION trigger_b2d852e1e2cb();
CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace();
@ -39948,9 +39933,6 @@ ALTER TABLE issue_search_data
ALTER TABLE product_analytics_events_experimental
ADD CONSTRAINT product_analytics_events_experimental_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_variables
ADD CONSTRAINT temp_fk_rails_8d3b04e3e1 FOREIGN KEY (pipeline_id_convert_to_bigint) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_follow_users
ADD CONSTRAINT user_follow_users_followee_id_fkey FOREIGN KEY (followee_id) REFERENCES users(id) ON DELETE CASCADE;

View File

@ -346,6 +346,70 @@ The resulting file is named `dump_gitlab_backup.tar`. This is useful for
systems that make use of rsync and incremental backups, and results in
considerably faster transfer speeds.
#### Backup compression
By default, Gzip fast compression is applied during backup of:
- [PostgreSQL database](#postgresql-databases) dumps.
- [blobs](#blobs), for example uploads, job artifacts, external merge request diffs.
The default command is `gzip -c -1`. You can override this command with `COMPRESS_CMD`.
Caveats:
- The compression command is used in a pipeline, so your custom command must output to stdout.
- If you specify a command that is not packaged with GitLab, then you must install it yourself.
- The resultant filenames will still end in `.gz`.
- The default decompression command, used during restore, is `gzip -cd`. Therefore if you override the compression command to use a format that cannot be decompressed by `gzip -cd`, you must override the decompression command during restore.
### Default compression: Gzip with fastest method
```shell
gitlab-backup create
```
### Gzip with slowest method
```shell
gitlab-backup create COMPRESS_CMD="gzip -c --best"
```
If `gzip` was used for backup, then restore does not require any options:
```shell
gitlab-backup restore
```
### No compression
If your backup destination has built-in automatic compression, then you may wish to skip compression.
The `tee` command pipes `stdin` to `stdout`.
```shell
gitlab-backup create COMPRESS_CMD=tee
```
And on restore:
```shell
gitlab-backup restore DECOMPRESS_CMD=tee
```
### Replace Gzip
This is an example of how to use a compression tool which you installed manually:
```shell
gitlab-backup create COMPRESS_CMD="foo --bar --baz"
```
Similarly, on restore:
```shell
gitlab-backup restore DECOMPRESS_CMD="foo --quz"
```
#### Confirm archive can be transferred
To ensure the generated archive is transferable by rsync, you can set the `GZIP_RSYNCABLE=yes`

View File

@ -1212,6 +1212,7 @@ Input type: `AdminSidekiqQueuesDeleteJobsInput`
| <a id="mutationadminsidekiqqueuesdeletejobsartifactusedcdn"></a>`artifactUsedCdn` | [`String`](#string) | Delete jobs matching artifact_used_cdn in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsartifactsdependenciescount"></a>`artifactsDependenciesCount` | [`String`](#string) | Delete jobs matching artifacts_dependencies_count in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsartifactsdependenciessize"></a>`artifactsDependenciesSize` | [`String`](#string) | Delete jobs matching artifacts_dependencies_size in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsbulkimportentityid"></a>`bulkImportEntityId` | [`String`](#string) | Delete jobs matching bulk_import_entity_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobscallerid"></a>`callerId` | [`String`](#string) | Delete jobs matching caller_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsclientid"></a>`clientId` | [`String`](#string) | Delete jobs matching client_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |

View File

@ -130,14 +130,17 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
Rotate a group access token. Revokes the previous token and creates a new token that expires in one week.
In GitLab 16.6 and later, you can use the `expires_at` parameter to set a different expiry date. This non-default expiry date can be up to a maximum of one year from the rotation date.
```plaintext
POST /groups/:id/access_tokens/:token_id/rotate
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) |
| `token_id` | integer or string | yes | ID of the project access token |
| Attribute | Type | required | Description |
|-----------|------------|----------|---------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) |
| `token_id` | integer/string | yes | ID of the access token |
| `expires_at` | date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/416795) in GitLab 16.6. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/<group_id>/access_tokens/<token_id>/rotate"

View File

@ -211,13 +211,16 @@ Example response:
Rotate a personal access token. Revokes the previous token and creates a new token that expires in one week.
In GitLab 16.6 and later, you can use the `expires_at` parameter to set a different expiry date. This non-default expiry date can be up to a maximum of one year from the rotation date.
```plaintext
POST /personal_access_tokens/:id/rotate
```
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | ID of personal access token |
| Attribute | Type | Required | Description |
|-----------|-----------|----------|---------------------|
| `id` | integer/string | yes | ID of personal access token |
| `expires_at` | date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/416795) in GitLab 16.6. |
NOTE:
Non-administrators can rotate their own tokens. Administrators can rotate tokens of any user.

View File

@ -139,6 +139,8 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
Rotate a project access token. Revokes the previous token and creates a new token that expires in one week.
In GitLab 16.6 and later, you can use the `expires_at` parameter to set a different expiry date. This non-default expiry date can be up to a maximum of one year from the rotation date.
WARNING:
When you rotate a project access token, the new token retains the expiry date of the old token. For more information, see [issue 423362](https://gitlab.com/gitlab-org/gitlab/-/issues/423362).
@ -146,10 +148,11 @@ When you rotate a project access token, the new token retains the expiry date of
POST /projects/:id/access_tokens/:token_id/rotate
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `token_id` | integer or string | yes | ID of the project access token |
| Attribute | Type | required | Description |
|-----------|------------|----------|---------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `token_id` | integer/string | yes | ID of the project access token |
| `expires_at` | date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/416795) in GitLab 16.6. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<project_id>/access_tokens/<token_id>/rotate"

View File

@ -15,7 +15,7 @@ Use CI/CD pipelines to automatically build, test, and deploy your code.
| [Create and run your first GitLab CI/CD pipeline](../ci/quick_start/index.md) | Create a `.gitlab-ci.yml` file and start a pipeline. | **{star}** |
| [Create a complex pipeline](../ci/quick_start/tutorial.md) | Learn about the most commonly used GitLab CI/CD keywords by building an increasingly complex pipeline. | |
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Get started: Learn about CI/CD](https://www.youtube.com/watch?v=sIegJaLy2ug) (9m 02s) | Learn about the `.gitlab-ci.yml` file and how it's used. | **{star}** |
| [GitLab CI/CD](https://levelup.gitlab.com/courses/continuous-integration-and-delivery-ci-cd-with-gitlab) | Learn about GitLab CI/CD and build a pipeline in this self-paced course. | **{star}** |
| [GitLab CI Fundamentals](https://levelup.gitlab.com/learn/learning-path/gitlab-ci-fundamentals) | Learn about GitLab CI/CD and build a pipeline in this self-paced course. | **{star}** |
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [CI deep dive](https://www.youtube.com/watch?v=ZVUbmVac-m8&list=PL05JrBw4t0KorkxIFgZGnzzxjZRCGROt_&index=27) (22m 51s) | Take a closer look at pipelines and continuous integration concepts. | |
| [Set up CI/CD in the cloud](../ci/examples/index.md#cicd-in-the-cloud) | Learn how to set up CI/CD in different cloud-based environments. | |
| [Find CI/CD examples and templates](../ci/examples/index.md#cicd-examples) | Use these examples and templates to set up CI/CD for your use case. | |

View File

@ -4,7 +4,9 @@ pre-push:
- ref: master
commands:
danger:
run: bundle exec rake danger_local
files: git diff --name-only $(git merge-base origin/master HEAD)..HEAD
# We need to specify {files} as part of the command, otherwise it won't execute the hook
run: echo {files} >/dev/null && FROM_LEFTHOOK=1 bundle exec rake danger_local
eslint:
tags: frontend style
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD

View File

@ -103,7 +103,7 @@ module Backup
pg_env = backup_model.config[:pg_env]
success = with_transient_pg_env(pg_env) do
decompress_rd, decompress_wr = IO.pipe
decompress_pid = spawn(*%w[gzip -cd], out: decompress_wr, in: db_file_name)
decompress_pid = spawn(decompress_cmd, out: decompress_wr, in: db_file_name)
decompress_wr.close
status, @errors =

View File

@ -8,7 +8,7 @@ module Backup
def dump(database_name, output_file, pgsql_args)
compress_rd, compress_wr = IO.pipe
compress_pid = spawn(gzip_cmd, in: compress_rd, out: [output_file, 'w', FILE_PERMISSION])
compress_pid = spawn(compress_cmd, in: compress_rd, out: [output_file, 'w', FILE_PERMISSION])
compress_rd.close
dump_pid = Process.spawn('pg_dump', *pgsql_args, database_name, out: compress_wr)

View File

@ -40,14 +40,14 @@ module Backup
end
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{backup_files_realpath} -cf - .]].flatten
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
status_list, output = run_pipeline!([tar_cmd, compress_cmd], out: [backup_tarball, 'w', 0600])
FileUtils.rm_rf(backup_files_realpath)
else
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{app_files_realpath} -cf - .]].flatten
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
status_list, output = run_pipeline!([tar_cmd, compress_cmd], out: [backup_tarball, 'w', 0600])
end
unless pipeline_succeeded?(tar_status: status_list[0], gzip_status: status_list[1], output: output)
unless pipeline_succeeded?(tar_status: status_list[0], compress_status: status_list[1], output: output)
raise_custom_error(backup_tarball)
end
end
@ -56,9 +56,9 @@ module Backup
def restore(backup_tarball, backup_id)
backup_existing_files_dir(backup_tarball)
cmd_list = [%w[gzip -cd], %W[#{tar} --unlink-first --recursive-unlink -C #{app_files_realpath} -xf -]]
cmd_list = [decompress_cmd, %W[#{tar} --unlink-first --recursive-unlink -C #{app_files_realpath} -xf -]]
status_list, output = run_pipeline!(cmd_list, in: backup_tarball)
unless pipeline_succeeded?(gzip_status: status_list[0], tar_status: status_list[1], output: output)
unless pipeline_succeeded?(compress_status: status_list[0], tar_status: status_list[1], output: output)
raise Backup::Error, "Restore operation failed: #{output}"
end
end
@ -108,8 +108,8 @@ module Backup
noncritical_warnings.map { |w| warning =~ w }.any?
end
def pipeline_succeeded?(tar_status:, gzip_status:, output:)
return false unless gzip_status&.success?
def pipeline_succeeded?(tar_status:, compress_status:, output:)
return false unless compress_status&.success?
tar_status&.success? || tar_ignore_non_success?(tar_status.exitstatus, output)
end

View File

@ -2,6 +2,8 @@
module Backup
module Helper
include ::Gitlab::Utils::StrongMemoize
def access_denied_error(path)
message = <<~EOS
@ -30,12 +32,27 @@ module Backup
raise message
end
def gzip_cmd
@gzip_cmd ||= if ENV['GZIP_RSYNCABLE'] == 'yes'
"gzip --rsyncable -c -1"
else
"gzip -c -1"
end
def compress_cmd
if ENV['COMPRESS_CMD'].present?
puts "Using custom COMPRESS_CMD '#{ENV['COMPRESS_CMD']}'"
puts "Ignoring GZIP_RSYNCABLE" if ENV['GZIP_RSYNCABLE'] == 'yes'
ENV['COMPRESS_CMD']
elsif ENV['GZIP_RSYNCABLE'] == 'yes'
"gzip --rsyncable -c -1"
else
"gzip -c -1"
end
end
strong_memoize_attr :compress_cmd
def decompress_cmd
if ENV['DECOMPRESS_CMD'].present?
puts "Using custom DECOMPRESS_CMD '#{ENV['DECOMPRESS_CMD']}'"
ENV['DECOMPRESS_CMD']
else
"gzip -cd"
end
end
strong_memoize_attr :decompress_cmd
end
end

View File

@ -26,7 +26,8 @@ module Gitlab
:artifacts_dependencies_size,
:artifacts_dependencies_count,
:root_caller_id,
:merge_action_status
:merge_action_status,
:bulk_import_entity_id
].freeze
private_constant :KNOWN_KEYS
@ -45,7 +46,8 @@ module Gitlab
Attribute.new(:artifacts_dependencies_size, Integer),
Attribute.new(:artifacts_dependencies_count, Integer),
Attribute.new(:root_caller_id, String),
Attribute.new(:merge_action_status, String)
Attribute.new(:merge_action_status, String),
Attribute.new(:bulk_import_entity_id, Integer)
].freeze
private_constant :APPLICATION_ATTRIBUTES
@ -95,6 +97,7 @@ module Gitlab
# rubocop: disable Metrics/CyclomaticComplexity
# rubocop: disable Metrics/PerceivedComplexity
# rubocop: disable Metrics/AbcSize
def to_lazy_hash
{}.tap do |hash|
assign_hash_if_value(hash, :caller_id)
@ -106,6 +109,7 @@ module Gitlab
assign_hash_if_value(hash, :artifacts_dependencies_size)
assign_hash_if_value(hash, :artifacts_dependencies_count)
assign_hash_if_value(hash, :merge_action_status)
assign_hash_if_value(hash, :bulk_import_entity_id)
hash[:user] = -> { username } if include_user?
hash[:user_id] = -> { user_id } if include_user?
@ -115,10 +119,12 @@ module Gitlab
hash[:pipeline_id] = -> { job&.pipeline_id } if set_values.include?(:job)
hash[:job_id] = -> { job&.id } if set_values.include?(:job)
hash[:artifact_size] = -> { artifact&.size } if set_values.include?(:artifact)
hash[:bulk_import_entity_id] = -> { bulk_import_entity_id } if set_values.include?(:bulk_import_entity_id)
end
end
# rubocop: enable Metrics/CyclomaticComplexity
# rubocop: enable Metrics/PerceivedComplexity
# rubocop: enable Metrics/AbcSize
def use
Labkit::Context.with_context(to_lazy_hash) { yield }

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
module Instrumentations
class GitlabConfigMetric < GenericMetric
value do
method_name_array = config_hash_to_method_array(options[:config])
method_name_array.inject(Gitlab.config, :public_send)
end
private
def config_hash_to_method_array(object)
object.each_with_object([]) do |(key, value), result|
result.append(key)
if value.is_a?(Hash)
result.concat(config_hash_to_method_array(value))
else
result.append(value)
end
end
end
end
end
end
end
end

View File

@ -163,11 +163,6 @@ module Gitlab
def features_usage_data_ce
{
container_registry_enabled: alt_usage_data(fallback: nil) { Gitlab.config.registry.enabled },
dependency_proxy_enabled: Gitlab.config.try(:dependency_proxy)&.enabled,
gitlab_shared_runners_enabled: alt_usage_data(fallback: nil) { Gitlab.config.gitlab_ci.shared_runners_enabled },
ldap_enabled: alt_usage_data(fallback: nil) { Gitlab.config.ldap.enabled },
mattermost_enabled: alt_usage_data(fallback: nil) { Gitlab.config.mattermost.enabled },
omniauth_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth.omniauth_enabled? },
prometheus_enabled: alt_usage_data(fallback: nil) { Gitlab::Prometheus::Internal.prometheus_enabled? },
prometheus_metrics_enabled: alt_usage_data(fallback: nil) { Gitlab::Metrics.prometheus_metrics_enabled? },

View File

@ -43315,6 +43315,9 @@ msgstr ""
msgid "SecurityOrchestration|Security Scan"
msgstr ""
msgid "SecurityOrchestration|Security policy overwrites this setting"
msgstr ""
msgid "SecurityOrchestration|Security policy project was linked successfully"
msgstr ""
@ -43465,6 +43468,9 @@ msgstr ""
msgid "SecurityOrchestration|You already have the maximum %{maximumAllowed} %{policyType} policies."
msgstr ""
msgid "SecurityOrchestration|You can't change the default branch because its protection is enforced by one or more %{security_policies_link_start}security policies%{security_policies_link_end}. %{learn_more_link_start}Learn more%{learn_more_link_end}."
msgstr ""
msgid "SecurityOrchestration|You can't unprotect this branch because its protection is enforced by one or more %{security_policies_link_start}security policies%{security_policies_link_end}. %{learn_more_link_start}Learn more%{learn_more_link_end}."
msgstr ""

View File

@ -27,7 +27,7 @@ module RuboCop
MSG = 'Avoid the use of `%{name}`. Use Gitlab::Saas.feature_available?. ' \
'See https://docs.gitlab.com/ee/development/ee_features.html#saas-only-feature'
RESTRICT_ON_SEND = %i[
com? com_except_jh? com_and_canary? com_but_not_canary? org_or_com? should_check_namespace_plan?
com? com_except_jh? com_and_canary? com_but_not_canary? org_or_com? should_check_namespace_plan? enabled?
].freeze
# @!method gitlab?(node)
@ -43,8 +43,16 @@ module RuboCop
{nil? (cbase)} :Gitlab) :CurrentSettings) :should_check_namespace_plan?)
PATTERN
# @!method saas_enabled?(node)
def_node_matcher :saas_enabled?, <<~PATTERN
(send
(const
(const
{nil? (cbase)} :Gitlab) :Saas) :enabled?)
PATTERN
def on_send(node)
return unless gitlab?(node) || should_check_namespace_plan?(node)
return unless gitlab?(node) || should_check_namespace_plan?(node) || saas_enabled?(node)
add_offense(node, message: format(MSG, name: node.method_name))
end

View File

@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { removeBreakLine, removeWhitespace } from 'helpers/text_helper';
import EnvironmentTable from '~/environments/components/environments_table.vue';
import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
import EnvironmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
@ -91,6 +92,10 @@ describe('Environments Folder View', () => {
).toContain('Environments / review');
});
it('should render the confirm rollback modal', () => {
expect(wrapper.findComponent(ConfirmRollbackModal).exists()).toBe(true);
});
describe('pagination', () => {
it('should render pagination', () => {
expect(wrapper.findComponent(GlPagination).exists()).toBe(true);

View File

@ -4,6 +4,7 @@ import RefSelector from '~/ref/components/ref_selector.vue';
import { REF_TYPE_BRANCHES } from '~/ref/constants';
describe('projects/settings/components/default_branch_selector', () => {
const disabled = true;
const persistedDefaultBranch = 'main';
const projectId = '123';
let wrapper;
@ -13,6 +14,7 @@ describe('projects/settings/components/default_branch_selector', () => {
const buildWrapper = () => {
wrapper = shallowMount(DefaultBranchSelector, {
propsData: {
disabled,
persistedDefaultBranch,
projectId,
},
@ -25,6 +27,7 @@ describe('projects/settings/components/default_branch_selector', () => {
it('displays a RefSelector component', () => {
expect(findRefSelector().props()).toEqual({
disabled,
value: persistedDefaultBranch,
enabledRefTypes: [REF_TYPE_BRANCHES],
projectId,

View File

@ -46,7 +46,7 @@ describe('Ref selector component', () => {
let commitApiCallSpy;
let requestSpies;
const createComponent = (mountOverrides = {}, propsData = {}) => {
const createComponent = ({ overrides = {}, propsData = {} } = {}) => {
wrapper = mountExtended(
RefSelector,
merge(
@ -64,7 +64,7 @@ describe('Ref selector component', () => {
},
store: createStore(),
},
mountOverrides,
overrides,
),
);
};
@ -211,7 +211,7 @@ describe('Ref selector component', () => {
const id = 'git-ref';
beforeEach(() => {
createComponent({ attrs: { id } });
createComponent({ overrides: { attrs: { id } } });
return waitForRequests();
});
@ -326,7 +326,7 @@ describe('Ref selector component', () => {
describe('branches', () => {
describe('when the branches search returns results', () => {
beforeEach(() => {
createComponent({}, { useSymbolicRefNames: true });
createComponent({ propsData: { useSymbolicRefNames: true } });
return waitForRequests();
});
@ -389,7 +389,7 @@ describe('Ref selector component', () => {
describe('tags', () => {
describe('when the tags search returns results', () => {
beforeEach(() => {
createComponent({}, { useSymbolicRefNames: true });
createComponent({ propsData: { useSymbolicRefNames: true } });
return waitForRequests();
});
@ -569,6 +569,20 @@ describe('Ref selector component', () => {
});
});
});
describe('disabled', () => {
it('does not disable the dropdown', () => {
createComponent();
expect(findListbox().props('disabled')).toBe(false);
});
it('disables the dropdown', async () => {
createComponent({ propsData: { disabled: true } });
expect(findListbox().props('disabled')).toBe(true);
await selectFirstBranch();
expect(wrapper.emitted('input')).toBeUndefined();
});
});
});
describe('with non-default ref types', () => {
@ -691,9 +705,7 @@ describe('Ref selector component', () => {
});
beforeEach(() => {
createComponent({
scopedSlots: { footer: createFooter },
});
createComponent({ overrides: { scopedSlots: { footer: createFooter } } });
updateQuery('abcd1234');

View File

@ -54,5 +54,12 @@ describe('MR widget status icon component', () => {
expect(findIcon().exists()).toBe(true);
expect(findIcon().props().name).toBe('merge-request-close');
});
it('renders empty status icon', () => {
createWrapper({ status: 'empty' });
expect(findStatusIcon().exists()).toBe(true);
expect(findStatusIcon().props().iconName).toBe('neutral');
});
});
});

View File

@ -8,7 +8,7 @@ exports[`New ready to merge state component renders permission text if canMerge
status="success"
/>
<p
class="gl-font-weight-bold gl-m-0! gl-text-gray-900! media-body"
class="gl-font-weight-bold gl-mb-0! gl-mt-1 gl-text-gray-900! media-body"
>
Ready to merge by members who can write to the target branch.
</p>
@ -23,7 +23,7 @@ exports[`New ready to merge state component renders permission text if canMerge
status="success"
/>
<p
class="gl-font-weight-bold gl-m-0! gl-text-gray-900! media-body"
class="gl-font-weight-bold gl-mb-0! gl-mt-1 gl-text-gray-900! media-body"
>
Ready to merge!
</p>

View File

@ -1,5 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { helpPagePath } from '~/helpers/help_page_helper';
import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
describe('NothingToMerge', () => {
@ -14,6 +15,7 @@ describe('NothingToMerge', () => {
};
const findNothingToMergeTextBody = () => wrapper.findByTestId('nothing-to-merge-body');
const findHelpLink = () => wrapper.findComponent(GlLink);
describe('With Blob link', () => {
beforeEach(() => {
@ -26,5 +28,9 @@ describe('NothingToMerge', () => {
'Use merge requests to propose changes to your project and discuss them with your team. To make changes, use the Code dropdown list above, then test them with CI/CD before merging.',
);
});
it('renders text with link to CI Help Page', () => {
expect(findHelpLink().attributes('href')).toBe(helpPagePath('ci/quick_start/index.html'));
});
});
});

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Backup::Database, :reestablished_active_record_base, feature_category: :backup_restore do
let(:progress) { StringIO.new }
let(:output) { progress.string }
let(:progress_output) { progress.string }
let(:backup_id) { 'some_id' }
let(:one_database_configured?) { base_models_for_backup.one? }
let(:timeout_service) do
@ -223,7 +223,7 @@ RSpec.describe Backup::Database, :reestablished_active_record_base, feature_cate
subject.restore(backup_dir, backup_id)
expect(output).to include('Removing all tables. Press `Ctrl-C` within 5 seconds to abort')
expect(progress_output).to include('Removing all tables. Press `Ctrl-C` within 5 seconds to abort')
end
it 'has a pre restore warning' do
@ -241,9 +241,21 @@ RSpec.describe Backup::Database, :reestablished_active_record_base, feature_cate
subject.restore(backup_dir, backup_id)
expect(output).to include("Restoring PostgreSQL database")
expect(output).to include("[DONE]")
expect(output).not_to include("ERRORS")
expect(progress_output).to include("Restoring PostgreSQL database")
expect(progress_output).to include("[DONE]")
expect(progress_output).not_to include("ERRORS")
end
context 'when DECOMPRESS_CMD is set to tee' do
before do
stub_env('DECOMPRESS_CMD', 'tee')
end
it 'outputs a message about DECOMPRESS_CMD' do
expect do
subject.restore(backup_dir, backup_id)
end.to output(/Using custom DECOMPRESS_CMD 'tee'/).to_stdout
end
end
end
@ -277,9 +289,9 @@ RSpec.describe Backup::Database, :reestablished_active_record_base, feature_cate
subject.restore(backup_dir, backup_id)
expect(output).to include("ERRORS")
expect(output).not_to include(noise)
expect(output).to include(visible_error)
expect(progress_output).to include("ERRORS")
expect(progress_output).not_to include(noise)
expect(progress_output).to include(visible_error)
expect(subject.post_restore_warning).not_to be_nil
end
end

View File

@ -32,5 +32,25 @@ RSpec.describe Backup::Dump::Postgres, feature_category: :backup_restore do
expect(File.exist?(db_file_name)).to eq(true)
end
context 'when COMPRESS_CMD is set to tee' do
let(:tee_pid) { spawn('tee', in: pipes[0], out: [db_file_name, 'w', 0o600]) }
before do
stub_env('COMPRESS_CMD', 'tee')
end
it 'passes through tee instead of gzip' do
expect(subject).to receive(:spawn).with('tee', in: pipes.first,
out: [db_file_name, 'w', 0o600]).and_return(tee_pid)
expect(Process).to receive(:spawn).with('pg_dump', *args, pg_database, out: pipes[1]).and_return(pg_dump_pid)
expect do
subject.dump(pg_database, db_file_name, args)
end.to output(/Using custom COMPRESS_CMD 'tee'/).to_stdout
expect(File.exist?(db_file_name)).to eq(true)
end
end
end
end

View File

@ -68,7 +68,7 @@ RSpec.describe Backup::Files, feature_category: :backup_restore do
it 'calls tar command with unlink' do
expect(subject).to receive(:tar).and_return('blabla-tar')
expect(subject).to receive(:run_pipeline!).with([%w[gzip -cd], %w[blabla-tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -]], any_args)
expect(subject).to receive(:run_pipeline!).with(["gzip -cd", %w[blabla-tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -]], any_args)
expect(subject).to receive(:pipeline_succeeded?).and_return(true)
subject.restore('registry.tar.gz', 'backup_id')
end
@ -107,6 +107,21 @@ RSpec.describe Backup::Files, feature_category: :backup_restore do
expect { subject.restore('registry.tar.gz', 'backup_id') }.to raise_error(/is a mountpoint/)
end
end
describe 'with DECOMPRESS_CMD' do
before do
stub_env('DECOMPRESS_CMD', 'tee')
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
end
it 'passes through tee instead of gzip' do
expect(subject).to receive(:run_pipeline!).with(['tee', anything], any_args).and_return([[true, true], ''])
expect do
subject.restore('registry.tar.gz', 'backup_id')
end.to output(/Using custom DECOMPRESS_CMD 'tee'/).to_stdout
end
end
end
describe '#dump' do
@ -173,6 +188,37 @@ RSpec.describe Backup::Files, feature_category: :backup_restore do
.and raise_error(/Failed to create compressed file/)
end
end
describe 'with COMPRESS_CMD' do
before do
stub_env('COMPRESS_CMD', 'tee')
end
it 'passes through tee instead of gzip' do
expect(subject).to receive(:run_pipeline!).with([anything, 'tee'], any_args)
expect do
subject.dump('registry.tar.gz', 'backup_id')
end.to output(/Using custom COMPRESS_CMD 'tee'/).to_stdout
end
end
context 'when GZIP_RSYNCABLE is "yes"' do
before do
stub_env('GZIP_RSYNCABLE', 'yes')
end
it 'gzips the files with rsyncable option' do
expect(subject).to receive(:run_pipeline!).with([anything, 'gzip --rsyncable -c -1'], any_args)
subject.dump('registry.tar.gz', 'backup_id')
end
end
context 'when GZIP_RSYNCABLE is not set' do
it 'gzips the files without the rsyncable option' do
expect(subject).to receive(:run_pipeline!).with([anything, 'gzip -c -1'], any_args)
subject.dump('registry.tar.gz', 'backup_id')
end
end
end
describe '#exclude_dirs' do
@ -226,13 +272,13 @@ RSpec.describe Backup::Files, feature_category: :backup_restore do
it 'returns true if both tar and gzip succeeeded' do
expect(
subject.pipeline_succeeded?(tar_status: status_0, gzip_status: status_0, output: 'any_output')
subject.pipeline_succeeded?(tar_status: status_0, compress_status: status_0, output: 'any_output')
).to be_truthy
end
it 'returns false if gzip failed' do
expect(
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_1, output: 'any_output')
subject.pipeline_succeeded?(tar_status: status_1, compress_status: status_1, output: 'any_output')
).to be_falsey
end
@ -243,7 +289,7 @@ RSpec.describe Backup::Files, feature_category: :backup_restore do
it 'returns true' do
expect(
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_0, output: 'any_output')
subject.pipeline_succeeded?(tar_status: status_1, compress_status: status_0, output: 'any_output')
).to be_truthy
end
end
@ -255,7 +301,7 @@ RSpec.describe Backup::Files, feature_category: :backup_restore do
it 'returns false' do
expect(
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_0, output: 'any_output')
subject.pipeline_succeeded?(tar_status: status_1, compress_status: status_0, output: 'any_output')
).to be_falsey
end
end

View File

@ -210,6 +210,14 @@ RSpec.describe Gitlab::ApplicationContext do
expect(result(context)).to include(job_id: job.id, project: project.full_path, pipeline_id: job.pipeline_id)
end
end
context 'when using bulk import context' do
it 'sets expected bulk_import_entity_id value' do
context = described_class.new(bulk_import_entity_id: 1)
expect(result(context)).to include(bulk_import_entity_id: 1)
end
end
end
describe '#use' do

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GitlabConfigMetric, feature_category: :service_ping do
describe 'config metric' do
using RSpec::Parameterized::TableSyntax
where(:config_value, :expected_value) do
false | false
true | true
end
with_them do
before do
stub_config(artifacts: { object_store: { enabled: config_value } })
end
it_behaves_like 'a correct instrumented metric value', {
time_frame: 'none',
options: {
config: {
artifacts: {
object_store: 'enabled'
}
}
}
}
end
end
end

View File

@ -571,13 +571,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
subject { described_class.features_usage_data_ce }
it 'gathers feature usage data', :aggregate_failures do
expect(subject[:mattermost_enabled]).to eq(Gitlab.config.mattermost.enabled)
expect(subject[:ldap_enabled]).to eq(Gitlab.config.ldap.enabled)
expect(subject[:omniauth_enabled]).to eq(Gitlab::Auth.omniauth_enabled?)
expect(subject[:reply_by_email_enabled]).to eq(Gitlab::Email::IncomingEmail.enabled?)
expect(subject[:container_registry_enabled]).to eq(Gitlab.config.registry.enabled)
expect(subject[:dependency_proxy_enabled]).to eq(Gitlab.config.dependency_proxy.enabled)
expect(subject[:gitlab_shared_runners_enabled]).to eq(Gitlab.config.gitlab_ci.shared_runners_enabled)
end
context 'with embedded Prometheus' do

View File

@ -179,6 +179,11 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
it 'uses SMTP delivery method and custom email settings' do
expect_service_desk_custom_email_delivery_options(service_desk_setting)
# Don't use ActionMailer::Base.smtp_settings, because it only contains explicitly set values.
merged_default_settings = Mail::SMTP.new({}).settings
# When forcibly used the configuration has a higher timeout. Ensure it's the default!
expect(subject.delivery_method.settings[:read_timeout]).to eq(merged_default_settings[:read_timeout])
expect(Gitlab::AppLogger).to have_received(:info).with({ category: 'custom_email' })
end
@ -537,7 +542,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
}
end
subject { Notify.service_desk_custom_email_verification_email(service_desk_setting) }
subject(:mail) { Notify.service_desk_custom_email_verification_email(service_desk_setting) }
it_behaves_like 'a custom email verification process email'
@ -547,6 +552,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
it 'forcibly uses SMTP delivery method and has correct settings' do
expect_service_desk_custom_email_delivery_options(service_desk_setting)
expect(mail.delivery_method.settings[:read_timeout]).to eq(described_class::VERIFICATION_EMAIL_TIMEOUT)
# defaults are unchanged after email overrode settings
expect(Mail::SMTP.new({}).settings).to include(expected_delivery_method_defaults)
@ -557,7 +563,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
end
it 'uses verification email address as recipient' do
expect(subject.to).to eq([service_desk_setting.custom_email_address_for_verification])
expect(mail.to).to eq([service_desk_setting.custom_email_address_for_verification])
end
it 'contains verification token' do

View File

@ -18,6 +18,8 @@ RSpec.describe RuboCop::Cop::Gitlab::AvoidGitlabInstanceChecks, feature_category
::Gitlab.com?
Gitlab::CurrentSettings.should_check_namespace_plan?
::Gitlab::CurrentSettings.should_check_namespace_plan?
Gitlab::Saas.enabled?
::Gitlab::Saas.enabled?
]
end

View File

@ -77,13 +77,8 @@ module UsageDataHelpers
USAGE_DATA_KEYS = %i[
counts
recorded_at
mattermost_enabled
ldap_enabled
omniauth_enabled
reply_by_email_enabled
container_registry_enabled
dependency_proxy_enabled
gitlab_shared_runners_enabled
gitlab_pages
git
gitaly

View File

@ -15,69 +15,78 @@ RSpec.describe Tooling::Danger::OutdatedTodo, feature_category: :tooling do
]
end
subject(:plugin) { described_class.new(filenames, context: fake_danger, todos: todos) }
subject(:plugin) { described_class.new(filenames, context: fake_danger, todos: todos, allow_fail: allow_fail) }
context 'when the filenames are mentioned in single todo' do
let(:filenames) { ['app/controllers/acme_challenges_controller.rb'] }
[true, false].each do |allow_failure|
context "with allow_fail set to #{allow_failure}" do
let(:allow_fail) { allow_failure }
let(:expected_method) do
allow_failure ? :fail : :warn
end
it 'warns about mentions' do
expect(fake_danger)
.to receive(:warn)
.with <<~MESSAGE
`app/controllers/acme_challenges_controller.rb` was removed but is mentioned in:
- `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:5`
MESSAGE
context 'when the filenames are mentioned in single todo' do
let(:filenames) { ['app/controllers/acme_challenges_controller.rb'] }
plugin.check
end
end
it 'warns about mentions' do
expect(fake_danger)
.to receive(expected_method)
.with <<~MESSAGE
`app/controllers/acme_challenges_controller.rb` was removed but is mentioned in:
- `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:5`
MESSAGE
context 'when the filenames are mentioned in multiple todos' do
let(:filenames) do
[
'app/controllers/application_controller.rb',
'app/controllers/acme_challenges_controller.rb'
]
end
plugin.check
end
end
it 'warns about mentions' do
expect(fake_danger)
.to receive(:warn)
.with(<<~FIRSTMESSAGE)
`app/controllers/application_controller.rb` was removed but is mentioned in:
- `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:4`
- `spec/fixtures/tooling/danger/rubocop_todo/cop2.yml:4`
FIRSTMESSAGE
context 'when the filenames are mentioned in multiple todos' do
let(:filenames) do
[
'app/controllers/application_controller.rb',
'app/controllers/acme_challenges_controller.rb'
]
end
expect(fake_danger)
.to receive(:warn)
.with(<<~SECONDMESSAGE)
`app/controllers/acme_challenges_controller.rb` was removed but is mentioned in:
- `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:5`
SECONDMESSAGE
it 'warns about mentions' do
expect(fake_danger)
.to receive(expected_method)
.with(<<~FIRSTMESSAGE)
`app/controllers/application_controller.rb` was removed but is mentioned in:
- `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:4`
- `spec/fixtures/tooling/danger/rubocop_todo/cop2.yml:4`
FIRSTMESSAGE
plugin.check
end
end
expect(fake_danger)
.to receive(expected_method)
.with(<<~SECONDMESSAGE)
`app/controllers/acme_challenges_controller.rb` was removed but is mentioned in:
- `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:5`
SECONDMESSAGE
context 'when the filenames are not mentioned in todos' do
let(:filenames) { ['any/inexisting/file.rb'] }
plugin.check
end
end
it 'does not warn' do
expect(fake_danger).not_to receive(:warn)
context 'when the filenames are not mentioned in todos' do
let(:filenames) { ['any/inexisting/file.rb'] }
plugin.check
end
end
it 'does not warn' do
expect(fake_danger).not_to receive(expected_method)
context 'when there is no todos' do
let(:filenames) { ['app/controllers/acme_challenges_controller.rb'] }
let(:todos) { [] }
plugin.check
end
end
it 'does not warn' do
expect(fake_danger).not_to receive(:warn)
context 'when there is no todos' do
let(:filenames) { ['app/controllers/acme_challenges_controller.rb'] }
let(:todos) { [] }
plugin.check
it 'does not warn' do
expect(fake_danger).not_to receive(expected_method)
plugin.check
end
end
end
end
end

View File

@ -63,11 +63,32 @@ RSpec.describe ClickHouse::EventsSyncWorker, feature_category: :value_stream_man
end
it 'inserts all records' do
expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
{ status: :processed, records_inserted: 4, reached_end_of_table: true })
worker.perform
events = ClickHouse::Client.select('SELECT * FROM events', :main)
expect(events.size).to eq(4)
end
context 'when new records are inserted while processing' do
it 'does not process new records created during the iteration' do
expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
{ status: :processed, records_inserted: 4,
reached_end_of_table: true })
# Simulating the case when there is an insert during the iteration
call_count = 0
allow(worker).to receive(:next_batch).and_wrap_original do |method|
call_count += 1
create(:event) if call_count == 3
method.call
end
worker.perform
end
end
end
context 'when time limit is reached' do
@ -96,6 +117,9 @@ RSpec.describe ClickHouse::EventsSyncWorker, feature_category: :value_stream_man
end
it 'syncs records after the cursor' do
expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
{ status: :processed, records_inserted: 3, reached_end_of_table: true })
worker.perform
events = ClickHouse::Client.select('SELECT id FROM events ORDER BY id', :main)

View File

@ -8,10 +8,11 @@ module Tooling
spec/support/rspec_order_todo.yml
].freeze
def initialize(filenames, context:, todos: TODOS_GLOBS)
def initialize(filenames, context:, todos: TODOS_GLOBS, allow_fail: false)
@filenames = filenames
@context = context
@todos_globs = todos
@allow_fail = allow_fail
end
def check
@ -22,17 +23,23 @@ module Tooling
private
attr_reader :filenames, :context
attr_reader :filenames, :context, :allow_fail
def check_filename(filename)
mentions = all_mentions_for(filename)
return if mentions.empty?
context.warn <<~MESSAGE
message = <<~MESSAGE
`#{filename}` was removed but is mentioned in:
#{mentions.join("\n")}
MESSAGE
if allow_fail
context.fail message
else
context.warn message
end
end
def all_mentions_for(filename)