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): edit command to update the metadata of an incident #96

Merged
merged 4 commits into from
Sep 27, 2024
Merged
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
33 changes: 33 additions & 0 deletions src/firefighter/incidents/forms/edit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from __future__ import annotations

from django import forms

from firefighter.incidents.models import Environment


def initial_environments() -> Environment:
return Environment.objects.get(default=True)


class EditMetaForm(forms.Form):
title = forms.CharField(
label="Title",
max_length=128,
min_length=10,
widget=forms.TextInput(attrs={"placeholder": "What's going on?"}),
)
description = forms.CharField(
label="Summary",
widget=forms.Textarea(
attrs={
"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."
}
),
min_length=10,
max_length=1200,
)
environment = forms.ModelChoiceField(
label="Environment",
queryset=Environment.objects.all(),
initial=initial_environments,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 4.2.14 on 2024-09-27 10:06

from django.db import migrations, models

import firefighter.incidents.models.environment


class Migration(migrations.Migration):

dependencies = [
("incidents", "0003_delete_featureteam"),
]

operations = [
migrations.AddField(
model_name="incidentupdate",
name="environment",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=models.SET(
firefighter.incidents.models.environment.Environment.get_default
),
to="incidents.environment",
),
),
]
35 changes: 22 additions & 13 deletions src/firefighter/incidents/models/incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,21 +335,27 @@ def can_be_closed(self) -> tuple[bool, list[tuple[str, str]]]:
return True, []
if self.needs_postmortem:
if self.status.value != IncidentStatus.POST_MORTEM:
cant_closed_reasons.append((
"STATUS_NOT_POST_MORTEM",
f"Incident is not in PostMortem status, and needs one because of its priority and environment ({self.priority.name}/{self.environment.value}).",
))
cant_closed_reasons.append(
(
"STATUS_NOT_POST_MORTEM",
f"Incident is not in PostMortem status, and needs one because of its priority and environment ({self.priority.name}/{self.environment.value}).",
)
)
elif self.status.value < IncidentStatus.FIXED:
cant_closed_reasons.append((
"STATUS_NOT_MITIGATED",
f"Incident is not in {IncidentStatus.FIXED.label} status (currently {self.status.label}).",
))
cant_closed_reasons.append(
(
"STATUS_NOT_MITIGATED",
f"Incident is not in {IncidentStatus.FIXED.label} status (currently {self.status.label}).",
)
)
missing_milestones = self.missing_milestones()
if len(missing_milestones) > 0:
cant_closed_reasons.append((
"MISSING_REQUIRED_KEY_EVENTS",
f"Missing key events: {', '.join(missing_milestones)}",
))
cant_closed_reasons.append(
(
"MISSING_REQUIRED_KEY_EVENTS",
f"Missing key events: {', '.join(missing_milestones)}",
)
)

if len(cant_closed_reasons) > 0:
return False, cant_closed_reasons
Expand Down Expand Up @@ -534,7 +540,7 @@ def update_roles(

return incident_update

def create_incident_update(
def create_incident_update( # noqa: PLR0913
self: Incident,
message: str | None = None,
status: int | None = None,
Expand All @@ -544,6 +550,7 @@ def create_incident_update(
event_type: str | None = None,
title: str | None = None,
description: str | None = None,
environment_id: str | None = None,
event_ts: datetime | None = None,
) -> IncidentUpdate:
updated_fields: list[str] = []
Expand All @@ -560,6 +567,7 @@ def _update_incident_field(
_update_incident_field(self, "component_id", component_id, updated_fields)
_update_incident_field(self, "title", title, updated_fields)
_update_incident_field(self, "description", description, updated_fields)
_update_incident_field(self, "environment_id", environment_id, updated_fields)

old_priority = self.priority if priority_id is not None else None

Expand All @@ -573,6 +581,7 @@ def _update_incident_field(
incident=self,
status=status, # type: ignore
priority_id=priority_id,
environment_id=environment_id,
component_id=component_id,
message=message,
created_by=created_by,
Expand Down
7 changes: 7 additions & 0 deletions src/firefighter/incidents/models/incident_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from firefighter.incidents.enums import IncidentStatus
from firefighter.incidents.models.component import Component
from firefighter.incidents.models.environment import Environment
from firefighter.incidents.models.priority import Priority
from firefighter.incidents.models.severity import Severity
from firefighter.incidents.models.user import User
Expand Down Expand Up @@ -65,6 +66,12 @@ class IncidentUpdate(models.Model):
priority = models.ForeignKey[Priority | None, Priority | None](
Priority, null=True, blank=True, on_delete=models.SET(Priority.get_default)
)
environment = models.ForeignKey[Environment | None, Environment | None](
Environment,
null=True,
blank=True,
on_delete=models.SET(Environment.get_default),
)
incident = models.ForeignKey["Incident", "Incident"](
"Incident", on_delete=models.CASCADE
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@
</div>
</div>
<div class="text-neutral-600 dark:text-neutral-100">
{% if incident_update.title %}
<p class="mt-0.5">
Title update
</p>
<p class="pl-2 mt-0.5 text-sm text-neutral-500 dark:text-neutral-300">
Title changed to: {{ incident_update.title }}
</p>
{% endif %}
{% if incident_update.description or incident_update.description|length > 0 %}
<p class="mt-0.5">
Description update
</p>
<div
class="pb-2"
>
<div class="text-sm flex flex-col border-b border-neutral-200 py-1 p-4 text-left sm:border-0 sm:border-l-4 break-words">
{{ incident_update.description | urlize | linebreaksbr}}
</div>
</div>
{% endif %}
{% if incident_update.event_type or incident_update.event_type|length > 0 %}
<p class="mt-0.5">
Key event: {{ incident_update.event_type|title }}
Expand All @@ -65,6 +85,17 @@
{% include "./status_pill.html" with status=incident_update.status IncidentStatus=IncidentStatus only %}
</p>
{% endif %}
{% if incident_update.environment %}
<p class="mt-0.5">
Environment update
</p>
{% endif %}
{% if incident_update.environment or incident_update.environment|length > 0 %}
<p class="pl-2 mt-0.5 text-sm text-neutral-500 dark:text-neutral-300">
Environment changed to:
{% include "./environment_pill.html" with environment=incident_update.environment only %}
</p>
{% endif %}
{% if incident_update.severity %}
<p class="mt-0.5">
Severity update
Expand All @@ -84,8 +115,6 @@
<p class="mt-0.5">
Component update
</p>
{% endif %}
{% if incident_update.component %}
<p class="pl-2 mt-0.5 text-sm text-neutral-500 dark:text-neutral-300">
Component impacted changed to:
{{ incident_update.component}}
Expand Down
86 changes: 54 additions & 32 deletions src/firefighter/slack/messages/slack_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,18 +359,20 @@ def get_blocks(self) -> list[Block]:
if len(fields) == 0:
fields.append("_No changes detected._")

blocks.extend([
DividerBlock(),
SectionBlock(
block_id="message_role_update",
fields=fields,
accessory=ButtonElement(
text="Update",
value=str(self.incident.id),
action_id=UpdateRolesModal.open_action,
blocks.extend(
[
DividerBlock(),
SectionBlock(
block_id="message_role_update",
fields=fields,
accessory=ButtonElement(
text="Update",
value=str(self.incident.id),
action_id=UpdateRolesModal.open_action,
),
),
),
])
]
)
if not self.first_update:
blocks.append(
ContextBlock(
Expand Down Expand Up @@ -457,6 +459,16 @@ def get_blocks(self) -> list[Block]:
blocks.append(
slack_block_quote(self.incident_update.message),
)
if self.incident_update.title and self.incident_update.title != "":
blocks.append(
SectionBlock(
text=f"New title: *{shorten(self.incident_update.title, 2985)}*"
),
)
if self.incident_update.description and self.incident_update.description != "":
blocks.append(
slack_block_quote(self.incident_update.description),
)
fields = []
if self.in_channel:
if self.incident_update.status:
Expand All @@ -477,24 +489,32 @@ def get_blocks(self) -> list[Block]:
text=f":package: *Component:* {self.incident.component.group.name} - {self.incident.component.name}"
)
)
if self.incident_update.environment:
fields.append(
MarkdownTextObject(
text=f":round_pushpin: *Environment:* {self.incident_update.environment.value}"
)
)

if len(fields) > 0:
blocks.extend([
DividerBlock(),
SectionBlock(
block_id="message_status_update",
fields=fields,
accessory=(
ButtonElement(
text="Update",
value=str(self.incident.id),
action_id=UpdateStatusModal.open_action,
)
if self.in_channel
else None
blocks.extend(
[
DividerBlock(),
SectionBlock(
block_id="message_status_update",
fields=fields,
accessory=(
ButtonElement(
text="Update",
value=str(self.incident.id),
action_id=UpdateStatusModal.open_action,
)
if self.in_channel
else None
),
),
),
])
]
)

if self.incident_update.created_by:
blocks.append(
Expand Down Expand Up @@ -683,13 +703,15 @@ def get_blocks(self) -> list[Block]:
]

if self.incident.status >= IncidentStatus.FIXED:
blocks.extend([
SectionBlock(
text=MarkdownTextObject(
text=f":white_check_mark: *UPDATE*: Incident #{self.incident.conversation.name} has been mitigated, you can resume your deployments."
blocks.extend(
[
SectionBlock(
text=MarkdownTextObject(
text=f":white_check_mark: *UPDATE*: Incident #{self.incident.conversation.name} has been mitigated, you can resume your deployments."
)
)
)
])
]
)
return blocks

def get_text(self) -> str:
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 @@ -16,6 +16,7 @@
from firefighter.slack.views.modals import (
modal_close,
modal_dowgrade_workflow,
modal_edit,
modal_open,
modal_postmortem,
modal_send_sos,
Expand Down Expand Up @@ -106,6 +107,8 @@ def manage_incident(ack: Ack, respond: Respond, body: dict[str, Any]) -> None:
modal_open.open_modal_aio(ack, body)
elif command == "update":
modal_update.open_modal_aio(ack, body)
elif command == "edit":
modal_edit.open_modal_aio(ack, body)
elif command == "close":
modal_close.open_modal_aio(ack=ack, body=body)
elif command == "status":
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 @@ -7,6 +7,7 @@
DowngradeWorkflowModal,
modal_dowgrade_workflow,
)
from firefighter.slack.views.modals.edit import EditMetaModal, modal_edit
from firefighter.slack.views.modals.key_event_message import ( # XXX(dugab) move and rename (not a modal but a surface...)
KeyEvents,
)
Expand Down Expand Up @@ -39,6 +40,7 @@

selectable_modals: list[type[SlackModal]] = [
UpdateModal,
EditMetaModal,
UpdateRolesModal,
OnCallModal,
CloseModal,
Expand Down
Loading
Loading