Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-04 21:34:59 +00:00
parent b87ea51c61
commit 9ccd95d7d7
108 changed files with 842 additions and 397 deletions

View File

@ -104,7 +104,7 @@ function mountPipelines() {
manualActionsLimit: 50,
mergeRequestId: mrWidgetData ? mrWidgetData.iid : null,
sourceProjectFullPath: mrWidgetData?.source_project_full_path || '',
useFailedJobsWidget: gon.features?.ciJobFailuresInMr || false,
useFailedJobsWidget: true,
},
render(createElement) {
return createElement('merge-request-pipelines-table', {

View File

@ -75,7 +75,7 @@ export default {
variant="danger"
>
<template #list-item>
<span class="gl-text-red-500">
<span class="gl-text-danger">
{{ $options.i18n.actionPrimaryText }}
</span>

View File

@ -61,7 +61,7 @@ export default {
variant="danger"
>
<template #list-item>
<span class="gl-text-red-500" data-testid="menu-item-text">
<span class="gl-text-danger" data-testid="menu-item-text">
{{ $options.modal.actionPrimary.text }}
</span>

View File

@ -4,6 +4,7 @@ import { __, s__ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import { noSpacesRegex } from '~/lib/utils/regexp';
import createModelMutation from '../graphql/mutations/create_model.mutation.graphql';
@ -11,6 +12,7 @@ import createModelMutation from '../graphql/mutations/create_model.mutation.grap
export default {
name: 'ModelCreate',
components: {
PageHeading,
MarkdownEditor,
GlAlert,
GlButton,
@ -64,6 +66,7 @@ export default {
},
async create() {
this.errorMessage = '';
try {
// Attempt creating a model if needed
if (!this.modelData) {
@ -120,7 +123,17 @@ export default {
<template>
<div>
<h2>{{ $options.i18n.title }}</h2>
<gl-alert
v-if="errorMessage"
class="gl-mt-5"
data-testid="create-alert"
variant="danger"
@dismiss="hideAlert"
>{{ errorMessage }}
</gl-alert>
<page-heading :heading="$options.i18n.title" />
<gl-form>
<gl-form-group
:label="$options.i18n.modelName"
@ -136,6 +149,7 @@ export default {
v-model="name"
data-testid="nameId"
type="text"
required
:placeholder="$options.i18n.namePlaceholder"
/>
</gl-form-group>
@ -163,22 +177,19 @@ export default {
@input="setDescription"
/>
</gl-form-group>
<div class="gl-flex gl-gap-3">
<gl-button
data-testid="primary-button"
variant="confirm"
:disabled="submitButtonDisabled"
@click="create"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
<gl-button data-testid="secondary-button" variant="default" @click="resetForm"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
</div>
</gl-form>
<gl-alert v-if="errorMessage" data-testid="create-alert" variant="danger" @dismiss="hideAlert"
>{{ errorMessage }}
</gl-alert>
<gl-button data-testid="secondary-button" variant="default" @click="resetForm"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
<gl-button
data-testid="primary-button"
variant="confirm"
:disabled="submitButtonDisabled"
@click="create"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
</div>
</template>

View File

@ -4,6 +4,7 @@ import { __, s__ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import { noSpacesRegex } from '~/lib/utils/regexp';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@ -12,6 +13,7 @@ import editModelMutation from '../graphql/mutations/edit_model.mutation.graphql'
export default {
name: 'ModelEdit',
components: {
PageHeading,
MarkdownEditor,
GlAlert,
GlButton,
@ -65,6 +67,7 @@ export default {
methods: {
async edit() {
this.errorMessage = '';
try {
const { data } = await this.$apollo.mutate({
mutation: editModelMutation,
@ -118,7 +121,17 @@ export default {
<template>
<div>
<h2>{{ $options.i18n.title }}</h2>
<gl-alert
v-if="errorMessage"
data-testid="edit-alert"
variant="danger"
class="gl-mt-5"
@dismiss="hideAlert"
>{{ errorMessage }}
</gl-alert>
<page-heading :heading="$options.i18n.title" />
<gl-form>
<gl-form-group
:label="$options.i18n.modelName"
@ -134,6 +147,7 @@ export default {
:value="model.name"
data-testid="nameId"
type="text"
required
:disabled="true"
/>
</gl-form-group>
@ -161,15 +175,15 @@ export default {
@input="setDescription"
/>
</gl-form-group>
<div class="gl-flex gl-gap-3">
<gl-button data-testid="primary-button" variant="confirm" @click="edit"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
<gl-button data-testid="secondary-button" variant="default" :href="modelPath"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
</div>
</gl-form>
<gl-alert v-if="errorMessage" data-testid="edit-alert" variant="danger" @dismiss="hideAlert"
>{{ errorMessage }}
</gl-alert>
<gl-button data-testid="secondary-button" variant="default" :href="modelPath"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
<gl-button data-testid="primary-button" variant="confirm" @click="edit"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
</div>
</template>

View File

@ -4,6 +4,7 @@ import { __, s__, sprintf } from '~/locale';
import { visitUrlWithAlerts } from '~/lib/utils/url_utility';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { semverRegex } from '~/lib/utils/regexp';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import createModelVersionMutation from '../graphql/mutations/create_model_version.mutation.graphql';
@ -11,6 +12,7 @@ import createModelVersionMutation from '../graphql/mutations/create_model_versio
export default {
name: 'ModelVersionCreate',
components: {
PageHeading,
GlAlert,
GlButton,
GlForm,
@ -109,6 +111,7 @@ export default {
},
async create() {
this.errorMessage = '';
try {
if (!this.versionData) {
this.versionData = await this.createModelVersion();
@ -176,9 +179,22 @@ export default {
<template>
<div>
<gl-alert
v-if="errorMessage"
class="gl-mt-5"
data-testid="create-alert"
variant="danger"
@dismiss="hideAlert"
>{{ errorMessage }}
</gl-alert>
<page-heading :heading="$options.i18n.title">
<template #description>
{{ $options.i18n.description }}
</template>
</page-heading>
<gl-form>
<h2 data-testid="title">{{ $options.i18n.title }}</h2>
<p data-testid="description" class="gl-text-default">{{ $options.i18n.description }}</p>
<gl-form-group
data-testid="versionDescriptionId"
:label="$options.i18n.versionLabelText"
@ -193,6 +209,7 @@ export default {
v-model="version"
data-testid="versionId"
type="text"
required
:placeholder="$options.i18n.versionPlaceholder"
autocomplete="off"
/>
@ -229,24 +246,24 @@ export default {
<import-artifact-zone
id="versionImportArtifactZone"
ref="importArtifactZoneRef"
class="gl-px-3 gl-py-0"
class="gl-px-0 gl-py-0"
:submit-on-select="false"
@error="onImportError"
/>
</gl-form-group>
<div class="gl-flex gl-gap-3">
<gl-button
data-testid="primary-button"
variant="confirm"
:disabled="submitButtonDisabled"
@click="create"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
<gl-button data-testid="secondary-button" variant="default" @click="resetForm"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
</div>
</gl-form>
<gl-alert v-if="errorMessage" variant="danger" @dismiss="hideAlert">{{
errorMessage
}}</gl-alert>
<gl-button
data-testid="primary-button"
variant="confirm"
:disabled="submitButtonDisabled"
@click="create"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
<gl-button data-testid="secondary-button" variant="default" @click="resetForm"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
</div>
</template>

View File

@ -3,6 +3,7 @@ import { GlAlert, GlButton, GlForm, GlFormGroup } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import editModelVersionMutation from '../graphql/mutations/edit_model_version.mutation.graphql';
@ -11,6 +12,7 @@ import { emptyArtifactFile } from '../constants';
export default {
name: 'ModelVersionEdit',
components: {
PageHeading,
MarkdownEditor,
GlAlert,
GlButton,
@ -104,7 +106,17 @@ export default {
<template>
<div>
<h2>{{ $options.i18n.title }}</h2>
<gl-alert
v-if="errorMessage"
data-testid="edit-alert"
variant="danger"
class="gl-mt-5"
@dismiss="hideAlert"
>{{ errorMessage }}
</gl-alert>
<page-heading :heading="$options.i18n.title" />
<gl-form>
<gl-form-group
:label="$options.i18n.descriptionLabel"
@ -129,16 +141,16 @@ export default {
:restricted-tool-bar-items="markdownEditorRestrictedToolBarItems"
@input="setDescription"
/>
<div class="gl-mt-5 gl-flex gl-gap-3">
<gl-button data-testid="primary-button" variant="confirm" @click="edit"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
<gl-button data-testid="secondary-button" variant="default" :href="modelVersionPath"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
</div>
</gl-form-group>
</gl-form>
<gl-alert v-if="errorMessage" data-testid="edit-alert" variant="danger" @dismiss="hideAlert"
>{{ errorMessage }}
</gl-alert>
<gl-button data-testid="secondary-button" variant="default" :href="modelVersionPath"
>{{ $options.i18n.actionSecondaryText }}
</gl-button>
<gl-button data-testid="primary-button" variant="confirm" @click="edit"
>{{ $options.i18n.actionPrimaryText }}
</gl-button>
</div>
</template>

View File

@ -5,27 +5,12 @@ import initTransferGroupForm from '~/groups/init_transfer_group_form';
import { initGroupSelects } from '~/vue_shared/components/entity_select/init_group_selects';
import { initProjectSelects } from '~/vue_shared/components/entity_select/init_project_selects';
import { initCascadingSettingsLockTooltips } from '~/namespaces/cascading_settings';
import { initDormantUsersInputSection } from '~/pages/admin/application_settings/account_and_limits';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import initSearchSettings from '~/search_settings';
import initSettingsPanels from '~/settings_panels';
import initConfirmDanger from '~/init_confirm_danger';
import { initGroupSettingsReadme } from '~/groups/settings/init_group_settings_readme';
/**
* Sets up logic inside "Dormant members" subsection:
* - checkbox enables/disables additional input
* - shows/hides an inline error on input validation
*/
function initDeactivateDormantMembersPeriodInputSection() {
initDormantUsersInputSection(
'group_remove_dormant_members',
'group_remove_dormant_members_period',
'group_remove_dormant_members_period_error',
);
}
initDeactivateDormantMembersPeriodInputSection();
initFilePickers();
initConfirmDanger();
initSettingsPanels();

View File

@ -47,8 +47,6 @@ module Groups
] },
:default_branch_name,
:allow_mfa_for_subgroups,
:remove_dormant_members,
:remove_dormant_members_period,
:resource_access_token_creation_allowed,
:resource_access_token_notify_inherited,
:lock_resource_access_token_notify_inherited,

View File

@ -49,7 +49,6 @@ module WikiActions
end
end
track_event :show, name: 'wiki_action'
track_internal_event :show, name: 'view_wiki_page'
helper_method :view_file_button, :diff_file_html_data

View File

@ -165,7 +165,9 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
return @merge_request.merge_head_diff if render_merge_ref_head_diff?
if @start_sha
@merge_request_diff.compare_with(@start_sha)
::MergeRequests::MergeRequestDiffComparison
.new(@merge_request_diff)
.compare_with(@start_sha)
else
@merge_request_diff
end

View File

@ -41,7 +41,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action only: [:show, :diffs, :rapid_diffs, :reports] do
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
push_frontend_feature_flag(:mr_experience_survey, project)
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
push_frontend_feature_flag(:mr_pipelines_graphql, project)
push_frontend_feature_flag(:ci_graphql_pipeline_mini_graph, project)
push_frontend_feature_flag(:notifications_todos_buttons, current_user)

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module MergeRequests
class MergeRequestDiffComparison
def initialize(merge_request_diff)
@merge_request_diff = merge_request_diff
@project = merge_request_diff.project
end
def compare_with(sha)
# When compare merge request versions we want diff A..B instead of A...B
# so we handle cases when user does squash and rebase of the commits between versions.
# For this reason we set straight to true by default.
CompareService
.new(project, merge_request_diff.head_commit_sha)
.execute(project, sha, straight: true)
end
private
attr_reader :merge_request_diff, :project
end
end

View File

@ -173,6 +173,10 @@ class AbuseReport < ApplicationRecord
nil
end
def uploads_sharding_key
{}
end
private
def reported_project

View File

@ -17,5 +17,9 @@ module Achievements
length: { maximum: 255 },
uniqueness: { case_sensitive: false, scope: [:namespace_id] }
validates :description, length: { maximum: 1024 }
def uploads_sharding_key
{ namespace_id: namespace_id }
end
end
end

View File

@ -7,6 +7,10 @@ module AlertManagement
belongs_to :alert, class_name: 'AlertManagement::Alert', foreign_key: 'alert_id', inverse_of: :metric_images
def uploads_sharding_key
{ project_id: project_id }
end
private
def local_path

View File

@ -102,6 +102,10 @@ class Appearance < ApplicationRecord
footer_message.present?
end
def uploads_sharding_key
{}
end
private
def logo_system_path(logo, mount_type)

View File

@ -22,5 +22,12 @@ module BulkImports
def retrieve_upload(_identifier, paths)
Upload.find_by(model: self, path: paths)
end
def uploads_sharding_key
{
project_id: export&.project_id,
namespace_id: export&.group_id
}
end
end
end

View File

@ -43,5 +43,9 @@ module DesignManagement
raise ArgumentError, "Expected a DesignManagement::Version, got #{version}"
end
end
def uploads_sharding_key
{ namespace_id: design&.namespace_id }
end
end
end

View File

@ -44,6 +44,13 @@ class ImportExportUpload < ApplicationRecord
false
end
def uploads_sharding_key
{
project_id: project_id,
namespace_id: group_id
}
end
private
def carrierwave_export_file

View File

@ -528,15 +528,6 @@ class MergeRequestDiff < ApplicationRecord
self.id == merge_request.latest_merge_request_diff_id
end
# rubocop: disable CodeReuse/ServiceClass
def compare_with(sha)
# When compare merge request versions we want diff A..B instead of A...B
# so we handle cases when user does squash and rebase of the commits between versions.
# For this reason we set straight to true by default.
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end
# rubocop: enable CodeReuse/ServiceClass
def modified_paths(fallback_on_overflow: false)
if fallback_on_overflow && overflow?
# This is an extremely slow means to find the modified paths for a given

View File

@ -755,6 +755,10 @@ class Namespace < ApplicationRecord
!!deleted_at
end
def uploads_sharding_key
{ organization_id: organization_id }
end
private
def require_organization?

View File

@ -25,16 +25,11 @@ class NamespaceSetting < ApplicationRecord
validates :enabled_git_access_protocol, inclusion: { in: enabled_git_access_protocols.keys }
validates :default_branch_protection_defaults, json_schema: { filename: 'default_branch_protection_defaults' }
validates :default_branch_protection_defaults, bytesize: { maximum: -> { DEFAULT_BRANCH_PROTECTIONS_DEFAULT_MAX_SIZE } }
validates :remove_dormant_members, inclusion: { in: [false] }, if: :subgroup?
validates :remove_dormant_members_period,
numericality: { only_integer: true, greater_than_or_equal_to: 90, less_than_or_equal_to: 1827 } # 90 days - ~5 years
validates :allow_mfa_for_subgroups, presence: true, if: :subgroup?
validates :resource_access_token_creation_allowed, presence: true, if: :subgroup?
sanitizes! :default_branch_name
after_initialize :set_default_values, if: :new_record?
before_validation :normalize_default_branch_name
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
@ -107,10 +102,6 @@ class NamespaceSetting < ApplicationRecord
private
def set_default_values
self.remove_dormant_members_period = 90
end
def all_ancestors_have_emails_enabled?
self.class.where(namespace_id: namespace.self_and_ancestors, emails_enabled: false).none?
end

View File

@ -723,6 +723,10 @@ class Note < ApplicationRecord
attributes.keys
end
def uploads_sharding_key
{ namespace_id: namespace_id }
end
private
def trigger_note_subscription?

View File

@ -12,5 +12,9 @@ module Organizations
validates :organization, presence: true
validates :description, length: { maximum: 1024 }
def uploads_sharding_key
{ organization_id: organization_id }
end
end
end

View File

@ -14,4 +14,8 @@ class PersonalSnippet < Snippet
def skip_project_check?
true
end
def uploads_sharding_key
{ organization_id: organization_id }
end
end

View File

@ -3437,6 +3437,10 @@ class Project < ApplicationRecord
)
end
def uploads_sharding_key
{ namespace_id: namespace_id }
end
def pages_url
Gitlab::Pages::UrlBuilder.new(self).pages_url
end

View File

@ -27,6 +27,10 @@ module Projects
skip_callback :save, :after, :store_export_file!
set_callback :commit, :after, :store_export_file!
end
def uploads_sharding_key
{ project_id: relation_export&.project_id }
end
end
end
end

View File

@ -76,6 +76,10 @@ module Projects
end
end
def uploads_sharding_key
{ organization_id: organization_id }
end
private
def validate_name_format

View File

@ -23,6 +23,7 @@ class Upload < ApplicationRecord
scope :preload_uploaded_by_user, -> { preload(:uploaded_by_user) }
before_save :calculate_checksum!, if: :foreground_checksummable?
before_save :ensure_sharding_key
# as the FileUploader is not mounted, the default CarrierWave ActiveRecord
# hooks are not executed and the file will not be deleted
after_destroy :delete_file!, if: -> { uploader_class <= FileUploader }
@ -182,6 +183,17 @@ class Upload < ApplicationRecord
def update_project_statistics
ProjectCacheWorker.perform_async(model_id, [], %w[uploads_size])
end
def ensure_sharding_key
sharding_key = model&.uploads_sharding_key
return unless sharding_key.present?
# This is workaround for some migrations that rely on application code to use
# bot users, and creating these fail in tests if the column is not present yet.
return unless sharding_key.each_key.all? { |k| respond_to?(k) }
assign_attributes(sharding_key)
end
end
Upload.prepend_mod_with('Upload')

View File

@ -2530,6 +2530,10 @@ class User < ApplicationRecord
false
end
def uploads_sharding_key
{}
end
protected
# override, from Devise::Validatable

View File

@ -8,6 +8,8 @@
# EventCreateService.new.new_issue(issue, current_user)
#
class EventCreateService
include Gitlab::InternalEventsTracking
IllegalActionError = Class.new(StandardError)
DEGIGN_EVENT_LABEL = 'usage_activity_by_stage_monthly.create.action_monthly_active_users_design_management'
@ -176,9 +178,14 @@ class EventCreateService
def wiki_event(wiki_page_meta, author, action, fingerprint)
raise IllegalActionError, action unless Event::WIKI_ACTIONS.include?(action)
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:wiki_action, values: author.id)
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:git_write_action, values: author.id)
track_internal_event("performed_wiki_action",
project: wiki_page_meta.project,
user: author,
additional_properties: { label: action.to_s }
)
duplicate = Event.for_wiki_meta(wiki_page_meta).for_fingerprint(fingerprint).first
return duplicate if duplicate.present?

View File

@ -89,8 +89,6 @@ module Groups
end
params.delete(:allow_mfa_for_subgroups)
params.delete(:remove_dormant_members)
params.delete(:remove_dormant_members_period)
params.delete(:math_rendering_limits_enabled)
params.delete(:lock_math_rendering_limits_enabled)
end

View File

@ -6,8 +6,6 @@ module Groups
SETTINGS_PARAMS = [
:allow_mfa_for_subgroups,
:remove_dormant_members,
:remove_dormant_members_period,
:early_access_program_participant
].freeze

View File

@ -1 +1 @@
#js-ci-token-access-app{ data: { full_path: @project.full_path, csv_download_path: export_job_token_authorizations_namespace_project_settings_ci_cd_path(@project), enforce_allowlist: Gitlab::CurrentSettings.enforce_ci_inbound_job_token_scope_enabled?.to_s } }
#js-ci-token-access-app{ data: { full_path: @project.full_path, csv_download_path: export_job_token_authorizations_namespace_project_settings_ci_cd_path(@project.namespace, @project), enforce_allowlist: Gitlab::CurrentSettings.enforce_ci_inbound_job_token_scope_enabled?.to_s } }

View File

@ -42,7 +42,7 @@
= render 'groups/settings/two_factor_auth', f: f, group: @group
= render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
= render 'groups/settings/membership', f: f, group: @group
= render 'groups/settings/remove_dormant_members', f: f, group: @group
= render_if_exists 'groups/settings/remove_dormant_members', f: f, group: @group
= render_if_exists 'groups/settings/personal_access_tokens', f: f, group: @group
= render_if_exists 'groups/settings/extensions_marketplace', f: f, group: @group

View File

@ -1,18 +0,0 @@
- return unless ::Feature.enabled?(:group_remove_dormant_members, @group)
- return unless @group.root?
.form-group
= f.label :remove_dormant_members, _('Dormant members'), class: 'label-bold'
= f.gitlab_ui_checkbox_component :remove_dormant_members,
_('Remove dormant members after a period of inactivity'),
help_text: _('Removed members no longer have access to this top-level group, its sub-groups, and their projects.'),
checkbox_options: { checked: group.namespace_settings&.remove_dormant_members }
.form-group
= f.label :remove_dormant_members_period, _('Days of inactivity before removal'), class: 'label-light'
= f.number_field :remove_dormant_members_period, class: 'form-control w-auto gl-form-input', min: 90, max: 1827,
required: true, value: group.namespace_settings&.remove_dormant_members_period,
disabled: !group.namespace_settings&.remove_dormant_members?
#group_remove_dormant_members_period_error.form-text.gl-text-red-500.gl-hidden
= _('Please enter a value between 90 and 1827 days (5 years).')
.form-text.gl-text-subtle
= _('Must be 90 days or more.')

View File

@ -1,6 +1,6 @@
- add_to_breadcrumbs s_('ModelRegistry|Model registry'), project_ml_models_path(@project)
- add_to_breadcrumbs @model_version.name, project_ml_model_path(@project, @model)
- breadcrumb_title @model_version.version
- breadcrumb_title _('Edit')
- page_title "#{@model_version.name} / #{@model_version.version}"
#js-mount-edit-ml-model-version{ data: { view_model: edit_ml_model_version_data(@model_version, @current_user) } }

View File

@ -1,6 +1,6 @@
- add_to_breadcrumbs s_('ModelRegistry|Model registry'), project_ml_models_path(@project)
- add_to_breadcrumbs @model.name, project_ml_model_path(@project, @model)
- breadcrumb_title @model.name
- breadcrumb_title s_('ModelRegistry|New version')
- page_title @model.name
#js-mount-new-ml-model-version{ data: { view_model: new_ml_model_version_data(@model, @current_user) } }

View File

@ -0,0 +1,18 @@
---
description: Action on a wiki page was performed
internal_events: true
action: performed_wiki_action
identifiers:
- project
- namespace
- user
additional_properties:
label:
description: Type of action (created, updated, destroyed)
product_group: knowledge
milestone: '17.7'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174095
tiers:
- free
- premium
- ultimate

View File

@ -1,8 +0,0 @@
---
name: ci_job_failures_in_mr
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122914
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414321
milestone: '16.1'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -1,7 +1,8 @@
---
name: work_items_alpha
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/377912
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155990
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360927
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/18942
milestone: '17.2'
type: development
group: group::project management

View File

@ -1,9 +0,0 @@
---
name: markdown_include_directive
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/195798
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169374
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/502242
milestone: '17.6'
group: group::knowledge
type: gitlab_com_derisk
default_enabled: false

View File

@ -1,18 +1,17 @@
---
data_category: optional
key_path: redis_hll_counters.source_code.wiki_action_monthly
description: Count of unique actions done on a wiki (create, edit, delete)
description: Count of unique users that did a wiki action (create, edit, delete)
product_group: knowledge
product_categories:
- wiki
value_type: number
status: active
time_frame: 28d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
options:
events:
- wiki_action
data_source: internal_events
events:
- name: performed_wiki_action
unique: user.id
distribution:
- ce
- ee

View File

@ -8,11 +8,10 @@ product_categories:
value_type: number
status: active
time_frame: 7d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
options:
events:
- wiki_action
data_source: internal_events
events:
- name: performed_wiki_action
unique: user.id
distribution:
- ce
- ee

View File

@ -0,0 +1,8 @@
---
migration_job_name: BackfillIssueEmailsNamespaceId
description: Backfills sharding key `issue_emails.namespace_id` from `issues`.
feature_category: service_desk
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174507
milestone: '17.7'
queued_migration_version: 20241203080309
finalized_by: # version of the migration that finalized this BBM

View File

@ -20,3 +20,4 @@ desired_sharding_key:
sharding_key: namespace_id
belongs_to: issue
table_size: small
desired_sharding_key_migration_job_name: BackfillIssueEmailsNamespaceId

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddShardingKeyIdToUploads < Gitlab::Database::Migration[2.2]
milestone '17.7'
def up
add_column :uploads, :organization_id, :bigint
add_column :uploads, :namespace_id, :bigint
add_column :uploads, :project_id, :bigint
end
def down
remove_column :uploads, :project_id
remove_column :uploads, :namespace_id
remove_column :uploads, :organization_id
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddNamespaceIdToIssueEmails < Gitlab::Database::Migration[2.2]
milestone '17.7'
def change
add_column :issue_emails, :namespace_id, :bigint
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class IndexIssueEmailsOnNamespaceId < Gitlab::Database::Migration[2.2]
milestone '17.7'
disable_ddl_transaction!
INDEX_NAME = 'index_issue_emails_on_namespace_id'
def up
add_concurrent_index :issue_emails, :namespace_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :issue_emails, INDEX_NAME
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddIssueEmailsNamespaceIdFk < Gitlab::Database::Migration[2.2]
milestone '17.7'
disable_ddl_transaction!
def up
add_concurrent_foreign_key :issue_emails, :namespaces, column: :namespace_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :issue_emails, column: :namespace_id
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddIssueEmailsNamespaceIdTrigger < Gitlab::Database::Migration[2.2]
milestone '17.7'
def up
install_sharding_key_assignment_trigger(
table: :issue_emails,
sharding_key: :namespace_id,
parent_table: :issues,
parent_sharding_key: :namespace_id,
foreign_key: :issue_id
)
end
def down
remove_sharding_key_assignment_trigger(
table: :issue_emails,
sharding_key: :namespace_id,
parent_table: :issues,
parent_sharding_key: :namespace_id,
foreign_key: :issue_id
)
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
class QueueBackfillIssueEmailsNamespaceId < Gitlab::Database::Migration[2.2]
milestone '17.7'
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
MIGRATION = "BackfillIssueEmailsNamespaceId"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:issue_emails,
:id,
:namespace_id,
:issues,
:namespace_id,
:issue_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(
MIGRATION,
:issue_emails,
:id,
[
:namespace_id,
:issues,
:namespace_id,
:issue_id
]
)
end
end

View File

@ -0,0 +1 @@
9414c5f6c3d687905fac7ff840361dec14fed7eb165a5c45ec5c554fa6c7d3fc

View File

@ -0,0 +1 @@
14c7ac108465e1774207abba8eb0dbad13ead13e845eb346ed3f5ec56143d917

View File

@ -0,0 +1 @@
917ce450e527be209e7f2e5b379a48b607223255e7c621cde5861a74d176788a

View File

@ -0,0 +1 @@
45c13577f68bff57cdd325470ab7b8b7a77fac5784f022072e5f160e7f971a48

View File

@ -0,0 +1 @@
8e19cb3f7d560c8631b6c62feafd00fdaf5184b7efe42da287db7c578bd26539

View File

@ -0,0 +1 @@
2678b7756f3c7eefffbe6ccd7d99d73f0b1b5fbfb9ee5a939c799fbd972947e4

View File

@ -2020,6 +2020,22 @@ RETURN NEW;
END
$$;
CREATE FUNCTION trigger_78c85ddc4031() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW."namespace_id" IS NULL THEN
SELECT "namespace_id"
INTO NEW."namespace_id"
FROM "issues"
WHERE "issues"."id" = NEW."issue_id";
END IF;
RETURN NEW;
END
$$;
CREATE FUNCTION trigger_7943cb549289() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -13812,6 +13828,7 @@ CREATE TABLE issue_emails (
id bigint NOT NULL,
issue_id bigint NOT NULL,
email_message_id text NOT NULL,
namespace_id bigint,
CONSTRAINT check_5abf3e6aea CHECK ((char_length(email_message_id) <= 1000))
);
@ -20796,6 +20813,9 @@ CREATE TABLE uploads (
secret character varying,
version integer DEFAULT 1 NOT NULL,
uploaded_by_user_id bigint,
organization_id bigint,
namespace_id bigint,
project_id bigint,
CONSTRAINT check_5e9547379c CHECK ((store IS NOT NULL))
);
@ -30865,6 +30885,8 @@ CREATE INDEX index_issue_emails_on_email_message_id ON issue_emails USING btree
CREATE INDEX index_issue_emails_on_issue_id ON issue_emails USING btree (issue_id);
CREATE INDEX index_issue_emails_on_namespace_id ON issue_emails USING btree (namespace_id);
CREATE INDEX index_issue_links_on_namespace_id ON issue_links USING btree (namespace_id);
CREATE INDEX index_issue_links_on_source_id ON issue_links USING btree (source_id);
@ -35593,6 +35615,8 @@ CREATE TRIGGER trigger_740afa9807b8 BEFORE INSERT OR UPDATE ON subscription_user
CREATE TRIGGER trigger_77d9fbad5b12 BEFORE INSERT OR UPDATE ON packages_debian_project_distribution_keys FOR EACH ROW EXECUTE FUNCTION trigger_77d9fbad5b12();
CREATE TRIGGER trigger_78c85ddc4031 BEFORE INSERT OR UPDATE ON issue_emails FOR EACH ROW EXECUTE FUNCTION trigger_78c85ddc4031();
CREATE TRIGGER trigger_7943cb549289 BEFORE INSERT OR UPDATE ON issuable_metric_images FOR EACH ROW EXECUTE FUNCTION trigger_7943cb549289();
CREATE TRIGGER trigger_7a8b08eed782 BEFORE INSERT OR UPDATE ON boards_epic_board_positions FOR EACH ROW EXECUTE FUNCTION trigger_7a8b08eed782();
@ -37363,6 +37387,9 @@ ALTER TABLE ONLY issuable_resource_links
ALTER TABLE ONLY merge_requests_compliance_violations
ADD CONSTRAINT fk_ec881c1c6f FOREIGN KEY (violating_user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY issue_emails
ADD CONSTRAINT fk_ed0f4c4b51 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY events
ADD CONSTRAINT fk_eea90e3209 FOREIGN KEY (personal_namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;

View File

@ -10,21 +10,13 @@ DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
It is very common that a [CI/CD pipeline](../pipelines/index.md) contains a
test job that verifies your code.
If the tests fail, the pipeline fails and users get notified. The person that
works on the merge request has to check the job logs and see where the
tests failed so that they can fix them.
You can configure your [CI/CD pipeline](../pipelines/index.md) to display unit test results directly in merge requests and pipeline details.
This makes it easier to identify test failures without searching through job logs.
You can configure your job to use Unit test reports, and GitLab displays a
report on the merge request so that it's easier and faster to identify the
failure without having to check the entire log. Unit test reports currently
only support test reports in the JUnit report format.
Unit test reports:
If you don't use merge requests but still want to see the unit test report
output without searching through job logs, the full
[Unit test reports](#view-unit-test-reports-on-gitlab) are available
in the pipeline detail view.
- Require the JUnit report format.
- Do not affect the job status. To make a job fail when unit tests fail, your job's [script](../yaml/index.md#script) must exit with a non-zero status.
Consider the following workflow:

View File

@ -1539,10 +1539,7 @@ $example = array(
## Includes
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195798) in GitLab 17.7 [with a flag](../administration/feature_flags.md) named `markdown_include_directive`. Disabled by default.
FLAG:
The availability of this feature is controlled by a feature flag. For more information, see the history.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195798) in GitLab 17.7.
Use includes, or include directives, to add the content of a document inside another document.

View File

@ -12,6 +12,9 @@ DETAILS:
## Transfer a project to another namespace
> - Support for transferring projects with container images within the same top-level namespace [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/499163) on GitLab.com in GitLab 17.7 [with a flag](../../../administration/feature_flags.md) named `transfer_project_with_tags`. Disabled by default.
> - Support for transferring projects with container images within the same top-level namespace [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/499163) in GitLab 17.7. Feature flag removed.
When you transfer a project to another namespace, you move the project to a different group.
All of the project's components (such as issues, merge requests, pipelines, and dashboards)
move with the transferred project.
@ -27,7 +30,9 @@ Prerequisites:
- You must have at least the Maintainer role for the [group](../../group/index.md#create-a-group) you are transferring to.
- You must be the Owner of the project you transfer.
- The group must allow creation of new projects.
- The project must not contain any [container images](../../packages/container_registry/index.md#move-or-rename-container-registry-repositories).
- For projects where the container registry is enabled:
- On GitLab.com: You can only transfer projects within the same top-level namespace.
- On GitLab self-managed: The project must not contain [container images](../../packages/container_registry/index.md#move-or-rename-container-registry-repositories).
- The project must not have a security policy.
If a security policy is assigned to the project, it is automatically unassigned during the transfer.
- If the root namespace changes, you must remove npm packages that follow the [naming convention](../../../user/packages/npm_registry/index.md#naming-convention) from the project.

View File

@ -18,7 +18,6 @@ module Banzai
timeout: Banzai::Filter::Concerns::TimeoutFilterHandler::RENDER_TIMEOUT)
def call
return text unless Feature.enabled?(:markdown_include_directive, context[:project])
return text unless wiki? || blob?
@included_content = {}

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillIssueEmailsNamespaceId < BackfillDesiredShardingKeyJob
operation_name :backfill_issue_emails_namespace_id
feature_category :service_desk
end
end
end

View File

@ -17,6 +17,7 @@ variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products"
DAST_IMAGE_SUFFIX: ""
dast:
stage: dast
@ -29,12 +30,13 @@ dast:
- /analyze
artifacts:
access: 'developer'
when: always
reports:
dast: gl-dast-report.json
paths:
- gl-dast-*.*
rules:
- if: $CI_GITLAB_FIPS_MODE == "true"
variables:
DAST_IMAGE_SUFFIX: "-fips"
- if: $CI_GITLAB_FIPS_MODE != "true"
variables:
DAST_IMAGE_SUFFIX: ""
- when: on_success

View File

@ -21,6 +21,8 @@ module Gitlab
end
def from_job(job)
return unless job&.running?
new(access_token_id: job.token, user_id: job.user.id, expire_at: job.project.build_timeout.seconds.from_now)
end

View File

@ -175,3 +175,4 @@ visit_ci_cd_failure_rate_tab-user: p_analytics_ci_cd_change_failure_rate
visit_ci_cd_time_to_restore_service_tab-user: p_analytics_ci_cd_time_to_restore_service
visit_compliance_credential_inventory-user: i_compliance_credential_inventory
commit_change_to_ciconfigfile-user: o_pipeline_authoring_unique_users_committing_ciconfigfile
performed_wiki_action-user: wiki_action

View File

@ -559,4 +559,3 @@
- users_visiting_security_vulnerability_report
- users_visiting_testing_license_compliance_full_report
- users_visiting_testing_manage_license_compliance
- wiki_action

View File

@ -35215,6 +35215,9 @@ msgstr ""
msgid "ModelRegistry|New model"
msgstr ""
msgid "ModelRegistry|New version"
msgstr ""
msgid "ModelRegistry|Version"
msgstr ""

View File

@ -217,7 +217,7 @@ RSpec.describe 'Database schema',
taggings: %w[tag_id taggable_id tagger_id],
timelogs: %w[user_id],
todos: %w[target_id commit_id],
uploads: %w[model_id],
uploads: %w[model_id organization_id namespace_id project_id],
user_agent_details: %w[subject_id],
users: %w[color_mode_id color_scheme_id created_by_id theme_id managing_group_id],
users_star_projects: %w[user_id],

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MergeRequests::MergeRequestDiffComparison, feature_category: :integrations do
include RepoHelpers
let(:diff_with_commits) { create(:merge_request).merge_request_diff }
subject(:merge_request_diff_instance) { described_class.new(diff_with_commits) }
describe '#compare_with' do
it 'delegates compare_with to the service' do
expect(CompareService).to receive(:new).and_call_original
described_class
.new(diff_with_commits)
.compare_with(nil)
end
it 'uses git diff A..B approach by default' do
diffs = described_class
.new(diff_with_commits)
.compare_with('0b4bc9a49b562e85de7cc9e834518ea6828729b9')
.diffs
expect(diffs.size).to eq(21)
end
end
end

View File

@ -4,6 +4,7 @@ FactoryBot.define do
factory :alert_metric_image, class: 'AlertManagement::MetricImage' do
association :alert, factory: :alert_management_alert
url { generate(:url) }
project_id { alert&.project_id }
trait :local do
file_store { ObjectStorage::Store::LOCAL }

View File

@ -307,76 +307,6 @@ RSpec.describe 'Edit group settings', feature_category: :groups_and_projects do
end
end
context 'Dormant members', :saas, :aggregate_failures, feature_category: :user_management do
before do
visit edit_group_path(group)
end
context "when feature flag is disabled" do
before do
stub_feature_flags(group_remove_dormant_members: false)
visit edit_group_path(group)
end
it 'does not expose the setting section' do
expect(page).not_to have_content('Dormant members')
expect(page).not_to have_field('Remove dormant members after a period of inactivity')
expect(page).not_to have_field('Days of inactivity before removal', disabled: true)
end
end
it 'exposes the setting section' do
expect(page).to have_content('Dormant members')
expect(page).to have_field('Remove dormant members after a period of inactivity')
expect(page).to have_field('Days of inactivity before removal', disabled: true)
end
it 'changes dormant members', :js do
expect(page).to have_unchecked_field(_('Remove dormant members after a period of inactivity'))
expect(group.namespace_settings.remove_dormant_members).to be_falsey
within_testid('permissions-settings') do
check _('Remove dormant members after a period of inactivity')
fill_in _('Days of inactivity before removal'), with: '90'
click_button _('Save changes')
end
expect(page).to have_content(
format(
_("Group '%{group_name}' was successfully updated."),
group_name: group.name
)
)
page.refresh
expect(page).to have_checked_field(_('Remove dormant members after a period of inactivity'))
expect(page).to have_field(_('Days of inactivity before removal'), disabled: false, with: '90')
end
it 'displays dormant members period field validation error', :js do
selector = '#group_remove_dormant_members_period_error'
expect(page).not_to have_selector(selector, visible: :visible)
within_testid('permissions-settings') do
check _('Remove dormant members after a period of inactivity')
fill_in _('Days of inactivity before removal'), with: '30'
click_button 'Save changes'
end
expect(page).to have_selector(selector, visible: :visible)
expect(page).to have_content _('Please enter a value between 90 and 1827 days (5 years)')
end
it 'auto disables dormant members period field depending on parent checkbox', :js do
uncheck _('Remove dormant members after a period of inactivity')
expect(page).to have_field(_('Days of inactivity before removal'), disabled: true)
check _('Remove dormant members after a period of inactivity')
expect(page).to have_field(_('Days of inactivity before removal'), disabled: false)
end
end
def update_path(new_group_path)
visit edit_group_path(group)

View File

@ -150,7 +150,10 @@ RSpec.describe 'Merge request > User sees versions', :js, feature_category: :cod
file: ".gitmodules",
old_line: 4,
new_line: 4,
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
diff_refs: ::MergeRequests::MergeRequestDiffComparison
.new(merge_request_diff3)
.compare_with(merge_request_diff1.head_commit_sha)
.diff_refs
)
outdated_diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position)
outdated_diff_note.position = outdated_diff_note.original_position

View File

@ -6,6 +6,7 @@ import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { visitUrlWithAlerts } from '~/lib/utils/url_utility';
import ModelVersionCreate from '~/ml/model_registry/components/model_version_create.vue';
import ImportArtifactZone from '~/ml/model_registry/components/import_artifact_zone.vue';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
import { uploadModel } from '~/ml/model_registry/services/upload_model';
import createModelVersionMutation from '~/ml/model_registry/graphql/mutations/create_model_version.mutation.graphql';
@ -62,13 +63,13 @@ describe('ModelVersionCreate', () => {
},
apolloProvider,
stubs: {
PageHeading,
UploadDropzone,
},
});
};
const findTitle = () => wrapper.findByTestId('title');
const findDescription = () => wrapper.findByTestId('description');
const findDescription = () => wrapper.findByTestId('page-heading-description');
const findPrimaryButton = () => wrapper.findByTestId('primary-button');
const findSecondaryButton = () => wrapper.findByTestId('secondary-button');
const findVersionInput = () => wrapper.findByTestId('versionId');
@ -90,7 +91,7 @@ describe('ModelVersionCreate', () => {
describe('Form', () => {
it('renders the title', () => {
expect(findTitle().text()).toBe('New version');
expect(wrapper.findByRole('heading').text()).toBe('New version');
});
it('renders the description', () => {
@ -281,8 +282,6 @@ describe('ModelVersionCreate', () => {
it('clicking on secondary button clears the form', async () => {
createWrapper();
await findVersionInput().vm.$emit('input', '1.0.0');
await findSecondaryButton().vm.$emit('click');
expect(findVersionInput().attributes('value')).toBe(undefined);
@ -294,6 +293,8 @@ describe('ModelVersionCreate', () => {
const failedCreateResolver = jest.fn().mockResolvedValue(createModelVersionResponses.failure);
createWrapper(failedCreateResolver);
findVersionInput().vm.$emit('input', '1.0.0');
await submitForm();
expect(findGlAlert().text()).toBe('Version is invalid');

View File

@ -96,7 +96,10 @@ RSpec.describe NotesHelper, feature_category: :team_planning do
file: ".gitmodules",
old_line: 4,
new_line: 4,
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
diff_refs: ::MergeRequests::MergeRequestDiffComparison
.new(merge_request_diff3)
.compare_with(merge_request_diff1.head_commit_sha)
.diff_refs
)
end

View File

@ -30,20 +30,6 @@ RSpec.describe Banzai::Filter::IncludeFilter, feature_category: :markdown do
allow(Gitlab::Git::Blob).to receive(:find).with(project.repository, ref, 'file.md').and_return(file_blob)
end
context 'when :markdown_include_directive feature flag disabled' do
before do
stub_feature_flags(markdown_include_directive: false)
end
it 'does not work for wikis' do
expect(filter(text_include, wiki: project.wiki, requested_path: './')).to eq text_include
end
it 'does not work for blobs' do
expect(filter(text_include, filter_context)).to eq text_include
end
end
it 'works for wikis' do
expect(filter(text_include, wiki: project.wiki, requested_path: './')).to eq file_data
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillIssueEmailsNamespaceId,
feature_category: :service_desk,
schema: 20241203080305 do
include_examples 'desired sharding key backfill job' do
let(:batch_table) { :issue_emails }
let(:backfill_column) { :namespace_id }
let(:backfill_via_table) { :issues }
let(:backfill_via_column) { :namespace_id }
let(:backfill_via_foreign_key) { :issue_id }
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::ConanToken do
RSpec.describe Gitlab::ConanToken, :aggregate_failures, feature_category: :package_registry do
let(:base_secret) { SecureRandom.base64(64) }
let(:jwt_secret) do
@ -16,147 +16,154 @@ RSpec.describe Gitlab::ConanToken do
allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
end
def build_jwt(access_token_id:, user_id:, expire_time: nil)
def build_encoded_jwt(access_token_id:, user_id:, expire_time: nil)
JSONWebToken::HMACToken.new(jwt_secret).tap do |jwt|
jwt['access_token'] = access_token_id
jwt['user_id'] = user_id || user_id
jwt.expire_time = expire_time ||
(jwt.issued_at + ::Gitlab::ConanToken::MAX_CONAN_TOKEN_EXPIRE_TIME).at_beginning_of_day
end
end.encoded
end
describe '.from_personal_access_token', :freeze_time do
let(:personal_access_token) { build(:personal_access_token) }
let_it_be(:personal_access_token) { build(:personal_access_token) }
let(:access_token_id) { SecureRandom.uuid }
it 'sets access token and user id and does not use the token id' do
token = described_class.from_personal_access_token(
access_token_id,
personal_access_token
)
subject(:conan_token) { described_class.from_personal_access_token(access_token_id, personal_access_token) }
expect(token.access_token_id).not_to eq(personal_access_token.id)
expect(token.access_token_id).to eq(access_token_id)
expect(token.expire_at).to eq(personal_access_token.expires_at)
it 'sets access token and expiry time' do
expect(conan_token.access_token_id).to eq(access_token_id)
expect(conan_token.expire_at).to eq(personal_access_token.expires_at)
end
context 'when expires is nil' do
let(:personal_access_token) { build(:personal_access_token, expires_at: nil) }
context 'when the expiration time is nil' do
let_it_be(:personal_access_token) { build(:personal_access_token, expires_at: nil) }
it 'sets default time' do
token = described_class.from_personal_access_token(
access_token_id,
personal_access_token
)
expect(token.expire_at).to eq(Time.zone.now + described_class::MAX_CONAN_TOKEN_EXPIRE_TIME)
expect(conan_token.expire_at).to eq(Time.zone.now + described_class::MAX_CONAN_TOKEN_EXPIRE_TIME)
end
end
context 'when token is not active' do
let(:expiration_date) { 1.day.ago }
let(:personal_access_token) { build(:personal_access_token, expires_at: expiration_date) }
let(:user_id) { personal_access_token.user_id }
context 'when the token is not active' do
let_it_be(:expiration_time) { 1.day.ago }
let_it_be(:personal_access_token) { build(:personal_access_token, expires_at: expiration_time) }
it 'does not set access token' do
expect(described_class.from_personal_access_token(user_id, personal_access_token)).to be_nil
it 'does not set the access token' do
expect(conan_token).to be_nil
end
end
end
describe '.from_job' do
let(:stubbed_user) { build_stubbed(:user) }
let_it_be(:stubbed_user) { build_stubbed(:user) }
let_it_be(:project) { create(:project) }
let(:job) { create(:ci_build, :with_token, project: project, user: stubbed_user) }
let_it_be(:job) { create(:ci_build, :with_token, :running, project: project, user: stubbed_user) }
subject(:conan_token) { described_class.from_job(job) }
it 'sets access token id and user id', :freeze_time do
token = described_class.from_job(job)
expect(job.token).to be_present
expect(token.user_id).to eq(stubbed_user.id)
expect(token.expire_at).to eq(Time.zone.now + project.build_timeout)
expect(conan_token.user_id).to eq(stubbed_user.id)
expect(conan_token.expire_at).to eq(Time.zone.now + project.build_timeout)
end
context 'when the job is not running' do
let_it_be(:job) { create(:ci_build, :with_token, :success, project: project, user: stubbed_user) }
it { is_expected.to be_nil }
end
context 'when the job is nil' do
let(:job) { nil }
it { is_expected.to be_nil }
end
end
describe '.from_deploy_token', :freeze_time do
let(:expiration_date) { 30.days.from_now.beginning_of_day }
let(:deploy_token) { build(:deploy_token, username: 'test_user', expires_at: expiration_date) }
let(:expiration_time) { 30.days.from_now.beginning_of_day }
let(:deploy_token) { build(:deploy_token, username: 'test_user', expires_at: expiration_time) }
subject(:conan_token) { described_class.from_deploy_token(deploy_token) }
before do
allow(deploy_token).to receive(:token).and_return('test_token')
end
it 'creates a ConanToken from a deploy token' do
conan_token = described_class.from_deploy_token(deploy_token)
it 'creates a conan token from a deploy token' do
expect(conan_token).to be_a(described_class)
expect(conan_token.access_token_id).to eq('test_token')
expect(conan_token.user_id).to eq('test_user')
expect(conan_token.expire_at).to eq(expiration_date)
expect(conan_token.expire_at).to eq(expiration_time)
end
context 'when expiration date is too long' do
let(:expiration_date) { 100.days.from_now.beginning_of_day }
context 'when the expiration time is too long' do
let(:expiration_time) { 100.days.from_now.beginning_of_day }
it 'updates dates to the maximum expire time' do
token = described_class.from_deploy_token(deploy_token)
expect(token.expire_at).to eq(Time.zone.now + described_class::MAX_CONAN_TOKEN_EXPIRE_TIME)
it 'updates time to the maximum expiration time' do
expect(conan_token.expire_at).to eq(Time.zone.now + described_class::MAX_CONAN_TOKEN_EXPIRE_TIME)
end
end
context 'when no expire date is given' do
let(:expiration_date) { nil }
context 'when there is no expiration time' do
let(:expiration_time) { nil }
it 'updates dates to default date' do
token = described_class.from_deploy_token(deploy_token)
expect(token.expire_at).to eq(Time.zone.now + described_class::MAX_CONAN_TOKEN_EXPIRE_TIME)
it 'updates time to default time' do
expect(conan_token.expire_at).to eq(Time.zone.now + described_class::MAX_CONAN_TOKEN_EXPIRE_TIME)
end
end
end
describe '.decode' do
let(:jwt) { build_encoded_jwt(access_token_id: 123, user_id: 456) }
subject(:conan_token) { described_class.decode(jwt) }
it 'sets access token id and user id' do
jwt = build_jwt(access_token_id: 123, user_id: 456)
token = described_class.decode(jwt.encoded)
expect(token.access_token_id).to eq(123)
expect(token.user_id).to eq(456)
expect(conan_token.access_token_id).to eq(123)
expect(conan_token.user_id).to eq(456)
end
it 'returns nil for invalid JWT' do
expect(described_class.decode('invalid-jwt')).to be_nil
context 'when given an invalid JWT token' do
let(:jwt) { 'invalid-jwt' }
it { is_expected.to be_nil }
end
it 'returns nil for expired JWT' do
jwt = build_jwt(access_token_id: 123,
user_id: 456,
expire_time: 1.day.ago)
context 'when given an expired JWT token' do
let(:jwt) { build_encoded_jwt(access_token_id: 123, user_id: 456, expire_time: 1.day.ago) }
expect(described_class.decode(jwt.encoded)).to be_nil
it { is_expected.to be_nil }
end
end
describe '#to_jwt' do
it 'returns the encoded JWT' do
allow(SecureRandom).to receive(:uuid).and_return('u-u-i-d')
jwt = build_jwt(access_token_id: 123,
user_id: 456,
expire_time: Time.zone.now + ::Gitlab::ConanToken::MAX_CONAN_TOKEN_EXPIRE_TIME
)
token = described_class.new(access_token_id: 123, user_id: 456)
expect(token.to_jwt).to eq(jwt.encoded)
let(:jwt) do
build_encoded_jwt(
access_token_id: 123,
user_id: 456,
expire_time: Time.zone.now + ::Gitlab::ConanToken::MAX_CONAN_TOKEN_EXPIRE_TIME
)
end
it 'returns the encoded JWT with date' do
subject(:conan_jwt_token) { described_class.new(access_token_id: 123, user_id: 456).to_jwt }
it 'returns the encoded JWT' do
allow(SecureRandom).to receive(:uuid).and_return('u-u-i-d')
expiration_date = 30.days.from_now
jwt = build_jwt(access_token_id: 123, user_id: 456, expire_time: expiration_date)
token = described_class.new(access_token_id: 123, user_id: 456, expire_at: expiration_date)
expect(token.to_jwt).to eq(jwt.encoded)
expect(conan_jwt_token).to eq(jwt)
end
context 'when creating with an expiry time' do
let(:expiration_time) { 30.days.from_now }
let(:jwt) { build_encoded_jwt(access_token_id: 123, user_id: 456, expire_time: expiration_time) }
subject(:conan_jwt_token) do
described_class.new(access_token_id: 123, user_id: 456, expire_at: expiration_time).to_jwt
end
it 'returns the encoded JWT with the expiry time' do
allow(SecureRandom).to receive(:uuid).and_return('u-u-i-d')
expect(conan_jwt_token).to eq(jwt)
end
end
end
end

View File

@ -198,7 +198,8 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do
"oauth_access_tokens" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717",
"oauth_access_grants" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717",
"oauth_openid_requests" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717",
"oauth_device_grants" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717"
"oauth_device_grants" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717",
"uploads" => "https://gitlab.com/gitlab-org/gitlab/-/issues/398199"
}
has_lfk = ->(lfks) { lfks.any? { |k| k.options[:column] == 'organization_id' && k.to_table == 'organizations' } }

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillIssueEmailsNamespaceId, feature_category: :service_desk do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :issue_emails,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_main_cell,
job_arguments: [
:namespace_id,
:issues,
:namespace_id,
:issue_id
]
)
}
end
end
end

View File

@ -438,4 +438,10 @@ RSpec.describe AbuseReport, feature_category: :insider_threat do
it { is_expected.to define_enum_for(:category).with_values(**categories) }
end
describe '#uploads_sharding_key' do
it 'returns empty hash' do
expect(report.uploads_sharding_key).to eq({})
end
end
end

View File

@ -32,4 +32,13 @@ RSpec.describe Achievements::Achievement, type: :model, feature_category: :user_
it_behaves_like Avatarable do
let(:model) { create(:achievement, :with_avatar) }
end
describe '#uploads_sharding_key' do
it 'returns namespace_id' do
namespace = build_stubbed(:namespace)
achievement = build_stubbed(:achievement, namespace: namespace)
expect(achievement.uploads_sharding_key).to eq(namespace_id: namespace.id)
end
end
end

View File

@ -15,4 +15,13 @@ RSpec.describe AlertManagement::MetricImage do
it { is_expected.to validate_length_of(:url).is_at_most(255) }
it { is_expected.to validate_length_of(:url_text).is_at_most(128) }
end
describe '#uploads_sharding_key' do
it 'returns project_id' do
project = build_stubbed(:project)
metric_image = build_stubbed(:alert_metric_image, project_id: project.id)
expect(metric_image.uploads_sharding_key).to eq(project_id: project.id)
end
end
end

View File

@ -166,4 +166,12 @@ RSpec.describe Appearance do
end
end
end
describe '#uploads_sharding_key' do
it 'returns epmty hash' do
appearance = build_stubbed(:appearance)
expect(appearance.uploads_sharding_key).to eq({})
end
end
end

View File

@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe BulkImports::ExportUpload, type: :model, feature_category: :importers do
subject { described_class.new(export: create(:bulk_import_export)) }
let(:export) { create(:bulk_import_export) }
subject(:bulk_import_export) { described_class.new(export: export) }
describe 'associations' do
it { is_expected.to belong_to(:export) }
@ -34,4 +36,13 @@ RSpec.describe BulkImports::ExportUpload, type: :model, feature_category: :impor
expect(find_callback(after_save_callbacks, :store_export_file!)).to be_nil
end
end
describe '#uploads_sharding_key' do
it 'returns project_id or group_id' do
expect(bulk_import_export.uploads_sharding_key).to eq(
project_id: export.project_id,
namespace_id: export.group_id
)
end
end
end

View File

@ -129,4 +129,14 @@ RSpec.describe DesignManagement::Action do
end
end
end
describe '#uploads_sharding_key' do
it 'returns namespace_id' do
namespace = build_stubbed(:namespace)
design = build_stubbed(:design, namespace_id: namespace.id)
design_action = build_stubbed(:design_action, design: design)
expect(design_action.uploads_sharding_key).to eq(namespace_id: namespace.id)
end
end
end

View File

@ -74,7 +74,10 @@ RSpec.describe DiffDiscussion do
new_path: ".gitmodules",
old_line: 4,
new_line: 4,
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
diff_refs: ::MergeRequests::MergeRequestDiffComparison
.new(merge_request_diff3)
.compare_with(merge_request_diff1.head_commit_sha)
.diff_refs
)
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe ImportExportUpload do
let(:project) { create(:project) }
subject { described_class.new(project: project) }
subject(:import_export_upload) { described_class.new(project: project) }
shared_examples 'stores the Import/Export file' do |method|
it 'stores the import file' do
@ -127,4 +127,13 @@ RSpec.describe ImportExportUpload do
end
end
end
describe '#uploads_sharding_key' do
it 'returns project_id / group_id' do
expect(import_export_upload.uploads_sharding_key).to eq(
project_id: project.id,
namespace_id: nil
)
end
end
end

View File

@ -1197,20 +1197,6 @@ RSpec.describe MergeRequestDiff, feature_category: :code_review_workflow do
end
end
describe '#compare_with' do
it 'delegates compare to the service' do
expect(CompareService).to receive(:new).and_call_original
diff_with_commits.compare_with(nil)
end
it 'uses git diff A..B approach by default' do
diffs = diff_with_commits.compare_with('0b4bc9a49b562e85de7cc9e834518ea6828729b9').diffs
expect(diffs.size).to eq(21)
end
end
describe '#commits_count' do
it 'returns number of commits using serialized commits' do
expect(diff_with_commits.commits_count).to eq(29)

View File

@ -4904,7 +4904,12 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
end
context 'when the diff refs are for a comparison between merge request versions' do
let(:diff_refs) { merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs }
let(:diff_refs) do
::MergeRequests::MergeRequestDiffComparison
.new(merge_request_diff3)
.compare_with(merge_request_diff1.head_commit_sha)
.diff_refs
end
it 'returns the diff ID and start sha of the versions to compare' do
expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha)

View File

@ -151,19 +151,6 @@ RSpec.describe NamespaceSetting, feature_category: :groups_and_projects, type: :
it { is_expected.to allow_value({ name: value }).for(:default_branch_protection_defaults) }
end
end
describe 'remove_dormant_members' do
it { expect(subgroup.namespace_settings).to validate_inclusion_of(:remove_dormant_members).in_array([false]) }
end
describe 'remove_dormant_members_period' do
it do
expect(namespace_settings).to validate_numericality_of(:remove_dormant_members_period)
.only_integer
.is_greater_than_or_equal_to(90)
.is_less_than_or_equal_to(1827)
end
end
end
describe '#prevent_sharing_groups_outside_hierarchy' do

View File

@ -2699,4 +2699,13 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
it { expect(nested_group.web_url).to include("groups/#{nested_group.full_path}") }
end
end
describe '#uploads_sharding_key' do
it 'returns organization_id' do
organization = build_stubbed(:organization)
namespace = build_stubbed(:namespace, organization: organization)
expect(namespace.uploads_sharding_key).to eq(organization_id: organization.id)
end
end
end

View File

@ -2014,4 +2014,13 @@ RSpec.describe Note, feature_category: :team_planning do
end
end
end
describe '#uploads_sharding_key' do
it 'returns namespace_id' do
namespace = build_stubbed(:namespace)
note = build_stubbed(:note, namespace: namespace)
expect(note.uploads_sharding_key).to eq(namespace_id: namespace.id)
end
end
end

View File

@ -32,4 +32,12 @@ RSpec.describe Organizations::OrganizationDetail, type: :model, feature_category
let(:uploader_class) { AttachmentUploader }
end
end
describe '#uploads_sharding_key' do
it 'returns organization_id' do
organization_detail = build_stubbed(:organization_detail)
expect(organization_detail.uploads_sharding_key).to eq(organization_id: organization_detail.organization_id)
end
end
end

View File

@ -35,4 +35,13 @@ RSpec.describe PersonalSnippet do
expect(snippet.parent_user).to eq(snippet.author)
end
end
describe '#uploads_sharding_key' do
it 'returns organization_id' do
organization = build_stubbed(:organization)
snippet = build_stubbed(:personal_snippet, organization: organization)
expect(snippet.uploads_sharding_key).to eq(organization_id: organization.id)
end
end
end

View File

@ -9750,4 +9750,13 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
it { expect(project.placeholder_reference_store).to be_nil }
end
end
describe '#uploads_sharding_key' do
it 'returns namespace_id' do
namespace = build_stubbed(:namespace)
project = build_stubbed(:project, namespace: namespace)
expect(project.uploads_sharding_key).to eq(namespace_id: namespace.id)
end
end
end

View File

@ -5,7 +5,8 @@ require 'spec_helper'
RSpec.describe Projects::ImportExport::RelationExportUpload, type: :model, feature_category: :importers do
subject { described_class.new(relation_export: project_relation_export) }
let_it_be(:project_relation_export) { create(:project_relation_export) }
let_it_be(:project) { create(:project) }
let_it_be(:project_relation_export) { create(:project_relation_export, project_id: project.id) }
describe 'associations' do
it { is_expected.to belong_to(:relation_export) }
@ -15,10 +16,19 @@ RSpec.describe Projects::ImportExport::RelationExportUpload, type: :model, featu
let_it_be(:project_export_job_1) { create(:project_export_job) }
let_it_be(:project_export_job_2) { create(:project_export_job) }
let_it_be(:relation_export_1) { create(:project_relation_export, project_export_job: project_export_job_1) }
let_it_be(:relation_export_2) { create(:project_relation_export, project_export_job: project_export_job_2) }
let_it_be(:relation_export_1) do
create(:project_relation_export, project_export_job: project_export_job_1,
project_id: project_export_job_1.project_id)
end
let_it_be(:relation_export_2) do
create(:project_relation_export, project_export_job: project_export_job_2,
project_id: project_export_job_2.project_id)
end
let_it_be(:relation_export_3) do
create(:project_relation_export, project_export_job: project_export_job_1, relation: 'milestones')
create(:project_relation_export, project_export_job: project_export_job_1,
project_id: project_export_job_1.project_id, relation: 'milestones')
end
let_it_be(:relation_export_upload_1) { create(:relation_export_upload, relation_export: relation_export_1) }
@ -65,4 +75,14 @@ RSpec.describe Projects::ImportExport::RelationExportUpload, type: :model, featu
expect(find_callback(after_save_callbacks, :store_export_file!)).to be_nil
end
end
describe '#uploads_sharding_key' do
it 'returns project_id' do
project = build_stubbed(:project)
export = build_stubbed(:project_relation_export, project_id: project.id)
export_upload = build_stubbed(:relation_export_upload, relation_export: export)
expect(export_upload.uploads_sharding_key).to eq(project_id: project.id)
end
end
end

View File

@ -132,4 +132,13 @@ RSpec.describe Projects::Topic do
expect(topic.title_or_name).to eq('topic')
end
end
describe '#uploads_sharding_key' do
it 'returns organization_id' do
organization = build_stubbed(:organization)
topic = build_stubbed(:topic, organization: organization)
expect(topic.uploads_sharding_key).to eq(organization_id: organization.id)
end
end
end

Some files were not shown because too many files have changed in this diff Show More