diff --git a/src/dispatch/database/revisions/tenant/versions/2023-12-08_2f06fd73eae6.py b/src/dispatch/database/revisions/tenant/versions/2023-12-08_2f06fd73eae6.py
new file mode 100644
index 000000000000..6b80160e443e
--- /dev/null
+++ b/src/dispatch/database/revisions/tenant/versions/2023-12-08_2f06fd73eae6.py
@@ -0,0 +1,27 @@
+"""Adds the engage_next_oncall column to the incident_role table.
+
+Revision ID: 2f06fd73eae6
+Revises: 580a18ec4c39
+Create Date: 2023-12-08 11:22:15.565073
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+# revision identifiers, used by Alembic.
+revision = "2f06fd73eae6"
+down_revision = "580a18ec4c39"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column("incident_role", sa.Column("engage_next_oncall", sa.Boolean(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("incident_role", "engage_next_oncall")
+ # ### end Alembic commands ###
diff --git a/src/dispatch/incident/service.py b/src/dispatch/incident/service.py
index c7838fd30bb5..436d90490d1e 100644
--- a/src/dispatch/incident/service.py
+++ b/src/dispatch/incident/service.py
@@ -276,6 +276,25 @@ def create(*, db_session, incident_in: IncidentCreate) -> Incident:
role=ParticipantRoleType.scribe,
)
+ # add observer (if engage_next_oncall is enabled)
+ incident_role = resolve_role(db_session=db_session, role=ParticipantRoleType.incident_commander, incident=incident)
+ if incident_role and incident_role.engage_next_oncall:
+ oncall_plugin = plugin_service.get_active_instance(
+ db_session=db_session, project_id=incident.project.id, plugin_type="oncall"
+ )
+ if not oncall_plugin:
+ log.debug("Resolved observer role not available since oncall plugin is not active.")
+ else:
+ oncall_email = oncall_plugin.instance.get_next_oncall(service_id=incident_role.service.external_id)
+ if oncall_email:
+ participant_flows.add_participant(
+ oncall_email,
+ incident,
+ db_session,
+ service_id=incident_role.service.id,
+ role=ParticipantRoleType.observer,
+ )
+
return incident
diff --git a/src/dispatch/incident_role/models.py b/src/dispatch/incident_role/models.py
index 7694b3aed692..8a519b67b7e5 100644
--- a/src/dispatch/incident_role/models.py
+++ b/src/dispatch/incident_role/models.py
@@ -60,6 +60,8 @@ class IncidentRole(Base, TimeStampMixin, ProjectMixin):
individual_id = Column(Integer, ForeignKey("individual_contact.id"))
individual = relationship("IndividualContact")
+ engage_next_oncall = Column(Boolean, default=False)
+
# Pydantic models
class IncidentRoleBase(DispatchBase):
@@ -70,6 +72,7 @@ class IncidentRoleBase(DispatchBase):
incident_priorities: Optional[List[IncidentPriorityRead]]
service: Optional[ServiceRead]
individual: Optional[IndividualContactRead]
+ engage_next_oncall: Optional[bool]
class IncidentRoleCreateUpdate(IncidentRoleBase):
diff --git a/src/dispatch/plugins/dispatch_pagerduty/plugin.py b/src/dispatch/plugins/dispatch_pagerduty/plugin.py
index b30b0456bdd2..d504bb800b01 100644
--- a/src/dispatch/plugins/dispatch_pagerduty/plugin.py
+++ b/src/dispatch/plugins/dispatch_pagerduty/plugin.py
@@ -20,6 +20,7 @@
oncall_shift_check,
get_escalation_policy,
get_service,
+ get_next_oncall,
)
@@ -114,3 +115,13 @@ def get_schedule_id_from_service_id(self, service_id: str) -> Optional[str]:
except Exception as e:
log.error("Error trying to retrieve schedule_id from service_id")
log.exception(e)
+
+ def get_next_oncall(self, service_id: str) -> Optional[str]:
+ schedule_id = self.get_schedule_id_from_service_id(service_id)
+
+ client = APISession(self.configuration.api_key.get_secret_value())
+ client.url = self.configuration.pagerduty_api_url
+ return get_next_oncall(
+ client=client,
+ schedule_id=schedule_id,
+ )
diff --git a/src/dispatch/plugins/dispatch_pagerduty/service.py b/src/dispatch/plugins/dispatch_pagerduty/service.py
index 89079192ff40..f4aa185d6752 100644
--- a/src/dispatch/plugins/dispatch_pagerduty/service.py
+++ b/src/dispatch/plugins/dispatch_pagerduty/service.py
@@ -194,3 +194,13 @@ def oncall_shift_check(client: APISession, schedule_id: str, hour: int) -> Optio
if previous_oncall["email"] != next_oncall["email"]:
return previous_oncall
+
+
+def get_next_oncall(client: APISession, schedule_id: str) -> Optional[str]:
+ """Retrieves the email of the next oncall person. Assumes 12-hour shifts"""
+ now = datetime.utcnow()
+
+ next_shift = (now + timedelta(hours=13)).isoformat(timespec="minutes") + "Z"
+ next_oncall = get_oncall_at_time(client=client, schedule_id=schedule_id, utctime=next_shift)
+
+ return None if not next_oncall else next_oncall["email"]
diff --git a/src/dispatch/static/dispatch/src/case/priority/Table.vue b/src/dispatch/static/dispatch/src/case/priority/Table.vue
index 1e23642f4960..67d7b7b24b7b 100644
--- a/src/dispatch/static/dispatch/src/case/priority/Table.vue
+++ b/src/dispatch/static/dispatch/src/case/priority/Table.vue
@@ -134,3 +134,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/case/severity/Table.vue b/src/dispatch/static/dispatch/src/case/severity/Table.vue
index 4495137a4c64..73e960501483 100644
--- a/src/dispatch/static/dispatch/src/case/severity/Table.vue
+++ b/src/dispatch/static/dispatch/src/case/severity/Table.vue
@@ -130,3 +130,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/case/type/Table.vue b/src/dispatch/static/dispatch/src/case/type/Table.vue
index 44ad0245108c..c72c6214a6de 100644
--- a/src/dispatch/static/dispatch/src/case/type/Table.vue
+++ b/src/dispatch/static/dispatch/src/case/type/Table.vue
@@ -151,3 +151,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/incident/priority/Table.vue b/src/dispatch/static/dispatch/src/incident/priority/Table.vue
index 91964a20fedc..bdb54c3086f7 100644
--- a/src/dispatch/static/dispatch/src/incident/priority/Table.vue
+++ b/src/dispatch/static/dispatch/src/incident/priority/Table.vue
@@ -195,3 +195,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/incident/severity/Table.vue b/src/dispatch/static/dispatch/src/incident/severity/Table.vue
index 2c5729a5bc73..11ad3d680cb2 100644
--- a/src/dispatch/static/dispatch/src/incident/severity/Table.vue
+++ b/src/dispatch/static/dispatch/src/incident/severity/Table.vue
@@ -130,3 +130,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/incident/type/Table.vue b/src/dispatch/static/dispatch/src/incident/type/Table.vue
index 13d2146e68be..bffcf543f236 100644
--- a/src/dispatch/static/dispatch/src/incident/type/Table.vue
+++ b/src/dispatch/static/dispatch/src/incident/type/Table.vue
@@ -140,3 +140,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/incident_cost_type/Table.vue b/src/dispatch/static/dispatch/src/incident_cost_type/Table.vue
index 137d8485100c..239a2eb6816a 100644
--- a/src/dispatch/static/dispatch/src/incident_cost_type/Table.vue
+++ b/src/dispatch/static/dispatch/src/incident_cost_type/Table.vue
@@ -158,3 +158,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/incident_role/PolicyRoleBuilder.vue b/src/dispatch/static/dispatch/src/incident_role/PolicyRoleBuilder.vue
index dd5fdee8589c..6e282fa42684 100644
--- a/src/dispatch/static/dispatch/src/incident_role/PolicyRoleBuilder.vue
+++ b/src/dispatch/static/dispatch/src/incident_role/PolicyRoleBuilder.vue
@@ -68,13 +68,22 @@
+
+
+
+
+
diff --git a/src/dispatch/static/dispatch/src/search/Table.vue b/src/dispatch/static/dispatch/src/search/Table.vue
index b68af7b38b95..dd4df5af4600 100644
--- a/src/dispatch/static/dispatch/src/search/Table.vue
+++ b/src/dispatch/static/dispatch/src/search/Table.vue
@@ -196,3 +196,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/signal/Table.vue b/src/dispatch/static/dispatch/src/signal/Table.vue
index 3f3931e4a592..541705a6f1d6 100644
--- a/src/dispatch/static/dispatch/src/signal/Table.vue
+++ b/src/dispatch/static/dispatch/src/signal/Table.vue
@@ -172,3 +172,9 @@ export default {
},
}
+
+
diff --git a/src/dispatch/static/dispatch/src/workflow/Table.vue b/src/dispatch/static/dispatch/src/workflow/Table.vue
index 877b6e4a506b..db34e4365560 100644
--- a/src/dispatch/static/dispatch/src/workflow/Table.vue
+++ b/src/dispatch/static/dispatch/src/workflow/Table.vue
@@ -141,3 +141,9 @@ export default {
},
}
+
+