Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-08-15 18:10:49 +00:00
parent de78f06b66
commit c0e21fd75e
161 changed files with 914 additions and 1102 deletions

View File

@ -13,7 +13,6 @@ Gitlab/Rails/SafeFormat:
- 'app/helpers/merge_requests_helper.rb'
- 'app/helpers/profiles_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/reminder_emails_helper.rb'
- 'app/helpers/search_helper.rb'
- 'app/helpers/sourcegraph_helper.rb'
- 'app/helpers/whats_new_helper.rb'

View File

@ -218,7 +218,6 @@ Layout/LineLength:
- 'app/helpers/projects/security/configuration_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/registrations_helper.rb'
- 'app/helpers/reminder_emails_helper.rb'
- 'app/helpers/repository_languages_helper.rb'
- 'app/helpers/routing/pseudonymization_helper.rb'
- 'app/helpers/search_helper.rb'

View File

@ -31,7 +31,6 @@ Rails/OutputSafety:
- 'app/helpers/page_layout_helper.rb'
- 'app/helpers/profiles_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/reminder_emails_helper.rb'
- 'app/helpers/safe_format_helper.rb'
- 'app/helpers/search_helper.rb'
- 'app/helpers/sidebars_helper.rb'

View File

@ -44,7 +44,6 @@ Style/FormatString:
- 'app/helpers/projects/project_members_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/registrations_helper.rb'
- 'app/helpers/reminder_emails_helper.rb'
- 'app/helpers/search_helper.rb'
- 'app/helpers/ssh_keys_helper.rb'
- 'app/helpers/tags_helper.rb'

View File

@ -21,7 +21,6 @@ Style/IfUnlessModifier:
- 'app/helpers/preferences_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/releases_helper.rb'
- 'app/helpers/reminder_emails_helper.rb'
- 'app/helpers/routing/artifacts_helper.rb'
- 'app/helpers/search_helper.rb'
- 'app/helpers/snippets_helper.rb'

View File

@ -30,6 +30,7 @@ export default {
isCanceling: false,
isRetrying: false,
showConfirmationModal: false,
pipelineToCancel: this.pipeline,
};
},
computed: {
@ -55,10 +56,11 @@ export default {
this.isCanceling = true;
this.showConfirmationModal = false;
this.$emit('cancel-pipeline', this.pipeline);
this.$emit('cancel-pipeline', this.pipelineToCancel);
},
handleCancelClick() {
this.showConfirmationModal = true;
this.pipelineToCancel = this.pipeline;
this.trackClick('click_cancel_button');
},
@ -79,7 +81,7 @@ export default {
<template>
<div class="gl-text-right">
<pipeline-stop-modal
:pipeline="pipeline"
:pipeline="pipelineToCancel"
:show-confirmation-modal="showConfirmationModal"
@submit="onConfirmCancelPipeline"
@close-modal="onCloseModal"

View File

@ -73,6 +73,7 @@ export default {
:title="modalTitle"
:action-primary="primaryProps"
:action-cancel="$options.cancelProps"
data-testid="pipeline-stop-modal"
@primary="emitSubmit($event)"
>
<p>

View File

@ -0,0 +1,13 @@
import { __ } from '~/locale';
export const START_RULE = {
regex: /^[a-zA-Z0-9\u{00A9}-\u{1f9ff}_]/u,
message: __('Group name must start with a letter, digit, emoji, or underscore.'),
};
export const CONTAINS_RULE = {
regex: /^[a-zA-Z0-9\p{Pd}\u{00A9}-\u{1f9ff}_. ()]+$/u,
message: __(
'Group name can contain only letters, digits, dashes, spaces, dots, underscores, parenthesis, and emojis.',
),
};

View File

@ -14,7 +14,7 @@ export default {
GlSprintf,
},
i18n: {
topicsTitle: s__('ProjectSettings|Topics'),
topicsTitle: s__('ProjectSettings|Project topics'),
topicsHelpText: s__(
'ProjectSettings|Topics are publicly visible even on private projects. Do not include sensitive information in topic names. %{linkStart}Learn more%{linkEnd}.',
),

View File

@ -55,15 +55,13 @@ const getTrialStatusWidgetData = (sidebarData) => {
trialDaysUsed,
trialDuration,
percentageComplete,
widgetUrl,
groupId,
featureId,
dismissEndpoint,
} = convertObjectPropsToCamelCase(sidebarData.duo_pro_trial_status_widget_data_attrs);
const { daysRemaining, trialEndDate, purchaseNowUrl } = convertObjectPropsToCamelCase(
sidebarData.duo_pro_trial_status_popover_data_attrs,
);
const { daysRemaining, trialEndDate, purchaseNowUrl, learnAboutButtonUrl } =
convertObjectPropsToCamelCase(sidebarData.duo_pro_trial_status_popover_data_attrs);
return {
showDuoProTrialStatusWidget: true,
@ -71,14 +69,13 @@ const getTrialStatusWidgetData = (sidebarData) => {
trialDaysUsed: Number(trialDaysUsed),
trialDuration: Number(trialDuration),
percentageComplete: Number(percentageComplete),
widgetUrl,
groupId,
featureId,
dismissEndpoint,
daysRemaining,
trialEndDate: new Date(trialEndDate),
purchaseNowUrl,
learnAboutButtonUrl: widgetUrl,
learnAboutButtonUrl,
};
}

View File

@ -351,7 +351,7 @@ export default {
}
this.markdownPreviewLoading = false;
this.markdownPreview = data.body || __('Nothing to preview.');
this.markdownPreview = data.body || data.html || __('Nothing to preview.');
this.$nextTick()
.then(() => {

View File

@ -1,77 +0,0 @@
# frozen_string_literal: true
module ReminderEmailsHelper
def invitation_reminder_salutation(reminder_index, format: nil)
case reminder_index
when 0
s_('InviteReminderEmail|Invitation pending')
when 1
if format == :html
wave_emoji_tag = Gitlab::Emoji.gl_emoji_tag(TanukiEmoji.find_by_alpha_code('wave'))
s_('InviteReminderEmail|Hey there %{wave_emoji}').html_safe % { wave_emoji: wave_emoji_tag }
else
s_('InviteReminderEmail|Hey there!')
end
when 2
s_('InviteReminderEmail|In case you missed it...')
end
end
def invitation_reminder_body(member, reminder_index, format: nil)
options = {
inviter: sanitize_name(member.created_by.name),
strong_start: '',
strong_end: '',
project_or_group_name: member_source.human_name,
project_or_group: member_source.model_name.singular,
role: member.human_access.downcase
}
if format == :html
options.merge!(
inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe,
strong_start: '<strong>'.html_safe,
strong_end: '</strong>'.html_safe
)
end
if reminder_index == 2
options[:invitation_age] = (Date.current - member.created_at.to_date).to_i
end
body = invitation_reminder_body_text(reminder_index)
(format == :html ? ERB::Util.html_escape(body) : body) % options
end
def invitation_reminder_accept_link(token, format: nil)
case format
when :html
link_to s_('InviteReminderEmail|Accept invitation'), invite_url(token), class: 'invite-btn-join'
else
s_('InviteReminderEmail|Accept invitation: %{invite_url}') % { invite_url: invite_url(token) }
end
end
def invitation_reminder_decline_link(token, format: nil)
case format
when :html
link_to s_('InviteReminderEmail|Decline invitation'), decline_invite_url(token), class: 'invite-btn-decline'
else
s_('InviteReminderEmail|Decline invitation: %{decline_url}') % { decline_url: decline_invite_url(token) }
end
end
private
def invitation_reminder_body_text(reminder_index)
case reminder_index
when 0
s_('InviteReminderEmail|%{inviter} is waiting for you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}.')
when 1
s_('InviteReminderEmail|This is a friendly reminder that %{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}.')
when 2
s_("InviteReminderEmail|It's been %{invitation_age} days since %{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}. What would you like to do?")
end
end
end

View File

@ -50,29 +50,6 @@ module Emails
subject: subject("Access to the #{human_name} #{member_source.model_name.singular} was denied"))
end
def member_invited_reminder_email(member_source_type, member_id, token, reminder_index)
@member_source_type = member_source_type
@member_id = member_id
@token = token
@reminder_index = reminder_index
return unless member_exists? && member.created_by && member.invite_to_unknown_user?
subjects = {
0 => s_("InviteReminderEmail|%{inviter}'s invitation to GitLab is pending"),
1 => s_('InviteReminderEmail|%{inviter} is waiting for you to join GitLab'),
2 => s_('InviteReminderEmail|%{inviter} is still waiting for you to join GitLab')
}
subject_line = subjects[reminder_index] % { inviter: member.created_by.name }
email_with_layout(
layout: 'unknown_user_mailer',
to: member.invite_email,
subject: subject(subject_line)
)
end
def member_invite_accepted_email(member_source_type, member_id)
@member_source_type = member_source_type
@member_id = member_id

View File

@ -0,0 +1,132 @@
# frozen_string_literal: true
module Members
class InviteReminderMailer < ApplicationMailer
include SafeFormatHelper
helper EmailsHelper
helper_method :reminder_common_body_options
layout 'unknown_user_mailer'
def email(member, token, reminder_index)
@member = member
@token = token
@reminder_index = reminder_index
return unless valid_to_email?
@email_instance = email_klass[reminder_index].new
subject_line = format(email_instance.subject, inviter: member.created_by.name)
mail_with_locale(to: member.invite_email, subject: EmailsHelper.subject_with_suffix([subject_line]))
end
private
attr_reader :token, :member, :reminder_index, :email_instance
def email_klass
{
0 => FirstEmail,
1 => SecondEmail,
2 => LastEmail
}
end
def valid_to_email?
return true if member.present? && member.created_by && member.invite_to_unknown_user?
Gitlab::AppLogger.info('Tried to send an email invitation for an invalid member.') if member.blank?
false
end
def reminder_common_body_options(member)
{
project_or_group_name: member.source.human_name,
project_or_group: member.source.model_name.singular,
role: member.human_access.downcase
}
end
class FirstEmail
def subject
s_("InviteReminderEmail|%{inviter}'s invitation to GitLab is pending")
end
def salutation
s_('InviteReminderEmail|Invitation pending')
end
alias_method :salutation_html, :salutation
def body_text
s_(
'InviteReminderEmail|%{inviter} is waiting for you to join the ' \
'%{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}.'
)
end
def extra_body_options(_)
{}
end
end
class SecondEmail
include SafeFormatHelper
def subject
s_('InviteReminderEmail|%{inviter} is waiting for you to join GitLab')
end
def salutation
s_('InviteReminderEmail|Hey there!')
end
def salutation_html
wave_emoji_tag = Gitlab::Emoji.gl_emoji_tag(TanukiEmoji.find_by_alpha_code('wave'))
safe_format(s_('InviteReminderEmail|Hey there %{wave_emoji}'), wave_emoji: wave_emoji_tag)
end
def body_text
s_(
'InviteReminderEmail|This is a friendly reminder that %{inviter} invited you to join the ' \
'%{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}.'
)
end
def extra_body_options(_)
{}
end
end
class LastEmail
def subject
s_('InviteReminderEmail|%{inviter} is still waiting for you to join GitLab')
end
def salutation
s_('InviteReminderEmail|In case you missed it...')
end
alias_method :salutation_html, :salutation
def body_text
s_(
"InviteReminderEmail|It's been %{invitation_age} days since %{inviter} invited you to join the " \
"%{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}. " \
"What would you like to do?"
)
end
def extra_body_options(created_at_date)
{
invitation_age: (Date.current - created_at_date).to_i
}
end
end
end
end

View File

@ -4,7 +4,6 @@ class Notify < ApplicationMailer
include ActionDispatch::Routing::PolymorphicRoutes
include GitlabRoutingHelper
include EmailsHelper
include ReminderEmailsHelper
include IssuablesHelper
mattr_accessor :override_layout_lookup_table, default: {}
@ -34,7 +33,6 @@ class Notify < ApplicationMailer
helper DiffHelper
helper BlobHelper
helper EmailsHelper
helper ReminderEmailsHelper
helper MembersHelper
helper AvatarsHelper
helper GitlabRoutingHelper

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module Members
class InviteReminderMailerPreview < ActionMailer::Preview
def first_reminder_email
Members::InviteReminderMailer.email(member, '1234', 0).message
end
def second_reminder_email
Members::InviteReminderMailer.email(member, '1234', 1).message
end
def last_reminder_email
Members::InviteReminderMailer.email(member, '1234', 2).message
end
private
def member
Member.not_accepted_invitations.with_created_by.last
end
end
end

View File

@ -461,7 +461,7 @@ class NotifyPreview < ActionMailer::Preview
end
def member
@member ||= Member.last
@member ||= Member.non_invite.last
end
def key

View File

@ -154,6 +154,7 @@ class Member < ApplicationRecord
scope :not_accepted_invitations_by_user, ->(user) { not_accepted_invitations.where(created_by: user) }
scope :not_expired, ->(today = Date.current) { where(arel_table[:expires_at].gt(today).or(arel_table[:expires_at].eq(nil))) }
scope :expiring_and_not_notified, ->(date) { where("expiry_notified_at is null AND expires_at >= ? AND expires_at <= ?", Date.current, date) }
scope :with_created_by, -> { where.associated(:created_by) }
scope :created_today, -> do
now = Date.current
@ -526,7 +527,9 @@ class Member < ApplicationRecord
generate_invite_token! unless @raw_invite_token
run_after_commit_or_now { notification_service.invite_member_reminder(self, @raw_invite_token, reminder_index) }
run_after_commit_or_now do
Members::InviteReminderMailer.email(self, @raw_invite_token, reminder_index).deliver_later
end
end
def create_notification_setting

View File

@ -315,14 +315,6 @@ class Repository
false
end
def branch_or_tag?(ref)
return false unless exists?
ref = Gitlab::Git.ref_name(ref, types: 'heads|tags')
branch_exists?(ref) || tag_exists?(ref)
end
def search_branch_names(pattern)
redis_set_cache.search('branch_names', pattern) { branch_names }
end

View File

@ -18,28 +18,25 @@ module Ci
end
def execute(ref)
# "ref" is not a enough for a cache key because the name is static but that branch can be changed any time.
sha = project.commit(ref).try(:sha)
with_reactive_cache(ref, sha) { |result| result }
with_reactive_cache(sha) { |result| result }
end
def calculate_reactive_cache(ref, sha)
def calculate_reactive_cache(sha)
config = ::Gitlab::Ci::ProjectConfig.new(project: project, sha: sha)
return {} unless config.exists?
# The `ref` parameter should be branch or tag name. However, the API also accepts a commit SHA and we can't
# change it to not introduce breaking changes. Instead, here we're checking if a commit SHA is passed
# as `ref`. If so, we should verify the sha whether it belongs to the project in YamlProcessor.
sha_passed_as_ref_parameter = !project.repository.branch_or_tag?(ref)
ref_name = Gitlab::Ci::RefFinder.new(project).find_by_sha(sha)
result = Gitlab::Ci::YamlProcessor.new(
config.content,
project: project,
user: current_user,
sha: sha,
ref: ref,
verify_project_sha: sha_passed_as_ref_parameter
ref: ref_name,
verify_project_sha: true
).execute
result.valid? ? result.root_variables_with_prefill_data : {}

View File

@ -568,10 +568,6 @@ class NotificationService
mailer.member_about_to_expire_email(member.real_source_type, member.id).deliver_later
end
def invite_member_reminder(group_member, token, reminder_index)
mailer.member_invited_reminder_email(group_member.real_source_type, group_member.id, token, reminder_index).deliver_later
end
def project_was_moved(project, old_path_with_namespace)
recipients = project_moved_recipients(project)
recipients = notifiable_users(recipients, :custom, custom_action: :moved_project, project: project)

View File

@ -8,7 +8,7 @@
%h1.gl-sr-only= @breadcrumb_title
- if can?(current_user, :admin_group, @group)
= render ::Layouts::SettingsBlockComponent.new(_('Naming, visibility'),
= render ::Layouts::SettingsBlockComponent.new(_('Naming, description, visibility'),
id: 'js-general-settings',
testid: 'general-settings',
expanded: true) do |c|

View File

@ -7,33 +7,34 @@
.form-group.col-md-5
= f.label :name, s_('Groups|Group name'), class: 'label-bold'
= f.text_field :name, class: 'form-control', data: { testid: 'group-name-field' }
.text-muted
%span.form-text.text-muted
= s_('Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.')
.form-group.col-md-7
.form-group.col-md-4
= f.label :id, s_('Groups|Group ID'), class: 'label-bold'
= f.text_field :id, class: 'form-control w-auto', readonly: true
= f.text_field :id, class: 'form-control gl-form-input', readonly: true
.row.gl-mt-3
.form-group.gl-mt-4.gl-mb-9
= render Pajamas::AvatarComponent.new(@group, size: 96, alt: '', class: 'gl-float-left gl-mr-5')
= f.label :avatar, s_('Groups|Group avatar'), class: 'label-bold gl-block'
= render 'shared/choose_avatar_button', f: f
- if @group.avatar?
%hr
= link_button_to s_('Groups|Remove avatar'), group_avatar_path(@group.to_param), aria: { label: s_('Groups|Remove avatar') }, data: { confirm: s_('Groups|Avatar will be removed. Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :delete, variant: :danger, category: :secondary
.row
.form-group.col-md-9
= f.label :description, s_('Groups|Group description (optional)'), class: 'label-bold'
= f.text_area :description, class: 'form-control', rows: 3, maxlength: 500
= f.text_area :description, class: 'form-control', rows: 4, maxlength: 500
.row.gl-mt-3
= render 'shared/repository_size_limit_setting_registration_features_cta', form: f
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
.row
.form-group.col-md-5
= f.label :description, s_('Groups|Group README'), class: 'label-bold'
#js-group-settings-readme{ data: group_settings_readme_app_data(@group) }
= render 'shared/repository_size_limit_setting_registration_features_cta', form: f
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
.form-group.gl-mt-3.gl-mb-6
= render Pajamas::AvatarComponent.new(@group, size: 96, alt: '', class: 'gl-float-left gl-mr-5')
= f.label :avatar, s_('Groups|Group avatar'), class: 'label-bold gl-block'
= render 'shared/choose_avatar_button', f: f
- if @group.avatar?
%hr
= link_button_to s_('Groups|Remove avatar'), group_avatar_path(@group.to_param), aria: { label: s_('Groups|Remove avatar') }, data: { confirm: s_('Groups|Avatar will be removed. Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :delete, variant: :danger, category: :secondary
.form-group.gl-form-group
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
= f.submit s_('Groups|Save changes'), pajamas_button: true, class: 'js-dirty-submit', data: { testid: 'save-name-visibility-settings-button' }

View File

@ -0,0 +1,12 @@
%tr
%td.text-content
%h2.invite-header
= @email_instance.salutation_html
%p.invite-body
- options = { inviter: link_to(@member.created_by.name, user_url(@member.created_by)) }.merge(reminder_common_body_options(@member))
= safe_format(@email_instance.body_text,
options.merge(tag_pair(tag.strong,
:strong_start, :strong_end)).merge(@email_instance.extra_body_options(@member.created_at.to_date)))
%p.invite-actions
= link_to s_('InviteReminderEmail|Accept invitation'), invite_url(@token), class: 'invite-btn-join'
= link_to s_('InviteReminderEmail|Decline invitation'), decline_invite_url(@token), class: 'invite-btn-decline'

View File

@ -0,0 +1,13 @@
<%= @email_instance.salutation %>
<% options = {
inviter: sanitize_name(@member.created_by.name),
strong_start: '',
strong_end: ''
}.merge(reminder_common_body_options(@member))
%>
<%= format(@email_instance.body_text, options.merge(@email_instance.extra_body_options(@member.created_at.to_date))) %>
<%= s_('InviteReminderEmail|Accept invitation: %{invite_url}') % { invite_url: invite_url(@token) } %>
<%= s_('InviteReminderEmail|Decline invitation: %{decline_url}') % { decline_url: decline_invite_url(@token) } %>

View File

@ -1,9 +0,0 @@
%tr
%td.text-content
%h2.invite-header
= invitation_reminder_salutation(@reminder_index, format: :html)
%p.invite-body
= invitation_reminder_body(member, @reminder_index, format: :html)
%p.invite-actions
= invitation_reminder_accept_link(@token, format: :html)
= invitation_reminder_decline_link(@token, format: :html)

View File

@ -1,6 +0,0 @@
<%= invitation_reminder_salutation(@reminder_index) %>
<%= invitation_reminder_body(member, @reminder_index) %>
<%= invitation_reminder_accept_link(@token) %>
<%= invitation_reminder_decline_link(@token) %>

View File

@ -3,16 +3,18 @@
- add_page_specific_style 'page_bundles/projects_edit'
- reduce_visibility_form_id = 'reduce-visibility-form'
- @force_desktop_expanded_sidebar = true
- project_docs_path = help_page_path('user/project/working_with_projects')
- project_docs_link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: project_docs_path }
%h1.gl-sr-only= @breadcrumb_title
- if can?(current_user, :admin_project, @project)
= render ::Layouts::SettingsBlockComponent.new(_('Naming, topics, avatar'),
= render ::Layouts::SettingsBlockComponent.new(_('Naming, description, topics'),
id: 'js-general-settings',
testid: 'general-settings-content',
expanded: true) do |c|
- c.with_description do
= _('Update your project name, topics, description, and avatar.')
= html_escape(_('Update your project name, description, avatar, and topics. %{link_start}Learn more about projects%{link_end}.')) % { link_start: project_docs_link_start, link_end: '</a>'.html_safe }
- c.with_body do
= render 'projects/settings/general'

View File

@ -8,33 +8,35 @@
= f.label :name, class: 'label-bold', for: 'project_name_edit' do
= _('Project name')
= f.text_field :name, class: 'form-control gl-form-input', id: "project_name_edit", data: { testid: 'project-name-field' }
%span.form-text.text-muted
= _('Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.')
.form-group.col-md-7
.form-group.col-md-4
= f.label :id, class: 'label-bold' do
= _('Project ID')
= f.text_field :id, class: 'form-control gl-form-input w-auto', readonly: true
= f.text_field :id, class: 'form-control gl-form-input', readonly: true
.form-group.gl-mt-2.gl-mb-8
= render Pajamas::AvatarComponent.new(@project, size: 96, alt: '', class: 'gl-float-left gl-mr-5')
= f.label :avatar, _('Project avatar'), class: 'label-bold gl-block'
= render 'shared/choose_avatar_button', f: f
- if @project.avatar?
%hr
= link_button_to _('Remove avatar'), project_avatar_path(@project), aria: { label: _('Remove avatar') }, data: { confirm: _('Avatar will be removed. Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :delete, variant: :danger, category: :secondary
.row
.form-group.col-md-9
= f.label :description, _('Project description (optional)'), class: 'label-bold'
= f.text_area :description, class: 'form-control gl-form-input', rows: 4
.row= render_if_exists 'projects/classification_policy_settings', f: f
= render 'shared/repository_size_limit_setting_registration_features_cta', form: f
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
.row
.form-group.col-md-9
.js-topics-selector{ data: { hidden_input_id: hidden_topics_field_id } }
= f.hidden_field :topics, value: @project.topic_list.join(', '), id: hidden_topics_field_id
.row
.form-group.col-md-9
= f.label :description, _('Project description (optional)'), class: 'label-bold'
= f.text_area :description, class: 'form-control gl-form-input', rows: 3
.row= render_if_exists 'projects/classification_policy_settings', f: f
= render 'shared/repository_size_limit_setting_registration_features_cta', form: f
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
.form-group.gl-mt-3.gl-mb-3
= render Pajamas::AvatarComponent.new(@project, size: 96, alt: '', class: 'gl-float-left gl-mr-5')
= f.label :avatar, _('Project avatar'), class: 'label-bold gl-block'
= render 'shared/choose_avatar_button', f: f
- if @project.avatar?
%hr
= link_button_to _('Remove avatar'), project_avatar_path(@project), aria: { label: _('Remove avatar') }, data: { confirm: _('Avatar will be removed. Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :delete, variant: :danger, category: :secondary
= f.submit _('Save changes'), pajamas_button: true, class: "gl-mt-6", data: { testid: 'save-naming-topics-avatar-button' }
= f.submit _('Save changes'), pajamas_button: true, data: { testid: 'save-naming-topics-avatar-button' }

View File

@ -1,4 +1,4 @@
%div{ class: [container_class, @content_class, 'gl-pt-5!'] }
%div{ class: [container_class, @content_class, '!gl-pt-5'] }
= render Pajamas::BannerComponent.new(button_text: s_('AutoDevOps|Enable in settings'),
button_link: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'),
svg_path: 'illustrations/devops-sm.svg',

View File

@ -3,7 +3,7 @@
- script = local_assigns.fetch(:script, true)
- method = params[:action] == 'create' ? :post : :put
%h1.page-title.gl-font-size-h-display
%h1.page-title.gl-text-size-h-display
= _('Anti-spam verification')
%hr

View File

@ -1,4 +1,4 @@
.gl-display-flex.gl-align-items-center.gl-gap-5{ data: { testid: 'divider' } }
%hr.gl-flex-grow-1.gl-border-gray-100
.gl-flex.gl-items-center.gl-gap-5{ data: { testid: 'divider' } }
%hr.gl-grow.gl-border-gray-100
= local_assigns[:text]
%hr.gl-flex-grow-1.gl-border-gray-100
%hr.gl-grow.gl-border-gray-100

View File

@ -1,6 +1,6 @@
- show_group_events = local_assigns.fetch(:show_group_events, false)
.scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full
.scrolling-tabs-container.inner-page-scroll-tabs.gl-grow.gl-min-w-0.gl-w-full
%button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
= sprite_icon('chevron-lg-left', size: 12)
%button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }

View File

@ -7,6 +7,6 @@
#blob-content.file-content.code.js-syntax-highlight
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight } }
%pre{ class: "code highlight gl-border-none!" }
%pre{ class: "code highlight !gl-border-none" }
%code.gl-border-none
= highlighted_blob

View File

@ -5,6 +5,6 @@
-# 100vh because of the presence of the bottom bar
#ide.gl-h-full{ data: data }
.web-ide-loader.gl-display-flex.gl-justify-content-center.gl-align-items-center.gl-flex-direction-column.gl-h-full.gl-mx-auto
.web-ide-loader.gl-flex.gl-justify-center.gl-items-center.gl-flex-col.gl-h-full.gl-mx-auto
= brand_header_logo
%h3.clblack.gl-mt-6= loading_text

View File

@ -5,22 +5,22 @@
- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count
- if issuable_mr > 0
%li.gl-block.has-tooltip{ title: _('Related merge requests'), data: { testid: 'merge-requests' }, class: 'gl-mr-0!' }
%li.gl-block.has-tooltip{ title: _('Related merge requests'), data: { testid: 'merge-requests' }, class: '!gl-mr-0' }
= sprite_icon('merge-request', css_class: "gl-align-middle")
= issuable_mr
- if note_count > 0
%li.gl-block.has-tooltip{ title: _('Comments'), data: { testid: 'issuable-comments' }, class: 'gl-mr-0!' }
%li.gl-block.has-tooltip{ title: _('Comments'), data: { testid: 'issuable-comments' }, class: '!gl-mr-0' }
= sprite_icon('comments', css_class: "gl-align-middle")
= note_count
- if upvotes > 0
%li.gl-block.has-tooltip{ title: _('Upvotes'), data: { testid: 'issuable-upvotes' }, class: 'gl-mr-0!' }
%li.gl-block.has-tooltip{ title: _('Upvotes'), data: { testid: 'issuable-upvotes' }, class: '!gl-mr-0' }
= sprite_icon('thumb-up', css_class: "gl-align-middle")
= upvotes
- if downvotes > 0
%li.gl-block.has-tooltip{ title: _('Downvotes'), data: { testid: 'issuable-downvotes' }, class: 'gl-mr-0!' }
%li.gl-block.has-tooltip{ title: _('Downvotes'), data: { testid: 'issuable-downvotes' }, class: '!gl-mr-0' }
= sprite_icon('thumb-down', css_class: "gl-align-middle")
= downvotes

View File

@ -11,7 +11,7 @@
= render "shared/label_row", label: label, force_priority: force_priority
%ul.label-actions-list
- if can?(current_user, :admin_label, @project)
%li.gl-display-inline-block.js-toggle-priority.gl-ml-3{ data: { url: remove_priority_project_label_path(@project, label),
%li.gl-inline-block.js-toggle-priority.gl-ml-3{ data: { url: remove_priority_project_label_path(@project, label),
dom_id: dom_id(label), type: label.type } }
= render Pajamas::ButtonComponent.new(category: :tertiary,
size: :small,
@ -22,7 +22,7 @@
icon: 'star-o',
button_options: { class: 'add-priority has-tooltip', title: _('Prioritize'), aria_label: _('Prioritize label'), data: { placement: 'bottom' } })
- if current_user
%li.gl-display-inline-block.label-subscription.js-label-subscription.gl-ml-3.gl-mt-1
%li.gl-inline-block.label-subscription.js-label-subscription.gl-ml-3.gl-mt-1
- if label.can_subscribe_to_label_in_different_levels?
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: "js-unsubscribe-button gl-w-full #{'hidden' if status.unsubscribed?}", data: { url: toggle_subscription_path, toggle: 'tooltip', container: 'body' }, title: tooltip_title }) do
= _('Unsubscribe')
@ -42,7 +42,7 @@
= render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-subscribe-button gl-w-full', data: { status: status, url: toggle_subscription_path, toggle: 'tooltip', container: 'body' }, title: tooltip_title }) do
= label_subscription_toggle_button_text(label, @project)
- if can?(current_user, :admin_label, label)
%li.gl-display-inline-block
%li.gl-inline-block
- can_promote = label.project_label? && label.project.group && can?(current_user, :admin_label, label.project.group)
.js-vue-label-actions{ data: {
label_id: label.id,

View File

@ -1,6 +1,6 @@
- full_path = label.subject_full_name
.gl-font-sm.gl-font-semibold.gl-text-secondary
.gl-text-sm.gl-font-semibold.gl-text-secondary
- if label.project_label?
= sprite_icon('project', size: 12, css_class: 'gl-text-gray-600')
- else

View File

@ -2,7 +2,7 @@
- show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues)
- show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests)
.label-name.gl-flex-shrink-0.gl-mr-5
.label-name.gl-shrink-0.gl-mr-5
= render_label(label, link: '#', tooltip: true, tooltip_shows_title: true)
- if show_labels_full_path?(@project, @group)
.gl-mt-2

View File

@ -9,9 +9,9 @@
= _('Only project members can comment.')
.md-area.position-relative
.md-header.gl-px-3.gl-rounded-top-base.gl-border-b.gl-border-gray-100
.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-justify-content-space-between
.md-header-toolbar.gl-display-flex.gl-py-3.gl-flex-wrap.gl-gap-y-3
.md-header.gl-px-3.gl-rounded-t-base.gl-border-b.gl-border-gray-100
.gl-flex.gl-items-center.gl-flex-wrap.gl-justify-between
.md-header-toolbar.gl-flex.gl-py-3.gl-flex-wrap.gl-gap-y-3
= render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, button_options: { class: 'js-md-preview-button', value: 'preview' }) do
= _('Preview')
= render 'shared/blob/markdown_buttons', supports_quick_actions: supports_quick_actions

View File

@ -1,3 +1,3 @@
- if can?(current_user, :read_pipeline, merge_request.head_pipeline)
%li.issuable-pipeline-status.gl-display-flex
= render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user), option_css_classes: 'gl-display-flex'
%li.issuable-pipeline-status.gl-flex
= render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user), option_css_classes: 'gl-flex'

View File

@ -1,6 +1,6 @@
- count_badge_classes = 'gl-hidden sm:gl-inline-flex'
= gl_tabs_nav({class: 'gl-border-b-0 gl-flex-grow-1', data: { testid: 'milestones-filter' } }) do
= gl_tabs_nav({class: 'gl-border-b-0 gl-grow', data: { testid: 'milestones-filter' } }) do
= gl_tab_link_to milestones_filter_path(state: 'opened'), { item_active: params[:state].blank? || params[:state] == 'opened' } do
= _('Open')
= gl_tab_counter_badge counts[:opened], { class: count_badge_classes }

View File

@ -1,7 +1,7 @@
- return unless ::Gitlab::SilentMode.enabled?
= content_for :page_level_alert do
%div{ class: [container_class, @content_class, 'gl-pt-5!'] }
%div{ class: [container_class, @content_class, '!gl-pt-5'] }
= render Pajamas::AlertComponent.new(title: s_('SilentMode|Silent mode is enabled'),
dismissible: false,
variant: :warning) do |c|

View File

@ -3,4 +3,4 @@
- fork_options = fork_modal_options(@project, blob)
- css_classes = false unless local_assigns[:css_classes]
.gl-display-inline-block{ data: { options: button_data.merge(fork_options).to_json, web_ide_promo_popover_img: image_path('web-ide-promo-popover.svg'), css_classes: css_classes }, id: "js-#{type}-web-ide-link" }
.gl-inline-block{ data: { options: button_data.merge(fork_options).to_json, web_ide_promo_popover_img: image_path('web-ide-promo-popover.svg'), css_classes: css_classes }, id: "js-#{type}-web-ide-link" }

View File

@ -9,7 +9,7 @@
= f.text_field :title, class: 'form-control gl-form-input gl-form-input-xl', required: true, data: { testid: 'deploy-key-title-field' }
.form-group
= f.label :key, class: "label-bold"
= f.text_area :key, class: 'form-control gl-form-input gl-form-input-xl gl-h-auto!', rows: 5, required: true, data: { testid: 'deploy-key-field' }
= f.text_area :key, class: 'form-control gl-form-input gl-form-input-xl !gl-h-auto', rows: 5, required: true, data: { testid: 'deploy-key-field' }
.form-text.text-muted
= _('Paste a public key here.')
= link_to _('How do I generate it?'), help_page_path("user/ssh")

View File

@ -1,6 +1,6 @@
- show_cancel_button = local_assigns.fetch(:cancel, false)
= gitlab_ui_form_for @application, url: url, html: { role: 'form', class: 'doorkeeper-app-form gl-max-w-80 gl-display-inline-block' } do |f|
= gitlab_ui_form_for @application, url: url, html: { role: 'form', class: 'doorkeeper-app-form gl-max-w-80 gl-inline-block' } do |f|
= form_errors(@application)
.form-group

View File

@ -40,7 +40,7 @@
= render "shared/tokens/scopes_list", token: @application
.gl-display-flex.gl-justify-content-space-between
.gl-flex.gl-justify-between
%div
- if @created
= link_button_to _('Continue'), index_path, class: 'gl-mr-3', variant: :confirm

View File

@ -7,7 +7,7 @@
- preview_url = preview_markdown_path(project, target_type: model.class.name)
.form-group
.gl-display-flex
.gl-flex
= form.label :description, _('Description'), class: 'gl-block'
- if model.is_a?(MergeRequest)
= render_if_exists "/shared/form_elements/summarize_merge_request"

View File

@ -1,27 +1,27 @@
- user = local_assigns.fetch(:user, current_user)
- access = user&.max_member_access_for_group(group.id)
%li.group-row.py-3.gl-align-items-center{ class: "gl-display-flex!" }
%li.group-row.py-3.gl-items-center{ class: "!gl-flex" }
= link_to group do
= render Pajamas::AvatarComponent.new(group, alt: group.name, size: 48, class: 'gl-mr-5')
.gl-min-w-0.gl-flex-grow-1
.gl-min-w-0.gl-grow
.title
= link_to group.full_name, group, class: 'group-name'
- if access&.nonzero?
= render Pajamas::BadgeComponent.new(Gitlab::Access.human_access(access), variant: 'neutral', class: 'gl-bg-transparent! !gl-shadow-inner-1-gray-100')
= render Pajamas::BadgeComponent.new(Gitlab::Access.human_access(access), variant: 'neutral', class: '!gl-bg-transparent !gl-shadow-inner-1-gray-100')
- if group.description.present?
.description
= markdown_field(group, :description)
.stats.gl-text-gray-500.gl-flex-shrink-0
.stats.gl-text-gray-500.gl-shrink-0
%span.gl-ml-5.has-tooltip{ title: _('Projects') }
= sprite_icon('project', css_class: 'gl-vertical-align-text-bottom')
= sprite_icon('project', css_class: 'gl-align-text-bottom')
= number_with_delimiter(group.non_archived_projects.size)
%span.gl-ml-5.has-tooltip{ title: _('Users') }
= sprite_icon('users', css_class: 'gl-vertical-align-text-bottom')
= sprite_icon('users', css_class: 'gl-align-text-bottom')
= number_with_delimiter(group.non_invite_group_members.size)
%span.gl-ml-5.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) }

View File

@ -1,4 +1,4 @@
%span.gl-display-flex.gl-align-items-center
%span.gl-flex.gl-items-center
%h5
= gl_badge_tag "POST", { variant: :info }
= hook_log.url
@ -16,7 +16,7 @@
- c.with_body do
= _('Error: %{error}') % { error: hook_log.internal_error_message }
%span.gl-display-flex.gl-align-items-center
%span.gl-flex.gl-items-center
%h3
= _('Response')
= render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log }

View File

@ -2,7 +2,7 @@
- breadcrumb_title @integration.title
- page_title @integration.title, _('Integrations')
.gl-display-flex.gl-align-items-center.gl-gap-3.gl-mt-5
.gl-flex.gl-items-center.gl-gap-3.gl-mt-5
= render Pajamas::AvatarComponent.new(@integration, size: 64, alt: '')
%h2.gl-m-0
= @integration.title

View File

@ -12,15 +12,15 @@
%th= _('Created')
%th
%tr
%td{ class: 'gl-py-3!' }
%td{ class: '!gl-py-3' }
= slack_integration.team_name
- if integration.project_level?
%td{ class: 'gl-py-3!' }
%td{ class: '!gl-py-3' }
= slack_integration.alias
%td{ class: 'gl-py-3!' }
%td{ class: '!gl-py-3' }
= time_ago_with_tooltip(slack_integration.created_at)
%td{ class: 'gl-py-3!' }
.controls.gl-display-flex.gl-justify-content-end.gl-gap-3
%td{ class: '!gl-py-3' }
.controls.gl-flex.gl-justify-end.gl-gap-3
- if integration.project_level?
= render Pajamas::ButtonComponent.new(href: edit_project_settings_slack_path(integration.parent)) do
= _('Edit')

View File

@ -1,5 +1,5 @@
.services-installation-info
- unless integration.activated?
= render Pajamas::ButtonComponent.new(href: new_project_mattermost_path(@project), button_text_classes: 'gl-display-flex gl-gap-2') do
= render Pajamas::ButtonComponent.new(href: new_project_mattermost_path(@project), button_text_classes: 'gl-flex gl-gap-2') do
= custom_icon('mattermost_logo')
= s_("MattermostService|Add to Mattermost")

View File

@ -2,7 +2,7 @@
- breadcrumb_title @integration.title
- page_title @integration.title, _('Integrations')
%h1.page-title.gl-font-size-h-display
%h1.page-title.gl-text-size-h-display
= @integration.title
- if @integration.title == 'Beyond Identity' && Feature.enabled?(:beyond_identity_exclusions)

View File

@ -2,7 +2,7 @@
- page_context_word = type.to_s.humanize(capitalize: false)
- display_count = local_assigns.fetch(:display_count, true)
= gl_tabs_nav({ class: 'issues-state-filters gl-border-b-0 gl-flex-grow-1' }) do
= gl_tabs_nav({ class: 'issues-state-filters gl-border-b-0 gl-grow' }) do
= gl_tab_link_to page_filter_path(state: 'opened'), { item_active: params[:state] == 'opened', id: 'state-opened', title: _("Filter by %{page_context_word} that are currently open.") % { page_context_word: page_context_word }, data: { state: 'opened' } } do
#{issuables_state_counter_text(type, :opened, display_count)}
- if type == :merge_requests

View File

@ -7,8 +7,8 @@
- block_css_class = type != :productivity_analytics ? 'row-content-block second-block' : ''
.issues-filters
.issues-details-filters.filtered-search-block.gl-flex.gl-flex-direction-column.gl-lg-flex-direction-row.gl-gap-3{ class: block_css_class }
.gl-flex.gl-flex-direction-column.gl-md-flex-direction-row.gl-flex-grow-1.gl-w-full
.issues-details-filters.filtered-search-block.gl-flex.gl-flex-col.lg:gl-flex-row.gl-gap-3{ class: block_css_class }
.gl-flex.gl-flex-col.md:gl-flex-row.gl-grow.gl-w-full
= form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form gl-w-full' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
@ -18,7 +18,7 @@
- c.with_label do
%span.gl-sr-only
= _('Select all')
.issues-other-filters.filtered-search-wrapper.gl-flex.gl-flex-direction-column.gl-md-flex-direction-row
.issues-other-filters.filtered-search-wrapper.gl-flex.gl-flex-col.md:gl-flex-row
.filtered-search-box
- if type != :boards
- text = tag.span(sprite_icon('history')) + tag.span(_('Recent searches'), class: "gl-sr-only")
@ -205,7 +205,7 @@
= render_if_exists 'shared/issuable/filter_weight', type: type
= render_if_exists 'shared/issuable/filter_epic', type: type
= render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, icon: 'clear', icon_classes: "clear-search-icon", button_options: { class: 'clear-search hidden gl-align-self-center gl-mr-1 has-tooltip', title: _('Clear') })
.filter-dropdown-container.gl-flex.gl-flex-direction-column.gl-md-flex-direction-row.gl-align-items-flex-start
= render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, icon: 'clear', icon_classes: "clear-search-icon", button_options: { class: 'clear-search hidden gl-self-center gl-mr-1 has-tooltip', title: _('Clear') })
.filter-dropdown-container.gl-flex.gl-flex-col.md:gl-flex-row.gl-items-start
- if type != :productivity_analytics && show_sorting_dropdown
= render 'shared/issuable/sort_dropdown'

View File

@ -9,12 +9,12 @@
- in_group_context_with_iterations = @project.group.present? && issuable_sidebar[:supports_iterations]
- is_merge_request = issuable_type === 'merge_request'
- add_page_specific_style 'page_bundles/labels'
- sidebar_header_classes = 'md:gl-flex lg:!gl-hidden gl-justify-content-end' if is_merge_request
- sidebar_header_classes = 'md:gl-flex lg:!gl-hidden gl-justify-end' if is_merge_request
%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { auto_collapse: true, always_show_toggle: true, signed: { in: signed_in }, issuable_type: issuable_type }, class: "#{sidebar_gutter_collapsed_class(is_merge_request)} #{'right-sidebar-merge-requests' if is_merge_request}", 'aria-live' => 'polite', 'aria-label': issuable_type }
.issuable-sidebar{ class: "#{'is-merge-request' if is_merge_request}" }
.issuable-sidebar-header{ class: sidebar_header_classes }
= render Pajamas::ButtonComponent.new(button_options: { class: "gutter-toggle gl-float-right js-sidebar-toggle has-tooltip !gl-shadow-none gl-display-block #{'gl-mt-2' if notifications_todos_buttons_enabled?}" , type: 'button', 'aria-label' => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }) do
= render Pajamas::ButtonComponent.new(button_options: { class: "gutter-toggle gl-float-right js-sidebar-toggle has-tooltip !gl-shadow-none gl-block #{'gl-mt-2' if notifications_todos_buttons_enabled?}" , type: 'button', 'aria-label' => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }) do
= sidebar_gutter_toggle_icon
- if signed_in
- if !is_merge_request
@ -22,7 +22,7 @@
- if notifications_todos_buttons_enabled?
.js-sidebar-subscriptions-widget-root
= form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: "issuable-context-form inline-update js-issuable-update #{'gl-pr-2!' if is_merge_request}" } do |f|
= form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: "issuable-context-form inline-update js-issuable-update #{'!gl-pr-2' if is_merge_request}" } do |f|
.block.assignee{ class: "#{'gl-mt-3' if !signed_in}", data: { testid: 'assignee-block-container' } }
= render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in
@ -63,7 +63,7 @@
- if issuable_sidebar[:supports_time_tracking]
.js-sidebar-time-tracking-root.block
// Fallback while content is loading
.title.hide-collapsed.gl-display-flex.gl-justify-content-space-between.gl-align-items-center{ class: 'gl-mb-0!' }
.title.hide-collapsed.gl-flex.gl-justify-between.gl-items-center{ class: '!gl-mb-0' }
%span.gl-font-bold= _('Time tracking')
= gl_loading_icon(inline: true)

View File

@ -4,6 +4,6 @@
.js-sidebar-assignees-root{ data: { field: issuable_type,
max_assignees: dropdown_options[:data][:"max-select"],
directly_invite_members: can_admin_project_member?(@project) } }
.title.hide-collapsed.gl-display-flex.gl-justify-content-space-between.gl-align-items-center{ class: 'gl-mb-0!' }
.title.hide-collapsed.gl-flex.gl-justify-between.gl-items-center{ class: '!gl-mb-0' }
%span.gl-font-bold= s_('Label|Assignee')
= gl_loading_icon(inline: true)

View File

@ -1,7 +1,7 @@
- issuable_type = issuable_sidebar[:type]
.js-sidebar-reviewers-root{ data: { field: issuable_type, signed_in: signed_in } }
.title.hide-collapsed.gl-display-flex.gl-justify-content-space-between.gl-align-items-center{ class: 'gl-mb-0!' }
.title.hide-collapsed.gl-flex.gl-justify-between.gl-items-center{ class: '!gl-mb-0' }
%span.gl-font-bold= _('Reviewers')
= gl_loading_icon(inline: true)

View File

@ -8,4 +8,4 @@
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
= dropdown_tag(users_dropdown_label(issuable.assignees), options: assignees_dropdown_options(issuable.to_ability_name))
= link_to _('Assign to me'), '#', class: "assign-to-me-link gl-whitespace-nowrap gl-md-pl-3 #{'hide' if issuable.assignees.include?(current_user)}", data: { testid: 'assign-to-me-link' }
= link_to _('Assign to me'), '#', class: "assign-to-me-link gl-whitespace-nowrap md:gl-pl-3 #{'hide' if issuable.assignees.include?(current_user)}", data: { testid: 'assign-to-me-link' }

View File

@ -19,7 +19,7 @@
= f.label :color, _("Background color")
.input-group
.input-group-prepend
%input.label-color-preview.gl-w-7.gl-h-full.gl-border-1.gl-border-solid.gl-border-gray-500.gl-border-r-0.gl-rounded-top-right-none.gl-rounded-bottom-right-none{ type: "color", placeholder: _('Select color') }
%input.label-color-preview.gl-w-7.gl-h-full.gl-border-1.gl-border-solid.gl-border-gray-500.gl-border-r-0.gl-rounded-tr-none.gl-rounded-br-none{ type: "color", placeholder: _('Select color') }
= f.text_field :color, class: "gl-form-input form-control", data: { testid: 'label-color-field' }
.form-text.text-muted
= _('Select a color from the color picker or from the presets below.')
@ -31,7 +31,7 @@
_('Lock label after a merge request is merged'),
help_text: label_lock_on_merge_help_text,
checkbox_options: { disabled: @label.lock_on_merge }
.gl-display-flex.gl-justify-content-space-between
.gl-flex.gl-justify-between
%div
- if @label.persisted?
= f.submit _('Save changes'), class: 'js-save-button gl-mr-2', pajamas_button: true

View File

@ -1,7 +1,7 @@
- subscribed = params[:subscribed]
.top-area.adjust
= gl_tabs_nav({ class: 'gl-flex-grow-1 gl-border-0' }) do
= gl_tabs_nav({ class: 'gl-grow gl-border-0' }) do
= gl_tab_link_to _('All'), labels_filter_path, { item_active: subscribed != 'true' }
- if current_user
= gl_tab_link_to _('Subscribed'), labels_filter_path(subscribed: 'true'), { item_active: subscribed == 'true' }

View File

@ -34,7 +34,7 @@
- if source.instance_of?(Group) && source != membership_source
&middot;
= link_to source.full_name, source, class: "gl-display-inline-block inline-link"
= link_to source.full_name, source, class: "gl-inline-block inline-link"
.gl-text-secondary.gl-text-sm
- if member.request?
@ -61,4 +61,4 @@
- if show_roles
.controls.member-controls.gl-items-center.gl-self-end
= render_if_exists 'shared/members/ee/ldap_tag', can_override: member.can_override?
= render Pajamas::BadgeComponent.new(member.human_access, variant: 'neutral', class: 'gl-bg-transparent! !gl-shadow-inner-1-gray-100')
= render Pajamas::BadgeComponent.new(member.human_access, variant: 'neutral', class: '!gl-bg-transparent !gl-shadow-inner-1-gray-100')

View File

@ -1,2 +1,2 @@
.gl-px-3.gl-py-3.gl-display-flex.gl-flex-direction-column.gl-md-flex-direction-row
.gl-px-3.gl-py-3.gl-flex.gl-flex-col.md:gl-flex-row
= yield

View File

@ -1,2 +1,2 @@
.gl-display-flex.gl-md-align-items-center.gl-flex-direction-column.gl-md-flex-direction-row.row-content-block.second-block
.gl-flex.md:gl-items-center.gl-flex-col.md:gl-flex-row.row-content-block.second-block
= yield

View File

@ -1,2 +1,2 @@
%span.gl-flex-grow-1.gl-py-3.gl-pr-3
%span.gl-grow.gl-py-3.gl-pr-3
= yield

View File

@ -14,7 +14,7 @@
- can_promote = @project && can_admin_group_milestones? && milestone.project
- can_read_milestone = can?(current_user, :read_milestone, @milestone)
.milestone-buttons.detail-page-header-actions.gl-display-flex.gl-align-self-start.gl-gap-3
.milestone-buttons.detail-page-header-actions.gl-flex.gl-self-start.gl-gap-3
- if milestone.active?
= render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :close }), method: :put, button_options: { class: 'btn-close gl-hidden md:gl-inline-block' }) do
= _('Close milestone')

View File

@ -5,10 +5,10 @@
- c.with_header do
.gl-flex-grow-2
= title
.gl-ml-3.gl-flex-shrink-0.gl-font-bold.gl-whitespace-nowrap{ class: milestone_counter_class(primary) }
.gl-ml-3.gl-shrink-0.gl-font-bold.gl-whitespace-nowrap{ class: milestone_counter_class(primary) }
- if show_counter
%span
= sprite_icon('issues', css_class: 'gl-vertical-align-text-bottom')
= sprite_icon('issues', css_class: 'gl-align-text-bottom')
= number_with_delimiter(issuables.length)
= render_if_exists "shared/milestones/issuables_weight", issuables: issuables
- c.with_body do

View File

@ -11,11 +11,11 @@
- open_issues = milestone_issues_by_label_count(@milestone, label, state: :opened)
- closed_issues = milestone_issues_by_label_count(@milestone, label, state: :closed)
%li.gl-list-style-none.gl-border-b.gl-last-of-type-border-b-0.md:gl-flex.gl-px-5.gl-py-4.gl-gap-5.gl-align-items-baseline
.gl-md-w-20.gl-flex-shrink-0.gl-flex-grow-1.gl-mb-4.gl-md-mb-0
%li.gl-list-none.gl-border-b.gl-last-of-type-border-b-0.md:gl-flex.gl-px-5.gl-py-4.gl-gap-5.gl-items-baseline
.md:gl-w-20.gl-shrink-0.gl-grow.gl-mb-4.md:gl-mb-0
= render_label(label, tooltip: false, link: milestones_issues_path(options))
- if markdown_field(label, :description).present?
.gl-w-full.gl-mb-4.gl-md-mb-0
.gl-w-full.gl-mb-4.md:gl-mb-0
= markdown_field(label, :description)
= render Pajamas::ButtonComponent.new(variant: :link, disabled: open_issues == 0, href: milestones_issues_path(options.merge(state: 'opened'))) do
= open_issues

View File

@ -50,7 +50,7 @@
- if can_admin_milestone
- show_delete = @project.present? || @group.present?
.col-1.order-2.order-md-3
.gl-flex.gl-justify-content-end
.gl-flex.gl-justify-end
.js-vue-milestone-actions{ data: { id: milestone.id,
title: milestone.title,
is_active: milestone.active?.to_s,

View File

@ -1,8 +1,8 @@
%ul.bordered-list
- users.each do |user|
%li
= link_to user, title: user.name, class: "gl-display-flex" do
= link_to user, title: user.name, class: "gl-flex" do
= render Pajamas::AvatarComponent.new(user, size: 32, class: "gl-mr-3")
.gl-display-flex.gl-flex-direction-column
.gl-flex.gl-flex-col
%strong= truncate(user.name, length: 40)
%small.gl-text-primary= user.username

View File

@ -165,8 +165,8 @@
- if milestone_ref.present?
.block.reference
= clipboard_button(text: milestone_ref, title: s_('MilestoneSidebar|Copy reference'), placement: "left", boundary: 'viewport', class: 'sidebar-collapsed-icon js-dont-change-state')
.gl-display-flex.gl-align-items-center.gl-justify-content-space-between.gl-mb-2.hide-collapsed
%span.gl-overflow-hidden.gl-text-overflow-ellipsis.gl-whitespace-nowrap
.gl-flex.gl-items-center.gl-justify-between.gl-mb-2.hide-collapsed
%span.gl-overflow-hidden.gl-text-ellipsis.gl-whitespace-nowrap
= s_('MilestoneSidebar|Reference:')
%span{ title: milestone_ref }
= milestone_ref

View File

@ -1,5 +1,5 @@
- noteable_name = @note.noteable.human_class_name
.js-comment-type-dropdown.float-left.gl-sm-mr-3{ data: { noteable_name: noteable_name } }
.js-comment-type-dropdown.float-left.sm:gl-mr-3{ data: { noteable_name: noteable_name } }
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { class: 'js-comment-button js-comment-submit-button', value: _('Comment'), data: { testid: 'comment-button' }}) do
= _('Comment')

View File

@ -5,7 +5,7 @@
- else
- preview_url = preview_markdown_path(@project)
= form_for form_resources, url: new_form_url, remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form discussion-reply-holder gl-border-top-0!", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f|
= form_for form_resources, url: new_form_url, remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form discussion-reply-holder !gl-border-t-0", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
= hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha)
@ -35,7 +35,7 @@
= render 'shared/notes/hints'
.error-alert
.note-form-actions.clearfix.gl-display-flex.gl-flex-wrap
.note-form-actions.clearfix.gl-flex.gl-flex-wrap
= render partial: 'shared/notes/comment_button'
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-close-discussion-note-form hide' }) do

View File

@ -1,11 +1,11 @@
- supports_file_upload = local_assigns.fetch(:supports_file_upload, true)
.comment-toolbar.gl-px-2.gl-display-flex.gl-justify-content-end.gl-rounded-bottom-left-base.gl-rounded-bottom-right-base.clearfix
.content-editor-switcher.gl-inline-flex.gl-align-items-center
= render Pajamas::ButtonComponent.new(category: :tertiary, icon: 'markdown-mark', size: :small, href: help_page_path('user/markdown'), target: '_blank', button_options: { class: 'gl-px-3!' })
.comment-toolbar.gl-px-2.gl-flex.gl-justify-end.gl-rounded-bl-base.gl-rounded-br-base.clearfix
.content-editor-switcher.gl-inline-flex.gl-items-center
= render Pajamas::ButtonComponent.new(category: :tertiary, icon: 'markdown-mark', size: :small, href: help_page_path('user/markdown'), target: '_blank', button_options: { class: '!gl-px-3' })
- if supports_file_upload
%span.uploading-container.gl-leading-32.gl-font-sm
%span.uploading-container.gl-leading-32.gl-text-sm
%span.uploading-progress-container.hide
= sprite_icon('paperclip', css_class: 'gl-icon gl-vertical-align-text-bottom')
= sprite_icon('paperclip', css_class: 'gl-icon gl-align-text-bottom')
%span.attaching-file-message
-# Populated by app/assets/javascripts/dropzone_input.js
%span.uploading-progress 0%
@ -13,7 +13,7 @@
%span.uploading-error-container.hide
%span.uploading-error-icon
= sprite_icon('paperclip', css_class: 'gl-icon gl-vertical-align-text-bottom')
= sprite_icon('paperclip', css_class: 'gl-icon gl-align-text-bottom')
%span.uploading-error-message
-# Populated by app/assets/javascripts/dropzone_input.js
= render Pajamas::ButtonComponent.new(variant: :link, button_options: { class: 'retry-uploading-link' }) do

View File

@ -26,7 +26,7 @@
- elsif note_counter == 0
- counter = badge_counter if local_assigns[:badge_counter]
- badge_class = "hidden" if @fresh_discussion || counter.nil?
%span.gl-display-flex.gl-align-items-center.gl-justify-content-center.gl-font-sm.design-note-pin.small.user-avatar{ class: badge_class }
%span.gl-flex.gl-items-center.gl-justify-center.gl-text-sm.design-note-pin.small.user-avatar{ class: badge_class }
= counter
.timeline-content
.note-header
@ -67,8 +67,8 @@
- if note.system
.system-note-commit-list-toggler.hide
= _("Toggle commit list")
= sprite_icon('chevron-down', css_class: 'js-chevron-down gl-ml-1 gl-vertical-align-text-bottom')
= sprite_icon('chevron-up', css_class: 'js-chevron-up gl-ml-1 gl-vertical-align-text-bottom gl-hidden')
= sprite_icon('chevron-down', css_class: 'js-chevron-down gl-ml-1 gl-align-text-bottom')
= sprite_icon('chevron-up', css_class: 'js-chevron-up gl-ml-1 gl-align-text-bottom gl-hidden')
- if note.attachment.url
.note-attachment
- if note.attachment.image?

View File

@ -1,5 +1,5 @@
- @sort ||= sort_value_latest_activity
.dropdown.js-project-filter-dropdown-wrap.gl-inline{ class: 'gl-m-0!' }
.dropdown.js-project-filter-dropdown-wrap.gl-inline{ class: '!gl-m-0' }
= dropdown_toggle(projects_sort_options_hash[@sort], { toggle: 'dropdown', display: 'static' }, { id: 'sort-projects-dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header

View File

@ -37,7 +37,7 @@
- load_max_project_member_accesses(projects) # Prime cache used in shared/projects/project view rendered below
- load_catalog_resources(projects)
- if card_mode
.projects-list.gl-text-secondary.gl-w-full.gl-display-flex.gl-flex-direction-column.gl-lg-flex-direction-row.gl-gap-4.gl-overflow-x-auto{ class: css_classes }
.projects-list.gl-text-secondary.gl-w-full.gl-flex.gl-flex-col.lg:gl-flex-row.gl-gap-4.gl-overflow-x-auto{ class: css_classes }
- projects.take(projects_limit).each_with_index do |project, i| # rubocop: disable CodeReuse/ActiveRecord -- it's Enumerable#take
= render "shared/projects/project_card", project: project, skip_namespace: skip_namespace,

View File

@ -9,12 +9,12 @@
- use_creator_avatar = local_assigns[:use_creator_avatar]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
- access = max_project_member_access(project)
- css_class = "sm:gl-flex gl-align-items-center !gl-align-middle" if project.description.blank? && !show_last_commit_as_description
- css_class = "sm:gl-flex gl-items-center !gl-align-middle" if project.description.blank? && !show_last_commit_as_description
- updated_tooltip = time_ago_with_tooltip(project.last_activity_at || project.updated_at)
- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
- last_pipeline = last_pipeline_from_status_cache(project) if show_pipeline_status_icon
- css_controls_class = "with-pipeline-status" if show_pipeline_status_icon && last_pipeline.present?
- css_metadata_classes = "gl-display-flex gl-align-items-center !gl-text-inherit icon-wrapper has-tooltip"
- css_metadata_classes = "gl-flex gl-items-center !gl-text-inherit icon-wrapper has-tooltip"
%li.project-row
- if avatar
@ -26,9 +26,9 @@
= render Pajamas::AvatarComponent.new(project, size: 48, alt: '')
.project-cell{ class: css_class }
.project-details.gl-pr-9.gl-sm-pr-0.gl-w-full.gl-display-flex.gl-flex-direction-column{ data: { testid: 'project-content', qa_project_name: project.name } }
.gl-display-flex.gl-align-items-baseline.gl-flex-wrap
%h2.gl-font-base.gl-leading-20.gl-my-0.gl-wrap-anywhere
.project-details.gl-pr-9.gl-sm-pr-0.gl-w-full.gl-flex.gl-flex-col{ data: { testid: 'project-content', qa_project_name: project.name } }
.gl-flex.gl-items-baseline.gl-flex-wrap
%h2.gl-text-base.gl-leading-20.gl-my-0.gl-wrap-anywhere
= link_to project_path(project), class: 'gl-text-default hover:gl-text-default gl-mr-3 js-prefetch-document', title: project.name do
%span.namespace-name.gl-font-normal
- if project.namespace && !skip_namespace
@ -43,26 +43,26 @@
= visibility_level_content(project, css_class: 'gl-mr-2')
- if project.catalog_resource
= render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(project.catalog_resource), css_class: 'gl-align-self-center gl-mr-2' }
= render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(project.catalog_resource), css_class: 'gl-self-center gl-mr-2' }
- if explore_projects_tab? && project_license_name(project)
%span.gl-inline-flex.gl-align-items-center.gl-mr-3
%span.gl-inline-flex.gl-items-center.gl-mr-3
= sprite_icon('scale', size: 14, css_class: 'gl-mr-2')
= project_license_name(project)
- if !explore_projects_tab? && access&.nonzero?
-# haml-lint:disable UnnecessaryStringOutput
= ' ' # prevent haml from eating the space between elements
= render Pajamas::BadgeComponent.new(localized_project_human_access(access), variant: 'neutral', class: 'gl-bg-transparent! !gl-shadow-inner-1-gray-100', data: { testid: 'user-access-role' })
= render Pajamas::BadgeComponent.new(localized_project_human_access(access), variant: 'neutral', class: '!gl-bg-transparent !gl-shadow-inner-1-gray-100', data: { testid: 'user-access-role' })
- if !explore_projects_tab?
= render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: project, additional_classes: 'gl-ml-2!'
= render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: project, additional_classes: '!gl-ml-2'
- if show_last_commit_as_description
.description.gl-hidden.sm:gl-block.gl-overflow-hidden.gl-mr-3.gl-mt-2.gl-font-sm
.description.gl-hidden.sm:gl-block.gl-overflow-hidden.gl-mr-3.gl-mt-2.gl-text-sm
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present?
.description.gl-hidden.sm:gl-block.gl-overflow-hidden.gl-mr-3.gl-mt-2.gl-font-sm
.description.gl-hidden.sm:gl-block.gl-overflow-hidden.gl-mr-3.gl-mt-2.gl-text-sm
= markdown_field(project, :description)
- if project.topics.any?
@ -71,8 +71,8 @@
= render_if_exists 'shared/projects/removed', project: project
.gl-display-flex.gl-align-items-center.gl-mt-3{ class: "#{css_class} sm:!gl-hidden" }
.controls.gl-display-flex.gl-align-items-center
.gl-flex.gl-items-center.gl-mt-3{ class: "#{css_class} sm:!gl-hidden" }
.controls.gl-flex.gl-items-center
- if show_pipeline_status_icon && last_pipeline.present?
- pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
%span.icon-wrapper.pipeline-status
@ -83,14 +83,14 @@
= link_to project_starrers_path(project), class: "#{css_metadata_classes} stars", title: _('Stars'), data: { container: 'body', placement: 'top' } do
= sprite_icon('star-o', size: 14, css_class: 'gl-mr-2')
= badge_count(project.star_count)
.updated-note.gl-font-sm.gl-ml-3.gl-sm-ml-0
.updated-note.gl-text-sm.gl-ml-3.gl-sm-ml-0
%span
= _('Updated')
= updated_tooltip
= render_if_exists 'shared/projects/actions', project: project
.project-cell.project-controls{ class: "#{css_class} !gl-hidden sm:!gl-table-cell", data: { testid: 'project_controls'} }
.controls.gl-display-flex.gl-align-items-center.gl-mb-2.gl-gap-4{ class: "#{css_controls_class} gl-pr-0! gl-justify-content-end!" }
.controls.gl-flex.gl-items-center.gl-mb-2.gl-gap-4{ class: "#{css_controls_class} !gl-pr-0 !gl-justify-end" }
- if show_pipeline_status_icon && last_pipeline.present?
- pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
%span.icon-wrapper.pipeline-status
@ -114,7 +114,7 @@
= sprite_icon('issues', size: 14, css_class: 'gl-mr-2')
= badge_count(project.open_issues_count)
= render_if_exists 'shared/projects/actions', project: project
.updated-note.gl-font-sm.gl-whitespace-nowrap.gl-justify-content-end
.updated-note.gl-text-sm.gl-whitespace-nowrap.gl-justify-end
%span
= _('Updated')
= updated_tooltip

View File

@ -26,7 +26,7 @@
.gl-w-full.gl-pt-2.gl-break-anywhere
.gl-flex.gl-items-center.gl-flex-wrap
%h2.gl-font-base.gl-leading-20.gl-my-0
%h2.gl-text-base.gl-leading-20.gl-my-0
= link_to project_path(project), class: 'gl-text-default hover:gl-text-default gl-mr-3 js-prefetch-document', title: project.name do
%span.namespace-name.gl-font-normal
- if project.namespace && !skip_namespace
@ -38,10 +38,10 @@
= visibility_level_content(project)
- if show_last_commit_as_description
.description.gl-hidden.sm:gl-block.gl-mt-2.gl-font-sm
.description.gl-hidden.sm:gl-block.gl-mt-2.gl-text-sm
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present?
.description.gl-hidden.sm:gl-block.gl-mt-2.gl-font-sm
.description.gl-hidden.sm:gl-block.gl-mt-2.gl-text-sm
= markdown_field(project, :description)
- if project.topics.any?
@ -83,7 +83,7 @@
= render 'ci/status/icon', status: last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
= render_if_exists 'shared/projects/badges', project: project
.updated-note.gl-font-sm.gl-whitespace-nowrap.gl-justify-content-start
.updated-note.gl-text-sm.gl-whitespace-nowrap.gl-justify-start
%span
= _('Updated')
= updated_tooltip

View File

@ -47,5 +47,5 @@
- if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id])
- selected_text = "#{namespace.kind}: #{namespace.full_path}" if namespace
.gl-flex.gl-w-full.gl-md-w-auto{ class: '!gl-m-0' }
.gl-flex.gl-w-full.md:gl-w-auto{ class: '!gl-m-0' }
.js-namespace-select{ data: { field_name: 'namespace_id', selected_id: namespace&.id, selected_text: selected_text, update_location: 'true' } }

View File

@ -1,7 +1,7 @@
- max_project_topic_length = 15
- if project.topics.present?
.gl-w-full.gl-inline-flex.gl-flex-wrap.gl-font-base.gl-font-normal.gl-align-items-center.-gl-mx-2.-gl-my-2{ 'data-testid': 'project_topic_list' }
.gl-w-full.gl-inline-flex.gl-flex-wrap.gl-text-base.gl-font-normal.gl-items-center.-gl-mx-2.-gl-my-2{ 'data-testid': 'project_topic_list' }
- project.topics_to_show.each do |topic|
- explore_project_topic_path = topic_explore_projects_cleaned_path(topic_name: topic[:name])
- if topic[:title].length > max_project_topic_length

View File

@ -1,4 +1,4 @@
%h1.page-title.gl-font-size-h-display.gl-display-flex.gl-align-items-center
%h1.page-title.gl-text-size-h-display.gl-flex.gl-items-center
= s_('Runners|Runner #%{runner_id}') % { runner_id: runner.id }
= render 'shared/runners/runner_type_badge', runner: runner

View File

@ -1,23 +1,23 @@
- link_project = local_assigns.fetch(:link_project, false)
- notes_count = @noteable_meta_data[snippet.id].user_notes_count
%li.gl-py-4.sm:gl-flex.gl-align-items-center.gl-justify-content-space-between.gl-border-b{ data: { testid: 'snippet-link', qa_snippet_title: snippet.title } }
%li.gl-py-4.sm:gl-flex.gl-items-center.gl-justify-between.gl-border-b{ data: { testid: 'snippet-link', qa_snippet_title: snippet.title } }
- unless current_path?('users#snippets')
.gl-hidden.sm:gl-block.gl-sm-mr-3
.gl-hidden.sm:gl-block.sm:gl-mr-3
= render Pajamas::AvatarComponent.new(snippet.author, size: 32, alt: "")
.sm:gl-flex.gl-justify-content-space-between.gl-align-items-center.gl-flex-grow-1
.sm:gl-flex.gl-justify-between.gl-items-center.gl-grow
%div
= link_to gitlab_snippet_path(snippet), class: "title gl-text-black-normal gl-font-bold" do
= link_to gitlab_snippet_path(snippet), class: "title gl-text-default gl-font-bold" do
= snippet.title
- if snippet.hidden_due_to_author_ban?
%span{ class: 'has-tooltip gl-bg-orange-50 gl-text-orange-600 border-radius-default gl-p-2', title: s_("Snippets|This snippet is hidden because its author has been banned") }
= sprite_icon('spam', size: '16')
.snippet-info.gl-font-sm.gl-text-gray-500!
.snippet-info.gl-text-sm{ class: '!gl-text-gray-500' }
.gl-inline{ data: { testid: 'snippet-created-at'} }
- created_at = time_ago_with_tooltip(snippet.created_at, placement: 'bottom')
- author = link_to(snippet.author_name, user_snippets_path(snippet.author), data: { user_id: snippet.author.id }, class: 'gl-text-black-normal')
- author = link_to(snippet.author_name, user_snippets_path(snippet.author), data: { user_id: snippet.author.id }, class: 'gl-text-default')
#{snippet.to_reference} &middot;
- if link_project && snippet.project_id?
- project_link = link_to(snippet.project.full_name, project_path(snippet.project))
@ -25,8 +25,8 @@
- else
= _('created %{timeAgo} by %{author}').html_safe % { timeAgo: created_at, author: author }
.sm:gl-flex.gl-flex-direction-column.gl-align-items-flex-end{ data: { testid: 'snippet-file-count-content', qa_snippet_files: snippet.statistics&.file_count } }
.gl-display-flex.gl-gap-4.gl-align-items-center
.sm:gl-flex.gl-flex-col.gl-items-end{ data: { testid: 'snippet-file-count-content', qa_snippet_files: snippet.statistics&.file_count } }
.gl-flex.gl-gap-4.gl-items-center
- if notes_count > 0
%span.has-tooltip{ title: _('Comments') }
= sprite_icon('comments')
@ -34,4 +34,4 @@
= snippet_file_count(snippet)
%span.has-tooltip{ title: visibility_level_label(snippet.visibility_level), data: { testid: 'snippet-visibility-content', qa_snippet_visibility: visibility_level_label(snippet.visibility_level) } }
= visibility_level_icon(snippet.visibility_level)
.gl-whitespace-nowrap.gl-font-sm.gl-text-secondary= _('updated %{timeAgo}').html_safe % { timeAgo: time_ago_with_tooltip(snippet.updated_at, placement: 'bottom') }
.gl-whitespace-nowrap.gl-text-sm.gl-text-secondary= _('updated %{timeAgo}').html_safe % { timeAgo: time_ago_with_tooltip(snippet.updated_at, placement: 'bottom') }

View File

@ -3,7 +3,7 @@
.col-lg-3.col-md-4.col-sm-12
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' },
body_options: { class: 'gl-display-flex gl-align-items-center' }) do |c|
body_options: { class: 'gl-flex gl-items-center' }) do |c|
- c.with_body do
= link_to detail_page_link do
= render Pajamas::AvatarComponent.new(topic, size: 48, alt: '', class: 'gl-mr-3')

View File

@ -12,7 +12,7 @@
= yield :usage_quotas_subtitle
#js-usage-quotas-view
.gl-font-lg.text-center
.gl-text-lg.text-center
%span.gl-mr-1= s_('UsageQuota|Loading Usage Quotas tabs')
= render Pajamas::SpinnerComponent.new(inline: true, size: :md)
= yield :usage_quotas_tabs

View File

@ -23,7 +23,7 @@
= gl_badge_tag(integration_webhook_event_human_name(trigger), variant: :neutral)
= gl_badge_tag(sslBadgeText, variant: :neutral)
.gl-font-sm
.gl-text-sm
= truncate(hook.description, length: 200)
.gl-flex.gl-items-baseline.gl-gap-3

View File

@ -32,7 +32,7 @@
%strong
= commit_author_link(commit, avatar: true, size: 24)
- if commit.description.present?
%pre.commit-description.gl-whitespace-pre-wrap.gl-line-height-normal
%pre.commit-description.gl-whitespace-pre-wrap.gl-leading-normal
= preserve(markdown_field(commit, :description))
= render 'projects/diffs/diffs', diffs: @diffs, diff_page_context: "is-wiki"

View File

@ -5,9 +5,9 @@
- git_access_url = wiki_path(@wiki, action: :git_access)
.wiki-page-header.top-area.gl-flex-direction-column.gl-lg-flex-direction-row
.wiki-page-header.top-area.gl-flex-col.lg:gl-flex-row
.gl-mt-5.gl-mb-3
.gl-display-flex.gl-justify-content-space-between
.gl-flex.gl-justify-between
%h2.gl-mt-0.gl-mb-5{ data: { testid: 'page-heading' } }= @page ? @page.human_title : _('Failed to retrieve page')
.js-wiki-page-content.md.gl-pt-2{ data: { testid: 'wiki-page-content' } }
= _('The page could not be displayed because it timed out.')

View File

@ -1,9 +1,9 @@
---
name: rules_exist_expand_globs_early
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/386595
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160446
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/477245
milestone: '17.3'
group: group::pipeline authoring
type: gitlab_com_derisk
name: prompt_migration_refactor_code
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/475050
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162716
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/479117
milestone: '17.4'
group: group::custom models
type: experiment
default_enabled: false

View File

@ -0,0 +1,100 @@
- name: Troubleshoot failed jobs with root cause analysis
description: |
Root cause analysis is now generally available. With root cause analysis, you can troubleshoot failed jobs in CI/CD pipelines faster. This AI-powered feature analyzes the failed job log, quickly determines the root cause of the job failure, and suggests a fix for you.
stage: verify
self-managed: true
gitlab-com: true
available_in: [Ultimate]
documentation_link: https://docs.gitlab.com/ee/user/gitlab_duo_chat/examples.html#troubleshoot-failed-cicd-jobs-with-root-cause-analysis
image_url: https://img.youtube.com/vi/Yf7Iidf2GW8/hqdefault.jpg
published_at: 2024-08-15
release: 17.3
- name: Resolve a vulnerability with AI
description: |
Vulnerability resolution uses AI to give specific code suggestions for users to fix vulnerabilities. With the click of a button you can open a merge request to get started resolving any SAST vulnerability from the [list of supported CWE identifiers](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#availability).
stage: govern
self-managed: true
gitlab-com: true
available_in: [Ultimate]
documentation_link: https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#vulnerability-resolution
image_url: https://about.gitlab.com/images/17_3/vulnerability_resolution.png
published_at: 2024-08-15
release: 17.3
- name: Add multiple compliance frameworks to a single project
description: |
You can create a compliance framework to identify that your project has certain compliance requirements or needs additional oversight.
The compliance framework can optionally enforce compliance pipeline configuration to the projects on which it is applied.
Previously, you could apply only one compliance framework to a project, which limited how many compliance requirements could be set on a project.
We have now provided the ability for a user to apply multiple different compliance frameworks per project. The project is then set with the compliance requirements of each framework.
stage: govern
self-managed: true
gitlab-com: true
available_in: [Premium, Ultimate]
documentation_link: https://docs.gitlab.com/ee/user/group/compliance_frameworks.html#add-a-compliance-framework-to-a-project
image_url: https://about.gitlab.com/images/17_3/multiple-compliance-frameworks.png
published_at: 2024-08-15
release: 17.3
- name: Delete a pod from the GitLab UI
description: |
Have you ever needed to restart or delete a failing pod in Kubernetes? Until now, you had to leave GitLab, use another tool to connect to the cluster, stop the pod, and wait for a new pod to start. GitLab now has built-in support for deleting pods, so you can smoothly troubleshoot your Kubernetes clusters.
You can stop a pod from a [dashboard for Kubernetes](https://docs.gitlab.com/ee/ci/environments/kubernetes_dashboard.html), which lists all the pods across your cluster or namespace.
stage: deploy
self-managed: true
gitlab-com: true
available_in: [Free, Premium, Ultimate]
documentation_link: https://docs.gitlab.com/ee/ci/environments/kubernetes_dashboard.html#delete-a-pod
image_url: https://about.gitlab.com/images/17_3/delete-pod.png
published_at: 2024-08-15
release: 17.3
- name: Easily connect to a cluster from your local terminal
description: |
Do you want to connect to a Kubernetes cluster from your local terminal or using one of the desktop Kubernetes GUI tools?
GitLab allows you to connect to a terminal using the [user access feature of the agent for Kubernetes](https://docs.gitlab.com/ee/user/clusters/agent/user_access.html). Previously, finding commands required navigating out of GitLab to browse the documentation. Now, GitLab provides the **connect** command from the UI. GitLab can even help you configure user access!
To retrieve the connection command, either go to a [Kubernetes dashboard](https://docs.gitlab.com/ee/ci/environments/kubernetes_dashboard.html), or to the [agent list](https://docs.gitlab.com/ee/user/clusters/agent/work_with_agent.html#view-your-agents).
stage: deploy
self-managed: true
gitlab-com: true
available_in: [Free, Premium, Ultimate]
documentation_link: https://docs.gitlab.com/ee/user/clusters/agent/user_access.html
image_url: https://about.gitlab.com/images/17_3/17-3-connect-agent.png
published_at: 2024-08-15
release: 17.3
- name: Health check for GitLab Duo in beta
description: |
You can now troubleshoot the setup for GitLab Duo on your self-managed instance. In the **Admin** area, on the GitLab Duo page, select **Run health check**. This health check performs a series of validations and suggests appropriate corrective actions to ensure GitLab Duo is operational.
The health check for GitLab Duo is available on Self-managed and GitLab Dedicated as a beta feature.
stage: data_store
self-managed: true
gitlab-com: false
available_in: [Premium, Ultimate]
documentation_link: https://docs.gitlab.com/ee/user/gitlab_duo/turn_on_off.html#run-a-health-check-for-gitlab-duo
image_url: https://about.gitlab.com/images/17_3/Failed_GitLab_Duo_health_check.png
published_at: 2024-08-15
release: 17.3
- name: "AI Impact analytics: Code Suggestions acceptance rate and GitLab Duo seats usage"
description: |
These two new metrics highlight the effectiveness and utilization of GitLab Duo, and are now included in the [AI Impact analytics in the Value Streams Dashboard](https://about.gitlab.com/blog/2024/05/15/developing-gitlab-duo-ai-impact-analytics-dashboard-measures-the-roi-of-ai/), which helps organizations understand the impact of GitLab Duo on delivering business value.
The **Code Suggestions acceptance rate** metric indicates how frequently developers accept code suggestions made by GitLab Duo. This metric reflects both the effectiveness of these suggestions and the level of trust contributors have in AI capabilities. Specifically, the metric represents the percentage of code suggestions provided by GitLab Duo that have been accepted by code contributors in the last 30 days.
The **GitLab Duo seats assigned and used** metric shows the percentage of consumed licensed seats, helping organizations plan effectively for license utilization, resource allocation, and understanding of usage patterns. This metric tracks the ratio of assigned seats that have used at least one AI feature in the last 30 days.
With the addition of these new metrics, we have also introduced new overview tiles — a new visualization which provides a clear summary of the metrics, helping you quickly assess the current state of your AI features.
stage: data_store
self-managed: true
gitlab-com: false
available_in: [Ultimate]
documentation_link: https://docs.gitlab.com/ee/user/analytics/value_streams_dashboard.html#ai-impact-analytics
image_url: https://about.gitlab.com/images/17_3/173_ai_tiles.png
published_at: 2024-08-15
release: 17.3

View File

@ -8,4 +8,4 @@ description: A set of cost factors per runner which are applied to ci job durati
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111977
milestone: '15.10'
gitlab_schema: gitlab_ci
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/460076
exempt_from_sharding: true # Cell-local table storing cost information for instance runners

View File

@ -182,15 +182,32 @@ To enable an Outbound Private Link:
required validation, and let GitLab know in the support ticket that you are using this option. If `Acceptance Required` is set to Yes on your
Endpoint Service, also note this on the support ticket because Dedicated will need to initiate the connection without Private DNS, wait for you
to confirm it has been accepted, and then update the connection to enable the use of Private DNS.
- Dedicated can manage a Private Hosted Zone (PHZ) within the Dedicated AWS Account and alias any arbitrary DNS names to the Endpoint, directing
requests for those names to your Endpoint Service. This may be useful if you have multiple DNS names/aliases that will be accessed using a
single Endpoint (for example, if you are running a reverse proxy to connect to more than one service in your environment), or if the domain you
want to use is not public and cannot be validated for use by Private DNS. Let GitLab know on the support ticket if you are using this option and
provide a list of DNS names that should resolve to the Private Link Endpoint. This list can be updated as needed in future.
- Dedicated can manage a private hosted zone (PHZ) within the Dedicated AWS account and alias DNS names to the endpoint, directing requests for those names to your endpoint service. These aliases are often referred to as PHZ entries. For more information, see [Private hosted zones](#private-hosted-zones).
GitLab then configures the tenant instance to create the necessary Endpoint Interfaces based on the service names you provided. Any matching outbound
connections made from the tenant instance are directed through the PrivateLink into your VPC.
#### Private hosted zones
You can use a private hosted zone (PHZ) if:
- You have multiple DNS names or aliases that will be accessed using a single endpoint. For example, if you are running a reverse proxy to connect to more than one service in your environment.
- The domain you want to use is not public and cannot be validated for use by private DNS.
To use private hosted zones, submit a [support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650). In the support ticket, provide a list of DNS names that should resolve to the endpoint service for the outbound private link. The list can be updated as needed.
When using your Dedicated instance's domain as part of an alias, you must include two subdomains before the main domain. This is because:
1. The first subdomain becomes the name of the PHZ.
1. The second subdomain becomes the record entry for the alias.
For example:
- This is a valid PHZ entry: `subdomain2.subdomain1.<your-tenant-id>.gitlab-dedicated.com`.
- This is an invalid PHZ entry: `subdomain1.<your-tenant-id>.gitlab-dedicated.com`.
If you don't use the Dedicated instance domain, the PHZ name and a PHZ entry in the format `phz-entry.phz-name.com` is still required.
### Custom certificates
In some cases, the GitLab Dedicated instance can't reach an internal service you own because it exposes a certificate that can't be validated using a public Certification Authority (CA). In these cases, custom certificates are required.

View File

@ -27,7 +27,6 @@ when a snippet's original author wasn't available. For example, when the user wa
Other examples of internal users:
- [GitLab Admin Bot](https://gitlab.com/gitlab-org/gitlab/-/blob/278bc9018dd1515a10cbf15b6c6cd55cb5431407/app/models/user.rb#L950-960)
- [GitLab Automation Bot](../user/group/iterations/index.md#gitlab-automation-bot-user)
- [Alert Bot](../operations/incident_management/alerts.md#trigger-actions-from-alerts)
- [Ghost User](../user/profile/account/delete_account.md#associated-records)
@ -36,3 +35,15 @@ Other examples of internal users:
- Resource access tokens, including [project access tokens](../user/project/settings/project_access_tokens.md)
and [group access tokens](../user/group/settings/group_access_tokens.md), which are
`project_{project_id}_bot_{random_string}` and `group_{group_id}_bot_{random_string}` users with a `PersonalAccessToken`.
## GitLab Admin Bot
[GitLab Admin Bot](https://gitlab.com/gitlab-org/gitlab/-/blob/1d38cfdbed081f8b3fa14b69dd743440fe85081b/lib/users/internal.rb#L104)
is an internal user that cannot be accessed or modified by regular users and is responsible for many tasks including:
- Applying [default compliance frameworks](../user/group/compliance_frameworks.md#default-compliance-frameworks) to
projects.
- [Automatically deactivating dormant users](moderate_users.md#automatically-deactivate-dormant-users).
- [Automatically deleting unconfirmed users](moderate_users.md#automatically-delete-unconfirmed-users).
- [Deleting inactive projects](inactive_project_deletion.md).
- [Locking users](../security/unlock_user.md).

View File

@ -32,7 +32,7 @@ POST /projects/:id/ci/lint
Example request:
```shell
curl --header "Content-Type: application/json" "https://gitlab.example.com/api/v4/projects/:id/ci/lint" --data '{"content": "{ \"image\": \"ruby:2.6\", \"services\": [\"postgres\"], \"before_script\": [\"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}'
curl --header "Content-Type: application/json" "https://gitlab.example.com/api/v4/projects/:id/ci/lint" --data '{"content": "{ \"image\": \"ruby:2.6\", \"services\": [\"postgres\"], \"before_script\": [\"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"stages\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}'
```
Example responses:

View File

@ -1,11 +0,0 @@
---
redirect_to: 'compute_minutes.md'
remove_date: '2024-08-13'
---
This document was moved to [another location](compute_minutes.md).
<!-- This redirect file can be deleted after <2024-08-13>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -435,7 +435,7 @@ Use sentence case for **Code explanation**.
On first mention on a page, use **GitLab Duo Code explanation**.
Thereafter, use **Code explanation** by itself.
## Code Generation
## code generation
Code Suggestions has evolved to include two primary features:

View File

@ -32,8 +32,8 @@ Report any issues, bugs, or feature requests in the
## Related topics
- [Download the plugin](https://marketplace.visualstudio.com/items?itemName=GitLab.GitLabExtensionForVisualStudio)
- [Plugin documentation](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension/-/blob/main/README.md)
- [Download the extension](https://marketplace.visualstudio.com/items?itemName=GitLab.GitLabExtensionForVisualStudio)
- [Extension documentation](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension/-/blob/main/README.md)
- [View source code](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension)
## Troubleshooting

View File

@ -1,11 +0,0 @@
---
redirect_to: 'index.md'
remove_date: '2024-08-06'
---
This document was moved to [another location](index.md).
<!-- This redirect file can be deleted after <2024-08-06>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -1,11 +0,0 @@
---
redirect_to: 'https://about.gitlab.com/blog/2023/07/27/gitlab-flow-duo/'
remove_date: '2024-07-27'
---
This document was moved to [another location](https://about.gitlab.com/blog/2023/07/27/gitlab-flow-duo/).
<!-- This redirect file can be deleted after <2024-07-27>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

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