firefighter-incident 0.0.14__py3-none-any.whl → 0.0.15__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 (63) hide show
  1. firefighter/_version.py +2 -2
  2. firefighter/api/serializers.py +9 -0
  3. firefighter/confluence/signals/incident_updated.py +2 -2
  4. firefighter/incidents/enums.py +22 -2
  5. firefighter/incidents/forms/closure_reason.py +45 -0
  6. firefighter/incidents/forms/unified_incident.py +406 -0
  7. firefighter/incidents/forms/update_status.py +87 -1
  8. firefighter/incidents/migrations/0027_add_closure_fields.py +40 -0
  9. firefighter/incidents/migrations/0028_add_closure_reason_constraint.py +33 -0
  10. firefighter/incidents/migrations/0029_add_custom_fields_to_incident.py +22 -0
  11. firefighter/incidents/models/incident.py +32 -5
  12. firefighter/incidents/static/css/main.min.css +1 -1
  13. firefighter/incidents/templates/layouts/partials/status_pill.html +1 -1
  14. firefighter/incidents/views/reports.py +3 -3
  15. firefighter/raid/apps.py +9 -26
  16. firefighter/raid/client.py +2 -2
  17. firefighter/raid/forms.py +75 -238
  18. firefighter/raid/signals/incident_created.py +38 -13
  19. firefighter/raid/signals/incident_updated.py +3 -2
  20. firefighter/slack/messages/slack_messages.py +19 -4
  21. firefighter/slack/rules.py +1 -1
  22. firefighter/slack/signals/create_incident_conversation.py +6 -0
  23. firefighter/slack/signals/incident_updated.py +7 -1
  24. firefighter/slack/views/modals/__init__.py +4 -0
  25. firefighter/slack/views/modals/base_modal/form_utils.py +63 -0
  26. firefighter/slack/views/modals/close.py +15 -2
  27. firefighter/slack/views/modals/closure_reason.py +193 -0
  28. firefighter/slack/views/modals/open.py +59 -12
  29. firefighter/slack/views/modals/opening/details/unified.py +203 -0
  30. firefighter/slack/views/modals/opening/set_details.py +3 -2
  31. firefighter/slack/views/modals/postmortem.py +10 -2
  32. firefighter/slack/views/modals/update_status.py +28 -2
  33. firefighter/slack/views/modals/utils.py +51 -0
  34. {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/METADATA +1 -1
  35. {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/RECORD +61 -37
  36. firefighter_tests/test_incidents/test_enums.py +100 -0
  37. firefighter_tests/test_incidents/test_forms/conftest.py +179 -0
  38. firefighter_tests/test_incidents/test_forms/test_closure_reason.py +91 -0
  39. firefighter_tests/test_incidents/test_forms/test_unified_incident_form.py +570 -0
  40. firefighter_tests/test_incidents/test_forms/test_unified_incident_form_integration.py +581 -0
  41. firefighter_tests/test_incidents/test_forms/test_unified_incident_form_p4_p5.py +410 -0
  42. firefighter_tests/test_incidents/test_forms/test_update_status_workflow.py +343 -0
  43. firefighter_tests/test_incidents/test_forms/test_workflow_transitions.py +167 -0
  44. firefighter_tests/test_incidents/test_models/test_incident_model.py +68 -0
  45. firefighter_tests/test_raid/conftest.py +154 -0
  46. firefighter_tests/test_raid/test_p1_p3_jira_fields.py +372 -0
  47. firefighter_tests/test_raid/test_raid_forms.py +10 -253
  48. firefighter_tests/test_raid/test_raid_signals.py +187 -0
  49. firefighter_tests/test_slack/messages/__init__.py +0 -0
  50. firefighter_tests/test_slack/messages/test_slack_messages.py +367 -0
  51. firefighter_tests/test_slack/views/modals/conftest.py +140 -0
  52. firefighter_tests/test_slack/views/modals/test_close.py +65 -3
  53. firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py +138 -0
  54. firefighter_tests/test_slack/views/modals/test_form_utils_multiple_choice.py +249 -0
  55. firefighter_tests/test_slack/views/modals/test_open.py +146 -2
  56. firefighter_tests/test_slack/views/modals/test_opening_unified.py +421 -0
  57. firefighter_tests/test_slack/views/modals/test_update_status.py +327 -3
  58. firefighter_tests/test_slack/views/modals/test_utils.py +135 -0
  59. firefighter/raid/views/open_normal.py +0 -139
  60. firefighter/slack/views/modals/opening/details/critical.py +0 -88
  61. {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/WHEEL +0 -0
  62. {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/entry_points.txt +0 -0
  63. {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,40 @@
1
+ # Generated by Django 4.2.23 on 2025-09-26 12:47
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
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="incident",
15
+ name="closure_reason",
16
+ field=models.CharField(
17
+ blank=True,
18
+ choices=[
19
+ ("resolved", "Resolved normally"),
20
+ ("duplicate", "Duplicate incident"),
21
+ ("false_positive", "False alarm - no actual issue"),
22
+ ("superseded", "Superseded by another incident"),
23
+ ("external", "External dependency/known issue"),
24
+ ("cancelled", "Cancelled - no longer relevant"),
25
+ ],
26
+ help_text="Reason for direct incident closure bypassing normal workflow",
27
+ max_length=50,
28
+ null=True,
29
+ ),
30
+ ),
31
+ migrations.AddField(
32
+ model_name="incident",
33
+ name="closure_reference",
34
+ field=models.CharField(
35
+ blank=True,
36
+ help_text="Reference incident ID or external link for closure context",
37
+ max_length=100,
38
+ ),
39
+ ),
40
+ ]
@@ -0,0 +1,33 @@
1
+ # Generated by Django 4.2.23 on 2025-09-26 12:47
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("incidents", "0027_add_closure_fields"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddConstraint(
14
+ model_name="incident",
15
+ constraint=models.CheckConstraint(
16
+ check=models.Q(
17
+ (
18
+ "closure_reason__in",
19
+ [
20
+ "resolved",
21
+ "duplicate",
22
+ "false_positive",
23
+ "superseded",
24
+ "external",
25
+ "cancelled",
26
+ None,
27
+ ],
28
+ )
29
+ ),
30
+ name="incidents_incident_closure_reason_valid",
31
+ ),
32
+ ),
33
+ ]
@@ -0,0 +1,22 @@
1
+ # Generated by Django 4.2.21 on 2025-10-13 13:55
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("incidents", "0028_add_closure_reason_constraint"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="incident",
15
+ name="custom_fields",
16
+ field=models.JSONField(
17
+ blank=True,
18
+ default=dict,
19
+ help_text="Custom fields for incident (zendesk_ticket_id, seller_contract_id, etc.)",
20
+ ),
21
+ ),
22
+ ]
@@ -29,7 +29,7 @@ from firefighter.firefighter.fields_forms_widgets import (
29
29
  GroupedCheckboxSelectMultiple,
30
30
  )
31
31
  from firefighter.incidents import signals
32
- from firefighter.incidents.enums import IncidentStatus
32
+ from firefighter.incidents.enums import ClosureReason, IncidentStatus
33
33
  from firefighter.incidents.models.environment import Environment
34
34
  from firefighter.incidents.models.group import Group
35
35
  from firefighter.incidents.models.incident_category import IncidentCategory
@@ -230,6 +230,24 @@ class Incident(models.Model):
230
230
  closed_at = models.DateTimeField(
231
231
  null=True, blank=True
232
232
  ) # XXX-ZOR make this an event
233
+ closure_reason = models.CharField(
234
+ max_length=50,
235
+ choices=ClosureReason.choices,
236
+ null=True,
237
+ blank=True,
238
+ help_text="Reason for direct incident closure bypassing normal workflow",
239
+ )
240
+ closure_reference = models.CharField(
241
+ max_length=100,
242
+ blank=True,
243
+ help_text="Reference incident ID or external link for closure context",
244
+ )
245
+
246
+ custom_fields = models.JSONField(
247
+ default=dict,
248
+ blank=True,
249
+ help_text="Custom fields for incident (zendesk_ticket_id, seller_contract_id, etc.)",
250
+ )
233
251
 
234
252
  # XXX-ZOR pick a more meaningful name. maybe 'hidden'
235
253
  # XXX-ZOR document intent and impl. status
@@ -268,7 +286,11 @@ class Incident(models.Model):
268
286
  models.CheckConstraint(
269
287
  name="%(app_label)s_%(class)s__status_valid",
270
288
  check=models.Q(_status__in=IncidentStatus.values),
271
- )
289
+ ),
290
+ models.CheckConstraint(
291
+ name="%(app_label)s_%(class)s_closure_reason_valid",
292
+ check=models.Q(closure_reason__in=[*ClosureReason.values, None]),
293
+ ),
272
294
  ]
273
295
 
274
296
  def __str__(self) -> str:
@@ -331,6 +353,11 @@ class Incident(models.Model):
331
353
  def can_be_closed(self) -> tuple[bool, list[tuple[str, str]]]:
332
354
  # XXX-ZOR we should use a proper FSM abstraction
333
355
  cant_closed_reasons: list[tuple[str, str]] = []
356
+
357
+ # Allow direct closure when closure_reason is provided (bypasses normal workflow)
358
+ if self.closure_reason:
359
+ return True, []
360
+
334
361
  if self.ignore:
335
362
  return True, []
336
363
  if self.needs_postmortem:
@@ -341,11 +368,11 @@ class Incident(models.Model):
341
368
  f"Incident is not in PostMortem status, and needs one because of its priority and environment ({self.priority.name}/{self.environment.value}).",
342
369
  )
343
370
  )
344
- elif self.status.value < IncidentStatus.FIXED:
371
+ elif self.status.value < IncidentStatus.MITIGATED:
345
372
  cant_closed_reasons.append(
346
373
  (
347
374
  "STATUS_NOT_MITIGATED",
348
- f"Incident is not in {IncidentStatus.FIXED.label} status (currently {self.status.label}).",
375
+ f"Incident is not in {IncidentStatus.MITIGATED.label} status (currently {self.status.label}).",
349
376
  )
350
377
  )
351
378
  missing_milestones = self.missing_milestones()
@@ -592,7 +619,7 @@ class Incident(models.Model):
592
619
  )
593
620
  incident_update.save()
594
621
 
595
- if status == IncidentStatus.FIXED:
622
+ if status == IncidentStatus.MITIGATED:
596
623
  IncidentUpdate.objects.update_or_create(
597
624
  incident_id=self.id,
598
625
  event_type="recovered",