Commit 633ab253 authored by Alexandru Croitor's avatar Alexandru Croitor

Fix rendering of duplicate system notes

This fixes rendering of duplicate weight change system notes,
when first weight change event is created, by generating different
discussion_id for events that are created close to each other in time
parent fa534442
...@@ -305,6 +305,10 @@ class Issue < ApplicationRecord ...@@ -305,6 +305,10 @@ class Issue < ApplicationRecord
labels.map(&:hook_attrs) labels.map(&:hook_attrs)
end end
def previous_updated_at
previous_changes['updated_at']&.first || updated_at
end
private private
def ensure_metrics def ensure_metrics
......
...@@ -12,6 +12,7 @@ class MilestoneNote < ::Note ...@@ -12,6 +12,7 @@ class MilestoneNote < ::Note
created_at: event.created_at, created_at: event.created_at,
noteable: resource, noteable: resource,
milestone: event.milestone, milestone: event.milestone,
discussion_id: event.discussion_id,
event: event, event: event,
system_note_metadata: ::SystemNoteMetadata.new(action: 'milestone'), system_note_metadata: ::SystemNoteMetadata.new(action: 'milestone'),
resource_parent: resource_parent resource_parent: resource_parent
......
...@@ -21,7 +21,7 @@ class ResourceEvent < ApplicationRecord ...@@ -21,7 +21,7 @@ class ResourceEvent < ApplicationRecord
private private
def discussion_id_key def discussion_id_key
[self.class.name, created_at, user_id] [self.class.name, id, user_id]
end end
def exactly_one_issuable def exactly_one_issuable
......
...@@ -103,6 +103,10 @@ class ResourceLabelEvent < ResourceEvent ...@@ -103,6 +103,10 @@ class ResourceLabelEvent < ResourceEvent
def resource_parent def resource_parent
issuable.project || issuable.group issuable.project || issuable.group
end end
def discussion_id_key
[self.class.name, created_at, user_id]
end
end end
ResourceLabelEvent.prepend_if_ee('EE::ResourceLabelEvent') ResourceLabelEvent.prepend_if_ee('EE::ResourceLabelEvent')
---
title: Ensure weight changes no longer render duplicate system notes
merge_request: 26014
author:
type: fixed
...@@ -7,5 +7,19 @@ module EE ...@@ -7,5 +7,19 @@ module EE
included do included do
has_many :resource_weight_events has_many :resource_weight_events
end end
def previous_weight
previous_changes['weight']&.first
end
# We want to know if resource already had a weight but no weight tracking events(i.e. no resource_weight_events record)
# in which case we will create a historical resource_weight_event record with last known weight. That is to try and
# populate more data when tracking changes, without having a huge migration parsing weight change notes.
#
# At some point this should be removed, when we will have enough historical data and it will be of no use:
# https://gitlab.com/gitlab-org/gitlab/issues/208785
def first_weight_event?
previous_weight.present? && resource_weight_events.none?
end
end end
end end
...@@ -13,6 +13,7 @@ module EE ...@@ -13,6 +13,7 @@ module EE
created_at: event.created_at, created_at: event.created_at,
noteable: resource, noteable: resource,
event: event, event: event,
discussion_id: event.discussion_id,
system_note_metadata: ::SystemNoteMetadata.new(action: 'weight'), system_note_metadata: ::SystemNoteMetadata.new(action: 'weight'),
resource_parent: resource_parent resource_parent: resource_parent
} }
......
...@@ -25,18 +25,10 @@ module EE ...@@ -25,18 +25,10 @@ module EE
changes = [] changes = []
base_data = { user_id: user.id, issue_id: resource.id } base_data = { user_id: user.id, issue_id: resource.id }
changes << base_data.merge({ weight: previous_weight(resource), created_at: resource.updated_at }) if first_weight_event?(resource) changes << base_data.merge({ weight: resource.previous_weight, created_at: resource.previous_updated_at }) if resource.first_weight_event?
changes << base_data.merge({ weight: resource.weight, created_at: event_created_at }) changes << base_data.merge({ weight: resource.weight, created_at: event_created_at })
end.flatten end.flatten
end end
def previous_weight(resource)
resource.previous_changes['weight']&.first
end
def first_weight_event?(resource)
previous_weight(resource) && ResourceWeightEvent.by_issue(resource).none?
end
end end
end end
end end
...@@ -19,7 +19,7 @@ module EE ...@@ -19,7 +19,7 @@ module EE
def weight_change_events def weight_change_events
return [] unless resource.respond_to?(:resource_weight_events) return [] unless resource.respond_to?(:resource_weight_events)
events = resource.resource_weight_events.includes(user: :status) # rubocop: disable CodeReuse/ActiveRecord events = resource.resource_weight_events.includes(user: :status).order(:id) # rubocop: disable CodeReuse/ActiveRecord
since_fetch_at(events) since_fetch_at(events)
end end
end end
......
...@@ -41,14 +41,14 @@ describe 'Resource weight events', :js do ...@@ -41,14 +41,14 @@ describe 'Resource weight events', :js do
visit project_issue_path(target_project, issue) visit project_issue_path(target_project, issue)
wait_for_all_requests wait_for_all_requests
expect(page).to have_content 'changed weight to 2' expect(page).to have_content('changed weight to 2', count: 1)
expect(page).to have_content 'changed weight to 3' expect(page).to have_content('changed weight to 3', count: 1)
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_all_requests wait_for_all_requests
expect(page).to have_content 'changed weight to 2' expect(page).to have_content('changed weight to 2', count: 1)
expect(page).to have_content 'changed weight to 3' expect(page).to have_content('changed weight to 3', count: 1)
expect(page).to have_content 'Closed' expect(page).to have_content 'Closed'
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'issue resource weight events', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project, author: user) }
context 'when user displays the issue' do
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue, note: 'some note') }
let!(:event1) { create(:resource_weight_event, issue: issue, weight: 1) }
let!(:event2) { create(:resource_weight_event, issue: issue, weight: 5) }
before do
visit project_issue_path(project, issue)
wait_for_all_requests
end
it 'shows both notes and resource weight event synthetic notes' do
page.within('#notes') do
expect(find("#note_#{note.id}")).to have_content 'some note'
expect(find("#note_#{event1.discussion_id}")).to have_content 'changed weight to 1', count: 1
expect(find("#note_#{event2.discussion_id}")).to have_content 'changed weight to 5', count: 1
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe EE::WeightEventable do
subject { build(:issue) }
describe 'associations' do
it { is_expected.to have_many(:resource_weight_events) }
end
describe '#first_weight_event?' do
it 'returns false as it has no weight changes' do
allow(subject).to receive(:previous_changes).and_return({ 'weight' => nil })
expect(subject.first_weight_event?).to be false
end
it 'returns false as it has no previous weight' do
allow(subject).to receive(:previous_changes).and_return({ 'weight' => [nil, 3] })
expect(subject.first_weight_event?).to be false
end
it 'returns false as it has already a resoure_weight_event' do
create(:resource_weight_event, issue: subject)
allow(subject).to receive(:previous_changes).and_return({ 'weight' => [nil, 3] })
expect(subject.first_weight_event?).to be false
end
it 'returns true as the previous weight exists and there is no resoure_weight_event record' do
allow(subject).to receive(:previous_changes).and_return({ 'weight' => [3, 4] })
expect(subject.first_weight_event?).to be true
end
end
end
...@@ -6,7 +6,7 @@ RSpec.describe EE::ResourceEvents::ChangeWeightService do ...@@ -6,7 +6,7 @@ RSpec.describe EE::ResourceEvents::ChangeWeightService do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, weight: 3) } let(:issue) { create(:issue, weight: 3) }
let(:created_at_time) { Time.new(2019, 1, 1, 12, 30, 48) } let(:created_at_time) { Time.utc(2019, 1, 1, 12, 30, 48, '123.123'.to_r) }
subject { described_class.new([issue], user, created_at_time).execute } subject { described_class.new([issue], user, created_at_time).execute }
...@@ -35,14 +35,16 @@ RSpec.describe EE::ResourceEvents::ChangeWeightService do ...@@ -35,14 +35,16 @@ RSpec.describe EE::ResourceEvents::ChangeWeightService do
context 'when there is no existing weight event record' do context 'when there is no existing weight event record' do
before do before do
ResourceWeightEvent.delete_all ResourceWeightEvent.delete_all
issue.update(weight: 5) issue.update(weight: 5, updated_at: 10.seconds.ago)
end end
it 'creates the expected event records' do it 'creates the expected event records' do
prev_update_at = issue.previous_changes['updated_at']&.first
expect { subject }.to change { ResourceWeightEvent.count }.by(2) expect { subject }.to change { ResourceWeightEvent.count }.by(2)
record = ResourceWeightEvent.first record = ResourceWeightEvent.first
expect_event_record(record, weight: 3, created_at: issue.reload.updated_at) expect_event_record(record, weight: 3, created_at: prev_update_at)
record = ResourceWeightEvent.last record = ResourceWeightEvent.last
expect_event_record(record, weight: 5, created_at: created_at_time) expect_event_record(record, weight: 5, created_at: created_at_time)
...@@ -53,7 +55,7 @@ RSpec.describe EE::ResourceEvents::ChangeWeightService do ...@@ -53,7 +55,7 @@ RSpec.describe EE::ResourceEvents::ChangeWeightService do
expect(record.issue).to eq(issue) expect(record.issue).to eq(issue)
expect(record.user).to eq(user) expect(record.user).to eq(user)
expect(record.weight).to eq(weight) expect(record.weight).to eq(weight)
expect(record.created_at).to eq(created_at) expect(record.created_at).to be_like_time(created_at)
end end
describe 'bulk issue weight updates' do describe 'bulk issue weight updates' do
......
...@@ -425,16 +425,16 @@ describe Issue do ...@@ -425,16 +425,16 @@ describe Issue do
let(:issue) { create(:issue, title: 'testing-issue') } let(:issue) { create(:issue, title: 'testing-issue') }
it 'starts with the issue iid' do it 'starts with the issue iid' do
expect(issue.to_branch_name).to match /\A#{issue.iid}-[A-Za-z\-]+\z/ expect(issue.to_branch_name).to match(/\A#{issue.iid}-[A-Za-z\-]+\z/)
end end
it "contains the issue title if not confidential" do it "contains the issue title if not confidential" do
expect(issue.to_branch_name).to match /testing-issue\z/ expect(issue.to_branch_name).to match(/testing-issue\z/)
end end
it "does not contain the issue title if confidential" do it "does not contain the issue title if confidential" do
issue = create(:issue, title: 'testing-issue', confidential: true) issue = create(:issue, title: 'testing-issue', confidential: true)
expect(issue.to_branch_name).to match /confidential-issue\z/ expect(issue.to_branch_name).to match(/confidential-issue\z/)
end end
context 'issue title longer than 100 characters' do context 'issue title longer than 100 characters' do
...@@ -932,4 +932,33 @@ describe Issue do ...@@ -932,4 +932,33 @@ describe Issue do
end end
it_behaves_like 'versioned description' it_behaves_like 'versioned description'
describe "#previous_updated_at" do
let_it_be(:updated_at) { Time.new(2012, 01, 06) }
let_it_be(:issue) { create(:issue, updated_at: updated_at) }
it 'returns updated_at value if updated_at did not change at all' do
allow(issue).to receive(:previous_changes).and_return({})
expect(issue.previous_updated_at).to eq(updated_at)
end
it 'returns updated_at value if `previous_changes` has nil value for `updated_at`' do
allow(issue).to receive(:previous_changes).and_return({ 'updated_at' => nil })
expect(issue.previous_updated_at).to eq(updated_at)
end
it 'returns updated_at value if previous updated_at value is not present' do
allow(issue).to receive(:previous_changes).and_return({ 'updated_at' => [nil, Time.new(2013, 02, 06)] })
expect(issue.previous_updated_at).to eq(updated_at)
end
it 'returns previous updated_at when present' do
allow(issue).to receive(:previous_changes).and_return({ 'updated_at' => [Time.new(2013, 02, 06), Time.new(2013, 03, 06)] })
expect(issue.previous_updated_at).to eq(Time.new(2013, 02, 06))
end
end
end end
...@@ -67,7 +67,7 @@ RSpec.describe ResourceWeightEvent, type: :model do ...@@ -67,7 +67,7 @@ RSpec.describe ResourceWeightEvent, type: :model do
it 'returns the expected id' do it 'returns the expected id' do
allow(Digest::SHA1).to receive(:hexdigest) allow(Digest::SHA1).to receive(:hexdigest)
.with("ResourceWeightEvent-2019-12-30 00:00:00 UTC-#{user1.id}") .with("ResourceWeightEvent-#{event.id}-#{user1.id}")
.and_return('73d167c478') .and_return('73d167c478')
expect(event.discussion_id).to eq('73d167c478') expect(event.discussion_id).to eq('73d167c478')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment