Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-10-31 00:13:19 +00:00
parent 639a34f955
commit 747345dee0
70 changed files with 934 additions and 213 deletions

View File

@ -2907,7 +2907,6 @@ Gitlab/BoundedContexts:
- 'ee/app/models/elastic/index_setting.rb'
- 'ee/app/models/elastic/migration_record.rb'
- 'ee/app/models/elastic/reindexing_slice.rb'
- 'ee/app/models/elastic/reindexing_subtask.rb'
- 'ee/app/models/elastic/reindexing_task.rb'
- 'ee/app/models/elasticsearch_indexed_namespace.rb'
- 'ee/app/models/elasticsearch_indexed_project.rb'

View File

@ -22,7 +22,6 @@ Gitlab/DocumentationLinks/Link:
- 'ee/app/helpers/projects/security/api_fuzzing_configuration_helper.rb'
- 'ee/app/helpers/vulnerabilities_helper.rb'
- 'ee/app/models/integrations/github.rb'
- 'ee/lib/api/managed_licenses.rb'
- 'ee/lib/ee/gitlab/namespace_storage_size_error_message.rb'
- 'ee/lib/gitlab/checks/secrets_check.rb'
- 'ee/lib/gitlab/llm/chain/tools/tool.rb'
@ -38,7 +37,6 @@ Gitlab/DocumentationLinks/Link:
- 'lib/gitlab/usage_data_counters/hll_redis_counter.rb'
- 'lib/slack/block_kit/app_home_opened.rb'
- 'lib/system_check/helpers.rb'
- 'lib/users/internal.rb'
- 'spec/helpers/ide_helper_spec.rb'
- 'spec/mailers/emails/pages_domains_spec.rb'
- 'spec/presenters/clusters/cluster_presenter_spec.rb'

View File

@ -60,7 +60,6 @@ Rails/InverseOf:
- 'ee/app/models/ee/project.rb'
- 'ee/app/models/ee/service_desk_setting.rb'
- 'ee/app/models/ee/user.rb'
- 'ee/app/models/elastic/reindexing_subtask.rb'
- 'ee/app/models/geo/event.rb'
- 'ee/app/models/geo/event_log.rb'
- 'ee/app/models/geo/job_artifact_registry.rb'

View File

@ -816,7 +816,6 @@ RSpec/FeatureCategory:
- 'ee/spec/models/ee/users_statistics_spec.rb'
- 'ee/spec/models/elastic/index_setting_spec.rb'
- 'ee/spec/models/elastic/reindexing_slice_spec.rb'
- 'ee/spec/models/elastic/reindexing_subtask_spec.rb'
- 'ee/spec/models/elastic/reindexing_task_spec.rb'
- 'ee/spec/models/epic_user_mention_spec.rb'
- 'ee/spec/models/gitlab/seat_link_data_spec.rb'

View File

@ -31,7 +31,6 @@ Search/NamespacedClass:
- 'ee/app/models/elastic/index_setting.rb'
- 'ee/app/models/elastic/migration_record.rb'
- 'ee/app/models/elastic/reindexing_slice.rb'
- 'ee/app/models/elastic/reindexing_subtask.rb'
- 'ee/app/models/elastic/reindexing_task.rb'
- 'ee/app/models/elasticsearch_indexed_namespace.rb'
- 'ee/app/models/elasticsearch_indexed_project.rb'

View File

@ -421,7 +421,6 @@ Style/ClassAndModuleChildren:
- 'ee/app/models/dast/profile_schedule.rb'
- 'ee/app/models/ee/ci/job_artifact.rb'
- 'ee/app/models/elastic/reindexing_slice.rb'
- 'ee/app/models/elastic/reindexing_subtask.rb'
- 'ee/app/models/elastic/reindexing_task.rb'
- 'ee/app/models/epic/metrics.rb'
- 'ee/app/models/epic/related_epic_link.rb'

View File

@ -1 +1 @@
f5742bf5cd567c3bfc02aca8880bc8f73a7fb7c4
c2675ac3885b916376fb35eb123dd54820ca797d

View File

@ -1,11 +1,12 @@
<script>
import { GlButton, GlIcon } from '@gitlab/ui';
import { GlBadge, GlButton, GlIcon } from '@gitlab/ui';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { __ } from '~/locale';
import FailedJobsList from './failed_jobs_list.vue';
export default {
components: {
GlBadge,
GlButton,
GlIcon,
FailedJobsList,
@ -45,6 +46,9 @@ export default {
bodyClasses() {
return this.isExpanded ? '' : 'gl-hidden';
},
failedJobsCountLabel() {
return `${this.isMaximumJobLimitReached ? '100+' : this.currentFailedJobsCount}`;
},
iconName() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';
},
@ -75,7 +79,7 @@ export default {
<crud-component
:id="$options.ariaControlsId"
class="expandable-card"
:class="{ 'is-collapsed gl-border-white hover:gl-border-gray-100': !isExpanded }"
:class="{ 'is-collapsed gl-border-transparent hover:gl-border-default': !isExpanded }"
data-testid="failed-jobs-card"
@click="toggleWidget"
>
@ -88,12 +92,16 @@ export default {
@click="toggleWidget"
>
<gl-icon :name="iconName" class="gl-mr-2" />
<span class="gl-font-bold gl-text-secondary">
<span class="gl-font-bold gl-text-subtle">
{{ $options.i18n.failedJobsLabel }}
</span>
<span> ({{ currentFailedJobsCount }}) </span>
</gl-button>
</template>
<template #count>
<gl-badge>
{{ failedJobsCountLabel }}
</gl-badge>
</template>
<failed-jobs-list
v-if="isExpanded"
:failed-jobs-count="failedJobsCount"

View File

@ -6,12 +6,15 @@ import {
GlSkeletonLoader,
GlSprintf,
GlTooltipDirective as GlTooltip,
GlTruncateText,
} from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { s__, __ } from '~/locale';
import DeploymentStatusLink from '~/environments/components/deployment_status_link.vue';
import DeploymentCommit from '~/environments/components/commit.vue';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { isFinished } from '../utils';
export default {
@ -21,6 +24,7 @@ export default {
GlLink,
GlSkeletonLoader,
GlSprintf,
GlTruncateText,
ClipboardButton,
TimeAgoTooltip,
DeploymentStatusLink,
@ -28,6 +32,7 @@ export default {
},
directives: {
GlTooltip,
SafeHtml,
},
props: {
deployment: {
@ -38,6 +43,11 @@ export default {
required: true,
type: Object,
},
release: {
required: false,
default: null,
type: Object,
},
loading: {
required: false,
default: false,
@ -99,14 +109,19 @@ export default {
return this.isFinished(this.deployment) ? this.finishedAt : this.createdAt;
},
},
mounted() {
renderGFM(this.$refs['gfm-content']);
},
methods: {
isFinished,
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
i18n: {
copyButton: __('Copy commit SHA'),
needsApproval: s__('Deployment|Needs Approval'),
startedTimeagoText: s__('Deployment|Started %{timeago} by %{username}'),
finishedTimeagoText: s__('Deployment|Finished %{timeago} by %{username}'),
releaseNotes: s__('Deployment|%{releaseName} release notes:'),
},
};
</script>
@ -157,5 +172,22 @@ export default {
</time-ago-tooltip>
</div>
<deployment-commit :commit="commit" />
<p v-if="release" class="gl-my-3 gl-font-bold">
<gl-sprintf :message="$options.i18n.releaseNotes">
<template #releaseName>
<gl-link :href="release.links.selfUrl" data-testid="release-page-link">
{{ release.name }}
</gl-link>
</template>
</gl-sprintf>
</p>
<gl-truncate-text v-if="release" :lines="2" :mobile-lines="2">
<div
ref="gfm-content"
v-safe-html:[$options.safeHtmlConfig]="release.descriptionHtml"
class="md gl-z-0"
data-testid="release-description-content"
></div>
</gl-truncate-text>
</div>
</template>

View File

@ -5,6 +5,7 @@ import { toggleQueryPollingByVisibility, etagQueryHeaders } from '~/graphql_shar
import { s__ } from '~/locale';
import deploymentQuery from '../graphql/queries/deployment.query.graphql';
import environmentQuery from '../graphql/queries/environment.query.graphql';
import releaseQuery from '../graphql/queries/release.query.graphql';
import DeploymentHeader from './deployment_header.vue';
import DeploymentAside from './deployment_aside.vue';
import DeploymentDeployBlock from './deployment_deploy_block.vue';
@ -58,9 +59,25 @@ export default {
this.errorMessage = this.$options.i18n.errorMessage;
},
},
release: {
query: releaseQuery,
variables() {
return { fullPath: this.projectPath, tagName: this.deployment?.ref };
},
update(data) {
return data?.project?.release;
},
skip() {
return !this.deployment?.tag;
},
error(error) {
captureException(error);
this.errorMessage = this.$options.i18n.errorMessage;
},
},
},
data() {
return { deployment: {}, environment: {}, errorMessage: '' };
return { deployment: {}, environment: {}, errorMessage: '', release: null };
},
computed: {
hasError() {
@ -104,6 +121,7 @@ export default {
v-else
:deployment="deployment"
:environment="environment"
:release="release"
:loading="isLoading"
/>
<details-feedback class="gl-mt-6" />

View File

@ -0,0 +1,13 @@
query relatedRelease($fullPath: ID!, $tagName: String!) {
project(fullPath: $fullPath) {
id
release(tagName: $tagName) {
id
name
descriptionHtml
links {
selfUrl
}
}
}
}

View File

@ -137,7 +137,7 @@ export default {
<toggle-replies-widget
:collapsed="!discussion.expanded"
:replies="replies"
class="gl-border-t -gl-mx-3 -gl-mb-3 gl-mt-4 !gl-border-x-0 !gl-border-b-0 gl-border-t-subtle"
class="gl-border-t -gl-mx-3 -gl-mb-3 gl-mt-4 !gl-border-x-0 !gl-border-b-0 gl-border-t-subtle dark:gl-border-t-section"
@toggle="toggleDiscussionHandler"
/>
</div>

View File

@ -102,7 +102,7 @@ export default {
:discussion-path="discussion.discussion_path"
:diff-file="discussion.diff_file"
:can-current-user-fork="false"
class="gl-border"
class="gl-border gl-border-section"
:expanded="!isCollapsed"
/>
<div v-if="isTextFile" class="diff-content">

View File

@ -421,7 +421,7 @@ export default {
<div
v-if="showMultiLineComment"
data-testid="multiline-comment"
class="gl-border-b-1 gl-border-gray-100 gl-px-5 gl-py-3 gl-text-gray-500 gl-border-b-solid"
class="gl-border-b gl-border-section gl-px-5 gl-py-3 gl-text-subtle"
>
<gl-sprintf :message="__('Comment on lines %{startLine} to %{endLine}')">
<template #startLine>

View File

@ -60,7 +60,7 @@ export default {
<template>
<li
:class="{ '!gl-rounded-b-base gl-text-subtle': collapsed }"
class="toggle-replies-widget gl-border-r gl-border-l !gl-flex gl-flex-wrap gl-items-center gl-bg-subtle gl-px-5 gl-py-3"
class="toggle-replies-widget gl-border-r gl-border-l !gl-flex gl-flex-wrap gl-items-center gl-border-l-section gl-border-r-section gl-bg-subtle gl-px-5 gl-py-3"
:aria-expanded="ariaState"
>
<gl-button

View File

@ -807,7 +807,7 @@ table.code {
margin-left: $note-spacing-left;
&[aria-expanded="false"] {
@apply gl-border-b;
@apply gl-border-b gl-border-b-section;
}
}
}

View File

@ -3,10 +3,10 @@
*
*/
.file-holder {
@apply gl-border gl-rounded-base;
@apply gl-border gl-border-section gl-rounded-base;
&.file-holder-top-border {
@apply gl-border;
@apply gl-border gl-border-section;
.file-title {
// Prevents the top border getting clipped by the background
@ -20,7 +20,7 @@
.file-title {
position: relative;
@apply gl-bg-subtle gl-border-b;
@apply gl-bg-subtle gl-border-b gl-border-b-section;
margin: 0;
text-align: left;
padding: 10px $gl-padding;
@ -42,7 +42,7 @@
.file-blame-legend {
text-align: right;
padding: 8px $gl-padding;
@apply gl-bg-subtle gl-border-b;
@apply gl-bg-subtle gl-border-b gl-border-b-section;
@include media-breakpoint-down(xs) {
text-align: left;
@ -114,7 +114,7 @@
margin-left: 40px;
padding: 10px 0;
margin-bottom: 0;
@apply gl-bg-default gl-border-l;
@apply gl-bg-default gl-border-l gl-border-l-section;
li {
color: $logs-li-color;
@ -203,7 +203,7 @@ span.idiff {
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
@apply gl-bg-subtle gl-border-b;
@apply gl-bg-subtle gl-border-b gl-border-b-section;
padding: $gl-padding-8 $gl-padding;
margin: 0;
min-height: px-to-rem($file-header-height);
@ -241,7 +241,7 @@ span.idiff {
display: flex;
align-items: center;
justify-content: flex-end;
@apply gl-bg-subtle gl-border-b;
@apply gl-bg-subtle gl-border-b gl-border-b-section;
padding: 5px $gl-padding;
}
@ -286,7 +286,7 @@ span.idiff {
padding-bottom: $gl-padding;
.discussion-reply-holder {
@apply gl-border-b gl-border-b-subtle;
@apply gl-border-b gl-border-b-subtle dark:gl-border-b-section;
border-radius: 0;
}
}
@ -449,7 +449,7 @@ span.idiff {
.tr {
display: flex;
@apply gl-border-b gl-border-b-subtle;
@apply gl-border-b gl-border-b-subtle dark:gl-border-b-section;
&.last-row {
border-bottom: 0;
@ -503,7 +503,7 @@ span.idiff {
.blame.file-content .td.line-numbers {
float: none;
@apply gl-border-l;
@apply gl-border-l gl-border-l-section;
border-radius: 0;
.file-line-num {
@ -517,7 +517,7 @@ span.idiff {
}
.blame-stream-container {
@apply gl-border-t;
@apply gl-border-t gl-border-t-section;
}
.blame-stream-loading {

View File

@ -67,7 +67,7 @@ $t-gray-a-16-design-pin: rgba($black, 0.16);
height: $design-pin-diameter;
width: $design-pin-diameter;
box-sizing: content-box;
background-color: var(--purple-500, $purple-500);
@apply gl-bg-purple-500 dark:gl-bg-purple-700;
color: var(--white, $white);
font-weight: $gl-font-weight-bold;
border-radius: 50%;

View File

@ -165,11 +165,11 @@
margin-left: $note-spacing-left;
.line_holder td:first-of-type {
@apply gl-border-l;
@apply gl-border-l gl-border-l-section;
}
.line_holder td:last-of-type {
@apply gl-border-r;
@apply gl-border-r gl-border-r-section;
}
.discussion-notes {
@ -248,15 +248,10 @@
border-left: 0;
.discussion-notes .timeline-entry:first-of-type > .timeline-entry-inner {
@apply gl-bg-default;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong gl-border-b-default;
}
@apply gl-bg-section dark:gl-border-b-section;
.toggle-replies-widget {
@apply gl-border-b-subtle;
@apply gl-border-b-subtle dark:gl-border-b-section;
}
.toggle-replies-widget[aria-expanded="false"] {
@ -317,7 +312,7 @@
.diff-files-holder {
.discussion-notes .timeline-entry:first-of-type > .timeline-entry-inner {
@apply gl-border-b gl-border-b-subtle;
@apply gl-border-b gl-border-b-subtle dark:gl-border-b-section;
}
}
@ -386,16 +381,11 @@
.timeline-entry-inner {
margin-left: $note-spacing-left;
@apply gl-bg-subtle gl-border-l gl-border-r;
@apply gl-bg-subtle gl-border-l gl-border-l-section gl-border-r gl-border-r-section;
.timeline-content {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
@apply gl-bg-default gl-border-b gl-border-b-subtle;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong gl-border-b-default;
}
@apply gl-bg-section gl-border-b gl-border-b-subtle dark:gl-border-b-section;
}
.timeline-avatar {

View File

@ -29,13 +29,8 @@
.timeline-content {
margin-left: $note-spacing-left;
@apply gl-border gl-rounded-base;
@apply gl-bg-section gl-border gl-border-section gl-rounded-base;
padding: $gl-padding-4 $gl-padding-8;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong gl-border-default;
}
}
.note-header-info {
@ -64,19 +59,14 @@
.timeline-content {
margin-left: $note-spacing-left;
@apply gl-border gl-border-b-subtle gl-rounded-t-base;
@apply gl-bg-section gl-border gl-border-b-subtle dark:gl-border-section gl-rounded-t-base;
padding: $gl-padding-4 $gl-padding-8;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong gl-border-default;
}
}
}
&:not(:first-of-type) .timeline-entry-inner {
margin-left: $note-spacing-left;
@apply gl-bg-subtle gl-border-l gl-border-r;
@apply gl-bg-subtle gl-border-l gl-border-l-section gl-border-r gl-border-r-section;
.timeline-content {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 18px;
@ -102,7 +92,7 @@
}
.discussion-reply-holder {
@apply gl-border-1 gl-border-solid gl-border-default gl-border-t-0;
@apply gl-border gl-border-section gl-border-t-0;
@apply gl-bg-subtle;
}
}
@ -292,16 +282,10 @@
.timeline-content {
padding: $gl-padding-8 !important;
@apply gl-border gl-rounded-base;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong gl-border-b;
}
@apply gl-bg-section gl-border gl-border-section gl-rounded-base;
&.expanded {
// Render border invisible
border-bottom: 1px solid var(--gl-background-color-subtle);
@apply gl-border-b-0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}

View File

@ -36,7 +36,7 @@ module Ci
EXECUTOR_TYPE_TO_NAMES = EXECUTOR_NAME_TO_TYPES.invert.freeze
belongs_to :runner
belongs_to :runner, class_name: 'Ci::Runner', inverse_of: :runner_managers
enum creation_state: {
started: 0,
@ -83,8 +83,11 @@ module Ci
).where(created_before_stale_deadline)
end
scope :for_runner, ->(runner_id) do
where(runner_id: runner_id)
scope :for_runner, ->(runner) do
scope = where(runner_id: runner)
scope = scope.where(runner_type: runner.runner_type) if runner.is_a?(Ci::Runner) # Use unique index if possible
scope
end
scope :with_system_xid, ->(system_xid) do

View File

@ -884,7 +884,7 @@ class Group < Namespace
def parent_allows_two_factor_authentication?
return true unless has_parent?
ancestor_settings = ancestors.find_by(parent_id: nil).namespace_settings
ancestor_settings = ancestors.find_top_level.namespace_settings
ancestor_settings.allow_mfa_for_subgroups
end

View File

@ -220,6 +220,7 @@ class Namespace < ApplicationRecord
scope :in_organization, ->(organization) { where(organization: organization) }
scope :by_name, ->(name) { where('name LIKE ?', "#{sanitize_sql_like(name)}%") }
scope :ordered_by_name, -> { order(:name) }
scope :top_level, -> { by_parent(nil) }
scope :with_statistics, -> do
namespace_statistic_columns = STATISTICS_COLUMNS.map { |column| sum_project_statistics_column(column) }
@ -279,6 +280,10 @@ class Namespace < ApplicationRecord
find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
end
def find_top_level
top_level.take
end
# Searches for namespaces matching the given query.
#
# This method uses ILIKE on PostgreSQL.
@ -346,10 +351,6 @@ class Namespace < ApplicationRecord
value.scan(Gitlab::Regex.group_name_regex_chars).join(' ')
end
def top_most
by_parent(nil)
end
def reference_prefix
User.reference_prefix
end
@ -366,7 +367,7 @@ class Namespace < ApplicationRecord
end
def username_reserved?(username)
without_project_namespaces.where(parent_id: nil).find_by_path_or_name(username).present?
without_project_namespaces.top_level.find_by_path_or_name(username).present?
end
end

View File

@ -107,7 +107,7 @@ class Namespace
.new(Namespace.where(id: namespace))
.base_and_ancestors
.reorder(nil)
.find_by(parent_id: nil)
.find_top_level
end
def db_query_timeout_counter

View File

@ -259,7 +259,7 @@ module Namespaces
.new(Namespace.where(id: parent_ids))
.base_and_ancestors
.reorder(nil)
.where(parent_id: nil)
.top_level
Namespace.lock.select(:id).where(id: roots).order(id: :asc).load
end

View File

@ -9,7 +9,7 @@ module Namespaces
def root_ancestor
if persisted? && !parent_id.nil?
strong_memoize(:root_ancestor) do
recursive_ancestors.reorder(nil).find_by(parent_id: nil)
recursive_ancestors.reorder(nil).find_top_level
end
elsif parent.nil?
self

View File

@ -10,4 +10,4 @@ feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166110
milestone: '17.5'
queued_migration_version: 20240912184158
finalized_by: # version of the migration that finalized this BBM
finalized_by: 20241025010332

View File

@ -0,0 +1,13 @@
---
table_name: ci_runner_machines_687967fa8a
classes:
- Ci::RunnerManager
feature_categories:
- runner
- fleet_visibility
- hosted_runners
description: Routing table for CI runner managers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168131
milestone: '17.6'
gitlab_schema: gitlab_ci
exempt_from_sharding: true

View File

@ -17,4 +17,4 @@ desired_sharding_key_migration_job_name: BackfillProjectIdToDependencyListExport
# organization_id: organizations
#
# A multi-column not-null constraint should be added on these columns.
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/501463

View File

@ -1,7 +1,7 @@
---
table_name: elastic_reindexing_subtasks
classes:
- Elastic::ReindexingSubtask
- Search::Elastic::ReindexingSubtask
feature_categories:
- global_search
description: TODO

View File

@ -0,0 +1,13 @@
---
table_name: group_type_ci_runner_machines_687967fa8a
classes:
- Ci::RunnerManager
feature_categories:
- runner
- fleet_visibility
description: Information about runner managers associated to group Ci::Runner models
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168131
milestone: '17.6'
gitlab_schema: gitlab_ci
sharding_key:
sharding_key_id: namespaces

View File

@ -0,0 +1,13 @@
---
table_name: instance_type_ci_runner_machines_687967fa8a
classes:
- Ci::RunnerManager
feature_categories:
- runner
- fleet_visibility
- hosted_runners
description: Information about runner managers associated to instance Ci::Runner models
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168131
milestone: '17.6'
gitlab_schema: gitlab_ci
exempt_from_sharding: true

View File

@ -0,0 +1,13 @@
---
table_name: project_type_ci_runner_machines_687967fa8a
classes:
- Ci::RunnerManager
feature_categories:
- runner
- fleet_visibility
description: Information about runner managers associated to project Ci::Runner models
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168131
milestone: '17.6'
gitlab_schema: gitlab_ci
sharding_key:
sharding_key_id: projects

View File

@ -8,4 +8,22 @@ description: Stores merge access settings for protected branches
introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5081
milestone: '8.11'
gitlab_schema: gitlab_main_cell
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/490485
allow_cross_foreign_keys:
- gitlab_main_clusterwide
desired_sharding_key:
protected_branch_project_id:
references: projects
backfill_via:
parent:
foreign_key: protected_branch_id
table: protected_branches
sharding_key: project_id
belongs_to: protected_branch
protected_branch_namespace_id:
references: namespaces
backfill_via:
parent:
foreign_key: protected_branch_id
table: protected_branches
sharding_key: namespace_id
belongs_to: protected_branch

View File

@ -12,3 +12,4 @@ gitlab_schema: gitlab_sec
# because the backfill migration was processed manually.
# This migration is custom due to data consistency issues and cross-DB backfill.
desired_sharding_key_migration_job_name: BackfillProjectIdToSecurityScans
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/501459

View File

@ -7,6 +7,8 @@ class RemoveBackfillVulnerabilityOccurrencePipelinesProjectId < Gitlab::Database
MIGRATION = "BackfillVulnerabilityOccurrencePipelinesProjectId"
def up
# rubocop:disable Migration/BatchMigrationsPostOnly -- Must be run before BBM code is deleted.
# Estimated runtime on .com is only 5 seconds.
delete_batched_background_migration(
MIGRATION,
:vulnerability_occurrence_pipelines,
@ -18,6 +20,7 @@ class RemoveBackfillVulnerabilityOccurrencePipelinesProjectId < Gitlab::Database
:occurrence_id
]
)
# rubocop:enable Migration/BatchMigrationsPostOnly
end
def down

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
class CreatePartitionedCiRunnerManagers < Gitlab::Database::Migration[2.2]
include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
include Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
milestone '17.6'
disable_ddl_transaction!
TABLE_NAME = 'ci_runner_machines'
PARTITIONED_TABLE_NAME = :ci_runner_machines_687967fa8a
PARTITIONED_TABLE_PK = %w[id runner_type]
def up
partition_table_by_list(
TABLE_NAME, 'runner_type', primary_key: PARTITIONED_TABLE_PK,
partition_mappings: { instance_type: 1, group_type: 2, project_type: 3 },
partition_name_format: '%{partition_name}_%{table_name}',
create_partitioned_table_fn: ->(name) { create_partitioned_table(name) }
)
end
def down
drop_partitioned_table_for(TABLE_NAME)
end
private
def create_partitioned_table(name)
options = 'PARTITION BY LIST (runner_type)'
# rubocop: disable Migration/EnsureFactoryForTable -- we'll reuse the ci_runner_machines factory once migrated
create_table name, primary_key: PARTITIONED_TABLE_PK, options: options do |t|
t.bigint :id, null: false
t.bigint :runner_id, null: false
t.bigint :sharding_key_id, null: true
t.timestamps_with_timezone null: false
t.datetime_with_timezone :contacted_at
t.integer :creation_state, null: false, limit: 2, default: 0
t.integer :executor_type, null: true, limit: 2
t.integer :runner_type, null: false, limit: 2
t.jsonb :config, null: false, default: {}
t.text :system_xid, null: false, limit: 64
t.text :platform, limit: 255
t.text :architecture, limit: 255
t.text :revision, limit: 255
t.text :ip_address, limit: 1024
t.text :version, limit: 2048
t.index [:runner_id, :runner_type, :system_xid], name: "idx_uniq_#{name}_on_runner_id_system_xid", unique: true
t.index :sharding_key_id, name: "idx_#{name}_on_sharding_key_where_notnull",
where: 'sharding_key_id IS NOT NULL'
t.index %i[contacted_at id], name: "idx_#{name}_on_contacted_at_desc_id_desc",
order: { contacted_at: :desc, id: :desc }
t.index %i[created_at id], name: "index_#{name}_on_created_at_and_id_desc",
order: { runner_type: :asc, id: :desc }
t.index %q[((substring(version from '^\d+\.'))), version, runner_id], name: "index_#{name}_on_major_version"
t.index %q[((substring(version from '^\d+\.\d+\.'))), version, runner_id], name: "index_#{name}_on_minor_version"
t.index %q[((substring(version from '^\d+\.\d+\.\d+'))), version, runner_id],
name: "index_#{name}_on_patch_version"
t.index :version, name: "index_#{name}_on_version"
end
# rubocop: enable Migration/EnsureFactoryForTable
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class AddShardingKeyCheckConstraintToPartitionedCiRunnerManagers < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.6'
PARTITIONED_TABLE_NAME = :ci_runner_machines_687967fa8a
CONSTRAINT_NAME = 'check_sharding_key_id_nullness'
def up
Gitlab::Database::PostgresPartitionedTable.each_partition(PARTITIONED_TABLE_NAME) do |partition|
source = partition.to_s
add_check_constraint(source,
source.start_with?('instance_type') ? 'sharding_key_id IS NULL' : 'sharding_key_id IS NOT NULL',
CONSTRAINT_NAME)
end
end
def down
Gitlab::Database::PostgresPartitionedTable.each_partition(PARTITIONED_TABLE_NAME) do |partition|
source = partition.to_s
remove_check_constraint(source, CONSTRAINT_NAME)
end
end
end

View File

@ -0,0 +1,67 @@
# frozen_string_literal: true
class AddFkFromPartitionedCiRunnerManagersToPartitionedCiRunners < Gitlab::Database::Migration[2.2]
include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
include Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
milestone '17.6'
disable_ddl_transaction!
SOURCE_TABLE_NAME = :ci_runner_machines_687967fa8a
TARGET_TABLE_NAME = :ci_runners_e59bb2812d
COLUMN = %i[runner_id runner_type]
TARGET_COLUMN = %i[id runner_type]
FK_NAME = :fk_rails_3f92913d27
def up
Gitlab::Database::PostgresPartitionedTable.each_partition(SOURCE_TABLE_NAME) do |partition|
add_partitioned_foreign_key(partition)
end
add_concurrent_foreign_key(SOURCE_TABLE_NAME, TARGET_TABLE_NAME,
name: FK_NAME,
column: COLUMN,
target_column: TARGET_COLUMN,
validate: true,
on_update: :cascade,
on_delete: :cascade,
reverse_lock_order: true,
allow_partitioned: true)
end
def down
Gitlab::Database::PostgresPartitionedTable.each_partition(SOURCE_TABLE_NAME) do |partition|
source = partition.to_s
with_lock_retries do
remove_foreign_key_if_exists(source, partitioned_target_table_name(source),
name: FK_NAME, reverse_lock_order: true)
end
end
with_lock_retries do
remove_foreign_key_if_exists(SOURCE_TABLE_NAME, TARGET_TABLE_NAME,
name: FK_NAME, reverse_lock_order: true)
end
end
private
def partitioned_target_table_name(source)
runner_type = source.match(/(.+?_type).+/)[1]
"#{runner_type}_#{TARGET_TABLE_NAME}"
end
def add_partitioned_foreign_key(partition)
source = partition.to_s
add_concurrent_foreign_key(source, partitioned_target_table_name(source),
name: FK_NAME,
column: COLUMN,
target_column: TARGET_COLUMN,
validate: true,
on_update: :cascade,
on_delete: :cascade,
reverse_lock_order: true)
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class FinalizeBackfillHasVulnerabilityResolution < Gitlab::Database::Migration[2.2]
MIGRATION = 'BackfillHasVulnerabilityResolution'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
milestone '17.6'
def up
ensure_batched_background_migration_is_finished(
job_class_name: MIGRATION,
table_name: :vulnerability_reads,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down
# no op
end
end

View File

@ -0,0 +1 @@
e0857bad7c3c291efa74c22894774df18956cf7abb4ca3d65eb8c3dbfd3d7cbf

View File

@ -0,0 +1 @@
219459af48a3bdd8be64ba0cb8386dc6c2285b3106bda3ee5a83581cafa60d49

View File

@ -0,0 +1 @@
ba92199ba7a07d17d1d2d08d283762e068dbc6daba4b9139475e9630ce769a85

View File

@ -0,0 +1 @@
2c9398a7f550db94fe876e862009cb26fdebb7a334165e13f35f98496e7c3729

View File

@ -839,6 +839,71 @@ $$;
COMMENT ON FUNCTION table_sync_function_686d6c7993() IS 'Partitioning migration: table sync for ci_runners table';
CREATE FUNCTION table_sync_function_e438f29263() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
DELETE FROM ci_runner_machines_687967fa8a where "id" = OLD."id";
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE ci_runner_machines_687967fa8a
SET "runner_id" = NEW."runner_id",
"sharding_key_id" = NEW."sharding_key_id",
"created_at" = NEW."created_at",
"updated_at" = NEW."updated_at",
"contacted_at" = NEW."contacted_at",
"creation_state" = NEW."creation_state",
"executor_type" = NEW."executor_type",
"runner_type" = NEW."runner_type",
"config" = NEW."config",
"system_xid" = NEW."system_xid",
"platform" = NEW."platform",
"architecture" = NEW."architecture",
"revision" = NEW."revision",
"ip_address" = NEW."ip_address",
"version" = NEW."version"
WHERE ci_runner_machines_687967fa8a."id" = NEW."id";
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO ci_runner_machines_687967fa8a ("id",
"runner_id",
"sharding_key_id",
"created_at",
"updated_at",
"contacted_at",
"creation_state",
"executor_type",
"runner_type",
"config",
"system_xid",
"platform",
"architecture",
"revision",
"ip_address",
"version")
VALUES (NEW."id",
NEW."runner_id",
NEW."sharding_key_id",
NEW."created_at",
NEW."updated_at",
NEW."contacted_at",
NEW."creation_state",
NEW."executor_type",
NEW."runner_type",
NEW."config",
NEW."system_xid",
NEW."platform",
NEW."architecture",
NEW."revision",
NEW."ip_address",
NEW."version");
END IF;
RETURN NULL;
END
$$;
COMMENT ON FUNCTION table_sync_function_e438f29263() IS 'Partitioning migration: table sync for ci_runner_machines table';
CREATE FUNCTION trigger_01b3fc052119() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -9301,6 +9366,32 @@ CREATE TABLE ci_runner_machines (
CONSTRAINT check_f214590856 CHECK ((char_length(ip_address) <= 1024))
);
CREATE TABLE ci_runner_machines_687967fa8a (
id bigint NOT NULL,
runner_id bigint NOT NULL,
sharding_key_id bigint,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
contacted_at timestamp with time zone,
creation_state smallint DEFAULT 0 NOT NULL,
executor_type smallint,
runner_type smallint NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
system_xid text NOT NULL,
platform text,
architecture text,
revision text,
ip_address text,
version text,
CONSTRAINT check_3d8736b3af CHECK ((char_length(system_xid) <= 64)),
CONSTRAINT check_5bad2a6944 CHECK ((char_length(revision) <= 255)),
CONSTRAINT check_7dc4eee8a5 CHECK ((char_length(version) <= 2048)),
CONSTRAINT check_b1e456641b CHECK ((char_length(ip_address) <= 1024)),
CONSTRAINT check_c788f4b18a CHECK ((char_length(platform) <= 255)),
CONSTRAINT check_f3d25ab844 CHECK ((char_length(architecture) <= 255))
)
PARTITION BY LIST (runner_type);
CREATE SEQUENCE ci_runner_machines_id_seq
START WITH 1
INCREMENT BY 1
@ -12399,6 +12490,32 @@ CREATE SEQUENCE group_ssh_certificates_id_seq
ALTER SEQUENCE group_ssh_certificates_id_seq OWNED BY group_ssh_certificates.id;
CREATE TABLE group_type_ci_runner_machines_687967fa8a (
id bigint NOT NULL,
runner_id bigint NOT NULL,
sharding_key_id bigint,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
contacted_at timestamp with time zone,
creation_state smallint DEFAULT 0 NOT NULL,
executor_type smallint,
runner_type smallint NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
system_xid text NOT NULL,
platform text,
architecture text,
revision text,
ip_address text,
version text,
CONSTRAINT check_3d8736b3af CHECK ((char_length(system_xid) <= 64)),
CONSTRAINT check_5bad2a6944 CHECK ((char_length(revision) <= 255)),
CONSTRAINT check_7dc4eee8a5 CHECK ((char_length(version) <= 2048)),
CONSTRAINT check_b1e456641b CHECK ((char_length(ip_address) <= 1024)),
CONSTRAINT check_c788f4b18a CHECK ((char_length(platform) <= 255)),
CONSTRAINT check_f3d25ab844 CHECK ((char_length(architecture) <= 255)),
CONSTRAINT check_sharding_key_id_nullness CHECK ((sharding_key_id IS NOT NULL))
);
CREATE TABLE group_type_ci_runners_e59bb2812d (
id bigint NOT NULL,
creator_id bigint,
@ -12965,6 +13082,32 @@ CREATE SEQUENCE instance_integrations_id_seq
ALTER SEQUENCE instance_integrations_id_seq OWNED BY instance_integrations.id;
CREATE TABLE instance_type_ci_runner_machines_687967fa8a (
id bigint NOT NULL,
runner_id bigint NOT NULL,
sharding_key_id bigint,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
contacted_at timestamp with time zone,
creation_state smallint DEFAULT 0 NOT NULL,
executor_type smallint,
runner_type smallint NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
system_xid text NOT NULL,
platform text,
architecture text,
revision text,
ip_address text,
version text,
CONSTRAINT check_3d8736b3af CHECK ((char_length(system_xid) <= 64)),
CONSTRAINT check_5bad2a6944 CHECK ((char_length(revision) <= 255)),
CONSTRAINT check_7dc4eee8a5 CHECK ((char_length(version) <= 2048)),
CONSTRAINT check_b1e456641b CHECK ((char_length(ip_address) <= 1024)),
CONSTRAINT check_c788f4b18a CHECK ((char_length(platform) <= 255)),
CONSTRAINT check_f3d25ab844 CHECK ((char_length(architecture) <= 255)),
CONSTRAINT check_sharding_key_id_nullness CHECK ((sharding_key_id IS NULL))
);
CREATE TABLE instance_type_ci_runners_e59bb2812d (
id bigint NOT NULL,
creator_id bigint,
@ -17821,6 +17964,32 @@ CREATE SEQUENCE project_topics_id_seq
ALTER SEQUENCE project_topics_id_seq OWNED BY project_topics.id;
CREATE TABLE project_type_ci_runner_machines_687967fa8a (
id bigint NOT NULL,
runner_id bigint NOT NULL,
sharding_key_id bigint,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
contacted_at timestamp with time zone,
creation_state smallint DEFAULT 0 NOT NULL,
executor_type smallint,
runner_type smallint NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
system_xid text NOT NULL,
platform text,
architecture text,
revision text,
ip_address text,
version text,
CONSTRAINT check_3d8736b3af CHECK ((char_length(system_xid) <= 64)),
CONSTRAINT check_5bad2a6944 CHECK ((char_length(revision) <= 255)),
CONSTRAINT check_7dc4eee8a5 CHECK ((char_length(version) <= 2048)),
CONSTRAINT check_b1e456641b CHECK ((char_length(ip_address) <= 1024)),
CONSTRAINT check_c788f4b18a CHECK ((char_length(platform) <= 255)),
CONSTRAINT check_f3d25ab844 CHECK ((char_length(architecture) <= 255)),
CONSTRAINT check_sharding_key_id_nullness CHECK ((sharding_key_id IS NOT NULL))
);
CREATE TABLE project_type_ci_runners_e59bb2812d (
id bigint NOT NULL,
creator_id bigint,
@ -22229,10 +22398,16 @@ ALTER TABLE ONLY p_ci_pipelines ATTACH PARTITION ci_pipelines FOR VALUES IN ('10
ALTER TABLE ONLY p_ci_stages ATTACH PARTITION ci_stages FOR VALUES IN ('100', '101');
ALTER TABLE ONLY ci_runner_machines_687967fa8a ATTACH PARTITION group_type_ci_runner_machines_687967fa8a FOR VALUES IN ('2');
ALTER TABLE ONLY ci_runners_e59bb2812d ATTACH PARTITION group_type_ci_runners_e59bb2812d FOR VALUES IN ('2');
ALTER TABLE ONLY ci_runner_machines_687967fa8a ATTACH PARTITION instance_type_ci_runner_machines_687967fa8a FOR VALUES IN ('1');
ALTER TABLE ONLY ci_runners_e59bb2812d ATTACH PARTITION instance_type_ci_runners_e59bb2812d FOR VALUES IN ('1');
ALTER TABLE ONLY ci_runner_machines_687967fa8a ATTACH PARTITION project_type_ci_runner_machines_687967fa8a FOR VALUES IN ('3');
ALTER TABLE ONLY ci_runners_e59bb2812d ATTACH PARTITION project_type_ci_runners_e59bb2812d FOR VALUES IN ('3');
ALTER TABLE ONLY abuse_events ALTER COLUMN id SET DEFAULT nextval('abuse_events_id_seq'::regclass);
@ -24546,6 +24721,9 @@ ALTER TABLE ONLY ci_resource_groups
ALTER TABLE ONLY ci_resources
ADD CONSTRAINT ci_resources_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_runner_machines_687967fa8a
ADD CONSTRAINT ci_runner_machines_687967fa8a_pkey PRIMARY KEY (id, runner_type);
ALTER TABLE ONLY ci_runner_machines
ADD CONSTRAINT ci_runner_machines_pkey PRIMARY KEY (id);
@ -25011,6 +25189,9 @@ ALTER TABLE ONLY group_security_exclusions
ALTER TABLE ONLY group_ssh_certificates
ADD CONSTRAINT group_ssh_certificates_pkey PRIMARY KEY (id);
ALTER TABLE ONLY group_type_ci_runner_machines_687967fa8a
ADD CONSTRAINT group_type_ci_runner_machines_687967fa8a_pkey PRIMARY KEY (id, runner_type);
ALTER TABLE ONLY group_type_ci_runners_e59bb2812d
ADD CONSTRAINT group_type_ci_runners_e59bb2812d_pkey PRIMARY KEY (id, runner_type);
@ -25098,6 +25279,9 @@ ALTER TABLE ONLY instance_audit_events_streaming_headers
ALTER TABLE ONLY instance_integrations
ADD CONSTRAINT instance_integrations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY instance_type_ci_runner_machines_687967fa8a
ADD CONSTRAINT instance_type_ci_runner_machines_687967fa8a_pkey PRIMARY KEY (id, runner_type);
ALTER TABLE ONLY instance_type_ci_runners_e59bb2812d
ADD CONSTRAINT instance_type_ci_runners_e59bb2812d_pkey PRIMARY KEY (id, runner_type);
@ -25788,6 +25972,9 @@ ALTER TABLE ONLY project_statistics
ALTER TABLE ONLY project_topics
ADD CONSTRAINT project_topics_pkey PRIMARY KEY (id);
ALTER TABLE ONLY project_type_ci_runner_machines_687967fa8a
ADD CONSTRAINT project_type_ci_runner_machines_687967fa8a_pkey PRIMARY KEY (id, runner_type);
ALTER TABLE ONLY project_type_ci_runners_e59bb2812d
ADD CONSTRAINT project_type_ci_runners_e59bb2812d_pkey PRIMARY KEY (id, runner_type);
@ -27554,6 +27741,38 @@ CREATE UNIQUE INDEX finding_link_name_url_idx ON vulnerability_finding_links USI
CREATE UNIQUE INDEX finding_link_url_idx ON vulnerability_finding_links USING btree (vulnerability_occurrence_id, url) WHERE (name IS NULL);
CREATE INDEX idx_ci_runner_machines_687967fa8a_on_contacted_at_desc_id_desc ON ONLY ci_runner_machines_687967fa8a USING btree (contacted_at DESC, id DESC);
CREATE INDEX group_type_ci_runner_machines_687967fa8a_contacted_at_id_idx ON group_type_ci_runner_machines_687967fa8a USING btree (contacted_at DESC, id DESC);
CREATE INDEX index_ci_runner_machines_687967fa8a_on_created_at_and_id_desc ON ONLY ci_runner_machines_687967fa8a USING btree (created_at, id DESC);
CREATE INDEX group_type_ci_runner_machines_687967fa8a_created_at_id_idx ON group_type_ci_runner_machines_687967fa8a USING btree (created_at, id DESC);
CREATE INDEX idx_ci_runner_machines_687967fa8a_on_sharding_key_where_notnull ON ONLY ci_runner_machines_687967fa8a USING btree (sharding_key_id) WHERE (sharding_key_id IS NOT NULL);
CREATE INDEX group_type_ci_runner_machines_687967fa8a_sharding_key_id_idx ON group_type_ci_runner_machines_687967fa8a USING btree (sharding_key_id) WHERE (sharding_key_id IS NOT NULL);
CREATE INDEX index_ci_runner_machines_687967fa8a_on_version ON ONLY ci_runner_machines_687967fa8a USING btree (version);
CREATE INDEX group_type_ci_runner_machines_687967fa8a_version_idx ON group_type_ci_runner_machines_687967fa8a USING btree (version);
CREATE INDEX index_ci_runner_machines_687967fa8a_on_major_version ON ONLY ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.'::text), version, runner_id);
CREATE INDEX group_type_ci_runner_machines_6_substring_version_runner_id_idx ON group_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.'::text), version, runner_id);
CREATE INDEX index_ci_runner_machines_687967fa8a_on_minor_version ON ONLY ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.'::text), version, runner_id);
CREATE INDEX group_type_ci_runner_machines__substring_version_runner_id_idx1 ON group_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.'::text), version, runner_id);
CREATE INDEX index_ci_runner_machines_687967fa8a_on_patch_version ON ONLY ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.\d+'::text), version, runner_id);
CREATE INDEX group_type_ci_runner_machines__substring_version_runner_id_idx2 ON group_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.\d+'::text), version, runner_id);
CREATE UNIQUE INDEX idx_uniq_ci_runner_machines_687967fa8a_on_runner_id_system_xid ON ONLY ci_runner_machines_687967fa8a USING btree (runner_id, runner_type, system_xid);
CREATE UNIQUE INDEX group_type_ci_runner_machines_runner_id_runner_type_system__idx ON group_type_ci_runner_machines_687967fa8a USING btree (runner_id, runner_type, system_xid);
CREATE UNIQUE INDEX index_uniq_ci_runners_e59bb2812d_on_token_encrypted_and_type ON ONLY ci_runners_e59bb2812d USING btree (token_encrypted, runner_type);
CREATE UNIQUE INDEX group_type_ci_runners_e59bb2812_token_encrypted_runner_type_idx ON group_type_ci_runners_e59bb2812d USING btree (token_encrypted, runner_type);
@ -32198,6 +32417,22 @@ CREATE INDEX index_zoom_meetings_on_issue_status ON zoom_meetings USING btree (i
CREATE INDEX index_zoom_meetings_on_project_id ON zoom_meetings USING btree (project_id);
CREATE UNIQUE INDEX instance_type_ci_runner_machi_runner_id_runner_type_system__idx ON instance_type_ci_runner_machines_687967fa8a USING btree (runner_id, runner_type, system_xid);
CREATE INDEX instance_type_ci_runner_machin_substring_version_runner_id_idx1 ON instance_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.'::text), version, runner_id);
CREATE INDEX instance_type_ci_runner_machin_substring_version_runner_id_idx2 ON instance_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.\d+'::text), version, runner_id);
CREATE INDEX instance_type_ci_runner_machine_substring_version_runner_id_idx ON instance_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.'::text), version, runner_id);
CREATE INDEX instance_type_ci_runner_machines_687967fa8a_contacted_at_id_idx ON instance_type_ci_runner_machines_687967fa8a USING btree (contacted_at DESC, id DESC);
CREATE INDEX instance_type_ci_runner_machines_687967fa8a_created_at_id_idx ON instance_type_ci_runner_machines_687967fa8a USING btree (created_at, id DESC);
CREATE INDEX instance_type_ci_runner_machines_687967fa8a_sharding_key_id_idx ON instance_type_ci_runner_machines_687967fa8a USING btree (sharding_key_id) WHERE (sharding_key_id IS NOT NULL);
CREATE INDEX instance_type_ci_runner_machines_687967fa8a_version_idx ON instance_type_ci_runner_machines_687967fa8a USING btree (version);
CREATE INDEX instance_type_ci_runners_e59bb2812d_active_id_idx ON instance_type_ci_runners_e59bb2812d USING btree (active, id);
CREATE INDEX instance_type_ci_runners_e59bb2812d_contacted_at_id_idx ON instance_type_ci_runners_e59bb2812d USING btree (contacted_at, id DESC);
@ -32272,6 +32507,22 @@ CREATE INDEX partial_index_user_id_app_id_created_at_token_not_revoked ON oauth_
CREATE UNIQUE INDEX pm_checkpoints_path_components ON pm_checkpoints USING btree (purl_type, data_type, version_format);
CREATE UNIQUE INDEX project_type_ci_runner_machin_runner_id_runner_type_system__idx ON project_type_ci_runner_machines_687967fa8a USING btree (runner_id, runner_type, system_xid);
CREATE INDEX project_type_ci_runner_machine_substring_version_runner_id_idx1 ON project_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.'::text), version, runner_id);
CREATE INDEX project_type_ci_runner_machine_substring_version_runner_id_idx2 ON project_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.\d+\.\d+'::text), version, runner_id);
CREATE INDEX project_type_ci_runner_machines_687967fa8a_contacted_at_id_idx ON project_type_ci_runner_machines_687967fa8a USING btree (contacted_at DESC, id DESC);
CREATE INDEX project_type_ci_runner_machines_687967fa8a_created_at_id_idx ON project_type_ci_runner_machines_687967fa8a USING btree (created_at, id DESC);
CREATE INDEX project_type_ci_runner_machines_687967fa8a_sharding_key_id_idx ON project_type_ci_runner_machines_687967fa8a USING btree (sharding_key_id) WHERE (sharding_key_id IS NOT NULL);
CREATE INDEX project_type_ci_runner_machines_687967fa8a_version_idx ON project_type_ci_runner_machines_687967fa8a USING btree (version);
CREATE INDEX project_type_ci_runner_machines_substring_version_runner_id_idx ON project_type_ci_runner_machines_687967fa8a USING btree ("substring"(version, '^\d+\.'::text), version, runner_id);
CREATE INDEX project_type_ci_runners_e59bb2812d_active_id_idx ON project_type_ci_runners_e59bb2812d USING btree (active, id);
CREATE INDEX project_type_ci_runners_e59bb2812d_contacted_at_id_idx ON project_type_ci_runners_e59bb2812d USING btree (contacted_at, id DESC);
@ -33942,6 +34193,24 @@ ALTER INDEX p_ci_pipelines_pkey ATTACH PARTITION ci_pipelines_pkey;
ALTER INDEX p_ci_stages_pkey ATTACH PARTITION ci_stages_pkey;
ALTER INDEX idx_ci_runner_machines_687967fa8a_on_contacted_at_desc_id_desc ATTACH PARTITION group_type_ci_runner_machines_687967fa8a_contacted_at_id_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_created_at_and_id_desc ATTACH PARTITION group_type_ci_runner_machines_687967fa8a_created_at_id_idx;
ALTER INDEX ci_runner_machines_687967fa8a_pkey ATTACH PARTITION group_type_ci_runner_machines_687967fa8a_pkey;
ALTER INDEX idx_ci_runner_machines_687967fa8a_on_sharding_key_where_notnull ATTACH PARTITION group_type_ci_runner_machines_687967fa8a_sharding_key_id_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_version ATTACH PARTITION group_type_ci_runner_machines_687967fa8a_version_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_major_version ATTACH PARTITION group_type_ci_runner_machines_6_substring_version_runner_id_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_minor_version ATTACH PARTITION group_type_ci_runner_machines__substring_version_runner_id_idx1;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_patch_version ATTACH PARTITION group_type_ci_runner_machines__substring_version_runner_id_idx2;
ALTER INDEX idx_uniq_ci_runner_machines_687967fa8a_on_runner_id_system_xid ATTACH PARTITION group_type_ci_runner_machines_runner_id_runner_type_system__idx;
ALTER INDEX index_uniq_ci_runners_e59bb2812d_on_token_encrypted_and_type ATTACH PARTITION group_type_ci_runners_e59bb2812_token_encrypted_runner_type_idx;
ALTER INDEX index_ci_runners_e59bb2812d_on_active_and_id ATTACH PARTITION group_type_ci_runners_e59bb2812d_active_id_idx;
@ -34106,6 +34375,24 @@ ALTER INDEX p_ci_builds_user_id_name_created_at_idx ATTACH PARTITION index_secur
ALTER INDEX p_ci_builds_name_id_idx ATTACH PARTITION index_security_ci_builds_on_name_and_id_parser_features;
ALTER INDEX idx_uniq_ci_runner_machines_687967fa8a_on_runner_id_system_xid ATTACH PARTITION instance_type_ci_runner_machi_runner_id_runner_type_system__idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_minor_version ATTACH PARTITION instance_type_ci_runner_machin_substring_version_runner_id_idx1;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_patch_version ATTACH PARTITION instance_type_ci_runner_machin_substring_version_runner_id_idx2;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_major_version ATTACH PARTITION instance_type_ci_runner_machine_substring_version_runner_id_idx;
ALTER INDEX idx_ci_runner_machines_687967fa8a_on_contacted_at_desc_id_desc ATTACH PARTITION instance_type_ci_runner_machines_687967fa8a_contacted_at_id_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_created_at_and_id_desc ATTACH PARTITION instance_type_ci_runner_machines_687967fa8a_created_at_id_idx;
ALTER INDEX ci_runner_machines_687967fa8a_pkey ATTACH PARTITION instance_type_ci_runner_machines_687967fa8a_pkey;
ALTER INDEX idx_ci_runner_machines_687967fa8a_on_sharding_key_where_notnull ATTACH PARTITION instance_type_ci_runner_machines_687967fa8a_sharding_key_id_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_version ATTACH PARTITION instance_type_ci_runner_machines_687967fa8a_version_idx;
ALTER INDEX index_ci_runners_e59bb2812d_on_active_and_id ATTACH PARTITION instance_type_ci_runners_e59bb2812d_active_id_idx;
ALTER INDEX index_ci_runners_e59bb2812d_on_contacted_at_and_id_desc ATTACH PARTITION instance_type_ci_runners_e59bb2812d_contacted_at_id_idx;
@ -34140,6 +34427,24 @@ ALTER INDEX index_uniq_ci_runners_e59bb2812d_on_token_encrypted_and_type ATTACH
ALTER INDEX p_ci_builds_scheduled_at_idx ATTACH PARTITION partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs;
ALTER INDEX idx_uniq_ci_runner_machines_687967fa8a_on_runner_id_system_xid ATTACH PARTITION project_type_ci_runner_machin_runner_id_runner_type_system__idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_minor_version ATTACH PARTITION project_type_ci_runner_machine_substring_version_runner_id_idx1;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_patch_version ATTACH PARTITION project_type_ci_runner_machine_substring_version_runner_id_idx2;
ALTER INDEX idx_ci_runner_machines_687967fa8a_on_contacted_at_desc_id_desc ATTACH PARTITION project_type_ci_runner_machines_687967fa8a_contacted_at_id_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_created_at_and_id_desc ATTACH PARTITION project_type_ci_runner_machines_687967fa8a_created_at_id_idx;
ALTER INDEX ci_runner_machines_687967fa8a_pkey ATTACH PARTITION project_type_ci_runner_machines_687967fa8a_pkey;
ALTER INDEX idx_ci_runner_machines_687967fa8a_on_sharding_key_where_notnull ATTACH PARTITION project_type_ci_runner_machines_687967fa8a_sharding_key_id_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_version ATTACH PARTITION project_type_ci_runner_machines_687967fa8a_version_idx;
ALTER INDEX index_ci_runner_machines_687967fa8a_on_major_version ATTACH PARTITION project_type_ci_runner_machines_substring_version_runner_id_idx;
ALTER INDEX index_ci_runners_e59bb2812d_on_active_and_id ATTACH PARTITION project_type_ci_runners_e59bb2812d_active_id_idx;
ALTER INDEX index_ci_runners_e59bb2812d_on_contacted_at_and_id_desc ATTACH PARTITION project_type_ci_runners_e59bb2812d_contacted_at_id_idx;
@ -34234,6 +34539,8 @@ CREATE TRIGGER table_sync_trigger_57c8465cd7 AFTER INSERT OR DELETE OR UPDATE ON
CREATE TRIGGER table_sync_trigger_61879721b5 AFTER INSERT OR DELETE OR UPDATE ON ci_runners FOR EACH ROW EXECUTE FUNCTION table_sync_function_686d6c7993();
CREATE TRIGGER table_sync_trigger_bc3e7b56bd AFTER INSERT OR DELETE OR UPDATE ON ci_runner_machines FOR EACH ROW EXECUTE FUNCTION table_sync_function_e438f29263();
CREATE TRIGGER table_sync_trigger_cd362c20e2 AFTER INSERT OR DELETE OR UPDATE ON merge_request_diff_files FOR EACH ROW EXECUTE FUNCTION table_sync_function_3f39f64fc3();
CREATE TRIGGER tags_loose_fk_trigger AFTER DELETE ON tags REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
@ -36634,6 +36941,18 @@ ALTER TABLE ONLY issuable_resource_links
ALTER TABLE ONLY board_assignees
ADD CONSTRAINT fk_rails_3f6f926bd5 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE;
ALTER TABLE ONLY group_type_ci_runner_machines_687967fa8a
ADD CONSTRAINT fk_rails_3f92913d27 FOREIGN KEY (runner_id, runner_type) REFERENCES group_type_ci_runners_e59bb2812d(id, runner_type) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY instance_type_ci_runner_machines_687967fa8a
ADD CONSTRAINT fk_rails_3f92913d27 FOREIGN KEY (runner_id, runner_type) REFERENCES instance_type_ci_runners_e59bb2812d(id, runner_type) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY project_type_ci_runner_machines_687967fa8a
ADD CONSTRAINT fk_rails_3f92913d27 FOREIGN KEY (runner_id, runner_type) REFERENCES project_type_ci_runners_e59bb2812d(id, runner_type) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ci_runner_machines_687967fa8a
ADD CONSTRAINT fk_rails_3f92913d27 FOREIGN KEY (runner_id, runner_type) REFERENCES ci_runners_e59bb2812d(id, runner_type) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY description_versions
ADD CONSTRAINT fk_rails_3ff658220b FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;

View File

@ -75,7 +75,7 @@ These versions of Oracle Linux are supported.
|:-----------------|:-------------------------------|:-------------|:----------------------------------------------------------------------------------------|:---------------------|:--------|
| Oracle Linux 7 | GitLab CE / GitLab EE 8.14.0 | `x86_64` | [Use CentOS installation documentation](https://about.gitlab.com/install/#centos-7) | Dec 2024 | [Oracle Linux details](https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf) |
| Oracle Linux 8 | GitLab CE / GitLab EE 12.8.1 | `x86_64` | [Use AlmaLinux installation documentation](https://about.gitlab.com/install/#almalinux) | July 2029 | [Oracle Linux details](https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf) |
| Oracle Linux 9 | GitLab CE / GitLab EE 16.0.0 | `x86_64` | [Use AlmaLinux installation documentation](https://about.gitlab.com/install/#almalinux) | June 2032 | [Oracle Linux details](https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf) |
| Oracle Linux 9 | GitLab CE / GitLab EE 16.2.0 | `x86_64` | [Use AlmaLinux installation documentation](https://about.gitlab.com/install/#almalinux) | June 2032 | [Oracle Linux details](https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf) |
## Raspberry Pi OS

View File

@ -82,7 +82,7 @@ Configure a GitLab CI/CD job to use CI Steps with the `run` keyword. You cannot
The `run` keyword accepts a list of steps to run. Steps are run one at a time in the order they are defined in the list.
Each list item has a `name` and either `step`, `script`, or `action`.
Name must consist only of alpha-numeric characters and underscores, and must not start with a number.
Name must consist only of alphanumeric characters and underscores, and must not start with a number.
### Run a step
@ -296,7 +296,7 @@ The specification defines inputs and outputs that the step receives and returns.
#### Specify inputs
Input names can only use alpha-numeric characters and underscores, and must not start with a number.
Input names can only use alphanumeric characters and underscores, and must not start with a number.
Inputs must have a type, and they can optionally specify a default value. An input with no default value
is a required input, it must be specified when using the step.
@ -333,7 +333,7 @@ run:
#### Specify outputs
Similar to inputs, output names can only use alpha-numeric characters and underscores,
Similar to inputs, output names can only use alphanumeric characters and underscores,
and must not start with a number. Outputs must have a type, and they can optionally specify a default value.
The default value is returned when the step doesn't return the output.
@ -409,7 +409,7 @@ Steps can:
#### Set environment variables
Set environment variables by using the `env` keyword. Environment variable names can only use
alpha-numeric characters and underscores, and must not start with a number.
alphanumeric characters and underscores, and must not start with a number.
Environment variables are made available either to the executable command or to all of the steps
if running a sequence of steps. For example:
@ -494,7 +494,7 @@ exec:
A step declares it runs a sequence of steps using the `steps` keyword. Steps run one at a time
in the order they are defined in the list. This syntax is the same as the `run` keyword.
Steps must have a name consisting only of alpha-numeric characters and underscores, and must not start with a number.
Steps must have a name consisting only of alphanumeric characters and underscores, and must not start with a number.
For example, this step installs Go, then runs a second step that expects Go to already
have been installed:

View File

@ -306,7 +306,7 @@ The value of the variable must:
- Be a single line with no spaces.
- Be 8 characters or longer.
- Not match the name of an existing predefined or custom CI/CD variable.
- Not include non-alpha-numeric characters other than `@`, `_`, `-`, `:`, or `+`.
- Not include non-alphanumeric characters other than `@`, `_`, `-`, `:`, or `+`.
Additionally, if [variable expansion](#prevent-cicd-variable-expansion) is enabled,
the value can contain only:

View File

@ -255,6 +255,47 @@ For CSS properties that are unit-less (e.g `display: flex`) it is okay to use CS
}
```
The preferred way to use `@apply` is to combine multiple CSS classes in a single line or at most two,
like in the example above. This approach keeps the CSS concise and easy to read:
```css
// Good
.my-class {
@apply gl-mt-5 gl-flex gl-items-center;
}
```
Avoid splitting classes across multiple lines, as shown below.
```css
// Avoid
@apply gl-mt-5;
@apply gl-flex;
@apply gl-items-center;
```
The reason for this is that IDE extensions might only be able to detect conflicts when
the CSS Classes are in one line:
```css
// ✅ Conflict detected: 'gl-bg-black' applies the same CSS properties as 'gl-bg-white'.(cssConflict)
@apply gl-bg-white gl-bg-black;
// ❌ No conflict detected
@apply gl-bg-white;
@apply gl-bg-black;
```
The exception to this rule is when working with `!important`. Since `!important` applies to
the entire line, each class that requires it should be applied on its own line. For instance:
```css
@apply gl-flex gl-items-center;
@apply gl-mt-5 #{!important};
```
This ensures that `!important` applies only where intended without affecting other classes in the same line.
## Naming
Filenames should use `snake_case`.

View File

@ -4,12 +4,19 @@ group: Compliance
info: For assistance with this tutorial, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
---
# Tutorial: Create a compliance pipeline
<!--- start_remove The following content will be removed on remove_date: '2025-08-15' -->
# Tutorial: Create a compliance pipeline (deprecated)
DETAILS:
**Tier:** Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159841) in GitLab 17.3
and is planned for removal in 18.0. Use [pipeline execution policy type](../../user/application_security/policies/pipeline_execution_policies.md) instead.
This change is a breaking change. For more information, see the [migration guide](../../user/group/compliance_pipelines.md#pipeline-execution-policies-migration).
You can use [compliance pipelines](../../user/group/compliance_pipelines.md) to ensure specific
compliance-related jobs are run on pipelines for all projects in a group. Compliance pipelines are applied
to projects through [compliance frameworks](../../user/group/compliance_frameworks.md).
@ -180,3 +187,5 @@ Notice the pipeline runs two jobs in a **test** stage:
Congratulations, you've created and configured a compliance pipeline!
See more [example compliance pipeline configurations](../../user/group/compliance_pipelines.md#example-configuration).
<!--- end_remove -->

View File

@ -30,7 +30,8 @@ To upgrade GitLab:
1. Familiarize yourself with the [maintenance policy documentation](../policy/maintenance.md).
1. Read the [release posts](https://about.gitlab.com/releases/categories/releases/) for versions you're passing over.
In particular, deprecations, removals, and important notes on upgrading.
1. Determine what [upgrade path](upgrade_paths.md) you should take. Your upgrade might require multiple upgrades. If
1. Determine what [upgrade path](upgrade_paths.md) you should take. If your upgrade path includes required upgrade stops, you might have to perform multiple
upgrades to move from your current version to your target version. If
relevant, check [OS compatibility with the target GitLab version](../administration/package_information/supported_os.md).
1. Check for [background migrations](background_migrations.md). All migrations must finish running before each upgrade.
You must spread out upgrades between major and minor releases to allow time for background migrations to finish.

View File

@ -4,7 +4,9 @@ group: Compliance
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Compliance pipelines
<!--- start_remove The following content will be removed on remove_date: '2025-08-15' -->
# Compliance pipelines (deprecated)
DETAILS:
**Tier:** Ultimate
@ -398,3 +400,5 @@ This error occurs because the pipeline execution policy includes the project's `
jobs when the jobs have already been declared in the pipeline.
To resolve this error, remove `include.project` from the separate YAML file linked in the pipeline execution policy.
<!--- end_remove -->

View File

@ -44,7 +44,7 @@ module API
namespaces = current_user.admin ? Namespace.all : current_user.namespaces(owned_only: owned_only)
namespaces = namespaces.top_most if params[:top_level_only]
namespaces = namespaces.top_level if params[:top_level_only]
namespaces = namespaces.without_project_namespaces.include_route

View File

@ -9,7 +9,7 @@ module API
namespace_path = ::Packages::Npm.scope_of(params[:package_name])
return unless namespace_path
Namespace.top_most.by_path(namespace_path)
Namespace.top_level.by_path(namespace_path)
end
end

View File

@ -47,6 +47,12 @@ module Gitlab
# No-op, required by the partition manager
end
def partition_name(lower_bound)
suffix = lower_bound&.strftime('%Y%m') || '000000'
"#{table_name}_#{suffix}"
end
private
def desired_partitions
@ -97,7 +103,7 @@ module Gitlab
end
def partition_for(upper_bound:, lower_bound: nil)
TimePartition.new(table_name, lower_bound, upper_bound)
TimePartition.new(table_name, lower_bound, upper_bound, partition_name: partition_name(lower_bound))
end
def pruning_old_partitions?

View File

@ -19,23 +19,17 @@ module Gitlab
new(table, from, to, partition_name: partition_name)
end
attr_reader :table, :from, :to
attr_reader :table, :from, :to, :partition_name
def initialize(table, from, to, partition_name:)
raise ArgumentError, "partition_name required but none given" unless partition_name
def initialize(table, from, to, partition_name: nil)
@table = table.to_s
@from = date_or_nil(from)
@to = date_or_nil(to)
@partition_name = partition_name
end
def partition_name
return @partition_name if @partition_name
suffix = from&.strftime('%Y%m') || '000000'
"#{table}_#{suffix}"
end
def to_sql
from_sql = from ? conn.quote(from.to_date.iso8601) : 'MINVALUE'
to_sql = conn.quote(to.to_date.iso8601)

View File

@ -10,11 +10,12 @@ module Gitlab
SUGGESTION_HEADER = "```suggestion:"
SUGGESTION_FOOTER = "```"
def initialize(diff, path, merge_request)
def initialize(diff, path, merge_request, prepend_text = nil)
@diff = diff
@path = path
@merge_request = merge_request
@project = merge_request.project
@prepend_text = prepend_text
end
def note_attributes_hash
@ -100,7 +101,9 @@ module Gitlab
end
def suggestion
array = [SUGGESTION_HEADER + suggestion_meta]
array = []
array << @prepend_text if @prepend_text
array << [SUGGESTION_HEADER + suggestion_meta]
diff_lines.each do |line|
array << line.text(prefix: false) if line.added? || line.unchanged?

View File

@ -34,7 +34,7 @@ module Gitlab
end
def by_namespace_domain(name)
namespace = Namespace.top_most.by_path(name)
namespace = Namespace.top_level.by_path(name)
return if namespace.blank?

View File

@ -46,7 +46,6 @@ module Users
u.bio = 'System bot that monitors detected vulnerabilities for solutions ' \
'and creates merge requests with the fixes.'
u.name = 'GitLab Security Bot'
u.website_url = Gitlab::Routing.url_helpers.help_page_url('user/application_security/security_bot/index.md')
u.avatar = bot_avatar(image: 'security-bot.png')
u.confirmed_at = Time.zone.now
u.private_profile = true

View File

@ -18989,6 +18989,9 @@ msgstr ""
msgid "Deployments|No deployment history"
msgstr ""
msgid "Deployment|%{releaseName} release notes:"
msgstr ""
msgid "Deployment|A colleague"
msgstr ""
@ -44452,13 +44455,13 @@ msgstr ""
msgid "ProtectedBranch|default"
msgstr ""
msgid "ProtectedEnvironments|%d Approval Rule"
msgid_plural "ProtectedEnvironments|%d Approval Rules"
msgid "ProtectedEnvironments|%d approval rule"
msgid_plural "ProtectedEnvironments|%d approval rules"
msgstr[0] ""
msgstr[1] ""
msgid "ProtectedEnvironments|%d Deployment Rule"
msgid_plural "ProtectedEnvironments|%d Deployment Rules"
msgid "ProtectedEnvironments|%d deployment rule"
msgid_plural "ProtectedEnvironments|%d deployment rules"
msgstr[0] ""
msgstr[1] ""
@ -55446,6 +55449,9 @@ msgstr ""
msgid "The subject will be used as the title of the new issue, and the message will be the description. %{quickActionsLinkStart}Quick actions%{quickActionsLinkEnd} and styling with %{markdownLinkStart}Markdown%{markdownLinkEnd} are supported."
msgstr ""
msgid "The suggested code changes were generated by GitLab Duo Vulnerability Resolution, an AI feature. **Use this feature with caution.** Before you apply the code changes, carefully review and test them, to ensure that they solve the vulnerability.%{line_break}The large language model that generated the suggested code changes was provided with the entire file that contains the vulnerable lines of code. It is not aware of any functionality outside of this context. Please see our [documentation](%{docs_link}) for more information about this feature and leave feedback in this [issue](%{feedback_issue_link})."
msgstr ""
msgid "The tag name can't be changed for an existing release."
msgstr ""

View File

@ -1,4 +1,4 @@
ARG GDK_SHA=d05ca4d8f629cc5c00d7fca73ae663789bcc3410
ARG GDK_SHA=f14f8f3445959818c1272a9767fefa3b282f59c2
# Use tag prefix when running on 'stable' branch to make sure 'protected' image is used which is not deleted by registry cleanup
ARG GDK_BASE_TAG_PREFIX

View File

@ -109,6 +109,10 @@ RSpec.describe 'Database schema',
p_ci_runner_machine_builds: %w[project_id],
ci_runners: %w[sharding_key_id], # This value is meant to populate the partitioned table, no other usage
ci_runner_machines: %w[sharding_key_id], # This value is meant to populate the partitioned table, no other usage
ci_runner_machines_687967fa8a: %w[sharding_key_id], # This field is only used in the partitions, and has the appropriate FKs
instance_type_ci_runner_machines_687967fa8a: %w[sharding_key_id], # This field is always NULL in this partition
group_type_ci_runner_machines_687967fa8a: %w[sharding_key_id], # No need for LFK, rows will be deleted by the FK to ci_runners
project_type_ci_runner_machines_687967fa8a: %w[sharding_key_id], # No need for LFK, rows will be deleted by the FK to ci_runners
ci_runner_projects: %w[runner_id],
ci_runners_e59bb2812d: %w[sharding_key_id], # This field is only used in the partitions, and has the appropriate FKs
instance_type_ci_runners_e59bb2812d: %w[sharding_key_id], # This field is always NULL in this partition

View File

@ -1,4 +1,4 @@
import { GlButton } from '@gitlab/ui';
import { GlBadge, GlButton } from '@gitlab/ui';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PipelineFailedJobsWidget from '~/ci/pipelines_page/components/failure_widget/pipeline_failed_jobs_widget.vue';
@ -38,6 +38,7 @@ describe('PipelineFailedJobsWidget component', () => {
const findFailedJobsButton = () => wrapper.findComponent(GlButton);
const findFailedJobsList = () => wrapper.findAllComponents(FailedJobsList);
const findCrudComponent = () => wrapper.findComponent(CrudComponent);
const findCount = () => wrapper.findComponent(GlBadge);
describe('when there are failed jobs', () => {
beforeEach(() => {
@ -46,7 +47,7 @@ describe('PipelineFailedJobsWidget component', () => {
it('renders the show failed jobs button with correct count', () => {
expect(findFailedJobsButton().exists()).toBe(true);
expect(findFailedJobsButton().text()).toContain(`${defaultProps.failedJobsCount}`);
expect(findCount().text()).toBe(String(defaultProps.failedJobsCount));
});
it('does not render the failed jobs widget', () => {
@ -54,7 +55,7 @@ describe('PipelineFailedJobsWidget component', () => {
});
});
const CSS_BORDER_CLASSES = 'is-collapsed gl-border-white hover:gl-border-gray-100';
const CSS_BORDER_CLASSES = 'is-collapsed gl-border-transparent hover:gl-border-default';
describe('when the job button is clicked', () => {
beforeEach(async () => {
@ -98,11 +99,11 @@ describe('PipelineFailedJobsWidget component', () => {
it('updates the job count', async () => {
const newJobCount = 12;
expect(findFailedJobsButton().text()).toContain(String(defaultProps.failedJobsCount));
expect(findCount().text()).toBe(String(defaultProps.failedJobsCount));
await wrapper.setProps({ failedJobsCount: newJobCount });
expect(findFailedJobsButton().text()).toContain(String(newJobCount));
expect(findCount().text()).toBe(String(newJobCount));
});
});
@ -114,11 +115,11 @@ describe('PipelineFailedJobsWidget component', () => {
it('updates the job count', async () => {
const newJobCount = 12;
expect(findFailedJobsButton().text()).toContain(String(defaultProps.failedJobsCount));
expect(findCount().text()).toBe(String(defaultProps.failedJobsCount));
await findFailedJobsList().at(0).vm.$emit('failed-jobs-count', newJobCount);
expect(findFailedJobsButton().text()).toContain(String(newJobCount));
expect(findCount().text()).toBe(String(newJobCount));
});
});
});

View File

@ -153,7 +153,7 @@ describe('~/deployments/components/show_deployment.vue', () => {
return createComponent();
});
it('should set up a toggle visiblity hook on mount', () => {
it('should set up a toggle visibility hook on mount', () => {
expect(toggleQueryPollingByVisibility).toHaveBeenCalled();
});
});

View File

@ -107,17 +107,23 @@ RSpec.describe API::Ci::Helpers::Runner, feature_category: :runner do
expect(current_runner_manager.contacted_at).to eq 1.hour.ago
end
context 'when a runner manager with nil runner_type and sharding_key_id already exists' do
context 'when a runner manager with nil sharding_key_id already exists' do
before do
existing_runner_manager.update_columns(runner_type: nil, sharding_key_id: nil)
Ci::ApplicationRecord.connection.execute <<~SQL
ALTER TABLE group_type_ci_runner_machines_687967fa8a
DROP CONSTRAINT IF EXISTS check_sharding_key_id_nullness
SQL
existing_runner_manager.update_columns(sharding_key_id: nil)
end
it 'reuses and updates existing runner manager', :aggregate_failures do
it 'reuses existing runner manager', :aggregate_failures do
expect { current_runner_manager }.not_to raise_error
expect(current_runner_manager).not_to be_nil
expect(current_runner_manager).to eq existing_runner_manager
expect(current_runner_manager.reload.contacted_at).to eq 1.hour.ago
expect(current_runner_manager.runner_type).to be_nil
expect(current_runner_manager.runner_type).to eq runner.runner_type
expect(current_runner_manager.sharding_key_id).to be_nil
end
end

View File

@ -78,34 +78,34 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
subject { described_class.new(model, partitioning_key, retain_for: 1.month).missing_partitions }
it 'does not include the missing partition from May 2020 because it would be dropped' do
expect(subject).not_to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01'))
expect(subject).not_to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: "#{model.table_name}_202005"))
end
it 'detects the missing partition for 1 month ago (July 2020)' do
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-07-01', '2020-08-01'))
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-07-01', '2020-08-01', partition_name: "#{model.table_name}_202007"))
end
end
it 'detects the gap and the missing partition in May 2020' do
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01'))
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: "#{model.table_name}_202005"))
end
it 'detects the missing partitions at the end of the range and expects a partition for July 2020' do
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-07-01', '2020-08-01'))
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-07-01', '2020-08-01', partition_name: "#{model.table_name}_202007"))
end
it 'detects the missing partitions at the end of the range and expects a partition for August 2020' do
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01'))
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01', partition_name: "#{model.table_name}_202008"))
end
it 'creates partitions 6 months out from now (Sep 2020 through Feb 2021)' do
expect(subject).to include(
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01')
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01', partition_name: "#{model.table_name}_202009"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01', partition_name: "#{model.table_name}_202010"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01', partition_name: "#{model.table_name}_202011"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01', partition_name: "#{model.table_name}_202012"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01', partition_name: "#{model.table_name}_202101"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01', partition_name: "#{model.table_name}_202102")
)
end
@ -129,7 +129,8 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
it 'detects exactly the set of partitions from June 2020 to March 2021' do
months = %w[2020-07-01 2020-08-01 2020-09-01 2020-10-01 2020-11-01 2020-12-01 2021-01-01 2021-02-01 2021-03-01]
expected = months[..-2].zip(months.drop(1)).map do |(from, to)|
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, from, to)
partition_name = "#{model.table_name}_#{Date.parse(from).strftime('%Y%m')}"
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, from, to, partition_name: partition_name)
end
expect(subject).to match_array(expected)
@ -137,21 +138,21 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
end
it 'detects the missing catch-all partition at the beginning' do
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-08-01'))
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-08-01', partition_name: "#{model.table_name}_000000"))
end
it 'detects the missing partition for today and expects a partition for August 2020' do
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01'))
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01', partition_name: "#{model.table_name}_202008"))
end
it 'creates partitions 6 months out from now (Sep 2020 through Feb 2021' do
expect(subject).to include(
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01'),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01')
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01', partition_name: "#{model.table_name}_202009"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01', partition_name: "#{model.table_name}_202010"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01', partition_name: "#{model.table_name}_202011"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01', partition_name: "#{model.table_name}_202012"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01', partition_name: "#{model.table_name}_202101"),
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01', partition_name: "#{model.table_name}_202102")
)
end
@ -174,7 +175,7 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
end
it 'detects a missing catch-all partition to add before the existing partition' do
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-06-01'))
expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-06-01', partition_name: "#{model.table_name}_000000"))
end
end
end
@ -400,4 +401,30 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
})
end
end
describe '#partition_name' do
let(:model) { double('model', table_name: table_name) }
let(:partitioning_key) { double }
let(:table_name) { '_test_partitioned_test' }
let(:from) { Date.parse('2020-04-01 00:00:00') }
let(:to) { Date.parse('2020-05-01 00:00:00') }
subject(:partition_name) { described_class.new(model, partitioning_key).partition_name(from) }
it 'uses table_name as prefix' do
expect(partition_name).to start_with(table_name)
end
it 'uses Year-Month (from) as suffix' do
expect(partition_name).to end_with("_202004")
end
context 'without from date' do
let(:from) { nil }
it 'uses 000000 as suffix for first partition' do
expect(partition_name).to end_with("_000000")
end
end
end
end

View File

@ -43,45 +43,14 @@ RSpec.describe Gitlab::Database::Partitioning::TimePartition, feature_category:
end
end
describe '#partition_name' do
subject { described_class.new(table, from, to, partition_name: partition_name).partition_name }
let(:table) { 'foo' }
let(:from) { '2020-04-01 00:00:00' }
let(:to) { '2020-05-01 00:00:00' }
let(:partition_name) { nil }
it 'uses table as prefix' do
expect(subject).to start_with(table)
end
it 'uses Year-Month (from) as suffix' do
expect(subject).to end_with("_202004")
end
context 'without from date' do
let(:from) { nil }
it 'uses 000000 as suffix for first partition' do
expect(subject).to end_with("_000000")
end
end
context 'with partition name explicitly given' do
let(:partition_name) { "foo_bar" }
it 'uses given partition name' do
expect(subject).to eq(partition_name)
end
end
end
describe '#to_sql' do
subject { described_class.new(table, from, to).to_sql }
subject { described_class.new(table, from, to, partition_name: partition_name).to_sql }
let(:table) { 'foo' }
let(:from) { '2020-04-01 00:00:00' }
let(:to) { '2020-05-01 00:00:00' }
let(:suffix) { '202004' }
let(:partition_name) { "#{table}_#{suffix}" }
it 'transforms to a CREATE TABLE statement' do
expect(subject).to eq(<<~SQL)
@ -93,6 +62,7 @@ RSpec.describe Gitlab::Database::Partitioning::TimePartition, feature_category:
context 'without from date' do
let(:from) { nil }
let(:suffix) { '000000' }
it 'uses MINVALUE instead' do
expect(subject).to eq(<<~SQL)
@ -140,8 +110,8 @@ RSpec.describe Gitlab::Database::Partitioning::TimePartition, feature_category:
expect_inequality(make_new, make_new(partition_name: 'different'))
end
it 'nil partition_name is ignored if auto-generated matches' do
expect_equality(make_new, make_new(partition_name: nil))
it 'raises en error if partition_name is nil' do
expect { make_new(partition_name: nil) }.to raise_error(ArgumentError, "partition_name required but none given")
end
end
@ -150,24 +120,24 @@ RSpec.describe Gitlab::Database::Partitioning::TimePartition, feature_category:
it 'sorts by partition name, i.e. by month - MINVALUE partition first' do
partitions = [
described_class.new(table, '2020-04-01', '2020-05-01'),
described_class.new(table, '2020-02-01', '2020-03-01'),
described_class.new(table, nil, '2020-02-01'),
described_class.new(table, '2020-03-01', '2020-04-01')
described_class.new(table, '2020-04-01', '2020-05-01', partition_name: "#{table}_202004"),
described_class.new(table, '2020-02-01', '2020-03-01', partition_name: "#{table}_202002"),
described_class.new(table, nil, '2020-02-01', partition_name: "#{table}_000000"),
described_class.new(table, '2020-03-01', '2020-04-01', partition_name: "#{table}_202003")
]
expect(partitions.sort).to eq(
[
described_class.new(table, nil, '2020-02-01'),
described_class.new(table, '2020-02-01', '2020-03-01'),
described_class.new(table, '2020-03-01', '2020-04-01'),
described_class.new(table, '2020-04-01', '2020-05-01')
described_class.new(table, nil, '2020-02-01', partition_name: "#{table}_000000"),
described_class.new(table, '2020-02-01', '2020-03-01', partition_name: "#{table}_202002"),
described_class.new(table, '2020-03-01', '2020-04-01', partition_name: "#{table}_202003"),
described_class.new(table, '2020-04-01', '2020-05-01', partition_name: "#{table}_202004")
])
end
it 'returns nil for partitions of different tables' do
one = described_class.new('foo', '2020-02-01', '2020-03-01')
two = described_class.new('bar', '2020-02-01', '2020-03-01')
one = described_class.new('foo', '2020-02-01', '2020-03-01', partition_name: 'foo_202002')
two = described_class.new('bar', '2020-02-01', '2020-03-01', partition_name: 'bar_202002')
expect(one.<=>(two)).to be_nil
end

View File

@ -25,7 +25,8 @@ RSpec.describe Gitlab::Diff::MergeRequestSuggestion, feature_category: :vulnerab
end
let_it_be(:diff) { File.read(File.join(fixtures_folder, 'input.diff')) }
let(:mr_suggestion) { described_class.new(diff, filepath, merge_request) }
let_it_be(:prepend_text) { nil }
let(:mr_suggestion) { described_class.new(diff, filepath, merge_request, prepend_text) }
subject(:attributes_hash) { mr_suggestion.note_attributes_hash }
@ -34,6 +35,8 @@ RSpec.describe Gitlab::Diff::MergeRequestSuggestion, feature_category: :vulnerab
end
context 'when a valid diff is supplied' do
let_it_be(:suggestion) { File.read(File.join(fixtures_folder, 'suggestion.md')) }
it 'returns a correctly formatted suggestion request payload' do
position_payload = {
position_type: 'text',
@ -51,7 +54,15 @@ RSpec.describe Gitlab::Diff::MergeRequestSuggestion, feature_category: :vulnerab
expect(attributes_hash[:noteable_type]).to eq(MergeRequest)
expect(attributes_hash[:noteable_id]).to eq(merge_request.id)
expect(attributes_hash[:position]).to eq(position_payload)
expect(attributes_hash[:note]).to eq(File.read(File.join(fixtures_folder, 'suggestion.md')))
expect(attributes_hash[:note]).to eq(suggestion)
end
context 'when a prepend text is present' do
let_it_be(:prepend_text) { 'prepend text' }
it 'returns a correctly formatted suggestion request payload' do
expect(attributes_hash[:note]).to eq("#{prepend_text}\n#{suggestion}")
end
end
end

View File

@ -216,6 +216,12 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2) }
end
context 'with numeric id for single runner' do
let(:runner_arg) { runner_a.id }
it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2) }
end
context 'with multiple runners' do
let(:runner_arg) { [runner_a, runner_b] }

View File

@ -540,6 +540,12 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
end
end
describe '.top_level' do
it 'includes correct namespaces' do
expect(described_class.top_level).to match_array([namespace, namespace1, namespace2])
end
end
describe '.by_root_id' do
it 'returns correct namespaces' do
expect(described_class.by_root_id(namespace1.id)).to match_array([namespace1, namespace1sub])
@ -1458,26 +1464,33 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
end
end
describe '.top_most' do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
describe '.find_by_path_or_name' do
let_it_be(:namespace) { create(:namespace, name: 'WoW', path: 'woW') }
subject { described_class.top_most.ids }
it 'only contains root namespaces' do
is_expected.to contain_exactly(group.id, namespace.id)
end
it { expect(described_class.find_by_path_or_name('wow')).to eq(namespace) }
it { expect(described_class.find_by_path_or_name('WOW')).to eq(namespace) }
it { expect(described_class.find_by_path_or_name('unknown')).to be_nil }
end
describe '.find_by_path_or_name' do
before do
@namespace = create(:namespace, name: 'WoW', path: 'woW')
describe '.find_top_level' do
# Due to the top level scope of this spec having a create of namespace, we'll avoid possible future flakiness here.
let(:namespace) { nil }
subject { described_class.find_top_level }
context 'when there are top level namespaces' do
# Order of creation matters here as we are only taking the first result and the single
# threaded FIFO order of creation in specs.
let_it_be(:sub_group) { create(:group, :nested) }
let_it_be(:another_parent_namespace) { create(:group) }
let(:parent_namespace) { sub_group.parent }
it { is_expected.to eq(parent_namespace) }
end
it { expect(described_class.find_by_path_or_name('wow')).to eq(@namespace) }
it { expect(described_class.find_by_path_or_name('WOW')).to eq(@namespace) }
it { expect(described_class.find_by_path_or_name('unknown')).to eq(nil) }
context 'when there are no top level namespaces' do
it { is_expected.to be_nil }
end
end
describe ".clean_path" do