11import logging
2+
23from datetime import datetime
34
45from dispatch .case .models import CaseRead
910from dispatch .event import service as event_service
1011from dispatch .group import flows as group_flows
1112from dispatch .group .enums import GroupType , GroupAction
13+ from dispatch .incident import flows as incident_flows
14+ from dispatch .incident import service as incident_service
15+ from dispatch .incident .enums import IncidentStatus
16+ from dispatch .incident .models import IncidentCreate
17+ from dispatch .individual .models import IndividualContactRead
18+ from dispatch .models import OrganizationSlug
19+ from dispatch .participant .models import ParticipantUpdate
1220from dispatch .storage import flows as storage_flows
21+ from dispatch .storage .enums import StorageAction
1322from dispatch .ticket import flows as ticket_flows
1423
1524from .models import Case , CaseStatus
2029
2130
2231@background_task
23- def case_new_create_flow (* , case_id : int , organization_slug : str , db_session = None ):
32+ def case_new_create_flow (* , case_id : int , organization_slug : OrganizationSlug , db_session = None ):
2433 """Runs the case new creation flow."""
2534 # we get the case
2635 case = get (db_session = db_session , case_id = case_id )
@@ -53,8 +62,10 @@ def case_new_create_flow(*, case_id: int, organization_slug: str, db_session=Non
5362 return
5463
5564 # we create the storage folder
56- members = [group .email ]
57- storage = storage_flows .create_storage (obj = case , members = members , db_session = db_session )
65+ storage_members = [group .email ]
66+ storage = storage_flows .create_storage (
67+ obj = case , storage_members = storage_members , db_session = db_session
68+ )
5869 if not storage :
5970 # we delete the group
6071 group_flows .delete_group (group = group , db_session = db_session )
@@ -97,14 +108,14 @@ def case_new_create_flow(*, case_id: int, organization_slug: str, db_session=Non
97108 document = document , project_id = case .project .id , db_session = db_session
98109 )
99110
100- # we send the case created notification
111+ # TODO(mvilanova): we send the case created notification
101112
102113 db_session .add (case )
103114 db_session .commit ()
104115
105116
106117@background_task
107- def case_triage_create_flow (* , case_id : int , organization_slug : str = None , db_session = None ):
118+ def case_triage_create_flow (* , case_id : int , organization_slug : OrganizationSlug , db_session = None ):
108119 """Runs the case triage creation flow."""
109120 # we run the case new creation flow
110121 case_new_create_flow (
@@ -119,7 +130,9 @@ def case_triage_create_flow(*, case_id: int, organization_slug: str = None, db_s
119130
120131
121132@background_task
122- def case_escalated_create_flow (* , case_id : int , organization_slug : str = None , db_session = None ):
133+ def case_escalated_create_flow (
134+ * , case_id : int , organization_slug : OrganizationSlug , db_session = None
135+ ):
123136 """Runs the case escalated creation flow."""
124137 # we run the case new creation flow
125138 case_new_create_flow (
@@ -133,11 +146,13 @@ def case_escalated_create_flow(*, case_id: int, organization_slug: str = None, d
133146 case_triage_status_flow (case = case , db_session = db_session )
134147
135148 # we transition the case to the escalated state
136- case_escalated_status_flow (case = case , db_session = db_session )
149+ case_escalated_status_flow (
150+ case = case , organization_slug = organization_slug , db_session = db_session
151+ )
137152
138153
139154@background_task
140- def case_closed_create_flow (* , case_id : int , organization_slug : str = None , db_session = None ):
155+ def case_closed_create_flow (* , case_id : int , organization_slug : OrganizationSlug , db_session = None ):
141156 """Runs the case closed creation flow."""
142157 # we run the case new creation flow
143158 case_new_create_flow (
@@ -160,7 +175,7 @@ def case_update_flow(
160175 case_id : int ,
161176 previous_case : CaseRead ,
162177 user_email : str ,
163- organization_slug : str = None ,
178+ organization_slug : OrganizationSlug ,
164179 db_session = None ,
165180):
166181 """Runs the case update flow."""
@@ -169,15 +184,25 @@ def case_update_flow(
169184
170185 # we run the transition flow based on the current and previous status of the case
171186 case_status_transition_flow_dispatcher (
172- case , case .status , previous_case .status , db_session = db_session
187+ case = case ,
188+ current_status = case .status ,
189+ previous_status = previous_case .status ,
190+ organization_slug = organization_slug ,
191+ db_session = db_session ,
173192 )
174193
194+ # TODO(mvilanova): check if ticket has changed
175195 # we update the ticket
176196 ticket_flows .update_case_ticket (case = case , db_session = db_session )
177197
198+ # TODO(mvilanova): check if assignee has changed
178199 # we update the group membership
179200 group_flows .update_group (
180- group = case .tactical_group , group_action = GroupAction .add_member , db_session = db_session
201+ obj = case ,
202+ group = case .tactical_group ,
203+ group_action = GroupAction .add_member ,
204+ group_member = case .assignee .email ,
205+ db_session = db_session ,
181206 )
182207
183208 # we send the case updated notification
@@ -199,23 +224,9 @@ def case_delete_flow(case: Case, db_session: SessionLocal):
199224 storage_flows .delete_storage (storage = case .storage , db_session = db_session )
200225
201226
202- def case_status_flow_common (case : Case , db_session = None ):
203- """Runs tasks common across case status transition flows."""
204- # we update the ticket
205- ticket_flows .update_case_ticket (case = case , db_session = db_session )
206-
207- # we update the timeline
208- event_service .log_case_event (
209- db_session = db_session ,
210- source = "Dispatch Core App" ,
211- description = f"The case status has been changed to { case .status .lower ()} " ,
212- case_id = case .id ,
213- )
214-
215-
216227def case_new_status_flow (case : Case , db_session = None ):
217228 """Runs the case new transition flow."""
218- case_status_flow_common ( case = case , db_session = db_session )
229+ pass
219230
220231
221232def case_triage_status_flow (case : Case , db_session = None ):
@@ -225,17 +236,17 @@ def case_triage_status_flow(case: Case, db_session=None):
225236 db_session .add (case )
226237 db_session .commit ()
227238
228- case_status_flow_common (case = case , db_session = db_session )
229-
230239
231- def case_escalated_status_flow (case : Case , db_session = None ):
240+ def case_escalated_status_flow (case : Case , organization_slug : OrganizationSlug , db_session = None ):
232241 """Runs the case escalated transition flow."""
233242 # we set the escalated_at time
234243 case .escalated_at = datetime .utcnow ()
235244 db_session .add (case )
236245 db_session .commit ()
237246
238- case_status_flow_common (case = case , db_session = db_session )
247+ case_to_incident_escalate_flow (
248+ case = case , organization_slug = organization_slug , db_session = db_session
249+ )
239250
240251
241252def case_closed_status_flow (case : Case , db_session = None ):
@@ -245,14 +256,13 @@ def case_closed_status_flow(case: Case, db_session=None):
245256 db_session .add (case )
246257 db_session .commit ()
247258
248- case_status_flow_common (case = case , db_session = db_session )
249-
250259
251260def case_status_transition_flow_dispatcher (
252261 case : Case ,
253262 current_status : CaseStatus ,
254263 previous_status : CaseStatus ,
255- db_session = SessionLocal ,
264+ organization_slug : OrganizationSlug ,
265+ db_session : SessionLocal ,
256266):
257267 """Runs the correct flows based on the current and previous status of the case."""
258268 # we changed the status of the case to new
@@ -271,7 +281,7 @@ def case_status_transition_flow_dispatcher(
271281 elif current_status == CaseStatus .triage :
272282 if previous_status == CaseStatus .new :
273283 # New -> Triage
274- case_triage_status_flow (case , db_session )
284+ case_triage_status_flow (case = case , db_session = db_session )
275285 elif previous_status == CaseStatus .escalated :
276286 # Escalated -> Triage
277287 pass
@@ -283,11 +293,15 @@ def case_status_transition_flow_dispatcher(
283293 elif current_status == CaseStatus .escalated :
284294 if previous_status == CaseStatus .new :
285295 # New -> Escalated
286- case_triage_status_flow (case , db_session )
287- case_escalated_status_flow (case , db_session )
296+ case_triage_status_flow (case = case , db_session = db_session )
297+ case_escalated_status_flow (
298+ case = case , organization_slug = organization_slug , db_session = db_session
299+ )
288300 elif previous_status == CaseStatus .triage :
289301 # Triage -> Escalated
290- case_escalated_status_flow (case , db_session )
302+ case_escalated_status_flow (
303+ case = case , organization_slug = organization_slug , db_session = db_session
304+ )
291305 elif previous_status == CaseStatus .closed :
292306 # Closed -> Escalated
293307 pass
@@ -296,10 +310,79 @@ def case_status_transition_flow_dispatcher(
296310 elif current_status == CaseStatus .closed :
297311 if previous_status == CaseStatus .new :
298312 # New -> Closed
299- case_closed_status_flow (case , db_session )
313+ case_triage_status_flow (case = case , db_session = db_session )
314+ case_closed_status_flow (case = case , db_session = db_session )
300315 elif previous_status == CaseStatus .triage :
301316 # Triage -> Closed
302- case_closed_status_flow (case , db_session )
317+ case_closed_status_flow (case = case , db_session = db_session )
303318 elif previous_status == CaseStatus .escalated :
304319 # Escalated -> Closed
305- case_closed_status_flow (case , db_session )
320+ case_closed_status_flow (case = case , db_session = db_session )
321+
322+
323+ def case_to_incident_escalate_flow (
324+ case : Case , organization_slug : OrganizationSlug , db_session = None
325+ ):
326+ """Escalates a case to an incident if the case's type is mapped to an incident type."""
327+ if case .incidents :
328+ # we don't escalate the case if the case is already linked to incidents
329+ return
330+
331+ if not case .case_type .incident_type :
332+ # we don't escalate the case if its type is not mapped to an incident type
333+ return
334+
335+ # we make the assignee of the case the reporter of the incident
336+ reporter = ParticipantUpdate (individual = IndividualContactRead (email = case .assignee .email ))
337+
338+ # we add information about the case in the incident's description
339+ description = (
340+ f"{ case .description } \n \n "
341+ f"This incident was the result of escalating case { case .name } "
342+ f"in the { case .project .name } project. Check out the case in the Dispatch Web UI for additional context."
343+ )
344+
345+ # we create the incident
346+ incident_in = IncidentCreate (
347+ title = case .title ,
348+ description = description ,
349+ status = IncidentStatus .active ,
350+ incident_type = case .case_type .incident_type ,
351+ incident_priority = case .case_priority ,
352+ visibility = case .visibility ,
353+ project = case .case_type .incident_type .project ,
354+ reporter = reporter ,
355+ )
356+ incident = incident_service .create (db_session = db_session , incident_in = incident_in )
357+
358+ # we map the case to the newly created incident
359+ case .incidents .append (incident )
360+
361+ # we run the incident creation flow
362+ incident_flows .incident_create_flow (
363+ incident_id = incident .id , organization_slug = organization_slug , db_session = db_session
364+ )
365+
366+ event_service .log_case_event (
367+ db_session = db_session ,
368+ source = "Dispatch Core App" ,
369+ description = f"The case has been linked to incident { incident .name } in the { incident .project .name } project" ,
370+ case_id = case .id ,
371+ )
372+
373+ # we add the incident's tactical group to the case's storage folder
374+ # to allow incident participants to access the case's artifacts in the folder
375+ storage_members = [incident .tactical_group .email ]
376+ storage_flows .update_storage (
377+ obj = case ,
378+ storage_action = StorageAction .add_members ,
379+ storage_members = storage_members ,
380+ db_session = db_session ,
381+ )
382+
383+ event_service .log_case_event (
384+ db_session = db_session ,
385+ source = "Dispatch Core App" ,
386+ description = f"The members of the incident's tactical group { incident .tactical_group .email } have been given permission to access the case's storage folder" ,
387+ case_id = case .id ,
388+ )
0 commit comments