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
|
@@ -9,6 +9,7 @@ from slack_sdk.errors import SlackApiError
|
|
|
9
9
|
|
|
10
10
|
from firefighter.incidents.enums import ClosureReason, IncidentStatus
|
|
11
11
|
from firefighter.incidents.factories import IncidentFactory, UserFactory
|
|
12
|
+
from firefighter.incidents.models import Environment, Priority
|
|
12
13
|
from firefighter.slack.views.modals.closure_reason import ClosureReasonModal
|
|
13
14
|
|
|
14
15
|
|
|
@@ -16,12 +17,16 @@ from firefighter.slack.views.modals.closure_reason import ClosureReasonModal
|
|
|
16
17
|
class TestClosureReasonModalMessageTabDisabled:
|
|
17
18
|
"""Test ClosureReasonModal handles messages_tab_disabled gracefully."""
|
|
18
19
|
|
|
19
|
-
def test_closure_reason_handles_messages_tab_disabled(
|
|
20
|
+
def test_closure_reason_handles_messages_tab_disabled(
|
|
21
|
+
self, caplog: pytest.LogCaptureFixture, mocker
|
|
22
|
+
) -> None:
|
|
20
23
|
"""Test that messages_tab_disabled error is handled gracefully with warning log."""
|
|
21
24
|
# Create test data
|
|
22
25
|
user = UserFactory.build()
|
|
23
26
|
user.save()
|
|
24
|
-
incident = IncidentFactory.build(
|
|
27
|
+
incident = IncidentFactory.build(
|
|
28
|
+
_status=IncidentStatus.INVESTIGATING, created_by=user
|
|
29
|
+
)
|
|
25
30
|
incident.save()
|
|
26
31
|
|
|
27
32
|
# Mock can_be_closed to return True so the closure can proceed
|
|
@@ -29,7 +34,7 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
29
34
|
type(incident),
|
|
30
35
|
"can_be_closed",
|
|
31
36
|
new_callable=mocker.PropertyMock,
|
|
32
|
-
return_value=(True, [])
|
|
37
|
+
return_value=(True, []),
|
|
33
38
|
)
|
|
34
39
|
|
|
35
40
|
# Create modal and mock
|
|
@@ -46,9 +51,7 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
46
51
|
"selected_option": {"value": ClosureReason.CANCELLED}
|
|
47
52
|
}
|
|
48
53
|
},
|
|
49
|
-
"closure_reference": {
|
|
50
|
-
"input_closure_reference": {"value": ""}
|
|
51
|
-
},
|
|
54
|
+
"closure_reference": {"input_closure_reference": {"value": ""}},
|
|
52
55
|
"closure_message": {
|
|
53
56
|
"input_closure_message": {"value": "Test closure message"}
|
|
54
57
|
},
|
|
@@ -63,18 +66,17 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
63
66
|
slack_error_response = MagicMock()
|
|
64
67
|
slack_error_response.get.return_value = "messages_tab_disabled"
|
|
65
68
|
|
|
66
|
-
with patch(
|
|
69
|
+
with patch(
|
|
70
|
+
"firefighter.slack.views.modals.closure_reason.respond"
|
|
71
|
+
) as mock_respond:
|
|
67
72
|
mock_respond.side_effect = SlackApiError(
|
|
68
73
|
message="The request to the Slack API failed.",
|
|
69
|
-
response=slack_error_response
|
|
74
|
+
response=slack_error_response,
|
|
70
75
|
)
|
|
71
76
|
|
|
72
77
|
# Execute
|
|
73
78
|
result = modal.handle_modal_fn(
|
|
74
|
-
ack=ack,
|
|
75
|
-
body=body,
|
|
76
|
-
incident=incident,
|
|
77
|
-
user=user
|
|
79
|
+
ack=ack, body=body, incident=incident, user=user
|
|
78
80
|
)
|
|
79
81
|
|
|
80
82
|
# Assertions
|
|
@@ -87,7 +89,8 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
87
89
|
|
|
88
90
|
# Verify warning was logged
|
|
89
91
|
assert any(
|
|
90
|
-
"Cannot send DM to user" in record.message
|
|
92
|
+
"Cannot send DM to user" in record.message
|
|
93
|
+
and record.levelname == "WARNING"
|
|
91
94
|
for record in caplog.records
|
|
92
95
|
)
|
|
93
96
|
|
|
@@ -96,7 +99,9 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
96
99
|
# Create test data
|
|
97
100
|
user = UserFactory.build()
|
|
98
101
|
user.save()
|
|
99
|
-
incident = IncidentFactory.build(
|
|
102
|
+
incident = IncidentFactory.build(
|
|
103
|
+
_status=IncidentStatus.INVESTIGATING, created_by=user
|
|
104
|
+
)
|
|
100
105
|
incident.save()
|
|
101
106
|
|
|
102
107
|
# Mock can_be_closed to return True so the closure can proceed
|
|
@@ -104,7 +109,7 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
104
109
|
type(incident),
|
|
105
110
|
"can_be_closed",
|
|
106
111
|
new_callable=mocker.PropertyMock,
|
|
107
|
-
return_value=(True, [])
|
|
112
|
+
return_value=(True, []),
|
|
108
113
|
)
|
|
109
114
|
|
|
110
115
|
# Create modal and mock
|
|
@@ -121,9 +126,7 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
121
126
|
"selected_option": {"value": ClosureReason.CANCELLED}
|
|
122
127
|
}
|
|
123
128
|
},
|
|
124
|
-
"closure_reference": {
|
|
125
|
-
"input_closure_reference": {"value": ""}
|
|
126
|
-
},
|
|
129
|
+
"closure_reference": {"input_closure_reference": {"value": ""}},
|
|
127
130
|
"closure_message": {
|
|
128
131
|
"input_closure_message": {"value": "Test closure message"}
|
|
129
132
|
},
|
|
@@ -138,17 +141,97 @@ class TestClosureReasonModalMessageTabDisabled:
|
|
|
138
141
|
slack_error_response = MagicMock()
|
|
139
142
|
slack_error_response.get.return_value = "channel_not_found"
|
|
140
143
|
|
|
141
|
-
with patch(
|
|
144
|
+
with patch(
|
|
145
|
+
"firefighter.slack.views.modals.closure_reason.respond"
|
|
146
|
+
) as mock_respond:
|
|
142
147
|
mock_respond.side_effect = SlackApiError(
|
|
143
148
|
message="The request to the Slack API failed.",
|
|
144
|
-
response=slack_error_response
|
|
149
|
+
response=slack_error_response,
|
|
145
150
|
)
|
|
146
151
|
|
|
147
152
|
# Execute and expect exception
|
|
148
153
|
with pytest.raises(SlackApiError):
|
|
149
|
-
modal.handle_modal_fn(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
modal.handle_modal_fn(ack=ack, body=body, incident=incident, user=user)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@pytest.mark.django_db
|
|
158
|
+
class TestClosureReasonModalEarlyClosureBypass:
|
|
159
|
+
"""Test early-closure path respects submitted closure reason."""
|
|
160
|
+
|
|
161
|
+
def test_allows_early_closure_with_submitted_reason(self, settings) -> None:
|
|
162
|
+
"""Ensure can_be_closed passes when a closure reason is provided for early closure."""
|
|
163
|
+
settings.ENABLE_JIRA_POSTMORTEM = True
|
|
164
|
+
|
|
165
|
+
# Ensure required priority/environment for needs_postmortem + PRD
|
|
166
|
+
priority = Priority.objects.create(
|
|
167
|
+
name="P1-test",
|
|
168
|
+
value=9991,
|
|
169
|
+
description="P1 test",
|
|
170
|
+
order=9991,
|
|
171
|
+
needs_postmortem=True,
|
|
172
|
+
)
|
|
173
|
+
env, _ = Environment.objects.get_or_create(
|
|
174
|
+
value="PRD",
|
|
175
|
+
defaults={
|
|
176
|
+
"name": "Production",
|
|
177
|
+
"description": "Production",
|
|
178
|
+
"order": 9991,
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
user = UserFactory.create()
|
|
183
|
+
incident = IncidentFactory.create(
|
|
184
|
+
_status=IncidentStatus.INVESTIGATING,
|
|
185
|
+
created_by=user,
|
|
186
|
+
priority=priority,
|
|
187
|
+
environment=env,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
modal = ClosureReasonModal()
|
|
191
|
+
ack = MagicMock()
|
|
192
|
+
|
|
193
|
+
body = {
|
|
194
|
+
"view": {
|
|
195
|
+
"state": {
|
|
196
|
+
"values": {
|
|
197
|
+
"closure_reason": {
|
|
198
|
+
"select_closure_reason": {
|
|
199
|
+
"selected_option": {"value": ClosureReason.CANCELLED}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"closure_reference": {
|
|
203
|
+
"input_closure_reference": {"value": "INC-42"}
|
|
204
|
+
},
|
|
205
|
+
"closure_message": {
|
|
206
|
+
"input_closure_message": {
|
|
207
|
+
"value": "Closing early with reason"
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"private_metadata": str(incident.id),
|
|
213
|
+
},
|
|
214
|
+
"user": {"id": "U123456"},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
with patch(
|
|
218
|
+
"firefighter.slack.views.modals.closure_reason.respond"
|
|
219
|
+
) as mock_respond:
|
|
220
|
+
mock_respond.return_value = None
|
|
221
|
+
|
|
222
|
+
result = modal.handle_modal_fn(
|
|
223
|
+
ack=ack,
|
|
224
|
+
body=body,
|
|
225
|
+
incident=incident,
|
|
226
|
+
user=user,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Early closure should succeed and close the incident
|
|
230
|
+
assert result is True
|
|
231
|
+
incident.refresh_from_db()
|
|
232
|
+
assert incident.status == IncidentStatus.CLOSED
|
|
233
|
+
assert incident.closure_reason == ClosureReason.CANCELLED
|
|
234
|
+
assert incident.closure_reference == "INC-42"
|
|
235
|
+
|
|
236
|
+
# Ack should clear modal stack
|
|
237
|
+
ack.assert_called_once_with(response_action="clear")
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
from firefighter.slack.views.modals.postmortem import PostMortemModal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestPostMortemModal:
|
|
9
|
+
@staticmethod
|
|
10
|
+
def _get_text_blocks(view_dict: dict) -> list[str]:
|
|
11
|
+
return [
|
|
12
|
+
block.get("text", {}).get("text", "")
|
|
13
|
+
for block in view_dict.get("blocks", [])
|
|
14
|
+
if block.get("type") == "section"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def _get_action_elements(view_dict: dict) -> list[dict]:
|
|
19
|
+
elements: list[dict] = []
|
|
20
|
+
for block in view_dict.get("blocks", []):
|
|
21
|
+
if block.get("type") == "actions":
|
|
22
|
+
elements.extend(block.get("elements", []))
|
|
23
|
+
return elements
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def test_p1_p2_shows_auto_creation_message(mocker):
|
|
27
|
+
incident = SimpleNamespace(id=123, needs_postmortem=True)
|
|
28
|
+
|
|
29
|
+
# No existing PMs
|
|
30
|
+
mocker.patch(
|
|
31
|
+
"firefighter.slack.views.modals.postmortem._safe_has_relation",
|
|
32
|
+
return_value=False,
|
|
33
|
+
create=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
view = PostMortemModal().build_modal_fn(incident)
|
|
37
|
+
view_dict = view.to_dict()
|
|
38
|
+
|
|
39
|
+
texts = TestPostMortemModal._get_text_blocks(view_dict)
|
|
40
|
+
assert any(
|
|
41
|
+
"automatically created when the incident reaches MITIGATED" in t
|
|
42
|
+
for t in texts
|
|
43
|
+
)
|
|
44
|
+
# No manual-create button for mandatory PMs
|
|
45
|
+
action_ids = [
|
|
46
|
+
el.get("action_id")
|
|
47
|
+
for el in TestPostMortemModal._get_action_elements(view_dict)
|
|
48
|
+
]
|
|
49
|
+
assert "incident_create_postmortem_now" not in action_ids
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def test_p3_shows_optional_message_and_button(mocker):
|
|
53
|
+
incident = SimpleNamespace(id=456, needs_postmortem=False)
|
|
54
|
+
|
|
55
|
+
# No existing PMs
|
|
56
|
+
mocker.patch(
|
|
57
|
+
"firefighter.slack.views.modals.postmortem._safe_has_relation",
|
|
58
|
+
return_value=False,
|
|
59
|
+
create=True,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
view = PostMortemModal().build_modal_fn(incident)
|
|
63
|
+
view_dict = view.to_dict()
|
|
64
|
+
|
|
65
|
+
texts = TestPostMortemModal._get_text_blocks(view_dict)
|
|
66
|
+
assert any("P3 incident post-mortem is not mandatory" in t for t in texts)
|
|
67
|
+
|
|
68
|
+
action_ids = [
|
|
69
|
+
el.get("action_id")
|
|
70
|
+
for el in TestPostMortemModal._get_action_elements(view_dict)
|
|
71
|
+
]
|
|
72
|
+
assert "incident_create_postmortem_now" in action_ids
|
|
@@ -129,12 +129,12 @@ class TestUpdateStatusModal:
|
|
|
129
129
|
|
|
130
130
|
# Verify that can_be_closed returns False due to missing milestones (real check, no mock)
|
|
131
131
|
can_close, reasons = incident.can_be_closed
|
|
132
|
-
assert
|
|
133
|
-
|
|
134
|
-
)
|
|
135
|
-
assert any(
|
|
136
|
-
|
|
137
|
-
)
|
|
132
|
+
assert (
|
|
133
|
+
can_close is False
|
|
134
|
+
), f"Incident should not be closable without required milestones. Got: {can_close}, reasons: {reasons}"
|
|
135
|
+
assert any(
|
|
136
|
+
"MISSING_REQUIRED_KEY_EVENTS" in reason[0] for reason in reasons
|
|
137
|
+
), f"Expected MISSING_REQUIRED_KEY_EVENTS in reasons, got: {reasons}"
|
|
138
138
|
|
|
139
139
|
modal = UpdateStatusModal()
|
|
140
140
|
trigger_incident_workflow = mocker.patch.object(
|
|
@@ -163,26 +163,23 @@ class TestUpdateStatusModal:
|
|
|
163
163
|
assert ack.called, "ack should have been called"
|
|
164
164
|
# Check the last call (the error response)
|
|
165
165
|
last_call_kwargs = ack.call_args.kwargs
|
|
166
|
-
assert
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
assert
|
|
170
|
-
|
|
171
|
-
)
|
|
172
|
-
assert
|
|
173
|
-
|
|
174
|
-
)
|
|
175
|
-
assert
|
|
176
|
-
|
|
177
|
-
)
|
|
166
|
+
assert (
|
|
167
|
+
"response_action" in last_call_kwargs
|
|
168
|
+
), f"Expected 'response_action' in ack, got: {last_call_kwargs}"
|
|
169
|
+
assert (
|
|
170
|
+
last_call_kwargs["response_action"] == "errors"
|
|
171
|
+
), f"Expected response_action='errors', got: {last_call_kwargs.get('response_action')}"
|
|
172
|
+
assert (
|
|
173
|
+
"errors" in last_call_kwargs
|
|
174
|
+
), f"Expected 'errors' in ack, got: {last_call_kwargs}"
|
|
175
|
+
assert (
|
|
176
|
+
"status" in last_call_kwargs["errors"]
|
|
177
|
+
), f"Expected 'status' in errors, got: {last_call_kwargs.get('errors')}"
|
|
178
178
|
# Check that the error message mentions the missing key events
|
|
179
179
|
error_msg = last_call_kwargs["errors"]["status"]
|
|
180
|
-
assert
|
|
181
|
-
|
|
182
|
-
)
|
|
183
|
-
assert "key events" in error_msg.lower(), (
|
|
184
|
-
f"Expected 'key events' in error, got: {error_msg}"
|
|
185
|
-
)
|
|
180
|
+
assert (
|
|
181
|
+
"missing key events" in error_msg.lower()
|
|
182
|
+
), f"Expected missing key events error, got: {error_msg}"
|
|
186
183
|
|
|
187
184
|
# Verify that incident update was NOT triggered
|
|
188
185
|
trigger_incident_workflow.assert_not_called()
|
|
@@ -233,12 +230,12 @@ class TestUpdateStatusModal:
|
|
|
233
230
|
|
|
234
231
|
# Verify that can_be_closed returns False due to missing milestones
|
|
235
232
|
can_close, reasons = incident.can_be_closed
|
|
236
|
-
assert
|
|
237
|
-
|
|
238
|
-
)
|
|
239
|
-
assert any(
|
|
240
|
-
|
|
241
|
-
)
|
|
233
|
+
assert (
|
|
234
|
+
can_close is False
|
|
235
|
+
), "Incident should not be closable without required milestones"
|
|
236
|
+
assert any(
|
|
237
|
+
"MISSING_REQUIRED_KEY_EVENTS" in reason[0] for reason in reasons
|
|
238
|
+
), f"Expected MISSING_REQUIRED_KEY_EVENTS in reasons, got: {reasons}"
|
|
242
239
|
|
|
243
240
|
modal = UpdateStatusModal()
|
|
244
241
|
trigger_incident_workflow = mocker.patch.object(
|
|
@@ -264,25 +261,22 @@ class TestUpdateStatusModal:
|
|
|
264
261
|
# Assert that ack was called with errors
|
|
265
262
|
assert ack.called, "ack should have been called"
|
|
266
263
|
last_call_kwargs = ack.call_args.kwargs
|
|
267
|
-
assert
|
|
268
|
-
|
|
269
|
-
)
|
|
270
|
-
assert
|
|
271
|
-
|
|
272
|
-
)
|
|
273
|
-
assert
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
assert
|
|
277
|
-
|
|
278
|
-
)
|
|
264
|
+
assert (
|
|
265
|
+
"response_action" in last_call_kwargs
|
|
266
|
+
), f"Expected 'response_action' in ack call, got: {last_call_kwargs}"
|
|
267
|
+
assert (
|
|
268
|
+
last_call_kwargs["response_action"] == "errors"
|
|
269
|
+
), f"Expected response_action='errors', got: {last_call_kwargs.get('response_action')}"
|
|
270
|
+
assert (
|
|
271
|
+
"errors" in last_call_kwargs
|
|
272
|
+
), f"Expected 'errors' in ack call, got: {last_call_kwargs}"
|
|
273
|
+
assert (
|
|
274
|
+
"status" in last_call_kwargs["errors"]
|
|
275
|
+
), f"Expected 'status' in errors, got: {last_call_kwargs.get('errors')}"
|
|
279
276
|
error_msg = last_call_kwargs["errors"]["status"]
|
|
280
|
-
assert
|
|
281
|
-
|
|
282
|
-
)
|
|
283
|
-
assert "key events" in error_msg.lower(), (
|
|
284
|
-
f"Expected 'key events' in error message, got: {error_msg}"
|
|
285
|
-
)
|
|
277
|
+
assert (
|
|
278
|
+
"missing key events" in error_msg.lower()
|
|
279
|
+
), f"Expected missing key events error message, got: {error_msg}"
|
|
286
280
|
|
|
287
281
|
# Verify that incident update was NOT triggered
|
|
288
282
|
trigger_incident_workflow.assert_not_called()
|
|
@@ -465,9 +459,9 @@ class TestUpdateStatusModal:
|
|
|
465
459
|
first_call_kwargs = (
|
|
466
460
|
ack.call_args_list[0][1] if ack.call_args_list else ack.call_args.kwargs
|
|
467
461
|
)
|
|
468
|
-
assert
|
|
469
|
-
|
|
470
|
-
)
|
|
462
|
+
assert (
|
|
463
|
+
first_call_kwargs == {} or "errors" not in first_call_kwargs
|
|
464
|
+
), f"Should allow updating priority without changing status. Got errors: {first_call_kwargs.get('errors')}"
|
|
471
465
|
|
|
472
466
|
# Verify that incident update WAS triggered (priority changed)
|
|
473
467
|
trigger_incident_workflow.assert_called_once()
|
|
File without changes
|
{firefighter_incident-0.0.26.dist-info → firefighter_incident-0.0.28.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{firefighter_incident-0.0.26.dist-info → firefighter_incident-0.0.28.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|