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.
- firefighter/_version.py +16 -3
- firefighter/api/serializers.py +8 -8
- firefighter/api/urls.py +8 -1
- firefighter/api/views/_base.py +1 -1
- firefighter/api/views/components.py +5 -5
- firefighter/api/views/incidents.py +9 -9
- firefighter/firefighter/settings/components/raid.py +3 -0
- firefighter/incidents/admin.py +24 -24
- firefighter/incidents/factories.py +14 -5
- firefighter/incidents/forms/close_incident.py +4 -4
- firefighter/incidents/forms/create_incident.py +4 -4
- firefighter/incidents/forms/update_status.py +4 -4
- firefighter/incidents/menus.py +2 -2
- firefighter/incidents/migrations/0005_enable_from_p1_to_p5_priority.py +7 -5
- firefighter/incidents/migrations/0009_update_sla.py +7 -5
- firefighter/incidents/migrations/0020_create_incident_category_model.py +64 -0
- firefighter/incidents/migrations/0021_copy_component_data_to_incident_category.py +57 -0
- firefighter/incidents/migrations/0022_add_incident_category_fields.py +34 -0
- firefighter/incidents/migrations/0023_populate_incident_category_references.py +57 -0
- firefighter/incidents/migrations/0024_remove_component_fields_and_model.py +26 -0
- firefighter/incidents/migrations/0025_make_incident_category_required.py +24 -0
- firefighter/incidents/migrations/0026_alter_incidentcategory_options_and_more.py +39 -0
- firefighter/incidents/models/__init__.py +1 -1
- firefighter/incidents/models/group.py +1 -1
- firefighter/incidents/models/incident.py +15 -15
- firefighter/incidents/models/{component.py → incident_category.py} +30 -29
- firefighter/incidents/models/incident_update.py +3 -3
- firefighter/incidents/tables.py +9 -9
- firefighter/incidents/templates/layouts/partials/incident_card.html +1 -1
- firefighter/incidents/templates/layouts/partials/incident_timeline.html +2 -2
- firefighter/incidents/templates/pages/{component_detail.html → incident_category_detail.html} +13 -13
- firefighter/incidents/templates/pages/{component_list.html → incident_category_list.html} +2 -2
- firefighter/incidents/templates/pages/incident_detail.html +3 -3
- firefighter/incidents/urls.py +6 -6
- firefighter/incidents/views/components/details.py +9 -9
- firefighter/incidents/views/components/list.py +9 -9
- firefighter/incidents/views/reports.py +2 -2
- firefighter/incidents/views/users/details.py +2 -2
- firefighter/incidents/views/views.py +7 -7
- firefighter/jira_app/client.py +1 -1
- firefighter/logging/custom_json_formatter.py +2 -1
- firefighter/pagerduty/tasks/trigger_oncall.py +1 -1
- firefighter/raid/admin.py +0 -11
- firefighter/raid/client.py +3 -3
- firefighter/raid/forms.py +53 -19
- firefighter/raid/migrations/0003_delete_raidarea.py +16 -0
- firefighter/raid/models.py +2 -21
- firefighter/raid/serializers.py +5 -4
- firefighter/raid/service.py +29 -27
- firefighter/raid/signals/incident_created.py +4 -2
- firefighter/raid/utils.py +1 -1
- firefighter/raid/views/__init__.py +1 -1
- firefighter/raid/views/open_normal.py +2 -2
- firefighter/slack/admin.py +8 -8
- firefighter/slack/management/commands/switch_test_users.py +272 -0
- firefighter/slack/messages/slack_messages.py +5 -5
- firefighter/slack/migrations/0005_add_incident_categories_fields.py +33 -0
- firefighter/slack/migrations/0006_copy_components_to_incident_categories.py +57 -0
- firefighter/slack/migrations/0007_remove_components_fields.py +22 -0
- firefighter/slack/migrations/0008_alter_conversation_incident_categories_and_more.py +33 -0
- firefighter/slack/models/conversation.py +3 -3
- firefighter/slack/models/incident_channel.py +1 -1
- firefighter/slack/models/user.py +1 -1
- firefighter/slack/models/user_group.py +3 -3
- firefighter/slack/rules.py +1 -1
- firefighter/slack/signals/get_users.py +2 -2
- firefighter/slack/signals/incident_updated.py +1 -1
- firefighter/slack/utils.py +2 -2
- firefighter/slack/views/events/home.py +2 -2
- firefighter/slack/views/modals/base_modal/form_utils.py +15 -0
- firefighter/slack/views/modals/close.py +3 -3
- firefighter/slack/views/modals/open.py +25 -1
- firefighter/slack/views/modals/opening/check_current_incidents.py +2 -2
- firefighter/slack/views/modals/opening/details/critical.py +1 -1
- firefighter/slack/views/modals/opening/select_impact.py +5 -2
- firefighter/slack/views/modals/update_status.py +4 -4
- firefighter_fixtures/incidents/{components.json → incident_categories.json} +52 -52
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/METADATA +2 -2
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/RECORD +98 -77
- firefighter_tests/conftest.py +4 -5
- firefighter_tests/test_api/test_api_landbot.py +1 -1
- firefighter_tests/test_firefighter/test_sso.py +146 -0
- firefighter_tests/test_incidents/test_forms/test_form_utils.py +15 -15
- firefighter_tests/test_incidents/test_incident_urls.py +3 -3
- firefighter_tests/test_incidents/test_models/test_incident_category.py +165 -0
- firefighter_tests/test_incidents/test_models/test_incident_model.py +2 -2
- firefighter_tests/test_raid/test_priority_mapping.py +267 -0
- firefighter_tests/test_raid/test_raid_client.py +580 -0
- firefighter_tests/test_raid/test_raid_forms.py +795 -0
- firefighter_tests/test_raid/test_raid_models.py +185 -0
- firefighter_tests/test_raid/test_raid_serializers.py +507 -0
- firefighter_tests/test_raid/test_raid_service.py +442 -0
- firefighter_tests/test_raid/test_raid_views.py +196 -0
- firefighter_tests/test_slack/views/modals/test_close.py +6 -6
- firefighter_tests/test_slack/views/modals/test_update_status.py +4 -4
- firefighter_fixtures/raid/area.json +0 -1
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.13.dist-info → firefighter_incident-0.0.14.dist-info}/licenses/LICENSE +0 -0
firefighter/_version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '0.0.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.14'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 14)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
firefighter/api/serializers.py
CHANGED
|
@@ -15,10 +15,10 @@ from rest_framework.fields import empty
|
|
|
15
15
|
from taggit.serializers import TaggitSerializer, TagListSerializerField
|
|
16
16
|
|
|
17
17
|
from firefighter.firefighter.utils import get_in
|
|
18
|
-
from firefighter.incidents.models.component import Component
|
|
19
18
|
from firefighter.incidents.models.environment import Environment
|
|
20
19
|
from firefighter.incidents.models.group import Group
|
|
21
20
|
from firefighter.incidents.models.incident import Incident
|
|
21
|
+
from firefighter.incidents.models.incident_category import IncidentCategory
|
|
22
22
|
from firefighter.incidents.models.incident_cost import IncidentCost
|
|
23
23
|
from firefighter.incidents.models.incident_cost_type import IncidentCostType
|
|
24
24
|
from firefighter.incidents.models.incident_membership import IncidentRole
|
|
@@ -157,11 +157,11 @@ class GroupSerializer(serializers.ModelSerializer[Group]):
|
|
|
157
157
|
fields = "__all__"
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
class
|
|
160
|
+
class IncidentCategorySerializer(serializers.ModelSerializer[IncidentCategory]):
|
|
161
161
|
group = GroupSerializer()
|
|
162
162
|
|
|
163
163
|
class Meta:
|
|
164
|
-
model =
|
|
164
|
+
model = IncidentCategory
|
|
165
165
|
fields = "__all__"
|
|
166
166
|
|
|
167
167
|
|
|
@@ -210,9 +210,9 @@ class IncidentSerializer(TaggitSerializer, serializers.ModelSerializer[Incident]
|
|
|
210
210
|
priority_id = serializers.PrimaryKeyRelatedField(
|
|
211
211
|
source="priority", queryset=Priority.objects.all(), write_only=True
|
|
212
212
|
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
source="
|
|
213
|
+
incident_category = IncidentCategorySerializer(read_only=True)
|
|
214
|
+
incident_category_id = serializers.PrimaryKeyRelatedField(
|
|
215
|
+
source="incident_category", queryset=IncidentCategory.objects.all(), write_only=True
|
|
216
216
|
)
|
|
217
217
|
|
|
218
218
|
status = serializers.SerializerMethodField()
|
|
@@ -274,14 +274,14 @@ class IncidentSerializer(TaggitSerializer, serializers.ModelSerializer[Incident]
|
|
|
274
274
|
"description",
|
|
275
275
|
"created_at",
|
|
276
276
|
"environment",
|
|
277
|
-
"
|
|
277
|
+
"incident_category",
|
|
278
278
|
"priority",
|
|
279
279
|
"status",
|
|
280
280
|
"slack_channel_name",
|
|
281
281
|
"status_page_url",
|
|
282
282
|
"status",
|
|
283
283
|
"environment_id",
|
|
284
|
-
"
|
|
284
|
+
"incident_category_id",
|
|
285
285
|
"priority_id",
|
|
286
286
|
"created_by_email",
|
|
287
287
|
"tags",
|
firefighter/api/urls.py
CHANGED
|
@@ -33,11 +33,18 @@ router.register(
|
|
|
33
33
|
views.severities.PriorityViewSet,
|
|
34
34
|
basename="priorities",
|
|
35
35
|
)
|
|
36
|
+
# Legacy endpoint for backward compatibility
|
|
36
37
|
router.register(
|
|
37
38
|
r"components",
|
|
38
|
-
views.components.
|
|
39
|
+
views.components.IncidentCategoryViewSet,
|
|
39
40
|
basename="components",
|
|
40
41
|
)
|
|
42
|
+
# New preferred endpoint
|
|
43
|
+
router.register(
|
|
44
|
+
r"incident-categories",
|
|
45
|
+
views.components.IncidentCategoryViewSet,
|
|
46
|
+
basename="incident-categories",
|
|
47
|
+
)
|
|
41
48
|
router.register(
|
|
42
49
|
r"groups",
|
|
43
50
|
views.groups.GroupViewSet,
|
firefighter/api/views/_base.py
CHANGED
|
@@ -20,7 +20,7 @@ T_co = TypeVar("T_co", bound=Model, covariant=True)
|
|
|
20
20
|
examples=[
|
|
21
21
|
OpenApiExample(
|
|
22
22
|
name="Comma separated list of fields",
|
|
23
|
-
value="id,name,
|
|
23
|
+
value="id,name,incident_category.name,incident_category.group.name",
|
|
24
24
|
request_only=True,
|
|
25
25
|
),
|
|
26
26
|
OpenApiExample(
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from firefighter.api.serializers import
|
|
3
|
+
from firefighter.api.serializers import IncidentCategorySerializer
|
|
4
4
|
from firefighter.api.views._base import ReadOnlyModelViewSet
|
|
5
|
-
from firefighter.incidents.models.
|
|
5
|
+
from firefighter.incidents.models.incident_category import IncidentCategory
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
9
|
-
queryset =
|
|
10
|
-
serializer_class =
|
|
8
|
+
class IncidentCategoryViewSet(ReadOnlyModelViewSet[IncidentCategory]):
|
|
9
|
+
queryset = IncidentCategory.objects.all().select_related("group")
|
|
10
|
+
serializer_class = IncidentCategorySerializer
|
|
@@ -44,7 +44,7 @@ class ProcessAfterResponse(Response):
|
|
|
44
44
|
examples=[
|
|
45
45
|
OpenApiExample(
|
|
46
46
|
name="Comma separated list of fields",
|
|
47
|
-
value="id,name,
|
|
47
|
+
value="id,name,incident_category.name,incident_category.group.name",
|
|
48
48
|
request_only=True,
|
|
49
49
|
),
|
|
50
50
|
OpenApiExample(
|
|
@@ -73,8 +73,8 @@ class IncidentViewSet(
|
|
|
73
73
|
Incident.objects.all()
|
|
74
74
|
.select_related(
|
|
75
75
|
"priority",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
76
|
+
"incident_category__group",
|
|
77
|
+
"incident_category",
|
|
78
78
|
"environment",
|
|
79
79
|
"conversation",
|
|
80
80
|
"created_by",
|
|
@@ -113,8 +113,8 @@ class IncidentViewSet(
|
|
|
113
113
|
"priority.name",
|
|
114
114
|
"title",
|
|
115
115
|
"description",
|
|
116
|
-
"
|
|
117
|
-
"
|
|
116
|
+
"incident_category.name",
|
|
117
|
+
"incident_category.group.name",
|
|
118
118
|
"created_at",
|
|
119
119
|
"slack_channel_name",
|
|
120
120
|
"status_page_url",
|
|
@@ -154,12 +154,12 @@ class IncidentViewSet(
|
|
|
154
154
|
OpenApiExample(
|
|
155
155
|
"Create an incident",
|
|
156
156
|
summary="Create an incident",
|
|
157
|
-
description="Create an incident, on INT, P4, with `Other`
|
|
157
|
+
description="Create an incident, on INT, P4, with `Other` incident category and John Doe as creator. All fields are required. The email must be a valid email of a ManoMano employee, that has a Slack account or has already used FireFighter before.",
|
|
158
158
|
value={
|
|
159
159
|
"title": "Title of the incident, limited to 128 characters.",
|
|
160
160
|
"description": "Longer description of the incident. No characters limit.",
|
|
161
161
|
"environment_id": "1b960430-995b-47e1-beab-23dbe3dbccbf",
|
|
162
|
-
"
|
|
162
|
+
"incident_category_id": "390a993a-d273-4db8-b7d6-190ab294961a",
|
|
163
163
|
"priority_id": "b814c9d2-48a8-4ac4-9c71-ff844e1b77f1",
|
|
164
164
|
"created_by_email": "john.doe@mycompany.com",
|
|
165
165
|
},
|
|
@@ -185,7 +185,7 @@ class IncidentViewSet(
|
|
|
185
185
|
"order": 3,
|
|
186
186
|
"default": False,
|
|
187
187
|
},
|
|
188
|
-
"
|
|
188
|
+
"incident_category": {
|
|
189
189
|
"id": "390a993a-d273-4db8-b7d6-190ab294961a",
|
|
190
190
|
"name": "Other",
|
|
191
191
|
"description": "",
|
|
@@ -241,7 +241,7 @@ class CreateIncidentViewSet(
|
|
|
241
241
|
viewsets.GenericViewSet[Incident],
|
|
242
242
|
):
|
|
243
243
|
queryset: QuerySet[Incident] = Incident.objects.all().select_related(
|
|
244
|
-
"priority", "
|
|
244
|
+
"priority", "incident_category__group", "incident_category", "environment", "conversation"
|
|
245
245
|
)
|
|
246
246
|
serializer_class = IncidentSerializer
|
|
247
247
|
filterset_class = IncidentFilterSet
|
|
@@ -20,3 +20,6 @@ if ENABLE_RAID:
|
|
|
20
20
|
|
|
21
21
|
RAID_TOOLBOX_URL: str = config("RAID_TOOLBOX_URL")
|
|
22
22
|
"Toolbox URL"
|
|
23
|
+
|
|
24
|
+
RAID_JIRA_INCIDENT_CATEGORY_FIELD: str = config("RAID_JIRA_INCIDENT_CATEGORY_FIELD", default="")
|
|
25
|
+
"Jira custom field ID for incident category (e.g. 'customfield_12345')"
|
firefighter/incidents/admin.py
CHANGED
|
@@ -19,10 +19,10 @@ from django.utils.translation import ngettext
|
|
|
19
19
|
from slack_sdk.errors import SlackApiError
|
|
20
20
|
|
|
21
21
|
from firefighter.incidents.models import (
|
|
22
|
-
Component,
|
|
23
22
|
Environment,
|
|
24
23
|
Group,
|
|
25
24
|
Incident,
|
|
25
|
+
IncidentCategory,
|
|
26
26
|
IncidentUpdate,
|
|
27
27
|
Severity,
|
|
28
28
|
User,
|
|
@@ -63,7 +63,7 @@ logger = logging.getLogger(__name__)
|
|
|
63
63
|
|
|
64
64
|
# Append inlines to these objects, from other modules
|
|
65
65
|
user_inlines: list[type[InlineModelAdmin[Any, User]]] = []
|
|
66
|
-
|
|
66
|
+
incident_category_inlines: list[type[InlineModelAdmin[Any, IncidentCategory]]] = []
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class IncidentCostInline(admin.TabularInline[IncidentCost, Incident]):
|
|
@@ -97,9 +97,9 @@ class IncidentMembershipInline(admin.StackedInline[IncidentMembership, Incident]
|
|
|
97
97
|
incident_inlines: MutableSequence[type[InlineModelAdmin[Any, Incident]]] = []
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
@admin.register(
|
|
101
|
-
class
|
|
102
|
-
model =
|
|
100
|
+
@admin.register(IncidentCategory)
|
|
101
|
+
class IncidentCategoryAdmin(admin.ModelAdmin[IncidentCategory]):
|
|
102
|
+
model = IncidentCategory
|
|
103
103
|
list_display = ["name", "group", "order", "private", "deploy_warning"]
|
|
104
104
|
list_editable = ["order", "group", "private"]
|
|
105
105
|
list_display_links = ["name"]
|
|
@@ -111,7 +111,7 @@ class ComponentAdmin(admin.ModelAdmin[Component]):
|
|
|
111
111
|
"created_at",
|
|
112
112
|
"updated_at",
|
|
113
113
|
]
|
|
114
|
-
inlines =
|
|
114
|
+
inlines = incident_category_inlines
|
|
115
115
|
|
|
116
116
|
fieldsets = (
|
|
117
117
|
(
|
|
@@ -132,8 +132,8 @@ class ComponentAdmin(admin.ModelAdmin[Component]):
|
|
|
132
132
|
)
|
|
133
133
|
|
|
134
134
|
|
|
135
|
-
class
|
|
136
|
-
model =
|
|
135
|
+
class IncidentCategoryInline(admin.StackedInline[IncidentCategory, IncidentCategory]):
|
|
136
|
+
model = IncidentCategory
|
|
137
137
|
fields = ["name", "order"]
|
|
138
138
|
|
|
139
139
|
|
|
@@ -218,7 +218,7 @@ class IncidentUpdateInline(admin.StackedInline[IncidentUpdate, Incident]):
|
|
|
218
218
|
"_status",
|
|
219
219
|
"priority",
|
|
220
220
|
"message",
|
|
221
|
-
"
|
|
221
|
+
"incident_category",
|
|
222
222
|
"created_by",
|
|
223
223
|
]
|
|
224
224
|
extra = 0
|
|
@@ -226,7 +226,7 @@ class IncidentUpdateInline(admin.StackedInline[IncidentUpdate, Incident]):
|
|
|
226
226
|
"priority",
|
|
227
227
|
"message",
|
|
228
228
|
"_status",
|
|
229
|
-
"
|
|
229
|
+
"incident_category",
|
|
230
230
|
"created_by",
|
|
231
231
|
"commander",
|
|
232
232
|
"communication_lead",
|
|
@@ -239,11 +239,11 @@ class IncidentUpdateInline(admin.StackedInline[IncidentUpdate, Incident]):
|
|
|
239
239
|
.get_queryset(request)
|
|
240
240
|
.select_related(
|
|
241
241
|
"priority",
|
|
242
|
-
"
|
|
242
|
+
"incident_category",
|
|
243
243
|
"incident",
|
|
244
244
|
"incident__priority",
|
|
245
|
-
"
|
|
246
|
-
"
|
|
245
|
+
"incident__incident_category",
|
|
246
|
+
"incident_category__group",
|
|
247
247
|
"created_by",
|
|
248
248
|
"commander",
|
|
249
249
|
"communication_lead",
|
|
@@ -398,7 +398,7 @@ class IncidentAdmin(admin.ModelAdmin[Incident]):
|
|
|
398
398
|
# Return None to display the change list page again.
|
|
399
399
|
|
|
400
400
|
date_hierarchy = "created_at"
|
|
401
|
-
autocomplete_fields = ["created_by", "
|
|
401
|
+
autocomplete_fields = ["created_by", "incident_category"]
|
|
402
402
|
actions: list[_ActionCallable[Any, Incident]] = [
|
|
403
403
|
compute_metrics,
|
|
404
404
|
compute_and_purge_metrics,
|
|
@@ -410,14 +410,14 @@ class IncidentAdmin(admin.ModelAdmin[Incident]):
|
|
|
410
410
|
"short_description",
|
|
411
411
|
"_status",
|
|
412
412
|
"priority",
|
|
413
|
-
"
|
|
413
|
+
"incident_category",
|
|
414
414
|
"environment",
|
|
415
415
|
"created_at",
|
|
416
416
|
"updated_at",
|
|
417
417
|
]
|
|
418
418
|
|
|
419
419
|
list_display_links = ["id", "title"]
|
|
420
|
-
list_filter = ("_status", "priority", "
|
|
420
|
+
list_filter = ("_status", "priority", "incident_category", "environment")
|
|
421
421
|
readonly_fields = (
|
|
422
422
|
"created_at",
|
|
423
423
|
"updated_at",
|
|
@@ -429,7 +429,7 @@ class IncidentAdmin(admin.ModelAdmin[Incident]):
|
|
|
429
429
|
|
|
430
430
|
list_select_related = (
|
|
431
431
|
"priority",
|
|
432
|
-
"
|
|
432
|
+
"incident_category__group",
|
|
433
433
|
"environment",
|
|
434
434
|
)
|
|
435
435
|
list_max_show_all = 1000
|
|
@@ -450,7 +450,7 @@ class IncidentAdmin(admin.ModelAdmin[Incident]):
|
|
|
450
450
|
)
|
|
451
451
|
},
|
|
452
452
|
),
|
|
453
|
-
(_("Relations"), {"fields": ("priority", "
|
|
453
|
+
(_("Relations"), {"fields": ("priority", "incident_category", "environment")}),
|
|
454
454
|
(
|
|
455
455
|
_("User Roles"),
|
|
456
456
|
{
|
|
@@ -506,10 +506,10 @@ class IncidentAdmin(admin.ModelAdmin[Incident]):
|
|
|
506
506
|
def _get_select_related(self) -> list[str]:
|
|
507
507
|
select_related = [
|
|
508
508
|
"priority",
|
|
509
|
-
"
|
|
509
|
+
"incident_category__group",
|
|
510
510
|
"environment",
|
|
511
511
|
"conversation",
|
|
512
|
-
"
|
|
512
|
+
"incident_category",
|
|
513
513
|
"created_by",
|
|
514
514
|
]
|
|
515
515
|
if apps.apps.is_installed("firefighter.confluence"):
|
|
@@ -525,13 +525,13 @@ class IncidentUpdateAdmin(admin.ModelAdmin[IncidentUpdate]):
|
|
|
525
525
|
"_status",
|
|
526
526
|
"priority",
|
|
527
527
|
"message",
|
|
528
|
-
"
|
|
528
|
+
"incident_category",
|
|
529
529
|
"created_by",
|
|
530
530
|
]
|
|
531
531
|
list_display_links = ["_status"]
|
|
532
|
-
list_filter = ("_status", "priority", "
|
|
532
|
+
list_filter = ("_status", "priority", "incident_category", "event_type")
|
|
533
533
|
readonly_fields = ("created_at", "updated_at", "incident")
|
|
534
|
-
list_select_related = ("priority", "
|
|
534
|
+
list_select_related = ("priority", "incident_category", "incident", "created_by")
|
|
535
535
|
search_fields = ["description", "message"]
|
|
536
536
|
|
|
537
537
|
|
|
@@ -544,7 +544,7 @@ class GroupAdmin(admin.ModelAdmin[Group]):
|
|
|
544
544
|
]
|
|
545
545
|
ordering = ["order"]
|
|
546
546
|
list_display_links = ["name"]
|
|
547
|
-
inlines = [
|
|
547
|
+
inlines = [IncidentCategoryInline]
|
|
548
548
|
search_fields = ["name"]
|
|
549
549
|
|
|
550
550
|
|
|
@@ -11,10 +11,10 @@ from factory.fuzzy import FuzzyChoice, FuzzyDateTime, FuzzyInteger
|
|
|
11
11
|
|
|
12
12
|
from firefighter.incidents.enums import IncidentStatus
|
|
13
13
|
from firefighter.incidents.models import (
|
|
14
|
-
Component,
|
|
15
14
|
Environment,
|
|
16
15
|
Group,
|
|
17
16
|
Incident,
|
|
17
|
+
IncidentCategory,
|
|
18
18
|
Priority,
|
|
19
19
|
User,
|
|
20
20
|
)
|
|
@@ -38,7 +38,12 @@ class GroupFactory(DjangoModelFactory[Group]):
|
|
|
38
38
|
order = FuzzyInteger(100, 1000) # type: ignore[no-untyped-call]
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
class
|
|
41
|
+
class PriorityFactory(DjangoModelFactory[Priority]):
|
|
42
|
+
"""Factory for creating Priority instances in tests.
|
|
43
|
+
|
|
44
|
+
Creates Priority objects with random values. Use specific values in tests
|
|
45
|
+
when testing priority-specific behavior (e.g., P1-P5 JIRA mapping).
|
|
46
|
+
"""
|
|
42
47
|
class Meta:
|
|
43
48
|
model = Priority
|
|
44
49
|
|
|
@@ -47,6 +52,10 @@ class SeverityFactory(DjangoModelFactory[Priority]):
|
|
|
47
52
|
order = FuzzyInteger(100, 1000) # type: ignore[no-untyped-call]
|
|
48
53
|
|
|
49
54
|
|
|
55
|
+
# Legacy alias - will be removed when Severity model is removed
|
|
56
|
+
SeverityFactory = PriorityFactory
|
|
57
|
+
|
|
58
|
+
|
|
50
59
|
class EnvironmentFactory(DjangoModelFactory[Environment]):
|
|
51
60
|
class Meta:
|
|
52
61
|
model = Environment
|
|
@@ -56,9 +65,9 @@ class EnvironmentFactory(DjangoModelFactory[Environment]):
|
|
|
56
65
|
order = FuzzyInteger(100, 1000) # type: ignore[no-untyped-call]
|
|
57
66
|
|
|
58
67
|
|
|
59
|
-
class
|
|
68
|
+
class IncidentCategoryFactory(DjangoModelFactory[IncidentCategory]):
|
|
60
69
|
class Meta:
|
|
61
|
-
model =
|
|
70
|
+
model = IncidentCategory
|
|
62
71
|
|
|
63
72
|
name = Faker("text", max_nb_chars=30) # type: ignore[no-untyped-call]
|
|
64
73
|
description = Faker("text", max_nb_chars=50) # type: ignore[no-untyped-call]
|
|
@@ -86,7 +95,7 @@ class IncidentFactory(DjangoModelFactory[Incident]):
|
|
|
86
95
|
tzinfo=timezone.get_current_timezone(),
|
|
87
96
|
)
|
|
88
97
|
) # type: ignore[no-untyped-call]
|
|
89
|
-
|
|
98
|
+
incident_category = Iterator(IncidentCategory.objects.all()) # type: ignore[no-untyped-call]
|
|
90
99
|
priority = Iterator(Priority.objects.all()) # type: ignore[no-untyped-call]
|
|
91
100
|
environment = Iterator(Environment.objects.all()) # type: ignore[no-untyped-call]
|
|
92
101
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from django import forms
|
|
4
4
|
|
|
5
5
|
from firefighter.incidents.forms.utils import GroupedModelChoiceField
|
|
6
|
-
from firefighter.incidents.models import
|
|
6
|
+
from firefighter.incidents.models import IncidentCategory
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class CloseIncidentForm(forms.Form):
|
|
@@ -26,10 +26,10 @@ class CloseIncidentForm(forms.Form):
|
|
|
26
26
|
max_length=1200,
|
|
27
27
|
required=False,
|
|
28
28
|
)
|
|
29
|
-
|
|
29
|
+
incident_category = GroupedModelChoiceField(
|
|
30
30
|
choices_groupby="group",
|
|
31
|
-
label="
|
|
32
|
-
queryset=
|
|
31
|
+
label="Incident category",
|
|
32
|
+
queryset=IncidentCategory.objects.all()
|
|
33
33
|
.select_related("group")
|
|
34
34
|
.order_by(
|
|
35
35
|
"group__order",
|
|
@@ -6,7 +6,7 @@ from django import forms
|
|
|
6
6
|
|
|
7
7
|
from firefighter.incidents.forms.select_impact import SelectImpactForm
|
|
8
8
|
from firefighter.incidents.forms.utils import GroupedModelChoiceField
|
|
9
|
-
from firefighter.incidents.models import
|
|
9
|
+
from firefighter.incidents.models import Environment, IncidentCategory, Priority
|
|
10
10
|
from firefighter.incidents.models.incident import Incident
|
|
11
11
|
from firefighter.incidents.signals import create_incident_conversation
|
|
12
12
|
|
|
@@ -52,11 +52,11 @@ class CreateIncidentForm(CreateIncidentFormBase):
|
|
|
52
52
|
max_length=1200,
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
incident_category = GroupedModelChoiceField(
|
|
56
56
|
choices_groupby="group",
|
|
57
|
-
label="
|
|
57
|
+
label="Incident category",
|
|
58
58
|
queryset=(
|
|
59
|
-
|
|
59
|
+
IncidentCategory.objects.all()
|
|
60
60
|
.select_related("group")
|
|
61
61
|
.order_by(
|
|
62
62
|
"group__order",
|
|
@@ -4,7 +4,7 @@ from django import forms
|
|
|
4
4
|
|
|
5
5
|
from firefighter.incidents.enums import IncidentStatus
|
|
6
6
|
from firefighter.incidents.forms.utils import EnumChoiceField, GroupedModelChoiceField
|
|
7
|
-
from firefighter.incidents.models import
|
|
7
|
+
from firefighter.incidents.models import IncidentCategory, Priority
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class UpdateStatusForm(forms.Form):
|
|
@@ -24,10 +24,10 @@ class UpdateStatusForm(forms.Form):
|
|
|
24
24
|
label="Priority",
|
|
25
25
|
queryset=Priority.objects.filter(enabled_update=True),
|
|
26
26
|
)
|
|
27
|
-
|
|
27
|
+
incident_category = GroupedModelChoiceField(
|
|
28
28
|
choices_groupby="group",
|
|
29
|
-
label="
|
|
30
|
-
queryset=
|
|
29
|
+
label="Incident category",
|
|
30
|
+
queryset=IncidentCategory.objects.all()
|
|
31
31
|
.select_related("group")
|
|
32
32
|
.order_by(
|
|
33
33
|
"group__order",
|
firefighter/incidents/menus.py
CHANGED
|
@@ -50,8 +50,8 @@ def setup_navbar_menu() -> None:
|
|
|
50
50
|
reverse("incidents:incident-statistics"),
|
|
51
51
|
),
|
|
52
52
|
MenuItem(
|
|
53
|
-
"
|
|
54
|
-
reverse("incidents:
|
|
53
|
+
"Incident categories",
|
|
54
|
+
reverse("incidents:incident-category-list"),
|
|
55
55
|
),
|
|
56
56
|
]
|
|
57
57
|
Menu.add_item(
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
1
3
|
from django.db import migrations
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
def update_priority_settings(apps, schema_editor):
|
|
5
7
|
Priority = apps.get_model("incidents", "Priority")
|
|
6
8
|
priorities_to_update = {
|
|
7
|
-
"P1": (
|
|
8
|
-
"P2": (
|
|
9
|
-
"P3": (
|
|
10
|
-
"P4": (
|
|
11
|
-
"P5": (
|
|
9
|
+
"P1": (timedelta(hours=1), True, True),
|
|
10
|
+
"P2": (timedelta(hours=4), True, True),
|
|
11
|
+
"P3": (timedelta(days=1), True, True),
|
|
12
|
+
"P4": (timedelta(days=5), True, True),
|
|
13
|
+
"P5": (timedelta(days=10), True, True),
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
for name, (sla, enabled_create, enabled_update) in priorities_to_update.items():
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
1
3
|
from django.db import migrations
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
def update_priority_settings(apps, schema_editor):
|
|
5
7
|
Priority = apps.get_model("incidents", "Priority")
|
|
6
8
|
priorities_to_update = {
|
|
7
|
-
"P1": (
|
|
8
|
-
"P2": (
|
|
9
|
-
"P3": (
|
|
10
|
-
"P4": (
|
|
11
|
-
"P5": (
|
|
9
|
+
"P1": (timedelta(hours=2), True, True),
|
|
10
|
+
"P2": (timedelta(hours=4), True, True),
|
|
11
|
+
"P3": (timedelta(days=1), True, True),
|
|
12
|
+
"P4": (timedelta(days=5), True, True),
|
|
13
|
+
"P5": (timedelta(days=10), True, True),
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
for name, (sla, enabled_create, enabled_update) in priorities_to_update.items():
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Generated manually on 2025-08-19 - Step 1: Create IncidentCategory model
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
("incidents", "0019_set_security_components_private"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.CreateModel(
|
|
16
|
+
name="IncidentCategory",
|
|
17
|
+
fields=[
|
|
18
|
+
(
|
|
19
|
+
"id",
|
|
20
|
+
models.UUIDField(
|
|
21
|
+
default=uuid.uuid4,
|
|
22
|
+
editable=False,
|
|
23
|
+
primary_key=True,
|
|
24
|
+
serialize=False,
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
("name", models.CharField(max_length=128)),
|
|
28
|
+
("description", models.TextField(blank=True)),
|
|
29
|
+
(
|
|
30
|
+
"order",
|
|
31
|
+
models.IntegerField(
|
|
32
|
+
default=0,
|
|
33
|
+
help_text="Order of the incident category in the list. Should be unique per `Group`.",
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
(
|
|
37
|
+
"private",
|
|
38
|
+
models.BooleanField(
|
|
39
|
+
default=False,
|
|
40
|
+
help_text="If true, incident created with this incident category won't be communicated, and conversations will be made private. This is useful for sensitive incident categories. In the future, private incidents may be visible only to its members.",
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
(
|
|
44
|
+
"deploy_warning",
|
|
45
|
+
models.BooleanField(
|
|
46
|
+
default=True,
|
|
47
|
+
help_text="If true, a warning will be sent when creating an incident of high severity with this incident category.",
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
51
|
+
("updated_at", models.DateTimeField(auto_now=True)),
|
|
52
|
+
(
|
|
53
|
+
"group",
|
|
54
|
+
models.ForeignKey(
|
|
55
|
+
on_delete=models.PROTECT,
|
|
56
|
+
to="incidents.group"
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
],
|
|
60
|
+
options={
|
|
61
|
+
"ordering": ["order"],
|
|
62
|
+
},
|
|
63
|
+
),
|
|
64
|
+
]
|