firefighter-incident 0.0.13__py3-none-any.whl → 0.0.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. firefighter/_version.py +16 -3
  2. firefighter/api/serializers.py +8 -8
  3. firefighter/api/urls.py +8 -1
  4. firefighter/api/views/_base.py +1 -1
  5. firefighter/api/views/components.py +5 -5
  6. firefighter/api/views/incidents.py +9 -9
  7. firefighter/firefighter/settings/components/raid.py +3 -0
  8. firefighter/incidents/admin.py +24 -24
  9. firefighter/incidents/factories.py +14 -5
  10. firefighter/incidents/forms/close_incident.py +4 -4
  11. firefighter/incidents/forms/create_incident.py +4 -4
  12. firefighter/incidents/forms/update_status.py +4 -4
  13. firefighter/incidents/menus.py +2 -2
  14. firefighter/incidents/migrations/0005_enable_from_p1_to_p5_priority.py +7 -5
  15. firefighter/incidents/migrations/0009_update_sla.py +7 -5
  16. firefighter/incidents/migrations/0020_create_incident_category_model.py +64 -0
  17. firefighter/incidents/migrations/0021_copy_component_data_to_incident_category.py +57 -0
  18. firefighter/incidents/migrations/0022_add_incident_category_fields.py +34 -0
  19. firefighter/incidents/migrations/0023_populate_incident_category_references.py +57 -0
  20. firefighter/incidents/migrations/0024_remove_component_fields_and_model.py +26 -0
  21. firefighter/incidents/migrations/0025_make_incident_category_required.py +24 -0
  22. firefighter/incidents/migrations/0026_alter_incidentcategory_options_and_more.py +39 -0
  23. firefighter/incidents/models/__init__.py +1 -1
  24. firefighter/incidents/models/group.py +1 -1
  25. firefighter/incidents/models/incident.py +15 -15
  26. firefighter/incidents/models/{component.py → incident_category.py} +30 -29
  27. firefighter/incidents/models/incident_update.py +3 -3
  28. firefighter/incidents/tables.py +9 -9
  29. firefighter/incidents/templates/layouts/partials/incident_card.html +1 -1
  30. firefighter/incidents/templates/layouts/partials/incident_timeline.html +2 -2
  31. firefighter/incidents/templates/pages/{component_detail.html → incident_category_detail.html} +13 -13
  32. firefighter/incidents/templates/pages/{component_list.html → incident_category_list.html} +2 -2
  33. firefighter/incidents/templates/pages/incident_detail.html +3 -3
  34. firefighter/incidents/urls.py +6 -6
  35. firefighter/incidents/views/components/details.py +9 -9
  36. firefighter/incidents/views/components/list.py +9 -9
  37. firefighter/incidents/views/reports.py +2 -2
  38. firefighter/incidents/views/users/details.py +2 -2
  39. firefighter/incidents/views/views.py +7 -7
  40. firefighter/jira_app/client.py +1 -1
  41. firefighter/logging/custom_json_formatter.py +2 -1
  42. firefighter/pagerduty/tasks/trigger_oncall.py +1 -1
  43. firefighter/raid/admin.py +0 -11
  44. firefighter/raid/client.py +3 -3
  45. firefighter/raid/forms.py +53 -19
  46. firefighter/raid/migrations/0003_delete_raidarea.py +16 -0
  47. firefighter/raid/models.py +2 -21
  48. firefighter/raid/serializers.py +5 -4
  49. firefighter/raid/service.py +29 -27
  50. firefighter/raid/signals/incident_created.py +4 -2
  51. firefighter/raid/utils.py +1 -1
  52. firefighter/raid/views/__init__.py +1 -1
  53. firefighter/raid/views/open_normal.py +2 -2
  54. firefighter/slack/admin.py +8 -8
  55. firefighter/slack/management/commands/switch_test_users.py +272 -0
  56. firefighter/slack/messages/slack_messages.py +5 -5
  57. firefighter/slack/migrations/0005_add_incident_categories_fields.py +33 -0
  58. firefighter/slack/migrations/0006_copy_components_to_incident_categories.py +57 -0
  59. firefighter/slack/migrations/0007_remove_components_fields.py +22 -0
  60. firefighter/slack/migrations/0008_alter_conversation_incident_categories_and_more.py +33 -0
  61. firefighter/slack/models/conversation.py +3 -3
  62. firefighter/slack/models/incident_channel.py +1 -1
  63. firefighter/slack/models/user.py +1 -1
  64. firefighter/slack/models/user_group.py +3 -3
  65. firefighter/slack/rules.py +1 -1
  66. firefighter/slack/signals/get_users.py +2 -2
  67. firefighter/slack/signals/incident_updated.py +1 -1
  68. firefighter/slack/utils.py +2 -2
  69. firefighter/slack/views/events/home.py +2 -2
  70. firefighter/slack/views/modals/base_modal/form_utils.py +15 -0
  71. firefighter/slack/views/modals/close.py +3 -3
  72. firefighter/slack/views/modals/open.py +25 -1
  73. firefighter/slack/views/modals/opening/check_current_incidents.py +2 -2
  74. firefighter/slack/views/modals/opening/details/critical.py +1 -1
  75. firefighter/slack/views/modals/opening/select_impact.py +5 -2
  76. firefighter/slack/views/modals/update_status.py +4 -4
  77. firefighter_fixtures/incidents/{components.json → incident_categories.json} +52 -52
  78. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/METADATA +2 -2
  79. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/RECORD +98 -77
  80. firefighter_tests/conftest.py +4 -5
  81. firefighter_tests/test_api/test_api_landbot.py +1 -1
  82. firefighter_tests/test_firefighter/test_sso.py +146 -0
  83. firefighter_tests/test_incidents/test_forms/test_form_utils.py +15 -15
  84. firefighter_tests/test_incidents/test_incident_urls.py +3 -3
  85. firefighter_tests/test_incidents/test_models/test_incident_category.py +165 -0
  86. firefighter_tests/test_incidents/test_models/test_incident_model.py +2 -2
  87. firefighter_tests/test_raid/test_priority_mapping.py +267 -0
  88. firefighter_tests/test_raid/test_raid_client.py +580 -0
  89. firefighter_tests/test_raid/test_raid_forms.py +795 -0
  90. firefighter_tests/test_raid/test_raid_models.py +185 -0
  91. firefighter_tests/test_raid/test_raid_serializers.py +507 -0
  92. firefighter_tests/test_raid/test_raid_service.py +442 -0
  93. firefighter_tests/test_raid/test_raid_views.py +196 -0
  94. firefighter_tests/test_slack/views/modals/test_close.py +6 -6
  95. firefighter_tests/test_slack/views/modals/test_update_status.py +4 -4
  96. firefighter_fixtures/raid/area.json +0 -1
  97. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/WHEEL +0 -0
  98. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/entry_points.txt +0 -0
  99. {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,57 @@
1
+ # Generated manually on 2025-08-19 - Copy component relationships to incident_categories
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ def copy_components_to_incident_categories(apps, schema_editor):
7
+ """Copy all component M2M relationships to incident_categories M2M relationships"""
8
+ Conversation = apps.get_model("slack", "Conversation")
9
+ UserGroup = apps.get_model("slack", "UserGroup")
10
+
11
+ # Copy conversation components to incident_categories
12
+ conversations_updated = 0
13
+ for conversation in Conversation.objects.prefetch_related("components").all():
14
+ # Copy all component relationships to incident_categories (same UUIDs)
15
+ incident_category_ids = list(conversation.components.values_list("id", flat=True))
16
+ conversation.incident_categories.set(incident_category_ids)
17
+ if incident_category_ids:
18
+ conversations_updated += 1
19
+
20
+ # Copy usergroup components to incident_categories
21
+ usergroups_updated = 0
22
+ for usergroup in UserGroup.objects.prefetch_related("components").all():
23
+ incident_category_ids = list(usergroup.components.values_list("id", flat=True))
24
+ usergroup.incident_categories.set(incident_category_ids)
25
+ if incident_category_ids:
26
+ usergroups_updated += 1
27
+
28
+
29
+ def reverse_copy_components_to_incident_categories(apps, schema_editor):
30
+ """Reverse: copy incident_categories relationships back to components"""
31
+ Conversation = apps.get_model("slack", "Conversation")
32
+ UserGroup = apps.get_model("slack", "UserGroup")
33
+
34
+ # Copy conversation incident_categories back to components
35
+ for conversation in Conversation.objects.prefetch_related("incident_categories").all():
36
+ component_ids = list(conversation.incident_categories.values_list("id", flat=True))
37
+ conversation.components.set(component_ids)
38
+
39
+ # Copy usergroup incident_categories back to components
40
+ for usergroup in UserGroup.objects.prefetch_related("incident_categories").all():
41
+ component_ids = list(usergroup.incident_categories.values_list("id", flat=True))
42
+ usergroup.components.set(component_ids)
43
+
44
+
45
+ class Migration(migrations.Migration):
46
+
47
+ dependencies = [
48
+ ("slack", "0005_add_incident_categories_fields"),
49
+ ("incidents", "0023_populate_incident_category_references"),
50
+ ]
51
+
52
+ operations = [
53
+ migrations.RunPython(
54
+ copy_components_to_incident_categories,
55
+ reverse_copy_components_to_incident_categories,
56
+ ),
57
+ ]
@@ -0,0 +1,22 @@
1
+ # Generated manually on 2025-08-19 - Remove components fields from Slack models
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("slack", "0006_copy_components_to_incident_categories"),
10
+ ("incidents", "0024_remove_component_fields_and_model"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.RemoveField(
15
+ model_name="conversation",
16
+ name="components",
17
+ ),
18
+ migrations.RemoveField(
19
+ model_name="usergroup",
20
+ name="components",
21
+ ),
22
+ ]
@@ -0,0 +1,33 @@
1
+ # Generated by Django 4.2.23 on 2025-09-17 17:49
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("incidents", "0026_alter_incidentcategory_options_and_more"),
10
+ ("slack", "0007_remove_components_fields"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="conversation",
16
+ name="incident_categories",
17
+ field=models.ManyToManyField(
18
+ blank=True,
19
+ related_name="conversations",
20
+ to="incidents.incidentcategory",
21
+ ),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name="usergroup",
25
+ name="incident_categories",
26
+ field=models.ManyToManyField(
27
+ blank=True,
28
+ help_text="Incident created with this usergroup automatically add the group members to these issue categories.",
29
+ related_name="usergroups",
30
+ to="incidents.incidentcategory",
31
+ ),
32
+ ),
33
+ ]
@@ -11,7 +11,7 @@ from django_stubs_ext.db.models import TypedModelMeta
11
11
  from slack_sdk.errors import SlackApiError
12
12
 
13
13
  from firefighter.firefighter.utils import get_in
14
- from firefighter.incidents.models import Component, User
14
+ from firefighter.incidents.models import IncidentCategory, User
15
15
  from firefighter.slack.messages.base import SlackMessageStrategy, SlackMessageSurface
16
16
  from firefighter.slack.models.user import SlackUser
17
17
  from firefighter.slack.slack_app import DefaultWebClient, SlackApp, slack_client
@@ -188,8 +188,8 @@ class Conversation(models.Model):
188
188
  updated_at = models.DateTimeField(auto_now=True)
189
189
 
190
190
  members = models.ManyToManyField(User, blank=True)
191
- components = models.ManyToManyField["Component", "Component"](
192
- Component, related_name="conversations", blank=True
191
+ incident_categories = models.ManyToManyField["IncidentCategory", "IncidentCategory"](
192
+ IncidentCategory, related_name="conversations", blank=True
193
193
  )
194
194
  tag = models.CharField(
195
195
  max_length=80,
@@ -100,7 +100,7 @@ class IncidentChannel(Conversation):
100
100
  self, client: WebClient = DefaultWebClient
101
101
  ) -> SlackResponse | None:
102
102
  incident: Incident = self.incident
103
- topic = f"Incident - {incident.priority.emoji} {incident.priority.name} - {incident.status.label} - {incident.component.group.name} - {incident.component.name} - {SLACK_APP_EMOJI} <{incident.status_page_url + '?utm_medium=FireFighter+Slack&utm_source=Slack+Topic&utm_campaign=Slack+Topic+Link'}| {APP_DISPLAY_NAME} Status>"
103
+ topic = f"Incident - {incident.priority.emoji} {incident.priority.name} - {incident.status.label} - {incident.incident_category.group.name} - {incident.incident_category.name} - {SLACK_APP_EMOJI} <{incident.status_page_url + '?utm_medium=FireFighter+Slack&utm_source=Slack+Topic&utm_campaign=Slack+Topic+Link'}| {APP_DISPLAY_NAME} Status>"
104
104
 
105
105
  if len(topic) > 250:
106
106
  logger.warning(
@@ -147,7 +147,7 @@ class SlackUserManager(models.Manager["SlackUser"]):
147
147
  # If we have no Slack User, let's go ahead and create a User and its associated SlackUser
148
148
  user, _created = User.objects.get_or_create(
149
149
  email=email,
150
- username=email.split("@")[0],
150
+ username=email.split("@", maxsplit=1)[0],
151
151
  defaults={
152
152
  "name": clean_user_info["name"],
153
153
  },
@@ -8,7 +8,7 @@ from django.db import models
8
8
  from django_stubs_ext.db.models import TypedModelMeta
9
9
 
10
10
  from firefighter.firefighter.utils import get_first_in, get_in
11
- from firefighter.incidents.models import Component, User
11
+ from firefighter.incidents.models import IncidentCategory, User
12
12
  from firefighter.slack.slack_app import DefaultWebClient, SlackApp, slack_client
13
13
 
14
14
  if TYPE_CHECKING:
@@ -167,8 +167,8 @@ class UserGroup(models.Model):
167
167
  help_text="Is this an external group, from an external Slack Workspace? Corresponds to the `is_external` field in the Slack API.",
168
168
  )
169
169
 
170
- components = models.ManyToManyField["Component", "Component"](
171
- Component,
170
+ incident_categories = models.ManyToManyField["IncidentCategory", "IncidentCategory"](
171
+ IncidentCategory,
172
172
  related_name="usergroups",
173
173
  blank=True,
174
174
  help_text="Incident created with this usergroup automatically add the group members to these issue categories.",
@@ -50,5 +50,5 @@ def should_publish_in_it_deploy_channel(incident: Incident) -> bool:
50
50
  incident.environment.value == "PRD"
51
51
  and incident.priority.value <= 1
52
52
  and not incident.private
53
- and incident.component.deploy_warning
53
+ and incident.incident_category.deploy_warning
54
54
  )
@@ -26,8 +26,8 @@ logger = logging.getLogger(__name__)
26
26
  def get_invites_from_slack(incident: Incident, **_kwargs: Any) -> Iterable[User]:
27
27
  """New version using cached users instead of querying Slack API."""
28
28
  # Prepare sub-queries
29
- slack_usergroups: QuerySet[UserGroup] = incident.component.usergroups.all()
30
- slack_conversations: QuerySet[Conversation] = incident.component.conversations.all()
29
+ slack_usergroups: QuerySet[UserGroup] = incident.incident_category.usergroups.all()
30
+ slack_conversations: QuerySet[Conversation] = incident.incident_category.conversations.all()
31
31
 
32
32
  # We make sure to exclude the bot user, and avoid duplicates with distinct()
33
33
  # Also make sure that all users have related SlackUser and a slack_id
@@ -44,7 +44,7 @@ def incident_updated_update_status_handler(
44
44
  # Update topic if needed
45
45
  if (
46
46
  "priority_id" in updated_fields
47
- or "component_id" in updated_fields
47
+ or "incident_category_id" in updated_fields
48
48
  or "_status" in updated_fields
49
49
  ):
50
50
  incident.conversation.set_incident_channel_topic()
@@ -97,9 +97,9 @@ def channel_name_from_incident(incident: Incident) -> str:
97
97
  )
98
98
  date_formatted = localtime(incident.created_at).strftime("%Y%m%d")
99
99
  if incident.environment is not None and incident.environment.value != "PRD":
100
- topic = f"{date_formatted}-{str(incident.id)[:8]}-{incident.environment.value}-{incident.component.name}"
100
+ topic = f"{date_formatted}-{str(incident.id)[:8]}-{incident.environment.value}-{incident.incident_category.name}"
101
101
  else:
102
- topic = f"{date_formatted}-{str(incident.id)[:8]}-{incident.component.name}"
102
+ topic = f"{date_formatted}-{str(incident.id)[:8]}-{incident.incident_category.name}"
103
103
 
104
104
  # Strip non-alphanumeric characters, cut at 80 chars
105
105
  # XXX django.utils.text.slugify should be used instead
@@ -57,7 +57,7 @@ def update_home_tab(
57
57
  Incident.objects.filter(_status__lt=IncidentStatus.CLOSED.value)
58
58
  .order_by("-id")
59
59
  .select_related(
60
- "priority", "component", "environment", "component__group", "conversation"
60
+ "priority", "incident_category", "environment", "incident_category__group", "conversation"
61
61
  )[:30]
62
62
  )
63
63
  blocks: list[Block] = [
@@ -148,7 +148,7 @@ def _home_incident_element(
148
148
  text=f":rotating_light: *Priority:* {incident.priority.emoji} {incident.priority.name}"
149
149
  ),
150
150
  MarkdownTextObject(
151
- text=f":package: *Issue category:* {incident.component.group.name} - {incident.component.name}"
151
+ text=f":package: *Incident category:* {incident.incident_category.group.name} - {incident.incident_category.name}"
152
152
  ),
153
153
  MarkdownTextObject(
154
154
  text=f":speaking_head_in_silhouette: *Last update:* {date_time(incident.updated_at)}"
@@ -336,6 +336,12 @@ class SlackForm(Generic[T]):
336
336
  ):
337
337
  slack_input_kwargs["options"].append(slack_input_kwargs["initial_option"])
338
338
 
339
+ # Ensure we have at least one option for Slack API
340
+ if not slack_input_kwargs["options"]:
341
+ slack_input_kwargs["options"] = [
342
+ SafeOption(label="Please select an option", value="__placeholder__")
343
+ ]
344
+
339
345
  field_name = f"{field_name}___{f.initial}{datetime.now().timestamp()}" # noqa: DTZ005
340
346
  field_name = field_name[:254]
341
347
  return SelectElement(action_id=field_name, **slack_input_kwargs)
@@ -401,6 +407,15 @@ class SlackForm(Generic[T]):
401
407
  OptionGroup(label=str(group_name), options=subgroup)
402
408
  )
403
409
 
410
+ # Ensure we have at least one option group for Slack API
411
+ if not slack_input_kwargs["option_groups"]:
412
+ slack_input_kwargs["option_groups"] = [
413
+ OptionGroup(
414
+ label="No options available",
415
+ options=[SafeOption(label="Please select an option", value="__placeholder__")]
416
+ )
417
+ ]
418
+
404
419
  return SelectElement(action_id=field_name, **slack_input_kwargs)
405
420
 
406
421
  @classmethod
@@ -229,8 +229,8 @@ class CloseModal(
229
229
  # If fields haven't changed, don't include them in the update.
230
230
  update_kwargs = {}
231
231
  for changed_key in form.changed_data:
232
- if changed_key == "component":
233
- update_kwargs["component_id"] = form.cleaned_data[changed_key].id
232
+ if changed_key == "incident_category":
233
+ update_kwargs["incident_category_id"] = form.cleaned_data[changed_key].id
234
234
  if changed_key in {"description", "title", "message"}:
235
235
  update_kwargs[changed_key] = form.cleaned_data[changed_key]
236
236
  # Check can close
@@ -258,7 +258,7 @@ class CloseModal(
258
258
  return {
259
259
  "title": incident.title,
260
260
  "description": incident.description,
261
- "component": incident.component,
261
+ "incident_category": incident.incident_category,
262
262
  }
263
263
 
264
264
  @staticmethod
@@ -56,6 +56,12 @@ INCIDENT_TYPES: dict[ResponseType, dict[str, dict[str, Any]]] = {
56
56
  "slack_form": OpeningCriticalModal,
57
57
  },
58
58
  },
59
+ "normal": {
60
+ "normal": {
61
+ "label": "Normal",
62
+ "slack_form": OpeningCriticalModal,
63
+ },
64
+ },
59
65
  }
60
66
 
61
67
 
@@ -224,6 +230,20 @@ class OpenModal(SlackModal):
224
230
  ) -> list[Block]:
225
231
  if details_form_modal_class is None:
226
232
  return []
233
+
234
+ # Check if we need incident type selection and if it's been done
235
+ response_type = open_incident_context.get("response_type")
236
+ incident_type_value = open_incident_context.get("incident_type")
237
+
238
+ # If there are multiple incident types and none is selected, don't show details button yet
239
+ if (
240
+ response_type
241
+ and response_type in INCIDENT_TYPES
242
+ and len(INCIDENT_TYPES[response_type]) > 1
243
+ and incident_type_value is None
244
+ ):
245
+ return []
246
+
227
247
  return [
228
248
  SectionBlock(
229
249
  text=f"{'✅' if details_form_done else '📝'} Finally, add incident details",
@@ -510,7 +530,8 @@ class OpenModal(SlackModal):
510
530
  """Format a single impact value into description text."""
511
531
  # Handle object with name and description attributes (impact levels)
512
532
  if hasattr(value, "name") and hasattr(value, "description"):
513
- if value.name == "NO" or not value.description:
533
+ # Filter out "no impact" levels using the value field instead of name
534
+ if (hasattr(value, "value") and value.value == "NO") or value.name == "NO" or not value.description:
514
535
  return ""
515
536
 
516
537
  description = ""
@@ -548,6 +569,9 @@ class OpenModal(SlackModal):
548
569
  return incident_types[next(iter(incident_types.keys()))].get("slack_form")
549
570
  if incident_types and incident_type_value is not None:
550
571
  return incident_types[incident_type_value].get("slack_form")
572
+ # Fallback for "normal" response type when no specific incident type is selected
573
+ if response_type == "normal" and incident_types:
574
+ return incident_types[next(iter(incident_types.keys()))].get("slack_form")
551
575
  logger.debug(
552
576
  f"No incident type found for {open_incident_context}. No fallback."
553
577
  )
@@ -44,9 +44,9 @@ class CheckCurrentIncidentsModal(
44
44
  .order_by("-created_at")
45
45
  .select_related(
46
46
  "priority",
47
- "component",
47
+ "incident_category",
48
48
  "environment",
49
- "component__group",
49
+ "incident_category__group",
50
50
  "conversation",
51
51
  )[:30]
52
52
  )
@@ -58,7 +58,7 @@ class CreateIncidentFormSlack(CreateIncidentForm):
58
58
  "label_from_instance": lambda obj: f"{obj.emoji} {obj.name} - {obj.description}",
59
59
  },
60
60
  },
61
- "component": {
61
+ "incident_category": {
62
62
  "input": {
63
63
  "placeholder": "Select affected issue category",
64
64
  },
@@ -139,13 +139,16 @@ class SelectImpactModal(
139
139
  impact_descriptions = ""
140
140
  if impact_form_data:
141
141
  for value in impact_form_data.values():
142
- if value.name != "NO" and value.description:
142
+ # Filter out "no impact" levels using the value field instead of name
143
+ if not ((hasattr(value, "value") and value.value == "NO") or value.name == "NO" or not value.description):
143
144
  if hasattr(value, "impact_type_id") and value.impact_type_id:
144
145
  impact_type = ImpactType.objects.get(pk=value.impact_type_id)
145
146
  if impact_type:
146
147
  impact_descriptions += f"\u00A0\u00A0 :exclamation: {impact_type} - {value}\n"
147
148
  for line in str(value.description).splitlines():
148
- impact_descriptions += f"\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0 {line}\n"
149
+ # Skip empty lines or lines with only dashes/whitespace
150
+ if line.strip() and line.strip() != "-":
151
+ impact_descriptions += f"\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0 • {line}\n"
149
152
  return impact_descriptions
150
153
 
151
154
  def handle_modal_fn( # type: ignore
@@ -51,7 +51,7 @@ class UpdateStatusFormSlack(UpdateStatusForm):
51
51
  "label_from_instance": priority_label,
52
52
  },
53
53
  },
54
- "component": {
54
+ "incident_category": {
55
55
  "input": {
56
56
  "placeholder": "Select affected issue category",
57
57
  }
@@ -73,7 +73,7 @@ class UpdateStatusModal(ModalForm[UpdateStatusFormSlack]):
73
73
  initial={
74
74
  "status": incident.status,
75
75
  "priority": incident.priority,
76
- "component": incident.component,
76
+ "incident_category": incident.incident_category,
77
77
  }
78
78
  ).slack_blocks()
79
79
  blocks.append(slack_block_separator())
@@ -97,7 +97,7 @@ class UpdateStatusModal(ModalForm[UpdateStatusFormSlack]):
97
97
  "initial": {
98
98
  "status": incident.status,
99
99
  "priority": incident.priority,
100
- "component": incident.component,
100
+ "incident_category": incident.incident_category,
101
101
  }
102
102
  },
103
103
  )
@@ -109,7 +109,7 @@ class UpdateStatusModal(ModalForm[UpdateStatusFormSlack]):
109
109
  return
110
110
  update_kwargs: dict[str, Any] = {}
111
111
  for changed_key in form.changed_data:
112
- if changed_key in {"component", "priority"}:
112
+ if changed_key in {"incident_category", "priority"}:
113
113
  update_kwargs[f"{changed_key}_id"] = form.cleaned_data[changed_key].id
114
114
  if changed_key in {"description", "title", "message", "status"}:
115
115
  update_kwargs[changed_key] = form.cleaned_data[changed_key]