firefighter-incident 0.0.14__py3-none-any.whl → 0.0.16__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.
- firefighter/_version.py +2 -2
- firefighter/api/serializers.py +9 -0
- firefighter/confluence/signals/incident_updated.py +2 -2
- firefighter/incidents/enums.py +22 -2
- firefighter/incidents/forms/closure_reason.py +45 -0
- firefighter/incidents/forms/unified_incident.py +406 -0
- firefighter/incidents/forms/update_status.py +87 -1
- firefighter/incidents/migrations/0027_add_closure_fields.py +40 -0
- firefighter/incidents/migrations/0028_add_closure_reason_constraint.py +33 -0
- firefighter/incidents/migrations/0029_add_custom_fields_to_incident.py +22 -0
- firefighter/incidents/models/incident.py +32 -5
- firefighter/incidents/static/css/main.min.css +1 -1
- firefighter/incidents/templates/layouts/partials/status_pill.html +1 -1
- firefighter/incidents/views/reports.py +3 -3
- firefighter/raid/apps.py +9 -26
- firefighter/raid/client.py +2 -2
- firefighter/raid/forms.py +75 -238
- firefighter/raid/signals/incident_created.py +38 -13
- firefighter/raid/signals/incident_updated.py +3 -2
- firefighter/slack/messages/slack_messages.py +19 -4
- firefighter/slack/rules.py +1 -1
- firefighter/slack/signals/create_incident_conversation.py +6 -0
- firefighter/slack/signals/incident_updated.py +7 -1
- firefighter/slack/views/modals/__init__.py +4 -0
- firefighter/slack/views/modals/base_modal/form_utils.py +63 -0
- firefighter/slack/views/modals/close.py +15 -2
- firefighter/slack/views/modals/closure_reason.py +193 -0
- firefighter/slack/views/modals/open.py +60 -13
- firefighter/slack/views/modals/opening/details/unified.py +203 -0
- firefighter/slack/views/modals/opening/select_impact.py +1 -1
- firefighter/slack/views/modals/opening/set_details.py +3 -2
- firefighter/slack/views/modals/postmortem.py +10 -2
- firefighter/slack/views/modals/update_status.py +28 -2
- firefighter/slack/views/modals/utils.py +51 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/RECORD +62 -38
- firefighter_tests/test_incidents/test_enums.py +100 -0
- firefighter_tests/test_incidents/test_forms/conftest.py +179 -0
- firefighter_tests/test_incidents/test_forms/test_closure_reason.py +91 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form.py +570 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form_integration.py +581 -0
- firefighter_tests/test_incidents/test_forms/test_unified_incident_form_p4_p5.py +410 -0
- firefighter_tests/test_incidents/test_forms/test_update_status_workflow.py +343 -0
- firefighter_tests/test_incidents/test_forms/test_workflow_transitions.py +167 -0
- firefighter_tests/test_incidents/test_models/test_incident_model.py +68 -0
- firefighter_tests/test_raid/conftest.py +154 -0
- firefighter_tests/test_raid/test_p1_p3_jira_fields.py +372 -0
- firefighter_tests/test_raid/test_raid_forms.py +10 -253
- firefighter_tests/test_raid/test_raid_signals.py +187 -0
- firefighter_tests/test_slack/messages/__init__.py +0 -0
- firefighter_tests/test_slack/messages/test_slack_messages.py +367 -0
- firefighter_tests/test_slack/views/modals/conftest.py +140 -0
- firefighter_tests/test_slack/views/modals/test_close.py +65 -3
- firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py +138 -0
- firefighter_tests/test_slack/views/modals/test_form_utils_multiple_choice.py +249 -0
- firefighter_tests/test_slack/views/modals/test_open.py +146 -2
- firefighter_tests/test_slack/views/modals/test_opening_unified.py +421 -0
- firefighter_tests/test_slack/views/modals/test_update_status.py +327 -3
- firefighter_tests/test_slack/views/modals/test_utils.py +135 -0
- firefighter/raid/views/open_normal.py +0 -139
- firefighter/slack/views/modals/opening/details/critical.py +0 -88
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.14.dist-info → firefighter_incident-0.0.16.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.
|
|
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.
|
|
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.
|
|
622
|
+
if status == IncidentStatus.MITIGATED:
|
|
596
623
|
IncidentUpdate.objects.update_or_create(
|
|
597
624
|
incident_id=self.id,
|
|
598
625
|
event_type="recovered",
|