Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-06-10 00:07:23 +00:00
parent e374f6b229
commit c1924278d0
33 changed files with 389 additions and 325 deletions

View File

@ -54,6 +54,7 @@ export default {
basePath: this.kasTunnelUrl,
baseOptions: {
headers: { 'GitLab-Agent-Id': this.gitlabAgentId, ...csrf.headers },
withCredentials: true,
},
};
},

View File

@ -114,11 +114,11 @@ export default {
},
},
methods: {
handleTextInput(query) {
handleTextInput(inputQuery) {
this.hideDropdownWithNoItems = false;
this.query = query;
this.query = inputQuery.trim();
this.loading = true;
this.retrieveUsers(query);
this.retrieveUsers();
},
updateTokenClasses() {
this.selectedTokens = this.selectedTokens.map((token) => ({

View File

@ -1,8 +1,11 @@
import emojiRegex from 'emoji-regex';
import { __ } from '~/locale';
import { initSetStatusForm } from '~/profile/profile';
import { initProfileEdit } from '~/profile/edit';
initSetStatusForm();
// It will do nothing for now when the feature flag is turned off
initProfileEdit();
const userNameInput = document.getElementById('user_name');
if (userNameInput) {

View File

@ -0,0 +1,10 @@
<script>
export default {};
</script>
<template>
<!-- This is left empty intensionally -->
<!-- It will be implemented in the upcoming MRs -->
<!-- Related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/389918 -->
<div></div>
</template>

View File

@ -0,0 +1,16 @@
import Vue from 'vue';
import ProfileEditApp from './components/profile_edit_app.vue';
export const initProfileEdit = () => {
const mountEl = document.querySelector('.js-user-profile');
if (!mountEl) return false;
return new Vue({
el: mountEl,
name: 'ProfileEditRoot',
render(createElement) {
return createElement(ProfileEditApp);
},
});
};

View File

@ -4,195 +4,198 @@
- gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host
- @force_desktop_expanded_sidebar = true
= gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user gl-mt-3 js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f|
.row.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_("Profiles|Public avatar")
%p
- if Feature.enabled?(:edit_user_profile_vue, current_user)
.js-user-profile
- else
= gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user gl-mt-3 js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f|
.row.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_("Profiles|Public avatar")
%p
- if @user.avatar?
- if gravatar_enabled?
= s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
- else
= s_("Profiles|You can change your avatar here")
- else
- if gravatar_enabled?
= s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
- else
= s_("Profiles|You can upload your avatar here")
- if current_appearance&.profile_image_guidelines?
.md
= brand_profile_image_guidelines
.col-lg-8
.avatar-image
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= render Pajamas::AvatarComponent.new(@user, size: 96, alt: "", class: 'gl-float-left gl-mr-5')
%h5.gl-mt-0= s_("Profiles|Upload new avatar")
.gl-display-flex.gl-align-items-center.gl-my-3
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-choose-user-avatar-button' }) do
= s_("Profiles|Choose file...")
%span.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen.")
= f.file_field :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
.gl-text-gray-500= s_("Profiles|The maximum file size allowed is 200KB.")
- if @user.avatar?
- if gravatar_enabled?
= s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
- else
= s_("Profiles|You can change your avatar here")
- else
- if gravatar_enabled?
= s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
- else
= s_("Profiles|You can upload your avatar here")
- if current_appearance&.profile_image_guidelines?
.md
= brand_profile_image_guidelines
.col-lg-8
.avatar-image
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= render Pajamas::AvatarComponent.new(@user, size: 96, alt: "", class: 'gl-float-left gl-mr-5')
%h5.gl-mt-0= s_("Profiles|Upload new avatar")
.gl-display-flex.gl-align-items-center.gl-my-3
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-choose-user-avatar-button' }) do
= s_("Profiles|Choose file...")
%span.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen.")
= f.file_field :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
.gl-text-gray-500= s_("Profiles|The maximum file size allowed is 200KB.")
- if @user.avatar?
= render Pajamas::ButtonComponent.new(variant: :danger,
category: :secondary,
href: profile_avatar_path,
button_options: { class: 'gl-mt-3', data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") } },
method: :delete) do
= s_("Profiles|Remove avatar")
.col-lg-12
%hr
.row.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0= s_("Profiles|Current status")
%p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
.col-lg-8
#js-user-profile-set-status-form
= f.fields_for :status, @user.status do |status_form|
= status_form.hidden_field :emoji, data: { js_name: 'emoji' }
= status_form.hidden_field :message, data: { js_name: 'message' }
= status_form.hidden_field :availability, data: { js_name: 'availability' }
= status_form.hidden_field :clear_status_after,
value: user_clear_status_at(@user),
data: { js_name: 'clearStatusAfter' }
.col-lg-12
%hr
.row.user-time-preferences.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0= s_("Profiles|Time settings")
%p= s_("Profiles|Set your local time zone.")
.col-lg-8
= f.label :user_timezone, _("Time zone")
.js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } }
.col-lg-12
%hr
.row.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_("Profiles|Main settings")
%p
= s_("Profiles|This information will appear on your profile.")
- if current_user.ldap_user?
= s_("Profiles|Some options are unavailable for LDAP accounts")
.col-lg-8
.row
.form-group.gl-form-group.col-md-9.rspec-full-name
= render 'profiles/name', form: f, user: @user
.form-group.gl-form-group.col-md-3
= f.label :id, s_('Profiles|User ID')
= f.text_field :id, class: 'gl-form-input form-control', readonly: true
.form-group.gl-form-group
= f.label :pronouns, s_('Profiles|Pronouns')
= f.text_field :pronouns, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Enter your pronouns to let people know how to refer to you.")
.form-group.gl-form-group
= f.label :pronunciation, s_('Profiles|Pronunciation')
= f.text_field :pronunciation, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Enter how your name is pronounced to help people address you correctly.")
= render_if_exists 'profiles/extra_settings', form: f
= render_if_exists 'profiles/email_settings', form: f
.form-group.gl-form-group
= f.label :skype
= f.text_field :skype, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|username")
.form-group.gl-form-group
= f.label :linkedin
= f.text_field :linkedin, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
.form-group.gl-form-group
= f.label :twitter
= f.text_field :twitter, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|@username")
.form-group.gl-form-group
- external_accounts_help_url = help_page_path('user/profile/index', anchor: 'add-external-accounts-to-your-user-profile-page')
- external_accounts_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: external_accounts_help_url }
- external_accounts_docs_link = s_('Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more.%{external_accounts_link_end}').html_safe % { external_accounts_link_start: external_accounts_link_start, external_accounts_link_end: '</a>'.html_safe }
- min_discord_length = 17
- max_discord_length = 20
= f.label :discord
= f.text_field :discord,
class: 'gl-form-input form-control gl-md-form-input-lg js-validate-length',
placeholder: s_("Profiles|User ID"),
data: { min_length: min_discord_length,
min_length_message: s_('Profiles|Discord ID is too short (minimum is %{min_length} characters).') % { min_length: min_discord_length },
max_length: max_discord_length,
max_length_message: s_('Profiles|Discord ID is too long (maximum is %{max_length} characters).') % { max_length: max_discord_length },
allow_empty: true}
%small.form-text.text-gl-muted
= external_accounts_docs_link
.form-group.gl-form-group
= f.label :website_url, s_('Profiles|Website url')
= f.text_field :website_url, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|https://website.com")
.form-group.gl-form-group
= f.label :location, s_('Profiles|Location')
- if @user.read_only_attribute?(:location)
= f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: true
= render Pajamas::ButtonComponent.new(variant: :danger,
category: :secondary,
href: profile_avatar_path,
button_options: { class: 'gl-mt-3', data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") } },
method: :delete) do
= s_("Profiles|Remove avatar")
.col-lg-12
%hr
.row.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0= s_("Profiles|Current status")
%p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
.col-lg-8
#js-user-profile-set-status-form
= f.fields_for :status, @user.status do |status_form|
= status_form.hidden_field :emoji, data: { js_name: 'emoji' }
= status_form.hidden_field :message, data: { js_name: 'message' }
= status_form.hidden_field :availability, data: { js_name: 'availability' }
= status_form.hidden_field :clear_status_after,
value: user_clear_status_at(@user),
data: { js_name: 'clearStatusAfter' }
.col-lg-12
%hr
.row.user-time-preferences.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0= s_("Profiles|Time settings")
%p= s_("Profiles|Set your local time zone.")
.col-lg-8
= f.label :user_timezone, _("Time zone")
.js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } }
.col-lg-12
%hr
.row.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0
= s_("Profiles|Main settings")
%p
= s_("Profiles|This information will appear on your profile.")
- if current_user.ldap_user?
= s_("Profiles|Some options are unavailable for LDAP accounts")
.col-lg-8
.row
.form-group.gl-form-group.col-md-9.rspec-full-name
= render 'profiles/name', form: f, user: @user
.form-group.gl-form-group.col-md-3
= f.label :id, s_('Profiles|User ID')
= f.text_field :id, class: 'gl-form-input form-control', readonly: true
.form-group.gl-form-group
= f.label :pronouns, s_('Profiles|Pronouns')
= f.text_field :pronouns, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else
= f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|City, country")
.form-group.gl-form-group
= f.label :job_title, s_('Profiles|Job title')
= f.text_field :job_title, class: 'gl-form-input form-control gl-md-form-input-lg'
.form-group.gl-form-group
= f.label :organization, s_('Profiles|Organization')
= f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Who you represent or work for.")
.form-group.gl-form-group
= f.label :bio, s_('Profiles|Bio')
= f.text_area :bio, class: 'gl-form-input gl-form-textarea form-control', rows: 4, maxlength: 250
%small.form-text.text-gl-muted
= s_("Profiles|Tell us about yourself in fewer than 250 characters.")
%hr
%fieldset.form-group.gl-form-group
%legend.col-form-label.col-form-label
= _('Private profile')
- private_profile_label = s_("Profiles|Don't display activity-related personal information on your profile.")
- private_profile_help_link = link_to sprite_icon('question-o'), help_page_path('user/profile/index.md', anchor: 'make-your-user-profile-page-private')
= f.gitlab_ui_checkbox_component :private_profile, '%{private_profile_label} %{private_profile_help_link}'.html_safe % { private_profile_label: private_profile_label, private_profile_help_link: private_profile_help_link.html_safe }
%fieldset.form-group.gl-form-group
%legend.col-form-label.col-form-label
= s_("Profiles|Private contributions")
= f.gitlab_ui_checkbox_component :include_private_contributions,
s_('Profiles|Include private contributions on your profile'),
help_text: s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.")
%fieldset.form-group.gl-form-group
%legend.col-form-label.col-form-label
= s_("Profiles|Achievements")
= f.gitlab_ui_checkbox_component :achievements_enabled,
s_('Profiles|Display achievements on your profile')
.row.js-hide-when-nothing-matches-search
.col-lg-12
%hr
= f.submit s_("Profiles|Update profile settings"), class: 'gl-mr-3 js-password-prompt-btn', pajamas_button: true
= render Pajamas::ButtonComponent.new(href: user_path(current_user)) do
= s_('TagsPage|Cancel')
= s_("Profiles|Enter your pronouns to let people know how to refer to you.")
.form-group.gl-form-group
= f.label :pronunciation, s_('Profiles|Pronunciation')
= f.text_field :pronunciation, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Enter how your name is pronounced to help people address you correctly.")
= render_if_exists 'profiles/extra_settings', form: f
= render_if_exists 'profiles/email_settings', form: f
.form-group.gl-form-group
= f.label :skype
= f.text_field :skype, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|username")
.form-group.gl-form-group
= f.label :linkedin
= f.text_field :linkedin, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
.form-group.gl-form-group
= f.label :twitter
= f.text_field :twitter, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|@username")
.form-group.gl-form-group
- external_accounts_help_url = help_page_path('user/profile/index', anchor: 'add-external-accounts-to-your-user-profile-page')
- external_accounts_link = link_to '', external_accounts_help_url, target: "_blank", rel: "noopener noreferrer"
- external_accounts_docs_link = safe_format(s_('Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more.%{external_accounts_link_end}'), tag_pair(external_accounts_link, :external_accounts_link_start, :external_accounts_link_end))
- min_discord_length = 17
- max_discord_length = 20
= f.label :discord
= f.text_field :discord,
class: 'gl-form-input form-control gl-md-form-input-lg js-validate-length',
placeholder: s_("Profiles|User ID"),
data: { min_length: min_discord_length,
min_length_message: s_('Profiles|Discord ID is too short (minimum is %{min_length} characters).') % { min_length: min_discord_length },
max_length: max_discord_length,
max_length_message: s_('Profiles|Discord ID is too long (maximum is %{max_length} characters).') % { max_length: max_discord_length },
allow_empty: true}
%small.form-text.text-gl-muted
= external_accounts_docs_link
#password-prompt-modal
.form-group.gl-form-group
= f.label :website_url, s_('Profiles|Website url')
= f.text_field :website_url, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|https://website.com")
.form-group.gl-form-group
= f.label :location, s_('Profiles|Location')
- if @user.read_only_attribute?(:location)
= f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: true
%small.form-text.text-gl-muted
= s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else
= f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|City, country")
.form-group.gl-form-group
= f.label :job_title, s_('Profiles|Job title')
= f.text_field :job_title, class: 'gl-form-input form-control gl-md-form-input-lg'
.form-group.gl-form-group
= f.label :organization, s_('Profiles|Organization')
= f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
= s_("Profiles|Who you represent or work for.")
.form-group.gl-form-group
= f.label :bio, s_('Profiles|Bio')
= f.text_area :bio, class: 'gl-form-input gl-form-textarea form-control', rows: 4, maxlength: 250
%small.form-text.text-gl-muted
= s_("Profiles|Tell us about yourself in fewer than 250 characters.")
%hr
%fieldset.form-group.gl-form-group
%legend.col-form-label.col-form-label
= _('Private profile')
- private_profile_label = s_("Profiles|Don't display activity-related personal information on your profile.")
- private_profile_help_link = link_to sprite_icon('question-o'), help_page_path('user/profile/index.md', anchor: 'make-your-user-profile-page-private')
= f.gitlab_ui_checkbox_component :private_profile, '%{private_profile_label} %{private_profile_help_link}'.html_safe % { private_profile_label: private_profile_label, private_profile_help_link: private_profile_help_link.html_safe }
%fieldset.form-group.gl-form-group
%legend.col-form-label.col-form-label
= s_("Profiles|Private contributions")
= f.gitlab_ui_checkbox_component :include_private_contributions,
s_('Profiles|Include private contributions on your profile'),
help_text: s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.")
%fieldset.form-group.gl-form-group
%legend.col-form-label.col-form-label
= s_("Profiles|Achievements")
= f.gitlab_ui_checkbox_component :achievements_enabled,
s_('Profiles|Display achievements on your profile')
.row.js-hide-when-nothing-matches-search
.col-lg-12
%hr
= f.submit s_("Profiles|Update profile settings"), class: 'gl-mr-3 js-password-prompt-btn', pajamas_button: true
= render Pajamas::ButtonComponent.new(href: user_path(current_user)) do
= s_('TagsPage|Cancel')
.modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } }
.modal-dialog
.modal-content
.modal-header
%h4.modal-title
= s_("Profiles|Position and size your new avatar")
= render Pajamas::ButtonComponent.new(category: :tertiary,
icon: 'close',
button_options: { class: 'close', "data-dismiss": "modal", "aria-label" => _("Close") })
.modal-body
.profile-crop-image-container
%img.modal-profile-crop-image{ alt: s_("Profiles|Avatar cropper") }
.gl-text-center.gl-mt-4
.btn-group
= render Pajamas::ButtonComponent.new(icon: 'search-minus',
button_options: {data: { method: 'zoom', option: '-0.1' }})
= render Pajamas::ButtonComponent.new(icon: 'search-plus',
button_options: {data: { method: 'zoom', option: '0.1' }})
.modal-footer
= render Pajamas::ButtonComponent.new(variant: :confirm,
button_options: { class: 'js-upload-user-avatar'}) do
= s_("Profiles|Set new profile picture")
#password-prompt-modal
.modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } }
.modal-dialog
.modal-content
.modal-header
%h4.modal-title
= s_("Profiles|Position and size your new avatar")
= render Pajamas::ButtonComponent.new(category: :tertiary,
icon: 'close',
button_options: { class: 'close', "data-dismiss": "modal", "aria-label" => _("Close") })
.modal-body
.profile-crop-image-container
%img.modal-profile-crop-image{ alt: s_("Profiles|Avatar cropper") }
.gl-text-center.gl-mt-4
.btn-group
= render Pajamas::ButtonComponent.new(icon: 'search-minus',
button_options: {data: { method: 'zoom', option: '-0.1' }})
= render Pajamas::ButtonComponent.new(icon: 'search-plus',
button_options: {data: { method: 'zoom', option: '0.1' }})
.modal-footer
= render Pajamas::ButtonComponent.new(variant: :confirm,
button_options: { class: 'js-upload-user-avatar'}) do
= s_("Profiles|Set new profile picture")

View File

@ -0,0 +1,8 @@
---
name: edit_user_profile_vue
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122402
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414552
milestone: '16.1'
type: development
group: group::tenant scale
default_enabled: false

View File

@ -209,7 +209,7 @@ the merge train drops your merge request automatically. For example, this could
- Changing the merge request to a [draft](../../user/project/merge_requests/drafts.md).
- A merge conflict.
- A new conversation thread that is unresolved, when [all threads must be resolved](../../user/discussions/index.md#prevent-merge-unless-all-threads-are-resolved)
- A new conversation thread that is unresolved, when [all threads must be resolved](../../user/project/merge_requests/index.md#prevent-merge-unless-all-threads-are-resolved)
is enabled.
You can find reason the merge request was dropped from the merge train in the system

View File

@ -18,7 +18,7 @@ GitLab encourages communication through comments, threads, and
Two types of comments are available:
- A standard comment.
- A comment in a thread, which can be [resolved](#resolve-a-thread).
- A comment in a thread, which can be [resolved](../project/merge_requests/index.md#resolve-a-thread).
In a comment, you can enter [Markdown](../markdown.md) and use [quick actions](../project/quick_actions.md).
@ -95,47 +95,6 @@ it's converted to a link in the context of the merge request.
For example, `28719b171a056960dfdc0012b625d0b47b123196` becomes `28719b17` that links to
`https://gitlab.example.com/example-group/example-project/-/merge_requests/12345/diffs?commit_id=28719b171a056960dfdc0012b625d0b47b123196`.
## Add a comment to a merge request file
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121429) in GitLab 16.1 [with a flag](../../administration/feature_flags.md) named `comment_on_files`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `comment_on_files`.
On GitLab.com, this feature is not available.
You can add comments to a merge request diff file. These comments persist across
rebases and file changes.
To add a comment a merge request file:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Merge requests** and find your merge request.
1. Select **Changes**.
1. In the header for the file you want to comment on, select **Comment** (**{comment}**).
## Add a comment to a commit
You can add comments and threads to a particular commit.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Repository > Commits**.
1. Below the commits, in the **Comment** field, enter a comment.
1. Select **Comment** or select the down arrow (**{chevron-down}**) to select **Start thread**.
WARNING:
Threads created this way are lost if the commit ID changes after a
force push.
## Add a comment to an image
In merge requests and commit detail views, you can add a comment to an image.
This comment can also be a thread.
1. Hover your mouse over the image.
1. Select the location where you want to comment.
An icon is displayed on the image and a comment field is displayed.
## Reply to a comment by sending email
If you have ["reply by email"](../../administration/reply_by_email.md) configured,
@ -304,75 +263,3 @@ To create a thread:
A threaded comment is created.
![Thread comment](img/discussion_comment.png)
## Resolve a thread
> Resolving comments individually was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/28750) in GitLab 13.6.
In a merge request, you can resolve a thread when you want to finish a conversation.
Prerequisites:
- You must have at least the Developer role
or be the author of the change being reviewed.
- Resolvable threads can be added only to merge requests. It doesn't work
for comments in issues, commits, or snippets.
To resolve a thread:
1. Go to the thread.
1. Do one of the following:
- In the upper-right corner of the original comment, select **Resolve thread** (**{check-circle}**).
- Below the last reply, in the **Reply** field, select **Resolve thread**.
- Below the last reply, in the **Reply** field, enter text, select the **Resolve thread** checkbox, and select **Add comment now**.
At the top of the page, the number of unresolved threads is updated:
![Count of unresolved threads](img/unresolved_threads_v15_4.png)
### Move all unresolved threads in a merge request to an issue
If you have multiple unresolved threads in a merge request, you can
create an issue to resolve them separately. In the merge request, at the top of the page,
select the ellipsis icon button (**{ellipsis_v}**) in the threads control and then select **Resolve all with new issue**:
![Open new issue for all unresolved threads](img/create_new_issue_v15_4.png)
All threads are marked as resolved, and a link is added from the merge request to
the newly created issue.
### Move one unresolved thread in a merge request to an issue
If you have one specific unresolved thread in a merge request, you can
create an issue to resolve it separately. In the merge request, under the last reply
to the thread, next to **Resolve thread**, select **Create issue to resolve thread** (**{issue-new}**):
![Create issue for thread](img/new-issue-one-thread_v14_3.png)
The thread is marked as resolved, and a link is added from the merge request to
the newly created issue.
### Prevent merge unless all threads are resolved
You can prevent merge requests from being merged until all threads are
resolved. When this setting is enabled, the **Unresolved threads** counter in a merge request
is shown in orange when at least one thread remains unresolved.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Merge requests**.
1. In the **Merge checks** section, select the **All threads must be resolved** checkbox.
1. Select **Save changes**.
### Automatically resolve threads in a merge request when they become outdated
You can set merge requests to automatically resolve threads when lines are modified
with a new push.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Merge requests**.
1. In the **Merge options** section, select
**Automatically resolve merge request diff threads when they become outdated**.
1. Select **Save changes**.
Threads are now resolved if a push makes a diff section outdated.
Threads on lines that don't change and top-level resolvable threads are not resolved.

View File

@ -129,7 +129,7 @@ The following table lists project permissions available for each role:
| [Merge requests](project/merge_requests/index.md):<br>Add labels | | | ✓ | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>Lock threads | | | ✓ | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>Manage or accept | | | ✓ | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>[Resolve a thread](discussions/index.md#resolve-a-thread) | | | ✓ | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>[Resolve a thread](project/merge_requests/index.md#resolve-a-thread) | | | ✓ | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>Manage [merge approval rules](project/merge_requests/approvals/settings.md) (project settings) | | | | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>Delete | | | | | ✓ |
| [Metrics dashboards](../operations/metrics/dashboards/index.md):<br>Manage user-starred metrics dashboards (6) | ✓ | ✓ | ✓ | ✓ | ✓ |

View File

@ -284,7 +284,7 @@ When they are imported, supported GitHub branch protection rules are mapped to e
| GitHub rule | GitLab rule | Introduced in |
| :---------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------ |
| **Require conversation resolution before merging** for the project's default branch | **All threads must be resolved** [project setting](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) |
| **Require conversation resolution before merging** for the project's default branch | **All threads must be resolved** [project setting](../merge_requests/index.md#prevent-merge-unless-all-threads-are-resolved) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) |
| **Require a pull request before merging** | **No one** option in the **Allowed to push and merge** list of [branch protection settings](../protected_branches.md#add-protection-to-existing-branches) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) |
| **Require signed commits** for the project's default branch | **Reject unsigned commits** GitLab [push rule](../repository/push_rules.md#prevent-unintended-consequences) **(PREMIUM)** | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) |
| **Allow force pushes - Everyone** | **Allowed to force push** [branch protection setting](../protected_branches.md#allow-force-push-on-a-protected-branch) | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) |

View File

@ -67,7 +67,7 @@ if a user approves a merge request and is shown in the reviewer list, a green ch
After a merge request receives the [number and type of approvals](rules.md) you configure, it can merge
unless it's blocked for another reason. Merge requests can be blocked by other problems,
such as merge conflicts, [unresolved threads](../../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved),
such as merge conflicts, [unresolved threads](../index.md#prevent-merge-unless-all-threads-are-resolved),
or a [failed CI/CD pipeline](../merge_when_pipeline_succeeds.md).
To prevent merge request authors from approving their own merge requests,

View File

@ -149,3 +149,31 @@ When there are conflicts between the source and target branch, we show an alert
per conflicted file on the merge request diff:
![Example of a conflict alert shown in a merge request diff](img/conflict_ui_v15_6.png)
## Add a comment to a merge request file
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121429) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `comment_on_files`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `comment_on_files`.
On GitLab.com, this feature is not available.
You can add comments to a merge request diff file. These comments persist across
rebases and file changes.
To add a comment a merge request file:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Merge requests** and find your merge request.
1. Select **Changes**.
1. In the header for the file you want to comment on, select **Comment** (**{comment}**).
## Add a comment to an image
In merge requests and commit detail views, you can add a comment to an image.
This comment can also be a thread.
1. Hover your mouse over the image.
1. Select the location where you want to comment.
An icon is displayed on the image and a comment field is displayed.

View File

@ -29,6 +29,19 @@ To navigate commits in a merge request:
![Merge requests commit navigation](img/commit_nav_v16_0.png)
## Add a comment to a commit
You can add comments and threads to a particular commit.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Repository > Commits**.
1. Below the commits, in the **Comment** field, enter a comment.
1. Select **Comment** or select the down arrow (**{chevron-down}**) to select **Start thread**.
WARNING:
Threads created this way are lost if the commit ID changes after a
force push.
## View merge request commits in context
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29274) in GitLab 13.12 [with a flag](../../../administration/feature_flags.md) named `context_commits`. Enabled by default.

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -352,6 +352,78 @@ only the items that are relevant to you.
Your selection persists across all merge requests. You can also change the
sort order by clicking the sort button on the right.
## Resolve a thread
> Resolving comments individually was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/28750) in GitLab 13.6.
In a merge request, you can resolve a thread when you want to finish a conversation.
Prerequisites:
- You must have at least the Developer role
or be the author of the change being reviewed.
- Resolvable threads can be added only to merge requests. It doesn't work
for comments in issues, commits, or snippets.
To resolve a thread:
1. Go to the thread.
1. Do one of the following:
- In the upper-right corner of the original comment, select **Resolve thread** (**{check-circle}**).
- Below the last reply, in the **Reply** field, select **Resolve thread**.
- Below the last reply, in the **Reply** field, enter text, select the **Resolve thread** checkbox, and select **Add comment now**.
At the top of the page, the number of unresolved threads is updated:
![Count of unresolved threads](img/unresolved_threads_v15_4.png)
### Move all unresolved threads in a merge request to an issue
If you have multiple unresolved threads in a merge request, you can
create an issue to resolve them separately. In the merge request, at the top of the page,
select the ellipsis icon button (**{ellipsis_v}**) in the threads control and then select **Resolve all with new issue**:
![Open new issue for all unresolved threads](img/create_new_issue_v15_4.png)
All threads are marked as resolved, and a link is added from the merge request to
the newly created issue.
### Move one unresolved thread in a merge request to an issue
If you have one specific unresolved thread in a merge request, you can
create an issue to resolve it separately. In the merge request, under the last reply
to the thread, next to **Resolve thread**, select **Create issue to resolve thread** (**{issue-new}**):
![Create issue for thread](img/new-issue-one-thread_v14_3.png)
The thread is marked as resolved, and a link is added from the merge request to
the newly created issue.
### Prevent merge unless all threads are resolved
You can prevent merge requests from being merged until all threads are
resolved. When this setting is enabled, the **Unresolved threads** counter in a merge request
is shown in orange when at least one thread remains unresolved.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Merge requests**.
1. In the **Merge checks** section, select the **All threads must be resolved** checkbox.
1. Select **Save changes**.
### Automatically resolve threads in a merge request when they become outdated
You can set merge requests to automatically resolve threads when lines are modified
with a new push.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Merge requests**.
1. In the **Merge options** section, select
**Automatically resolve merge request diff threads when they become outdated**.
1. Select **Save changes**.
Threads are now resolved if a push makes a diff section outdated.
Threads on lines that don't change and top-level resolvable threads are not resolved.
## Related topics
- [Create a merge request](creating_merge_requests.md)

View File

@ -31,7 +31,7 @@ Prerequisites:
- You must have at least the Developer role in the project.
- If the project is configured to require it, all threads in the
merge request [must be resolved](../../discussions/index.md#resolve-a-thread).
merge request [must be resolved](index.md#resolve-a-thread).
- The merge request must have received all required approvals.
To do this when pushing from the command line, use the `merge_request.merge_when_pipeline_succeeds`

View File

@ -147,7 +147,7 @@ When you submit your review, GitLab:
### Resolve or unresolve thread with a comment
Review comments can also resolve or unresolve [resolvable threads](../../../discussions/index.md#resolve-a-thread).
Review comments can also resolve or unresolve [resolvable threads](../index.md#resolve-a-thread).
To resolve or unresolve a thread when replying to a comment:
1. In the comment text area, write your comment.

View File

@ -159,7 +159,7 @@ Configure your project's merge request settings:
- Enable [merge request approvals](../merge_requests/approvals/index.md).
- Enable [status checks](../merge_requests/status_checks.md).
- Enable [merge only if pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
- Enable [merge only when all threads are resolved](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved).
- Enable [merge only when all threads are resolved](../merge_requests/index.md#prevent-merge-unless-all-threads-are-resolved).
- Enable [require an associated issue from Jira](../../../integration/jira/issues.md#require-associated-jira-issue-for-merge-requests-to-be-merged).
- Enable [**Delete source branch when merge request is accepted** option by default](#delete-the-source-branch-on-merge-by-default).
- Configure [suggested changes commit messages](../merge_requests/reviews/suggestions.md#configure-the-commit-message-for-applied-suggestions).

View File

@ -30091,9 +30091,6 @@ msgstr ""
msgid "New Group"
msgstr ""
msgid "New Group Name"
msgstr ""
msgid "New Identity"
msgstr ""
@ -30180,6 +30177,9 @@ msgstr ""
msgid "New group"
msgstr ""
msgid "New group name"
msgstr ""
msgid "New health check access token has been generated!"
msgstr ""

View File

@ -281,7 +281,7 @@
"vue-loader-vue3": "npm:vue-loader@17",
"vue-test-utils-compat": "0.0.13",
"vuex-mock-store": "^0.1.0",
"webpack-dev-server": "4.15.0",
"webpack-dev-server": "4.15.1",
"xhr-mock": "^2.5.1",
"yarn-check-webpack-plugin": "^1.2.0",
"yarn-deduplicate": "^6.0.2"

View File

@ -6,6 +6,10 @@ RSpec.describe 'Admin Appearance', feature_category: :shared do
let!(:appearance) { create(:appearance) }
let(:admin) { create(:admin) }
before do
stub_feature_flags(edit_user_profile_vue: false)
end
flag_values = [true, false]
flag_values.each do |val|
context "with #{val}" do

View File

@ -8,6 +8,7 @@ RSpec.describe 'Upload a user avatar', :js, feature_category: :user_profile do
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
before do
stub_feature_flags(edit_user_profile_vue: false)
sign_in(user)
visit(profile_path)
attach_file('user_avatar-trigger', file.path, make_visible: true)

View File

@ -8,6 +8,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
let_it_be(:user) { create(:user) }
before do
stub_feature_flags(edit_user_profile_vue: false)
sign_in(user)
visit(profile_path)
end

View File

@ -7,6 +7,7 @@ RSpec.describe 'User searches their settings', :js, feature_category: :user_prof
before do
sign_in(user)
stub_feature_flags(edit_user_profile_vue: false)
end
context 'in profile page' do

View File

@ -7,6 +7,7 @@ RSpec.describe 'User visits their profile', feature_category: :user_profile do
before do
stub_feature_flags(profile_tabs_vue: false)
stub_feature_flags(edit_user_profile_vue: false)
sign_in(user)
end

View File

@ -7,6 +7,7 @@ RSpec.describe 'User uploads avatar to profile', feature_category: :user_profile
let(:avatar_file_path) { Rails.root.join('spec', 'fixtures', 'dk.png') }
before do
stub_feature_flags(edit_user_profile_vue: false)
sign_in user
visit profile_path
end

View File

@ -22,6 +22,7 @@ const configuration = {
basePath: provide.kasTunnelUrl.replace(/\/$/, ''),
baseOptions: {
headers: { 'GitLab-Agent-Id': '1' },
withCredentials: true,
},
};

View File

@ -130,6 +130,18 @@ describe('MembersTokenSelect', () => {
expect(tokenSelector.props('hideDropdownWithNoItems')).toBe(false);
});
it('calls the API with search parameter with whitespaces and is trimmed', async () => {
tokenSelector.vm.$emit('text-input', ' foo@bar.com ');
await waitForPromises();
expect(UserApi.getUsers).toHaveBeenCalledWith('foo@bar.com', {
active: true,
without_project_bots: true,
});
expect(tokenSelector.props('hideDropdownWithNoItems')).toBe(false);
});
describe('when input text is an email', () => {
it('allows user defined tokens', async () => {
tokenSelector.vm.$emit('text-input', 'foo@bar.com');

View File

@ -10,6 +10,7 @@ RSpec.describe 'profiles/show' do
assign(:user, user)
allow(controller).to receive(:current_user).and_return(user)
allow(view).to receive(:experiment_enabled?)
stub_feature_flags(edit_user_profile_vue: false)
end
context 'when the profile page is opened' do

View File

@ -2468,10 +2468,10 @@
dependencies:
"@types/node" "*"
"@types/ws@^8.0.0", "@types/ws@^8.5.1":
version "8.5.3"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==
"@types/ws@^8.0.0", "@types/ws@^8.5.5":
version "8.5.5"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb"
integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==
dependencies:
"@types/node" "*"
@ -12990,10 +12990,10 @@ webpack-dev-middleware@^5.3.1:
range-parser "^1.2.1"
schema-utils "^4.0.0"
webpack-dev-server@4.15.0:
version "4.15.0"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz#87ba9006eca53c551607ea0d663f4ae88be7af21"
integrity sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ==
webpack-dev-server@4.15.1:
version "4.15.1"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz#8944b29c12760b3a45bdaa70799b17cb91b03df7"
integrity sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==
dependencies:
"@types/bonjour" "^3.5.9"
"@types/connect-history-api-fallback" "^1.3.5"
@ -13001,7 +13001,7 @@ webpack-dev-server@4.15.0:
"@types/serve-index" "^1.9.1"
"@types/serve-static" "^1.13.10"
"@types/sockjs" "^0.3.33"
"@types/ws" "^8.5.1"
"@types/ws" "^8.5.5"
ansi-html-community "^0.0.8"
bonjour-service "^1.0.11"
chokidar "^3.5.3"