Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(slack): add /critical shortcut to create incident more efficiently #117

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/firefighter/incidents/forms/create_incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,16 @@ class CreateIncidentForm(CreateIncidentFormBase):
def trigger_incident_workflow(
self,
creator: User,
impacts_data: dict[str, ImpactLevel],
impacts_data: dict[str, ImpactLevel] | None,
*args: Any,
**kwargs: Any,
) -> None:
incident = Incident.objects.declare(created_by=creator, **self.cleaned_data)
impacts_form = SelectImpactForm(impacts_data)

impacts_form.save(incident=incident)
if impacts_data is not None:
impacts_form = SelectImpactForm(impacts_data)
impacts_form.save(incident=incident)

create_incident_conversation.send(
"create_incident_form",
incident=incident,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def get_manifest(
"command": command,
"url": f"{public_base_url}/api/v2/firefighter/slack/incident/",
"description": "Manage Incidents 🚨",
"usage_hint": "[open|update|close|status|help]",
"usage_hint": "[open|critical|update|close|status|help]",
"should_escape": False,
}
for command in [main_command, *command_aliases]
Expand Down
3 changes: 3 additions & 0 deletions src/firefighter/slack/views/events/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
)
from firefighter.slack.views.modals import (
modal_close,
modal_critical,
modal_dowgrade_workflow,
modal_edit,
modal_open,
Expand Down Expand Up @@ -111,6 +112,8 @@ def manage_incident(ack: Ack, respond: Respond, body: dict[str, Any]) -> None:
modal_edit.open_modal_aio(ack, body)
elif command == "close":
modal_close.open_modal_aio(ack=ack, body=body)
elif command == "critical":
modal_critical.open_modal_aio(ack=ack, body=body)
elif command == "status":
modal_status.open_modal_aio(ack=ack, body=body)
elif command in {"oncall", "on-call"}:
Expand Down
2 changes: 2 additions & 0 deletions src/firefighter/slack/views/modals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING

from firefighter.slack.views.modals.close import CloseModal, modal_close
from firefighter.slack.views.modals.critical import CriticalModal, modal_critical
from firefighter.slack.views.modals.downgrade_workflow import (
DowngradeWorkflowModal,
modal_dowgrade_workflow,
Expand Down Expand Up @@ -48,4 +49,5 @@
StatusModal,
SendSosModal,
DowngradeWorkflowModal,
CriticalModal
]
126 changes: 126 additions & 0 deletions src/firefighter/slack/views/modals/critical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

from django.conf import settings
from slack_sdk.models.blocks.blocks import SectionBlock
from slack_sdk.models.views import View

from firefighter.incidents.forms.create_incident import CreateIncidentForm
from firefighter.slack.slack_app import SlackApp
from firefighter.slack.views.modals.base_modal.base import ModalForm

if TYPE_CHECKING:
from slack_bolt.context.ack.ack import Ack

from firefighter.incidents.models.incident import Priority, Severity
from firefighter.incidents.models.user import User
from firefighter.slack.views.modals.base_modal.form_utils import (
SlackFormAttributesDict,
)

app = SlackApp()
logger = logging.getLogger(__name__)


def priority_label(obj: Severity | Priority) -> str:
return f"{obj.emoji} {obj.name} - {obj.description}"


class CriticalFormSlack(CreateIncidentForm):
slack_fields: SlackFormAttributesDict = {
"title": {
"input": {
"multiline": False,
"placeholder": "Short, punchy description of what's happening.",
},
"block": {"hint": None},
},
"description": {
"input": {
"multiline": True,
"placeholder": "Help people responding to the incident. This will be posted to #tech-incidents and on our internal status page.\nThis description can be edited later.",
},
"block": {"hint": None},
},
"component": {
"input": {
"placeholder": "Select affected component",
}
},
"priority": {
"input": {
"placeholder": "Select a priority",
},
"widget": {
"post_block": (
SectionBlock(
text=f"_<{settings.SLACK_SEVERITY_HELP_GUIDE_URL}|How to choose the priority?>_"
)
if settings.SLACK_SEVERITY_HELP_GUIDE_URL
else None
),
"label_from_instance": priority_label,
},
},
}


class CriticalModal(ModalForm[CriticalFormSlack]):
open_action: str = "open_modal_incident_critical"
open_shortcut = "modal_critical"
callback_id: str = "incident_critical"

form_class = CriticalFormSlack

def build_modal_fn(self, **kwargs: Any) -> View:
form_instance = self.get_form_class()()
blocks = [
SectionBlock(
text="*Warning:* The use of `/critical` is reserved for experienced users."
),
*form_instance.slack_blocks(),
]

return View(
type="modal",
title="Open a critical incident"[:24],
submit="Create the incident"[:24],
callback_id=self.callback_id,
blocks=blocks,
clear_on_close=False,
close=None,
)

def handle_modal_fn( # type: ignore
self, ack: Ack, body: dict[str, Any], user: User
) -> None :

slack_form = self.handle_form_errors(
ack, body, forms_kwargs={},
)

if slack_form is None:
return

form = slack_form.form

try:
if hasattr(form, "trigger_incident_workflow") and callable(
form.trigger_incident_workflow
):
form.trigger_incident_workflow(
creator=user,
impacts_data=None,
)
except: # noqa: E722
logger.exception("Error triggering incident workflow")
# XXX warn the user via DM!

if len(form.cleaned_data) == 0:
logger.warning("Form is empty, no data captured.")
return


modal_critical = CriticalModal()