From 60b750213fb0b1cc54f48d6acb7ea758e4551bbe Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 11 Nov 2024 21:14:18 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- app/models/issue.rb | 2 + app/models/issue_email_participant.rb | 1 + .../work_items/widgets/email_participants.rb | 2 + .../data_sync/widgets/email_participants.rb | 21 ++++- doc/development/integrations/index.md | 4 +- .../secret_detection/pipeline/index.md | 2 +- .../project/integrations/webhook_events.md | 17 ++-- .../widgets/email_participants_spec.rb | 78 +++++++++++++++++++ ...eable_and_moveable_data_stared_examples.rb | 18 ++++- 9 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 spec/services/work_items/data_sync/widgets/email_participants_spec.rb diff --git a/app/models/issue.rb b/app/models/issue.rb index 0808d3c4c06..e4fa7164b62 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -92,6 +92,8 @@ class Issue < ApplicationRecord has_many :issue_assignees has_many :issue_email_participants + alias_method :email_participants, :issue_email_participants + has_one :email has_many :assignees, class_name: "User", through: :issue_assignees has_many :zoom_meetings diff --git a/app/models/issue_email_participant.rb b/app/models/issue_email_participant.rb index bb03b3d72e6..472b344b4c3 100644 --- a/app/models/issue_email_participant.rb +++ b/app/models/issue_email_participant.rb @@ -4,6 +4,7 @@ class IssueEmailParticipant < ApplicationRecord include BulkInsertSafe include Presentable include CaseSensitivity + include EachBatch belongs_to :issue diff --git a/app/models/work_items/widgets/email_participants.rb b/app/models/work_items/widgets/email_participants.rb index 37f385d8100..d7bed30f730 100644 --- a/app/models/work_items/widgets/email_participants.rb +++ b/app/models/work_items/widgets/email_participants.rb @@ -5,6 +5,8 @@ module WorkItems class EmailParticipants < Base delegate :issue_email_participants, to: :work_item + alias_method :email_participants, :issue_email_participants + def self.quick_action_commands [:add_email, :remove_email] end diff --git a/app/services/work_items/data_sync/widgets/email_participants.rb b/app/services/work_items/data_sync/widgets/email_participants.rb index e7b0bf2d7e8..608a1ab40bf 100644 --- a/app/services/work_items/data_sync/widgets/email_participants.rb +++ b/app/services/work_items/data_sync/widgets/email_participants.rb @@ -4,12 +4,27 @@ module WorkItems module DataSync module Widgets class EmailParticipants < Base - def after_save_commit - # copy email participants + def after_create + return unless params[:operation] == :move + return unless target_work_item.get_widget(:email_participants) + + work_item.email_participants.each_batch(of: BATCH_SIZE) do |email_participants_batch| + ::IssueEmailParticipant.insert_all(new_work_item_email_participants(email_participants_batch)) + end end def post_move_cleanup - # do it + work_item.email_participants.each_batch(of: BATCH_SIZE) do |email_participants_batch| + email_participants_batch.delete_all + end + end + + private + + def new_work_item_email_participants(email_participants_batch) + email_participants_batch.map do |email_participant| + email_participant.attributes.except("id").tap { |ep| ep["issue_id"] = target_work_item.id } + end end end end diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 61b8a1308e1..85888f10145 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -110,8 +110,8 @@ The following events are supported for integrations: | Alert event | | `alert` | A a new, unique alert is recorded. | | Commit event | ✓ | `commit` | A commit is created or updated. | | [Deployment event](../../user/project/integrations/webhook_events.md#deployment-events) | | `deployment` | A deployment starts or finishes. | -| [Issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `issue` | An issue is created, updated, or closed. | -| [Confidential issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `confidential_issue` | A confidential issue is created, updated, or closed. | +| [Work item event](../../user/project/integrations/webhook_events.md#work-item-events) | ✓ | `issue` | An issue is created, updated, or closed. | +| [Confidential issue event](../../user/project/integrations/webhook_events.md#work-item-events) | ✓ | `confidential_issue` | A confidential issue is created, updated, or closed. | | [Job event](../../user/project/integrations/webhook_events.md#job-events) | | `job` | | | [Merge request event](../../user/project/integrations/webhook_events.md#merge-request-events) | ✓ | `merge_request` | A merge request is created, updated, or merged. | | [Comment event](../../user/project/integrations/webhook_events.md#comment-events) | | `comment` | A new comment is added. | diff --git a/doc/user/application_security/secret_detection/pipeline/index.md b/doc/user/application_security/secret_detection/pipeline/index.md index c68359a9c18..a63d967d72a 100644 --- a/doc/user/application_security/secret_detection/pipeline/index.md +++ b/doc/user/application_security/secret_detection/pipeline/index.md @@ -843,7 +843,7 @@ Prerequisites: [local Docker container registry](../../../packages/container_registry/index.md): ```plaintext - registry.gitlab.com/security-products/secrets:4 + registry.gitlab.com/security-products/secrets:6 ``` The pipeline secret detection analyzer's image is [periodically updated](../../index.md#vulnerability-scanner-maintenance) diff --git a/doc/user/project/integrations/webhook_events.md b/doc/user/project/integrations/webhook_events.md index bf58b595756..fb704f18d2a 100644 --- a/doc/user/project/integrations/webhook_events.md +++ b/doc/user/project/integrations/webhook_events.md @@ -20,15 +20,15 @@ Event type | Trigger ---------------------------------------------|----------------------------------------------------------------------------- [Push event](#push-events) | A push is made to the repository. [Tag event](#tag-events) | Tags are created or deleted in the repository. -[Issue event](#issue-events) | A new issue is created or an existing issue is updated, closed, or reopened. +[Work item event](#work-item-events) | A new work item is created or an existing one is edited, closed, or reopened. [Comment event](#comment-events) | A new comment is made or edited on commits, merge requests, issues, and code snippets. 1 -[Merge request event](#merge-request-events) | A merge request is created, updated, merged, or closed, or a commit is added in the source branch. -[Wiki page event](#wiki-page-events) | A wiki page is created, updated, or deleted. +[Merge request event](#merge-request-events) | A merge request is created, edited, merged, or closed, or a commit is added in the source branch. +[Wiki page event](#wiki-page-events) | A wiki page is created, edited, or deleted. [Pipeline event](#pipeline-events) | A pipeline status changes. [Job event](#job-events) | A job status changes. [Deployment event](#deployment-events) | A deployment starts, succeeds, fails, or is canceled. [Feature flag event](#feature-flag-events) | A feature flag is turned on or off. -[Release event](#release-events) | A release is created, updated, or deleted. +[Release event](#release-events) | A release is created, edited, or deleted. [Emoji event](#emoji-events) | An emoji reaction is added or removed. [Project or group access token event](#project-and-group-access-token-events) | A project or group access token will expire in seven days. @@ -207,13 +207,16 @@ Payload example: } ``` -## Issue events +## Work item events > - `type` attribute in `object_attributes` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/467415) in GitLab 17.2. +> - Support for epics [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13056) in GitLab 17.3. Your administrator must have [enabled the new look for epics](../../group/epics/epic_work_items.md). -Issue events are triggered when an issue or work item is created, updated, closed, or reopened. +Work item events are triggered when a work item is created, edited, closed, or reopened. The supported work item types are: +- [Epics](../../group/epics/index.md) +- [Issue](../../project/issues/index.md) - [Tasks](../../tasks.md) - [Incidents](../../../operations/incident_management/incidents.md) - [Test cases](../../../ci/test_cases/index.md) @@ -223,6 +226,8 @@ The supported work item types are: For issues and [Service Desk](../service_desk/index.md) issues, the `object_kind` is `issue`, and the `type` is `Issue`. For all other work items, the `object_kind` field is `work_item`, and the `type` is the work item type. +For work item type `Epic`, to get events for changes, the webhook must be registered for the group. + The available values for `object_attributes.action` in the payload are: - `open` diff --git a/spec/services/work_items/data_sync/widgets/email_participants_spec.rb b/spec/services/work_items/data_sync/widgets/email_participants_spec.rb new file mode 100644 index 00000000000..20e966ed36b --- /dev/null +++ b/spec/services/work_items/data_sync/widgets/email_participants_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::DataSync::Widgets::EmailParticipants, feature_category: :team_planning do + let_it_be(:current_user) { create(:user) } + let_it_be_with_reload(:work_item) { create(:work_item) } + let_it_be(:target_work_item) { create(:work_item) } + let_it_be(:participant1) { create(:issue_email_participant, issue: work_item, email: 'user1@example.com') } + let_it_be(:participant2) { create(:issue_email_participant, issue: work_item, email: 'user2@example.com') } + + let(:params) { { operation: :move } } + + subject(:callback) do + described_class.new( + work_item: work_item, target_work_item: target_work_item, current_user: current_user, params: params + ) + end + + describe '#after_create' do + context 'when target work item has email_participants widget' do + before do + allow(target_work_item).to receive(:get_widget).with(:email_participants).and_return(true) + end + + it 'copies email_participants from work_item to target_work_item' do + expect(callback).to receive(:new_work_item_email_participants).and_call_original + expect(::IssueEmailParticipant).to receive(:insert_all).and_call_original + + callback.after_create + + target_email_participants = target_work_item.reload.issue_email_participants.map(&:email) + expect(target_email_participants).to match_array([participant1, participant2].map(&:email)) + end + end + + context 'when operation is clone' do + let(:params) { { operation: :clone } } + + it 'does not copy email_participants' do + expect(callback).not_to receive(:new_work_item_email_participants) + expect(::IssueEmailParticipant).not_to receive(:insert_all) + + callback.after_create + + expect(target_work_item.reload.issue_email_participants).to be_empty + end + end + + context 'when target work item does not have email_participants widget' do + before do + target_work_item.reload + allow(target_work_item).to receive(:get_widget).with(:email_participants).and_return(false) + end + + it 'does not copy email_participants' do + expect(callback).not_to receive(:new_work_item_email_participants) + expect(::IssueEmailParticipant).not_to receive(:insert_all) + + callback.after_create + + expect(target_work_item.reload.issue_email_participants).to be_empty + end + end + end + + describe '#post_move_cleanup' do + it 'is defined and can be called' do + expect { callback.post_move_cleanup }.not_to raise_error + end + + it 'removes original work item email_participants' do + callback.post_move_cleanup + + expect(work_item.issue_email_participants).to be_empty + end + end +end diff --git a/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb b/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb index 6bea9ff97bf..85e197f758c 100644 --- a/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb +++ b/spec/support/shared_examples/services/work_items/data_sync/cloneable_and_moveable_data_stared_examples.rb @@ -58,9 +58,8 @@ RSpec.shared_examples 'cloneable and moveable widget data' do work_item.reload.award_emoji.pluck(:user_id, :name) end - where(:widget_name, :eval_value, :expected_data, :operations) do - :assignees | :work_item_assignees | ref(:assignees) | [ref(:move), ref(:clone)] - :award_emoji | :work_item_award_emoji | ref(:award_emojis) | [ref(:move)] + def work_item_emails(work_item) + work_item.reload.email_participants.pluck(:email) end let(:move) { WorkItems::DataSync::MoveService } @@ -70,14 +69,27 @@ RSpec.shared_examples 'cloneable and moveable widget data' do let_it_be(:milestone) { create(:milestone) } let_it_be(:thumbs_ups) { create_list(:award_emoji, 2, name: 'thumbsup', awardable: original_work_item) } let_it_be(:thumbs_downs) { create_list(:award_emoji, 2, name: 'thumbsdown', awardable: original_work_item) } + let_it_be(:participant2) { create(:issue_email_participant, issue: original_work_item, email: 'user2@example.com') } let_it_be(:award_emojis) { original_work_item.reload.award_emoji.pluck(:user_id, :name) } + let_it_be(:emails) do + create_list(:issue_email_participant, 2, issue: original_work_item) + # create email participants on original work item and return emails as `expected_data` for later comparison. + original_work_item.reload.email_participants.map(&:email) + end + let_it_be(:assignees) do original_work_item.assignee_ids = users.map(&:id) # set assignees and return assigned users as `expected_data` for later comparison. users end + where(:widget_name, :eval_value, :expected_data, :operations) do + :assignees | :work_item_assignees | ref(:assignees) | [ref(:move), ref(:clone)] + :award_emoji | :work_item_award_emoji | ref(:award_emojis) | [ref(:move)] + :email_participants | :work_item_emails | ref(:emails) | [ref(:move)] + end + with_them do context "with widget" do it 'clones and moves widget data' do