Commit ae5b9e31 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch '356215-save-incident-status-on-update' into 'master'

Persist issuable escalation statuses on issue update

See merge request gitlab-org/gitlab!83787
parents 854ec76c d3011a20
# frozen_string_literal: true
module IncidentManagement
module IssuableEscalationStatuses
class BuildService < ::BaseProjectService
def initialize(issue)
@issue = issue
@alert = issue.alert_management_alert
super(project: issue.project)
end
def execute
return issue.escalation_status if issue.escalation_status
issue.build_incident_management_issuable_escalation_status(alert_params)
end
private
attr_reader :issue, :alert
def alert_params
return {} unless alert
{
status_event: alert.status_event_for(alert.status_name)
}
end
end
end
end
IncidentManagement::IssuableEscalationStatuses::BuildService.prepend_mod
...@@ -2,14 +2,15 @@ ...@@ -2,14 +2,15 @@
module IncidentManagement module IncidentManagement
module IssuableEscalationStatuses module IssuableEscalationStatuses
class CreateService < BaseService class CreateService < ::BaseProjectService
def initialize(issue) def initialize(issue)
@issue = issue @issue = issue
@alert = issue.alert_management_alert
super(project: issue.project)
end end
def execute def execute
escalation_status = ::IncidentManagement::IssuableEscalationStatus.new(issue: issue, **alert_params) escalation_status = BuildService.new(issue).execute
if escalation_status.save if escalation_status.save
ServiceResponse.success(payload: { escalation_status: escalation_status }) ServiceResponse.success(payload: { escalation_status: escalation_status })
...@@ -20,17 +21,7 @@ module IncidentManagement ...@@ -20,17 +21,7 @@ module IncidentManagement
private private
attr_reader :issue, :alert attr_reader :issue
def alert_params
return {} unless alert
{
status_event: alert.status_event_for(alert.status_name)
}
end
end end
end end
end end
IncidentManagement::IssuableEscalationStatuses::CreateService.prepend_mod
...@@ -31,9 +31,7 @@ module IncidentManagement ...@@ -31,9 +31,7 @@ module IncidentManagement
attr_reader :issuable, :param_errors attr_reader :issuable, :param_errors
def available? def available?
issuable.supports_escalation? && issuable.supports_escalation? && user_has_permissions?
user_has_permissions? &&
escalation_status.present?
end end
def user_has_permissions? def user_has_permissions?
...@@ -42,7 +40,7 @@ module IncidentManagement ...@@ -42,7 +40,7 @@ module IncidentManagement
def escalation_status def escalation_status
strong_memoize(:escalation_status) do strong_memoize(:escalation_status) do
issuable.escalation_status issuable.escalation_status || BuildService.new(issuable).execute
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module EE module EE
module IncidentManagement module IncidentManagement
module IssuableEscalationStatuses module IssuableEscalationStatuses
module CreateService module BuildService
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
override :alert_params override :alert_params
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::IssuableEscalationStatuses::BuildService do
let_it_be(:project) { create(:project) }
let_it_be(:incident, reload: true) { create(:incident, project: project) }
let(:service) { described_class.new(incident) }
subject(:execute) { service.execute }
before do
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
end
it_behaves_like 'initializes new escalation status with expected attributes'
context 'with associated alert' do
let_it_be(:alert) { create(:alert_management_alert, :acknowledged, project: project, issue: incident) }
it_behaves_like 'initializes new escalation status with expected attributes', { status_event: :acknowledge }
context 'with escalation policy' do
let_it_be(:policy) { create(:incident_management_escalation_policy, project: project) }
it_behaves_like 'initializes new escalation status with expected attributes' do
let(:expected_attributes) do
{
status_event: :acknowledge,
policy_id: policy.id,
escalations_started_at: alert.reload.created_at
}
end
end
context 'with escalation policies feature unavailable' do
before do
stub_licensed_features(oncall_schedules: false, escalation_policies: false)
end
it_behaves_like 'initializes new escalation status with expected attributes', { status_event: :acknowledge }
end
end
end
end
...@@ -13,7 +13,6 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do ...@@ -13,7 +13,6 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do
before do before do
stub_licensed_features(oncall_schedules: true, escalation_policies: true) stub_licensed_features(oncall_schedules: true, escalation_policies: true)
stub_feature_flags(incident_escalations: true)
end end
shared_examples 'creates an escalation status for the incident with no policy set' do shared_examples 'creates an escalation status for the incident with no policy set' do
...@@ -52,9 +51,9 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do ...@@ -52,9 +51,9 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do
expect(status.status_name).to eq(alert.status_name) expect(status.status_name).to eq(alert.status_name)
end end
context 'when feature flag is disabled' do context 'when escalation policy features are disabled' do
before do before do
stub_feature_flags(incident_escalations: false) stub_licensed_features(oncall_schedules: false, escalation_policies: false)
end end
it_behaves_like 'creates an escalation status for the incident with no policy set' it_behaves_like 'creates an escalation status for the incident with no policy set'
......
...@@ -453,7 +453,6 @@ RSpec.describe Issues::UpdateService do ...@@ -453,7 +453,6 @@ RSpec.describe Issues::UpdateService do
let(:opts) { { escalation_status: { policy: policy } } } let(:opts) { { escalation_status: { policy: policy } } }
let(:escalation_update_class) { ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService } let(:escalation_update_class) { ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService }
let!(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue) }
let!(:policy) { create(:incident_management_escalation_policy, project: project) } let!(:policy) { create(:incident_management_escalation_policy, project: project) }
before do before do
...@@ -484,7 +483,7 @@ RSpec.describe Issues::UpdateService do ...@@ -484,7 +483,7 @@ RSpec.describe Issues::UpdateService do
shared_examples 'does not change the status record' do shared_examples 'does not change the status record' do
specify do specify do
expect { update_issue(opts) }.not_to change { issue.escalation_status.reload } expect { update_issue(opts) }.not_to change { issue.reload.escalation_status }
end end
it 'does not trigger side-effects' do it 'does not trigger side-effects' do
...@@ -495,52 +494,87 @@ RSpec.describe Issues::UpdateService do ...@@ -495,52 +494,87 @@ RSpec.describe Issues::UpdateService do
end end
end end
context 'when issue is an incident' do context 'with an existing escalation status record' do
let(:issue) { create(:incident, project: project) } let!(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue) }
context 'setting the escalation policy' do context 'when issue is an incident' do
include_examples 'updates the escalation status record' do let(:issue) { create(:incident, project: project) }
let(:expected_policy) { policy }
let(:expected_status) { :triggered }
end
context 'with the policy value defined but unchanged' do context 'setting the escalation policy' do
let!(:escalation_status) { create(:incident_management_issuable_escalation_status, :paging, issue: issue, policy: policy) } include_examples 'updates the escalation status record' do
let(:expected_policy) { policy }
let(:expected_status) { :triggered }
end
it_behaves_like 'does not change the status record' context 'with the policy value defined but unchanged' do
end let!(:escalation_status) { create(:incident_management_issuable_escalation_status, :paging, issue: issue, policy: policy) }
end
context 'unsetting the escalation policy' do it_behaves_like 'does not change the status record'
let(:policy) { nil } end
end
context 'when the policy is already set' do context 'unsetting the escalation policy' do
let!(:escalation_status) { create(:incident_management_issuable_escalation_status, :paging, issue: issue) } let(:policy) { nil }
let(:expected_policy) { nil }
include_examples 'updates the escalation status record' do context 'when the policy is already set' do
let!(:escalation_status) { create(:incident_management_issuable_escalation_status, :paging, issue: issue) }
let(:expected_policy) { nil } let(:expected_policy) { nil }
let(:expected_status) { :triggered }
end
context 'in addition to other attributes' do
let(:opts) { { escalation_status: { policy: policy, status: 'acknowledged' } } }
include_examples 'updates the escalation status record' do include_examples 'updates the escalation status record' do
let(:expected_policy) { nil } let(:expected_policy) { nil }
let(:expected_status) { :acknowledged } let(:expected_status) { :triggered }
end
context 'in addition to other attributes' do
let(:opts) { { escalation_status: { policy: policy, status: 'acknowledged' } } }
include_examples 'updates the escalation status record' do
let(:expected_policy) { nil }
let(:expected_status) { :acknowledged }
end
end end
end end
context 'with the policy value defined but unchanged' do
it_behaves_like 'does not change the status record'
end
end end
end
context 'when issue is not an incident' do
it_behaves_like 'does not change the status record'
end
end
context 'without an existing escalation status record' do
context 'when incident has an associated alert' do
let!(:alert) { create(:alert_management_alert, :acknowledged, :with_incident, project: project) }
let(:issue) { alert.issue }
context 'setting the status' do
let(:opts) { { escalation_status: { status: :resolved } } }
include_examples 'updates the escalation status record' do
let(:expected_policy) { policy }
let(:expected_status) { :resolved }
end
end
context 'unsetting the policy' do
let(:opts) { { escalation_status: { policy: nil } } }
context 'with the policy value defined but unchanged' do
it_behaves_like 'does not change the status record' it_behaves_like 'does not change the status record'
end end
end end
end
context 'when issue is not an incident' do context 'when incident has no associated alert' do
it_behaves_like 'does not change the status record' let(:issue) { create(:incident, project: project) }
include_examples 'updates the escalation status record' do
let(:expected_policy) { policy }
let(:expected_status) { :triggered }
end
end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::IssuableEscalationStatuses::BuildService do
let_it_be(:project) { create(:project) }
let_it_be(:incident, reload: true) { create(:incident, project: project) }
let(:service) { described_class.new(incident) }
subject(:execute) { service.execute }
it_behaves_like 'initializes new escalation status with expected attributes'
context 'with associated alert' do
let_it_be(:alert) { create(:alert_management_alert, :acknowledged, project: project, issue: incident) }
it_behaves_like 'initializes new escalation status with expected attributes', { status_event: :acknowledge }
end
end
...@@ -8,12 +8,12 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do ...@@ -8,12 +8,12 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do
let(:incident) { create(:incident, project: project) } let(:incident) { create(:incident, project: project) }
let(:service) { described_class.new(incident) } let(:service) { described_class.new(incident) }
subject(:execute) { service.execute} subject(:execute) { service.execute }
it 'creates an escalation status for the incident with no policy set' do it 'creates an escalation status for the incident with no policy set' do
expect { execute }.to change { incident.reload.incident_management_issuable_escalation_status }.from(nil) expect { execute }.to change { incident.reload.escalation_status }.from(nil)
status = incident.incident_management_issuable_escalation_status status = incident.escalation_status
expect(status.policy_id).to eq(nil) expect(status.policy_id).to eq(nil)
expect(status.escalations_started_at).to eq(nil) expect(status.escalations_started_at).to eq(nil)
...@@ -24,7 +24,22 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do ...@@ -24,7 +24,22 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do
let!(:existing_status) { create(:incident_management_issuable_escalation_status, issue: incident) } let!(:existing_status) { create(:incident_management_issuable_escalation_status, issue: incident) }
it 'exits without changing anything' do it 'exits without changing anything' do
expect { execute }.not_to change { incident.reload.incident_management_issuable_escalation_status } expect { execute }.not_to change { incident.reload.escalation_status }
end
end
context 'with associated alert' do
before do
create(:alert_management_alert, :acknowledged, project: project, issue: incident)
end
it 'creates an escalation status matching the alert attributes' do
expect { execute }.to change { incident.reload.escalation_status }.from(nil)
expect(incident.escalation_status).to have_attributes(
status_name: :acknowledged,
policy_id: nil,
escalations_started_at: nil
)
end end
end end
end end
...@@ -71,7 +71,12 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateServ ...@@ -71,7 +71,12 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateServ
context 'when an IssuableEscalationStatus record for the issue does not exist' do context 'when an IssuableEscalationStatus record for the issue does not exist' do
let(:issue) { create(:incident) } let(:issue) { create(:incident) }
it_behaves_like 'availability error response' it_behaves_like 'successful response', { status_event: :acknowledge }
it 'initializes an issuable escalation status record' do
expect { result }.not_to change(::IncidentManagement::IssuableEscalationStatus, :count)
expect(issue.escalation_status).to be_present
end
end end
context 'when called without params' do context 'when called without params' do
......
...@@ -1224,6 +1224,18 @@ RSpec.describe Issues::UpdateService, :mailer do ...@@ -1224,6 +1224,18 @@ RSpec.describe Issues::UpdateService, :mailer do
end end
context 'without an escalation status record' do context 'without an escalation status record' do
it 'creates a new record' do
expect { update_issue(opts) }.to change(::IncidentManagement::IssuableEscalationStatus, :count).by(1)
end
it_behaves_like 'updates the escalation status record', :acknowledged
end
context 'with :incident_escalations feature flag disabled' do
before do
stub_feature_flags(incident_escalations: false)
end
it_behaves_like 'does not change the status record' it_behaves_like 'does not change the status record'
end end
end end
......
# frozen_string_literal: true
RSpec.shared_examples 'initializes new escalation status with expected attributes' do |attributes = {}|
let(:expected_attributes) { attributes }
specify do
expect { execute }.to change { incident.escalation_status }
.from(nil)
.to(instance_of(::IncidentManagement::IssuableEscalationStatus))
expect(incident.escalation_status).to have_attributes(
id: nil,
issue_id: incident.id,
policy_id: nil,
escalations_started_at: nil,
status_event: nil,
**expected_attributes
)
end
end
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