Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d8aae64906
commit
a3271972b5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ export default {
|
|||
},
|
||||
resolveCheckboxText() {
|
||||
return this.discussion.resolved
|
||||
? s__('DesignManagement|Unresolve thread')
|
||||
? s__('DesignManagement|Reopen thread')
|
||||
: s__('DesignManagement|Resolve thread');
|
||||
},
|
||||
firstNote() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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.'),
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export default {
|
|||
},
|
||||
resolveCheckboxText() {
|
||||
return this.discussion.resolved
|
||||
? s__('DesignManagement|Unresolve thread')
|
||||
? s__('DesignManagement|Reopen thread')
|
||||
: s__('DesignManagement|Resolve thread');
|
||||
},
|
||||
firstNote() {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
2d2a8ed393743febc3d36889fd9be71d275104565a41ff7374a3ae04befdfe97
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||

|
||||

|
||||
|
||||
- Does it cross-link to an issue? Check the description and merge widget
|
||||
for links to other issues. Some merge requests are straightforward, but
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>[</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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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.'}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in New Issue