firefighter-incident 0.0.9__py3-none-any.whl → 0.0.11__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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.0.9'
21
- __version_tuple__ = version_tuple = (0, 0, 9)
20
+ __version__ = version = '0.0.11'
21
+ __version_tuple__ = version_tuple = (0, 0, 11)
@@ -25,7 +25,7 @@ class Kwargs(TypedDict, total=False):
25
25
 
26
26
 
27
27
  @component.register("avatar")
28
- class Avatar(component.Component): # type: ignore[type-var]
28
+ class Avatar(component.Component):
29
29
  template_name = "avatar/avatar.html"
30
30
 
31
31
  def get_context_data(self, user: User, **kwargs: Any) -> Data:
@@ -14,7 +14,7 @@ class Data(TypedDict, total=False):
14
14
 
15
15
 
16
16
  @component.register("card")
17
- class Card(component.Component): # type: ignore[type-var]
17
+ class Card(component.Component):
18
18
  template_name = "card/card.html"
19
19
 
20
20
  def get_context_data(self, *args: Any, **kwargs: Unpack[Data]) -> Data:
@@ -27,7 +27,7 @@ class Kwargs(TypedDict, total=False):
27
27
 
28
28
 
29
29
  @component.register("export_button")
30
- class ExportButton(component.Component): # type: ignore[type-var]
30
+ class ExportButton(component.Component):
31
31
  template_name = "export_button/export_button.html"
32
32
 
33
33
  def get_context_data(
@@ -19,8 +19,8 @@ class Data(TypedDict):
19
19
 
20
20
 
21
21
  @component.register("form")
22
- class Form(component.Component): # type: ignore[type-var] # type: ignore[override]
22
+ class FormComponent(component.Component):
23
23
  template_name = "form/form.html"
24
24
 
25
- def get_context_data(self, form: forms.Form, **kwargs: Any) -> Data: # type: ignore[override]
25
+ def get_context_data(self, form: forms.Form, **kwargs: Any) -> Data:
26
26
  return Data(form=form)
@@ -22,7 +22,7 @@ class Kwargs(TypedDict, total=True):
22
22
 
23
23
 
24
24
  @component.register("form_field")
25
- class FormField(component.Component): # type: ignore[type-var]
25
+ class FormField(component.Component):
26
26
  template_name = "form_field/form_field.html"
27
27
 
28
28
  def get_context_data(
@@ -14,7 +14,7 @@ class Data(TypedDict):
14
14
 
15
15
 
16
16
  @component.register("messages")
17
- class Messages(component.Component): # type: ignore[type-var]
17
+ class MessagesComponent(component.Component):
18
18
  template_name = "messages/messages.html"
19
19
 
20
20
  def get_context_data(self, messages: BaseStorage, **kwargs: Any) -> Data:
@@ -28,7 +28,7 @@ class Slots(TypedDict):
28
28
 
29
29
 
30
30
  @component.register("modal")
31
- class Modal(component.Component): # type: ignore[type-var]
31
+ class Modal(component.Component):
32
32
  template_name = "modal/modal.html"
33
33
 
34
34
  def get_context_data(self, *args: Any, **kwargs: Unpack[Kwargs]) -> Data:
@@ -39,7 +39,7 @@ class SelectImpactForm(forms.Form):
39
39
 
40
40
  super().__init__(*args, **kwargs)
41
41
 
42
- for impact_type in ImpactType.objects.all().order_by("-order"):
42
+ for impact_type in ImpactType.objects.all().order_by("order"):
43
43
  field_name = f"set_impact_type_{impact_type.value}"
44
44
  self.fields[field_name] = forms.ModelChoiceField(
45
45
  label=impact_type.emoji + " " + impact_type.name,
@@ -0,0 +1,28 @@
1
+ # Generated by ChatGPT on 2024-06-24
2
+ from django.db import migrations
3
+
4
+
5
+ def reorder_impact_types(apps, schema_editor):
6
+ ImpactType = apps.get_model("incidents", "ImpactType")
7
+ # On suppose que les valeurs sont :
8
+ # business_impact, customers_impact, sellers_impact, employees_impact
9
+ order_map = {
10
+ "business_impact": 1,
11
+ "customers_impact": 2,
12
+ "sellers_impact": 3,
13
+ "employees_impact": 4,
14
+ }
15
+ for value, order in order_map.items():
16
+ impact_type = ImpactType.objects.filter(value=value).first()
17
+ if impact_type:
18
+ impact_type.order = order
19
+ impact_type.save(update_fields=["order"])
20
+
21
+
22
+ class Migration(migrations.Migration):
23
+ dependencies = [
24
+ ("incidents", "0016_update_business_incidents_and_level"),
25
+ ]
26
+ operations = [
27
+ migrations.RunPython(reorder_impact_types),
28
+ ]
@@ -0,0 +1,48 @@
1
+ # Generated by ChatGPT on 2024-06-24
2
+ from django.db import migrations
3
+
4
+
5
+ def update_impactlevel_names(apps, schema_editor):
6
+ ImpactType = apps.get_model("incidents", "ImpactType")
7
+ ImpactLevel = apps.get_model("incidents", "ImpactLevel")
8
+
9
+ # Map: (impact_type.value, impact_level.value) -> new name
10
+ updates = {
11
+ ("business_impact", "HT"): "Critical Impact (>5% BV loss)",
12
+ ("business_impact", "HI"): "Major Impact (<5% BV loss)",
13
+ ("business_impact", "MD"): "Uncertain Business Impact",
14
+ ("business_impact", "LO"): "Low Impact not measurable",
15
+ ("business_impact", "LT"): "Very Low Impact not measurable",
16
+ ("customers_impact", "HT"): "Critical issue for many customers",
17
+ ("customers_impact", "HI"): "Major issue for many customers",
18
+ ("customers_impact", "MD"): "Major issue for few customers",
19
+ ("customers_impact", "LO"): "Minor issue for customers",
20
+ ("customers_impact", "LT"): "Cosmetic minor issue for customers",
21
+ ("sellers_impact", "HT"): "Critical issue for many sellers",
22
+ ("sellers_impact", "HI"): "Major issue for many sellers",
23
+ ("sellers_impact", "MD"): "Major issue for few sellers",
24
+ ("sellers_impact", "LO"): "Minor issue for sellers",
25
+ ("sellers_impact", "LT"): "Cosmetic minor issue for sellers",
26
+ ("employees_impact", "MD"): "Departments fully blocked",
27
+ ("employees_impact", "LO"): "Some employees affected",
28
+ ("employees_impact", "LT"): "Degraded service for employees",
29
+ ("employees_impact", "NO"): "No impact for employees",
30
+ }
31
+
32
+ for (impact_type_value, level_value), new_name in updates.items():
33
+ impact_type = ImpactType.objects.filter(value=impact_type_value).first()
34
+ if not impact_type:
35
+ continue
36
+ impact_level = ImpactLevel.objects.filter(impact_type=impact_type, value=level_value).first()
37
+ if impact_level:
38
+ impact_level.name = new_name
39
+ impact_level.save(update_fields=["name"])
40
+
41
+
42
+ class Migration(migrations.Migration):
43
+ dependencies = [
44
+ ("incidents", "0017_reorder_impact_types"),
45
+ ]
46
+ operations = [
47
+ migrations.RunPython(update_impactlevel_names),
48
+ ]
@@ -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,
@@ -23,6 +22,7 @@ from slack_sdk.models.views import View
23
22
  from firefighter.firefighter.utils import is_during_office_hours
24
23
  from firefighter.incidents.enums import IncidentStatus
25
24
  from firefighter.incidents.forms.select_impact import SelectImpactForm
25
+ from firefighter.incidents.models.impact import ImpactType
26
26
  from firefighter.incidents.models.incident import Incident
27
27
  from firefighter.incidents.models.priority import Priority
28
28
  from firefighter.slack.slack_app import SlackApp
@@ -73,7 +73,9 @@ class OpenModal(SlackModal):
73
73
  # 1. Check if impact form is good
74
74
  is_impact_form_valid: bool = self._check_impact_form(open_incident_context)
75
75
 
76
- # 2. Check if we have a normal incident type
76
+ # 2. Auto-determine response type based on priority
77
+ self._auto_set_response_type(open_incident_context)
78
+
77
79
  incident_type_value: str | None = open_incident_context.get(
78
80
  "incident_type", None
79
81
  )
@@ -180,11 +182,18 @@ class OpenModal(SlackModal):
180
182
  SelectImpactModal,
181
183
  )
182
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
+
183
192
  return [
184
193
  SectionBlock(
185
- text=f"{'✅' if impact_form_done else '📝'} First, define the incident impacts and priority.",
194
+ text=f"{emoji} First, define the incident impacts and priority.",
186
195
  accessory=ButtonElement(
187
- text="Edit impacts" if impact_form_done else "Set impacts",
196
+ text=button_text,
188
197
  action_id=SelectImpactModal.push_action,
189
198
  value=json.dumps(open_incident_context, cls=SlackFormJSONEncoder),
190
199
  ),
@@ -370,58 +379,79 @@ class OpenModal(SlackModal):
370
379
 
371
380
  return is_valid, details_form_class, details_form
372
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
+
373
421
  @staticmethod
374
422
  def _build_response_type_blocks(open_incident_context: OpeningData) -> list[Block]:
375
423
  selected_response_type = open_incident_context.get("response_type")
376
424
  if selected_response_type not in {"critical", "normal"}:
377
425
  return []
378
426
 
379
- response_types: list[ResponseType] = cast(
380
- "list[ResponseType]", INCIDENT_TYPES.keys()
381
- )
382
- elements: list[ButtonElement] = []
383
-
384
- for response_type in response_types:
385
- if response_type != selected_response_type:
386
- continue
387
-
388
- is_selected = (
389
- open_incident_context.get("response_type") == response_type
390
- or len(INCIDENT_TYPES) == 1
391
- )
392
- style: str | None = "primary" if is_selected else None
393
- text = (
394
- ":slack: Slack :jira_new: Jira ticket"
395
- if response_type == "critical"
396
- else ":jira_new: Jira ticket"
397
- )
398
- button = ButtonElement(
399
- text=text,
400
- action_id=f"incident_open_set_res_type_{response_type}",
401
- value=json.dumps(open_incident_context, cls=SlackFormJSONEncoder),
402
- style=style,
403
- )
404
- elements.append(button)
405
-
406
- blocks: list[Block] = [ActionsBlock(elements=elements)]
427
+ blocks: list[Block] = []
428
+ # No buttons needed - response type is auto-determined
407
429
  if impact_form_data := open_incident_context.get("impact_form_data"):
408
430
  impact_form = SelectImpactForm(impact_form_data)
409
431
  if impact_form.is_valid():
410
432
  priority: Priority = Priority.objects.get(
411
433
  value=impact_form.suggest_priority_from_impact()
412
434
  )
435
+ process = ":slack: Slack :jira_new: Jira ticket" if open_incident_context.get("response_type") == "critical" else ":jira_new: Jira ticket"
436
+
437
+ impact_descriptions = OpenModal._get_impact_descriptions(open_incident_context)
438
+
413
439
  blocks.append(
414
440
  ContextBlock(
415
441
  elements=[
416
442
  MarkdownTextObject(
417
- text=f"> {priority.emoji} Selected priority: {priority}"
443
+ text=f"> {priority.emoji} Selected priority: *{priority} - {priority.description}*\n"
444
+ f"> ⏱️ SLA: {priority.sla}\n"
445
+ f"> :gear: Process: {process}\n"
446
+ f"> :pushpin: Selected impacts:\n"
447
+ f"{impact_descriptions}"
418
448
  + (
419
449
  (
420
- "\n> Critical incidents are for *emergency* only"
450
+ "> :warning: Critical incidents are for *emergency* only"
421
451
  + (
422
- f"<{SLACK_SEVERITY_HELP_GUIDE_URL}|learn more>"
452
+ f" <{SLACK_SEVERITY_HELP_GUIDE_URL}|more info>"
423
453
  if SLACK_SEVERITY_HELP_GUIDE_URL
424
- else "" + "."
454
+ else "."
425
455
  )
426
456
  )
427
457
  if selected_response_type == "critical"
@@ -448,6 +478,59 @@ class OpenModal(SlackModal):
448
478
 
449
479
  return blocks
450
480
 
481
+ @staticmethod
482
+ def _get_impact_descriptions(open_incident_context: OpeningData) -> str:
483
+ impact_form_data = open_incident_context.get("impact_form_data", {})
484
+ if not impact_form_data:
485
+ return ""
486
+
487
+ impact_descriptions = ""
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
506
+ return impact_descriptions
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
+
451
534
  @staticmethod
452
535
  def get_details_modal_form_class(
453
536
  open_incident_context: OpeningData,
@@ -508,21 +591,7 @@ class OpenModal(SlackModal):
508
591
  logger.exception("Error triggering incident workflow")
509
592
  # XXX warn the user via DM!
510
593
 
511
- @app.action("incident_open_set_res_type_normal")
512
- @app.action("incident_open_set_res_type_critical")
513
- @staticmethod
514
- def handle_set_incident_response_type_action(
515
- ack: Ack, body: dict[str, Any]
516
- ) -> None:
517
- action_name: str = body.get("actions", [{}])[0].get("action_id", "")
518
- action_name = action_name.replace("incident_open_set_res_type_", "")
519
- opening_data = cast(
520
- "OpeningData", json.loads(body.get("actions", [{}])[0].get("value", {})) or {}
521
- )
522
-
523
- OpenModal._update_incident_modal(
524
- action_name, "response_type", ack, body, opening_data
525
- )
594
+ # Response type buttons removed - now auto-determined based on priority
526
595
 
527
596
  @app.action("set_type")
528
597
  @staticmethod
@@ -544,7 +613,11 @@ class OpenModal(SlackModal):
544
613
  body: dict[str, Any],
545
614
  opening_data: OpeningData,
546
615
  ) -> None:
547
- data: OpeningData = {**opening_data, metadata_key: action_value} # type: ignore
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
+
548
621
  user = get_user_from_context(body)
549
622
  view = cls().build_modal_fn(open_incident_context=data, user=user)
550
623
 
@@ -205,7 +205,16 @@ class SelectImpactModal(
205
205
  def _calculate_proposed_incident_type(
206
206
  suggested_priority_value: int,
207
207
  ) -> ResponseType:
208
- return "critical" if suggested_priority_value <= 3 else "normal"
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=Priority.objects.get(
235
- value=form.form.suggest_priority_from_impact()
236
- ),
269
+ priority=priority,
237
270
  response_type=SelectImpactModal._calculate_proposed_incident_type(
238
- form.form.suggest_priority_from_impact()
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.9
3
+ Version: 0.0.11
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=J0-psBAr052UgYPFe4_RoTpIwBL8wFdIM3d_qC8JwcE,511
9
+ firefighter/_version.py,sha256=rk0lhpp6Em5toAI4J7GwApfOdY7w_QTcFpJpUR4GdVY,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
@@ -32,25 +32,25 @@ firefighter/api/views/severities.py,sha256=mdkR4GjZibydC1Dx-Sglm-f35GZxWbjmqStAx
32
32
  firefighter/components/__init__.py,sha256=Vd_Uk5Uq7Mqp6NOFp5QiniWZAyzmYLqNSFEEw1x7COk,167
33
33
  firefighter/components/avatar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  firefighter/components/avatar/avatar.html,sha256=oleFPTy1qs9X6hZx3iWppBteHummFvSxoNzPOOl5aeA,773
35
- firefighter/components/avatar/avatar.py,sha256=Gx2IFJz5sfw8sziAViiqU7oVRNfCT8YV7UVeYiopzzw,849
35
+ firefighter/components/avatar/avatar.py,sha256=AIqffX8I_sJ7oEpuyIswJkB6KuW51FuEa4tBn9eSths,823
36
36
  firefighter/components/card/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  firefighter/components/card/card.html,sha256=ziahql8X7WiQdprMktzy7sx0gVXNIxqtAsh_Rc5Iy0g,725
38
- firefighter/components/card/card.py,sha256=lro45nf9Z02rhlGU6706r1jbPhtidGKVYeFqZ_A5puU,513
38
+ firefighter/components/card/card.py,sha256=yB4veoRQ3zfdJPCsAGIYtmLCDz3G2kmcYFg12jzkNks,487
39
39
  firefighter/components/export_button/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  firefighter/components/export_button/export_button.html,sha256=XTA9FbDxho8aIqnfTcsDUm7swGWfn2JSw5WndDaThYY,1989
41
- firefighter/components/export_button/export_button.py,sha256=0jzBjMTjduM7DF0-czv9B7J_PVPUQ2RUufUJtNdT-tk,1729
41
+ firefighter/components/export_button/export_button.py,sha256=AqkkaZFGVzuLKpIxcPZ-vcXG9VXf9jqpfyxYePiGe4E,1703
42
42
  firefighter/components/form/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  firefighter/components/form/form.html,sha256=LtW07AxFsR-MI-iGVq0CsUVsUN3af5rS2R4SbAlfI5s,312
44
- firefighter/components/form/form.py,sha256=DowWqIk8oSz3Z5zKKMqXlclCJMiVveaqrSD8AnfQ3bY,665
44
+ firefighter/components/form/form.py,sha256=92qb1HTO3YvYlQMsw6uN8QUQZpbRbyGaCnPGuGX7VDs,596
45
45
  firefighter/components/form_field/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  firefighter/components/form_field/form_field.html,sha256=k5yyxvETqJSt5jHb8Z2v5o9oJRDLwWBKhISy0j0184Y,450
47
- firefighter/components/form_field/form_field.py,sha256=s_TXauUVNDUcyynCPKHgh-RTNYEVfJE_Dwd-8WPeC84,760
47
+ firefighter/components/form_field/form_field.py,sha256=QZrmEh-3wN8TUdx6db9_8rSpKuTfbyQ-1K0N7st4CYg,734
48
48
  firefighter/components/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  firefighter/components/messages/messages.html,sha256=P0V6Wsz7lTZL0xNPu_5w-J6Dc_aPkP0wqbHjMJ1D4qs,3157
50
- firefighter/components/messages/messages.py,sha256=FL8Um_j66qsAqMJ4tppjANrr2g3uib6amvsUgZ0DHTM,536
50
+ firefighter/components/messages/messages.py,sha256=vQ1aaIkRo9T9xtIfYneelrUbBw0cf9BOoaphEznmWS4,519
51
51
  firefighter/components/modal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  firefighter/components/modal/modal.html,sha256=PrOtS8eJHvzTHqILlXmjE4KsGVdbubSCexHiPa_I3Yk,2391
53
- firefighter/components/modal/modal.py,sha256=PNlncLbCir-WPd8en18OXe9OQAzS7hICNR7P4sHU-Us,895
53
+ firefighter/components/modal/modal.py,sha256=rpleZPmOWNCSQAEXTsQRfETwbyodjT3o7ni-pIqMWiI,869
54
54
  firefighter/confluence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  firefighter/confluence/admin.py,sha256=aDXghuuLc7G_TLt-655M31smx-H6vkIgLtEmmNCA3lg,1490
56
56
  firefighter/confluence/apps.py,sha256=vKswBwQL7L9e2JQwvRb7xy3myyE_GRldYX78jSY3XCM,406
@@ -140,7 +140,7 @@ firefighter/incidents/forms/__init__.py,sha256=OU0r5eZc2A0UJNsL83n8AI5EvwUvg4Yx0
140
140
  firefighter/incidents/forms/close_incident.py,sha256=syT5Lpr_WXNFT3KGCe1oy-FzOqMt98S7YEzovdnp7To,940
141
141
  firefighter/incidents/forms/create_incident.py,sha256=Wpp0qqUJQs5-5BXrS-P5-dGvM5zgr9XqaEEl6tpNZi4,2739
142
142
  firefighter/incidents/forms/edit.py,sha256=2rQkiKak-vac-K3cIsqlGv4R5nhI7JLxw3DhFMXbWms,956
143
- firefighter/incidents/forms/select_impact.py,sha256=0cnV2DFHuO1In6CgRATjg0zNLd9YN_LbQnPL7Fm9pAU,4631
143
+ firefighter/incidents/forms/select_impact.py,sha256=jLbzVj4UeUGwOYYa5P92PXkEu1J_6H43UATZYzDgSLY,4630
144
144
  firefighter/incidents/forms/update_key_events.py,sha256=1Xmnxe5OgZqLFS2HmMzQm3VGFPQipsdrLgKSwdh-fKc,4441
145
145
  firefighter/incidents/forms/update_roles.py,sha256=Q26UPfwAj-8N23RNZLQkvmHGnS1_j_X5KQWjJmPjMKY,3635
146
146
  firefighter/incidents/forms/update_status.py,sha256=QCRKfDhSYZhVsJ6oofQxOXGMWMDRQEDnH29y8YnFn_Y,1034
@@ -161,6 +161,8 @@ firefighter/incidents/migrations/0013_add_missing_component.py,sha256=qVLQEl-riF
161
161
  firefighter/incidents/migrations/0014_update_components_slack_groups.py,sha256=5tiQnrtOpYVUztFdvZ6xTUReuJfpDX0cOvqIvDZpBv0,8700
162
162
  firefighter/incidents/migrations/0015_update_impact_level.py,sha256=OQVTVrWvQ1orxqdrqwrhBMjxKY5qzqN2ZRobHc_tGDc,5451
163
163
  firefighter/incidents/migrations/0016_update_business_incidents_and_level.py,sha256=O5AL9twmjstSw44ndJ2-Og6dpKreReXVw_brpQb-t0w,3600
164
+ firefighter/incidents/migrations/0017_reorder_impact_types.py,sha256=7NN2KjcDRHNInXyEh8YKZli2LWT_i0UHXBbI21dXB6w,877
165
+ firefighter/incidents/migrations/0018_update_impactlevel_names.py,sha256=E37png_LiXABzuXk-vmxTgikw8YmULODkiytE2mkxT0,2227
164
166
  firefighter/incidents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
165
167
  firefighter/incidents/models/__init__.py,sha256=dCNP-zRYNNDOZB3JDDWp7vCl084Jh6RgDT_iP57RkOY,862
166
168
  firefighter/incidents/models/component.py,sha256=7GyXKNFk1MZns6RUGLpkNw5u6He7H9N1LexzXbG4sBM,7445
@@ -383,7 +385,7 @@ firefighter/slack/views/modals/close.py,sha256=ur1SSRWk9NYFfL24gjOqoIiXKquDy6qeE
383
385
  firefighter/slack/views/modals/downgrade_workflow.py,sha256=S0y0_GYH4q7ewZUr_eA9Ly2c1FQueZzNCTiuIiWYUoY,3109
384
386
  firefighter/slack/views/modals/edit.py,sha256=60xav4XG4KGS9KknqsQNCQjl3qQzk7OtmHiEYTQ9pUk,3861
385
387
  firefighter/slack/views/modals/key_event_message.py,sha256=ga3-ITZyzJExwzctX-GfgnDqyQaxTfcqpqnOwY2E38M,5620
386
- firefighter/slack/views/modals/open.py,sha256=mAjOkvejCciUP7-QQmVfPKmljuEowNdGybAhwyKlfUI,21548
388
+ firefighter/slack/views/modals/open.py,sha256=LX9aBZ4bUosoffJlIepzYjpbf7LsvzppYAjqep8tVtM,25495
387
389
  firefighter/slack/views/modals/postmortem.py,sha256=AeEtmiam_XgCRxDmltKluNT2VN1gcuCB2VbYeeATVcA,2525
388
390
  firefighter/slack/views/modals/select.py,sha256=Y-Ji_ALnzhYkXDBAyi497UL1Xn2vCGqXCtj8eog75Jk,3312
389
391
  firefighter/slack/views/modals/send_sos.py,sha256=bP6HgYyDwPrIcTq7n_sQz6UQsxhYbvBDS4HjM0uRccA,4838
@@ -400,7 +402,7 @@ firefighter/slack/views/modals/base_modal/mixins.py,sha256=c7WYs0aXKXVktEMNSZ8IU
400
402
  firefighter/slack/views/modals/base_modal/modal_utils.py,sha256=1uHTlLxxeXUQttH3bHaehJwCuI6a-h04s-GzdnVA4sI,2459
401
403
  firefighter/slack/views/modals/opening/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
402
404
  firefighter/slack/views/modals/opening/check_current_incidents.py,sha256=28GN0SXP7rVPa55arX1aI98k45w9568GCRDA73eCHEM,2535
403
- firefighter/slack/views/modals/opening/select_impact.py,sha256=qTggWabR6lgs4OZa7wYEmFlQTbgUka1052n01NNLcsc,9754
405
+ firefighter/slack/views/modals/opening/select_impact.py,sha256=McVKE5z8vjcg0Z1kbqTsXBW9FvTqX02W6HiIPQ8cicI,11424
404
406
  firefighter/slack/views/modals/opening/set_details.py,sha256=i6zQM2FYz3Z6s5AZH7lXgB2e8yjS0rDwgfMBZaiOqIw,5791
405
407
  firefighter/slack/views/modals/opening/types.py,sha256=ETpp0DAz5OMI5h7iv62Of7yJCbI-Q4-3kKSS6msPQeY,563
406
408
  firefighter/slack/views/modals/opening/details/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -442,12 +444,12 @@ firefighter_tests/test_slack/test_models/test_conversations.py,sha256=t3ttmgwiu7
442
444
  firefighter_tests/test_slack/test_models/test_incident_channel.py,sha256=qWoGe9iadmK6-R8usWvjH87AHRkvhG_dHQeC3kHeJrs,17487
443
445
  firefighter_tests/test_slack/test_models/test_slack_user.py,sha256=uzur-Rf03I5dpUTO4ZI6O1arBUrAorg1Zvgshf8M-J4,7000
444
446
  firefighter_tests/test_slack/views/modals/test_close.py,sha256=kcwGwonjIiniGb5f78ZwlKjuvYB-xat-SrbouV9VCEc,42894
445
- firefighter_tests/test_slack/views/modals/test_open.py,sha256=Iatphd7vnrEMrv8ysKxDVDAuZNEsVXBksIEpIaDHQss,4100
447
+ firefighter_tests/test_slack/views/modals/test_open.py,sha256=z3lvAPOXCUSt7i_9jWYcQWGIRwRg7Z1DT6AfMOK22_s,4900
446
448
  firefighter_tests/test_slack/views/modals/test_send_sos.py,sha256=_rE6jD-gOzcGyhlY0R9GzlGtPx65oOOguJYdENgxtLc,1289
447
449
  firefighter_tests/test_slack/views/modals/test_status.py,sha256=oQzPfwdg2tkbo9nfkO1GfS3WydxqSC6vy1AZjZDKT30,2226
448
450
  firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=Y8Oa_fraj1vtaGig9Y28_6tOWvMrRPS-wyg3rY-DHBk,39380
449
- firefighter_incident-0.0.9.dist-info/METADATA,sha256=AC3MrJi0Y5Gck0TpVsOIciZCSTtqFB37fecMBDrPFO0,5487
450
- firefighter_incident-0.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
451
- firefighter_incident-0.0.9.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
452
- firefighter_incident-0.0.9.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
453
- firefighter_incident-0.0.9.dist-info/RECORD,,
451
+ firefighter_incident-0.0.11.dist-info/METADATA,sha256=9Xvtj0AnQmzza1kfVGGGEMcJJEokzGn9vUs6FzslagA,5488
452
+ firefighter_incident-0.0.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
453
+ firefighter_incident-0.0.11.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
454
+ firefighter_incident-0.0.11.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
455
+ firefighter_incident-0.0.11.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
- ActionsBlock,
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
- assert len(blocks) == 1
109
- first_block = blocks[0]
110
- assert isinstance(first_block, ActionsBlock)
111
- assert len(first_block.elements) == 1
112
- assert all(isinstance(element, ButtonElement) for element in first_block.elements)
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