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 { }, } + +