Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b87ea51c61
commit
9ccd95d7d7
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -173,6 +173,10 @@ class AbuseReport < ApplicationRecord
|
|||
nil
|
||||
end
|
||||
|
||||
def uploads_sharding_key
|
||||
{}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reported_project
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ class Appearance < ApplicationRecord
|
|||
footer_message.present?
|
||||
end
|
||||
|
||||
def uploads_sharding_key
|
||||
{}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def logo_system_path(logo, mount_type)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -755,6 +755,10 @@ class Namespace < ApplicationRecord
|
|||
!!deleted_at
|
||||
end
|
||||
|
||||
def uploads_sharding_key
|
||||
{ organization_id: organization_id }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_organization?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -723,6 +723,10 @@ class Note < ApplicationRecord
|
|||
attributes.keys
|
||||
end
|
||||
|
||||
def uploads_sharding_key
|
||||
{ namespace_id: namespace_id }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def trigger_note_subscription?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -14,4 +14,8 @@ class PersonalSnippet < Snippet
|
|||
def skip_project_check?
|
||||
true
|
||||
end
|
||||
|
||||
def uploads_sharding_key
|
||||
{ organization_id: organization_id }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ module Projects
|
|||
end
|
||||
end
|
||||
|
||||
def uploads_sharding_key
|
||||
{ organization_id: organization_id }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_name_format
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -2530,6 +2530,10 @@ class User < ApplicationRecord
|
|||
false
|
||||
end
|
||||
|
||||
def uploads_sharding_key
|
||||
{}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# override, from Devise::Validatable
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ module Groups
|
|||
|
||||
SETTINGS_PARAMS = [
|
||||
:allow_mfa_for_subgroups,
|
||||
:remove_dormant_members,
|
||||
:remove_dormant_members_period,
|
||||
:early_access_program_participant
|
||||
].freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.')
|
||||
|
|
@ -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) } }
|
||||
|
|
|
|||
|
|
@ -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) } }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -20,3 +20,4 @@ desired_sharding_key:
|
|||
sharding_key: namespace_id
|
||||
belongs_to: issue
|
||||
table_size: small
|
||||
desired_sharding_key_migration_job_name: BackfillIssueEmailsNamespaceId
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
9414c5f6c3d687905fac7ff840361dec14fed7eb165a5c45ec5c554fa6c7d3fc
|
||||
|
|
@ -0,0 +1 @@
|
|||
14c7ac108465e1774207abba8eb0dbad13ead13e845eb346ed3f5ec56143d917
|
||||
|
|
@ -0,0 +1 @@
|
|||
917ce450e527be209e7f2e5b379a48b607223255e7c621cde5861a74d176788a
|
||||
|
|
@ -0,0 +1 @@
|
|||
45c13577f68bff57cdd325470ab7b8b7a77fac5784f022072e5f160e7f971a48
|
||||
|
|
@ -0,0 +1 @@
|
|||
8e19cb3f7d560c8631b6c62feafd00fdaf5184b7efe42da287db7c578bd26539
|
||||
|
|
@ -0,0 +1 @@
|
|||
2678b7756f3c7eefffbe6ccd7d99d73f0b1b5fbfb9ee5a939c799fbd972947e4
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -559,4 +559,3 @@
|
|||
- users_visiting_security_vulnerability_report
|
||||
- users_visiting_testing_license_compliance_full_report
|
||||
- users_visiting_testing_manage_license_compliance
|
||||
- wiki_action
|
||||
|
|
|
|||
|
|
@ -35215,6 +35215,9 @@ msgstr ""
|
|||
msgid "ModelRegistry|New model"
|
||||
msgstr ""
|
||||
|
||||
msgid "ModelRegistry|New version"
|
||||
msgstr ""
|
||||
|
||||
msgid "ModelRegistry|Version"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' } }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue