Commit d3011a20 authored by Sarah Yasonik's avatar Sarah Yasonik Committed by Douglas Barbosa Alexandre

Persist issuable escalation statuses on issue update

Behind the :incident_escalations feature flag.

If a user updates the incident status or escalation policy
for an incident which does not yet have an IssuableEscalationStatus
record, we should create the record with the provided value.

If issue is associated with an alert, we want to pull in attributes
from the alert to populate the escalation policy of the incident.
parent 1139628a
# 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,6 +494,9 @@ RSpec.describe Issues::UpdateService do ...@@ -495,6 +494,9 @@ RSpec.describe Issues::UpdateService do
end end
end end
context 'with an existing escalation status record' do
let!(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue) }
context 'when issue is an incident' do context 'when issue is an incident' do
let(:issue) { create(:incident, project: project) } let(:issue) { create(:incident, project: project) }
...@@ -544,6 +546,38 @@ RSpec.describe Issues::UpdateService do ...@@ -544,6 +546,38 @@ RSpec.describe Issues::UpdateService do
end end
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 } } }
it_behaves_like 'does not change the status record'
end
end
context 'when incident has no associated alert' do
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
it_behaves_like 'existing issuable with scoped labels' do it_behaves_like 'existing issuable with scoped labels' do
let(:issuable) { issue } let(:issuable) { issue }
let(:parent) { project } let(:parent) { project }
......
# 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