Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-07-24 06:08:43 +00:00
parent fb8ebc84b8
commit 9673d4228d
39 changed files with 399 additions and 245 deletions

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlIcon, GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { GlIcon, GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { s__, __ } from '~/locale';
import { captureException } from '~/sentry/sentry_browser_wrapper';
@ -21,7 +21,6 @@ export default {
ConfirmModal,
KeysPanel,
NavigationTabs,
GlButton,
GlIcon,
GlLoadingIcon,
GlPagination,
@ -90,7 +89,6 @@ export default {
},
i18n: {
loading: s__('DeployKeys|Loading deploy keys'),
addButton: s__('DeployKeys|Add new key'),
prevPage: __('Go to previous page'),
nextPage: __('Go to next page'),
next: __('Next'),
@ -172,7 +170,7 @@ export default {
<template>
<div class="deploy-keys">
<confirm-modal :visible="confirmModalVisible" @remove="removeKey" @cancel="cancel" />
<div class="gl-new-card-header gl-align-items-center gl-py-0 gl-pl-0">
<div class="gl-items-center gl-py-0 gl-pl-0">
<div class="top-area scrolling-tabs-container inner-page-scroll-tabs gl-border-b-0">
<div class="fade-left">
<gl-icon name="chevron-lg-left" :size="12" />
@ -188,16 +186,6 @@ export default {
@onChangeTab="onChangeTab"
/>
</div>
<div class="gl-new-card-actions">
<gl-button
size="small"
class="js-toggle-button js-toggle-content"
data-testid="add-new-deploy-key-button"
>
{{ $options.i18n.addButton }}
</gl-button>
</div>
</div>
<gl-loading-icon
v-if="$apollo.queries.deployKeys.loading"

View File

@ -83,7 +83,7 @@ export default {
<slot v-if="$scopedSlots.title" name="title"></slot>
<template v-else>{{ title }}</template>
</component>
<p class="gl-text-secondary gl-m-0"><slot name="description"></slot></p>
<p class="gl-text-subtle gl-m-0"><slot name="description"></slot></p>
</div>
<div class="gl-flex-shrink-0 gl-px-2">
<gl-button

View File

@ -18,11 +18,15 @@ export default {
<section class="settings-section">
<div class="settings-sticky-header">
<div class="settings-sticky-header-inner">
<h2 class="gl-heading-2 !gl-mb-2">
<h2 class="gl-heading-2 !gl-mb-2" data-testid="settings-section-heading">
<slot v-if="$scopedSlots.heading" name="heading"></slot>
<template v-else>{{ heading }}</template>
</h2>
<p v-if="$scopedSlots.description || description" class="gl-text-secondary gl-mb-3">
<p
v-if="$scopedSlots.description || description"
class="gl-text-subtle gl-mb-3"
data-testid="settings-section-description"
>
<slot v-if="$scopedSlots.description" name="description"></slot>
<template v-else>{{ description }}</template>
</p>

View File

@ -1,7 +1,7 @@
<script>
import { isEmpty } from 'lodash';
import { GlAlert, GlButton, GlTooltipDirective, GlEmptyState } from '@gitlab/ui';
import noAccessSvg from '@gitlab/svgs/dist/illustrations/analytics/no-access.svg?raw';
import noAccessSvg from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { s__ } from '~/locale';
import { getParameterByName, updateHistory, setUrlParams } from '~/lib/utils/url_utility';
@ -249,9 +249,6 @@ export default {
workItemIconName() {
return this.workItem.workItemType?.iconName;
},
noAccessSvgPath() {
return `data:image/svg+xml;utf8,${encodeURIComponent(noAccessSvg)}`;
},
hasDescriptionWidget() {
return this.isWidgetPresent(WIDGET_TYPE_DESCRIPTION);
},
@ -480,6 +477,7 @@ export default {
},
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
WORKSPACE_PROJECT,
noAccessSvg,
};
</script>
@ -528,8 +526,7 @@ export default {
v-else-if="error"
:title="$options.i18n.fetchErrorTitle"
:description="error"
:svg-path="noAccessSvgPath"
:svg-height="null"
:svg-path="$options.noAccessSvg"
/>
<div v-else data-testid="detail-wrapper">
<div class="gl-block sm:!gl-flex gl-items-start gl-flex-row gl-gap-3">

View File

@ -5,25 +5,25 @@
%h2.gl-text-base.gl-font-bold.gl-leading-24.gl-inline-flex.gl-gap-3.gl-m-0{ data: { testid: 'crud-title' } }
= @title
- if @count
%span.gl-inline-flex.gl-items-center.gl-gap-2.gl-text-sm.gl-text-secondary{ data: { testid: 'crud-count' } }
%span.gl-inline-flex.gl-items-center.gl-gap-2.gl-text-sm.gl-text-subtle{ data: { testid: 'crud-count' } }
- if @icon
= sprite_icon(@icon)
%span{ class: @count_class }
= @count
- if description? || @description
.gl-text-sm.gl-text-secondary.gl-mt-1.gl-mb-0{ data: { testid: 'crud-description' } }
.gl-text-sm.gl-text-subtle.gl-mt-1.gl-mb-0{ data: { testid: 'crud-description' } }
= description || @description
.gl-flex.gl-gap-3.gl-items-baseline{ data: { testid: 'crud-actions' } }
- if @toggle_text
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content', data: { testid: 'crud-action-toggle' } }) do
= render Pajamas::ButtonComponent.new(size: :small, button_options: button_options_attrs) do
= @toggle_text
= actions
- if form?
.gl-p-5.gl-pt-4.gl-bg-default.gl-border-b.gl-border-default{ class: ('gl-hidden js-toggle-content' if @toggle_text), data: { testid: 'crud-form' } }
.gl-p-5.gl-pt-4.gl-bg-default.gl-border-b.gl-border-default{ form_options_attrs }
= form
.crud-body.gl-mx-5.gl-my-4{ class: ('gl-rounded-b-base' unless footer), data: { testid: 'crud-body' } }
.crud-body.gl-mx-5.gl-my-4{ body_options_attrs }
= body
- if footer?

View File

@ -8,7 +8,14 @@ module Layouts
# @param [String] icon
# @param [String] toggle_text
# @param [Hash] options
def initialize(title, description: nil, count: nil, count_class: nil, icon: nil, toggle_text: nil, options: {})
# @param [Hash] body_options
# @param [Hash] form_options
# @param [Hash] toggle_options
def initialize(
title, description: nil, count: nil, count_class: nil, icon: nil,
toggle_text: nil, options: {}, body_options: {}, form_options: {},
toggle_options: {}
)
@title = title
@description = description
@count = count
@ -16,6 +23,9 @@ module Layouts
@icon = icon
@toggle_text = toggle_text
@options = options
@body_options = body_options
@form_options = form_options
@toggle_options = toggle_options
end
renders_one :description
@ -25,6 +35,42 @@ module Layouts
renders_one :footer
renders_one :pagination
def body_options_attrs
default_testid = 'crud-body'
default_classes = [
('gl-rounded-b-base' unless footer)
]
@body_options.merge(default_attrs(@body_options, default_testid, default_classes))
end
def button_options_attrs
default_testid = 'crud-action-toggle'
default_classes = ['js-toggle-button js-toggle-content']
@toggle_options.merge(default_attrs(@toggle_options, default_testid, default_classes))
end
def form_options_attrs
default_testid = 'crud-form'
default_classes = [
('js-toggle-content' if @toggle_text),
('gl-hidden' if @toggle_text && !@form_options[:class])
]
@form_options.merge(default_attrs(@form_options, default_testid, default_classes))
end
delegate :sprite_icon, to: :helpers
private
def default_attrs(attrs, default_testid = nil, default_classes = [])
data = attrs[:data] || {}
data[:testid] = default_testid unless data[:testid]
classes = attrs[:class] || ""
{
data: data,
class: "#{classes} #{default_classes.join(' ')}"
}
end
end
end

View File

@ -1,10 +1,10 @@
%section{ class: section_classes, id: @id, data: (@testid ? { testid: @testid } : {}) }
.gl-flex.gl-justify-between.gl-items-start.gl-pt-5
.gl-flex.gl-justify-between.gl-items-start.gl-gap-x-3.gl-pt-5
.gl-grow
%h2{ class: title_classes }
= heading || @heading
- if description || @description
%p.gl-text-secondary.gl-m-0
%p.gl-text-subtle.gl-m-0
= description || @description
.gl-shrink-0.gl-px-2
= render Pajamas::ButtonComponent.new(button_options: @button_options.merge(class: 'gl-min-w-12 js-settings-toggle')) do

View File

@ -4,7 +4,7 @@
%h2.gl-heading-2{ class: '!gl-mb-2' }
= heading || @heading
- if description || @description
%p.gl-text-secondary.gl-mb-3
%p.gl-text-subtle.gl-mb-3
= description || @description
%div{ data: { testid: 'settings-section-body' } }
= body

View File

@ -46,10 +46,20 @@ class Namespace
%w[namespaces], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424279'
) do
Namespace.transaction do
@root.lock!("FOR NO KEY UPDATE")
lock_options = 'FOR NO KEY UPDATE'
lock_options += ' NOWAIT' if Feature.enabled?(:sync_traversal_ids_nowait, Feature.current_request)
@root.lock!(lock_options)
Namespace.connection.exec_query(sql)
end
end
rescue ActiveRecord::LockWaitTimeout => e
if e.message.starts_with? 'PG::LockNotAvailable'
db_nowait_counter.increment(source: 'Namespace#sync_traversal_ids!')
end
raise
rescue ActiveRecord::Deadlocked
db_deadlock_counter.increment(source: 'Namespace#sync_traversal_ids!')
raise
@ -95,6 +105,10 @@ class Namespace
.find_by(parent_id: nil)
end
def db_nowait_counter
Gitlab::Metrics.counter(:db_nowait, 'Counts the times we triggered NOWAIT on a database lock operation')
end
def db_deadlock_counter
Gitlab::Metrics.counter(:db_deadlock, 'Counts the times we have deadlocked in the database')
end

View File

@ -1,12 +1,9 @@
%section.settings.as-default-branch-name.no-animate#js-default-branch-name{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Default branch')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= s_('GroupSettings|Set the initial name and protections for the default branch of new repositories created in the group.')
.settings-content
= render ::Layouts::SettingsBlockComponent.new(_('Default branch'),
id: 'js-default-branch-name',
expanded: expanded_by_default?) do |c|
- c.with_description do
= s_('GroupSettings|Set the initial name and protections for the default branch of new repositories created in the group.')
- c.with_body do
= gitlab_ui_form_for @group, url: group_path(@group, anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f|
= form_errors(@group)
- fallback_branch_name = "<code>#{Gitlab::DefaultBranch.value(object: @group)}</code>"

View File

@ -1,14 +1,11 @@
- expanded = expanded_by_default?
%section.settings.no-animate#branch-defaults-settings{ class: ('expanded' if expanded) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Branch defaults')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= s_('ProjectSettings|Select the default branch for this project, and configure the template for branch names.')
.settings-content
= render ::Layouts::SettingsBlockComponent.new(_('Branch defaults'),
id: 'branch-defaults-settings',
expanded: expanded) do |c|
- c.with_description do
= s_('ProjectSettings|Select the default branch for this project, and configure the template for branch names.')
- c.with_body do
- url = namespace_project_settings_repository_path(@project.namespace, @project)
= gitlab_ui_form_for @project, url: url, method: :put, html: { multipart: true, class: "issue-settings-form js-issue-settings-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-issue-settings' }

View File

@ -3,14 +3,12 @@
- show_status_checks = @project.licensed_feature_available?(:external_status_checks)
- show_approvers = @project.licensed_feature_available?(:merge_request_approvers)
%section.settings.no-animate#branch-rules{ class: ('expanded' if expanded), data: { testid: 'branch-rules-content' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Branch rules')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Define rules for who can push, merge, and the required approvals for each branch.')
= link_to(_('Leave feedback.'), 'https://gitlab.com/gitlab-org/gitlab/-/issues/388149', target: '_blank', rel: 'noopener noreferrer')
.settings-content
= render ::Layouts::SettingsBlockComponent.new(_('Branch rules'),
id: 'branch-rules',
testid: 'branch-rules-content',
expanded: expanded) do |c|
- c.with_description do
= _('Define rules for who can push, merge, and the required approvals for each branch.')
= link_to(_('Leave feedback.'), 'https://gitlab.com/gitlab-org/gitlab/-/issues/388149', target: '_blank', rel: 'noopener noreferrer')
- c.with_body do
#js-branch-rules{ data: { project_path: @project.full_path, branch_rules_path: project_settings_repository_branch_rules_path(@project), show_code_owners: show_code_owners.to_s, show_status_checks: show_status_checks.to_s, show_approvers: show_approvers.to_s } }

View File

@ -1,13 +1,11 @@
- expanded = expanded_by_default?
%section.settings.no-animate#cleanup{ class: ('expanded' if expanded) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Repository maintenance')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary.gl-pb-3
= s_('ProjectMaintenance|Manage repository storage and cleanup.')
.settings-content
= render ::Layouts::SettingsBlockComponent.new(_('Repository maintenance'),
id: 'cleanup',
expanded: expanded) do |c|
- c.with_description do
= s_('ProjectMaintenance|Manage repository storage and cleanup.')
- c.with_body do
= render Pajamas::AlertComponent.new(variant: :danger, alert_options: { class: 'gl-mb-5' }, dismissible: false) do |c|
- c.with_body do
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe

View File

@ -1,56 +1,47 @@
- expanded = expanded_by_default?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
- mirror_settings_enabled = can?(current_user, :admin_remote_mirror, @project)
- mirror_settings_class = "#{'expanded' if expanded} #{'js-mirror-settings' if mirror_settings_enabled}".strip
- mirror_settings_class = "#{'js-mirror-settings' if mirror_settings_enabled}".strip
%section.settings.project-mirror-settings.no-animate#js-push-remote-settings{ class: mirror_settings_class, data: { testid: 'mirroring-repositories-settings-content' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Mirroring repositories')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
= link_to _('How do I mirror repositories?'), help_page_path('user/project/repository/mirror/index'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c|
- c.with_header do
.gl-new-card-title-wrapper
%h5.gl-new-card-title
= _('Mirrored repositories')
.gl-new-card-count
= sprite_icon('earth', css_class: 'gl-mr-2')
%span.js-mirrored-repo-count
= mirrored_repositories_count
.gl-new-card-actions
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: "js-toggle-button js-toggle-content", data: { testid: 'add-new-mirror' } }) do
= _('Add new')
= render ::Layouts::SettingsBlockComponent.new(_('Mirroring repositories'),
id: 'js-push-remote-settings',
testid: 'mirroring-repositories-settings-content',
css_class: "project-mirror-settings #{mirror_settings_class}",
expanded: expanded) do |c|
- c.with_description do
= _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
= link_to _('How do I mirror repositories?'), help_page_path('user/project/repository/mirror/index'), target: '_blank', rel: 'noopener noreferrer'
- c.with_body do
= render ::Layouts::CrudComponent.new(_('Mirrored repositories'),
icon: 'earth',
count: mirrored_repositories_count,
toggle_text: _('Add new'),
toggle_options: { data: { testid: 'add-new-mirror' } }) do |c|
- c.with_body do
- if mirror_settings_enabled
.gl-new-card-add-form.gl-m-3.gl-mb-4.gl-display-none.js-toggle-content
%h4.gl-mt-0
= s_('Profiles|Add new mirror repository')
= gitlab_ui_form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
%div= form_errors(@project)
.form-group.has-feedback
= label_tag :url, _('Git repository URL'), class: 'label-light'
= text_field_tag :url, nil, class: 'form-control gl-form-input js-mirror-url js-repo-url gl-form-input-xl', placeholder: _('Input the remote repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password', data: { testid: 'mirror-repository-url-field' }
= render 'projects/mirrors/instructions'
= render 'projects/mirrors/mirror_repos_form', f: f
= render 'projects/mirrors/branch_filter'
= f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { testid: 'mirror-repository-button' }
= render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do
= _('Cancel')
- else
- unless mirror_settings_enabled
= render Pajamas::AlertComponent.new(dismissible: false) do |c|
- c.with_body do
= _('Mirror settings are only available to GitLab administrators.')
= render 'projects/mirrors/mirror_repos_list'
- c.with_form do
- if mirror_settings_enabled
%h4.gl-mt-0
= s_('Profiles|Add new mirror repository')
= gitlab_ui_form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
%div= form_errors(@project)
.form-group.has-feedback
= label_tag :url, _('Git repository URL'), class: 'label-light'
= text_field_tag :url, nil, class: 'form-control gl-form-input js-mirror-url js-repo-url gl-form-input-xl', placeholder: _('Input the remote repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password', data: { testid: 'mirror-repository-url-field' }
= render 'projects/mirrors/instructions'
= render 'projects/mirrors/mirror_repos_form', f: f
= render 'projects/mirrors/branch_filter'
= f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { testid: 'mirror-repository-button' }
= render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do
= _('Cancel')

View File

@ -2,7 +2,7 @@
.table-responsive.gl-mb-0
- if !@project.mirror? && @project.remote_mirrors.count == 0
.gl-new-card-empty.gl-px-5.gl-py-4= _('There are currently no mirrored repositories.')
.gl-text-subtle= _('There are currently no mirrored repositories.')
- else
%table.table.b-table.gl-table.b-table-stacked-md
%thead.gl-hidden.md:gl-table-header-group

View File

@ -1,35 +1,25 @@
- expanded = expanded_by_default?
%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded), data: { testid: 'protected-tag-settings-content' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_("ProtectedTag|Protected tags")
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= s_("ProtectedTag|Limit access to creating and updating tags.")
= link_to s_("ProtectedTag|What are protected tags?"), help_page_path("user/project/protected_tags")
.settings-content
= render ::Layouts::SettingsBlockComponent.new(s_("ProtectedTag|Protected tags"),
id: 'js-protected-tags-settings',
testid: 'protected-tag-settings-content',
expanded: expanded) do |c|
- c.with_description do
= s_("ProtectedTag|Limit access to creating and updating tags.")
= link_to s_("ProtectedTag|What are protected tags?"), help_page_path("user/project/protected_tags")
- c.with_body do
%p.gl-text-secondary
= s_("ProtectedTag|By default, protected tags restrict who can modify the tag.")
= link_to s_("ProtectedTag|Learn more."), help_page_path("user/project/protected_tags", anchor: "who-can-modify-a-protected-tag")
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c|
- c.with_header do
.gl-new-card-title-wrapper
%h3.gl-new-card-title
= _('Protected tags')
.gl-new-card-count
= sprite_icon('tag', css_class: 'gl-mr-2')
= @protected_tags_count
- if can? current_user, :admin_project, @project
.gl-new-card-actions
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: "js-toggle-button js-toggle-content" }) do
= _('Add tag')
= render ::Layouts::CrudComponent.new(_('Protected tags'),
icon: 'tag',
count: @protected_tags_count,
toggle_text: _('Add tag')) do |c|
- c.with_body do
- if can? current_user, :admin_project, @project
.gl-new-card-add-form.gl-m-3.gl-mb-4.gl-display-none.js-toggle-content
%h4.gl-mt-0
= _('Protect a tag')
= yield :create_protected_tag
= yield :tag_list
- c.with_form do
- if can? current_user, :admin_project, @project
%h4.gl-mt-0
= _('Protect a tag')
= yield :create_protected_tag

View File

@ -1,6 +1,6 @@
.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.gl-new-card-empty.gl-px-5.gl-py-4
.gl-text-subtle
= s_('ProtectedBranch|No tags are protected.')
- else
- can_admin_project = can?(current_user, :admin_project, @project)

View File

@ -1,6 +1,6 @@
.protected-branches-list.js-protected-branches-list{ data: { testid: 'protected-branches-list' } }
- if @protected_branches.empty?
%p.gl-new-card-empty.gl-px-5.gl-py-4.js-toggle-content
%p.gl-text-subtle
= s_("ProtectedBranch|There are currently no protected branches, to protect a branch start by creating a new one above.")
- else
.flash-container

View File

@ -1,16 +1,14 @@
- can_admin_entity = protected_branch_can_admin_entity?(protected_branch_entity)
- expanded = expanded_by_default?
%section.settings.no-animate#js-protected-branches-settings{ class: ('expanded' if expanded), data: { testid: 'protected-branches-settings-content' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_("ProtectedBranch|Protected branches")
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= s_("ProtectedBranch|Keep stable branches secure and force developers to use merge requests.")
= link_to s_("ProtectedBranch|What are protected branches?"), help_page_path("user/project/protected_branches")
.settings-content
= render ::Layouts::SettingsBlockComponent.new(s_("ProtectedBranch|Protected branches"),
id: 'js-protected-branches-settings',
testid: 'protected-branches-settings-content',
expanded: expanded) do |c|
- c.with_description do
= s_("ProtectedBranch|Keep stable branches secure and force developers to use merge requests.")
= link_to s_("ProtectedBranch|What are protected branches?"), help_page_path("user/project/protected_branches")
- c.with_body do
.js-alert-protected-branch-created-container.gl-mt-5
= render Pajamas::AlertComponent.new(variant: :warning,
@ -20,25 +18,20 @@
= s_("ProtectedBranch|Giving merge rights to a protected branch also gives elevated permissions for certain CI/CD features.")
= link_to s_("ProtectedBranch|What are the security implications?"), help_page_path('ci/pipelines/index', anchor: 'pipeline-security-on-protected-branches'), target: '_blank', rel: 'noopener noreferrer'
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c|
- c.with_header do
.gl-new-card-title-wrapper.gl-justify-content-space-between
%h3.gl-new-card-title
= s_("ProtectedBranch|Protected branches")
.gl-new-card-count
= sprite_icon('branch', css_class: 'gl-mr-2')
%span= @protected_branches.size
.gl-new-card-actions
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content', data: { testid: 'add-protected-branch-button' } }) do
= _('Add protected branch')
.gl-new-card-description.gl-mt-2.gl-sm-mt-0
= s_("ProtectedBranch|By default, protected branches restrict who can modify the branch.")
= link_to s_("ProtectedBranch|Learn more."), help_page_path("user/project/protected_branches", anchor: "who-can-modify-a-protected-branch")
= render ::Layouts::CrudComponent.new(s_("ProtectedBranch|Protected branches"),
icon: 'branch',
count: @protected_branches.size,
toggle_text: _('Add protected branch'),
toggle_options: { data: { testid: 'add-protected-branch-button' } }) do |c|
- c.with_description do
= s_("ProtectedBranch|By default, protected branches restrict who can modify the branch.")
= link_to s_("ProtectedBranch|Learn more."), help_page_path("user/project/protected_branches", anchor: "who-can-modify-a-protected-branch")
- c.with_body do
- if can_admin_entity
.gl-new-card-add-form.gl-m-3.gl-display-none.js-toggle-content
= content_for :create_protected_branch
= content_for :branches_list
= paginate @protected_branches, theme: 'gitlab'
- c.with_form do
- if can_admin_entity
= content_for :create_protected_branch
- c.with_pagination do
= paginate @protected_branches, theme: 'gitlab'

View File

@ -1,21 +1,25 @@
- expanded = expanded_by_default?
%section.rspec-deploy-keys-settings.settings.no-animate#js-deploy-keys-settings{ class: ('expanded' if expanded), data: { testid: 'deploy-keys-settings-content' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Deploy keys')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
- link = link_to('', help_page_path('user/project/deploy_keys/index'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(_("Add deploy keys to grant read/write access to this repository. %{link_start}What are deploy keys?%{link_end}"), tag_pair(link, :link_start, :link_end))
.settings-content
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c|
- c.with_body do
.gl-new-card-add-form.gl-m-3.gl-display-none.js-toggle-content
= render @deploy_keys.form_partial_path
= render ::Layouts::SettingsBlockComponent.new(_('Deploy keys'),
id: 'js-deploy-keys-settings',
testid: 'deploy-keys-settings-content',
css_class: 'rspec-deploy-keys-settings',
expanded: expanded) do |c|
- c.with_description do
- link = link_to('', help_page_path('user/project/deploy_keys/index'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(_("Add deploy keys to grant read/write access to this repository. %{link_start}What are deploy keys?%{link_end}"), tag_pair(link, :link_start, :link_end))
- c.with_body do
= render ::Layouts::CrudComponent.new(_('Deploy keys'),
body_options: { class: '!gl-m-0' },
toggle_text: s_('DeployKeys|Add new key'),
toggle_options: { data: { testid: 'add-new-deploy-key-button' } }) do |c|
- c.with_body do
#js-deploy-keys{ data: { project_id: @project.id,
project_path: @project.full_path,
enabled_endpoint: enabled_keys_project_deploy_keys_path(@project),
available_project_endpoint: available_project_keys_project_deploy_keys_path(@project),
available_public_endpoint: available_public_keys_project_deploy_keys_path(@project)
} }
- c.with_form do
= render @deploy_keys.form_partial_path

View File

@ -1,12 +1,11 @@
- expanded = expand_deploy_tokens_section?(@new_deploy_token, @created_deploy_token)
%section.settings.no-animate#js-deploy-tokens{ class: ('expanded' if expanded), data: { testid: 'deploy-tokens-settings-content' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= s_('DeployTokens|Deploy tokens')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= description
.settings-content
= render ::Layouts::SettingsBlockComponent.new(s_('DeployTokens|Deploy tokens'),
id: 'js-deploy-tokens',
testid: 'deploy-tokens-settings-content',
expanded: expanded) do |c|
- c.with_description do
= description
- c.with_body do
#new-deploy-token-alert
= render 'shared/deploy_tokens/table', group_or_project: group_or_project, active_tokens: @deploy_tokens

View File

@ -1,24 +1,8 @@
= render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c|
- c.with_header do
.gl-new-card-title-wrapper
%h5.gl-new-card-title
= s_("DeployTokens|Active deploy tokens")
.gl-new-card-count
= sprite_icon('token', css_class: 'gl-mr-2')
= active_tokens.length
.gl-new-card-actions
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: "js-toggle-button js-toggle-content" }) do
= _('Add token')
= render ::Layouts::CrudComponent.new(s_("DeployTokens|Active deploy tokens"),
icon: 'token',
count: active_tokens.length,
toggle_text: _('Add token')) do |c|
- c.with_body do
.gl-new-card-add-form.gl-m-3.gl-mb-4.gl-display-none.js-toggle-content
#js-new-deploy-token{ data: {
container_registry_enabled: container_registry_enabled?(group_or_project),
packages_registry_enabled: packages_registry_enabled?(group_or_project),
create_new_token_path: create_deploy_token_path(group_or_project),
token_type: group_or_project.is_a?(Group) ? 'group' : 'project',
deploy_tokens_help_url: help_page_path('user/project/deploy_tokens/index')
}
}
- if active_tokens.present?
%table.table.b-table.gl-table.b-table-stacked-md
%thead
@ -50,5 +34,14 @@
.js-deploy-token-revoke-button{ data: deploy_token_revoke_button_data(token: token, group_or_project: group_or_project) }
- else
.gl-new-card-empty.gl-px-5.gl-py-4
.gl-text-subtle
= s_('DeployTokens|This %{entity_type} has no active deploy tokens.') % { entity_type: group_or_project.class.name.downcase }
- c.with_form do
#js-new-deploy-token{ data: {
container_registry_enabled: container_registry_enabled?(group_or_project),
packages_registry_enabled: packages_registry_enabled?(group_or_project),
create_new_token_path: create_deploy_token_path(group_or_project),
token_type: group_or_project.is_a?(Group) ? 'group' : 'project',
deploy_tokens_help_url: help_page_path('user/project/deploy_tokens/index')
}
}

View File

@ -1,9 +1,9 @@
---
name: use_sonnet_35
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468334
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/157696
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/469499
milestone: '17.2'
group: group::ai framework
type: beta
default_enabled: true
name: sync_traversal_ids_nowait
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468848
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158024
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/472155
milestone: '17.3'
group: group::tenant scale
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class RemoveOldCodeSuggestionEventsCronWorker < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.3'
WORKER_CLASS = 'ClickHouse::CodeSuggestionEventsCronWorker'
def up
# TODO: make shard-aware. See https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/3430
Gitlab::SidekiqSharding::Validator.allow_unrouted_sidekiq_calls do
Sidekiq::Cron::Job.destroy('click_house_code_suggestion_events_cron_worker')
end
sidekiq_remove_jobs(job_klasses: [WORKER_CLASS])
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
8e40a312b2615cb6677b8ba3c13c7ad339c61bead58ebacf18d985d873ab0a11

View File

@ -46,7 +46,7 @@ The workaround is to set your variables in [GitLab CI/CD variables](../variables
POSTGRES_DB: $POSTGRES_DB
POSTGRES_USER: $POSTGRES_USER
POSTGRES_PASSWORD: $POSTGRES_PASSWORD
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_HOST_AUTH_METHOD: trust
```
For more information about using `postgres` for the `Host`, see [How services are linked to the job](../services/index.md#how-services-are-linked-to-the-job).

View File

@ -86,6 +86,9 @@ that were found on the branch it was run on.
DETAILS:
**Tier:** Ultimate
**Offering:** GitLab.com, Self-managed
**Status:** Beta
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72724) in GitLab 14.5 [with a flag](../../administration/feature_flags.md) named `project_quality_summary_page`. This feature is in [beta](../../policy/experiment-beta-support.md). Disabled by default.
The project quality view displays an overview of the code quality findings. The view can be found under **Analyze > CI/CD analytics**, and requires [`project_quality_summary_page`](../../user/feature_flags.md) feature flag to be enabled for this particular project.

View File

@ -86,7 +86,11 @@ Examples:
| `compliance_frameworks` | `array` | | List of IDs of the compliance frameworks in scope of enforcement, in an array of objects with key `id`. |
| `projects` | `object` | `including`, `excluding` | Use `excluding:` or `including:` then list the IDs of the projects you wish to include or exclude, in an array of objects with key `id`. |
### Example security policies project
### Examples
These examples demonstrate what you can achieve with pipeline execution policies.
#### Pipeline execution policy
You can use the following example in a `.gitlab/security-policies/policy.yml` file stored in a
[security policy project](index.md#security-policy-project):
@ -108,3 +112,27 @@ pipeline_execution_policy:
including:
- id: 361
```
##### Customize enforced jobs based on project variables
You can customize enforced jobs, based on the presence of a project variable. In this example,
the value of `CS_IMAGE` is defined in the policy as `alpine:latest`. However, if the project
also defines the value of `CS_IMAGE`, that value is used instead. The CI/CD variable must be a
predefined project variable, not defined in the project's `.gitlab-ci.yml` file.
```yaml
variables:
CS_ANALYZER_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/security-products/container-scanning:7"
CS_IMAGE: alpine:latest
policy::container-security:
stage: .pipeline-policy-pre
rules:
- if: $CS_IMAGE
variables:
CS_IMAGE: $PROJECT_CS_IMAGE
- when: always
script:
- echo "CS_ANALYZER_IMAGE:$CS_ANALYZER_IMAGE"
- echo "CS_IMAGE:$CS_IMAGE"
```

View File

@ -5,6 +5,10 @@ module QA
module Project
module Settings
class DeployKeys < Page::Base
view 'app/views/shared/deploy_keys/_index.html.haml' do
element 'add-new-deploy-key-button'
end
view 'app/views/shared/deploy_keys/_form.html.haml' do
element 'deploy-key-title-field'
element 'deploy-key-field'
@ -19,7 +23,6 @@ module QA
view 'app/assets/javascripts/deploy_keys/components/app.vue' do
element 'project-deploy-keys-container'
element 'add-new-deploy-key-button'
end
view 'app/assets/javascripts/deploy_keys/components/key.vue' do

View File

@ -51,6 +51,15 @@ RSpec.describe Layouts::CrudComponent, type: :component, feature_category: :shar
expect(page).to have_css('[data-testid="crud-action-toggle"].js-toggle-button.js-toggle-content')
end
it 'renders action toggle custom attributes' do
render_inline described_class.new(title,
toggle_text: toggle_text,
toggle_options: { class: 'custom-button-class', data: { testid: 'crud-custom-toggle-id' } })
expect(page).to have_css('.custom-button-class', text: toggle_text)
expect(page).to have_css('[data-testid="crud-custom-toggle-id"]', text: toggle_text)
end
it 'renders actions slot' do
render_inline component_title do |c|
c.with_actions { actions }
@ -59,12 +68,23 @@ RSpec.describe Layouts::CrudComponent, type: :component, feature_category: :shar
expect(page).to have_css('[data-testid="crud-actions"]', text: actions)
end
it 'renders form slot' do
render_inline component_title do |c|
it 'renders hidden form slot if toggle is set' do
render_inline described_class.new(title, toggle_text: toggle_text) do |c|
c.with_form { form }
end
expect(page).to have_css('[data-testid="crud-form"]', text: form)
expect(page).to have_css('.gl-hidden', text: form)
end
it 'renders form custom attributes' do
render_inline described_class.new(title,
form_options: { class: 'error-class', data: { testid: 'crud-custom-form-id' } }) do |c|
c.with_form { form }
end
expect(page).to have_css('.error-class', text: form)
expect(page).not_to have_css('.gl-hidden', text: form)
expect(page).to have_css('[data-testid="crud-custom-form-id"]', text: form)
end
it 'renders body slot' do
@ -75,6 +95,16 @@ RSpec.describe Layouts::CrudComponent, type: :component, feature_category: :shar
expect(page).to have_css('[data-testid="crud-body"]', text: body)
end
it 'renders body custom attributes' do
render_inline described_class.new(title,
body_options: { class: '!gl-m-0', data: { testid: 'crud-custom-body-id' } }) do |c|
c.with_body { body }
end
expect(page).to have_css('.\!gl-m-0', text: body)
expect(page).to have_css('[data-testid="crud-custom-body-id"]', text: body)
end
it 'renders footer slot' do
render_inline component_title do |c|
c.with_footer { footer }

View File

@ -19,7 +19,7 @@ RSpec.describe Layouts::SettingsBlockComponent, type: :component, feature_catego
it 'renders description' do
render_inline described_class.new(heading, description: description)
expect(page).to have_css('.gl-text-secondary', text: description)
expect(page).to have_css('.gl-text-subtle', text: description)
end
it 'renders description slot' do
@ -27,7 +27,7 @@ RSpec.describe Layouts::SettingsBlockComponent, type: :component, feature_catego
c.with_description { description }
end
expect(page).to have_css('.gl-text-secondary', text: description)
expect(page).to have_css('.gl-text-subtle', text: description)
end
it 'renders body slot' do

View File

@ -19,7 +19,7 @@ RSpec.describe Layouts::SettingsSectionComponent, type: :component, feature_cate
it 'renders description' do
render_inline described_class.new(heading, description: description)
expect(page).to have_css('.gl-text-secondary', text: description)
expect(page).to have_css('.gl-text-subtle', text: description)
end
it 'renders description slot' do
@ -27,7 +27,7 @@ RSpec.describe Layouts::SettingsSectionComponent, type: :component, feature_cate
c.with_description { description }
end
expect(page).to have_css('.gl-text-secondary', text: description)
expect(page).to have_css('.gl-text-subtle', text: description)
end
it 'renders body slot' do

View File

@ -265,7 +265,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :s
end
it 'hides remote mirror settings' do
expect(page.find('.project-mirror-settings')).not_to have_selector('form')
expect(find_by_testid('mirroring-repositories-settings-content')).not_to have_selector('form')
expect(page).to have_content('Mirror settings are only available to GitLab administrators.')
end
end
@ -337,7 +337,7 @@ RSpec.describe 'Projects > Settings > Repository settings', feature_category: :s
context 'for admin' do
shared_examples_for 'shows mirror settings' do
it 'shows mirror settings' do
expect(page.find('.project-mirror-settings')).to have_selector('form')
expect(find_by_testid('mirroring-repositories-settings-content')).to have_selector('form')
expect(page).not_to have_content('Changing mirroring setting is disabled for non-admin users.')
end
end

View File

@ -122,11 +122,11 @@ RSpec.describe "User interacts with deploy keys", :js, feature_category: :contin
it 'click on cancel hides the form' do
click_button('Add new key')
expect(page).to have_css('.gl-new-card-add-form')
expect(page).to have_css('[data-testid="crud-form"]')
click_button('Cancel')
expect(page).not_to have_css('.gl-new-card-add-form')
expect(page).not_to have_css('[data-testid="crud-form"]')
end
end

View File

@ -73,7 +73,7 @@ RSpec.describe 'Protected Tags', :js, :with_license, feature_category: :source_c
set_allowed_to('create')
click_on_protect
within("#js-protected-tags-settings .gl-new-card-count") do
within('#js-protected-tags-settings [data-testid="crud-count"]') do
expect(page).to have_content("2")
end

View File

@ -8,8 +8,8 @@ describe('Settings Block', () => {
wrapper = mountExtended(SettingsSection, {
propsData,
slots: {
heading: '<div data-testid="heading-slot">Advanced</div>',
description: '<div data-testid="description-slot"></div>',
heading: '<div data-testid="heading-slot">Heading</div>',
description: '<div data-testid="description-slot">Description</div>',
default: '<div data-testid="default-slot"></div>',
},
});
@ -17,7 +17,9 @@ describe('Settings Block', () => {
const findDefaultSlot = () => wrapper.findByTestId('default-slot');
const findHeadingSlot = () => wrapper.findByTestId('heading-slot');
const findHeading = () => wrapper.findByTestId('settings-section-heading');
const findDescriptionSlot = () => wrapper.findByTestId('description-slot');
const findDescription = () => wrapper.findByTestId('settings-section-description');
it('has a default slot', () => {
mountComponent();
@ -31,9 +33,23 @@ describe('Settings Block', () => {
expect(findHeadingSlot().exists()).toBe(true);
});
it('has correct heading text and classes', () => {
mountComponent();
expect(findHeading().text()).toBe('Heading');
expect(findHeading().classes()).toEqual(['gl-heading-2', '!gl-mb-2']);
});
it('has a description slot', () => {
mountComponent();
expect(findDescriptionSlot().exists()).toBe(true);
});
it('has correct description text and classes', () => {
mountComponent();
expect(findDescription().text()).toBe('Description');
expect(findDescription().classes()).toEqual(['gl-text-subtle', 'gl-mb-3']);
});
});

View File

@ -129,7 +129,7 @@ RSpec.describe Banzai::Filter::EmojiFilter, feature_category: :team_planning do
end
end
it 'limit keeps it from timing out' do
it 'limit keeps it from timing out', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/454749' do
expect do
Timeout.timeout(1.second) { filter('⏯ :play_pause: ' * 500000) }
end.not_to raise_error

View File

@ -68,7 +68,7 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model, feature_category: :g
end
end
it_behaves_like 'locked row' do
it_behaves_like 'locked row', nowait: true do
let(:recorded_queries) { ActiveRecord::QueryRecorder.new }
let(:row) { root }
@ -77,6 +77,38 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model, feature_category: :g
end
end
context 'when record is already locked' do
before do
msg = %(PG::LockNotAvailable: ERROR: could not obtain lock on row in relation "namespaces"\n)
allow(root).to receive(:lock!).and_raise(ActiveRecord::LockWaitTimeout.new(msg))
end
it { expect { subject }.to raise_error(ActiveRecord::LockWaitTimeout) }
it 'increment db_nowait counter' do
expect do
subject
rescue StandardError
nil
end.to change { db_nowait_total('Namespace#sync_traversal_ids!') }.by(1)
end
end
context 'when sync_traversal_ids_nowait feature flag is disabled' do
before do
stub_feature_flags(sync_traversal_ids_nowait: false)
end
it_behaves_like 'locked row', nowait: false do
let(:recorded_queries) { ActiveRecord::QueryRecorder.new }
let(:row) { root }
before do
recorded_queries.record { subject }
end
end
end
context 'when deadlocked' do
before do
allow(root).to receive(:lock!) { raise ActiveRecord::Deadlocked }
@ -94,6 +126,12 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model, feature_category: :g
end
end
def db_nowait_total(source)
Gitlab::Metrics
.counter(:db_nowait, 'Counts the times we triggered NOWAIT on a database lock operation')
.get(source: source)
end
def db_deadlock_total(source)
Gitlab::Metrics
.counter(:db_deadlock, 'Counts the times we have deadlocked in the database')

View File

@ -4,17 +4,19 @@
# Ensure a transaction also occurred.
# Be careful! This form of spec is not foolproof, but better than nothing.
RSpec.shared_examples 'locked row' do
RSpec.shared_examples 'locked row' do |nowait: false|
it "has locked row" do
table_name = row.class.table_name
ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+FOR NO KEY UPDATE/m
expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
expect(recorded_queries.log).to include a_string_matching ids_regex
expect(recorded_queries.log).to include a_string_matching 'NOWAIT' if nowait
end
end
RSpec.shared_examples 'locked rows' do
RSpec.shared_examples 'locked rows' do |nowait: false|
it "has locked rows" do
table_name = rows.first.class.table_name
@ -23,5 +25,7 @@ RSpec.shared_examples 'locked rows' do
expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
expect(recorded_queries.log).to include a_string_matching ids_regex
expect(recorded_queries.log).to include a_string_matching 'NOWAIT' if nowait
end
end