Commit d92f4232 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '330277-incident-escalations-table' into 'master'

Add escalations table for issues

See merge request gitlab-org/gitlab!65209
parents 1f1e125f 21b6cc8e
...@@ -8,6 +8,7 @@ Gitlab::Database::Partitioning::PartitionManager.register(WebHookLog) ...@@ -8,6 +8,7 @@ Gitlab::Database::Partitioning::PartitionManager.register(WebHookLog)
if Gitlab.ee? if Gitlab.ee?
Gitlab::Database::Partitioning::PartitionManager.register(IncidentManagement::PendingEscalations::Alert) Gitlab::Database::Partitioning::PartitionManager.register(IncidentManagement::PendingEscalations::Alert)
Gitlab::Database::Partitioning::PartitionManager.register(IncidentManagement::PendingEscalations::Issue)
end end
begin begin
......
# frozen_string_literal: true
class CreateIncidentManagementPendingIssueEscalations < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
execute(<<~SQL)
CREATE TABLE incident_management_pending_issue_escalations (
id bigserial NOT NULL,
rule_id bigint NOT NULL,
issue_id bigint NOT NULL,
process_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
PRIMARY KEY (id, process_at)
) PARTITION BY RANGE (process_at);
CREATE INDEX index_incident_management_pending_issue_escalations_on_issue_id
ON incident_management_pending_issue_escalations USING btree (issue_id);
CREATE INDEX index_incident_management_pending_issue_escalations_on_rule_id
ON incident_management_pending_issue_escalations USING btree (rule_id);
SQL
end
end
def down
with_lock_retries do
drop_table :incident_management_pending_issue_escalations
end
end
end
# frozen_string_literal: true
class AddForeignKeysForPendingIssueEscalations < ActiveRecord::Migration[6.1]
include Gitlab::Database::PartitioningMigrationHelpers
disable_ddl_transaction!
def up
add_concurrent_partitioned_foreign_key :incident_management_pending_issue_escalations,
:incident_management_escalation_rules,
column: :rule_id
add_concurrent_partitioned_foreign_key :incident_management_pending_issue_escalations,
:issues,
column: :issue_id
end
def down
remove_foreign_key_if_exists :incident_management_pending_issue_escalations, :incident_management_escalation_rules, column: :rule_id
remove_foreign_key_if_exists :incident_management_pending_issue_escalations, :issues, column: :issue_id
end
end
2d0399beca58815197487d310318ed1cb3d8e85671d55581a6256ceac7667b43
\ No newline at end of file
892a71a3f6fdeb20cb2837a426d6d0931c756f8bf3d647e520a72a0bb6f78309
\ No newline at end of file
...@@ -222,6 +222,16 @@ CREATE TABLE incident_management_pending_alert_escalations ( ...@@ -222,6 +222,16 @@ CREATE TABLE incident_management_pending_alert_escalations (
) )
PARTITION BY RANGE (process_at); PARTITION BY RANGE (process_at);
CREATE TABLE incident_management_pending_issue_escalations (
id bigint NOT NULL,
rule_id bigint NOT NULL,
issue_id bigint NOT NULL,
process_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
)
PARTITION BY RANGE (process_at);
CREATE TABLE web_hook_logs ( CREATE TABLE web_hook_logs (
id bigint NOT NULL, id bigint NOT NULL,
web_hook_id integer NOT NULL, web_hook_id integer NOT NULL,
...@@ -14147,6 +14157,15 @@ CREATE SEQUENCE incident_management_pending_alert_escalations_id_seq ...@@ -14147,6 +14157,15 @@ CREATE SEQUENCE incident_management_pending_alert_escalations_id_seq
ALTER SEQUENCE incident_management_pending_alert_escalations_id_seq OWNED BY incident_management_pending_alert_escalations.id; ALTER SEQUENCE incident_management_pending_alert_escalations_id_seq OWNED BY incident_management_pending_alert_escalations.id;
CREATE SEQUENCE incident_management_pending_issue_escalations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE incident_management_pending_issue_escalations_id_seq OWNED BY incident_management_pending_issue_escalations.id;
CREATE TABLE index_statuses ( CREATE TABLE index_statuses (
id integer NOT NULL, id integer NOT NULL,
project_id integer NOT NULL, project_id integer NOT NULL,
...@@ -20531,6 +20550,8 @@ ALTER TABLE ONLY incident_management_oncall_shifts ALTER COLUMN id SET DEFAULT n ...@@ -20531,6 +20550,8 @@ ALTER TABLE ONLY incident_management_oncall_shifts ALTER COLUMN id SET DEFAULT n
ALTER TABLE ONLY incident_management_pending_alert_escalations ALTER COLUMN id SET DEFAULT nextval('incident_management_pending_alert_escalations_id_seq'::regclass); ALTER TABLE ONLY incident_management_pending_alert_escalations ALTER COLUMN id SET DEFAULT nextval('incident_management_pending_alert_escalations_id_seq'::regclass);
ALTER TABLE ONLY incident_management_pending_issue_escalations ALTER COLUMN id SET DEFAULT nextval('incident_management_pending_issue_escalations_id_seq'::regclass);
ALTER TABLE ONLY index_statuses ALTER COLUMN id SET DEFAULT nextval('index_statuses_id_seq'::regclass); ALTER TABLE ONLY index_statuses ALTER COLUMN id SET DEFAULT nextval('index_statuses_id_seq'::regclass);
ALTER TABLE ONLY insights ALTER COLUMN id SET DEFAULT nextval('insights_id_seq'::regclass); ALTER TABLE ONLY insights ALTER COLUMN id SET DEFAULT nextval('insights_id_seq'::regclass);
...@@ -21967,6 +21988,9 @@ ALTER TABLE ONLY incident_management_oncall_shifts ...@@ -21967,6 +21988,9 @@ ALTER TABLE ONLY incident_management_oncall_shifts
ALTER TABLE ONLY incident_management_pending_alert_escalations ALTER TABLE ONLY incident_management_pending_alert_escalations
ADD CONSTRAINT incident_management_pending_alert_escalations_pkey PRIMARY KEY (id, process_at); ADD CONSTRAINT incident_management_pending_alert_escalations_pkey PRIMARY KEY (id, process_at);
ALTER TABLE ONLY incident_management_pending_issue_escalations
ADD CONSTRAINT incident_management_pending_issue_escalations_pkey PRIMARY KEY (id, process_at);
ALTER TABLE ONLY index_statuses ALTER TABLE ONLY index_statuses
ADD CONSTRAINT index_statuses_pkey PRIMARY KEY (id); ADD CONSTRAINT index_statuses_pkey PRIMARY KEY (id);
...@@ -24266,6 +24290,10 @@ CREATE INDEX index_incident_management_pending_alert_escalations_on_rule_id ON O ...@@ -24266,6 +24290,10 @@ CREATE INDEX index_incident_management_pending_alert_escalations_on_rule_id ON O
CREATE INDEX index_incident_management_pending_alert_escalations_on_schedule ON ONLY incident_management_pending_alert_escalations USING btree (schedule_id); CREATE INDEX index_incident_management_pending_alert_escalations_on_schedule ON ONLY incident_management_pending_alert_escalations USING btree (schedule_id);
CREATE INDEX index_incident_management_pending_issue_escalations_on_issue_id ON ONLY incident_management_pending_issue_escalations USING btree (issue_id);
CREATE INDEX index_incident_management_pending_issue_escalations_on_rule_id ON ONLY incident_management_pending_issue_escalations USING btree (rule_id);
CREATE UNIQUE INDEX index_index_statuses_on_project_id ON index_statuses USING btree (project_id); CREATE UNIQUE INDEX index_index_statuses_on_project_id ON index_statuses USING btree (project_id);
CREATE INDEX index_insights_on_namespace_id ON insights USING btree (namespace_id); CREATE INDEX index_insights_on_namespace_id ON insights USING btree (namespace_id);
...@@ -27077,6 +27105,9 @@ ALTER TABLE ONLY incident_management_oncall_participants ...@@ -27077,6 +27105,9 @@ ALTER TABLE ONLY incident_management_oncall_participants
ALTER TABLE ONLY events ALTER TABLE ONLY events
ADD CONSTRAINT fk_rails_0434b48643 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_0434b48643 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE incident_management_pending_issue_escalations
ADD CONSTRAINT fk_rails_0470889ee5 FOREIGN KEY (rule_id) REFERENCES incident_management_escalation_rules(id) ON DELETE CASCADE;
ALTER TABLE ONLY ip_restrictions ALTER TABLE ONLY ip_restrictions
ADD CONSTRAINT fk_rails_04a93778d5 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_04a93778d5 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
...@@ -27686,6 +27717,9 @@ ALTER TABLE ONLY status_page_published_incidents ...@@ -27686,6 +27717,9 @@ ALTER TABLE ONLY status_page_published_incidents
ALTER TABLE ONLY deployment_clusters ALTER TABLE ONLY deployment_clusters
ADD CONSTRAINT fk_rails_6359a164df FOREIGN KEY (deployment_id) REFERENCES deployments(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_6359a164df FOREIGN KEY (deployment_id) REFERENCES deployments(id) ON DELETE CASCADE;
ALTER TABLE incident_management_pending_issue_escalations
ADD CONSTRAINT fk_rails_636678b3bd FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY evidences ALTER TABLE ONLY evidences
ADD CONSTRAINT fk_rails_6388b435a6 FOREIGN KEY (release_id) REFERENCES releases(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_6388b435a6 FOREIGN KEY (release_id) REFERENCES releases(id) ON DELETE CASCADE;
# frozen_string_literal: true
module IncidentManagement
# Functionality needed for models which represent escalations.
#
# Implemeting classes should alias `target` to the attribute
# of the relevant escalatable.
#
# EX) `alias_attribute :target, :alert`
module BasePendingEscalation
extend ActiveSupport::Concern
include PartitionedTable
include EachBatch
ESCALATION_BUFFER = 1.month.freeze
included do
# Required to find records by id on partitioned tables.
self.primary_key = :id
partitioned_by :process_at, strategy: :monthly
belongs_to :rule, class_name: 'EscalationRule', foreign_key: 'rule_id'
validates :process_at, :rule_id, presence: true
scope :processable, -> { where(process_at: ESCALATION_BUFFER.ago..Time.current) }
delegate :project, to: :target
end
end
end
...@@ -70,6 +70,8 @@ module EE ...@@ -70,6 +70,8 @@ module EE
has_many :feature_flag_issues has_many :feature_flag_issues
has_many :feature_flags, through: :feature_flag_issues, class_name: '::Operations::FeatureFlag' has_many :feature_flags, through: :feature_flag_issues, class_name: '::Operations::FeatureFlag'
has_many :pending_escalations, class_name: 'IncidentManagement::PendingEscalations::Issue', foreign_key: :issue_id, inverse_of: :issue
validates :weight, allow_nil: true, numericality: { greater_than_or_equal_to: 0 } validates :weight, allow_nil: true, numericality: { greater_than_or_equal_to: 0 }
validate :validate_confidential_epic validate :validate_confidential_epic
......
...@@ -3,30 +3,18 @@ ...@@ -3,30 +3,18 @@
module IncidentManagement module IncidentManagement
module PendingEscalations module PendingEscalations
class Alert < ApplicationRecord class Alert < ApplicationRecord
include PartitionedTable include ::IncidentManagement::BasePendingEscalation
include EachBatch
include IgnorableColumns include IgnorableColumns
ignore_columns :schedule_id, :status, remove_with: '14.4', remove_after: '2021-09-22' ignore_columns :schedule_id, :status, remove_with: '14.4', remove_after: '2021-09-22'
alias_attribute :target, :alert
self.primary_key = :id
self.table_name = 'incident_management_pending_alert_escalations' self.table_name = 'incident_management_pending_alert_escalations'
ESCALATION_BUFFER = 1.month.freeze alias_attribute :target, :alert
partitioned_by :process_at, strategy: :monthly
belongs_to :alert, class_name: 'AlertManagement::Alert', foreign_key: 'alert_id', inverse_of: :pending_escalations belongs_to :alert, class_name: 'AlertManagement::Alert', foreign_key: 'alert_id', inverse_of: :pending_escalations
belongs_to :rule, class_name: 'EscalationRule', foreign_key: 'rule_id'
scope :processable, -> { where(process_at: ESCALATION_BUFFER.ago..Time.current) }
validates :process_at, presence: true
validates :rule_id, presence: true, uniqueness: { scope: [:alert_id] }
delegate :project, to: :alert validates :rule_id, uniqueness: { scope: [:alert_id] }
end end
end end
end end
# frozen_string_literal: true
module IncidentManagement
module PendingEscalations
class Issue < ApplicationRecord
include ::IncidentManagement::BasePendingEscalation
self.table_name = 'incident_management_pending_issue_escalations'
alias_attribute :target, :issue
belongs_to :issue, class_name: '::Issue', foreign_key: 'issue_id', inverse_of: :pending_escalations
validates :rule_id, uniqueness: { scope: [:issue_id] }
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :incident_management_pending_issue_escalation, class: 'IncidentManagement::PendingEscalations::Issue' do
transient do
project { association :project }
policy { association :incident_management_escalation_policy, project: project }
end
rule { association :incident_management_escalation_rule, policy: policy }
issue { association :issue, project: rule.policy.project }
process_at { 5.minutes.from_now }
end
end
...@@ -3,34 +3,5 @@ ...@@ -3,34 +3,5 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe IncidentManagement::PendingEscalations::Alert do RSpec.describe IncidentManagement::PendingEscalations::Alert do
subject { create(:incident_management_pending_alert_escalation) } include_examples 'IncidentManagement::PendingEscalation model'
it { is_expected.to be_valid }
describe 'validations' do
it { is_expected.to validate_presence_of(:process_at) }
it { is_expected.to delegate_method(:project).to(:alert) }
it { is_expected.to validate_uniqueness_of(:rule_id).scoped_to([:alert_id]) }
end
describe 'associations' do
it { is_expected.to belong_to(:alert) }
it { is_expected.to belong_to(:rule) }
end
describe 'scopes' do
describe '.processable' do
subject { described_class.processable }
let_it_be(:policy) { create(:incident_management_escalation_policy) }
let_it_be(:rule) { policy.rules.first }
let_it_be(:two_months_ago_escalation) { create(:incident_management_pending_alert_escalation, rule: rule, process_at: 2.months.ago) }
let_it_be(:three_weeks_ago_escalation) { create(:incident_management_pending_alert_escalation, rule: rule, process_at: 3.weeks.ago) }
let_it_be(:three_days_ago_escalation) { create(:incident_management_pending_alert_escalation, rule: rule, process_at: 3.days.ago) }
let_it_be(:future_escalation) { create(:incident_management_pending_alert_escalation, rule: rule, process_at: 5.minutes.from_now) }
it { is_expected.to eq [three_weeks_ago_escalation, three_days_ago_escalation] }
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::PendingEscalations::Issue do
include_examples 'IncidentManagement::PendingEscalation model'
end
...@@ -14,6 +14,7 @@ RSpec.describe Issue do ...@@ -14,6 +14,7 @@ RSpec.describe Issue do
it { is_expected.to have_many(:resource_iteration_events) } it { is_expected.to have_many(:resource_iteration_events) }
it { is_expected.to have_one(:issuable_sla) } it { is_expected.to have_one(:issuable_sla) }
it { is_expected.to have_many(:metric_images) } it { is_expected.to have_many(:metric_images) }
it { is_expected.to have_many(:pending_escalations) }
it { is_expected.to have_one(:requirement) } it { is_expected.to have_one(:requirement) }
it { is_expected.to have_many(:test_reports) } it { is_expected.to have_many(:test_reports) }
......
# frozen_string_literal: true
RSpec.shared_examples 'IncidentManagement::PendingEscalation model' do
let_it_be(:escalatable_type) { described_class.name.demodulize.downcase.to_sym }
let_it_be(:pending_escalation) { create_escalation }
subject { pending_escalation }
it { is_expected.to be_valid }
describe 'validations' do
it { is_expected.to validate_presence_of(:process_at) }
it { is_expected.to validate_presence_of(:rule_id) }
it { is_expected.to delegate_method(:project).to(escalatable_type) }
it { is_expected.to validate_uniqueness_of(:rule_id).scoped_to([:"#{escalatable_type}_id"]) }
end
describe 'associations' do
it { is_expected.to belong_to(escalatable_type) }
it { is_expected.to belong_to(:rule) }
end
describe 'scopes' do
describe '.processable' do
subject { described_class.processable }
let_it_be(:policy) { create(:incident_management_escalation_policy) }
let_it_be(:rule) { policy.rules.first }
let_it_be(:two_months_ago_escalation) { create_escalation(rule: rule, process_at: 2.months.ago) }
let_it_be(:three_weeks_ago_escalation) { create_escalation(rule: rule, process_at: 3.weeks.ago) }
let_it_be(:three_days_ago_escalation) { create_escalation(rule: rule, process_at: 3.days.ago) }
let_it_be(:future_escalation) { create_escalation(rule: rule, process_at: 5.minutes.from_now) }
it { is_expected.to eq [three_weeks_ago_escalation, three_days_ago_escalation] }
end
end
private
def create_escalation(type: escalatable_type, **options)
create(
:"incident_management_pending_#{type}_escalation",
**options
)
end
end
...@@ -58,6 +58,7 @@ issues: ...@@ -58,6 +58,7 @@ issues:
- test_reports - test_reports
- requirement - requirement
- incident_management_issuable_escalation_status - incident_management_issuable_escalation_status
- pending_escalations
work_item_type: work_item_type:
- issues - issues
events: events:
......
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