Skip to content
Snippets Groups Projects
Commit 3de34293 authored by Adam Hegyi's avatar Adam Hegyi
Browse files

Track issue and MR assignment events

This change starts tracking the issue and MR assignment events. The
change is behind a feature flag.
parent 74aa1b75
No related branches found
No related tags found
2 merge requests!118700Remove refactor_vulnerability_filters feature flag,!117545Track issue and MR assignment events
......@@ -93,6 +93,7 @@ def handle_assignee_changes(issue, old_assignees)
return if issue.assignees == old_assignees
create_assignee_note(issue, old_assignees)
Gitlab::ResourceEvents::AssignmentEventRecorder.new(parent: issue, old_assignees: old_assignees).record
end
def resolve_discussions_with_issue(issue)
......
......@@ -75,6 +75,7 @@ def handle_assignee_changes(issue, old_assignees)
return if issue.assignees == old_assignees
create_assignee_note(issue, old_assignees)
Gitlab::ResourceEvents::AssignmentEventRecorder.new(parent: issue, old_assignees: old_assignees).record
notification_service.async.reassigned_issue(issue, current_user, old_assignees)
todo_service.reassigned_assignable(issue, current_user, old_assignees)
track_incident_action(current_user, issue, :incident_assigned)
......
......@@ -15,6 +15,7 @@ def async_execute(merge_request, old_assignees, options = {})
def execute(merge_request, old_assignees, options = {})
create_assignee_note(merge_request, old_assignees)
notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignees.to_a)
Gitlab::ResourceEvents::AssignmentEventRecorder.new(parent: merge_request, old_assignees: old_assignees).record
todo_service.reassigned_assignable(merge_request, current_user, old_assignees)
new_assignees = merge_request.assignees - old_assignees
......
---
name: record_issue_and_mr_assignee_events
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117545
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397050
milestone: '15.11'
type: development
group: group::optimize
default_enabled: false
# frozen_string_literal: true
module Gitlab
module ResourceEvents
class AssignmentEventRecorder
BATCH_SIZE = 100
def initialize(parent:, old_assignees:)
@parent = parent
@old_assignees = old_assignees
end
def record
return if Feature.disabled?(:record_issue_and_mr_assignee_events, parent.project)
case parent
when Issue
record_for_parent(
::ResourceEvents::IssueAssignmentEvent,
:issue_id,
parent,
old_assignees
)
when MergeRequest
record_for_parent(
::ResourceEvents::MergeRequestAssignmentEvent,
:merge_request_id,
parent,
old_assignees
)
end
end
private
attr_reader :parent, :old_assignees
def record_for_parent(resource_klass, foreign_key, parent, old_assignees)
removed_events = (old_assignees - parent.assignees).map do |unassigned_user|
{
foreign_key => parent.id,
user_id: unassigned_user.id,
action: :remove
}
end.to_set
added_events = (parent.assignees.to_a - old_assignees).map do |added_user|
{
foreign_key => parent.id,
user_id: added_user.id,
action: :add
}
end.to_set
(removed_events + added_events).each_slice(BATCH_SIZE) do |events|
resource_klass.insert_all(events)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::ResourceEvents::AssignmentEventRecorder, feature_category: :value_stream_management do
using RSpec::Parameterized::TableSyntax
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
let_it_be(:user3) { create(:user) }
let_it_be_with_refind(:issue_with_two_assignees) { create(:issue, assignees: [user1, user2]) }
let_it_be_with_refind(:mr_with_no_assignees) { create(:merge_request) }
let_it_be_with_refind(:mr_with_one_assignee) { create(:merge_request, assignee: [user3]) }
let(:parent_records) do
{
issue_with_two_assignees: issue_with_two_assignees,
mr_with_no_assignees: mr_with_no_assignees,
mr_with_one_assignee: mr_with_one_assignee
}
end
let(:user_records) do
{
user1: user1,
user2: user2,
user3: user3
}
end
where(:parent, :new_assignees, :assignee_history) do
:issue_with_two_assignees | [:user1, :user2, :user3] | [[:user3, :add]]
:issue_with_two_assignees | [:user1, :user3] | [[:user2, :remove], [:user3, :add]]
:issue_with_two_assignees | [:user1] | [[:user2, :remove]]
:issue_with_two_assignees | [] | [[:user1, :remove], [:user2, :remove]]
:mr_with_no_assignees | [:user1] | [[:user1, :add]]
:mr_with_no_assignees | [] | []
:mr_with_one_assignee | [:user3] | []
:mr_with_one_assignee | [:user1] | [[:user3, :remove], [:user1, :add]]
end
with_them do
it 'records the assignment history corrently' do
parent_record = parent_records[parent]
old_assignees = parent_record.assignees.to_a
parent_record.assignees = new_assignees.map { |user_variable_name| user_records[user_variable_name] }
described_class.new(parent: parent_record, old_assignees: old_assignees).record
expected_records = assignee_history.map do |user_variable_name, action|
have_attributes({
user_id: user_records[user_variable_name].id,
action: action.to_s
})
end
expect(parent_record.assignment_events).to match_array(expected_records)
end
end
context 'when batching' do
it 'invokes multiple insert queries' do
stub_const('Gitlab::ResourceEvents::AssignmentEventRecorder::BATCH_SIZE', 1)
expect(ResourceEvents::MergeRequestAssignmentEvent).to receive(:insert_all).twice
described_class.new(parent: mr_with_one_assignee, old_assignees: [user1]).record # 1 assignment, 1 unassignment
end
end
context 'when duplicated old assignees were given' do
it 'deduplicates the records' do
expect do
described_class.new(parent: mr_with_one_assignee, old_assignees: [user3, user2, user2]).record
end.to change { ResourceEvents::MergeRequestAssignmentEvent.count }.by(1)
end
end
context 'when the record_issue_and_mr_assignee_events FF is off' do
before do
stub_feature_flags(record_issue_and_mr_assignee_events: false)
end
it 'does nothing' do
expect do
described_class.new(parent: mr_with_one_assignee, old_assignees: [user2, user3]).record
end.not_to change { mr_with_one_assignee.assignment_events.count }
end
end
end
......@@ -381,6 +381,13 @@
expect(assignee.assigned_open_issues_count).to eq 1
end
it 'records the assignee assignment event' do
result = described_class.new(container: project, current_user: user, params: opts, spam_params: spam_params).execute
issue = result.payload[:issue]
expect(issue.assignment_events).to match([have_attributes(user_id: assignee.id, action: 'add')])
end
end
context 'when duplicate label titles are given' do
......
......@@ -1159,6 +1159,39 @@ def update_issue(opts)
end
end
end
it 'tracks the assignment events' do
original_assignee = issue.assignees.first!
update_issue(assignee_ids: [user2.id])
update_issue(assignee_ids: [])
update_issue(assignee_ids: [user3.id])
expected_events = [
have_attributes({
issue_id: issue.id,
user_id: original_assignee.id,
action: 'remove'
}),
have_attributes({
issue_id: issue.id,
user_id: user2.id,
action: 'add'
}),
have_attributes({
issue_id: issue.id,
user_id: user2.id,
action: 'remove'
}),
have_attributes({
issue_id: issue.id,
user_id: user3.id,
action: 'add'
})
]
expect(issue.assignment_events).to match_array(expected_events)
end
end
context 'updating mentions' do
......
......@@ -445,13 +445,21 @@
}
end
it 'invalidates open merge request counter for assignees when merge request is assigned' do
before do
project.add_maintainer(user2)
end
it 'invalidates open merge request counter for assignees when merge request is assigned' do
described_class.new(project: project, current_user: user, params: opts).execute
expect(user2.assigned_open_merge_requests_count).to eq 1
end
it 'records the assignee assignment event', :sidekiq_inline do
mr = described_class.new(project: project, current_user: user, params: opts).execute.reload
expect(mr.assignment_events).to match([have_attributes(user_id: user2.id, action: 'add')])
end
end
context "when issuable feature is private" do
......
......@@ -782,6 +782,27 @@ def update_merge_request(opts)
expect(user3.assigned_open_merge_requests_count).to eq 0
expect(user2.assigned_open_merge_requests_count).to eq 1
end
it 'records the assignment history', :sidekiq_inline do
original_assignee = merge_request.assignees.first!
update_merge_request(assignee_ids: [user2.id])
expected_events = [
have_attributes({
merge_request_id: merge_request.id,
user_id: original_assignee.id,
action: 'remove'
}),
have_attributes({
merge_request_id: merge_request.id,
user_id: user2.id,
action: 'add'
})
]
expect(merge_request.assignment_events).to match_array(expected_events)
end
end
context 'when the target branch changes' do
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment