Skip to content

Commit

Permalink
Merge branch 'master' into enhancement/allow-for-incident-commander-t…
Browse files Browse the repository at this point in the history
…o-be-specified
  • Loading branch information
whitdog47 authored Dec 12, 2023
2 parents 57e9788 + 4e8d3fb commit 5906461
Show file tree
Hide file tree
Showing 23 changed files with 472 additions and 206 deletions.
8 changes: 4 additions & 4 deletions requirements-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ hypothesis-graphql==0.10.0
# via schemathesis
hypothesis-jsonschema==0.22.1
# via schemathesis
idna==3.4
idna==3.6
# via
# anyio
# email-validator
Expand Down Expand Up @@ -246,7 +246,7 @@ oauthlib[signedtoken]==3.2.2
# atlassian-python-api
# jira
# requests-oauthlib
openai==1.3.7
openai==1.3.8
# via -r requirements-base.in
packaging==23.1
# via
Expand All @@ -257,7 +257,7 @@ packaging==23.1
# statsmodels
# thinc
# weasel
pandas==2.1.3
pandas==2.1.4
# via
# -r requirements-base.in
# statsmodels
Expand Down Expand Up @@ -509,7 +509,7 @@ werkzeug==3.0.1
# via schemathesis
wrapt==1.15.0
# via deprecated
yarl==1.9.2
yarl==1.9.4
# via
# aiohttp
# schemathesis
Expand Down
24 changes: 21 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
#
# pip-compile --resolver=backtracking requirements-dev.in
#
aiohttp==3.9.1
# via black
aiosignal==1.3.1
# via aiohttp
asttokens==2.2.1
# via
# devtools
# stack-data
attrs==22.1.0
# via -r requirements-dev.in
black==23.11.0
# via
# -r requirements-dev.in
# aiohttp
black==23.12.0
# via -r requirements-dev.in
cfgv==3.4.0
# via pre-commit
Expand Down Expand Up @@ -38,8 +44,14 @@ faker==20.1.0
# factory-boy
filelock==3.12.2
# via virtualenv
frozenlist==1.4.0
# via
# aiohttp
# aiosignal
identify==2.5.27
# via pre-commit
idna==3.6
# via yarl
iniconfig==2.0.0
# via pytest
ipython==8.18.0
Expand All @@ -48,6 +60,10 @@ jedi==0.19.0
# via ipython
matplotlib-inline==0.1.6
# via ipython
multidict==6.0.4
# via
# aiohttp
# yarl
mypy-extensions==1.0.0
# via black
nodeenv==1.8.0
Expand All @@ -68,7 +84,7 @@ platformdirs==3.10.0
# virtualenv
pluggy==1.2.0
# via pytest
pre-commit==3.5.0
pre-commit==3.6.0
# via -r requirements-dev.in
prompt-toolkit==3.0.39
# via ipython
Expand Down Expand Up @@ -106,6 +122,8 @@ vulture==2.10
# via -r requirements-dev.in
wcwidth==0.2.6
# via prompt-toolkit
yarl==1.9.4
# via aiohttp

# The following packages are considered to be unsafe in a requirements file:
# setuptools
Original file line number Diff line number Diff line change
@@ -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 ###
19 changes: 19 additions & 0 deletions src/dispatch/incident/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,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


Expand Down
3 changes: 3 additions & 0 deletions src/dispatch/incident_role/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
11 changes: 11 additions & 0 deletions src/dispatch/plugins/dispatch_pagerduty/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
oncall_shift_check,
get_escalation_policy,
get_service,
get_next_oncall,
)


Expand Down Expand Up @@ -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,
)
10 changes: 10 additions & 0 deletions src/dispatch/plugins/dispatch_pagerduty/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
15 changes: 8 additions & 7 deletions src/dispatch/plugins/dispatch_slack/case/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -1459,8 +1459,9 @@ def signal_button_click(
if len(raw_text) > 2900:
blocks = [
Section(
text=f"```{raw_text[:2750]}... \n Signal text too long, please vist Dispatch UI for full details.```"
)
text="The alert data exceeds Slack's viewing limit. Please go to the Dispatch Web UI for full details.\n"
),
Section(text=f"```{raw_text[:2750]}...```"),
]
else:
blocks = [Section(text=f"```{raw_text}```")]
Expand Down Expand Up @@ -1619,7 +1620,7 @@ def handle_engagement_submission_event(
# Check if last_mfa_time was within the last hour
last_hour = datetime.now() - timedelta(hours=1)
if (user.last_mfa_time and user.last_mfa_time > last_hour) or mfa_enabled is False:
return send_engagment_response(
return send_engagement_response(
case=case,
client=client,
context_from_user=context_from_user,
Expand All @@ -1638,7 +1639,7 @@ def handle_engagement_submission_event(
type="Are you confirming the behavior as expected in Dispatch?",
)
if response == PushResponseResult.allow:
send_engagment_response(
send_engagement_response(
case=case,
client=client,
context_from_user=context_from_user,
Expand All @@ -1654,7 +1655,7 @@ def handle_engagement_submission_event(
db_session.commit()
return
else:
return send_engagment_response(
return send_engagement_response(
case=case,
client=client,
context_from_user=context_from_user,
Expand All @@ -1668,7 +1669,7 @@ def handle_engagement_submission_event(
)


def send_engagment_response(
def send_engagement_response(
case: Case,
client: WebClient,
context_from_user: str,
Expand Down Expand Up @@ -1710,7 +1711,7 @@ def send_engagment_response(
)

if response == PushResponseResult.allow:
# We only update engagment message (which removes Confirm/Deny button) for success
# We only update engagement message (which removes Confirm/Deny button) for success
# this allows the user to retry the confirmation if the MFA check failed
blocks = create_signal_engagement_message(
case=case,
Expand Down
Loading

0 comments on commit 5906461

Please sign in to comment.