From 5c6626a2d72843f5aa5ab21ba5a14ca4a47e39a1 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Fri, 4 Nov 2022 15:59:32 +0530 Subject: [PATCH 01/17] Add update logic for timeline event --- .../timeline_event/update.rb | 4 ++ .../timeline_events/update_service.rb | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/app/graphql/mutations/incident_management/timeline_event/update.rb b/app/graphql/mutations/incident_management/timeline_event/update.rb index 1f53bdc19cb2e7..b35feed308249d 100644 --- a/app/graphql/mutations/incident_management/timeline_event/update.rb +++ b/app/graphql/mutations/incident_management/timeline_event/update.rb @@ -18,6 +18,10 @@ class Update < Base required: false, description: 'Timestamp when the event occurred.' + argument :timeline_event_tag_names, [GraphQL::Types::String], + required: false, + description: copy_field_description(Types::IncidentManagement::TimelineEventType, :timeline_event_tags) + def resolve(id:, **args) timeline_event = authorized_find!(id: id) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 8d4e29c6857485..86c337caf53dd2 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -17,6 +17,7 @@ def initialize(timeline_event, user, params) @note = params[:note] @occurred_at = params[:occurred_at] @validation_context = VALIDATION_CONTEXT + @timeline_event_tags = params[:timeline_event_tag_names] end def execute @@ -61,6 +62,51 @@ def was_changed(timeline_event) :none end + def update_timeline_event_tags(timeline_event, tag_updates) + return unless tag_updates.any? + + tag_updates = tag_updates.map(&:downcase) + already_assigned_tags = timeline_event.timeline_event_tags.pluck_names.map(&:downcase) + tags_defined_on_project = timeline_event.project.incident_management_timeline_event_tags.pluck_names.map(&:downcase) + + tags_to_remove = already_assigned_tags - tag_updates + tags_to_add = tag_updates - already_assigned_tags + + validate_tags(tags_to_add, tags_defined_on_project) + + remove_tag_links(timeline_event, tags_to_remove) + create_tag_links(timeline_event, tags_to_add) + end + + def remove_tag_links(timeline_event, tags_to_remove_names) + tags_to_remove_ids = timeline_event.timeline_event_tags.by_names(tags_to_remove_names).ids + + timeline_event.timeline_event_tag_links.where(timeline_event_tag_id: tag_to_remove_ids).delete_all + end + + def create_tag_links(timeline_event, tags_to_add_names) + tags_to_add_ids = timeline_event.project.incident_management_timeline_event_tags.by_names(tags_to_add_names).ids + + tag_links = tags_to_add_ids.map do |tag_id| + { + timeline_even_id: timeline_event.id + timeline_event_tag_id: tag_id + created_at: DateTime.current + } + end + + IncidentManagement::TimelineEventTagLink.insert_all(tag_links) if tag_links.any? + end + + def validate_tags(tags_to_add, defined_tags) + non_existing_tags = tags_to_add - defined_tags + + return if non_existing_tags.empty? + + raise Gitlab::Graphql::Errors::ArgumentError, + "Following tags don't exist: #{non_existing_tags}" + end + def allowed? user&.can?(:edit_incident_management_timeline_event, timeline_event) end -- GitLab From f4b98a94f4cbd78c4fc19a4c6651e406b4712a66 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Fri, 4 Nov 2022 16:01:11 +0530 Subject: [PATCH 02/17] Add update logic for timeline event --- .../incident_management/timeline_events/update_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 86c337caf53dd2..e0f26875b1f062 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -89,8 +89,8 @@ def create_tag_links(timeline_event, tags_to_add_names) tag_links = tags_to_add_ids.map do |tag_id| { - timeline_even_id: timeline_event.id - timeline_event_tag_id: tag_id + timeline_even_id: timeline_event.id, + timeline_event_tag_id: tag_id, created_at: DateTime.current } end -- GitLab From 15bf25c6eccf820ea16cc112cfbec051b16aef81 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Fri, 4 Nov 2022 17:17:04 +0530 Subject: [PATCH 03/17] Update gql docs --- .../timeline_events/update_service.rb | 9 +++++---- doc/api/graphql/reference/index.md | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index e0f26875b1f062..5d9c695f14bc60 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -24,6 +24,7 @@ def execute return error_no_permissions unless allowed? timeline_event.assign_attributes(update_params) + update_timeline_event_tags(timeline_event, timeline_event_tags) if timeline_event_tags if timeline_event.save(context: validation_context) add_system_note(timeline_event) @@ -37,7 +38,7 @@ def execute private - attr_reader :timeline_event, :incident, :user, :note, :occurred_at, :validation_context + attr_reader :timeline_event, :incident, :user, :note, :occurred_at, :validation_context, :timeline_event_tags def update_params { updated_by_user: user, note: note, occurred_at: occurred_at }.compact @@ -74,14 +75,14 @@ def update_timeline_event_tags(timeline_event, tag_updates) validate_tags(tags_to_add, tags_defined_on_project) - remove_tag_links(timeline_event, tags_to_remove) - create_tag_links(timeline_event, tags_to_add) + remove_tag_links(timeline_event, tags_to_remove) if tags_to_remove.any? + create_tag_links(timeline_event, tags_to_add) if tags_to_add.any? end def remove_tag_links(timeline_event, tags_to_remove_names) tags_to_remove_ids = timeline_event.timeline_event_tags.by_names(tags_to_remove_names).ids - timeline_event.timeline_event_tag_links.where(timeline_event_tag_id: tag_to_remove_ids).delete_all + timeline_event.timeline_event_tag_links.where(timeline_event_tag_id: tags_to_remove_ids).delete_all end def create_tag_links(timeline_event, tags_to_add_names) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 0d04223d96e439..d1583cbdc8ac3a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5067,6 +5067,7 @@ Input type: `TimelineEventUpdateInput` | <a id="mutationtimelineeventupdateid"></a>`id` | [`IncidentManagementTimelineEventID!`](#incidentmanagementtimelineeventid) | ID of the timeline event to update. | | <a id="mutationtimelineeventupdatenote"></a>`note` | [`String`](#string) | Text note of the timeline event. | | <a id="mutationtimelineeventupdateoccurredat"></a>`occurredAt` | [`Time`](#time) | Timestamp when the event occurred. | +| <a id="mutationtimelineeventupdatetimelineeventtagnames"></a>`timelineEventTagNames` | [`[String!]`](#string) | Tags for the incident timeline event. | #### Fields -- GitLab From 5580c59d16da4e64ffeb2e67dbb0e497ce004998 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Fri, 4 Nov 2022 21:03:03 +0530 Subject: [PATCH 04/17] Always execute tag updates unless nil --- .../incident_management/timeline_events/update_service.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 5d9c695f14bc60..972add03618313 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -24,7 +24,7 @@ def execute return error_no_permissions unless allowed? timeline_event.assign_attributes(update_params) - update_timeline_event_tags(timeline_event, timeline_event_tags) if timeline_event_tags + update_timeline_event_tags(timeline_event, timeline_event_tags) unless timeline_event_tags.nil? if timeline_event.save(context: validation_context) add_system_note(timeline_event) @@ -64,8 +64,6 @@ def was_changed(timeline_event) end def update_timeline_event_tags(timeline_event, tag_updates) - return unless tag_updates.any? - tag_updates = tag_updates.map(&:downcase) already_assigned_tags = timeline_event.timeline_event_tags.pluck_names.map(&:downcase) tags_defined_on_project = timeline_event.project.incident_management_timeline_event_tags.pluck_names.map(&:downcase) @@ -90,7 +88,7 @@ def create_tag_links(timeline_event, tags_to_add_names) tag_links = tags_to_add_ids.map do |tag_id| { - timeline_even_id: timeline_event.id, + timeline_event_id: timeline_event.id, timeline_event_tag_id: tag_id, created_at: DateTime.current } -- GitLab From 9145208ca26685c5fded05506fadc61f22549ae6 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Wed, 16 Nov 2022 19:34:37 +0530 Subject: [PATCH 05/17] Rebase to master and refactor validate tags --- .../incident_management/timeline_events/update_service.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 972add03618313..b855dbc9087191 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -66,12 +66,11 @@ def was_changed(timeline_event) def update_timeline_event_tags(timeline_event, tag_updates) tag_updates = tag_updates.map(&:downcase) already_assigned_tags = timeline_event.timeline_event_tags.pluck_names.map(&:downcase) - tags_defined_on_project = timeline_event.project.incident_management_timeline_event_tags.pluck_names.map(&:downcase) tags_to_remove = already_assigned_tags - tag_updates tags_to_add = tag_updates - already_assigned_tags - validate_tags(tags_to_add, tags_defined_on_project) + validate_tags(tags_to_add) remove_tag_links(timeline_event, tags_to_remove) if tags_to_remove.any? create_tag_links(timeline_event, tags_to_add) if tags_to_add.any? @@ -97,7 +96,9 @@ def create_tag_links(timeline_event, tags_to_add_names) IncidentManagement::TimelineEventTagLink.insert_all(tag_links) if tag_links.any? end - def validate_tags(tags_to_add, defined_tags) + def validate_tags(tags_to_add) + defined_tags = timeline_event.project.incident_management_timeline_event_tags.by_names(tags_to_add) + non_existing_tags = tags_to_add - defined_tags return if non_existing_tags.empty? -- GitLab From 6d527e61d5f6fdffbc2567f00a82445d982852b9 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Wed, 16 Nov 2022 19:44:01 +0530 Subject: [PATCH 06/17] Fix cop offense for spec --- app/models/incident_management/timeline_event_tag.rb | 4 ++++ .../timeline_events/update_service.rb | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/models/incident_management/timeline_event_tag.rb b/app/models/incident_management/timeline_event_tag.rb index d1e3fbc2a6a8b8..59a3ed2d9f9b0d 100644 --- a/app/models/incident_management/timeline_event_tag.rb +++ b/app/models/incident_management/timeline_event_tag.rb @@ -25,5 +25,9 @@ class TimelineEventTag < ApplicationRecord def self.pluck_names pluck(:name) end + + def self.tag_ids + pluck(:id) + end end end diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index b855dbc9087191..e480316dea960e 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -77,13 +77,18 @@ def update_timeline_event_tags(timeline_event, tag_updates) end def remove_tag_links(timeline_event, tags_to_remove_names) - tags_to_remove_ids = timeline_event.timeline_event_tags.by_names(tags_to_remove_names).ids + tags_to_remove_ids = timeline_event.timeline_event_tags.by_names(tags_to_remove_names).tag_ids - timeline_event.timeline_event_tag_links.where(timeline_event_tag_id: tags_to_remove_ids).delete_all + timeline_event + .timeline_event_tag_links + .find_by_timeline_event_tag_id(timeline_event_tag_id: tags_to_remove_ids).delete_all end def create_tag_links(timeline_event, tags_to_add_names) - tags_to_add_ids = timeline_event.project.incident_management_timeline_event_tags.by_names(tags_to_add_names).ids + tags_to_add_ids = timeline_event + .project + .incident_management_timeline_event_tags + .by_names(tags_to_add_names).tag_ids tag_links = tags_to_add_ids.map do |tag_id| { -- GitLab From 35c16f5f1f479e751ad53e7e4849a73bf81dfcda Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Thu, 17 Nov 2022 14:46:20 +0530 Subject: [PATCH 07/17] Update service for update event Update link modal with a scope Add specs for the graphql, api and service --- .../timeline_event_tag_link.rb | 2 + .../timeline_events/update_service.rb | 36 ++++++--- .../timeline_event/update_spec.rb | 44 ++++++++++- .../timeline_event_tag_link_spec.rb | 29 +++++++ .../timeline_event/update_spec.rb | 41 +++++++++- .../timeline_events/update_service_spec.rb | 77 +++++++++++++++++++ 6 files changed, 215 insertions(+), 14 deletions(-) diff --git a/app/models/incident_management/timeline_event_tag_link.rb b/app/models/incident_management/timeline_event_tag_link.rb index 912339717a848f..23a2bd65a7f6ca 100644 --- a/app/models/incident_management/timeline_event_tag_link.rb +++ b/app/models/incident_management/timeline_event_tag_link.rb @@ -7,5 +7,7 @@ class TimelineEventTagLink < ApplicationRecord belongs_to :timeline_event_tag, class_name: 'IncidentManagement::TimelineEventTag' belongs_to :timeline_event, class_name: 'IncidentManagement::TimelineEvent' + + scope :by_tag_ids, -> (ids) { where(timeline_event_tag_id: ids) } end end diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index e480316dea960e..0023655c4b814e 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -23,8 +23,18 @@ def initialize(timeline_event, user, params) def execute return error_no_permissions unless allowed? + # We first update the tags, if any, as they return error for non-existing tags. + # And then we update the other attributes of timeline event. + unless timeline_event_tags.nil? + tags_to_remove, tags_to_add = compute_tag_updates(timeline_event, timeline_event_tags) + non_existing_tags = validate_tags(tags_to_add) + + return error("#{_("Following tags don't exist")}: #{non_existing_tags}") unless non_existing_tags.empty? + + update_timeline_event_tags(timeline_event, tags_to_add, tags_to_remove) + end + timeline_event.assign_attributes(update_params) - update_timeline_event_tags(timeline_event, timeline_event_tags) unless timeline_event_tags.nil? if timeline_event.save(context: validation_context) add_system_note(timeline_event) @@ -63,15 +73,17 @@ def was_changed(timeline_event) :none end - def update_timeline_event_tags(timeline_event, tag_updates) + def compute_tag_updates(timeline_event, tag_updates) tag_updates = tag_updates.map(&:downcase) already_assigned_tags = timeline_event.timeline_event_tags.pluck_names.map(&:downcase) tags_to_remove = already_assigned_tags - tag_updates tags_to_add = tag_updates - already_assigned_tags - validate_tags(tags_to_add) + [tags_to_remove, tags_to_add] + end + def update_timeline_event_tags(timeline_event, tags_to_add, tags_to_remove) remove_tag_links(timeline_event, tags_to_remove) if tags_to_remove.any? create_tag_links(timeline_event, tags_to_add) if tags_to_add.any? end @@ -81,7 +93,7 @@ def remove_tag_links(timeline_event, tags_to_remove_names) timeline_event .timeline_event_tag_links - .find_by_timeline_event_tag_id(timeline_event_tag_id: tags_to_remove_ids).delete_all + .by_tag_ids(tags_to_remove_ids).delete_all end def create_tag_links(timeline_event, tags_to_add_names) @@ -102,14 +114,14 @@ def create_tag_links(timeline_event, tags_to_add_names) end def validate_tags(tags_to_add) - defined_tags = timeline_event.project.incident_management_timeline_event_tags.by_names(tags_to_add) - - non_existing_tags = tags_to_add - defined_tags - - return if non_existing_tags.empty? - - raise Gitlab::Graphql::Errors::ArgumentError, - "Following tags don't exist: #{non_existing_tags}" + defined_tags = timeline_event + .project + .incident_management_timeline_event_tags + .by_names(tags_to_add) + .pluck_names + .map(&:downcase) + + tags_to_add - defined_tags end def allowed? diff --git a/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb index 7081fb7117e26b..317e8f5fcb6d0a 100644 --- a/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb +++ b/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb @@ -7,21 +7,33 @@ let_it_be(:reporter) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } let_it_be_with_reload(:timeline_event) do create(:incident_management_timeline_event, project: project, incident: incident) end + # Pre-attach a tag to the event + let_it_be(:tag_link1) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag1 + ) + end + let(:args) do { id: timeline_event_id, note: note, - occurred_at: occurred_at + occurred_at: occurred_at, + timeline_event_tag_names: tag_names } end let(:note) { 'Updated Note' } let(:timeline_event_id) { GitlabSchema.id_from_object(timeline_event).to_s } let(:occurred_at) { 1.minute.ago } + let(:tag_names) { [] } before do project.add_developer(developer) @@ -92,6 +104,36 @@ expect(resolve).to eq(timeline_event: nil, errors: ["Occurred at can't be blank"]) end end + + context 'when timeline event tag do not exist' do + let(:tag_names) { ['some other tag'] } + + it 'does not update the timeline event' do + expect { resolve }.not_to change { timeline_event.reload.updated_at } + end + + it 'responds with error' do + expect(resolve).to eq(timeline_event: nil, errors: ["Following tags don't exist: [\"some other tag\"]"]) + end + end + end + + context 'when timeline event tags are passed' do + let(:tag_names) { [tag2.name] } + + it 'returns updated timeline event' do + expect(resolve).to eq( + timeline_event: timeline_event.reload, + errors: [] + ) + end + + it 'removes tag1 and assigns tag2 to the event' do + response = resolve + timeline_event = response[:timeline_event] + + expect(timeline_event.timeline_event_tags).to contain_exactly(tag2) + end end end diff --git a/spec/models/incident_management/timeline_event_tag_link_spec.rb b/spec/models/incident_management/timeline_event_tag_link_spec.rb index fe31a6604c14f7..65225337f037f4 100644 --- a/spec/models/incident_management/timeline_event_tag_link_spec.rb +++ b/spec/models/incident_management/timeline_event_tag_link_spec.rb @@ -7,4 +7,33 @@ it { is_expected.to belong_to(:timeline_event) } it { is_expected.to belong_to(:timeline_event_tag) } end + + describe '#by_tag_ids scope' do + let_it_be(:project) { create(:project) } + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, name: 'Test tag 1', project: project) } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, name: 'Test tag 2', project: project) } + let_it_be(:incident) { create(:incident, project: project) } + + let_it_be(:timeline_event) do + create(:incident_management_timeline_event, project: project, incident: incident) + end + + let_it_be(:tag_link1) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag1 + ) + end + + let_it_be(:tag_link2) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag2 + ) + end + + it 'returns the tag requested' do + expect(described_class.by_tag_ids(tag1.id)).to contain_exactly(tag_link1) + end + end end diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb index 542d51b990f674..207dd55d527011 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb @@ -8,18 +8,30 @@ let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } let_it_be_with_reload(:timeline_event) do create(:incident_management_timeline_event, incident: incident, project: project) end + # Pre-attach a tag to the event + let_it_be(:tag_link1) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag1 + ) + end + let(:occurred_at) { 1.minute.ago.iso8601 } let(:note) { 'Updated note' } + let(:tag_names) { [] } let(:variables) do { id: timeline_event.to_global_id.to_s, note: note, - occurred_at: occurred_at + occurred_at: occurred_at, + timeline_event_tag_names: tag_names } end @@ -33,6 +45,7 @@ author { id username } updatedByUser { id username } incident { id title } + timelineEventTags { nodes { name } } note noteHtml occurredAt @@ -71,6 +84,9 @@ 'id' => incident.to_global_id.to_s, 'title' => incident.title }, + 'timelineEventTags' => { + 'nodes' => [] + }, 'note' => note, 'noteHtml' => timeline_event.note_html, 'occurredAt' => occurred_at, @@ -85,4 +101,27 @@ it_behaves_like 'timeline event mutation responds with validation error', error_message: 'Timeline text is too long (maximum is 280 characters)' end + + context 'when timeline event tag names are passed' do + context 'when tags exist' do + let(:tag_names) { [tag2.name] } + + it 'removes tag1 and adds tag2' do + post_graphql_mutation(mutation, current_user: user) + + timeline_event_response = mutation_response['timelineEvent'] + tag_names = timeline_event_response['timelineEventTags']['nodes'] + + expect(response).to have_gitlab_http_status(:success) + expect(tag_names).to contain_exactly({ "name" => tag2.name }) + end + end + + context 'when tags do not exist' do + let(:tag_names) { ['some other tag'] } + + it_behaves_like 'timeline event mutation responds with validation error', + error_message: "Following tags don't exist: [\"some other tag\"]" + end + end end diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb index 2373a73e108673..f87a632731f2f7 100644 --- a/spec/services/incident_management/timeline_events/update_service_spec.rb +++ b/spec/services/incident_management/timeline_events/update_service_spec.rb @@ -142,6 +142,83 @@ it_behaves_like 'error response', 'You have insufficient permissions to manage timeline events for this incident' end + + context 'when timeline event tags are passed' do + context 'when they exist' do + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } + let_it_be(:tag3) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 3') } + + let!(:tag_link1) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag3 + ) + end + + let(:params) do + { + note: 'Updated note', + occurred_at: occurred_at, + timeline_event_tag_names: [tag3.name, tag1.name] + } + end + + it_behaves_like 'successful response' + + it 'adds the new tag' do + expect { execute }.to change(timeline_event.timeline_event_tags, :count).by(1) + end + + it 'adds the new tag link' do + expect { execute }.to change(IncidentManagement::TimelineEventTagLink, :count).by(1) + end + + it 'returns the new tag in response' do + timeline_event = execute.payload[:timeline_event] + + expect(timeline_event.timeline_event_tags.pluck_names).to contain_exactly(tag1.name, tag3.name) + end + + context 'when tag is removed' do + let(:params) { { note: 'Updated note', occurred_at: occurred_at, timeline_event_tag_names: [tag2.name] } } + + it_behaves_like 'successful response' + + it 'adds the new tag and removes the old tag' do + # Since it adds a tag (+1) and removes old tag (-1) so next change in count in 0 + expect { execute }.to change(timeline_event.timeline_event_tags, :count).by(0) + end + + it 'adds the new tag link and removes the old tag link' do + # Since it adds a tag link (+1) and removes old tag link (-1) so next change in count in 0 + expect { execute }.to change(IncidentManagement::TimelineEventTagLink, :count).by(0) + end + + it 'returns the new tag and does not contain the old tag in response' do + timeline_event = execute.payload[:timeline_event] + + expect(timeline_event.timeline_event_tags.pluck_names).to contain_exactly(tag2.name) + end + end + end + + context 'when they do not exist' do + let(:params) do + { + note: 'Updated note 2', + occurred_at: occurred_at, + timeline_event_tag_names: ['non existing tag'] + } + end + + it_behaves_like 'error response', "Following tags don't exist: [\"non existing tag\"]" + + it 'does not update the note' do + expect { execute }.not_to change { timeline_event.reload.note } + end + end + end end context 'when user does not have permissions' do -- GitLab From a7958c1b088e4bc6acb0820dbac3563ab3d8794d Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Thu, 17 Nov 2022 14:56:41 +0530 Subject: [PATCH 08/17] Remove passing event around in the service --- .../timeline_events/update_service.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 0023655c4b814e..213c1c76c7453f 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -26,12 +26,12 @@ def execute # We first update the tags, if any, as they return error for non-existing tags. # And then we update the other attributes of timeline event. unless timeline_event_tags.nil? - tags_to_remove, tags_to_add = compute_tag_updates(timeline_event, timeline_event_tags) + tags_to_remove, tags_to_add = compute_tag_updates non_existing_tags = validate_tags(tags_to_add) return error("#{_("Following tags don't exist")}: #{non_existing_tags}") unless non_existing_tags.empty? - update_timeline_event_tags(timeline_event, tags_to_add, tags_to_remove) + update_timeline_event_tags(tags_to_add, tags_to_remove) end timeline_event.assign_attributes(update_params) @@ -73,8 +73,8 @@ def was_changed(timeline_event) :none end - def compute_tag_updates(timeline_event, tag_updates) - tag_updates = tag_updates.map(&:downcase) + def compute_tag_updates + tag_updates = timeline_event_tags.map(&:downcase) already_assigned_tags = timeline_event.timeline_event_tags.pluck_names.map(&:downcase) tags_to_remove = already_assigned_tags - tag_updates @@ -83,12 +83,12 @@ def compute_tag_updates(timeline_event, tag_updates) [tags_to_remove, tags_to_add] end - def update_timeline_event_tags(timeline_event, tags_to_add, tags_to_remove) - remove_tag_links(timeline_event, tags_to_remove) if tags_to_remove.any? - create_tag_links(timeline_event, tags_to_add) if tags_to_add.any? + def update_timeline_event_tags(tags_to_add, tags_to_remove) + remove_tag_links(tags_to_remove) if tags_to_remove.any? + create_tag_links(tags_to_add) if tags_to_add.any? end - def remove_tag_links(timeline_event, tags_to_remove_names) + def remove_tag_links(tags_to_remove_names) tags_to_remove_ids = timeline_event.timeline_event_tags.by_names(tags_to_remove_names).tag_ids timeline_event @@ -96,7 +96,7 @@ def remove_tag_links(timeline_event, tags_to_remove_names) .by_tag_ids(tags_to_remove_ids).delete_all end - def create_tag_links(timeline_event, tags_to_add_names) + def create_tag_links(tags_to_add_names) tags_to_add_ids = timeline_event .project .incident_management_timeline_event_tags -- GitLab From 1c0a2b3166cfff34ba57863892253ebefe2f8ced Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Fri, 18 Nov 2022 12:16:21 +0530 Subject: [PATCH 09/17] Add transaction to the service Add random case to the tag inputs in spec --- .../timeline_events/update_service.rb | 16 +++++--- .../timeline_events/update_service_spec.rb | 40 +++++++++++++------ 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 213c1c76c7453f..bf264e3e45dbaa 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -23,18 +23,14 @@ def initialize(timeline_event, user, params) def execute return error_no_permissions unless allowed? - # We first update the tags, if any, as they return error for non-existing tags. - # And then we update the other attributes of timeline event. unless timeline_event_tags.nil? tags_to_remove, tags_to_add = compute_tag_updates non_existing_tags = validate_tags(tags_to_add) return error("#{_("Following tags don't exist")}: #{non_existing_tags}") unless non_existing_tags.empty? - - update_timeline_event_tags(tags_to_add, tags_to_remove) end - timeline_event.assign_attributes(update_params) + update_timeline_event_and_event_tags(tags_to_add, tags_to_remove) if timeline_event.save(context: validation_context) add_system_note(timeline_event) @@ -50,6 +46,16 @@ def execute attr_reader :timeline_event, :incident, :user, :note, :occurred_at, :validation_context, :timeline_event_tags + def update_timeline_event_and_event_tags(tags_to_add, tags_to_remove) + IncidentManagement::TimelineEvent.transaction do + IncidentManagement::TimelineEventTag.transaction do + update_timeline_event_tags(tags_to_add, tags_to_remove) unless timeline_event_tags.nil? + + timeline_event.assign_attributes(update_params) + end + end + end + def update_params { updated_by_user: user, note: note, occurred_at: occurred_at }.compact end diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb index f87a632731f2f7..8dc644006a77d9 100644 --- a/spec/services/incident_management/timeline_events/update_service_spec.rb +++ b/spec/services/incident_management/timeline_events/update_service_spec.rb @@ -13,6 +13,24 @@ let(:current_user) { user } describe '#execute' do + shared_examples 'successful tag response' do + it_behaves_like 'successful response' + + it 'adds the new tag' do + expect { execute }.to change(timeline_event.timeline_event_tags, :count).by(1) + end + + it 'adds the new tag link' do + expect { execute }.to change(IncidentManagement::TimelineEventTagLink, :count).by(1) + end + + it 'returns the new tag in response' do + timeline_event = execute.payload[:timeline_event] + + expect(timeline_event.timeline_event_tags.pluck_names).to contain_exactly(tag1.name, tag3.name) + end + end + shared_examples 'successful response' do it 'responds with success', :aggregate_failures do expect(execute).to be_success @@ -164,20 +182,18 @@ } end - it_behaves_like 'successful response' - - it 'adds the new tag' do - expect { execute }.to change(timeline_event.timeline_event_tags, :count).by(1) - end + it_behaves_like 'successful tag response' - it 'adds the new tag link' do - expect { execute }.to change(IncidentManagement::TimelineEventTagLink, :count).by(1) - end - - it 'returns the new tag in response' do - timeline_event = execute.payload[:timeline_event] + context 'when tag name is of random case' do + let(:params) do + { + note: 'Updated note', + occurred_at: occurred_at, + timeline_event_tag_names: ['tAg 3', 'TaG 1'] + } + end - expect(timeline_event.timeline_event_tags.pluck_names).to contain_exactly(tag1.name, tag3.name) + it_behaves_like 'successful tag response' end context 'when tag is removed' do -- GitLab From 54caba302b7127a649107ddd4ea2edebd6d5d11d Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Fri, 18 Nov 2022 13:57:27 +0530 Subject: [PATCH 10/17] Add more specs, make update as a transaction --- .../timeline_events/update_service.rb | 39 ++++++++----------- .../timeline_events/update_service_spec.rb | 8 ++-- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index bf264e3e45dbaa..e789a1317858f6 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -25,12 +25,17 @@ def execute unless timeline_event_tags.nil? tags_to_remove, tags_to_add = compute_tag_updates - non_existing_tags = validate_tags(tags_to_add) + defined_tags = timeline_event + .project + .incident_management_timeline_event_tags + .by_names(tags_to_add) + + non_existing_tags = validate_tags(tags_to_add, defined_tags) return error("#{_("Following tags don't exist")}: #{non_existing_tags}") unless non_existing_tags.empty? end - update_timeline_event_and_event_tags(tags_to_add, tags_to_remove) + update_timeline_event_and_event_tags(tags_to_add, tags_to_remove, defined_tags) if timeline_event.save(context: validation_context) add_system_note(timeline_event) @@ -46,13 +51,11 @@ def execute attr_reader :timeline_event, :incident, :user, :note, :occurred_at, :validation_context, :timeline_event_tags - def update_timeline_event_and_event_tags(tags_to_add, tags_to_remove) - IncidentManagement::TimelineEvent.transaction do - IncidentManagement::TimelineEventTag.transaction do - update_timeline_event_tags(tags_to_add, tags_to_remove) unless timeline_event_tags.nil? + def update_timeline_event_and_event_tags(tags_to_add, tags_to_remove, defined_tags) + ApplicationRecord.transaction do + update_timeline_event_tags(tags_to_add, tags_to_remove, defined_tags) unless timeline_event_tags.nil? - timeline_event.assign_attributes(update_params) - end + timeline_event.assign_attributes(update_params) end end @@ -89,9 +92,9 @@ def compute_tag_updates [tags_to_remove, tags_to_add] end - def update_timeline_event_tags(tags_to_add, tags_to_remove) + def update_timeline_event_tags(tags_to_add, tags_to_remove, defined_tags) remove_tag_links(tags_to_remove) if tags_to_remove.any? - create_tag_links(tags_to_add) if tags_to_add.any? + create_tag_links(tags_to_add, defined_tags) if tags_to_add.any? end def remove_tag_links(tags_to_remove_names) @@ -102,11 +105,8 @@ def remove_tag_links(tags_to_remove_names) .by_tag_ids(tags_to_remove_ids).delete_all end - def create_tag_links(tags_to_add_names) - tags_to_add_ids = timeline_event - .project - .incident_management_timeline_event_tags - .by_names(tags_to_add_names).tag_ids + def create_tag_links(tags_to_add_names, defined_tags) + tags_to_add_ids = defined_tags.tag_ids tag_links = tags_to_add_ids.map do |tag_id| { @@ -119,13 +119,8 @@ def create_tag_links(tags_to_add_names) IncidentManagement::TimelineEventTagLink.insert_all(tag_links) if tag_links.any? end - def validate_tags(tags_to_add) - defined_tags = timeline_event - .project - .incident_management_timeline_event_tags - .by_names(tags_to_add) - .pluck_names - .map(&:downcase) + def validate_tags(tags_to_add, defined_tags) + defined_tags = defined_tags.pluck_names.map(&:downcase) tags_to_add - defined_tags end diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb index 8dc644006a77d9..ff4105f5ded20a 100644 --- a/spec/services/incident_management/timeline_events/update_service_spec.rb +++ b/spec/services/incident_management/timeline_events/update_service_spec.rb @@ -17,11 +17,11 @@ it_behaves_like 'successful response' it 'adds the new tag' do - expect { execute }.to change(timeline_event.timeline_event_tags, :count).by(1) + expect { execute }.to change { timeline_event.timeline_event_tags.count }.by(1) end it 'adds the new tag link' do - expect { execute }.to change(IncidentManagement::TimelineEventTagLink, :count).by(1) + expect { execute }.to change { IncidentManagement::TimelineEventTagLink.count }.by(1) end it 'returns the new tag in response' do @@ -203,12 +203,12 @@ it 'adds the new tag and removes the old tag' do # Since it adds a tag (+1) and removes old tag (-1) so next change in count in 0 - expect { execute }.to change(timeline_event.timeline_event_tags, :count).by(0) + expect { execute }.to change { timeline_event.timeline_event_tags.count }.by(0) end it 'adds the new tag link and removes the old tag link' do # Since it adds a tag link (+1) and removes old tag link (-1) so next change in count in 0 - expect { execute }.to change(IncidentManagement::TimelineEventTagLink, :count).by(0) + expect { execute }.to change { IncidentManagement::TimelineEventTagLink.count }.by(0) end it 'returns the new tag and does not contain the old tag in response' do -- GitLab From fb2fe66182cacf081f096377af836966ba69a31c Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Fri, 18 Nov 2022 14:31:24 +0530 Subject: [PATCH 11/17] Add more specs to check the transaction --- .../timeline_events/update_service.rb | 10 ++++- .../timeline_events/update_service_spec.rb | 45 ++++++++++++------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index e789a1317858f6..583697937d4c6e 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -35,9 +35,13 @@ def execute return error("#{_("Following tags don't exist")}: #{non_existing_tags}") unless non_existing_tags.empty? end - update_timeline_event_and_event_tags(tags_to_add, tags_to_remove, defined_tags) + begin + timeline_event_saved = update_timeline_event_and_event_tags(tags_to_add, tags_to_remove, defined_tags) + rescue ActiveRecord::RecordInvalid + error_in_save(timeline_event) + end - if timeline_event.save(context: validation_context) + if timeline_event_saved add_system_note(timeline_event) track_usage_event(:incident_management_timeline_event_edited, user.id) @@ -56,6 +60,8 @@ def update_timeline_event_and_event_tags(tags_to_add, tags_to_remove, defined_ta update_timeline_event_tags(tags_to_add, tags_to_remove, defined_tags) unless timeline_event_tags.nil? timeline_event.assign_attributes(update_params) + + timeline_event.save!(context: validation_context) end end diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb index ff4105f5ded20a..96d8f2f6143206 100644 --- a/spec/services/incident_management/timeline_events/update_service_spec.rb +++ b/spec/services/incident_management/timeline_events/update_service_spec.rb @@ -6,6 +6,16 @@ let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } + let_it_be(:tag3) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 3') } + + let!(:tag_link1) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag3 + ) + end let!(:timeline_event) { create(:incident_management_timeline_event, project: project, incident: incident) } let(:occurred_at) { 1.minute.ago } @@ -85,7 +95,7 @@ it_behaves_like 'passing the correct was_changed value', :occurred_at_and_note context 'when note is nil' do - let(:params) { { occurred_at: occurred_at } } + let(:params) { { occurred_at: occurred_at, timeline_event_tag_names: [tag3.name, tag2.name] } } it_behaves_like 'successful response' it_behaves_like 'passing the correct was_changed value', :occurred_at @@ -97,18 +107,30 @@ it 'updates occurred_at' do expect { execute }.to change { timeline_event.occurred_at }.to(params[:occurred_at]) end + + it 'updates the tags' do + expect { execute }.to change { timeline_event.timeline_event_tags.count }.by(1) + end end context 'when note is blank' do - let(:params) { { note: '', occurred_at: occurred_at } } + let(:params) { { note: '', occurred_at: occurred_at, timeline_event_tag_names: [tag3.name, tag2.name] } } it_behaves_like 'error response', "Timeline text can't be blank" + + it 'does not add the tags as it rollsback the transaction' do + expect { execute }.not_to change { timeline_event.timeline_event_tags.count } + end end context 'when note is more than 280 characters long' do - let(:params) { { note: 'n' * 281, occurred_at: occurred_at } } + let(:params) { { note: 'n' * 281, occurred_at: occurred_at, timeline_event_tag_names: [tag3.name, tag2.name] } } it_behaves_like 'error response', 'Timeline text is too long (maximum is 280 characters)' + + it 'does not add the tags as it rollsback the transaction' do + expect { execute }.not_to change { timeline_event.timeline_event_tags.count } + end end context 'when occurred_at is nil' do @@ -127,9 +149,13 @@ end context 'when occurred_at is blank' do - let(:params) { { note: 'Updated note', occurred_at: '' } } + let(:params) { { note: 'Updated note', occurred_at: '', timeline_event_tag_names: [tag3.name, tag2.name] } } it_behaves_like 'error response', "Occurred at can't be blank" + + it 'does not add the tags as it rollsback the transaction' do + expect { execute }.not_to change { timeline_event.timeline_event_tags.count } + end end context 'when both occurred_at and note is nil' do @@ -163,17 +189,6 @@ context 'when timeline event tags are passed' do context 'when they exist' do - let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } - let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } - let_it_be(:tag3) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 3') } - - let!(:tag_link1) do - create(:incident_management_timeline_event_tag_link, - timeline_event: timeline_event, - timeline_event_tag: tag3 - ) - end - let(:params) do { note: 'Updated note', -- GitLab From 8eb01bf74daeee758a4f2545d77f77b0f3c3cc50 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Wed, 23 Nov 2022 18:09:17 +0530 Subject: [PATCH 12/17] Add db reviewer and backend suggestions --- .../timeline_events/update_service.rb | 53 +++++-------------- .../timeline_event_tag_spec.rb | 18 +++++-- 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 583697937d4c6e..3996d76c4e63c1 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -24,19 +24,18 @@ def execute return error_no_permissions unless allowed? unless timeline_event_tags.nil? - tags_to_remove, tags_to_add = compute_tag_updates + tags_to_add = compute_tags_to_add defined_tags = timeline_event .project .incident_management_timeline_event_tags - .by_names(tags_to_add) non_existing_tags = validate_tags(tags_to_add, defined_tags) - return error("#{_("Following tags don't exist")}: #{non_existing_tags}") unless non_existing_tags.empty? + return error("#{_("Following tags don't exist")}: #{non_existing_tags}") if non_existing_tags.any? end begin - timeline_event_saved = update_timeline_event_and_event_tags(tags_to_add, tags_to_remove, defined_tags) + timeline_event_saved = update_timeline_event_and_event_tags(defined_tags) rescue ActiveRecord::RecordInvalid error_in_save(timeline_event) end @@ -55,9 +54,12 @@ def execute attr_reader :timeline_event, :incident, :user, :note, :occurred_at, :validation_context, :timeline_event_tags - def update_timeline_event_and_event_tags(tags_to_add, tags_to_remove, defined_tags) + def update_timeline_event_and_event_tags(defined_tags) ApplicationRecord.transaction do - update_timeline_event_tags(tags_to_add, tags_to_remove, defined_tags) unless timeline_event_tags.nil? + unless timeline_event_tags.nil? + tags = defined_tags.by_names(timeline_event_tags.map(&:downcase)) + timeline_event.timeline_event_tags = tags + end timeline_event.assign_attributes(update_params) @@ -88,45 +90,18 @@ def was_changed(timeline_event) :none end - def compute_tag_updates + def compute_tags_to_add tag_updates = timeline_event_tags.map(&:downcase) already_assigned_tags = timeline_event.timeline_event_tags.pluck_names.map(&:downcase) - tags_to_remove = already_assigned_tags - tag_updates - tags_to_add = tag_updates - already_assigned_tags - - [tags_to_remove, tags_to_add] - end - - def update_timeline_event_tags(tags_to_add, tags_to_remove, defined_tags) - remove_tag_links(tags_to_remove) if tags_to_remove.any? - create_tag_links(tags_to_add, defined_tags) if tags_to_add.any? - end - - def remove_tag_links(tags_to_remove_names) - tags_to_remove_ids = timeline_event.timeline_event_tags.by_names(tags_to_remove_names).tag_ids - - timeline_event - .timeline_event_tag_links - .by_tag_ids(tags_to_remove_ids).delete_all - end - - def create_tag_links(tags_to_add_names, defined_tags) - tags_to_add_ids = defined_tags.tag_ids - - tag_links = tags_to_add_ids.map do |tag_id| - { - timeline_event_id: timeline_event.id, - timeline_event_tag_id: tag_id, - created_at: DateTime.current - } - end - - IncidentManagement::TimelineEventTagLink.insert_all(tag_links) if tag_links.any? + tag_updates - already_assigned_tags end def validate_tags(tags_to_add, defined_tags) - defined_tags = defined_tags.pluck_names.map(&:downcase) + defined_tags = defined_tags + .by_names(tags_to_add) + .pluck_names + .map(&:downcase) tags_to_add - defined_tags end diff --git a/spec/models/incident_management/timeline_event_tag_spec.rb b/spec/models/incident_management/timeline_event_tag_spec.rb index 1ec4fa30fb5f4a..0bc198eeec55c7 100644 --- a/spec/models/incident_management/timeline_event_tag_spec.rb +++ b/spec/models/incident_management/timeline_event_tag_spec.rb @@ -26,12 +26,20 @@ it { is_expected.not_to allow_value('s' * 256).for(:name) } end - describe '.pluck_names' do - it 'returns the names of the tags' do - tag1 = create(:incident_management_timeline_event_tag) - tag2 = create(:incident_management_timeline_event_tag) + describe 'model methods' do + let_it_be(:tag1) { create(:incident_management_timeline_event_tag) } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag) } - expect(described_class.pluck_names).to contain_exactly(tag1.name, tag2.name) + context 'when .pluck_names is called' do + it 'returns the names of the tags' do + expect(described_class.pluck_names).to contain_exactly(tag1.name, tag2.name) + end + end + + context 'when .tag_ids is called' do + it 'returns the ids of the tags' do + expect(described_class.tag_ids).to contain_exactly(tag1.id, tag2.id) + end end end -- GitLab From 4215083f6d7786db2cc9a94e6e6e32e9151af6eb Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Wed, 23 Nov 2022 18:18:37 +0530 Subject: [PATCH 13/17] Add one spec for removing all tags --- .../timeline_events/update_service_spec.rb | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb index 96d8f2f6143206..8ccc6ded6d05e8 100644 --- a/spec/services/incident_management/timeline_events/update_service_spec.rb +++ b/spec/services/incident_management/timeline_events/update_service_spec.rb @@ -232,6 +232,26 @@ expect(timeline_event.timeline_event_tags.pluck_names).to contain_exactly(tag2.name) end end + + context 'when all assigned tags are removed' do + let(:params) { { note: 'Updated note', occurred_at: occurred_at, timeline_event_tag_names: [] } } + + it_behaves_like 'successful response' + + it 'removes all the assigned tags' do + expect { execute }.to change { timeline_event.timeline_event_tags.count }.by(-1) + end + + it 'removes all the assigned tag links' do + expect { execute }.to change { IncidentManagement::TimelineEventTagLink.count }.by(-1) + end + + it 'does not contain any tags in response' do + timeline_event = execute.payload[:timeline_event] + + expect(timeline_event.timeline_event_tags.pluck_names).to be_empty + end + end end context 'when they do not exist' do -- GitLab From 57cd330008b86b5867f1194694c82828d034fe12 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Wed, 23 Nov 2022 18:26:57 +0530 Subject: [PATCH 14/17] Remove redandunt method in model --- .../incident_management/timeline_event_tag.rb | 4 ---- .../timeline_event_tag_spec.rb | 18 +++++------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/app/models/incident_management/timeline_event_tag.rb b/app/models/incident_management/timeline_event_tag.rb index 59a3ed2d9f9b0d..d1e3fbc2a6a8b8 100644 --- a/app/models/incident_management/timeline_event_tag.rb +++ b/app/models/incident_management/timeline_event_tag.rb @@ -25,9 +25,5 @@ class TimelineEventTag < ApplicationRecord def self.pluck_names pluck(:name) end - - def self.tag_ids - pluck(:id) - end end end diff --git a/spec/models/incident_management/timeline_event_tag_spec.rb b/spec/models/incident_management/timeline_event_tag_spec.rb index 0bc198eeec55c7..1ec4fa30fb5f4a 100644 --- a/spec/models/incident_management/timeline_event_tag_spec.rb +++ b/spec/models/incident_management/timeline_event_tag_spec.rb @@ -26,20 +26,12 @@ it { is_expected.not_to allow_value('s' * 256).for(:name) } end - describe 'model methods' do - let_it_be(:tag1) { create(:incident_management_timeline_event_tag) } - let_it_be(:tag2) { create(:incident_management_timeline_event_tag) } + describe '.pluck_names' do + it 'returns the names of the tags' do + tag1 = create(:incident_management_timeline_event_tag) + tag2 = create(:incident_management_timeline_event_tag) - context 'when .pluck_names is called' do - it 'returns the names of the tags' do - expect(described_class.pluck_names).to contain_exactly(tag1.name, tag2.name) - end - end - - context 'when .tag_ids is called' do - it 'returns the ids of the tags' do - expect(described_class.tag_ids).to contain_exactly(tag1.id, tag2.id) - end + expect(described_class.pluck_names).to contain_exactly(tag1.name, tag2.name) end end -- GitLab From ce26c6a044dc1921f2944d2bf32da3c8c9e59943 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Wed, 23 Nov 2022 18:31:33 +0530 Subject: [PATCH 15/17] Remove tag ids scope for tag links model --- app/models/incident_management/timeline_event_tag_link.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/incident_management/timeline_event_tag_link.rb b/app/models/incident_management/timeline_event_tag_link.rb index 23a2bd65a7f6ca..912339717a848f 100644 --- a/app/models/incident_management/timeline_event_tag_link.rb +++ b/app/models/incident_management/timeline_event_tag_link.rb @@ -7,7 +7,5 @@ class TimelineEventTagLink < ApplicationRecord belongs_to :timeline_event_tag, class_name: 'IncidentManagement::TimelineEventTag' belongs_to :timeline_event, class_name: 'IncidentManagement::TimelineEvent' - - scope :by_tag_ids, -> (ids) { where(timeline_event_tag_id: ids) } end end -- GitLab From fa73922299922dc1b3eee2e41ada3e9b453c4aca Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Wed, 23 Nov 2022 19:06:07 +0530 Subject: [PATCH 16/17] Remove tag link specs for tag ids scope --- .../timeline_event_tag_link_spec.rb | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/spec/models/incident_management/timeline_event_tag_link_spec.rb b/spec/models/incident_management/timeline_event_tag_link_spec.rb index 65225337f037f4..fe31a6604c14f7 100644 --- a/spec/models/incident_management/timeline_event_tag_link_spec.rb +++ b/spec/models/incident_management/timeline_event_tag_link_spec.rb @@ -7,33 +7,4 @@ it { is_expected.to belong_to(:timeline_event) } it { is_expected.to belong_to(:timeline_event_tag) } end - - describe '#by_tag_ids scope' do - let_it_be(:project) { create(:project) } - let_it_be(:tag1) { create(:incident_management_timeline_event_tag, name: 'Test tag 1', project: project) } - let_it_be(:tag2) { create(:incident_management_timeline_event_tag, name: 'Test tag 2', project: project) } - let_it_be(:incident) { create(:incident, project: project) } - - let_it_be(:timeline_event) do - create(:incident_management_timeline_event, project: project, incident: incident) - end - - let_it_be(:tag_link1) do - create(:incident_management_timeline_event_tag_link, - timeline_event: timeline_event, - timeline_event_tag: tag1 - ) - end - - let_it_be(:tag_link2) do - create(:incident_management_timeline_event_tag_link, - timeline_event: timeline_event, - timeline_event_tag: tag2 - ) - end - - it 'returns the tag requested' do - expect(described_class.by_tag_ids(tag1.id)).to contain_exactly(tag_link1) - end - end end -- GitLab From 49f22442fbb2a4d5e79bfc10daa6e73f44da86d6 Mon Sep 17 00:00:00 2001 From: Rajendra Kadam <rkadam@gitlab.com> Date: Thu, 24 Nov 2022 10:01:28 +0530 Subject: [PATCH 17/17] Apply maintainer suggestions - 2 --- .../timeline_events/update_service.rb | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/app/services/incident_management/timeline_events/update_service.rb b/app/services/incident_management/timeline_events/update_service.rb index 3996d76c4e63c1..0e424f86e1a5ee 100644 --- a/app/services/incident_management/timeline_events/update_service.rb +++ b/app/services/incident_management/timeline_events/update_service.rb @@ -24,18 +24,18 @@ def execute return error_no_permissions unless allowed? unless timeline_event_tags.nil? - tags_to_add = compute_tags_to_add - defined_tags = timeline_event - .project - .incident_management_timeline_event_tags + new_tags = timeline_event + .project + .incident_management_timeline_event_tags + .by_names(timeline_event_tags) - non_existing_tags = validate_tags(tags_to_add, defined_tags) + non_existing_tags = validate_tags(new_tags) return error("#{_("Following tags don't exist")}: #{non_existing_tags}") if non_existing_tags.any? end begin - timeline_event_saved = update_timeline_event_and_event_tags(defined_tags) + timeline_event_saved = update_timeline_event_and_event_tags(new_tags) rescue ActiveRecord::RecordInvalid error_in_save(timeline_event) end @@ -54,12 +54,9 @@ def execute attr_reader :timeline_event, :incident, :user, :note, :occurred_at, :validation_context, :timeline_event_tags - def update_timeline_event_and_event_tags(defined_tags) + def update_timeline_event_and_event_tags(new_tags) ApplicationRecord.transaction do - unless timeline_event_tags.nil? - tags = defined_tags.by_names(timeline_event_tags.map(&:downcase)) - timeline_event.timeline_event_tags = tags - end + timeline_event.timeline_event_tags = new_tags unless timeline_event_tags.nil? timeline_event.assign_attributes(update_params) @@ -90,20 +87,8 @@ def was_changed(timeline_event) :none end - def compute_tags_to_add - tag_updates = timeline_event_tags.map(&:downcase) - already_assigned_tags = timeline_event.timeline_event_tags.pluck_names.map(&:downcase) - - tag_updates - already_assigned_tags - end - - def validate_tags(tags_to_add, defined_tags) - defined_tags = defined_tags - .by_names(tags_to_add) - .pluck_names - .map(&:downcase) - - tags_to_add - defined_tags + def validate_tags(new_tags) + timeline_event_tags.map(&:downcase) - new_tags.map(&:name).map(&:downcase) end def allowed? -- GitLab