firefighter-incident 0.0.5__py3-none-any.whl → 0.0.7__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.5'
21
- __version_tuple__ = version_tuple = (0, 0, 5)
20
+ __version__ = version = '0.0.7'
21
+ __version_tuple__ = version_tuple = (0, 0, 7)
@@ -45,21 +45,21 @@ class SelectImpactForm(forms.Form):
45
45
  label=impact_type.emoji + " " + impact_type.name,
46
46
  queryset=impact_type.levels.all().order_by("-order"),
47
47
  help_text=impact_type.help_text,
48
- initial=impact_type.levels.get(value=LevelChoices.LOWEST.value),
48
+ initial=impact_type.levels.get(value=LevelChoices.NONE.value),
49
49
  )
50
50
  self.fields[field_name].label_from_instance = ( # type: ignore[attr-defined]
51
51
  lambda obj: obj.emoji + " " + obj.name
52
52
  )
53
53
 
54
54
  def suggest_priority_from_impact(self) -> int:
55
- """Suggest a priority from 1 (highest) to 4 (lowest) based on the impact choices."""
55
+ """Suggest a priority from 1 (highest) to 5 (lowest) based on the impact choices."""
56
56
  if self.is_valid():
57
57
  impact: dict[str, ImpactLevel] = self.cleaned_data
58
58
 
59
59
  impact_values = [impact_type.value for impact_type in impact.values()]
60
60
  priorities = [level.priority for level in LevelChoices if level in impact_values]
61
- return min(priorities) if priorities else LevelChoices.LOWEST.priority
62
- return LevelChoices.LOWEST.priority
61
+ return min(priorities) if priorities else LevelChoices.NONE.priority
62
+ return LevelChoices.NONE.priority
63
63
 
64
64
  @property
65
65
  def business_impact_new(self) -> str | None:
@@ -50,7 +50,7 @@ def setup_navbar_menu() -> None:
50
50
  reverse("incidents:incident-statistics"),
51
51
  ),
52
52
  MenuItem(
53
- "Components",
53
+ "Issue categories",
54
54
  reverse("incidents:component-list"),
55
55
  ),
56
56
  ]
@@ -53,6 +53,7 @@ def update_impact_levels(apps, schema_editor):
53
53
  "MD": "▶️",
54
54
  "LO": "🔽",
55
55
  "LT": "⏬",
56
+ "NO": "",
56
57
  }
57
58
 
58
59
  updates = [
@@ -61,105 +62,105 @@ def update_impact_levels(apps, schema_editor):
61
62
  "old_value": "HI",
62
63
  "new_name": None,
63
64
  "new_value": "HT",
64
- "new_order": 20,
65
+ "new_order": 5,
65
66
  },
66
67
  {
67
68
  "old_name": "Some customers have issues",
68
69
  "old_value": "MD",
69
70
  "new_name": "Some customers with major issues",
70
71
  "new_value": "HI",
71
- "new_order": 15,
72
+ "new_order": 4,
72
73
  },
73
74
  {
74
75
  "old_name": "Few customers with minor issues",
75
76
  "old_value": "LO",
76
77
  "new_name": "Some customers with significant issues",
77
78
  "new_value": "MD",
78
- "new_order": 10,
79
+ "new_order": 3,
79
80
  },
80
81
  {
81
82
  "old_name": "No impact on customers",
82
83
  "old_value": "N/A",
83
84
  "new_name": None,
84
85
  "new_value": "LT",
85
- "new_order": 0,
86
+ "new_order": 1,
86
87
  },
87
88
  {
88
89
  "old_name": "Key services inaccessible for most",
89
90
  "old_value": "HI",
90
91
  "new_name": "Critical issues for many sellers",
91
92
  "new_value": "HT",
92
- "new_order": 20,
93
+ "new_order": 5,
93
94
  },
94
95
  {
95
96
  "old_name": "Some sellers have significant issues",
96
97
  "old_value": "MD",
97
98
  "new_name": "Some sellers with major issues",
98
99
  "new_value": "HI",
99
- "new_order": 15,
100
+ "new_order": 4,
100
101
  },
101
102
  {
102
103
  "old_name": "Few sellers with minor issues",
103
104
  "old_value": "LO",
104
105
  "new_name": "Some sellers with significant issues",
105
106
  "new_value": "MD",
106
- "new_order": 10,
107
+ "new_order": 3,
107
108
  },
108
109
  {
109
110
  "old_name": "No impact on sellers",
110
111
  "old_value": "N/A",
111
112
  "new_name": None,
112
113
  "new_value": "LT",
113
- "new_order": 0,
114
+ "new_order": 1,
114
115
  },
115
116
  {
116
117
  "old_name": "Significant issues for some employees",
117
118
  "old_value": "MD",
118
119
  "new_name": "Major issues for internal users",
119
120
  "new_value": "LO",
120
- "new_order": 10,
121
+ "new_order": 2,
121
122
  },
122
123
  {
123
124
  "old_name": "Critical internal tools down",
124
125
  "old_value": "HI",
125
126
  "new_name": "Critical issues for internal users",
126
127
  "new_value": "MD",
127
- "new_order": 15,
128
+ "new_order": 3,
128
129
  },
129
130
  {
130
131
  "old_name": "No impact on employees",
131
132
  "old_value": "N/A",
132
133
  "new_name": "Minor issues for internal users",
133
134
  "new_value": "LT",
134
- "new_order": 0,
135
+ "new_order": 1,
135
136
  },
136
137
  {
137
138
  "old_name": "Whole business or revenue at risk",
138
139
  "old_value": "HI",
139
140
  "new_name": "Critical business impact",
140
141
  "new_value": "HT",
141
- "new_order": 20,
142
+ "new_order": 5,
142
143
  },
143
144
  {
144
145
  "old_name": "Significant business or revenue loss",
145
146
  "old_value": "MD",
146
147
  "new_name": "Major business impact",
147
148
  "new_value": "HI",
148
- "new_order": 15,
149
+ "new_order": 4,
149
150
  },
150
151
  {
151
152
  "old_name": "Minor impact on business or revenue",
152
153
  "old_value": "LO",
153
154
  "new_name": "Significant business impact",
154
155
  "new_value": "MD",
155
- "new_order": 10,
156
+ "new_order": 3,
156
157
  },
157
158
  {
158
159
  "old_name": "No impact on business or revenue",
159
160
  "old_value": "N/A",
160
161
  "new_name": None,
161
162
  "new_value": "LT",
162
- "new_order": 0,
163
+ "new_order": 1,
163
164
  },
164
165
  ]
165
166
 
@@ -192,38 +193,69 @@ def add_impact_levels(apps, schema_editor):
192
193
 
193
194
  "name": "Some customers with minor issues",
194
195
  "value": LevelChoices.LOW.value,
195
- "order": 5,
196
+ "order": 2,
196
197
  "impact_type_id": 3,
197
198
  "emoji": LevelChoices.LOW.emoji
198
199
  },
199
200
  {
200
201
  "name": "Some sellers with minor issues",
201
202
  "value": LevelChoices.LOW.value,
202
- "order": 5,
203
+ "order": 2,
203
204
  "impact_type_id": 2,
204
205
  "emoji": LevelChoices.LOW.emoji
205
206
  },
206
207
  {
207
208
  "name": "Minor business impact",
208
209
  "value": LevelChoices.LOW.value,
209
- "order": 5,
210
+ "order": 2,
210
211
  "impact_type_id": 1,
211
212
  "emoji": LevelChoices.LOW.emoji
212
213
  },
214
+ {
215
+
216
+ "name": "N/A",
217
+ "value": LevelChoices.NONE.value,
218
+ "order": 0,
219
+ "impact_type_id": 1,
220
+ "emoji": LevelChoices.NONE.emoji
221
+ },
222
+ {
223
+
224
+ "name": "N/A",
225
+ "value": LevelChoices.NONE.value,
226
+ "order": 0,
227
+ "impact_type_id": 2,
228
+ "emoji": LevelChoices.NONE.emoji
229
+ },
230
+ {
231
+
232
+ "name": "N/A",
233
+ "value": LevelChoices.NONE.value,
234
+ "order": 0,
235
+ "impact_type_id": 3,
236
+ "emoji": LevelChoices.NONE.emoji
237
+ },
238
+ {
239
+
240
+ "name": "N/A",
241
+ "value": LevelChoices.NONE.value,
242
+ "order": 0,
243
+ "impact_type_id": 4,
244
+ "emoji": LevelChoices.NONE.emoji
245
+ },
213
246
  ]
214
247
 
215
248
  for add in adds:
216
249
  name = add["name"]
217
250
  try:
218
- impact_level = ImpactLevel.objects.filter(name=name).first()
219
-
220
251
  impact_type_id = add["impact_type_id"]
252
+ impact_level = ImpactLevel.objects.filter(name=name, impact_type_id=impact_type_id).first()
221
253
  impact_type = ImpactType.objects.filter(id=impact_type_id).first()
222
254
  if not impact_type:
223
255
  logger.error(f"There is no impact type id:'{impact_type_id}'.")
224
256
  continue
225
257
  if impact_level:
226
- logger.error(f"There is already an impact level with name:'{name}'.")
258
+ logger.error(f"There is already an impact level with name:'{name}' and type:'{impact_type_id}'.")
227
259
  continue
228
260
  new_impact_level = ImpactLevel(
229
261
  name=name,
@@ -249,7 +281,7 @@ class Migration(migrations.Migration):
249
281
  migrations.AlterField(
250
282
  model_name="impactlevel",
251
283
  name="emoji",
252
- field=models.CharField(default="", max_length=5),
284
+ field=models.CharField(default="", max_length=5),
253
285
  ),
254
286
  migrations.RemoveConstraint(
255
287
  model_name="impactlevel",
@@ -0,0 +1,42 @@
1
+ # Generated by Django 4.2.21 on 2025-06-02 16:29
2
+ import uuid
3
+
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("incidents", "0011_update_incidents"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="impactlevel",
16
+ name="id",
17
+ field=models.UUIDField(
18
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False
19
+ ),
20
+ ),
21
+ migrations.AlterField(
22
+ model_name="impactlevel",
23
+ name="value",
24
+ field=models.CharField(
25
+ choices=[
26
+ ("HT", "Highest"),
27
+ ("HI", "High"),
28
+ ("MD", "Medium"),
29
+ ("LO", "Low"),
30
+ ("LT", "Lowest"),
31
+ ("NO", "N/A"),
32
+ ],
33
+ default="NO",
34
+ max_length=2,
35
+ ),
36
+ ),
37
+ migrations.AlterField(
38
+ model_name="impacttype",
39
+ name="emoji",
40
+ field=models.CharField(default="", max_length=5),
41
+ ),
42
+ ]
@@ -0,0 +1,61 @@
1
+ import logging
2
+
3
+ from django.db import migrations
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ def get_new_components() -> dict:
9
+ """
10
+ Returns a dictionary of new components to be created.
11
+
12
+ Each entry in the dictionary maps a component name to a tuple containing:
13
+ - group_name: The name of the group the component belongs to.
14
+ - slack_channel: The associated Slack channel for the component.
15
+
16
+ Returns:
17
+ dict: A mapping of component names to (group name, slack channel) tuples.
18
+ """
19
+ return {
20
+ "Data Tools": ("Data", "impact-data-tools"),
21
+ "Catalog Access": ("Catalog", "impact-catalog-access"),
22
+ }
23
+
24
+
25
+ def add_new_components(apps, schema_editor):
26
+ Component = apps.get_model("incidents", "Component")
27
+ Group = apps.get_model("incidents", "Group")
28
+ new_components = get_new_components()
29
+
30
+ for name, (group_name, _slack) in new_components.items():
31
+ logger.info(f"Creating new component: '{name}' belonging to group '{group_name}'")
32
+ try:
33
+ group_instance = Group.objects.get(name=group_name)
34
+ new_component = Component(name=name, group=group_instance)
35
+ new_component.save()
36
+ except Exception:
37
+ logger.exception(f"Failed to create new group: '{group_name}'.")
38
+
39
+
40
+ def remove_new_components(apps, schema_editor):
41
+ Component = apps.get_model("incidents", "Component")
42
+ new_component_names = get_new_components().keys()
43
+
44
+ for name in new_component_names:
45
+ try:
46
+ component = Component.objects.get(name=name)
47
+ logger.info(f"Removing component: '{name}'")
48
+ component.delete()
49
+ except Exception:
50
+ logger.exception(f"Component '{name}' does not exist, skipping removal.")
51
+
52
+
53
+ class Migration(migrations.Migration):
54
+
55
+ dependencies = [
56
+ ("incidents", "0012_alter_impactlevel"),
57
+ ]
58
+
59
+ operations = [
60
+ migrations.RunPython(add_new_components, remove_new_components),
61
+ ]
@@ -0,0 +1,177 @@
1
+ import logging
2
+
3
+ from django.db import migrations, transaction
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ COMPONENT_SLACK_GROUPS = {
8
+ "Controlling": ("S08UYJGJ9HB", "finance-controlling"),
9
+ "Applications": ("S08V22ZJ6DQ", "corporate-it-applications"),
10
+ "Gitlab": ("S090A6LQ0U8", "platform-gitlab"),
11
+ "Seller Admin and Experience": ("S08UV261ZRU", "seller-seller-admin-and-experience"),
12
+ "Revenue": ("S08V22ZPBBL", "finance-revenue"),
13
+ "Catalog Exposition": ("S08VD2WE5ND", "catalog-catalog-exposition"),
14
+ "Company reputation": ("S090A63V7FS", "marketing-communication-company-reputation"),
15
+ "BO Catalog - Master Experience": ("S08VD2Y1DMF", "catalog-bo-catalog-master-experience"),
16
+ "Customer service": ("S08UV2GB7UN", "operations-customer-service"),
17
+ "Seller's invoices": ("S08VD38AFFB", "finance-sellers-invoices"),
18
+ "Cart & funnel": ("S08V2N7PRN0", "marketplace-cart-funnel"),
19
+ "Data Ingestion": ("S08UYJHEFEH", "data-data-ingestion"),
20
+ "Refunds": ("S08UM14D3LP", "payment-operations-refunds"),
21
+ "Data Analytics": ("S08V2NNDG9J", "data-data-analytics"),
22
+ "Mobile Apps": ("S08UV20CL6S", "marketplace-mobile-apps"),
23
+ "Navigation & Product discovery": ("S08UYHZFFD3", "marketplace-navigation-product-discovery"),
24
+ "Helpcenter after sales": ("S08UU3SHQTV", "operations-helpcenter-after-sales"),
25
+ "CDN": ("S08UM12RNTZ", "platform-cdn"),
26
+ "Customer's invoices": ("S08UYJGELJH", "finance-customers-invoices"),
27
+ "Returns": ("S08UYJHDNQM", "operations-returns"),
28
+ "Inventory": ("S08V2NNLW3W", "operations-inventory"),
29
+ "Loyalty and coupons": ("S08V230H1V0", "payment-operations-loyalty-and-coupons"),
30
+ "Seller Services": ("S08UM0SJ66T", "seller-seller-services"),
31
+ "Spinak": ("S08UYJGRTE1", "platform-spinak"),
32
+ "Data Tools": ("S08V2NPFDPE", "data-data-tools"),
33
+ "Product Management": ("S08UYJ5GU93", "catalog-product-management"),
34
+ "Compromised laptop / server": ("S08UU3T6H51", "security-compromised-laptop-server"),
35
+ "Catalog Performance": ("S08V22QDHJ6", "catalog-catalog-performance"),
36
+ "Spartacux Foundations": ("S08UYHZH97X", "marketplace-spartacux-foundations"),
37
+ "Stolen account(s) or IT materials": ("S08UU3TFJ3D", "security-stolen-accounts-or-it-materials"),
38
+ "MM Fulfillment": ("S08VD371F0R", "operations-mm-fulfillment"),
39
+ "HUB Integrators": ("S08UM0SSNNB", "seller-hub-integrators"),
40
+ "Exploited vulnerability": ("S08V2NPD084", "security-exploited-vulnerability"),
41
+ "Commercial Animation": ("S08V2N6UA3E", "marketplace-commercial-animation"),
42
+ "Cloud Infrastructure": ("S08UYJJ01HB", "platform-cloud-infrastructure"),
43
+ "Tracking": ("S08UYHZPSTX", "marketplace-tracking"),
44
+ "Data Warehouse": ("S08UYJHRWQ5", "data-data-warehouse"),
45
+ "Infra - System": ("S08UV2H5WDC", "corporate-it-infra-system"),
46
+ "VAT": ("S090A6LGGV6", "finance-vat"),
47
+ "Web performance": ("S090A63K124", "marketplace-web-performance"),
48
+ "Payouts to seller": ("S090A6M61A4", "payment-operations-payouts-to-seller"),
49
+ "Seller Catalog and Offer Management": ("S08UV261CHL", "seller-seller-catalog-and-offer-management"),
50
+ "Other": ("S08V22Z8S58", "other-other"),
51
+ "Network": ("S090A6M3B7S", "corporate-it-network"),
52
+ "Bot management & rate limiting & WAF": ("S090A6M8740", "security-bot-management-rate-limiting-waf"),
53
+ "Customer login & signup": ("S08V22H38AE", "marketplace-customer-login-signup"),
54
+ "Delivery experience": ("S08V2NP1E5A", "operations-delivery-experience"),
55
+ "Accounting": ("S08VD37CJBT", "finance-accounting"),
56
+ "Traffic acquisition": ("S08UU3BNPPV", "marketing-communication-traffic-acquisition"),
57
+ "Payment": ("S08UM13DTFH", "payment-operations-payment"),
58
+ "Data leak": ("S08UYJHBJPP", "security-data-leak"),
59
+ "Offer (Price & Stock)": ("S08V2NCSPJ8", "catalog-offer-price-stock"),
60
+ "Product Structure": ("S08V2NCNFV2", "catalog-product-structure"),
61
+ "Catalog Access": ("S090A69J064", "catalog-catalog-access"),
62
+ "Order management": ("S08UU3TRGJF", "operations-order-management"),
63
+ }
64
+
65
+
66
+ def update_components_slack_groups(apps, _):
67
+ try:
68
+ Component = apps.get_model("incidents", "Component")
69
+ UserGroup = apps.get_model("slack", "UserGroup")
70
+ except LookupError:
71
+ logger.exception("The 'slack' app is not installed. Skipping migration.")
72
+ return
73
+
74
+ with transaction.atomic():
75
+ for component_name, (user_group_id, user_group_name) in COMPONENT_SLACK_GROUPS.items():
76
+ try:
77
+ # Vérifier/créer le SlackUserGroup
78
+ user_group, _ = UserGroup.objects.get_or_create(
79
+ name=user_group_name
80
+ )
81
+ # Si le slack_group_id a changé, le mettre à jour
82
+ if user_group.usergroup_id != user_group_id:
83
+ user_group.usergroup_id = user_group_id
84
+ user_group.handle = user_group_name
85
+ user_group.save()
86
+
87
+ # Vérifier/créer le Component
88
+ component = Component.objects.get(name=component_name)
89
+ if not component:
90
+ logger.error(f"Component '{component_name}' n'existe pas.")
91
+ continue
92
+
93
+ # Lier le Component au SlackUserGroup via la relation du UserGroup
94
+ if not hasattr(user_group, "components"):
95
+ logger.error(f"UserGroup '{user_group_name}' n'a pas d'attribut 'components'.")
96
+ continue
97
+
98
+ if not user_group.components.filter(id=component.id).exists():
99
+ user_group.components.add(component)
100
+ logger.info(f"Component '{component_name}' ajouté au SlackUserGroup '{user_group_name}'")
101
+ except Exception:
102
+ logger.exception(f"Failed to update component: '{component_name}'.")
103
+
104
+
105
+ def ensure_firefighters_user_group(apps, _):
106
+ try:
107
+ Component = apps.get_model("incidents", "Component")
108
+ UserGroup = apps.get_model("slack", "UserGroup")
109
+ except LookupError:
110
+ logger.exception("The 'slack' app is not installed. Skipping Firefighters user group check.")
111
+ return
112
+
113
+ try:
114
+ firefighters_group = UserGroup.objects.get(name="firefighters")
115
+ except UserGroup.DoesNotExist:
116
+ logger.exception("UserGroup 'firefighters' does not exist.")
117
+ return
118
+
119
+ for component in Component.objects.all():
120
+ if not hasattr(firefighters_group, "components"):
121
+ logger.error("UserGroup 'firefighters' n'a pas d'attribut 'components'.")
122
+ break
123
+ if not firefighters_group.components.filter(id=component.id).exists():
124
+ firefighters_group.components.add(component)
125
+ logger.info(f"Component '{component.name}' ajouté au SlackUserGroup 'firefighters'.")
126
+
127
+
128
+ def cleanup_unused_slack_user_groups(apps, _):
129
+ try:
130
+ UserGroup = apps.get_model("slack", "UserGroup")
131
+ except LookupError:
132
+ logger.exception("The 'slack' app is not installed. Skipping user groups cleanup.")
133
+ return
134
+
135
+ valid_usergroup_ids = {v[0] for v in COMPONENT_SLACK_GROUPS.values()}
136
+
137
+ for user_group in UserGroup.objects.all():
138
+ if (
139
+ user_group.usergroup_id not in valid_usergroup_ids
140
+ and "firefighter" not in user_group.name.lower()
141
+ and hasattr(user_group, "components")
142
+ ):
143
+ components_to_remove = list(user_group.components.all())
144
+ if components_to_remove:
145
+ user_group.components.clear()
146
+ logger.info(
147
+ f"Removed components {[c.name for c in components_to_remove]} from SlackUserGroup '{user_group.name}'"
148
+ )
149
+
150
+
151
+ def delete_unused_slack_user_groups(apps, _):
152
+ try:
153
+ UserGroup = apps.get_model("slack", "UserGroup")
154
+ except LookupError:
155
+ logger.exception("The 'slack' app is not installed. Skipping user groups deletion.")
156
+ return
157
+
158
+ for user_group in UserGroup.objects.all():
159
+ if "firefighters" in user_group.name.lower():
160
+ continue
161
+ if hasattr(user_group, "components") and user_group.components.count() == 0:
162
+ logger.info(f"Deleting SlackUserGroup '{user_group.name}' (ID: {user_group.usergroup_id}) as it has no components.")
163
+ user_group.delete()
164
+
165
+
166
+ class Migration(migrations.Migration):
167
+
168
+ dependencies = [
169
+ ("incidents", "0013_add_missing_component"),
170
+ ]
171
+
172
+ operations = [
173
+ migrations.RunPython(update_components_slack_groups),
174
+ migrations.RunPython(ensure_firefighters_user_group),
175
+ migrations.RunPython(cleanup_unused_slack_user_groups),
176
+ migrations.RunPython(delete_unused_slack_user_groups),
177
+ ]
@@ -0,0 +1,136 @@
1
+ # Generated by Django 4.2.7 on 2023-12-04 11:09
2
+
3
+
4
+ import logging
5
+
6
+ from django.db import migrations, models
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Mapping for updating ImpactLevel names based on value and impact_type
11
+ IMPACT_LEVEL_NAME_MAPPING = {
12
+ # Format: (value, impact_type): (new_name, description)
13
+ ("LT", "business_impact"): (
14
+ "Business Lowest Not measurable",
15
+ "Lowest business impact, not measurable."
16
+ ),
17
+ ("LO", "business_impact"): (
18
+ "Business Lower Not measurable",
19
+ "Lower business impact, not measurable."
20
+ ),
21
+ ("MD", "business_impact"): (
22
+ "Uncertain Business Impact",
23
+ "Uncertain business value daily loss."
24
+ ),
25
+ ("HI", "business_impact"): (
26
+ "Major Business Impact (<5% BV daily loss)",
27
+ "Major business impact, less than 5% business value daily loss."
28
+ ),
29
+ ("HT", "business_impact"): (
30
+ "Critical Business Impact (>5% BV daily loss)",
31
+ "Critical business impact, more than 5% business value daily loss."
32
+ ),
33
+ ("NO", "business_impact"): (
34
+ "No impact on business.",
35
+ ""
36
+ ),
37
+ ("LT", "customers_impact"): (
38
+ "Cosmetic issues for few customers",
39
+ "Minor functional affecting few users / cosmetic issues."
40
+ ),
41
+ ("LO", "customers_impact"): (
42
+ "Minor functional issues for many customers",
43
+ "Minor functional issues affecting many customers."
44
+ ),
45
+ ("MD", "customers_impact"): (
46
+ "Major functional issues for many customers",
47
+ "Many customer with major functional issues (no affecting BV)."
48
+ ),
49
+ ("HI", "customers_impact"): (
50
+ "Some customers have major issues",
51
+ "Purchase flow experience with major experience issues impacting <5% of customers.\nAccess to product not working affecting <5% of customers/errors\nMarketplace unstable <30% errors."
52
+ ),
53
+ ("HT", "customers_impact"): (
54
+ "Critical issue for many customers",
55
+ "Purchase flow experience with major experience issues impacting >5% of customers.\nAccess to product not working affecting >5% of customers/errors\nMarketplace unstable >30% errors."
56
+ ),
57
+ ("NO", "customers_impact"): (
58
+ "No impact for customers.",
59
+ ""
60
+ ),
61
+ ("LT", "sellers_impact"): (
62
+ "Cosmetic issues for few sellers",
63
+ "Minor cosmetic issues affecting few sellers."
64
+ ),
65
+ ("LO", "sellers_impact"): (
66
+ "Minor functional issues for many sellers",
67
+ "Minor functional issues affecting many sellers."
68
+ ),
69
+ ("MD", "sellers_impact"): (
70
+ "Major functional issues for many sellers",
71
+ "Many sellers with major functional issues (no affecting fulfillment / catalog availability / order).\nIssues affecting commercial operations (e.g. wrong pricing on inventory / offers / etc) of big part of our inventory (e.g. big / important sellers)."
72
+ ),
73
+ ("HI", "sellers_impact"): (
74
+ "Some sellers have major issues",
75
+ "Toolbox access hindered affecting <20% of sellers.\nImpossibility of publishing / modifying catalogue affecting <20% of sellers."
76
+ ),
77
+ ("HT", "sellers_impact"): (
78
+ "Critical issue for many sellers",
79
+ "Toolbox access hindered affecting >20% of sellers.\nImpossibility of publishing / modifying catalogue affecting >20% of sellers."
80
+ ),
81
+ ("NO", "sellers_impact"): (
82
+ "No impact for sellers.",
83
+ ""
84
+ ),
85
+ ("LT", "employees_impact"): (
86
+ "Degraded service for MM workers or partners but not blocked",
87
+ "Service is degraded for MM workers or partners, but they are not blocked."
88
+ ),
89
+ ("LO", "employees_impact"): (
90
+ "Few MM workers or partners affected systematically or blocked",
91
+ "A few MM workers or partners are systematically affected or blocked."
92
+ ),
93
+ ("MD", "employees_impact"): (
94
+ "Full departments blocked on their work.",
95
+ "Entire departments are blocked in their work."
96
+ ),
97
+ ("NO", "employees_impact"): (
98
+ "No impact for employee.",
99
+ ""
100
+ ),
101
+ }
102
+
103
+
104
+ def update_impact_level_names(apps, _schema_editor):
105
+ ImpactLevel = apps.get_model("incidents", "ImpactLevel")
106
+ ImpactType = apps.get_model("incidents", "ImpactType")
107
+ for (value, impact_type_value), (new_name, description) in IMPACT_LEVEL_NAME_MAPPING.items():
108
+ try:
109
+ impact_type = ImpactType.objects.filter(value=impact_type_value).first()
110
+ if not impact_type:
111
+ logger.warning(f"ImpactType '{impact_type}' does not exist. Skipping update for value '{value}' and impact_type '{impact_type_value}'.")
112
+ continue
113
+
114
+ impact_level = ImpactLevel.objects.get(value=value, impact_type=impact_type.id)
115
+ impact_level.name = new_name
116
+ impact_level.description = description
117
+ impact_level.save()
118
+ logger.info(f"Updated ImpactLevel ({value}, {impact_type}) to name '{new_name}'")
119
+ except ImpactLevel.DoesNotExist:
120
+ logger.warning(f"ImpactLevel with value '{value}' and impact_type '{impact_type}' does not exist.")
121
+
122
+
123
+ class Migration(migrations.Migration):
124
+
125
+ dependencies = [
126
+ ("incidents", "0014_update_components_slack_groups"),
127
+ ]
128
+
129
+ operations = [
130
+ migrations.AddField(
131
+ model_name="impactlevel",
132
+ name="description",
133
+ field=models.TextField(null=True, blank=True),
134
+ ),
135
+ migrations.RunPython(update_impact_level_names),
136
+ ]
@@ -0,0 +1,84 @@
1
+ # Generated by Django 4.2.7 on 2023-12-04 11:09
2
+
3
+
4
+ import logging
5
+
6
+ from django.db import migrations
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def update_incidents(apps, _schema_editor):
12
+ ImpactLevel = apps.get_model("incidents", "ImpactLevel")
13
+ ImpactType = apps.get_model("incidents", "ImpactType")
14
+ Incident = apps.get_model("incidents", "Incident")
15
+
16
+ business_impact_type = ImpactType.objects.filter(value="business_impact").first()
17
+ if not business_impact_type:
18
+ logger.error("ImpactType 'business_impact' does not exist. Skipping incident update.")
19
+ return
20
+
21
+ md_impact_level = ImpactLevel.objects.filter(value="MD", impact_type=business_impact_type.id).first()
22
+ if not md_impact_level:
23
+ logger.error("ImpactLevel 'MD' for business_impact does not exist. Skipping incident update.")
24
+ return
25
+
26
+ for level_value in ["LT", "LO"]:
27
+ old_impact_level = ImpactLevel.objects.filter(value=level_value, impact_type=business_impact_type.id).first()
28
+ if not old_impact_level:
29
+ logger.error(f"ImpactLevel '{level_value}' for business_impact does not exist. Skipping.")
30
+ continue
31
+
32
+ # Find incidents that have the old impact level through the impacts relationship
33
+ incidents_with_old_level = Incident.objects.filter(impacts=old_impact_level.id)
34
+ updated_count = 0
35
+
36
+ for incident in incidents_with_old_level:
37
+ # Remove the old impact level and add the new one if not already present
38
+ incident.impacts.remove(old_impact_level)
39
+ if not incident.impacts.filter(id=md_impact_level.id).exists():
40
+ incident.impacts.add(md_impact_level)
41
+ updated_count += 1
42
+
43
+ logger.info(f"Updated {updated_count} incidents from impact_level '{level_value}' to 'MD' for business_impact.")
44
+
45
+
46
+ def remove_old_impact_levels(apps, _schema_editor):
47
+ ImpactLevel = apps.get_model("incidents", "ImpactLevel")
48
+ ImpactType = apps.get_model("incidents", "ImpactType")
49
+ Impact = apps.get_model("incidents", "Impact")
50
+
51
+ business_impact_type = ImpactType.objects.filter(value="business_impact").first()
52
+ if not business_impact_type:
53
+ logger.error("ImpactType 'business_impact' does not exist. Skipping impact level removal.")
54
+ return
55
+
56
+ md_impact_level = ImpactLevel.objects.filter(value="MD", impact_type=business_impact_type.id).first()
57
+ if not md_impact_level:
58
+ logger.error("ImpactLevel 'MD' for business_impact does not exist. Cannot update Impact objects.")
59
+ return
60
+
61
+ for level_value in ["LT", "LO"]:
62
+ impact_level = ImpactLevel.objects.filter(value=level_value, impact_type=business_impact_type.id).first()
63
+ if impact_level:
64
+ # Update all Impact objects that reference this impact level
65
+ impacts_to_update = Impact.objects.filter(impact_level=impact_level)
66
+ updated_count = impacts_to_update.update(impact_level=md_impact_level)
67
+ logger.info(f"Updated {updated_count} Impact objects from '{level_value}' to 'MD' for business_impact.")
68
+
69
+ # Now safe to delete the impact level
70
+ impact_level.delete()
71
+ logger.info(f"Removed ImpactLevel '{level_value}' for business_impact.")
72
+ else:
73
+ logger.error(f"ImpactLevel '{level_value}' for business_impact not found.")
74
+
75
+
76
+ class Migration(migrations.Migration):
77
+
78
+ dependencies = [
79
+ ("incidents", "0015_update_impact_level"),
80
+ ]
81
+ operations = [
82
+ migrations.RunPython(update_incidents),
83
+ migrations.RunPython(remove_old_impact_levels),
84
+ ]
@@ -50,12 +50,12 @@ class LevelChoices(models.TextChoices):
50
50
  self.LOWEST: 5,
51
51
  self.NONE: 6,
52
52
  }
53
- return priority_mapping.get(self, 5) # type: ignore [call-overload]
53
+ return priority_mapping.get(self, 6) # type: ignore [call-overload]
54
54
 
55
55
  @property
56
56
  def emoji(self) -> str:
57
57
  """Send emoji un function of priority."""
58
- none_emoji = ""
58
+ none_emoji = ""
59
59
  emoji_mapping = {
60
60
  self.HIGHEST: "⏫",
61
61
  self.HIGH: "🔼",
@@ -80,9 +80,13 @@ class ImpactLevel(models.Model):
80
80
  default="",
81
81
  help_text="Description for the impact level for this impact type.",
82
82
  )
83
- value = models.CharField(choices=LevelChoices.choices, max_length=2, default="LT")
83
+ value = models.CharField(choices=LevelChoices.choices, max_length=2, default="NO")
84
84
  order = models.PositiveSmallIntegerField(default=10)
85
-
85
+ description = models.TextField(
86
+ blank=True,
87
+ null=True,
88
+ help_text="Detailed multi-line description for this impact level.",
89
+ )
86
90
  class Meta(TypedModelMeta):
87
91
  constraints = [
88
92
  models.UniqueConstraint(
@@ -1,7 +1,7 @@
1
1
  {% extends '../layouts/view_filters.html' %}
2
2
 
3
3
  {% block page_title %}
4
- Components List <div role="status" class="hx-progress htmx-indicator inline">
4
+ Issue category list <div role="status" class="hx-progress htmx-indicator inline">
5
5
  <svg class="inline mr-2 w-6 h-6 text-gray-200 animate-spin dark:text-gray-600 fill-primary" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
6
6
  <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
7
7
  <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
@@ -0,0 +1,24 @@
1
+ # Generated by Django 4.2.21 on 2025-06-03 15:03
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("incidents", "0014_update_components_slack_groups"),
10
+ ("slack", "0003_alter_usergroup_tag"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="usergroup",
16
+ name="components",
17
+ field=models.ManyToManyField(
18
+ blank=True,
19
+ help_text="Incident created with this usergroup automatically add the group members to these issue categories.",
20
+ related_name="usergroups",
21
+ to="incidents.component",
22
+ ),
23
+ ),
24
+ ]
@@ -153,7 +153,12 @@ class OpenModal(SlackModal):
153
153
  def get_intro_blocks() -> list[Block]:
154
154
  blocks: list[Block] = [
155
155
  SectionBlock(
156
- text="Hello and thanks for reporting a new incident! :beetle:\n\nPlease report as much information as you can!"
156
+ text=(
157
+ "Hello and thanks for reporting a new incident! :beetle:\n\n"
158
+ "Please report as much information as you can!\n\n"
159
+ "More information about the incident process in this "
160
+ "<https://manomano.atlassian.net/wiki/spaces/TC/pages/3928261283/IMPACT+-+Incident+Management+Platform|documentation>."
161
+ )
157
162
  )
158
163
  ]
159
164
 
@@ -8,7 +8,9 @@ from typing import TYPE_CHECKING, Any, Never, cast
8
8
  from django import forms
9
9
  from django.conf import settings
10
10
  from django.db.models import Model, QuerySet
11
+ from slack_sdk.models.blocks.basic_components import MarkdownTextObject
11
12
  from slack_sdk.models.blocks.blocks import (
13
+ ContextBlock,
12
14
  DividerBlock,
13
15
  SectionBlock,
14
16
  )
@@ -16,6 +18,7 @@ from slack_sdk.models.views import View
16
18
 
17
19
  from firefighter.firefighter.utils import get_in
18
20
  from firefighter.incidents.forms.select_impact import SelectImpactForm
21
+ from firefighter.incidents.models.impact import ImpactType
19
22
  from firefighter.incidents.models.priority import Priority
20
23
  from firefighter.slack.slack_app import SlackApp
21
24
  from firefighter.slack.views.modals import modal_open
@@ -85,13 +88,14 @@ class SelectImpactModal(
85
88
  initial_form_values_impact[field_name] = None # type: ignore
86
89
  blocks = [
87
90
  SectionBlock(
88
- text=f"{SLACK_APP_EMOJI} Define the following impact to find the incident's priority:"
91
+ text=f"{SLACK_APP_EMOJI} Define the impact(s) to find the incident priority (<https://manomano.atlassian.net/wiki/spaces/TC/pages/4024500413/Priority+levels|documentation>):"
89
92
  ),
90
93
  *self.get_form_class()(initial=initial_form_values_impact).slack_blocks(
91
94
  "section_accessory"
92
95
  ),
93
96
  ]
94
97
  priority_data = open_incident_context.get("priority")
98
+
95
99
  if open_incident_context and priority_data:
96
100
  if not isinstance(priority_data, Priority):
97
101
  priority = Priority.objects.get(pk=priority_data)
@@ -101,11 +105,24 @@ class SelectImpactModal(
101
105
  err_msg = f"Invalid priority data: {priority_data}" # type: ignore[unreachable]
102
106
  raise TypeError(err_msg)
103
107
  process = ":slack: Slack :jira_new: Jira ticket" if open_incident_context.get("response_type") == "critical" else ":jira_new: Jira ticket"
108
+
109
+ impact_descriptions = self.extract_descriptions(open_incident_context)
110
+
104
111
  blocks.extend((
105
112
  DividerBlock(),
106
- SectionBlock(
107
- text=f"💡 Selected priority: {priority} - {priority.description}\n⏱️ SLA: {priority.sla}\n{process}"
108
- ),
113
+ ContextBlock(
114
+ elements=[
115
+ MarkdownTextObject(
116
+ text=(
117
+ f":dart: Selected priority: *{priority} - {priority.description}*\n"
118
+ f"⏱️ SLA: {priority.sla}\n"
119
+ f":gear: Process: {process}\n"
120
+ f":pushpin: Selected impacts:\n"
121
+ f"{impact_descriptions}\n"
122
+ )
123
+ )
124
+ ]
125
+ )
109
126
  ))
110
127
 
111
128
  return View(
@@ -117,6 +134,20 @@ class SelectImpactModal(
117
134
  submit="Save impacts",
118
135
  )
119
136
 
137
+ def extract_descriptions(self, open_incident_context: OpeningData) -> str:
138
+ impact_form_data = open_incident_context.get("impact_form_data", {})
139
+ impact_descriptions = ""
140
+ if impact_form_data:
141
+ for value in impact_form_data.values():
142
+ if value.name != "NO" and value.description:
143
+ if hasattr(value, "impact_type_id") and value.impact_type_id:
144
+ impact_type = ImpactType.objects.get(pk=value.impact_type_id)
145
+ if impact_type:
146
+ impact_descriptions += f"\u00A0\u00A0 :exclamation: {impact_type} - {value}\n"
147
+ for line in str(value.description).splitlines():
148
+ impact_descriptions += f"\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0 • {line}\n"
149
+ return impact_descriptions
150
+
120
151
  def handle_modal_fn( # type: ignore
121
152
  self,
122
153
  ack: Ack,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firefighter-incident
3
- Version: 0.0.5
3
+ Version: 0.0.7
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=WcvKdm4etz9_Q5VW_LRgfCR0icS2PEtbz-C1JY6enCc,511
9
+ firefighter/_version.py,sha256=Fi0KXFzRbmtJaZ31ANCJStB858SeU6K67Widq0qwDIs,511
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
@@ -132,7 +132,7 @@ firefighter/incidents/admin.py,sha256=ycKw5kYL55W1n12t39Mkgx9rqfi_iawXw0waQznByf
132
132
  firefighter/incidents/apps.py,sha256=GySoDZsz-BH-nsIZWTOd0H1hgL9s9il1BEuLC4gKTXs,378
133
133
  firefighter/incidents/enums.py,sha256=qRVpRvcCXEZ8xF6FTXmAtZjDxCj06hLDgEZlak6ket4,937
134
134
  firefighter/incidents/factories.py,sha256=lfR6qqqBJc5nEMHmTg9D6837VZnDSx4y68lzcciYg6w,3364
135
- firefighter/incidents/menus.py,sha256=EnxMBTS5g6N8Bhyj802vrpNmr4YYXH8K1aHnIxUixNI,4123
135
+ firefighter/incidents/menus.py,sha256=rttZBReRACqOMZFng2svvk3gU1Ld84PQCNbBo9AIecE,4129
136
136
  firefighter/incidents/signals.py,sha256=hUlPVVKSi9zuL4gz_Sa-HrDleYCASeMqKqA-kkHCaDQ,2128
137
137
  firefighter/incidents/tables.py,sha256=SH-ScSw7SkwbdiAIApHn1eYfOuj7CRy__jY9gDadenk,3324
138
138
  firefighter/incidents/urls.py,sha256=4QJAIjmyE90_j8HLNExa4UpjM2CgasBOlx_r07ucjiM,2059
@@ -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=twgvfvyT6TL4yzv1dx7H6IWrncfGTeMf2Qg6x0hTWjw,4637
143
+ firefighter/incidents/forms/select_impact.py,sha256=0cnV2DFHuO1In6CgRATjg0zNLd9YN_LbQnPL7Fm9pAU,4631
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
@@ -152,16 +152,21 @@ firefighter/incidents/migrations/0004_incidentupdate_environment.py,sha256=BoIua
152
152
  firefighter/incidents/migrations/0005_enable_from_p1_to_p5_priority.py,sha256=sBr3nMOf7qnAg1qHjfPOY79fP08yuvgbUaFkOFIBAiU,863
153
153
  firefighter/incidents/migrations/0006_update_group_names.py,sha256=RHWre_VrnBmloFaqfoKA3TQtF8J2k5w1vzTEfGW81SQ,3172
154
154
  firefighter/incidents/migrations/0007_update_component_name.py,sha256=Al9k5Xx2_ea4u20MIp2blTErdv7jkGDYCLhGJ3P-gdw,6555
155
- firefighter/incidents/migrations/0008_impact_level.py,sha256=ZPDTYbt3q0gSEjMC7Nhl7LND9g4J4Hu5n_BO44sKFuc,8852
155
+ firefighter/incidents/migrations/0008_impact_level.py,sha256=tjSzEMTa1hDB4JFola1Z_Zs-zWV6kzcWmIK8OaU_v7Q,9702
156
156
  firefighter/incidents/migrations/0009_update_sla.py,sha256=cLbDmOKrcQOa6lb1kMd-xPJC1w7iDJuEsDTKymEnUMw,849
157
157
  firefighter/incidents/migrations/0010_update_components.py,sha256=IwAj3axIXM95DrQcAZ8NBe-3GgL4NPrZqyBMdVqA8Lo,4301
158
158
  firefighter/incidents/migrations/0011_update_incidents.py,sha256=tV4MA5ZJcqiRPteOLxJNZuhj7q6ncWXjAklm_9IzLbo,3190
159
+ firefighter/incidents/migrations/0012_alter_impactlevel.py,sha256=krszrYoJ_vkYW5RdrgnT-vIIoCa4HC0SPGUtvZ4lMzs,1136
160
+ firefighter/incidents/migrations/0013_add_missing_component.py,sha256=qVLQEl-riFwYNCbhXM-9-GNCHE-bk9QhbVMKqqOL0Ag,1962
161
+ firefighter/incidents/migrations/0014_update_components_slack_groups.py,sha256=5tiQnrtOpYVUztFdvZ6xTUReuJfpDX0cOvqIvDZpBv0,8700
162
+ firefighter/incidents/migrations/0015_update_impact_level.py,sha256=OQVTVrWvQ1orxqdrqwrhBMjxKY5qzqN2ZRobHc_tGDc,5451
163
+ firefighter/incidents/migrations/0016_update_business_incidents_and_level.py,sha256=O5AL9twmjstSw44ndJ2-Og6dpKreReXVw_brpQb-t0w,3600
159
164
  firefighter/incidents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
165
  firefighter/incidents/models/__init__.py,sha256=dCNP-zRYNNDOZB3JDDWp7vCl084Jh6RgDT_iP57RkOY,862
161
166
  firefighter/incidents/models/component.py,sha256=7GyXKNFk1MZns6RUGLpkNw5u6He7H9N1LexzXbG4sBM,7445
162
167
  firefighter/incidents/models/environment.py,sha256=51txwua3dCrWZ1iSG3ZA8rbDn9c00pyMAZujl9gwE5c,827
163
168
  firefighter/incidents/models/group.py,sha256=MscG3IY5AdyJbmq8h4mMqEvLwmnJLor9rkd6CIycKho,685
164
- firefighter/incidents/models/impact.py,sha256=KyIDFdAAdlNfX0aUF8mOY1ZjSqKNeefjPU-VLDGN5zs,4715
169
+ firefighter/incidents/models/impact.py,sha256=URoMsQBJQnyfaNRpVl1o4A1IllM7dg94sd6tf5Ssr-k,4868
165
170
  firefighter/incidents/models/incident.py,sha256=aQUAboXmHH9DO1Nq6AbB4w17dPQqn5iMTwT2r4TeJrA,26129
166
171
  firefighter/incidents/models/incident_cost.py,sha256=juwOfJKRaNQpOHkRUCHShDDba0FU98YjRPkU4I0ofAU,1346
167
172
  firefighter/incidents/models/incident_cost_type.py,sha256=wm8diry_VySJzIjC9M3Yavv2tYbvJgpN9UDb2gFRuH4,845
@@ -222,7 +227,7 @@ firefighter/incidents/templates/layouts/partials/table.html,sha256=eZE4kh0nQwGik
222
227
  firefighter/incidents/templates/layouts/partials/user_card.html,sha256=LgYHSpqC0BAhVENMSmLpow8jlPnh5uRvHzooJlJna6w,802
223
228
  firefighter/incidents/templates/layouts/partials/user_tooltip.html,sha256=KkVPpoODXIzrm6iItTg5RQphBGruQw4dFdOgAuGRt1E,562
224
229
  firefighter/incidents/templates/pages/component_detail.html,sha256=WLO9zg8zDCUtINOHK9xP5dKd5WaB8UWT6JNjLjYSQKs,5277
225
- firefighter/incidents/templates/pages/component_list.html,sha256=Dq7Fo-J-S7fM3v1ByiwAh8XH_f5LSpzXOKiujjRoVAY,1715
230
+ firefighter/incidents/templates/pages/component_list.html,sha256=WbkLGozNbijTk_5iklOS5Hftlc-JHBpkSVL2s1FgUgk,1719
226
231
  firefighter/incidents/templates/pages/dashboard.html,sha256=jGfTCTdyLt3fWtX66HVG1bNgpffV4J7jagVgksCGsSE,1743
227
232
  firefighter/incidents/templates/pages/docs_metrics.html,sha256=q10CCPwjujuj9_h7Mj4-NgP6BMt4TJ6Z3D20W9qZbaQ,3744
228
233
  firefighter/incidents/templates/pages/incident_create.html,sha256=syDS8EqmdcCf6z2dJox5gSPTOsyNdq0jFiDdzVc_vmU,2280
@@ -331,6 +336,7 @@ firefighter/slack/messages/slack_messages.py,sha256=8iYBfRDJw74wl_b5c5A-8_wJBNrT
331
336
  firefighter/slack/migrations/0001_initial_oss.py,sha256=XmTPgq7zCME2xDwzRFoVi4OegSIG9eSKoyTNoW05Qtg,12933
332
337
  firefighter/slack/migrations/0002_usergroup_tag.py,sha256=098tmGA81mT-R2uhb6uQfZ7gKiRG9bFhEwQ8rrp4SKM,583
333
338
  firefighter/slack/migrations/0003_alter_usergroup_tag.py,sha256=ncH3KUWEPZHlbdcAtOJ0KGt5H6EX-cKspTGU3osrAhE,591
339
+ firefighter/slack/migrations/0004_alter_usergroup_components.py,sha256=uCp1bhjcHRHbxlc_oewY__r4FY7eRHcoLSltJWgJ5x0,703
334
340
  firefighter/slack/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
335
341
  firefighter/slack/models/__init__.py,sha256=MGc4yuDnVhmAiHy1-5rjaLIfVv9JOup5arRutcUs8Ak,332
336
342
  firefighter/slack/models/conversation.py,sha256=euN3eS-9KUIxOErfxDhZ8nNdwkrp8JKefLJN6ypszXA,16190
@@ -377,7 +383,7 @@ firefighter/slack/views/modals/close.py,sha256=ur1SSRWk9NYFfL24gjOqoIiXKquDy6qeE
377
383
  firefighter/slack/views/modals/downgrade_workflow.py,sha256=S0y0_GYH4q7ewZUr_eA9Ly2c1FQueZzNCTiuIiWYUoY,3109
378
384
  firefighter/slack/views/modals/edit.py,sha256=60xav4XG4KGS9KknqsQNCQjl3qQzk7OtmHiEYTQ9pUk,3861
379
385
  firefighter/slack/views/modals/key_event_message.py,sha256=ga3-ITZyzJExwzctX-GfgnDqyQaxTfcqpqnOwY2E38M,5620
380
- firefighter/slack/views/modals/open.py,sha256=rAM93z6704I52nr-vLJSGo77OtgeBwVGzfTmqPR5zGA,21290
386
+ firefighter/slack/views/modals/open.py,sha256=mAjOkvejCciUP7-QQmVfPKmljuEowNdGybAhwyKlfUI,21548
381
387
  firefighter/slack/views/modals/postmortem.py,sha256=AeEtmiam_XgCRxDmltKluNT2VN1gcuCB2VbYeeATVcA,2525
382
388
  firefighter/slack/views/modals/select.py,sha256=Y-Ji_ALnzhYkXDBAyi497UL1Xn2vCGqXCtj8eog75Jk,3312
383
389
  firefighter/slack/views/modals/send_sos.py,sha256=bP6HgYyDwPrIcTq7n_sQz6UQsxhYbvBDS4HjM0uRccA,4838
@@ -394,7 +400,7 @@ firefighter/slack/views/modals/base_modal/mixins.py,sha256=c7WYs0aXKXVktEMNSZ8IU
394
400
  firefighter/slack/views/modals/base_modal/modal_utils.py,sha256=1uHTlLxxeXUQttH3bHaehJwCuI6a-h04s-GzdnVA4sI,2459
395
401
  firefighter/slack/views/modals/opening/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
396
402
  firefighter/slack/views/modals/opening/check_current_incidents.py,sha256=28GN0SXP7rVPa55arX1aI98k45w9568GCRDA73eCHEM,2535
397
- firefighter/slack/views/modals/opening/select_impact.py,sha256=P38gsWJ2UmHG9qunsrO9cl1qwZdGU572SYiafh8QLUU,8168
403
+ firefighter/slack/views/modals/opening/select_impact.py,sha256=qTggWabR6lgs4OZa7wYEmFlQTbgUka1052n01NNLcsc,9754
398
404
  firefighter/slack/views/modals/opening/set_details.py,sha256=i6zQM2FYz3Z6s5AZH7lXgB2e8yjS0rDwgfMBZaiOqIw,5791
399
405
  firefighter/slack/views/modals/opening/types.py,sha256=ETpp0DAz5OMI5h7iv62Of7yJCbI-Q4-3kKSS6msPQeY,563
400
406
  firefighter/slack/views/modals/opening/details/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -419,7 +425,7 @@ firefighter_tests/test_firefighter/test_firefighter_utils.py,sha256=og79bkErCBQB
419
425
  firefighter_tests/test_firefighter/test_logging.py,sha256=4HUH73vLDwmOCpMiXwDasMLL7F0kA1B_DxE48mqswSk,878
420
426
  firefighter_tests/test_firefighter/test_urls.py,sha256=UMGx4oW98RoL0ceePkIIKEVjbHdFECvQuGNXYAJForQ,4839
421
427
  firefighter_tests/test_incidents/test_incident_urls.py,sha256=vQy9f1ewJK3N9cjVQDBnLaZjhtiBv5TzoRiGUdV3u5E,3769
422
- firefighter_tests/test_incidents/test_forms/test_form_select_impact.py,sha256=hcDqy3zXJ-klC0tYGWogxrDLDsaD5i4xNSejY0wBbHE,3210
428
+ firefighter_tests/test_incidents/test_forms/test_form_select_impact.py,sha256=DTaPGrJi8mXHfh7mhvDTKYVvDCxqarILauE59UDlwqo,3210
423
429
  firefighter_tests/test_incidents/test_forms/test_form_utils.py,sha256=24ATxxAjkLlz7cj_jIQZvuQvtrfieeh4oaIM90yQrlc,2299
424
430
  firefighter_tests/test_incidents/test_forms/test_update_key_events.py,sha256=rHRGRU9iFXDdMr_kK3pMB7gyeZuMf7Dyq8bRZkddBC4,1644
425
431
  firefighter_tests/test_incidents/test_models/test_incident_model.py,sha256=Kl2dQ0P3qruAaQP5M8WRYJiff-4SpmxpFYHYmA9Xv3k,879
@@ -440,8 +446,8 @@ firefighter_tests/test_slack/views/modals/test_open.py,sha256=Iatphd7vnrEMrv8ysK
440
446
  firefighter_tests/test_slack/views/modals/test_send_sos.py,sha256=_rE6jD-gOzcGyhlY0R9GzlGtPx65oOOguJYdENgxtLc,1289
441
447
  firefighter_tests/test_slack/views/modals/test_status.py,sha256=oQzPfwdg2tkbo9nfkO1GfS3WydxqSC6vy1AZjZDKT30,2226
442
448
  firefighter_tests/test_slack/views/modals/test_update_status.py,sha256=Y8Oa_fraj1vtaGig9Y28_6tOWvMrRPS-wyg3rY-DHBk,39380
443
- firefighter_incident-0.0.5.dist-info/METADATA,sha256=MrJmuQfWF1YiuJkIxwM4hJ5vmOnbW7YMg8ycN9fZZpM,5487
444
- firefighter_incident-0.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
445
- firefighter_incident-0.0.5.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
446
- firefighter_incident-0.0.5.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
447
- firefighter_incident-0.0.5.dist-info/RECORD,,
449
+ firefighter_incident-0.0.7.dist-info/METADATA,sha256=jotwpjDAdeLAEmCdvDXxltqYsVWB5WYKNwaYbE5Wjls,5487
450
+ firefighter_incident-0.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
451
+ firefighter_incident-0.0.7.dist-info/entry_points.txt,sha256=c13meJbv7YNmYz7MipMOQwzQ5IeFOPXUBYAJ44XMQsM,61
452
+ firefighter_incident-0.0.7.dist-info/licenses/LICENSE,sha256=krRiGp-a9-1nH1bWpBEdxyTKLhjLmn6DMVVoIb0zF90,1087
453
+ firefighter_incident-0.0.7.dist-info/RECORD,,
@@ -74,7 +74,7 @@ def test_suggest_priority_from_impact(form_data, expected_priority):
74
74
  "set_impact_type_customers_impact": "LT",
75
75
  "set_impact_type_employees_impact": "LT",
76
76
  },
77
- 5,
77
+ 6,
78
78
  ),
79
79
  (
80
80
  {
@@ -83,7 +83,7 @@ def test_suggest_priority_from_impact(form_data, expected_priority):
83
83
  "set_impact_type_customers_impact": "MD",
84
84
  "set_impact_type_employees_impact": "LT",
85
85
  },
86
- 5,
86
+ 6,
87
87
  ),
88
88
  (
89
89
  {
@@ -92,7 +92,7 @@ def test_suggest_priority_from_impact(form_data, expected_priority):
92
92
  "set_impact_type_customers_impact": "INVALID",
93
93
  "set_impact_type_employees_impact": "LT",
94
94
  },
95
- 5,
95
+ 6,
96
96
  ),
97
97
  ],
98
98
  )