firefighter-incident 0.0.26__py3-none-any.whl → 0.0.28__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/confluence/models.py +16 -1
- firefighter/incidents/management/__init__.py +1 -0
- firefighter/incidents/management/commands/__init__.py +1 -0
- firefighter/incidents/management/commands/backdate_incident_mitigated.py +94 -0
- firefighter/incidents/management/commands/test_postmortem_reminders.py +113 -0
- firefighter/incidents/migrations/0030_add_mitigated_at_field.py +22 -0
- firefighter/incidents/models/incident.py +43 -8
- firefighter/jira_app/service_postmortem.py +13 -0
- firefighter/jira_app/signals/postmortem_created.py +108 -46
- firefighter/jira_app/templates/jira/postmortem/impact.txt +9 -4
- firefighter/slack/messages/slack_messages.py +234 -18
- firefighter/slack/migrations/0009_add_postmortem_reminder_periodic_task.py +60 -0
- firefighter/slack/rules.py +22 -0
- firefighter/slack/tasks/send_postmortem_reminders.py +127 -0
- firefighter/slack/views/modals/close.py +113 -3
- firefighter/slack/views/modals/closure_reason.py +39 -15
- firefighter/slack/views/modals/postmortem.py +75 -7
- firefighter/slack/views/modals/update_status.py +4 -4
- {firefighter_incident-0.0.26.dist-info → firefighter_incident-0.0.28.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.26.dist-info → firefighter_incident-0.0.28.dist-info}/RECORD +32 -24
- firefighter_tests/test_incidents/test_incident_urls.py +4 -0
- firefighter_tests/test_incidents/test_models/test_incident_model.py +109 -1
- firefighter_tests/test_incidents/test_views/test_incident_detail_view.py +4 -0
- firefighter_tests/test_slack/messages/test_slack_messages.py +4 -0
- firefighter_tests/test_slack/views/modals/test_close.py +4 -0
- firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py +109 -26
- firefighter_tests/test_slack/views/modals/test_postmortem_modal.py +72 -0
- firefighter_tests/test_slack/views/modals/test_update_status.py +45 -51
- {firefighter_incident-0.0.26.dist-info → firefighter_incident-0.0.28.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.26.dist-info → firefighter_incident-0.0.28.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.26.dist-info → firefighter_incident-0.0.28.dist-info}/licenses/LICENSE +0 -0
|
@@ -115,6 +115,19 @@ class ClosureReasonModal(IncidentSelectableModalMixin, SlackModal):
|
|
|
115
115
|
self, ack: Ack, body: dict[str, Any], incident: Incident, user: User
|
|
116
116
|
) -> bool | None:
|
|
117
117
|
"""Handle the closure reason modal submission."""
|
|
118
|
+
# Extract form values up-front so validation can account for the submitted reason
|
|
119
|
+
state_values = body["view"]["state"]["values"]
|
|
120
|
+
closure_reason = state_values["closure_reason"]["select_closure_reason"][
|
|
121
|
+
"selected_option"
|
|
122
|
+
]["value"]
|
|
123
|
+
closure_reference = (
|
|
124
|
+
state_values["closure_reference"]["input_closure_reference"].get(
|
|
125
|
+
"value", ""
|
|
126
|
+
)
|
|
127
|
+
or ""
|
|
128
|
+
)
|
|
129
|
+
message = state_values["closure_message"]["input_closure_message"]["value"]
|
|
130
|
+
|
|
118
131
|
# For early closure (OPEN/INVESTIGATING), we bypass normal workflow checks
|
|
119
132
|
# For normal closure (MITIGATED/POST_MORTEM), we must validate key events
|
|
120
133
|
current_status = incident.status
|
|
@@ -134,22 +147,31 @@ class ClosureReasonModal(IncidentSelectableModalMixin, SlackModal):
|
|
|
134
147
|
}
|
|
135
148
|
)
|
|
136
149
|
return False
|
|
150
|
+
else:
|
|
151
|
+
# Early closure path - validate with the submitted closure reason
|
|
152
|
+
# Temporarily inject the submitted closure_reason so early-closure bypass applies
|
|
153
|
+
original_closure_reason = incident.closure_reason
|
|
154
|
+
incident.closure_reason = closure_reason
|
|
155
|
+
try:
|
|
156
|
+
can_close, reasons = incident.can_be_closed
|
|
157
|
+
finally:
|
|
158
|
+
incident.closure_reason = original_closure_reason
|
|
159
|
+
if not can_close:
|
|
160
|
+
# Build error message from reasons
|
|
161
|
+
error_messages = [reason[1] for reason in reasons]
|
|
162
|
+
error_text = "\n".join([f"• {msg}" for msg in error_messages])
|
|
163
|
+
ack(
|
|
164
|
+
response_action="errors",
|
|
165
|
+
errors={
|
|
166
|
+
"closure_message": f"Cannot close this incident:\n{error_text}"
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
return False
|
|
137
170
|
|
|
138
171
|
# Clear ALL modals in the stack (not just this one)
|
|
139
172
|
# This ensures the underlying "Update Status" modal is also closed
|
|
140
173
|
ack(response_action="clear")
|
|
141
174
|
|
|
142
|
-
# Extract form values
|
|
143
|
-
state_values = body["view"]["state"]["values"]
|
|
144
|
-
closure_reason = state_values["closure_reason"]["select_closure_reason"][
|
|
145
|
-
"selected_option"
|
|
146
|
-
]["value"]
|
|
147
|
-
closure_reference = (
|
|
148
|
-
state_values["closure_reference"]["input_closure_reference"].get("value", "")
|
|
149
|
-
or ""
|
|
150
|
-
)
|
|
151
|
-
message = state_values["closure_message"]["input_closure_message"]["value"]
|
|
152
|
-
|
|
153
175
|
try:
|
|
154
176
|
# Update incident with closure fields
|
|
155
177
|
incident.closure_reason = closure_reason
|
|
@@ -165,9 +187,7 @@ class ClosureReasonModal(IncidentSelectableModalMixin, SlackModal):
|
|
|
165
187
|
)
|
|
166
188
|
|
|
167
189
|
except Exception:
|
|
168
|
-
logger.exception(
|
|
169
|
-
"Error closing incident #%s with reason", incident.id
|
|
170
|
-
)
|
|
190
|
+
logger.exception("Error closing incident #%s with reason", incident.id)
|
|
171
191
|
respond(
|
|
172
192
|
body=body,
|
|
173
193
|
text=f"❌ Failed to close incident #{incident.id}",
|
|
@@ -182,7 +202,11 @@ class ClosureReasonModal(IncidentSelectableModalMixin, SlackModal):
|
|
|
182
202
|
f"✅ Incident #{incident.id} has been closed.\n"
|
|
183
203
|
f"*Reason:* {ClosureReason(closure_reason).label}\n"
|
|
184
204
|
f"*Message:* {message}"
|
|
185
|
-
+ (
|
|
205
|
+
+ (
|
|
206
|
+
f"\n*Reference:* {closure_reference}"
|
|
207
|
+
if closure_reference
|
|
208
|
+
else ""
|
|
209
|
+
)
|
|
186
210
|
),
|
|
187
211
|
)
|
|
188
212
|
except SlackApiError as e:
|
|
@@ -3,10 +3,15 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
7
|
+
from slack_sdk.models.blocks.block_elements import ButtonElement
|
|
8
|
+
from slack_sdk.models.blocks.blocks import ActionsBlock, Block, SectionBlock
|
|
7
9
|
from slack_sdk.models.views import View
|
|
8
10
|
|
|
9
|
-
from firefighter.
|
|
11
|
+
from firefighter.confluence.models import PostMortemManager
|
|
12
|
+
from firefighter.incidents.models.incident import Incident
|
|
13
|
+
from firefighter.slack.utils import respond
|
|
14
|
+
from firefighter.slack.views.modals.base_modal.base import SlackModal, app
|
|
10
15
|
from firefighter.slack.views.modals.base_modal.mixins import (
|
|
11
16
|
IncidentSelectableModalMixin,
|
|
12
17
|
)
|
|
@@ -14,8 +19,6 @@ from firefighter.slack.views.modals.base_modal.mixins import (
|
|
|
14
19
|
if TYPE_CHECKING:
|
|
15
20
|
from slack_bolt.context.ack.ack import Ack
|
|
16
21
|
|
|
17
|
-
from firefighter.incidents.models.incident import Incident
|
|
18
|
-
|
|
19
22
|
|
|
20
23
|
logger = logging.getLogger(__name__)
|
|
21
24
|
|
|
@@ -32,8 +35,8 @@ class PostMortemModal(
|
|
|
32
35
|
blocks: list[Block] = []
|
|
33
36
|
|
|
34
37
|
# Check existing post-mortems
|
|
35
|
-
has_confluence =
|
|
36
|
-
has_jira =
|
|
38
|
+
has_confluence = _safe_has_relation(incident, "postmortem_for")
|
|
39
|
+
has_jira = _safe_has_relation(incident, "jira_postmortem_for")
|
|
37
40
|
|
|
38
41
|
if has_confluence or has_jira:
|
|
39
42
|
blocks.append(
|
|
@@ -53,12 +56,29 @@ class PostMortemModal(
|
|
|
53
56
|
text=f"• Jira: <{incident.jira_postmortem_for.issue_url}|{incident.jira_postmortem_for.jira_issue_key}>"
|
|
54
57
|
)
|
|
55
58
|
)
|
|
56
|
-
|
|
59
|
+
elif incident.needs_postmortem:
|
|
57
60
|
blocks.append(
|
|
58
61
|
SectionBlock(
|
|
59
62
|
text=f"Post-mortem for incident #{incident.id} will be automatically created when the incident reaches MITIGATED status."
|
|
60
63
|
)
|
|
61
64
|
)
|
|
65
|
+
else:
|
|
66
|
+
blocks.extend(
|
|
67
|
+
[
|
|
68
|
+
SectionBlock(
|
|
69
|
+
text="P3 incident post-mortem is not mandatory. You can still have one if you think is necessary by clicking on the button below."
|
|
70
|
+
),
|
|
71
|
+
ActionsBlock(
|
|
72
|
+
elements=[
|
|
73
|
+
ButtonElement(
|
|
74
|
+
text="Create post-mortem now",
|
|
75
|
+
action_id="incident_create_postmortem_now",
|
|
76
|
+
value=str(incident.id),
|
|
77
|
+
)
|
|
78
|
+
]
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
)
|
|
62
82
|
|
|
63
83
|
return View(
|
|
64
84
|
type="modal",
|
|
@@ -76,4 +96,52 @@ class PostMortemModal(
|
|
|
76
96
|
ack()
|
|
77
97
|
|
|
78
98
|
|
|
99
|
+
@app.action("incident_create_postmortem_now")
|
|
100
|
+
def handle_create_postmortem_action(ack: Ack, body: dict[str, Any]) -> None:
|
|
101
|
+
"""Create post-mortem(s) on demand from the modal (e.g. P3+ incidents)."""
|
|
102
|
+
ack()
|
|
103
|
+
|
|
104
|
+
incident_id = str(body.get("actions", [{}])[0].get("value", "")).strip()
|
|
105
|
+
try:
|
|
106
|
+
incident = Incident.objects.get(pk=incident_id)
|
|
107
|
+
except Incident.DoesNotExist:
|
|
108
|
+
respond(body, text=":x: Incident not found.")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
confluence_pm, jira_pm = PostMortemManager.create_postmortem_for_incident(
|
|
113
|
+
incident
|
|
114
|
+
)
|
|
115
|
+
except Exception:
|
|
116
|
+
logger.exception("Failed to create post-mortem for incident #%s", incident_id)
|
|
117
|
+
respond(body, text=":x: Failed to create post-mortem. Please try again.")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
created_targets: list[str] = []
|
|
121
|
+
if confluence_pm:
|
|
122
|
+
created_targets.append("Confluence")
|
|
123
|
+
if jira_pm:
|
|
124
|
+
created_targets.append("Jira")
|
|
125
|
+
|
|
126
|
+
if created_targets:
|
|
127
|
+
targets = " and ".join(created_targets)
|
|
128
|
+
respond(body, text=f":white_check_mark: {targets} post-mortem created.")
|
|
129
|
+
else:
|
|
130
|
+
respond(body, text=":warning: No post-mortem was created.")
|
|
131
|
+
|
|
132
|
+
|
|
79
133
|
modal_postmortem = PostMortemModal()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _safe_has_relation(instance: Incident, attr: str) -> bool:
|
|
137
|
+
"""Safely check if a reverse relation exists without triggering KeyError in cache.
|
|
138
|
+
|
|
139
|
+
Django's reverse OneToOne descriptor can raise KeyError when using hasattr
|
|
140
|
+
on unsaved or freshly created instances. We guard against that here.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
getattr(instance, attr)
|
|
144
|
+
except (AttributeError, ObjectDoesNotExist, KeyError):
|
|
145
|
+
return False
|
|
146
|
+
else:
|
|
147
|
+
return True
|
|
@@ -127,12 +127,12 @@ class UpdateStatusModal(ModalForm[UpdateStatusFormSlack]):
|
|
|
127
127
|
# Build error message from reasons
|
|
128
128
|
error_messages = [reason[1] for reason in reasons]
|
|
129
129
|
error_text = "\n".join([f"• {msg}" for msg in error_messages])
|
|
130
|
-
logger.warning(
|
|
130
|
+
logger.warning(
|
|
131
|
+
f"Cannot close incident #{incident.id} via Update Status: {error_text}"
|
|
132
|
+
)
|
|
131
133
|
ack(
|
|
132
134
|
response_action="errors",
|
|
133
|
-
errors={
|
|
134
|
-
"status": f"Cannot close this incident:\n{error_text}"
|
|
135
|
-
}
|
|
135
|
+
errors={"status": f"{error_text}"},
|
|
136
136
|
)
|
|
137
137
|
return
|
|
138
138
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firefighter-incident
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.28
|
|
4
4
|
Summary: Incident Management tool made for Slack using Django
|
|
5
5
|
Project-URL: Repository, https://github.com/ManoManoTech/firefighter-incident
|
|
6
6
|
Project-URL: Documentation, https://manomanotech.github.io/firefighter-incident/latest/
|
|
@@ -6,7 +6,7 @@ gunicorn.conf.py,sha256=vHsTGjaKOr8FDMp6fTKYTX4AtokmPgYvvt5Mr0Q6APc,273
|
|
|
6
6
|
main.py,sha256=CsbprHoOYhjCLpTJmq9Z_aRYFoFgWxoz2pDLuwm8Eqg,1558
|
|
7
7
|
manage.py,sha256=5ivHGD13C6nJ8QvltKsJ9T9akA5he8da70HLWaEP3k8,689
|
|
8
8
|
firefighter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
firefighter/_version.py,sha256=
|
|
9
|
+
firefighter/_version.py,sha256=rsZxmANBVfE-nN63y-ZZ_71Lkm6YP4u4TgVEBJV3mNM,706
|
|
10
10
|
firefighter/api/__init__.py,sha256=JQW0Bv6xwGqy7ioxx3h6UGMzkkJ4DntDpbvV1Ncgi8k,136
|
|
11
11
|
firefighter/api/admin.py,sha256=x9Ysy-GiYjb0rynmFdS9g56e6n24fkN0ouGy5QD9Yrc,4629
|
|
12
12
|
firefighter/api/apps.py,sha256=P5uU1_gMrDfzurdMbfqw1Bnb2uNKKcMq17WBPg2sLhc,204
|
|
@@ -55,7 +55,7 @@ firefighter/confluence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
55
55
|
firefighter/confluence/admin.py,sha256=aDXghuuLc7G_TLt-655M31smx-H6vkIgLtEmmNCA3lg,1490
|
|
56
56
|
firefighter/confluence/apps.py,sha256=vKswBwQL7L9e2JQwvRb7xy3myyE_GRldYX78jSY3XCM,406
|
|
57
57
|
firefighter/confluence/client.py,sha256=xjSsrsGPF75JANNvam2YgiUkztuXhOcs9pMmPbb7ymk,6361
|
|
58
|
-
firefighter/confluence/models.py,sha256=
|
|
58
|
+
firefighter/confluence/models.py,sha256=66RNfD-lRdixZtOo5pNW2e-LZboTRjdoH2h-R7ne-q0,9154
|
|
59
59
|
firefighter/confluence/serializers.py,sha256=CzuHVXIJNS47NCAJLXSTDOevtg5sf309XXEcWKQ1sAQ,258
|
|
60
60
|
firefighter/confluence/service.py,sha256=dOQXj0uDInEm25nvL6lXiSH4hQ5oC2VDyBd1zbEcZ5U,12296
|
|
61
61
|
firefighter/confluence/tables.py,sha256=ANEtFXzXyPK6E5FIrBC5XoQt5R3ZUY1DME_RbD1h_NE,732
|
|
@@ -147,6 +147,10 @@ firefighter/incidents/forms/update_key_events.py,sha256=1Xmnxe5OgZqLFS2HmMzQm3VG
|
|
|
147
147
|
firefighter/incidents/forms/update_roles.py,sha256=Q26UPfwAj-8N23RNZLQkvmHGnS1_j_X5KQWjJmPjMKY,3635
|
|
148
148
|
firefighter/incidents/forms/update_status.py,sha256=7GSno_EqD2Brd6wWcSb3zsP6nz8_mUTXXnl0QCRhv48,6682
|
|
149
149
|
firefighter/incidents/forms/utils.py,sha256=15e_dBebVd9SvX03DYd0FyZ8s0YpxyBlZfIzEZattwg,4267
|
|
150
|
+
firefighter/incidents/management/__init__.py,sha256=A2LtnedT5NvTcNAN5nXMkPwK56JBNLuptcyObvq7zcc,40
|
|
151
|
+
firefighter/incidents/management/commands/__init__.py,sha256=wc5DFEklUo-wB-6VAAmsV5UTbo5s3t936Lu61z4lojs,29
|
|
152
|
+
firefighter/incidents/management/commands/backdate_incident_mitigated.py,sha256=phAXH18TNvzA03o1XtJfRVeOPbrKp8wdsBMx-QGAIeo,3410
|
|
153
|
+
firefighter/incidents/management/commands/test_postmortem_reminders.py,sha256=Bx-AVhkSjkL6c2_Eh7mRHa7qOEtytDl9T2hJYxcBC-4,4233
|
|
150
154
|
firefighter/incidents/migrations/0001_initial_oss.py,sha256=OCrPbxf90h3NW9xolGGcsAryHKptD1TtKj5FucjBjg8,60311
|
|
151
155
|
firefighter/incidents/migrations/0002_alter_severity_name_alter_user_password_featureteam.py,sha256=YfIJhw_-Yqm8qrkbp01461bkcUr7v5Zy90oHjkY3bSA,1113
|
|
152
156
|
firefighter/incidents/migrations/0003_delete_featureteam.py,sha256=kH5UUSx3k5DtjR_goDxROdV0htCC2JZfBGwJpn-dEQs,336
|
|
@@ -176,12 +180,13 @@ firefighter/incidents/migrations/0026_alter_incidentcategory_options_and_more.py
|
|
|
176
180
|
firefighter/incidents/migrations/0027_add_closure_fields.py,sha256=MDWckXmjJNC2iVoFJD6IIwDmmqyeL1VG_pHR568JAtk,1344
|
|
177
181
|
firefighter/incidents/migrations/0028_add_closure_reason_constraint.py,sha256=z6FjCURDt9c-hyBeCvCKsbZOiuReYtbjtguIh3T6dnk,920
|
|
178
182
|
firefighter/incidents/migrations/0029_add_custom_fields_to_incident.py,sha256=G6DsnP5bM4Hy0s8IqXhLYzFKt3eumEsCnJfPIw5tcX4,567
|
|
183
|
+
firefighter/incidents/migrations/0030_add_mitigated_at_field.py,sha256=pELNJWbAuctv_dA-pdD3fsqg4qMm5f6upW9hFrjNLDI,546
|
|
179
184
|
firefighter/incidents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
180
185
|
firefighter/incidents/models/__init__.py,sha256=FLVyBwIdyxLdgSvXRAKC3fry9YwwqlqhitTIuG0vWrk,877
|
|
181
186
|
firefighter/incidents/models/environment.py,sha256=51txwua3dCrWZ1iSG3ZA8rbDn9c00pyMAZujl9gwE5c,827
|
|
182
187
|
firefighter/incidents/models/group.py,sha256=VrVL315VFUvKW69AZuRUBg1h0jZJvn8zWeMxMOWec1Y,700
|
|
183
188
|
firefighter/incidents/models/impact.py,sha256=D9NngMtg4XdDWnMgdVYaWCoUZ-fMXTvfL0eTEk9sc7M,4854
|
|
184
|
-
firefighter/incidents/models/incident.py,sha256=
|
|
189
|
+
firefighter/incidents/models/incident.py,sha256=4G4vLurh2bgMRo5eOuYgKWNRC8Xf_07cFf_UdpLPOOg,29638
|
|
185
190
|
firefighter/incidents/models/incident_category.py,sha256=g4OHv_XQhWcH6dvkqkyCgjlruo_1eih_CdtAPgPhaW4,7744
|
|
186
191
|
firefighter/incidents/models/incident_cost.py,sha256=juwOfJKRaNQpOHkRUCHShDDba0FU98YjRPkU4I0ofAU,1346
|
|
187
192
|
firefighter/incidents/models/incident_cost_type.py,sha256=wm8diry_VySJzIjC9M3Yavv2tYbvJgpN9UDb2gFRuH4,845
|
|
@@ -273,7 +278,7 @@ firefighter/jira_app/admin.py,sha256=ZHAAbhy0hm_DcklK59KMmid_ZiPn8n5V6g7cZCSNrpc
|
|
|
273
278
|
firefighter/jira_app/apps.py,sha256=T6vHrQuMZHJoTth-xjy3CbNfPv6DyXgcR3PSMju2JS4,504
|
|
274
279
|
firefighter/jira_app/client.py,sha256=qpMqNTjJUq5OqAxmwvVOE20uJe7kp737HSdsiqUu1G4,21982
|
|
275
280
|
firefighter/jira_app/models.py,sha256=2zKy5VaKkhiHYA8Dukz8g0NTG82Qy5UHAHY9eMv67NE,3097
|
|
276
|
-
firefighter/jira_app/service_postmortem.py,sha256=
|
|
281
|
+
firefighter/jira_app/service_postmortem.py,sha256=tqJN91vZPX5ISd_PAGWDebHK225eZvDvwhi7ONy_D-A,11325
|
|
277
282
|
firefighter/jira_app/types.py,sha256=Ukak1U1EhcH2jQPN-UoEL6AMZ-kzPsQ8c7FUr7GmahE,956
|
|
278
283
|
firefighter/jira_app/utils.py,sha256=3xuzr8viZCBm6j2J9oFzA4bUvVW8TN1DOdlpbruJ_TE,3443
|
|
279
284
|
firefighter/jira_app/management/__init__.py,sha256=wy4qMZb7_K-INwwGGEhMtEeI0XTLqgUw4P8_-VEnrEw,40
|
|
@@ -283,10 +288,10 @@ firefighter/jira_app/migrations/0002_add_jira_postmortem_model.py,sha256=oFSbYNc
|
|
|
283
288
|
firefighter/jira_app/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
284
289
|
firefighter/jira_app/signals/__init__.py,sha256=OpXFlbRgIrh73DGlUzQ6WUTedKsD5wYW9bxGMq_DnIs,325
|
|
285
290
|
firefighter/jira_app/signals/incident_key_events_updated.py,sha256=uaV3MON1QzeOZizzAwSdyktBwe2mWxHJeNSvy9MYc3k,3204
|
|
286
|
-
firefighter/jira_app/signals/postmortem_created.py,sha256=
|
|
291
|
+
firefighter/jira_app/signals/postmortem_created.py,sha256=S7sKbEgo5RroC5ji1OepAT3HMckwS120dpKeUteNaXA,8300
|
|
287
292
|
firefighter/jira_app/tasks/__init__.py,sha256=XLCPkolM6LwIUGv0MNbk_0lCuBHyzgRFHsE3vTRD5ds,86
|
|
288
293
|
firefighter/jira_app/tasks/sync_users_jira.py,sha256=sSSLsVCdzkPNRS6Gt8j0YwCTuoRqkJAJLxDBu7IElmM,1437
|
|
289
|
-
firefighter/jira_app/templates/jira/postmortem/impact.txt,sha256=
|
|
294
|
+
firefighter/jira_app/templates/jira/postmortem/impact.txt,sha256=WeF6uuwKjSER-CYqpLgH6p-wIkWgtVaeGKp8TsWSekQ,515
|
|
290
295
|
firefighter/jira_app/templates/jira/postmortem/incident_summary.txt,sha256=Bnias41O8TR2v0CAWpOoyRVVBAyx6vk2iICeQsLOuCs,392
|
|
291
296
|
firefighter/jira_app/templates/jira/postmortem/mitigation_actions.txt,sha256=7DWOcMhU0NiJugiUmEvSg1Z3ajW7IDZejprxt8urue0,242
|
|
292
297
|
firefighter/jira_app/templates/jira/postmortem/root_causes.txt,sha256=27DgQdrtHvHcju1llyYqU1jufegeHjJN_qvVkyECINM,291
|
|
@@ -345,7 +350,7 @@ firefighter/slack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
345
350
|
firefighter/slack/admin.py,sha256=pNJbA-szxUUrghxv_Z0BNezu6lULDzFcOu_K5i4m7Cs,13963
|
|
346
351
|
firefighter/slack/apps.py,sha256=gR0zWTtqT58tjPayBX22ZSzMkLiNpmoOvLShNvhJA6Q,664
|
|
347
352
|
firefighter/slack/factories.py,sha256=tnrUTbtgehCuBr24MtTyJ3uezKC6gJbOdHuYZ5JBoyU,3886
|
|
348
|
-
firefighter/slack/rules.py,sha256=
|
|
353
|
+
firefighter/slack/rules.py,sha256=Y-DYJ_1D13a4nQNESCbhalNe2nC9xVNpYtSeWTgDcYc,2374
|
|
349
354
|
firefighter/slack/slack_app.py,sha256=mvaH0hPFaNIUxEB7J0fy6y-PNPGsdPqjXFVmpTQ_hCo,4201
|
|
350
355
|
firefighter/slack/slack_incident_context.py,sha256=PjE7-w-pGFyV4faw8EMsEFp4RG_T251RhofmqrsDG7Q,7277
|
|
351
356
|
firefighter/slack/slack_templating.py,sha256=rWe8m1n648wizw08U_vLz8daRnp4zmkcWRqocIBpQj4,3841
|
|
@@ -359,7 +364,7 @@ firefighter/slack/management/commands/generate_manifest.py,sha256=zFWHAC7ioozcDd
|
|
|
359
364
|
firefighter/slack/management/commands/switch_test_users.py,sha256=2KTSvCBxsEvZa61J8p0r3huPNhwuytcj2J7IawwZWpQ,11064
|
|
360
365
|
firefighter/slack/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
361
366
|
firefighter/slack/messages/base.py,sha256=biH-YEAaldJ-OLHEs5ZjW-gtUYUbjOqxrAEflqV2XS0,4593
|
|
362
|
-
firefighter/slack/messages/slack_messages.py,sha256=
|
|
367
|
+
firefighter/slack/messages/slack_messages.py,sha256=9-zbxBuzDweKQZiwc2DMdiQcsnd23CGCi4YWhGgJx60,43713
|
|
363
368
|
firefighter/slack/migrations/0001_initial_oss.py,sha256=XmTPgq7zCME2xDwzRFoVi4OegSIG9eSKoyTNoW05Qtg,12933
|
|
364
369
|
firefighter/slack/migrations/0002_usergroup_tag.py,sha256=098tmGA81mT-R2uhb6uQfZ7gKiRG9bFhEwQ8rrp4SKM,583
|
|
365
370
|
firefighter/slack/migrations/0003_alter_usergroup_tag.py,sha256=ncH3KUWEPZHlbdcAtOJ0KGt5H6EX-cKspTGU3osrAhE,591
|
|
@@ -368,6 +373,7 @@ firefighter/slack/migrations/0005_add_incident_categories_fields.py,sha256=KMdKf
|
|
|
368
373
|
firefighter/slack/migrations/0006_copy_components_to_incident_categories.py,sha256=xUF7lLyWERux6SyIYHK2Uk1Yb4QLCGTaHW_KXVqX8n4,2478
|
|
369
374
|
firefighter/slack/migrations/0007_remove_components_fields.py,sha256=_GXmcpB3enpVBT1NZ-tGDlh16r_cM-JkH2gebrmwIOs,563
|
|
370
375
|
firefighter/slack/migrations/0008_alter_conversation_incident_categories_and_more.py,sha256=yzuAnunYvlF-wcYd7oe5h-kL5aOoawSXv_QGfmTFoBo,1034
|
|
376
|
+
firefighter/slack/migrations/0009_add_postmortem_reminder_periodic_task.py,sha256=Vze5TvhQExaF7-KymByzMZZE1wa0GHlWZXP1v1hub5w,2035
|
|
371
377
|
firefighter/slack/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
372
378
|
firefighter/slack/models/__init__.py,sha256=MGc4yuDnVhmAiHy1-5rjaLIfVv9JOup5arRutcUs8Ak,332
|
|
373
379
|
firefighter/slack/models/conversation.py,sha256=f7a0muD0lrpf8mIhF6E2gEhNsgwZFw9jlKIQppZhNL0,16227
|
|
@@ -388,6 +394,7 @@ firefighter/slack/tasks/__init__.py,sha256=28QxZkakyi9l7Ae83fQuzOS-9EaBiwuh_peUZ
|
|
|
388
394
|
firefighter/slack/tasks/fetch_conversations_members.py,sha256=lLQ491_l8HEJrjoDpD0AETqoFUFogkyMJ002hguA-Dg,5381
|
|
389
395
|
firefighter/slack/tasks/reminder_postmortem.py,sha256=mZvT4cpzmMhC6JrWhZb1uFvTJJrbkEKgcCy6liKHrKM,2322
|
|
390
396
|
firefighter/slack/tasks/send_message.py,sha256=N0FIE93bUnzbHdWkSWC-4-eLn737u6hHq8F8erMu8kI,810
|
|
397
|
+
firefighter/slack/tasks/send_postmortem_reminders.py,sha256=uyF9v8um2uxuYFyDSe2GhmG3iX0iAJrG1yE3LS0_NSo,4741
|
|
391
398
|
firefighter/slack/tasks/send_reminders.py,sha256=hy1Q_rG2RUQdXNYEYiLyLnT7rkG8PFOmxur62YCCDrk,4370
|
|
392
399
|
firefighter/slack/tasks/sync_users.py,sha256=T5ytYnZpcUqrh4sOklxWttsUk82C_2bwayg_fdcdg1g,2391
|
|
393
400
|
firefighter/slack/tasks/update_usergroups_members.py,sha256=W-rPt3r2c9UboVMNiyQFvi_W7XWrR8ireVBWcCJMj5A,4642
|
|
@@ -410,20 +417,20 @@ firefighter/slack/views/events/message.py,sha256=c8tvo0btOUu_5Bc83oiO3IQbaEyoRiU
|
|
|
410
417
|
firefighter/slack/views/events/message_deleted.py,sha256=tyA1-sAlG9ImcKIhqSn6EgujHmbvj4Uw2QzQ4JH4QwI,747
|
|
411
418
|
firefighter/slack/views/events/reaction_added.py,sha256=AipwBnrU5B35D97YIZCXdSW8W7-9QTIIQqUcrLTLQ5c,4241
|
|
412
419
|
firefighter/slack/views/modals/__init__.py,sha256=U9PapAIlpuYqBonOUmBGWT8_HjQa35ilMQJXGaFLgd0,1945
|
|
413
|
-
firefighter/slack/views/modals/close.py,sha256=
|
|
414
|
-
firefighter/slack/views/modals/closure_reason.py,sha256=
|
|
420
|
+
firefighter/slack/views/modals/close.py,sha256=eTT1IVEMXUrdUXsu5VSmC6-cGUsOYUGG7CN-3yAnuDo,17482
|
|
421
|
+
firefighter/slack/views/modals/closure_reason.py,sha256=qgjg6x5JBh5ggR6Z6aVhpnc3k8En38UxKtFb5RkTU4U,9016
|
|
415
422
|
firefighter/slack/views/modals/downgrade_workflow.py,sha256=cRWsm3DmKRRI1-Jpjprb5xeY2U7HvRo6eZlUbGuzr1A,3192
|
|
416
423
|
firefighter/slack/views/modals/edit.py,sha256=1N0OBSxsDuN6lJoH-djbEljy7f0LcDEpJF-U5YoEFXA,5895
|
|
417
424
|
firefighter/slack/views/modals/key_event_message.py,sha256=C6yhQLQ6jBuhIr-YAoAyt-qZKu0V6nJMGZ_t3DLtUbo,5943
|
|
418
425
|
firefighter/slack/views/modals/open.py,sha256=YIxpo8_C4cWCy_pQ3YRWl7NMyLmjqNjggTQINTBW6mo,29189
|
|
419
|
-
firefighter/slack/views/modals/postmortem.py,sha256=
|
|
426
|
+
firefighter/slack/views/modals/postmortem.py,sha256=Re4F0ZQEEOfdXljVCkswU1ESZksmpAqRy3es3_Wmeiw,5070
|
|
420
427
|
firefighter/slack/views/modals/select.py,sha256=Y-Ji_ALnzhYkXDBAyi497UL1Xn2vCGqXCtj8eog75Jk,3312
|
|
421
428
|
firefighter/slack/views/modals/send_sos.py,sha256=bP6HgYyDwPrIcTq7n_sQz6UQsxhYbvBDS4HjM0uRccA,4838
|
|
422
429
|
firefighter/slack/views/modals/status.py,sha256=C8-eJRtquSeaHe568SC7yCFef1k14m2_6lUqBezdSH8,3970
|
|
423
430
|
firefighter/slack/views/modals/trigger_oncall.py,sha256=h_LAD5X5rjMFWiDYTEp5VB9OaF7sTvKZhNaW3KQkw5M,5065
|
|
424
431
|
firefighter/slack/views/modals/update.py,sha256=OF9sf-Z6IiviNmjN28MQNYiUbJ5tha0MdHUQyPpVFiY,2150
|
|
425
432
|
firefighter/slack/views/modals/update_roles.py,sha256=De3Gv67MZQHyNdonX3S99F5MtKF_Rj3y71gdWibxBaM,2419
|
|
426
|
-
firefighter/slack/views/modals/update_status.py,sha256=
|
|
433
|
+
firefighter/slack/views/modals/update_status.py,sha256=SYjQLXnWSZLk461b-L9WFRSxy7clIA4O0C4ZMrNWiuc,5964
|
|
427
434
|
firefighter/slack/views/modals/utils.py,sha256=zKLJD2KhTGcX2d9WCYwshYRa6ok_9-ED1_pgOLp028s,2133
|
|
428
435
|
firefighter/slack/views/modals/base_modal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
429
436
|
firefighter/slack/views/modals/base_modal/base.py,sha256=7mvOxZTtegSmitSMnDvu8BK0qLUXoudUsda6CaLjdkY,12479
|
|
@@ -459,7 +466,7 @@ firefighter_tests/test_firefighter/test_logging.py,sha256=4HUH73vLDwmOCpMiXwDasM
|
|
|
459
466
|
firefighter_tests/test_firefighter/test_sso.py,sha256=uX2ry0REDgXzQc9Y1BmAgI0OgbmzWoOv9H_GDyOqQmQ,5205
|
|
460
467
|
firefighter_tests/test_firefighter/test_urls.py,sha256=UMGx4oW98RoL0ceePkIIKEVjbHdFECvQuGNXYAJForQ,4839
|
|
461
468
|
firefighter_tests/test_incidents/test_enums.py,sha256=wMxxL1uakrmzJIi-2xkAvG-Y3NDDmIt0PHyOAJBz0yQ,4341
|
|
462
|
-
firefighter_tests/test_incidents/test_incident_urls.py,sha256=
|
|
469
|
+
firefighter_tests/test_incidents/test_incident_urls.py,sha256=VD9dj0IHHKXJHC5ApZg-L9CMtotaQb9uRAKcZIhOrDI,3978
|
|
463
470
|
firefighter_tests/test_incidents/test_forms/conftest.py,sha256=YYF5Lm-Jmt-HM9zt_gjrNkiuqOaNMW8lLBr1crAP6J8,5423
|
|
464
471
|
firefighter_tests/test_incidents/test_forms/test_closure_reason.py,sha256=H6RObqazFAit_pvo7N-lotiSsLOYMafZIk23A5Wiodg,3533
|
|
465
472
|
firefighter_tests/test_incidents/test_forms/test_form_select_impact.py,sha256=DTaPGrJi8mXHfh7mhvDTKYVvDCxqarILauE59UDlwqo,3210
|
|
@@ -471,10 +478,10 @@ firefighter_tests/test_incidents/test_forms/test_update_key_events.py,sha256=rHR
|
|
|
471
478
|
firefighter_tests/test_incidents/test_forms/test_update_status_workflow.py,sha256=q0xXU2BbBG8B0uvvyBWlo4HM8ckbcNAP05Fq8oJNtOw,16270
|
|
472
479
|
firefighter_tests/test_incidents/test_forms/test_workflow_transitions.py,sha256=priKh7QYZxGDPu2SvPC8pGnqOsZWg5cLkyC40pDvLAU,7184
|
|
473
480
|
firefighter_tests/test_incidents/test_models/test_incident_category.py,sha256=aRoBOhb8fNjLF9CMPZ1FXM8AT51Cd80XPsY2Y3wHY_M,5701
|
|
474
|
-
firefighter_tests/test_incidents/test_models/test_incident_model.py,sha256=
|
|
481
|
+
firefighter_tests/test_incidents/test_models/test_incident_model.py,sha256=AWyWfQYcHNP9GPizIo0wRxNGTJTEJnAwNSd4UmRq-dk,8626
|
|
475
482
|
firefighter_tests/test_incidents/test_models/test_migrations/test_incident_migrations.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
476
483
|
firefighter_tests/test_incidents/test_utils/test_date_utils.py,sha256=ogP7qOEwItL4YGI5gbQPVssOS9ilwiuZC8OrT2qngBY,6568
|
|
477
|
-
firefighter_tests/test_incidents/test_views/test_incident_detail_view.py,sha256=
|
|
484
|
+
firefighter_tests/test_incidents/test_views/test_incident_detail_view.py,sha256=lkCIRfz99Ea0o0Id08LFWrjXLDmHv6XvezaSsjg-eYQ,871
|
|
478
485
|
firefighter_tests/test_incidents/test_views/test_index_view.py,sha256=InpxbaWOFwRn4YWeIKZhj17vMymrQQf2p2LFhe2Bcdw,816
|
|
479
486
|
firefighter_tests/test_jira_app/__init__.py,sha256=JxZ3v-0kiHOoO-N3kR8NHTmD8tEvuEYKW1GX_S1ZLMY,33
|
|
480
487
|
firefighter_tests/test_jira_app/conftest.py,sha256=HmZd7EBZgng-rb3kIaB14TPVMixMG4YEvnShVqgjodE,545
|
|
@@ -502,24 +509,25 @@ firefighter_tests/test_slack/test_conversation_tags.py,sha256=nNqTZRRBfF6Z4wpFSY
|
|
|
502
509
|
firefighter_tests/test_slack/test_signals_downgrade.py,sha256=mgl4H5vwr2kImf6g4IZbhv7YEPmMzbYSaVr8E6taL88,5420
|
|
503
510
|
firefighter_tests/test_slack/test_slack_utils.py,sha256=9PLobMNXh3xDyFuwzcQFpKJhe4j__sIgf_WRHIpANJw,3957
|
|
504
511
|
firefighter_tests/test_slack/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
505
|
-
firefighter_tests/test_slack/messages/test_slack_messages.py,sha256=
|
|
512
|
+
firefighter_tests/test_slack/messages/test_slack_messages.py,sha256=uyxfeAy1BQxx1zcCzlSJWn5YF1EnH-5Kt2XoIn9dekM,17484
|
|
506
513
|
firefighter_tests/test_slack/test_models/test_conversations.py,sha256=t3ttmgwiu7c-N55iU3XZPmrkEhvkTzJoXszJncy4Bts,793
|
|
507
514
|
firefighter_tests/test_slack/test_models/test_incident_channel.py,sha256=qWoGe9iadmK6-R8usWvjH87AHRkvhG_dHQeC3kHeJrs,17487
|
|
508
515
|
firefighter_tests/test_slack/test_models/test_slack_user.py,sha256=uzur-Rf03I5dpUTO4ZI6O1arBUrAorg1Zvgshf8M-J4,7000
|
|
509
516
|
firefighter_tests/test_slack/views/modals/conftest.py,sha256=TKJVQgqWaFs3Gg1T526pti9XpZBtQs47WBH6L_qSDeo,4532
|
|
510
|
-
firefighter_tests/test_slack/views/modals/test_close.py,sha256=
|
|
511
|
-
firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py,sha256=
|
|
517
|
+
firefighter_tests/test_slack/views/modals/test_close.py,sha256=FWNV7RIUpqp3tiz9IBbBxaZk1XQt2f7vWB5TzJKYK3o,45630
|
|
518
|
+
firefighter_tests/test_slack/views/modals/test_closure_reason_modal.py,sha256=mvg5RiCXQEp1GhyOBCNW4idkNR1StgZjPvFrjzJ549Q,8333
|
|
512
519
|
firefighter_tests/test_slack/views/modals/test_edit.py,sha256=ykirry-S3i6PtoSs3rff_k6jqmvv1oMWC_iR8e5Jsg0,12022
|
|
513
520
|
firefighter_tests/test_slack/views/modals/test_form_utils_multiple_choice.py,sha256=Svab_ZyYTMf0T-uJEQcm7gS1WzxtC4gPh1W--Z2v_Y8,8415
|
|
514
521
|
firefighter_tests/test_slack/views/modals/test_key_event_message.py,sha256=BCg-c27ZLJqNgFuG4JDgXrSTp8_sT4FeBtpASzSq8NI,1107
|
|
515
522
|
firefighter_tests/test_slack/views/modals/test_open.py,sha256=IzgG9le5NN_CvltehAIqkj94ioTKCqdA6yoRp2NlNsE,10700
|
|
516
523
|
firefighter_tests/test_slack/views/modals/test_opening_unified.py,sha256=OejtLyc_mehav2TDaLzUnhilMNvhCzc6T4FodCqfQPk,17406
|
|
524
|
+
firefighter_tests/test_slack/views/modals/test_postmortem_modal.py,sha256=zNN40sIRSM5w_kyOcQ-AODkH5WpVxkSGVXkh9rMgmQ0,2378
|
|
517
525
|
firefighter_tests/test_slack/views/modals/test_send_sos.py,sha256=_rE6jD-gOzcGyhlY0R9GzlGtPx65oOOguJYdENgxtLc,1289
|
|
518
526
|
firefighter_tests/test_slack/views/modals/test_status.py,sha256=oQzPfwdg2tkbo9nfkO1GfS3WydxqSC6vy1AZjZDKT30,2226
|
|
519
|
-
firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=
|
|
527
|
+
firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=vbHGx6dkM_0swE1vJ0HrkhI1oJzD_WHZuIQ-_arAxXo,55686
|
|
520
528
|
firefighter_tests/test_slack/views/modals/test_utils.py,sha256=DJd2n9q6fFu8UuCRdiq9U_Cn19MdnC5c-ydLLrk6rkc,5218
|
|
521
|
-
firefighter_incident-0.0.
|
|
522
|
-
firefighter_incident-0.0.
|
|
523
|
-
firefighter_incident-0.0.
|
|
524
|
-
firefighter_incident-0.0.
|
|
525
|
-
firefighter_incident-0.0.
|
|
529
|
+
firefighter_incident-0.0.28.dist-info/METADATA,sha256=mM6LIiFAeLibO2jnQEf9KZiEOFlZJT09A3jrCExS5sM,5570
|
|
530
|
+
firefighter_incident-0.0.28.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
531
|
+
firefighter_incident-0.0.28.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
|
|
532
|
+
firefighter_incident-0.0.28.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
|
|
533
|
+
firefighter_incident-0.0.28.dist-info/RECORD,,
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
+
from django.apps import apps
|
|
7
8
|
from django.urls import reverse
|
|
8
9
|
|
|
9
10
|
from firefighter.incidents.factories import IncidentFactory, UserFactory
|
|
@@ -15,6 +16,9 @@ if TYPE_CHECKING:
|
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(__name__)
|
|
17
18
|
|
|
19
|
+
if not apps.is_installed("firefighter.confluence"):
|
|
20
|
+
pytest.skip("Confluence app not installed; skipping incident URLs tests", allow_module_level=True)
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
@pytest.mark.django_db
|
|
20
24
|
def test_incidents_dashboard_unauthorized(client: Client) -> None:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
|
+
from unittest.mock import PropertyMock, patch
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
6
7
|
from hypothesis import given
|
|
@@ -10,8 +11,11 @@ from hypothesis.strategies import builds
|
|
|
10
11
|
from firefighter.incidents.enums import ClosureReason, IncidentStatus
|
|
11
12
|
from firefighter.incidents.factories import IncidentFactory
|
|
12
13
|
from firefighter.incidents.models import IncidentUpdate
|
|
14
|
+
from firefighter.jira_app.models import JiraPostMortem
|
|
13
15
|
|
|
14
16
|
if TYPE_CHECKING:
|
|
17
|
+
from pytest_mock import MockerFixture
|
|
18
|
+
|
|
15
19
|
from firefighter.incidents.models import Incident
|
|
16
20
|
|
|
17
21
|
|
|
@@ -58,7 +62,9 @@ class TestIncidentCanBeClosed:
|
|
|
58
62
|
can_close, reasons = incident.can_be_closed
|
|
59
63
|
|
|
60
64
|
# Should be closable (assuming no missing milestones)
|
|
61
|
-
assert can_close is True or "STATUS_NOT_MITIGATED" not in [
|
|
65
|
+
assert can_close is True or "STATUS_NOT_MITIGATED" not in [
|
|
66
|
+
r[0] for r in reasons
|
|
67
|
+
]
|
|
62
68
|
|
|
63
69
|
def test_can_close_incident_with_closure_reason(self) -> None:
|
|
64
70
|
"""Test that incidents with closure_reason can always be closed."""
|
|
@@ -73,6 +79,108 @@ class TestIncidentCanBeClosed:
|
|
|
73
79
|
assert can_close is True
|
|
74
80
|
assert reasons == []
|
|
75
81
|
|
|
82
|
+
def test_cannot_close_when_jira_postmortem_not_ready(self, settings: None) -> None:
|
|
83
|
+
"""Block closure if Jira post-mortem exists but is not in Ready status."""
|
|
84
|
+
settings.ENABLE_JIRA_POSTMORTEM = True
|
|
85
|
+
incident = IncidentFactory.create(
|
|
86
|
+
_status=IncidentStatus.POST_MORTEM,
|
|
87
|
+
priority__value=1,
|
|
88
|
+
priority__needs_postmortem=True,
|
|
89
|
+
environment__value="PRD",
|
|
90
|
+
)
|
|
91
|
+
JiraPostMortem.objects.create(
|
|
92
|
+
incident=incident,
|
|
93
|
+
jira_issue_key="INC-999",
|
|
94
|
+
jira_issue_id="999",
|
|
95
|
+
created_by=incident.created_by,
|
|
96
|
+
)
|
|
97
|
+
incident.refresh_from_db()
|
|
98
|
+
assert hasattr(incident, "jira_postmortem_for")
|
|
99
|
+
|
|
100
|
+
with (
|
|
101
|
+
patch.object(
|
|
102
|
+
type(incident),
|
|
103
|
+
"needs_postmortem",
|
|
104
|
+
new_callable=PropertyMock,
|
|
105
|
+
return_value=True,
|
|
106
|
+
),
|
|
107
|
+
patch.object(type(incident), "missing_milestones", return_value=[]),
|
|
108
|
+
patch(
|
|
109
|
+
"firefighter.jira_app.service_postmortem.jira_postmortem_service.is_postmortem_ready",
|
|
110
|
+
return_value=(False, "In Progress"),
|
|
111
|
+
),
|
|
112
|
+
):
|
|
113
|
+
can_close, reasons = incident.can_be_closed
|
|
114
|
+
|
|
115
|
+
assert can_close is False
|
|
116
|
+
assert any(r[0] == "POSTMORTEM_NOT_READY" for r in reasons)
|
|
117
|
+
|
|
118
|
+
def test_postmortem_ready_allows_closure(
|
|
119
|
+
self, mocker: MockerFixture, settings: None
|
|
120
|
+
) -> None:
|
|
121
|
+
"""When Jira PM is Ready, can_be_closed should allow closure for PM incidents."""
|
|
122
|
+
settings.ENABLE_JIRA_POSTMORTEM = True
|
|
123
|
+
incident = IncidentFactory.create(
|
|
124
|
+
_status=IncidentStatus.POST_MORTEM,
|
|
125
|
+
priority__value=1,
|
|
126
|
+
priority__needs_postmortem=True,
|
|
127
|
+
environment__value="PRD",
|
|
128
|
+
)
|
|
129
|
+
JiraPostMortem.objects.create(
|
|
130
|
+
incident=incident,
|
|
131
|
+
jira_issue_key="INC-READY",
|
|
132
|
+
jira_issue_id="123",
|
|
133
|
+
created_by=incident.created_by,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
mocker.patch.object(type(incident), "missing_milestones", return_value=[])
|
|
137
|
+
mocker.patch(
|
|
138
|
+
"firefighter.jira_app.service_postmortem.jira_postmortem_service.is_postmortem_ready",
|
|
139
|
+
return_value=(True, "Ready"),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
can_close, reasons = incident.can_be_closed
|
|
143
|
+
|
|
144
|
+
assert can_close is True
|
|
145
|
+
assert reasons == []
|
|
146
|
+
|
|
147
|
+
def test_postmortem_status_unknown_sets_reason(
|
|
148
|
+
self, mocker: MockerFixture, settings: None
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Errors while checking Jira PM should return POSTMORTEM_STATUS_UNKNOWN."""
|
|
151
|
+
settings.ENABLE_JIRA_POSTMORTEM = True
|
|
152
|
+
incident = IncidentFactory.create(
|
|
153
|
+
_status=IncidentStatus.POST_MORTEM,
|
|
154
|
+
priority__value=1,
|
|
155
|
+
priority__needs_postmortem=True,
|
|
156
|
+
environment__value="PRD",
|
|
157
|
+
)
|
|
158
|
+
JiraPostMortem.objects.create(
|
|
159
|
+
incident=incident,
|
|
160
|
+
jira_issue_key="INC-ERR",
|
|
161
|
+
jira_issue_id="124",
|
|
162
|
+
created_by=incident.created_by,
|
|
163
|
+
)
|
|
164
|
+
incident.refresh_from_db()
|
|
165
|
+
assert hasattr(incident, "jira_postmortem_for")
|
|
166
|
+
|
|
167
|
+
mocker.patch.object(type(incident), "missing_milestones", return_value=[])
|
|
168
|
+
mocker.patch(
|
|
169
|
+
"firefighter.jira_app.service_postmortem.jira_postmortem_service.is_postmortem_ready",
|
|
170
|
+
side_effect=Exception("boom"),
|
|
171
|
+
)
|
|
172
|
+
mocker.patch.object(
|
|
173
|
+
type(incident),
|
|
174
|
+
"needs_postmortem",
|
|
175
|
+
new_callable=PropertyMock,
|
|
176
|
+
return_value=True,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
can_close, reasons = incident.can_be_closed
|
|
180
|
+
|
|
181
|
+
assert can_close is False
|
|
182
|
+
assert any(r[0] == "POSTMORTEM_STATUS_UNKNOWN" for r in reasons)
|
|
183
|
+
|
|
76
184
|
|
|
77
185
|
@pytest.mark.django_db
|
|
78
186
|
class TestIncidentSetStatus:
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
from django.apps import apps
|
|
4
5
|
from django.test import Client
|
|
5
6
|
from django.urls import reverse
|
|
6
7
|
|
|
7
8
|
from firefighter.incidents.models import Incident
|
|
8
9
|
from firefighter.incidents.models.user import User
|
|
9
10
|
|
|
11
|
+
if not apps.is_installed("firefighter.confluence"):
|
|
12
|
+
pytest.skip("Confluence app not installed; skipping incident detail view test", allow_module_level=True)
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
@pytest.mark.django_db
|
|
12
16
|
def test_incident_detail_view(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
from django.apps import apps
|
|
4
5
|
|
|
5
6
|
from firefighter.incidents.enums import IncidentStatus
|
|
6
7
|
from firefighter.incidents.factories import IncidentFactory, UserFactory
|
|
@@ -18,6 +19,9 @@ try:
|
|
|
18
19
|
except (ImportError, AttributeError):
|
|
19
20
|
PostMortem = None
|
|
20
21
|
|
|
22
|
+
if not apps.is_installed("firefighter.confluence"):
|
|
23
|
+
pytest.skip("Confluence app not installed; skipping slack message tests", allow_module_level=True)
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
@pytest.mark.django_db
|
|
23
27
|
class TestSlackMessageIncidentStatusUpdated:
|
|
@@ -5,6 +5,7 @@ from copy import deepcopy
|
|
|
5
5
|
from unittest.mock import MagicMock, PropertyMock
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
+
from django.apps import apps
|
|
8
9
|
from pytest_mock import MockerFixture
|
|
9
10
|
|
|
10
11
|
from firefighter.incidents.enums import IncidentStatus
|
|
@@ -14,6 +15,9 @@ from firefighter.slack.views import CloseModal
|
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
16
17
|
|
|
18
|
+
if not apps.is_installed("firefighter.confluence"):
|
|
19
|
+
pytest.skip("Confluence app not installed; skipping close modal tests", allow_module_level=True)
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
@pytest.mark.django_db
|
|
19
23
|
class TestCloseModal:
|