firefighter-incident 0.0.10__py3-none-any.whl → 0.0.12__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/pagerduty/tasks/trigger_oncall.py +1 -1
- firefighter/raid/client.py +2 -2
- firefighter/raid/signals/incident_created.py +1 -1
- firefighter/slack/messages/slack_messages.py +3 -3
- firefighter/slack/views/events/home.py +1 -1
- firefighter/slack/views/modals/open.py +112 -62
- firefighter/slack/views/modals/opening/select_impact.py +38 -5
- {firefighter_incident-0.0.10.dist-info → firefighter_incident-0.0.12.dist-info}/METADATA +1 -1
- {firefighter_incident-0.0.10.dist-info → firefighter_incident-0.0.12.dist-info}/RECORD +14 -14
- firefighter_tests/test_slack/views/modals/test_open.py +25 -8
- {firefighter_incident-0.0.10.dist-info → firefighter_incident-0.0.12.dist-info}/WHEEL +0 -0
- {firefighter_incident-0.0.10.dist-info → firefighter_incident-0.0.12.dist-info}/entry_points.txt +0 -0
- {firefighter_incident-0.0.10.dist-info → firefighter_incident-0.0.12.dist-info}/licenses/LICENSE +0 -0
firefighter/_version.py
CHANGED
|
@@ -38,7 +38,7 @@ def trigger_oncall(
|
|
|
38
38
|
details = f"""Triggered from {APP_DISPLAY_NAME} incident #{incident.id} {f"by {triggered_by.full_name}" if triggered_by else ""}
|
|
39
39
|
Priority: {incident.priority}
|
|
40
40
|
Environment: {incident.environment}
|
|
41
|
-
|
|
41
|
+
Issue category: {incident.component.group.name} - {incident.component.name}
|
|
42
42
|
FireFighter page: {incident.status_page_url + "?utm_medium=FireFighter+PagerDuty&utm_source=PagerDuty+Incident&utm_campaign=OnCall+Message+In+Channel"}
|
|
43
43
|
Slack channel #{incident.slack_channel_name}: {incident.slack_channel_url}
|
|
44
44
|
|
firefighter/raid/client.py
CHANGED
|
@@ -65,8 +65,8 @@ class RaidJiraClient(JiraClient):
|
|
|
65
65
|
if priority is None:
|
|
66
66
|
priority_value = "-"
|
|
67
67
|
else:
|
|
68
|
-
if not 1 <= priority <=
|
|
69
|
-
raise ValueError("Priority must be between 1 and
|
|
68
|
+
if not 1 <= priority <= 5:
|
|
69
|
+
raise ValueError("Priority must be between 1 and 5")
|
|
70
70
|
priority_value = str(priority)
|
|
71
71
|
if area:
|
|
72
72
|
extra_args["customfield_10920"] = str(area)
|
|
@@ -43,7 +43,7 @@ def create_ticket(
|
|
|
43
43
|
description=f"""{incident.description}\n
|
|
44
44
|
\n
|
|
45
45
|
🧯 This incident has been created for a critical incident. Links below to Slack and {APP_DISPLAY_NAME}.\n
|
|
46
|
-
📦
|
|
46
|
+
📦 Issue category: {incident.component.name} ({incident.component.group.name})\n
|
|
47
47
|
{incident.priority.emoji} Priority: {incident.priority.name}\n""",
|
|
48
48
|
assignee=None,
|
|
49
49
|
reporter=account_id,
|
|
@@ -210,7 +210,7 @@ class SlackMessageIncidentDeclaredAnnouncement(SlackMessageSurface):
|
|
|
210
210
|
def get_blocks(self) -> list[Block]:
|
|
211
211
|
fields = [
|
|
212
212
|
f"{self.incident.priority.emoji} *Priority:* {self.incident.priority.name}",
|
|
213
|
-
f":package: *
|
|
213
|
+
f":package: *Issue category:* {self.incident.component.name}",
|
|
214
214
|
f":speaking_head_in_silhouette: *Opened by:* {user_slack_handle_or_name(self.incident.created_by)}",
|
|
215
215
|
f":calendar: *Created at:* {date_time(self.incident.created_at)}",
|
|
216
216
|
f"{SLACK_APP_EMOJI} <{self.incident.status_page_url + '?utm_medium=FireFighter+Slack&utm_source=Slack+Message&utm_campaign=Announcement+Message+In+Channel'}|*{APP_DISPLAY_NAME} Status Page*>",
|
|
@@ -270,7 +270,7 @@ class SlackMessageIncidentDeclaredAnnouncementGeneral(SlackMessageSurface):
|
|
|
270
270
|
def get_blocks(self) -> list[Block]:
|
|
271
271
|
fields = [
|
|
272
272
|
f"{self.incident.priority.emoji} *Priority:* {self.incident.priority.name}",
|
|
273
|
-
f":package: *
|
|
273
|
+
f":package: *Issue category:* {self.incident.component.name}",
|
|
274
274
|
f"{SLACK_APP_EMOJI} <{self.incident.status_page_url + '?utm_medium=FireFighter+Slack&utm_source=Slack+Message&utm_campaign=Announcement+Message+General'}|*{APP_DISPLAY_NAME} Status Page*>",
|
|
275
275
|
f":speaking_head_in_silhouette: *Opened by:* {user_slack_handle_or_name(self.incident.created_by)}",
|
|
276
276
|
f":calendar: *Created at:* {date_time(self.incident.created_at)}",
|
|
@@ -486,7 +486,7 @@ class SlackMessageIncidentStatusUpdated(SlackMessageSurface):
|
|
|
486
486
|
if self.incident_update.component:
|
|
487
487
|
fields.append(
|
|
488
488
|
MarkdownTextObject(
|
|
489
|
-
text=f":package: *
|
|
489
|
+
text=f":package: *Issue category:* {self.incident.component.group.name} - {self.incident.component.name}"
|
|
490
490
|
)
|
|
491
491
|
)
|
|
492
492
|
if self.incident_update.environment:
|
|
@@ -148,7 +148,7 @@ def _home_incident_element(
|
|
|
148
148
|
text=f":rotating_light: *Priority:* {incident.priority.emoji} {incident.priority.name}"
|
|
149
149
|
),
|
|
150
150
|
MarkdownTextObject(
|
|
151
|
-
text=f":package: *
|
|
151
|
+
text=f":package: *Issue category:* {incident.component.group.name} - {incident.component.name}"
|
|
152
152
|
),
|
|
153
153
|
MarkdownTextObject(
|
|
154
154
|
text=f":speaking_head_in_silhouette: *Last update:* {date_time(incident.updated_at)}"
|
|
@@ -12,7 +12,6 @@ from django.utils.translation import ngettext
|
|
|
12
12
|
from slack_sdk.models.blocks.basic_components import MarkdownTextObject, Option
|
|
13
13
|
from slack_sdk.models.blocks.block_elements import ButtonElement, StaticSelectElement
|
|
14
14
|
from slack_sdk.models.blocks.blocks import (
|
|
15
|
-
ActionsBlock,
|
|
16
15
|
Block,
|
|
17
16
|
ContextBlock,
|
|
18
17
|
DividerBlock,
|
|
@@ -74,7 +73,9 @@ class OpenModal(SlackModal):
|
|
|
74
73
|
# 1. Check if impact form is good
|
|
75
74
|
is_impact_form_valid: bool = self._check_impact_form(open_incident_context)
|
|
76
75
|
|
|
77
|
-
# 2.
|
|
76
|
+
# 2. Auto-determine response type based on priority
|
|
77
|
+
self._auto_set_response_type(open_incident_context)
|
|
78
|
+
|
|
78
79
|
incident_type_value: str | None = open_incident_context.get(
|
|
79
80
|
"incident_type", None
|
|
80
81
|
)
|
|
@@ -181,11 +182,18 @@ class OpenModal(SlackModal):
|
|
|
181
182
|
SelectImpactModal,
|
|
182
183
|
)
|
|
183
184
|
|
|
185
|
+
# Check if we have actual impacts (not all "NO") by checking if response_type is set
|
|
186
|
+
has_real_impacts = open_incident_context.get("response_type") is not None
|
|
187
|
+
|
|
188
|
+
# Show ✅ only if form is valid AND has real impacts, otherwise 📝
|
|
189
|
+
emoji = "✅" if impact_form_done and has_real_impacts else "📝"
|
|
190
|
+
button_text = "Edit impacts" if impact_form_done and has_real_impacts else "Set impacts"
|
|
191
|
+
|
|
184
192
|
return [
|
|
185
193
|
SectionBlock(
|
|
186
|
-
text=f"{
|
|
194
|
+
text=f"{emoji} First, define the incident impacts and priority.",
|
|
187
195
|
accessory=ButtonElement(
|
|
188
|
-
text=
|
|
196
|
+
text=button_text,
|
|
189
197
|
action_id=SelectImpactModal.push_action,
|
|
190
198
|
value=json.dumps(open_incident_context, cls=SlackFormJSONEncoder),
|
|
191
199
|
),
|
|
@@ -292,11 +300,11 @@ class OpenModal(SlackModal):
|
|
|
292
300
|
|
|
293
301
|
if slack_msg is None:
|
|
294
302
|
slack_msg = "> :slack: A dedicated Slack channel will be created, and responders will be invited to help.\n"
|
|
295
|
-
text =
|
|
303
|
+
text = "> :jira_new: An associated Jira ticket will also be created."
|
|
296
304
|
if not is_during_office_hours(timezone.now()):
|
|
297
305
|
text += "\n> :pagerduty: If you need it, you'll be able to escalate the incident to our 24/7 on-call response teams."
|
|
298
306
|
else:
|
|
299
|
-
text = "> :
|
|
307
|
+
text = "> :jira_new: A Jira ticket will be created."
|
|
300
308
|
done_review_blocks += [SectionBlock(text=text)]
|
|
301
309
|
|
|
302
310
|
return done_review_blocks
|
|
@@ -371,40 +379,53 @@ class OpenModal(SlackModal):
|
|
|
371
379
|
|
|
372
380
|
return is_valid, details_form_class, details_form
|
|
373
381
|
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _auto_set_response_type(open_incident_context: OpeningData) -> None:
|
|
384
|
+
"""Auto-determine response type based on priority from impact form."""
|
|
385
|
+
impact_form_data = open_incident_context.get("impact_form_data")
|
|
386
|
+
if not impact_form_data:
|
|
387
|
+
# Clear response_type and priority if no impact data
|
|
388
|
+
open_incident_context.pop("response_type", None)
|
|
389
|
+
open_incident_context.pop("priority", None)
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
impact_form = SelectImpactForm(impact_form_data)
|
|
393
|
+
if not impact_form.is_valid():
|
|
394
|
+
# Clear response_type and priority if form is invalid
|
|
395
|
+
open_incident_context.pop("response_type", None)
|
|
396
|
+
open_incident_context.pop("priority", None)
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
priority_value = impact_form.suggest_priority_from_impact()
|
|
400
|
+
|
|
401
|
+
# If no impacts are selected (all set to "NO"), don't set priority/response_type
|
|
402
|
+
# Priority value 6 corresponds to LevelChoices.NONE.priority
|
|
403
|
+
if priority_value == 6:
|
|
404
|
+
open_incident_context.pop("response_type", None)
|
|
405
|
+
open_incident_context.pop("priority", None)
|
|
406
|
+
return
|
|
407
|
+
|
|
408
|
+
priority = Priority.objects.get(value=priority_value)
|
|
409
|
+
|
|
410
|
+
# Set priority in context
|
|
411
|
+
open_incident_context["priority"] = priority
|
|
412
|
+
|
|
413
|
+
# Set response type based on priority recommendation
|
|
414
|
+
if priority.recommended_response_type:
|
|
415
|
+
open_incident_context["response_type"] = cast("ResponseType | None", priority.recommended_response_type)
|
|
416
|
+
else:
|
|
417
|
+
# Default fallback: P1/P2/P3 = critical, P4/P5 = normal
|
|
418
|
+
response_type = cast("ResponseType", "critical" if priority_value < 4 else "normal")
|
|
419
|
+
open_incident_context["response_type"] = response_type
|
|
420
|
+
|
|
374
421
|
@staticmethod
|
|
375
422
|
def _build_response_type_blocks(open_incident_context: OpeningData) -> list[Block]:
|
|
376
423
|
selected_response_type = open_incident_context.get("response_type")
|
|
377
424
|
if selected_response_type not in {"critical", "normal"}:
|
|
378
425
|
return []
|
|
379
426
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
)
|
|
383
|
-
elements: list[ButtonElement] = []
|
|
384
|
-
|
|
385
|
-
for response_type in response_types:
|
|
386
|
-
if response_type != selected_response_type:
|
|
387
|
-
continue
|
|
388
|
-
|
|
389
|
-
is_selected = (
|
|
390
|
-
open_incident_context.get("response_type") == response_type
|
|
391
|
-
or len(INCIDENT_TYPES) == 1
|
|
392
|
-
)
|
|
393
|
-
style: str | None = "primary" if is_selected else None
|
|
394
|
-
text = (
|
|
395
|
-
":slack: Slack :jira_new: Jira ticket"
|
|
396
|
-
if response_type == "critical"
|
|
397
|
-
else ":jira_new: Jira ticket"
|
|
398
|
-
)
|
|
399
|
-
button = ButtonElement(
|
|
400
|
-
text=text,
|
|
401
|
-
action_id=f"incident_open_set_res_type_{response_type}",
|
|
402
|
-
value=json.dumps(open_incident_context, cls=SlackFormJSONEncoder),
|
|
403
|
-
style=style,
|
|
404
|
-
)
|
|
405
|
-
elements.append(button)
|
|
406
|
-
|
|
407
|
-
blocks: list[Block] = [ActionsBlock(elements=elements)]
|
|
427
|
+
blocks: list[Block] = []
|
|
428
|
+
# No buttons needed - response type is auto-determined
|
|
408
429
|
if impact_form_data := open_incident_context.get("impact_form_data"):
|
|
409
430
|
impact_form = SelectImpactForm(impact_form_data)
|
|
410
431
|
if impact_form.is_valid():
|
|
@@ -414,6 +435,7 @@ class OpenModal(SlackModal):
|
|
|
414
435
|
process = ":slack: Slack :jira_new: Jira ticket" if open_incident_context.get("response_type") == "critical" else ":jira_new: Jira ticket"
|
|
415
436
|
|
|
416
437
|
impact_descriptions = OpenModal._get_impact_descriptions(open_incident_context)
|
|
438
|
+
|
|
417
439
|
blocks.append(
|
|
418
440
|
ContextBlock(
|
|
419
441
|
elements=[
|
|
@@ -422,12 +444,12 @@ class OpenModal(SlackModal):
|
|
|
422
444
|
f"> ⏱️ SLA: {priority.sla}\n"
|
|
423
445
|
f"> :gear: Process: {process}\n"
|
|
424
446
|
f"> :pushpin: Selected impacts:\n"
|
|
425
|
-
f"{impact_descriptions}
|
|
447
|
+
f"{impact_descriptions}"
|
|
426
448
|
+ (
|
|
427
449
|
(
|
|
428
|
-
"
|
|
450
|
+
"> :warning: Critical incidents are for *emergency* only"
|
|
429
451
|
+ (
|
|
430
|
-
f" <{SLACK_SEVERITY_HELP_GUIDE_URL}|
|
|
452
|
+
f" <{SLACK_SEVERITY_HELP_GUIDE_URL}|more info>"
|
|
431
453
|
if SLACK_SEVERITY_HELP_GUIDE_URL
|
|
432
454
|
else "."
|
|
433
455
|
)
|
|
@@ -459,18 +481,56 @@ class OpenModal(SlackModal):
|
|
|
459
481
|
@staticmethod
|
|
460
482
|
def _get_impact_descriptions(open_incident_context: OpeningData) -> str:
|
|
461
483
|
impact_form_data = open_incident_context.get("impact_form_data", {})
|
|
484
|
+
if not impact_form_data:
|
|
485
|
+
return ""
|
|
486
|
+
|
|
462
487
|
impact_descriptions = ""
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
488
|
+
for field_name, original_value in impact_form_data.items():
|
|
489
|
+
value = original_value
|
|
490
|
+
# Handle case where value might be an ID instead of an object
|
|
491
|
+
if isinstance(value, int | str) and not hasattr(value, "name"):
|
|
492
|
+
# Try to get the object from the database
|
|
493
|
+
form = SelectImpactForm()
|
|
494
|
+
if field_name in form.fields:
|
|
495
|
+
field = form.fields[field_name]
|
|
496
|
+
if hasattr(field, "queryset") and field.queryset is not None:
|
|
497
|
+
try:
|
|
498
|
+
value = field.queryset.get(pk=value)
|
|
499
|
+
except field.queryset.model.DoesNotExist:
|
|
500
|
+
logger.warning(f"Could not find impact object with pk={value} for field {field_name}")
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
description = OpenModal._format_single_impact_description(value)
|
|
504
|
+
if description:
|
|
505
|
+
impact_descriptions += description
|
|
472
506
|
return impact_descriptions
|
|
473
507
|
|
|
508
|
+
@staticmethod
|
|
509
|
+
def _format_single_impact_description(value: Any) -> str:
|
|
510
|
+
"""Format a single impact value into description text."""
|
|
511
|
+
# Handle object with name and description attributes (impact levels)
|
|
512
|
+
if hasattr(value, "name") and hasattr(value, "description"):
|
|
513
|
+
if value.name == "NO" or not value.description:
|
|
514
|
+
return ""
|
|
515
|
+
|
|
516
|
+
description = ""
|
|
517
|
+
# Add impact type header if available
|
|
518
|
+
if hasattr(value, "impact_type_id") and value.impact_type_id:
|
|
519
|
+
try:
|
|
520
|
+
impact_type = ImpactType.objects.get(pk=value.impact_type_id)
|
|
521
|
+
# Use value.name instead of value to avoid showing IDs
|
|
522
|
+
description += f"> \u00A0\u00A0 :exclamation: {impact_type} - {value.name}\n"
|
|
523
|
+
except ImpactType.DoesNotExist:
|
|
524
|
+
description += f"> \u00A0\u00A0 :exclamation: {value.name}\n"
|
|
525
|
+
|
|
526
|
+
# Add description lines
|
|
527
|
+
for line in str(value.description).splitlines():
|
|
528
|
+
description += f"> \u00A0\u00A0\u00A0\u00A0\u00A0\u00A0 • {line}\n"
|
|
529
|
+
return description
|
|
530
|
+
|
|
531
|
+
# Skip string values - incident_type is handled separately, not in impact descriptions
|
|
532
|
+
return ""
|
|
533
|
+
|
|
474
534
|
@staticmethod
|
|
475
535
|
def get_details_modal_form_class(
|
|
476
536
|
open_incident_context: OpeningData,
|
|
@@ -531,21 +591,7 @@ class OpenModal(SlackModal):
|
|
|
531
591
|
logger.exception("Error triggering incident workflow")
|
|
532
592
|
# XXX warn the user via DM!
|
|
533
593
|
|
|
534
|
-
|
|
535
|
-
@app.action("incident_open_set_res_type_critical")
|
|
536
|
-
@staticmethod
|
|
537
|
-
def handle_set_incident_response_type_action(
|
|
538
|
-
ack: Ack, body: dict[str, Any]
|
|
539
|
-
) -> None:
|
|
540
|
-
action_name: str = body.get("actions", [{}])[0].get("action_id", "")
|
|
541
|
-
action_name = action_name.replace("incident_open_set_res_type_", "")
|
|
542
|
-
opening_data = cast(
|
|
543
|
-
"OpeningData", json.loads(body.get("actions", [{}])[0].get("value", {})) or {}
|
|
544
|
-
)
|
|
545
|
-
|
|
546
|
-
OpenModal._update_incident_modal(
|
|
547
|
-
action_name, "response_type", ack, body, opening_data
|
|
548
|
-
)
|
|
594
|
+
# Response type buttons removed - now auto-determined based on priority
|
|
549
595
|
|
|
550
596
|
@app.action("set_type")
|
|
551
597
|
@staticmethod
|
|
@@ -567,7 +613,11 @@ class OpenModal(SlackModal):
|
|
|
567
613
|
body: dict[str, Any],
|
|
568
614
|
opening_data: OpeningData,
|
|
569
615
|
) -> None:
|
|
570
|
-
|
|
616
|
+
# Ensure we preserve all existing data, especially impact_form_data
|
|
617
|
+
data: OpeningData = OpeningData()
|
|
618
|
+
data.update(opening_data)
|
|
619
|
+
data[metadata_key] = action_value
|
|
620
|
+
|
|
571
621
|
user = get_user_from_context(body)
|
|
572
622
|
view = cls().build_modal_fn(open_incident_context=data, user=user)
|
|
573
623
|
|
|
@@ -205,7 +205,16 @@ class SelectImpactModal(
|
|
|
205
205
|
def _calculate_proposed_incident_type(
|
|
206
206
|
suggested_priority_value: int,
|
|
207
207
|
) -> ResponseType:
|
|
208
|
-
|
|
208
|
+
try:
|
|
209
|
+
priority = Priority.objects.get(value=suggested_priority_value)
|
|
210
|
+
# Use priority recommendation if available
|
|
211
|
+
if priority.recommended_response_type:
|
|
212
|
+
return cast("ResponseType", priority.recommended_response_type)
|
|
213
|
+
except Priority.DoesNotExist:
|
|
214
|
+
logger.warning(f"Priority with value {suggested_priority_value} does not exist")
|
|
215
|
+
|
|
216
|
+
# Fallback logic: P1/P2/P3 = critical, P4/P5 = normal
|
|
217
|
+
return cast("ResponseType", "critical" if suggested_priority_value < 4 else "normal")
|
|
209
218
|
|
|
210
219
|
@staticmethod
|
|
211
220
|
def _update_private_metadata(
|
|
@@ -230,12 +239,36 @@ class SelectImpactModal(
|
|
|
230
239
|
)
|
|
231
240
|
except queryset.model.DoesNotExist:
|
|
232
241
|
form.form.data[field_name] = None # type: ignore
|
|
242
|
+
suggested_priority_value = form.form.suggest_priority_from_impact()
|
|
243
|
+
|
|
244
|
+
# If no impacts are selected (all set to "NO"), don't set priority/response_type
|
|
245
|
+
if suggested_priority_value == 6: # LevelChoices.NONE.priority
|
|
246
|
+
return OpeningData(
|
|
247
|
+
priority=None,
|
|
248
|
+
response_type=None,
|
|
249
|
+
impact_form_data=cast("dict[str, Any]", form.form.data),
|
|
250
|
+
details_form_data=private_metadata_raw.get("details_form_data", {}),
|
|
251
|
+
incident_type=private_metadata_raw.get("incident_type"),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
priority = Priority.objects.get(value=suggested_priority_value)
|
|
256
|
+
except Priority.DoesNotExist as err:
|
|
257
|
+
logger.exception(
|
|
258
|
+
f"Priority with value {suggested_priority_value} does not exist"
|
|
259
|
+
)
|
|
260
|
+
# Fallback to default priority (assuming P3 exists)
|
|
261
|
+
fallback_priority = Priority.objects.filter(value__gte=3).first()
|
|
262
|
+
if not fallback_priority:
|
|
263
|
+
# If no priority exists, create a minimal fallback
|
|
264
|
+
logger.exception("No priority found in database")
|
|
265
|
+
raise ValueError("No priority configuration found in database") from err
|
|
266
|
+
priority = fallback_priority
|
|
267
|
+
|
|
233
268
|
return OpeningData(
|
|
234
|
-
priority=
|
|
235
|
-
value=form.form.suggest_priority_from_impact()
|
|
236
|
-
),
|
|
269
|
+
priority=priority,
|
|
237
270
|
response_type=SelectImpactModal._calculate_proposed_incident_type(
|
|
238
|
-
|
|
271
|
+
suggested_priority_value
|
|
239
272
|
),
|
|
240
273
|
impact_form_data=cast("dict[str, Any]", form.form.data),
|
|
241
274
|
details_form_data=private_metadata_raw.get("details_form_data", {}),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firefighter-incident
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
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=yXPCLHqHWroxl6PYku74xz7jqX7Xrf288eRJfEDWwBo,513
|
|
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
|
|
@@ -287,7 +287,7 @@ firefighter/pagerduty/tasks/__init__.py,sha256=7OtGthF6OX0Dmbe9U81Gk8h7A0dehfsJY
|
|
|
287
287
|
firefighter/pagerduty/tasks/fetch_oncall.py,sha256=knoyHbBLUSrBsrWdv8UODPlByOOvqW5a5hInRa7-lGI,3952
|
|
288
288
|
firefighter/pagerduty/tasks/fetch_services.py,sha256=GD3cCZtIBmI3mnRJnb4HdWeJwIm1X081gTIE0zAZvks,1268
|
|
289
289
|
firefighter/pagerduty/tasks/fetch_users.py,sha256=wPFV9xR2xj3pNJlHPjM9BlX9I6E8hh7wfO29bH-Jz0k,2489
|
|
290
|
-
firefighter/pagerduty/tasks/trigger_oncall.py,sha256=
|
|
290
|
+
firefighter/pagerduty/tasks/trigger_oncall.py,sha256=tQbFlhYf0RMA4KIGU1E3s2f5rYxF6O06CvQiC_Af-9M,3395
|
|
291
291
|
firefighter/pagerduty/templates/pages/oncall_list.html,sha256=fDgQnST-l5-CiIScQLAioT_ewb7Gs6yW0BWo4T_EDbE,4388
|
|
292
292
|
firefighter/pagerduty/templates/pages/oncall_trigger.html,sha256=Un6BgHp8r52cCbqH8FJLI6criaHpzu6BtjJq10kJg4w,568
|
|
293
293
|
firefighter/pagerduty/templates/partials/trigger_oncall_form_view.html,sha256=m3uIodbv3U76QGcGq5ijCQYwe-PvBY2ISXnEMksdlB4,450
|
|
@@ -298,7 +298,7 @@ firefighter/pagerduty/views/oncall_trigger.py,sha256=LYHpWyEaR6O8NazmsTl5ydtw1XH
|
|
|
298
298
|
firefighter/raid/__init__.py,sha256=nMNmvHCSkyLQsdhTow7myMU62vXk1e755gUntVfFFlY,154
|
|
299
299
|
firefighter/raid/admin.py,sha256=YaqjJ7UcoEf_72kKVVN-O2Pp7_9lGuUdc-nHSQZYEYM,1405
|
|
300
300
|
firefighter/raid/apps.py,sha256=BUkaZSu45Ghdpx1XnE3DaFD4SC2SeUtSDZOTYynte5k,1655
|
|
301
|
-
firefighter/raid/client.py,sha256=
|
|
301
|
+
firefighter/raid/client.py,sha256=Y-aAXbVC_seyo4CfBF5CoiTAu36Jq3XKBTsXTLJGFH0,7993
|
|
302
302
|
firefighter/raid/forms.py,sha256=oJOPHYFtwlffuPgFi4lbbOCoKhwianrvafNicOmT_-c,15816
|
|
303
303
|
firefighter/raid/messages.py,sha256=e75kwi0hCe5ChwU4t-_6Q3Rcy22MLLdVSsYyjvG2SCM,5542
|
|
304
304
|
firefighter/raid/models.py,sha256=FNfPHvlF9h09LxL2a4zFPfc0raArmufywjzTdqGATms,2662
|
|
@@ -312,7 +312,7 @@ firefighter/raid/migrations/0001_initial_oss.py,sha256=oZQ44dHboLeHbvVyziHX_hhdi
|
|
|
312
312
|
firefighter/raid/migrations/0002_featureteam_remove_qualifierrotation_jira_user_and_more.py,sha256=1UkwuNqqjtRJ_xlgNMs9qn2ryLcssQN06ZBCL296CWw,1034
|
|
313
313
|
firefighter/raid/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
314
314
|
firefighter/raid/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
315
|
-
firefighter/raid/signals/incident_created.py,sha256=
|
|
315
|
+
firefighter/raid/signals/incident_created.py,sha256=ksXsjiEU-PgEf30XwEXWd7nbrWMXZgwfidiaYvCaOT8,3740
|
|
316
316
|
firefighter/raid/signals/incident_updated.py,sha256=cLiqgCIKjGZpvkb7w3ASDtJ8tWP4agFpmgfmDnT3xOs,1330
|
|
317
317
|
firefighter/raid/tasks/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
318
318
|
firefighter/raid/views/__init__.py,sha256=a-ZiWaudIAH1OkR7FAFSMtasQ1SaB-lfoKJoArKraCM,5182
|
|
@@ -334,7 +334,7 @@ firefighter/slack/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
334
334
|
firefighter/slack/management/commands/generate_manifest.py,sha256=zFWHAC7ioozcDdhJ5KrLU4JNhULdtYjBUJTtclgBgSo,6911
|
|
335
335
|
firefighter/slack/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
336
336
|
firefighter/slack/messages/base.py,sha256=biH-YEAaldJ-OLHEs5ZjW-gtUYUbjOqxrAEflqV2XS0,4593
|
|
337
|
-
firefighter/slack/messages/slack_messages.py,sha256=
|
|
337
|
+
firefighter/slack/messages/slack_messages.py,sha256=yzxN0v-G0KNlz-rA6va9MlweOZxCTJhgjfQl662F0dc,33072
|
|
338
338
|
firefighter/slack/migrations/0001_initial_oss.py,sha256=XmTPgq7zCME2xDwzRFoVi4OegSIG9eSKoyTNoW05Qtg,12933
|
|
339
339
|
firefighter/slack/migrations/0002_usergroup_tag.py,sha256=098tmGA81mT-R2uhb6uQfZ7gKiRG9bFhEwQ8rrp4SKM,583
|
|
340
340
|
firefighter/slack/migrations/0003_alter_usergroup_tag.py,sha256=ncH3KUWEPZHlbdcAtOJ0KGt5H6EX-cKspTGU3osrAhE,591
|
|
@@ -374,7 +374,7 @@ firefighter/slack/views/events/channel_shared.py,sha256=179KUJ8wZ1hPFXF58Neh3njJ
|
|
|
374
374
|
firefighter/slack/views/events/channel_unarchive.py,sha256=YSwMz9H5CeXXeLOUHWI6uAIS4q_oxe4fyMJ9ha0-fuk,968
|
|
375
375
|
firefighter/slack/views/events/channel_unshared.py,sha256=attB5woORof1OOECyfAbxLqL0QrUtdSVfOdEIjS4YgU,734
|
|
376
376
|
firefighter/slack/views/events/commands.py,sha256=M65X9gtjKOQtbuMuWoeJHqyskw6J0bgNUwyjydVmxLY,4827
|
|
377
|
-
firefighter/slack/views/events/home.py,sha256=
|
|
377
|
+
firefighter/slack/views/events/home.py,sha256=xqQeLfxfvcRrjT-JPou3ddbRSKGTZvnyLfokxHh_K_I,6415
|
|
378
378
|
firefighter/slack/views/events/member_joined_channel.py,sha256=BJySHHTAuDotyRK0TXBiyqzZXsEDZGKM_4KILdXUr5g,2370
|
|
379
379
|
firefighter/slack/views/events/member_left_channel.py,sha256=49CQxYvjJwFEBnnpgrLl8WFk8ArOrj-L7GPNIlULQVw,2181
|
|
380
380
|
firefighter/slack/views/events/message.py,sha256=c8tvo0btOUu_5Bc83oiO3IQbaEyoRiUWFk118yqF90g,2068
|
|
@@ -385,7 +385,7 @@ firefighter/slack/views/modals/close.py,sha256=ur1SSRWk9NYFfL24gjOqoIiXKquDy6qeE
|
|
|
385
385
|
firefighter/slack/views/modals/downgrade_workflow.py,sha256=S0y0_GYH4q7ewZUr_eA9Ly2c1FQueZzNCTiuIiWYUoY,3109
|
|
386
386
|
firefighter/slack/views/modals/edit.py,sha256=60xav4XG4KGS9KknqsQNCQjl3qQzk7OtmHiEYTQ9pUk,3861
|
|
387
387
|
firefighter/slack/views/modals/key_event_message.py,sha256=ga3-ITZyzJExwzctX-GfgnDqyQaxTfcqpqnOwY2E38M,5620
|
|
388
|
-
firefighter/slack/views/modals/open.py,sha256=
|
|
388
|
+
firefighter/slack/views/modals/open.py,sha256=en8F0T36A1FbdEaLKEWAuV8Vr5D47x9S1KdtRa_0Kg8,25348
|
|
389
389
|
firefighter/slack/views/modals/postmortem.py,sha256=AeEtmiam_XgCRxDmltKluNT2VN1gcuCB2VbYeeATVcA,2525
|
|
390
390
|
firefighter/slack/views/modals/select.py,sha256=Y-Ji_ALnzhYkXDBAyi497UL1Xn2vCGqXCtj8eog75Jk,3312
|
|
391
391
|
firefighter/slack/views/modals/send_sos.py,sha256=bP6HgYyDwPrIcTq7n_sQz6UQsxhYbvBDS4HjM0uRccA,4838
|
|
@@ -402,7 +402,7 @@ firefighter/slack/views/modals/base_modal/mixins.py,sha256=c7WYs0aXKXVktEMNSZ8IU
|
|
|
402
402
|
firefighter/slack/views/modals/base_modal/modal_utils.py,sha256=1uHTlLxxeXUQttH3bHaehJwCuI6a-h04s-GzdnVA4sI,2459
|
|
403
403
|
firefighter/slack/views/modals/opening/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
404
404
|
firefighter/slack/views/modals/opening/check_current_incidents.py,sha256=28GN0SXP7rVPa55arX1aI98k45w9568GCRDA73eCHEM,2535
|
|
405
|
-
firefighter/slack/views/modals/opening/select_impact.py,sha256=
|
|
405
|
+
firefighter/slack/views/modals/opening/select_impact.py,sha256=McVKE5z8vjcg0Z1kbqTsXBW9FvTqX02W6HiIPQ8cicI,11424
|
|
406
406
|
firefighter/slack/views/modals/opening/set_details.py,sha256=i6zQM2FYz3Z6s5AZH7lXgB2e8yjS0rDwgfMBZaiOqIw,5791
|
|
407
407
|
firefighter/slack/views/modals/opening/types.py,sha256=ETpp0DAz5OMI5h7iv62Of7yJCbI-Q4-3kKSS6msPQeY,563
|
|
408
408
|
firefighter/slack/views/modals/opening/details/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -444,12 +444,12 @@ firefighter_tests/test_slack/test_models/test_conversations.py,sha256=t3ttmgwiu7
|
|
|
444
444
|
firefighter_tests/test_slack/test_models/test_incident_channel.py,sha256=qWoGe9iadmK6-R8usWvjH87AHRkvhG_dHQeC3kHeJrs,17487
|
|
445
445
|
firefighter_tests/test_slack/test_models/test_slack_user.py,sha256=uzur-Rf03I5dpUTO4ZI6O1arBUrAorg1Zvgshf8M-J4,7000
|
|
446
446
|
firefighter_tests/test_slack/views/modals/test_close.py,sha256=kcwGwonjIiniGb5f78ZwlKjuvYB-xat-SrbouV9VCEc,42894
|
|
447
|
-
firefighter_tests/test_slack/views/modals/test_open.py,sha256=
|
|
447
|
+
firefighter_tests/test_slack/views/modals/test_open.py,sha256=z3lvAPOXCUSt7i_9jWYcQWGIRwRg7Z1DT6AfMOK22_s,4900
|
|
448
448
|
firefighter_tests/test_slack/views/modals/test_send_sos.py,sha256=_rE6jD-gOzcGyhlY0R9GzlGtPx65oOOguJYdENgxtLc,1289
|
|
449
449
|
firefighter_tests/test_slack/views/modals/test_status.py,sha256=oQzPfwdg2tkbo9nfkO1GfS3WydxqSC6vy1AZjZDKT30,2226
|
|
450
450
|
firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=Y8Oa_fraj1vtaGig9Y28_6tOWvMrRPS-wyg3rY-DHBk,39380
|
|
451
|
-
firefighter_incident-0.0.
|
|
452
|
-
firefighter_incident-0.0.
|
|
453
|
-
firefighter_incident-0.0.
|
|
454
|
-
firefighter_incident-0.0.
|
|
455
|
-
firefighter_incident-0.0.
|
|
451
|
+
firefighter_incident-0.0.12.dist-info/METADATA,sha256=246jLZgtEuOVdQ5X7fKsv38jrGzyvFsyrILgQ4yjATg,5488
|
|
452
|
+
firefighter_incident-0.0.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
453
|
+
firefighter_incident-0.0.12.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
|
|
454
|
+
firefighter_incident-0.0.12.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
|
|
455
|
+
firefighter_incident-0.0.12.dist-info/RECORD,,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
|
-
from unittest.mock import MagicMock
|
|
4
|
+
from unittest.mock import MagicMock, Mock, patch
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
-
from slack_sdk.models.blocks.block_elements import ButtonElement
|
|
8
7
|
from slack_sdk.models.blocks.blocks import (
|
|
9
|
-
|
|
8
|
+
ContextBlock,
|
|
10
9
|
)
|
|
11
10
|
|
|
12
11
|
from firefighter.incidents.forms.create_incident import CreateIncidentFormBase
|
|
@@ -102,14 +101,32 @@ def test_validate_details_form_invalid() -> None:
|
|
|
102
101
|
|
|
103
102
|
|
|
104
103
|
def test_build_response_type_blocks_bis(open_incident_context: OpeningData) -> None:
|
|
104
|
+
# With no impact_form_data, should return empty list
|
|
105
105
|
open_incident_context["response_type"] = "critical"
|
|
106
106
|
blocks = OpenModal._build_response_type_blocks(open_incident_context)
|
|
107
|
+
assert len(blocks) == 0
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
# With valid impact_form_data, should return context blocks
|
|
110
|
+
mock_impact_form = Mock()
|
|
111
|
+
mock_impact_form.is_valid.return_value = True
|
|
112
|
+
mock_impact_form.suggest_priority_from_impact.return_value = 1
|
|
113
|
+
|
|
114
|
+
# Mock Priority object
|
|
115
|
+
mock_priority = Mock()
|
|
116
|
+
mock_priority.emoji = "🔴"
|
|
117
|
+
mock_priority.description = "Critical"
|
|
118
|
+
mock_priority.sla = "15 min"
|
|
119
|
+
mock_priority.recommended_response_type = None
|
|
120
|
+
|
|
121
|
+
open_incident_context["impact_form_data"] = {"test_field": "test_value"}
|
|
122
|
+
|
|
123
|
+
with patch("firefighter.slack.views.modals.open.SelectImpactForm", return_value=mock_impact_form), \
|
|
124
|
+
patch("firefighter.slack.views.modals.open.Priority.objects.get", return_value=mock_priority), \
|
|
125
|
+
patch.object(OpenModal, "_get_impact_descriptions", return_value="Test impact"):
|
|
126
|
+
blocks = OpenModal._build_response_type_blocks(open_incident_context)
|
|
127
|
+
assert len(blocks) == 1
|
|
128
|
+
first_block = blocks[0]
|
|
129
|
+
assert isinstance(first_block, ContextBlock)
|
|
113
130
|
|
|
114
131
|
|
|
115
132
|
@pytest.mark.django_db
|
|
File without changes
|
{firefighter_incident-0.0.10.dist-info → firefighter_incident-0.0.12.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{firefighter_incident-0.0.10.dist-info → firefighter_incident-0.0.12.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|