Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-07-02 21:15:37 +00:00
parent d8aae64906
commit a3271972b5
56 changed files with 814 additions and 164 deletions

View File

@ -643,7 +643,7 @@ lib/gitlab/checks/
/doc/administration/merge_request_diffs.md @aqualls
/doc/administration/merge_requests_approvals.md @brendan777
/doc/administration/moderate_users.md @lciutacu
/doc/administration/monitoring/github_imports.md @ashrafkhamis
/doc/administration/monitoring/ @ashrafkhamis
/doc/administration/monitoring/prometheus/_index.md @axil @eread
/doc/administration/monitoring/prometheus/registry_exporter.md @z_painter
/doc/administration/nfs.md @axil @eread
@ -830,7 +830,6 @@ lib/gitlab/checks/
/doc/api/pipelines.md @lyspin
/doc/api/plan_limits.md @idurham
/doc/api/policy_settings.md @rlehmann1
/doc/api/product_analytics.md @lciutacu
/doc/api/project_access_tokens.md @idurham
/doc/api/project_aliases.md @brendan777
/doc/api/project_badges.md @phillipwells
@ -1018,7 +1017,6 @@ lib/gitlab/checks/
/doc/solutions/ @jfullam @Darwinjs @sbrightwell
/doc/solutions/integrations/servicenow.md @ashrafkhamis
/doc/subscriptions/ @lciutacu
/doc/subscriptions/gitlab_com/ @lciutacu
/doc/subscriptions/gitlab_com/compute_minutes.md @lyspin
/doc/subscriptions/gitlab_dedicated/ @lyspin
/doc/topics/ @msedlakjakubowski
@ -1084,8 +1082,8 @@ lib/gitlab/checks/
/doc/user/crm/ @msedlakjakubowski
/doc/user/custom_roles/ @idurham
/doc/user/discussions/ @aqualls
/doc/user/duo_agent_platform/ @sselhorn
/doc/user/duo_amazon_q/ @sselhorn
/doc/user/duo_workflow/ @sselhorn
/doc/user/emoji_reactions.md @msedlakjakubowski
/doc/user/enterprise_user/ @idurham
/doc/user/get_started/get_started_managing_code.md @brendan777
@ -1142,7 +1140,7 @@ lib/gitlab/checks/
/doc/user/project/import/jira.md @msedlakjakubowski
/doc/user/project/import/jira_migration_options.md @msedlakjakubowski
/doc/user/project/insights/ @lciutacu
/doc/user/project/integrations/ @ashrafkhamis
/doc/user/project/integrations/_index.md @ashrafkhamis
/doc/user/project/integrations/asana.md @msedlakjakubowski
/doc/user/project/integrations/bamboo.md @lyspin
/doc/user/project/integrations/beyond_identity.md @brendan777
@ -1150,16 +1148,38 @@ lib/gitlab/checks/
/doc/user/project/integrations/clickup.md @msedlakjakubowski
/doc/user/project/integrations/confluence.md @msedlakjakubowski
/doc/user/project/integrations/custom_issue_tracker.md @msedlakjakubowski
/doc/user/project/integrations/discord_notifications.md @ashrafkhamis
/doc/user/project/integrations/emails_on_push.md @brendan777
/doc/user/project/integrations/ewm.md @msedlakjakubowski
/doc/user/project/integrations/git_guardian.md @brendan777
/doc/user/project/integrations/github.md @lyspin
/doc/user/project/integrations/gitlab_slack_app_troubleshooting.md @ashrafkhamis
/doc/user/project/integrations/gitlab_slack_application.md @ashrafkhamis
/doc/user/project/integrations/google_artifact_management.md @z_painter
/doc/user/project/integrations/hangouts_chat.md @ashrafkhamis
/doc/user/project/integrations/harbor.md @z_painter
/doc/user/project/integrations/irker.md @ashrafkhamis
/doc/user/project/integrations/matrix.md @sselhorn
/doc/user/project/integrations/mattermost.md @ashrafkhamis
/doc/user/project/integrations/mattermost_slash_commands.md @ashrafkhamis
/doc/user/project/integrations/microsoft_teams.md @ashrafkhamis
/doc/user/project/integrations/mock_ci.md @lyspin
/doc/user/project/integrations/phorge.md @msedlakjakubowski
/doc/user/project/integrations/pipeline_status_emails.md @lyspin
/doc/user/project/integrations/pivotal_tracker.md @msedlakjakubowski
/doc/user/project/integrations/pumble.md @ashrafkhamis
/doc/user/project/integrations/redmine.md @msedlakjakubowski
/doc/user/project/integrations/slack.md @ashrafkhamis
/doc/user/project/integrations/slack_slash_commands.md @ashrafkhamis
/doc/user/project/integrations/squash_tm.md @msedlakjakubowski
/doc/user/project/integrations/telegram.md @ashrafkhamis
/doc/user/project/integrations/unify_circuit.md @ashrafkhamis
/doc/user/project/integrations/webex_teams.md @ashrafkhamis
/doc/user/project/integrations/webhook_events.md @ashrafkhamis
/doc/user/project/integrations/webhooks.md @ashrafkhamis
/doc/user/project/integrations/webhooks_troubleshooting.md @ashrafkhamis
/doc/user/project/integrations/youtrack.md @msedlakjakubowski
/doc/user/project/integrations/zentao.md @ashrafkhamis
/doc/user/project/issue_board.md @msedlakjakubowski
/doc/user/project/issues/ @msedlakjakubowski
/doc/user/project/issues/csv_import.md @ashrafkhamis
@ -1287,7 +1307,6 @@ lib/gitlab/checks/
/app/assets/javascripts/pages/projects/settings/access_tokens/
/app/assets/javascripts/pages/user_settings/personal_access_tokens/
/app/assets/javascripts/profile/password_prompt/
/app/assets/javascripts/projects/settings/topics/components/
/app/assets/stylesheets/page_bundles/profile_two_factor_auth.scss
/app/controllers/admin/impersonation_tokens_controller.rb
/app/controllers/concerns/access_tokens_actions.rb

View File

@ -39,9 +39,9 @@ export default {
? s__('MergeRequests|Thread stays resolved')
: s__('MergeRequests|Thread will be resolved');
} else if (discussionResolved) {
message = s__('MergeRequests|Thread will be unresolved');
message = s__('MergeRequests|Thread will be open');
} else if (this.$options.showStaysResolved) {
message = s__('MergeRequests|Thread stays unresolved');
message = s__('MergeRequests|Thread stays open');
}
return message;

View File

@ -489,13 +489,13 @@ export const MR_TOGGLE_REVIEW = {
export const MR_NEXT_UNRESOLVED_DISCUSSION = {
id: 'mergeRequests.nextUnresolvedDiscussion',
description: __('Next unresolved thread'),
description: __('Next open thread'),
defaultKeys: ['n'],
};
export const MR_PREVIOUS_UNRESOLVED_DISCUSSION = {
id: 'mergeRequests.previousUnresolvedDiscussion',
description: __('Previous unresolved thread'),
description: __('Previous open thread'),
defaultKeys: ['p'],
};

View File

@ -137,7 +137,7 @@ export default {
},
resolveCheckboxText() {
return this.discussion.resolved
? s__('DesignManagement|Unresolve thread')
? s__('DesignManagement|Reopen thread')
: s__('DesignManagement|Resolve thread');
},
firstNote() {

View File

@ -52,6 +52,8 @@ import {
TOKEN_TITLE_ENVIRONMENT,
TOKEN_TYPE_SUBSCRIBED,
TOKEN_TITLE_SUBSCRIBED,
TOKEN_TYPE_SEARCH_WITHIN,
TOKEN_TITLE_SEARCH_WITHIN,
} from '~/vue_shared/components/filtered_search_bar/constants';
import {
convertToApiParams,
@ -471,6 +473,22 @@ export default {
},
],
},
{
type: TOKEN_TYPE_SEARCH_WITHIN,
title: TOKEN_TITLE_SEARCH_WITHIN,
icon: 'search',
token: GlFilteredSearchToken,
unique: true,
operators: OPERATORS_IS,
options: [
{ icon: 'title', value: 'TITLE', title: __('Titles') },
{
icon: 'text-description',
value: 'DESCRIPTION',
title: __('Descriptions'),
},
],
},
].filter(Boolean);
},
showPaginationControls() {

View File

@ -8,6 +8,7 @@ query getGroupMergeRequests(
$sort: MergeRequestSort
$state: MergeRequestState
$search: String
$in: [IssuableSearchableField!]
$approvedBy: [String!]
$assigneeUsernames: String
$assigneeWildcardId: AssigneeWildcardId
@ -39,6 +40,7 @@ query getGroupMergeRequests(
sort: $sort
state: $state
search: $search
in: $in
approvedBy: $approvedBy
assigneeUsername: $assigneeUsernames
assigneeWildcardId: $assigneeWildcardId

View File

@ -1,6 +1,7 @@
query getProjectMergeRequestsCount(
$fullPath: ID!
$search: String
$in: [IssuableSearchableField!]
$approvedBy: [String!]
$assigneeUsernames: String
$assigneeWildcardId: AssigneeWildcardId
@ -27,6 +28,7 @@ query getProjectMergeRequestsCount(
openedMergeRequests: mergeRequests(
state: opened
search: $search
in: $in
approvedBy: $approvedBy
assigneeUsername: $assigneeUsernames
assigneeWildcardId: $assigneeWildcardId
@ -53,6 +55,7 @@ query getProjectMergeRequestsCount(
mergedMergeRequests: mergeRequests(
state: merged
search: $search
in: $in
approvedBy: $approvedBy
assigneeUsername: $assigneeUsernames
assigneeWildcardId: $assigneeWildcardId
@ -79,6 +82,7 @@ query getProjectMergeRequestsCount(
closedMergeRequests: mergeRequests(
state: closed
search: $search
in: $in
approvedBy: $approvedBy
assigneeUsername: $assigneeUsernames
assigneeWildcardId: $assigneeWildcardId
@ -105,6 +109,7 @@ query getProjectMergeRequestsCount(
allMergeRequests: mergeRequests(
state: all
search: $search
in: $in
approvedBy: $approvedBy
assigneeUsername: $assigneeUsernames
assigneeWildcardId: $assigneeWildcardId

View File

@ -142,7 +142,7 @@ export default {
/>
</template>
<template v-else>
{{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
{{ n__('%d open thread', '%d open threads', unresolvedDiscussionsCount) }}
<gl-button-group class="gl-ml-3">
<gl-button
v-gl-tooltip.html="previousUnresolvedDiscussionTooltip"

View File

@ -444,7 +444,7 @@ export default {
<label>
<template v-if="discussionResolved">
<gl-form-checkbox v-model="isUnresolving" class="js-unresolve-checkbox">
{{ __('Unresolve thread') }}
{{ __('Reopen thread') }}
</gl-form-checkbox>
</template>
<template v-else>

View File

@ -11,7 +11,7 @@ export default {
return this.note.resolved;
},
resolveButtonTitle() {
return this.discussionResolved ? __('Unresolve thread') : __('Resolve thread');
return this.discussionResolved ? __('Reopen thread') : __('Resolve thread');
},
},
methods: {

View File

@ -26,7 +26,7 @@ export const FAILURE_REASONS = {
commits_status: __('Source branch exists and contains commits.'),
ci_must_pass: __('Pipeline must succeed.'),
conflict: __('Merge conflicts must be resolved.'),
discussions_not_resolved: __('Unresolved discussions must be resolved.'),
discussions_not_resolved: __('Open threads must be resolved.'),
draft_status: __('Merge request must not be draft.'),
not_open: __('Merge request must be open.'),
need_rebase: __('Merge request must be rebased, because a fast-forward merge is not possible.'),

View File

@ -20,7 +20,7 @@ export default {
tertiaryActionsButtons() {
return [
{
text: s__('mrWidget|Go to first unresolved thread'),
text: s__('mrWidget|Go to first open thread'),
category: 'default',
onClick: () => notesEventHub.$emit('jumpToFirstUnresolvedDiscussion'),
},

View File

@ -129,7 +129,7 @@ export default {
},
resolveCheckboxText() {
return this.discussion.resolved
? s__('DesignManagement|Unresolve thread')
? s__('DesignManagement|Reopen thread')
: s__('DesignManagement|Resolve thread');
},
firstNote() {

View File

@ -220,7 +220,7 @@ export default {
};
},
resolveDiscussionTitle() {
return this.isDiscussionResolved ? __('Unresolve thread') : __('Resolve thread');
return this.isDiscussionResolved ? __('Reopen thread') : __('Resolve thread');
},
hasEmailParticipantsWidget() {
return Boolean(findEmailParticipantsWidget(this.workItem));

View File

@ -202,7 +202,7 @@ export default {
return !this.isNewDiscussion && this.isDiscussionResolvable && this.hasReplies;
},
resolveCheckboxLabel() {
return this.isDiscussionResolved ? __('Unresolve thread') : __('Resolve thread');
return this.isDiscussionResolved ? __('Reopen thread') : __('Resolve thread');
},
canMarkNoteAsInternal() {
return this.workItem?.userPermissions?.markNoteAsInternal;

View File

@ -44,7 +44,7 @@
= @discussion_to_resolve ? 'thread' : 'threads'
at
= link_to_discussions_to_resolve(@merge_request_to_resolve_discussions_of, @discussion_to_resolve)
will stay unresolved. Ask someone with permission to resolve
will stay open. Ask someone with permission to resolve
= @discussion_to_resolve ? 'it.' : 'them.'
= render_if_exists 'observability/observability_links', observability_values: @observability_values, issuable: issuable, project: project

View File

@ -0,0 +1,8 @@
---
migration_job_name: BackfillSentNotificationsAfterPartition
description: Backfills sent_notifications records including the sharding key. Deletes orphaned and invalid records.
feature_category: team_planning
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/194482
milestone: '18.2'
queued_migration_version: 20250613142340
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
class QueueBackfillSentNotificationsAfterPartition < Gitlab::Database::Migration[2.3]
milestone '18.2'
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "BackfillSentNotificationsAfterPartition"
BATCH_SIZE = 10_000
SUB_BATCH_SIZE = 200
GITLAB_OPTIMIZED_BATCH_SIZE = 30_000
GITLAB_OPTIMIZED_MAX_BATCH_SIZE = 75_000
GITLAB_OPTIMIZED_SUB_BATCH_SIZE = 500
# ID based on a manual search of the sent_notifications table. ID for records created approximately around
# 2024-06-13, so approximately 1 years ago at the time of writing. We plan to preserve only 1 year of records from
# now on
DOT_COM_START_ID = 2290000000
class MigrationPartSentNotification < MigrationRecord
extend SuppressCompositePrimaryKeyWarning
include PartitionedTable
self.table_name = :sent_notifications_7abbf02cb6
partitioned_by :created_at, strategy: :monthly, retain_for: 1.year
end
def up
queue_batched_background_migration(
MIGRATION,
:sent_notifications,
:id,
batch_min_value: batch_start_id,
batch_max_value: batch_end_id,
**batch_sizes
)
end
def down
delete_batched_background_migration(MIGRATION, :sent_notifications, :id, [])
end
private
def batch_sizes
if Gitlab.com_except_jh?
{
batch_size: GITLAB_OPTIMIZED_BATCH_SIZE,
sub_batch_size: GITLAB_OPTIMIZED_SUB_BATCH_SIZE,
max_batch_size: GITLAB_OPTIMIZED_MAX_BATCH_SIZE
}
else
{
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
}
end
end
def batch_start_id
return DOT_COM_START_ID if Gitlab.com_except_jh?
1
end
def batch_end_id
minimum_partitioned = MigrationPartSentNotification.minimum(:id)
minimum_partitioned if minimum_partitioned && minimum_partitioned >= batch_start_id
end
end

View File

@ -7,13 +7,11 @@ class FinalizeBackfillOnboardingStatusRegistrationObjective < Gitlab::Database::
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillOnboardingStatusRegistrationObjective',
table_name: :user_details,
column_name: :id,
job_arguments: [],
finalize: true
)
# no-op
# This migration finalization was changed to a no-op because the job arguments did not match
# the batched background migration configuration, causing database testing pipeline failures.
# Original MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195690
# Error: Could not find batched background migration for the given configuration with job_arguments: []
end
def down

View File

@ -0,0 +1 @@
2d2a8ed393743febc3d36889fd9be71d275104565a41ff7374a3ae04befdfe97

View File

@ -1088,7 +1088,7 @@ curl --request POST \
### Resolve a merge request thread
Resolve or unresolve a thread of discussion in a merge request.
Resolve or reopen a thread of discussion in a merge request.
Prerequisites:
@ -1105,7 +1105,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
| `discussion_id` | string | yes | The ID of a thread. |
| `merge_request_iid` | integer | yes | The IID of a merge request. |
| `resolved` | boolean | yes | Resolve or unresolve the discussion. |
| `resolved` | boolean | yes | Resolve or reopen the discussion. |
```shell
curl --request PUT \
@ -1156,7 +1156,7 @@ Parameters:
| `merge_request_iid` | integer | yes | The IID of a merge request. |
| `note_id` | integer | yes | The ID of a thread note. |
| `body` | string | no | The content of the note or reply. Exactly one of `body` or `resolved` must be set. |
| `resolved` | boolean | no | Resolve or unresolve the note. Exactly one of `body` or `resolved` must be set. |
| `resolved` | boolean | no | Resolve or reopen the note. Exactly one of `body` or `resolved` must be set. |
```shell
curl --request PUT \

View File

@ -189,7 +189,7 @@ Use this extension to review, comment on, and approve merge requests without lea
Use the diff to:
- Review and create discussions.
- Resolve and unresolve these discussions.
- Resolve and reopen these discussions.
- Delete and edit individual comments.
### Compare with default branch

View File

@ -76,7 +76,7 @@ Each table row groups related information about a merge request together into co
- **Author** - The author's avatar.
- **Reviewers** - The reviewers' avatars. Reviewers with a green check mark have approved the merge request.
- **Checks** - A compact assessment of mergeability.
- Number of unresolved threads, for example `0 of 3`.
- Number of open threads, for example `0 of 3`.
- Current required [approval status](../../user/project/merge_requests/approvals/_index.md#in-the-list-of-merge-requests).
- Most recent pipeline's status.
- Date of last update.

View File

@ -87,9 +87,9 @@ to a problem or a feature request in an issue.
Below the description, check the merge request widget to understand the
current status of this work. This example shows a merge widget for a merge request
that is missing approvals, is still in draft mode, and has unresolved discussion threads:
that is missing approvals, is still in draft mode, and has open discussion threads:
![Merge widget showing blocked status with three failed checks: missing approvals, draft mode, and unresolved discussions](img/widget_v17_0.png)
![Merge widget showing blocked status with three failed checks: missing approvals, draft mode, and open threads](img/widget_v17_0.png)
- Does it cross-link to an issue? Check the description and merge widget
for links to other issues. Some merge requests are straightforward, but

View File

@ -352,7 +352,7 @@ To resolve a thread:
Additionally, in merge requests, you can [do more with threads](../project/merge_requests/_index.md#resolve-a-thread),
such as:
- Move unresolved threads to a new issue.
- Move open threads to a new issue.
- Prevent merging until all threads are resolved.
## Summarize issue discussions with Duo Chat

View File

@ -561,7 +561,7 @@ To change this behavior:
### 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, child projects in your group display unresolved thread counts in orange on merge requests with at least one unresolved thread.
You can prevent merge requests from being merged until all threads are resolved. When this setting is enabled, child projects in your group display open thread counts in orange on merge requests with at least one open thread.
Prerequisites:

View File

@ -1,6 +1,6 @@
---
stage: Create
group: Import
group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Emails on push
---

View File

@ -1,6 +1,6 @@
---
stage: Create
group: Import
stage: Verify
group: Pipeline Execution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Mock CI
---

View File

@ -1,6 +1,6 @@
---
stage: Create
group: Import
stage: Verify
group: Pipeline Execution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Pipeline status emails
---

View File

@ -1,6 +1,6 @@
---
stage: Create
group: Import
stage: Plan
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Squash TM
---

View File

@ -273,9 +273,9 @@ To delete a comment from a design:
When you're done discussing part of a design, you can resolve the discussion thread.
To mark a thread as resolved or unresolved, either:
To mark a thread as resolved or open, either:
- In the upper-right corner of the first comment of the discussion, select **Resolve thread** or **Unresolve thread** ({{< icon name="check-circle" >}}).
- In the upper-right corner of the first comment of the discussion, select **Resolve thread** or **Reopen thread** ({{< icon name="check-circle" >}}).
- Add a new comment to the thread and select or clear the **Resolve thread** checkbox.
Resolving a discussion thread also marks any pending [to-do items](../../todos.md) related to notes

View File

@ -377,17 +377,17 @@ sort order by clicking the sort button on the right.
When you want to finish a conversation in a merge request,
[resolve a thread](../../discussions/_index.md#resolve-a-thread).
GitLab shows the number of unresolved threads in the top right corner of a
merge request, like this: `7 unresolved threads`.
GitLab shows the number of open threads in the top right corner of a
merge request, like this: `7 open threads`.
### Move all unresolved threads in a merge request to an issue
### Move all open threads in a merge request to an issue
If you have multiple unresolved threads in a merge request, you can
If you have multiple open threads in a merge request, you can
create an issue to resolve them separately:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests** and find your merge request.
1. In the merge request, in the top right, find the **Unresolved threads**
1. In the merge request, in the top right, find the **Open threads**
dropdown list, and select **Thread options** ({{< icon name="ellipsis_v" >}}).
1. Select **Resolve all with new issue**.
1. Fill out the fields in the new issue, and select **Create issue**.
@ -395,9 +395,9 @@ create an issue to resolve them separately:
GitLab marks all threads as resolved, and adds a link from the merge request to
the newly created issue.
### Move one unresolved thread in a merge request to an issue
### Move one open thread in a merge request to an issue
If you have one specific unresolved thread in a merge request, you can
If you have one specific open thread in a merge request, you can
create an issue to resolve it separately:
1. On the left sidebar, select **Search or go to** and find your project.
@ -412,9 +412,9 @@ the newly created issue.
### Prevent merge unless all threads are resolved
You can prevent merge requests from merging while threads remain unresolved.
When you enable this setting, the **Unresolved threads** counter in a merge request
is shown in orange while at least one thread remains unresolved.
You can prevent merge requests from merging while threads remain open.
When you enable this setting, the **Open threads** counter in a merge request
is shown in orange while at least one thread remains open.
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > Merge requests**.

View File

@ -158,7 +158,7 @@ Approved merge requests display a green check mark ({{< icon name="check-circle-
After a merge request receives the required approvals, it is ready to merge, unless it's blocked due to:
- [Merge conflicts](../conflicts.md)
- [Unresolved threads](../_index.md#prevent-merge-unless-all-threads-are-resolved)
- [Open threads](../_index.md#prevent-merge-unless-all-threads-are-resolved)
- [Failed CI/CD pipeline](../auto_merge.md)
### Prevent author approval

View File

@ -179,20 +179,20 @@ To start your review of a merge request:
Next, submit your review.
### Resolve or unresolve thread with a comment
### Resolve or reopen thread with a comment
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:
Review comments can also resolve or reopen [resolvable threads](../_index.md#resolve-a-thread).
To resolve or reopen a thread when replying to a comment:
1. In the comment text area, write your comment.
1. Select or clear **Resolve thread**.
1. Select or clear **Resolve thread** or **Reopen thread**.
1. Select **Add comment now** or **Add to review**.
Pending comments display information about delayed actions. GitLab does not perform these actions
until you publish the comment:
- {{< icon name="check-circle-filled" >}} Resolves thread.
- {{< icon name="check-circle" >}} Thread stays unresolved.
- {{< icon name="check-circle" >}} Thread stays open.
## Submit a review

View File

@ -120,8 +120,8 @@ These shortcuts are available when viewing [merge requests](project/merge_reques
| <kbd>]</kbd> or <kbd>j</kbd> | | Move to next file. |
| <kbd>&#91;</kbd> or <kbd>k</kbd> | | Move to previous file. |
| <kbd>Command</kbd> + <kbd>p</kbd> | <kbd>Control</kbd> + <kbd>p</kbd> | Search for, and then jump to a file for review. |
| <kbd>n</kbd> | | Move to next unresolved discussion. |
| <kbd>p</kbd> | | Move to previous unresolved discussion. |
| <kbd>n</kbd> | | Move to next open thread. |
| <kbd>p</kbd> | | Move to previous open thread. |
| <kbd>b</kbd> | | Copy source branch name. |
| <kbd>c</kbd> + <kbd>r</kbd> | | Copy merge request reference. |
| <kbd>r</kbd> | | Start writing a comment. Pre-selected text is quoted in the comment. |

View File

@ -0,0 +1,147 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillSentNotificationsAfterPartition < BatchedMigrationJob # rubocop:disable Metrics/ClassLength -- Necessary for migration
operation_name :insert_batch # This is used as the key on collecting metrics
feature_category :team_planning
def perform
each_sub_batch do |sub_batch|
update_issue_records(sub_batch)
update_merge_request_records(sub_batch)
update_commit_records(sub_batch)
update_epic_records(sub_batch)
update_project_snippet_records(sub_batch)
update_design_records(sub_batch)
update_wiki_records(sub_batch)
end
end
private
def update_issue_records(sub_batch)
connection.execute(
<<~SQL
#{query_prefix(sub_batch, 'Issue')}
issues.namespace_id
FROM
filtered_relation
INNER JOIN issues ON filtered_relation.noteable_id = issues.id
ON CONFLICT DO NOTHING
SQL
)
end
def update_merge_request_records(sub_batch)
connection.execute(
<<~SQL
#{query_prefix(sub_batch, 'MergeRequest')}
projects.project_namespace_id
FROM
filtered_relation
INNER JOIN merge_requests ON filtered_relation.noteable_id = merge_requests.id
INNER JOIN projects ON projects.id = merge_requests.target_project_id
ON CONFLICT DO NOTHING
SQL
)
end
def update_commit_records(sub_batch)
connection.execute(
<<~SQL
#{query_prefix(sub_batch, 'Commit')}
projects.project_namespace_id
FROM
filtered_relation
INNER JOIN projects ON projects.id = filtered_relation.project_id
ON CONFLICT DO NOTHING
SQL
)
end
def update_epic_records(sub_batch)
connection.execute(
<<~SQL
#{query_prefix(sub_batch, 'Epic')}
epics.group_id
FROM
filtered_relation
INNER JOIN epics ON filtered_relation.noteable_id = epics.id
ON CONFLICT DO NOTHING
SQL
)
end
def update_project_snippet_records(sub_batch)
connection.execute(
<<~SQL
#{query_prefix(sub_batch, 'ProjectSnippet')}
projects.project_namespace_id
FROM
filtered_relation
INNER JOIN snippets ON filtered_relation.noteable_id = snippets.id
INNER JOIN projects ON projects.id = snippets.project_id
ON CONFLICT DO NOTHING
SQL
)
end
def update_design_records(sub_batch)
connection.execute(
<<~SQL
#{query_prefix(sub_batch, 'DesignManagement::Design')}
design_management_designs.namespace_id
FROM
filtered_relation
INNER JOIN design_management_designs ON filtered_relation.noteable_id = design_management_designs.id
ON CONFLICT DO NOTHING
SQL
)
end
def update_wiki_records(sub_batch)
connection.execute(
<<~SQL
#{query_prefix(sub_batch, 'WikiPage::Meta')}
coalesce(wiki_page_meta.namespace_id, projects.project_namespace_id)
FROM
filtered_relation
INNER JOIN wiki_page_meta ON filtered_relation.noteable_id = wiki_page_meta.id
LEFT JOIN projects ON projects.id = wiki_page_meta.project_id
ON CONFLICT DO NOTHING
SQL
)
end
def query_prefix(sub_batch, noteable_type)
<<~SQL
WITH relation AS (
#{sub_batch.limit(sub_batch_size).to_sql}
),
filtered_relation AS (
SELECT * FROM relation WHERE noteable_type = '#{noteable_type}' LIMIT #{sub_batch_size}
)
-- Insert batch, including the sharding key value
INSERT INTO sent_notifications_7abbf02cb6 (
id, project_id, noteable_type, noteable_id,
recipient_id, commit_id, reply_key,
in_reply_to_discussion_id, issue_email_participant_id,
created_at, namespace_id
)
SELECT
filtered_relation.id,
filtered_relation.project_id,
filtered_relation.noteable_type,
filtered_relation.noteable_id,
filtered_relation.recipient_id,
filtered_relation.commit_id,
filtered_relation.reply_key,
filtered_relation.in_reply_to_discussion_id,
filtered_relation.issue_email_participant_id,
filtered_relation.created_at,
SQL
end
end
end
end

View File

@ -416,6 +416,11 @@ msgid_plural "%d more comments"
msgstr[0] ""
msgstr[1] ""
msgid "%d open thread"
msgid_plural "%d open threads"
msgstr[0] ""
msgstr[1] ""
msgid "%d option"
msgid_plural "%d options"
msgstr[0] ""
@ -521,11 +526,6 @@ msgid_plural "%d templates found"
msgstr[0] ""
msgstr[1] ""
msgid "%d unresolved thread"
msgid_plural "%d unresolved threads"
msgstr[0] ""
msgstr[1] ""
msgid "%d version"
msgid_plural "%d versions"
msgstr[0] ""
@ -22554,6 +22554,9 @@ msgstr ""
msgid "DesignManagement|Hide comments"
msgstr ""
msgid "DesignManagement|Reopen thread"
msgstr ""
msgid "DesignManagement|Requested design version does not exist."
msgstr ""
@ -22602,9 +22605,6 @@ msgstr ""
msgid "DesignManagement|To upload designs, you'll need to enable LFS and have an admin enable hashed storage. %{requirements_link_start}More information%{requirements_link_end}"
msgstr ""
msgid "DesignManagement|Unresolve thread"
msgstr ""
msgid "DesignManagement|Unselect the design"
msgstr ""
@ -38968,18 +38968,18 @@ msgstr ""
msgid "MergeRequests|Squashing not allowed: This project doesn't allow you to squash commits when merging."
msgstr ""
msgid "MergeRequests|Thread stays open"
msgstr ""
msgid "MergeRequests|Thread stays resolved"
msgstr ""
msgid "MergeRequests|Thread stays unresolved"
msgid "MergeRequests|Thread will be open"
msgstr ""
msgid "MergeRequests|Thread will be resolved"
msgstr ""
msgid "MergeRequests|Thread will be unresolved"
msgstr ""
msgid "MergeRequests|View file @ %{commitId}"
msgstr ""
@ -41139,6 +41139,9 @@ msgstr ""
msgid "Next file in diff"
msgstr ""
msgid "Next open thread"
msgstr ""
msgid "Next scan"
msgstr ""
@ -41148,9 +41151,6 @@ msgstr ""
msgid "Next step in review workflow"
msgstr ""
msgid "Next unresolved thread"
msgstr ""
msgid "Next update"
msgstr ""
@ -43318,6 +43318,9 @@ msgstr ""
msgid "Open raw"
msgstr ""
msgid "Open threads must be resolved."
msgstr ""
msgid "Open with"
msgstr ""
@ -47180,10 +47183,10 @@ msgstr ""
msgid "Previous file in diff"
msgstr ""
msgid "Previous step"
msgid "Previous open thread"
msgstr ""
msgid "Previous unresolved thread"
msgid "Previous step"
msgstr ""
msgid "Primary navigation sidebar"
@ -51738,6 +51741,9 @@ msgstr ""
msgid "Reopen this %{quick_action_target}"
msgstr ""
msgid "Reopen thread"
msgstr ""
msgid "Reopened this %{quick_action_target}."
msgstr ""
@ -66162,15 +66168,9 @@ msgstr ""
msgid "Unresolve"
msgstr ""
msgid "Unresolve thread"
msgstr ""
msgid "Unresolved"
msgstr ""
msgid "Unresolved discussions must be resolved."
msgstr ""
msgid "Unschedule job"
msgstr ""
@ -74507,7 +74507,7 @@ msgstr ""
msgid "mrWidget|GitLab %{linkStart}CI/CD can automatically build, test, and deploy your application.%{linkEnd} It only takes a few minutes to get started, and we can help you create a pipeline configuration file."
msgstr ""
msgid "mrWidget|Go to first unresolved thread"
msgid "mrWidget|Go to first open thread"
msgstr ""
msgid "mrWidget|If the %{type} branch exists in your local repository, you can merge this merge request manually using the command line."

View File

@ -22,7 +22,7 @@ module QA
expect(show).to have_comment('new comment to start review')
expect(show).to have_comment('comment added to review')
expect(show).to have_content("2 unresolved threads")
expect(show).to have_content("2 open threads")
end
end

View File

@ -4,7 +4,6 @@ ee/spec/frontend/admin/subscriptions/show/components/subscription_breakdown_spec
ee/spec/frontend/analytics/analytics_dashboards/components/analytics_dashboards_breadcrumbs_spec.js
ee/spec/frontend/analytics/analytics_dashboards/components/analytics_data_explorer_spec.js
ee/spec/frontend/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content_spec.js
ee/spec/frontend/analytics/dashboards/ai_impact/components/metric_table_spec.js
ee/spec/frontend/analytics/devops_reports/devops_adoption/components/devops_adoption_app_spec.js
ee/spec/frontend/analytics/group_ci_cd_analytics/components/release_stats_card_spec.js
ee/spec/frontend/approvals/mr_edit/mr_rules_spec.js
@ -91,7 +90,6 @@ spec/frontend/members/components/modals/leave_modal_spec.js
spec/frontend/members/components/table/max_role_spec.js
spec/frontend/members/components/table/members_table_spec.js
spec/frontend/ml/model_registry/components/model_edit_spec.js
spec/frontend/notes/components/discussion_notes_spec.js
spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js

View File

@ -77,7 +77,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j
it 'shows a warning that the merge request contains unresolved threads' do
click_button 'Expand merge checks'
expect(page).to have_content 'Unresolved discussions must be resolved'
expect(page).to have_content 'Open threads must be resolved'
end
end
end
@ -91,7 +91,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j
end
it 'shows a notice to ask someone else to resolve the threads' do
expect(page).to have_content("The threads at #{merge_request.to_reference} will stay unresolved. Ask someone with permission to resolve them.")
expect(page).to have_content("The threads at #{merge_request.to_reference} will stay open. Ask someone with permission to resolve them.")
end
end
end

View File

@ -48,7 +48,7 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue',
it 'shows the link for creating a new issue when unresolving a thread' do
page.within '.diff-content' do
click_button 'Unresolve thread'
click_button 'Reopen thread'
end
expect(page).to have_css resolve_discussion_selector
@ -87,7 +87,7 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue',
it 'shows a notice to ask someone else to resolve the threads' do
expect(page).to have_content("The thread at #{merge_request.to_reference} "\
"(discussion #{discussion.first_note.id}) will stay unresolved. "\
"(discussion #{discussion.first_note.id}) will stay open. "\
"Ask someone with permission to resolve it.")
end
end

View File

@ -314,7 +314,7 @@ RSpec.describe 'Merge request > Batch comments', :js, feature_category: :code_re
end
if unresolve
page.check('Unresolve thread')
page.check('Reopen thread')
end
click_button(button_text)

View File

@ -37,7 +37,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
context 'single thread' do
it 'shows text with how many threads' do
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
@ -51,7 +51,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
page.within '.diff-content' do
expect(page).to have_selector('.btn', text: 'Unresolve thread')
expect(page).to have_selector('.btn', text: 'Reopen thread')
end
page.within(first('.discussions-counter')) do
@ -74,11 +74,11 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
it 'allows user to unresolve thread' do
page.within '.diff-content' do
find('button[data-testid="resolve-discussion-button"]').click
click_button 'Unresolve thread'
click_button 'Reopen thread'
end
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
@ -155,13 +155,13 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
it 'allows user to unresolve from reply form without a comment' do
page.within '.diff-content' do
click_button 'Unresolve thread'
click_button 'Reopen thread'
wait_for_requests
end
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
@ -177,7 +177,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
end
@ -211,7 +211,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
it 'allows user to quickly scroll to next unresolved thread', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/410109' do
page.within(first('.discussions-counter')) do
click_button _('Next unresolved thread')
click_button _('Next open thread')
end
expect(page).to have_button('Resolve thread', visible: true)
@ -291,7 +291,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
it 'shows text with how many threads' do
page.within(first('.discussions-counter')) do
expect(page).to have_content('2 unresolved threads')
expect(page).to have_content('2 open threads')
end
end
@ -299,7 +299,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
click_button('Resolve thread', match: :first)
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
@ -325,7 +325,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
end
it 'allows user to quickly scroll to next unresolved thread' do
it 'allows user to quickly scroll to next open thread' do
page.within('.discussion-reply-holder', match: :first) do
find('button[data-testid="resolve-discussion-button"]').click
end
@ -376,7 +376,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
context 'changes tab' do
it 'shows text with how many threads' do
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
@ -386,7 +386,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
page.within '.diff-content' do
expect(page).to have_selector('.btn', text: 'Unresolve thread')
expect(page).to have_selector('.btn', text: 'Reopen thread')
end
page.within(first('.discussions-counter')) do
@ -407,11 +407,11 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
it 'allows user to unresolve thread' do
page.within '.diff-content' do
find('button[data-testid="resolve-discussion-button"]').click
click_button 'Unresolve thread'
click_button 'Reopen thread'
end
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
@ -446,7 +446,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
end
@ -469,7 +469,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
@ -493,7 +493,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
page.within '.diff-content' do
expect(page).to have_selector('.btn', text: 'Unresolve thread')
expect(page).to have_selector('.btn', text: 'Reopen thread')
end
page.within(first('.discussions-counter')) do
@ -515,7 +515,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js, feat
end
page.within(first('.discussions-counter')) do
expect(page).to have_content('1 unresolved thread')
expect(page).to have_content('1 open thread')
end
end
end

View File

@ -192,13 +192,13 @@ RSpec.describe 'Merge request > User sees discussions navigation', :js, feature_
end
def goto_next_thread
click_button 'Next unresolved thread', obscured: false
click_button 'Next open thread', obscured: false
# Wait for scroll
sleep(1)
end
def goto_previous_thread
click_button 'Previous unresolved thread', obscured: false
click_button 'Previous open thread', obscured: false
# Wait for scroll
sleep(1)
end

View File

@ -25,7 +25,7 @@ RSpec.describe 'Merge request > User sees merge button depending on unresolved t
click_button 'Expand merge checks'
expect(page).to have_content('Unresolved discussions must be resolved')
expect(page).to have_content('Open threads must be resolved')
end
end

View File

@ -399,13 +399,13 @@ RSpec.describe 'User comments on a diff', :js, feature_category: :code_review_wo
it 'resolves discussion when applied' do
page.within("[id='#{hash}']") do
expect(page).not_to have_content('Unresolve thread')
expect(page).not_to have_content('Reopen thread')
click_button('Apply suggestion')
click_button('Apply')
wait_for_requests
expect(page).to have_content('Unresolve thread')
expect(page).to have_content('Reopen thread')
end
end
end

View File

@ -247,12 +247,12 @@ describe('Design discussions component', () => {
expect(findReplyPlaceholder().isVisible()).toBe(true);
});
it('renders a checkbox with Unresolve thread text in reply form', async () => {
it('renders a checkbox with Reopen thread text in reply form', async () => {
findReplyPlaceholder().vm.$emit('focus');
wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });
await nextTick();
expect(findResolveCheckbox().text()).toBe('Unresolve thread');
expect(findResolveCheckbox().text()).toBe('Reopen thread');
});
});
});

View File

@ -34,6 +34,7 @@ import {
TOKEN_TYPE_DEPLOYED_AFTER,
TOKEN_TYPE_DEPLOYED_BEFORE,
TOKEN_TYPE_SUBSCRIBED,
TOKEN_TYPE_SEARCH_WITHIN,
OPERATOR_IS,
OPERATOR_NOT,
} from '~/vue_shared/components/filtered_search_bar/constants';
@ -329,6 +330,7 @@ describe('Merge requests list app', () => {
{ type: TOKEN_TYPE_DEPLOYED_BEFORE },
{ type: TOKEN_TYPE_DEPLOYED_AFTER },
{ type: TOKEN_TYPE_SUBSCRIBED },
{ type: TOKEN_TYPE_SEARCH_WITHIN },
]);
});
});
@ -346,6 +348,7 @@ describe('Merge requests list app', () => {
my_reaction_emoji: '🔥',
'target_branches[]': 'branch-a',
'source_branches[]': 'branch-b',
in: 'TITLE',
};
beforeEach(async () => {
@ -390,6 +393,7 @@ describe('Merge requests list app', () => {
{ type: TOKEN_TYPE_DEPLOYED_BEFORE },
{ type: TOKEN_TYPE_DEPLOYED_AFTER },
{ type: TOKEN_TYPE_SUBSCRIBED },
{ type: TOKEN_TYPE_SEARCH_WITHIN },
]);
});
@ -406,6 +410,7 @@ describe('Merge requests list app', () => {
{ type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_TARGET_BRANCH },
{ type: TOKEN_TYPE_SOURCE_BRANCH },
{ type: TOKEN_TYPE_SEARCH_WITHIN },
]);
});
});

View File

@ -33,6 +33,8 @@ describe('DiscussionNotes', () => {
let wrapper;
const getList = () => getByRole(wrapper.element, 'list');
const findNoteableNotes = () => wrapper.findAllComponents(NoteableNote);
const createComponent = (props, mountingMethod = shallowMount) => {
wrapper = mountingMethod(DiscussionNotes, {
store,
@ -69,49 +71,50 @@ describe('DiscussionNotes', () => {
it('renders an element for each note in the discussion', () => {
createComponent();
const notesCount = discussionMock.notes.length;
const els = wrapper.findAllComponents(NoteableNote);
expect(els.length).toBe(notesCount);
expect(findNoteableNotes().length).toBe(notesCount);
});
it('renders one element if replies groupping is enabled', () => {
createComponent({ shouldGroupReplies: true });
const els = wrapper.findAllComponents(NoteableNote);
expect(els.length).toBe(1);
expect(findNoteableNotes().length).toBe(1);
});
it('uses proper component to render each note type', () => {
const discussion = { ...discussionMock };
const notesData = [
// PlaceholderSystemNote
{
it.each([
{
note: {
id: 1,
isPlaceholderNote: true,
placeholderType: SYSTEM_NOTE,
notes: [{ body: 'PlaceholderSystemNote' }],
},
// PlaceholderNote
{
component: PlaceholderSystemNote,
},
{
note: {
id: 2,
isPlaceholderNote: true,
notes: [{ body: 'PlaceholderNote' }],
},
// SystemNote
{
component: PlaceholderNote,
},
{
note: {
id: 3,
system: true,
note: 'SystemNote',
},
// NoteableNote
discussion.notes[0],
];
discussion.notes = notesData;
createComponent({ discussion, shouldRenderDiffs: true });
const notes = wrapper.findAll('.notes > *');
component: SystemNote,
},
{
note: discussionMock.notes[0],
component: NoteableNote,
},
])('uses $component.name to render note', ({ note, component }) => {
createComponent({
discussion: { ...discussionMock, notes: [note] },
});
expect(notes.at(0).is(PlaceholderSystemNote)).toBe(true);
expect(notes.at(1).is(PlaceholderNote)).toBe(true);
expect(notes.at(2).is(SystemNote)).toBe(true);
expect(notes.at(3).is(NoteableNote)).toBe(true);
expect(wrapper.findComponent(component).exists()).toBe(true);
});
it('renders footer scoped slot with showReplies === true when expanded', () => {
@ -131,35 +134,27 @@ describe('DiscussionNotes', () => {
});
describe('events', () => {
describe('with groupped notes and replies expanded', () => {
const findNoteAtIndex = (index) => {
const noteComponents = [NoteableNote, SystemNote, PlaceholderNote, PlaceholderSystemNote];
return wrapper
.findAll('.notes *')
.filter((w) => noteComponents.some((Component) => w.is(Component)))
.at(index);
};
describe('with grouped notes and replies expanded', () => {
beforeEach(() => {
createComponent({ shouldGroupReplies: true, isExpanded: true });
});
it('emits deleteNote when first note emits handleDeleteNote', async () => {
findNoteAtIndex(0).vm.$emit('handleDeleteNote');
findNoteableNotes().at(0).vm.$emit('handleDeleteNote');
await nextTick();
expect(wrapper.emitted().deleteNote).toHaveLength(1);
});
it('emits startReplying when first note emits startReplying', async () => {
findNoteAtIndex(0).vm.$emit('startReplying');
findNoteableNotes().at(0).vm.$emit('startReplying');
await nextTick();
expect(wrapper.emitted().startReplying).toHaveLength(1);
});
it('emits deleteNote when second note emits handleDeleteNote', async () => {
findNoteAtIndex(1).vm.$emit('handleDeleteNote');
findNoteableNotes().at(1).vm.$emit('handleDeleteNote');
await nextTick();
expect(wrapper.emitted().deleteNote).toHaveLength(1);

View File

@ -351,7 +351,7 @@ describe('noteActions', () => {
const resolveButton = wrapper.findComponent({ ref: 'resolveButton' });
expect(resolveButton.exists()).toBe(true);
expect(resolveButton.attributes('title')).toBe('Thread stays unresolved');
expect(resolveButton.attributes('title')).toBe('Thread stays open');
});
});

View File

@ -16,7 +16,7 @@ describe('Merge request merge checks message component', () => {
${'commits_status'} | ${'Source branch exists and contains commits.'}
${'ci_must_pass'} | ${'Pipeline must succeed.'}
${'conflict'} | ${'Merge conflicts must be resolved.'}
${'discussions_not_resolved'} | ${'Unresolved discussions must be resolved.'}
${'discussions_not_resolved'} | ${'Open threads must be resolved.'}
${'draft_status'} | ${'Merge request must not be draft.'}
${'not_open'} | ${'Merge request must be open.'}
${'need_rebase'} | ${'Merge request must be rebased, because a fast-forward merge is not possible.'}

View File

@ -31,9 +31,9 @@ describe('MergeChecksUnresolvedDiscussions component', () => {
expect(wrapper.findComponent(MergeChecksMessage).props('check')).toEqual(check);
});
it('does not show go to first unresolved discussion button with passed state', () => {
it('does not show go to first open discussion button with passed state', () => {
createComponent({ check: { status: 'success', identifier: 'discussions_not_resolved' } });
const button = wrapper.findByRole('button', { name: 'Go to first unresolved thread' });
const button = wrapper.findByRole('button', { name: 'Go to first open thread' });
expect(button.exists()).toBe(false);
});
@ -42,7 +42,7 @@ describe('MergeChecksUnresolvedDiscussions component', () => {
notesEventHub.$on('jumpToFirstUnresolvedDiscussion', callback);
createComponent();
wrapper.findByRole('button', { name: 'Go to first unresolved thread' }).trigger('click');
wrapper.findByRole('button', { name: 'Go to first open thread' }).trigger('click');
expect(callback).toHaveBeenCalled();
});

View File

@ -284,12 +284,12 @@ describe('Design discussions component', () => {
expect(findReplyPlaceholder().isVisible()).toBe(true);
});
it('renders a checkbox with Unresolve thread text in reply form', async () => {
it('renders a checkbox with Reopen thread text in reply form', async () => {
findReplyPlaceholder().vm.$emit('focus');
wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });
await nextTick();
expect(findResolveCheckbox().text()).toBe('Unresolve thread');
expect(findResolveCheckbox().text()).toBe('Reopen thread');
});
});

View File

@ -0,0 +1,277 @@
# frozen_string_literal: true
require 'spec_helper'
# rubocop:disable RSpec/MultipleMemoizedHelpers -- Necessary for backfill setup
RSpec.describe Gitlab::BackgroundMigration::BackfillSentNotificationsAfterPartition, feature_category: :team_planning do
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let(:sent_notifications) { table(:sent_notifications) }
let(:p_sent_notifications) { partitioned_table(:sent_notifications_7abbf02cb6) }
let(:organization) { table(:organizations).create!(name: 'organization', path: 'organization') }
let(:user) { table(:users).create!(email: 'email@example.com', username: 'user1', projects_limit: 10) }
let(:mr_namespace) do
namespaces.create!(name: "mr", path: "mr", organization_id: organization.id)
end
let(:mr_project) do
projects.create!(
namespace_id: mr_namespace.id,
project_namespace_id: mr_namespace.id,
organization_id: organization.id
)
end
let(:merge_request) do
table(:merge_requests).create!(target_project_id: mr_project.id, target_branch: 'main', source_branch: 'not-main')
end
let(:issue_namespace) do
namespaces.create!(name: "issue", path: "issue", organization_id: organization.id)
end
let(:issue_work_item_type_id) { table(:work_item_types).find_by(name: 'Issue').id }
let(:issue) do
table(:issues).create!(
title: 'First issue',
iid: 1,
namespace_id: issue_namespace.id,
work_item_type_id: issue_work_item_type_id
)
end
let(:epic_namespace) do
namespaces.create!(name: "epic", path: "epic", organization_id: organization.id)
end
let(:epic) do
table(:epics).create!(
group_id: epic_namespace.id,
author_id: user.id,
iid: 1,
title: 't',
title_html: 't',
issue_id: issue.id
)
end
let(:snippet_namespace) do
namespaces.create!(name: "snippet", path: "snippet", organization_id: organization.id)
end
let(:snippet_project) do
projects.create!(
namespace_id: snippet_namespace.id,
project_namespace_id: snippet_namespace.id,
organization_id: organization.id
)
end
let(:project_snippet) do
table(:snippets).create!(
type: 'ProjectSnippet',
author_id: user.id,
project_id: snippet_project.id,
title: 'Snippet3'
)
end
let(:design_namespace) do
namespaces.create!(name: "design", path: "design", organization_id: organization.id)
end
let(:design_project) do
projects.create!(
namespace_id: design_namespace.id,
project_namespace_id: design_namespace.id,
organization_id: organization.id
)
end
let(:design) do
table(:design_management_designs).create!(project_id: design_project.id, filename: 'final_v2.jpg', iid: 1)
end
let(:wiki_page_namespace) do
namespaces.create!(name: "wiki", path: "wiki", organization_id: organization.id)
end
let(:wiki_page_project) do
projects.create!(
namespace_id: wiki_page_namespace.id,
project_namespace_id: wiki_page_namespace.id,
organization_id: organization.id
)
end
let(:wpm_project) { table(:wiki_page_meta).create!(title: 'Backlog', project_id: wiki_page_project.id) }
let(:wpm_group) { table(:wiki_page_meta).create!(title: 'Backlog', namespace_id: wiki_page_namespace.id) }
let(:migration) do
start_id, end_id = sent_notifications.pick('MIN(id), MAX(id)')
described_class.new(
start_id: start_id,
end_id: end_id,
batch_table: :sent_notifications,
batch_column: :id,
sub_batch_size: 2,
pause_ms: 0,
job_arguments: [],
connection: ApplicationRecord.connection
)
end
let!(:issue_notification) do
sent_notifications.create!(
noteable_type: 'Issue',
noteable_id: issue.id,
namespace_id: 0,
reply_key: 'issue'
)
end
let!(:mr_notification) do
sent_notifications.create!(
noteable_type: 'MergeRequest',
noteable_id: merge_request.id,
namespace_id: 0,
reply_key: 'mr'
)
end
let!(:epic_notification) do
sent_notifications.create!(
noteable_type: 'Epic',
noteable_id: epic.id,
namespace_id: 0,
reply_key: 'epic'
)
end
let!(:design_notification) do
sent_notifications.create!(
noteable_type: 'DesignManagement::Design',
noteable_id: design.id,
namespace_id: 0,
reply_key: 'design'
)
end
let(:commit_namespace) do
namespaces.create!(name: "commit", path: "commit", organization_id: organization.id)
end
let(:commit_project) do
projects.create!(
namespace_id: commit_namespace.id,
project_namespace_id: commit_namespace.id,
organization_id: organization.id
)
end
let!(:commit_notification) do
sent_notifications.create!(
noteable_type: 'Commit',
noteable_id: nil,
commit_id: '15db82db72d40952d7bc929888a0164bec3cc4e4',
namespace_id: 0,
project_id: commit_project.id,
reply_key: 'commit'
)
end
let!(:wpm_project_notification) do
sent_notifications.create!(
noteable_type: 'WikiPage::Meta',
noteable_id: wpm_project.id,
namespace_id: 0,
reply_key: 'wpmp'
)
end
let!(:wpm_group_notification) do
sent_notifications.create!(
noteable_type: 'WikiPage::Meta',
noteable_id: wpm_group.id,
namespace_id: 0,
reply_key: 'wpmg'
)
end
let!(:snippet_notification) do
sent_notifications.create!(
noteable_type: 'ProjectSnippet',
noteable_id: project_snippet.id,
namespace_id: 0,
reply_key: 'snippet'
)
end
let(:notification_already_on_partitioned_table) do
sent_notifications.create!(
noteable_type: 'ProjectSnippet',
noteable_id: project_snippet.id,
namespace_id: 0,
reply_key: 'existing_record'
)
end
subject(:migrate) { migration.perform }
before do
# invalid type record should not be backfilled
sent_notifications.create!(
noteable_type: 'INVALID_TYPE',
noteable_id: issue.id,
namespace_id: 0,
reply_key: 'invalid_record_s'
)
# valid type but missing record should not be backfilled
sent_notifications.create!(
noteable_type: 'Issue',
noteable_id: -1,
namespace_id: 0,
reply_key: 'deleted_issue_id'
)
# delete all because the sync trigger will create all the test records
p_sent_notifications.delete_all
# Intentionally creating this record in both tables to test existing records don't make the insert fail because of
# a unique constraint on the PK
notification_already_on_partitioned_table
end
describe '#up' do
it 'inserts records in batches' do
expect do
migrate
# 11 records, 2 per batch, 6 batches, 7 queries per batch
end.to make_queries_matching(/INSERT INTO sent_notifications_7abbf02cb6/, 42)
end
it 'sets correct namespace_id in every record' do
expect do
migrate
end.to change { p_sent_notifications.count }.from(1).to(9)
expect(p_sent_notifications.all).to contain_exactly(
have_attributes(id: issue_notification.id, namespace_id: issue_namespace.id),
have_attributes(id: mr_notification.id, namespace_id: mr_namespace.id),
have_attributes(id: epic_notification.id, namespace_id: epic_namespace.id),
have_attributes(id: design_notification.id, namespace_id: design_namespace.id),
have_attributes(id: commit_notification.id, namespace_id: commit_namespace.id),
have_attributes(id: wpm_project_notification.id, namespace_id: wiki_page_namespace.id),
have_attributes(id: wpm_group_notification.id, namespace_id: wiki_page_namespace.id),
have_attributes(id: snippet_notification.id, namespace_id: snippet_namespace.id),
# We do not update the namespace_id for records that made it to the partitioned table already.
# Every record in the partitioned table is ALWAYS created with the right namespace_id
have_attributes(id: notification_already_on_partitioned_table.id, namespace_id: 0)
)
end
end
end
# rubocop:enable RSpec/MultipleMemoizedHelpers

View File

@ -0,0 +1,104 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillSentNotificationsAfterPartition, migration: :gitlab_main, feature_category: :team_planning do
let!(:batched_migration) { described_class::MIGRATION }
let(:sent_notifications) { table(:sent_notifications) }
let(:partitioned_sent_notifications) { partitioned_table(:sent_notifications_7abbf02cb6) }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_main,
table_name: :sent_notifications,
column_name: :id,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
batch_min_value: 1,
batch_max_value: 1
)
}
end
end
context 'when migration is run in .com', :saas do
before do
sent_notifications.create!(
id: described_class::DOT_COM_START_ID - 1,
noteable_type: 'Issue',
noteable_id: 2,
namespace_id: 1,
reply_key: 'key1'
)
sent_notifications.create!(
id: described_class::DOT_COM_START_ID + 1,
noteable_type: 'Issue',
noteable_id: 2,
namespace_id: 1,
reply_key: 'key2'
)
sent_notifications.create!(
id: described_class::DOT_COM_START_ID + 2,
noteable_type: 'Issue',
noteable_id: 2,
namespace_id: 1,
reply_key: 'key3'
)
end
it 'sets the min id to the value defined for .com' do
migrate!
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_main,
table_name: :sent_notifications,
column_name: :id,
batch_size: described_class::GITLAB_OPTIMIZED_BATCH_SIZE,
sub_batch_size: described_class::GITLAB_OPTIMIZED_SUB_BATCH_SIZE,
max_batch_size: described_class::GITLAB_OPTIMIZED_MAX_BATCH_SIZE,
batch_min_value: described_class::DOT_COM_START_ID,
batch_max_value: described_class::DOT_COM_START_ID + 2
)
end
end
context 'when records exist in the partitioned table already' do
before do
partitioned_sent_notifications.create!(
id: 100,
noteable_type: 'Issue',
noteable_id: 2,
namespace_id: 1,
reply_key: 'key'
)
partitioned_sent_notifications.create!(
id: 101,
noteable_type: 'Issue',
noteable_id: 2,
namespace_id: 1,
reply_key: 'key'
)
end
it 'sets the max id to the minimum on the partitioned table' do
migrate!
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_main,
table_name: :sent_notifications,
column_name: :id,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
batch_min_value: 1,
batch_max_value: 100
)
end
end
end

View File

@ -22,7 +22,8 @@ module SimpleCovEnvCore
def configure_profile
SimpleCov.configure do
load_profile 'test_frameworks'
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/194688#note_2595596467
add_filter %r{^/(ee/|jh/)?spec/}
add_filter %r{^/(ee/)?(bin|gems|vendor)}
add_filter %r{^/(ee/)?db/fixtures/development}