From e282c22043ae0c7a68c1869e39a503ba31b211fe Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:44:31 -0800 Subject: [PATCH 01/14] Allow cases to be re-assigned after addition of participant model to cases --- src/dispatch/case/service.py | 33 ++++++------ src/dispatch/participant/flows.py | 82 +++++++++++++++++++---------- src/dispatch/participant/service.py | 13 ++++- 3 files changed, 84 insertions(+), 44 deletions(-) diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index 383353106ff4..9b56f38b8ec5 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -230,23 +230,24 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc if case_in.assignee: if case.assignee.individual.email != case_in.assignee.individual.email: - case_assignee = auth_service.get_by_email( - db_session=db_session, email=case_in.assignee.individual.email + participant_flows.add_participant( + case_in.assignee.individual.email, + case, + db_session, + role=ParticipantRoleType.assignee, + ) + participant_flows.remove_participant( + case.assignee.individual.email, + case, + db_session, + ) + event_service.log_case_event( + db_session=db_session, + source="Dispatch Core App", + description=f"Case assigned to {case_in.assignee.individual.email} by {current_user.email}", + dispatch_user_id=case.assignee.id, + case_id=case.id, ) - if case_assignee: - case.assignee = case_assignee - - event_service.log_case_event( - db_session=db_session, - source="Dispatch Core App", - description=f"Case assigned to {case_in.assignee.individual.email} by {current_user.email}", - dispatch_user_id=current_user.id, - case_id=case.id, - ) - else: - log.warning( - f"Dispatch user with email address {case_in.assignee.individual.email} not found." - ) if case_in.case_type: if case.case_type.name != case_in.case_type.name: diff --git a/src/dispatch/participant/flows.py b/src/dispatch/participant/flows.py index 5047822f27eb..82f2a3c66827 100644 --- a/src/dispatch/participant/flows.py +++ b/src/dispatch/participant/flows.py @@ -11,7 +11,7 @@ from dispatch.participant_role.models import ParticipantRoleType, ParticipantRoleCreate from dispatch.service import service as service_service -from .service import get_or_create, get_by_incident_id_and_email +from .service import get_or_create, get_by_incident_id_and_email, get_by_case_id_and_email log = logging.getLogger(__name__) @@ -89,43 +89,63 @@ def add_participant( return participant -def remove_participant(user_email: str, incident: Incident, db_session: SessionLocal): +def remove_participant(user_email: str, subject: Subject, db_session: SessionLocal): """Removes a participant.""" - inactivated = inactivate_participant(user_email, incident, db_session) + inactivated = inactivate_participant(user_email, subject, db_session) + subject_type = get_table_name_by_class_instance(subject) if inactivated: - participant = get_by_incident_id_and_email( - db_session=db_session, incident_id=incident.id, email=user_email - ) + if subject_type == "incident": + participant = get_by_incident_id_and_email( + db_session=db_session, incident_id=subject.id, email=user_email + ) + if subject_type == "case": + participant = get_by_case_id_and_email( + db_session=db_session, case_id=subject.id, email=user_email + ) - log.debug(f"Removing {participant.individual.name} from {incident.name} incident...") + log.debug(f"Removing {participant.individual.name} from {subject.name}...") participant.service = None db_session.add(participant) db_session.commit() - event_service.log_incident_event( - db_session=db_session, - source="Dispatch Core App", - description=f"{participant.individual.name} has been removed", - incident_id=incident.id, - ) - - -def inactivate_participant(user_email: str, incident: Incident, db_session: SessionLocal): + if subject_type == "incident": + event_service.log_incident_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{participant.individual.name} has been removed", + incident_id=subject.id, + ) + if subject_type == "case": + event_service.log_case_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{participant.individual.name} has been removed", + case_id=subject.id, + ) + + +def inactivate_participant(user_email: str, subject: Subject, db_session: SessionLocal): """Inactivates a participant.""" - participant = get_by_incident_id_and_email( - db_session=db_session, incident_id=incident.id, email=user_email - ) + subject_type = get_table_name_by_class_instance(subject) + if subject_type == "incident": + participant = get_by_incident_id_and_email( + db_session=db_session, incident_id=subject.id, email=user_email + ) + if subject_type == "case": + participant = get_by_case_id_and_email( + db_session=db_session, case_id=subject.id, email=user_email + ) if not participant: log.debug( - f"Can't inactivate participant with {user_email} email. They're not a participant of {incident.name} incident." + f"Can't inactivate participant with {user_email} email. They're not a participant of {subject.name}." ) return False - log.debug(f"Inactivating {participant.individual.name} from {incident.name} incident...") + log.debug(f"Inactivating {participant.individual.name} from {subject.name}...") participant_active_roles = participant_role_service.get_all_active_roles( db_session=db_session, participant_id=participant.id @@ -135,12 +155,20 @@ def inactivate_participant(user_email: str, incident: Incident, db_session: Sess db_session=db_session, participant_role=participant_active_role ) - event_service.log_incident_event( - db_session=db_session, - source="Dispatch Core App", - description=f"{participant.individual.name} has been inactivated", - incident_id=incident.id, - ) + if subject_type == "incident": + event_service.log_incident_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{participant.individual.name} has been inactivated", + incident_id=subject.id, + ) + if subject_type == "case": + event_service.log_case_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{participant.individual.name} has been inactivated", + case_id=subject.id, + ) return True diff --git a/src/dispatch/participant/service.py b/src/dispatch/participant/service.py index 8f2e806e5a1d..da525afae216 100644 --- a/src/dispatch/participant/service.py +++ b/src/dispatch/participant/service.py @@ -44,6 +44,17 @@ def get_by_incident_id_and_email( ) +def get_by_case_id_and_email(*, db_session, case_id: int, email: str) -> Optional[Participant]: + """Get a participant by incident id and email.""" + return ( + db_session.query(Participant) + .join(IndividualContact) + .filter(Participant.case_id == case_id) + .filter(IndividualContact.email == email) + .one_or_none() + ) + + def get_by_incident_id_and_service_id( *, db_session, incident_id: int, service_id: int ) -> Optional[Participant]: @@ -84,7 +95,7 @@ def get_or_create( subject_id: int, subject_type: str, individual_id: int, - service_id: int, + service_id: int = None, participant_roles: List[ParticipantRoleCreate], ) -> Participant: """Gets an existing participant object or creates a new one.""" From 12b45e6b105802fc03003f248a72083d1b404194 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:50:44 -0800 Subject: [PATCH 02/14] Update src/dispatch/case/service.py --- src/dispatch/case/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index 9b56f38b8ec5..7fead19b1699 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -245,7 +245,7 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc db_session=db_session, source="Dispatch Core App", description=f"Case assigned to {case_in.assignee.individual.email} by {current_user.email}", - dispatch_user_id=case.assignee.id, + dispatch_user_id=current_user.id, case_id=case.id, ) From 977273d780782e26698dd4177b931167ef93df56 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Mon, 6 Feb 2023 18:09:11 -0800 Subject: [PATCH 03/14] Update src/dispatch/participant/service.py --- src/dispatch/participant/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatch/participant/service.py b/src/dispatch/participant/service.py index da525afae216..ed2a2d1b7ec9 100644 --- a/src/dispatch/participant/service.py +++ b/src/dispatch/participant/service.py @@ -45,7 +45,7 @@ def get_by_incident_id_and_email( def get_by_case_id_and_email(*, db_session, case_id: int, email: str) -> Optional[Participant]: - """Get a participant by incident id and email.""" + """Get a participant by case id and email.""" return ( db_session.query(Participant) .join(IndividualContact) From 8a857db2cd06d7ed76749706c3ea74f685da9aa1 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Mon, 6 Feb 2023 19:22:56 -0800 Subject: [PATCH 04/14] change old assignee to a participant instead of removing them --- src/dispatch/case/service.py | 13 ++++++++----- src/dispatch/participant/flows.py | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index 7fead19b1699..cdd535590fea 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -230,17 +230,20 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc if case_in.assignee: if case.assignee.individual.email != case_in.assignee.individual.email: + # We change the current assignee role to participant + participant_flows.change_participant_role( + db_session=db_session, + case_id=case.id, + email=case.assignee.individual.email, + role=ParticipantRoleType.participant, + ) + # We add the new participant as the assignee participant_flows.add_participant( case_in.assignee.individual.email, case, db_session, role=ParticipantRoleType.assignee, ) - participant_flows.remove_participant( - case.assignee.individual.email, - case, - db_session, - ) event_service.log_case_event( db_session=db_session, source="Dispatch Core App", diff --git a/src/dispatch/participant/flows.py b/src/dispatch/participant/flows.py index 82f2a3c66827..172021672465 100644 --- a/src/dispatch/participant/flows.py +++ b/src/dispatch/participant/flows.py @@ -173,6 +173,31 @@ def inactivate_participant(user_email: str, subject: Subject, db_session: Sessio return True +def change_participant_role( + db_session: SessionLocal, + case_id: int, + email: str, + role: ParticipantRoleType = ParticipantRoleType.participant, +): + """Changes a participants role.""" + participant = get_by_case_id_and_email( + db_session=db_session, + case_id=case_id, + email=email, + ) + log.debug(f"Changing {participant.individual.name}'s role to {role}...") + + participant_role_in = ParticipantRoleCreate(role=role) + participant_role = participant_role_service.create( + db_session=db_session, participant_role_in=participant_role_in + ) + participant.participant_roles.clear() + participant.participant_roles.append(participant_role) + + db_session.add(participant) + db_session.commit() + + def reactivate_participant( user_email: str, incident: Incident, db_session: SessionLocal, service_id: int = None ): From 5bd37eecb522227584ae292269dc83d99d9608f6 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:20:23 -0800 Subject: [PATCH 05/14] leverage case update service in change flow and remove old code --- src/dispatch/case/service.py | 7 +- src/dispatch/participant/flows.py | 129 ++++++++++++++---------------- 2 files changed, 62 insertions(+), 74 deletions(-) diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index cdd535590fea..cfaf2a4f21c9 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -233,9 +233,10 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc # We change the current assignee role to participant participant_flows.change_participant_role( db_session=db_session, - case_id=case.id, - email=case.assignee.individual.email, - role=ParticipantRoleType.participant, + case=case, + user_email=case.assignee.individual.email, + from_role=ParticipantRoleType.assignee, + to_role=ParticipantRoleType.participant, ) # We add the new participant as the assignee participant_flows.add_participant( diff --git a/src/dispatch/participant/flows.py b/src/dispatch/participant/flows.py index 172021672465..7f5f604b89aa 100644 --- a/src/dispatch/participant/flows.py +++ b/src/dispatch/participant/flows.py @@ -2,13 +2,17 @@ from typing import TypeVar from dispatch.case.models import Case -from dispatch.database.core import SessionLocal, get_table_name_by_class_instance +from dispatch.database.core import Session, SessionLocal, get_table_name_by_class_instance from dispatch.event import service as event_service from dispatch.incident.models import Incident from dispatch.individual import service as individual_service from dispatch.participant.models import Participant from dispatch.participant_role import service as participant_role_service -from dispatch.participant_role.models import ParticipantRoleType, ParticipantRoleCreate +from dispatch.participant_role.models import ( + ParticipantRoleType, + ParticipantRoleCreate, + ParticipantRoleUpdate, +) from dispatch.service import service as service_service from .service import get_or_create, get_by_incident_id_and_email, get_by_case_id_and_email @@ -89,63 +93,43 @@ def add_participant( return participant -def remove_participant(user_email: str, subject: Subject, db_session: SessionLocal): +def remove_participant(user_email: str, incident: Incident, db_session: SessionLocal): """Removes a participant.""" - inactivated = inactivate_participant(user_email, subject, db_session) + inactivated = inactivate_participant(user_email, incident, db_session) - subject_type = get_table_name_by_class_instance(subject) if inactivated: - if subject_type == "incident": - participant = get_by_incident_id_and_email( - db_session=db_session, incident_id=subject.id, email=user_email - ) - if subject_type == "case": - participant = get_by_case_id_and_email( - db_session=db_session, case_id=subject.id, email=user_email - ) + participant = get_by_incident_id_and_email( + db_session=db_session, incident_id=incident.id, email=user_email + ) - log.debug(f"Removing {participant.individual.name} from {subject.name}...") + log.debug(f"Removing {participant.individual.name} from {incident.name} incident...") participant.service = None db_session.add(participant) db_session.commit() - if subject_type == "incident": - event_service.log_incident_event( - db_session=db_session, - source="Dispatch Core App", - description=f"{participant.individual.name} has been removed", - incident_id=subject.id, - ) - if subject_type == "case": - event_service.log_case_event( - db_session=db_session, - source="Dispatch Core App", - description=f"{participant.individual.name} has been removed", - case_id=subject.id, - ) - - -def inactivate_participant(user_email: str, subject: Subject, db_session: SessionLocal): - """Inactivates a participant.""" - subject_type = get_table_name_by_class_instance(subject) - if subject_type == "incident": - participant = get_by_incident_id_and_email( - db_session=db_session, incident_id=subject.id, email=user_email - ) - if subject_type == "case": - participant = get_by_case_id_and_email( - db_session=db_session, case_id=subject.id, email=user_email + event_service.log_incident_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{participant.individual.name} has been removed", + incident_id=incident.id, ) + +def inactivate_participant(user_email: str, incident: Incident, db_session: SessionLocal): + """Inactivates a participant.""" + participant = get_by_incident_id_and_email( + db_session=db_session, incident_id=incident.id, email=user_email + ) + if not participant: log.debug( - f"Can't inactivate participant with {user_email} email. They're not a participant of {subject.name}." + f"Can't inactivate participant with {user_email} email. They're not a participant of {incident.name} incident." ) return False - log.debug(f"Inactivating {participant.individual.name} from {subject.name}...") + log.debug(f"Inactivating {participant.individual.name} from {incident.name} incident...") participant_active_roles = participant_role_service.get_all_active_roles( db_session=db_session, participant_id=participant.id @@ -155,47 +139,50 @@ def inactivate_participant(user_email: str, subject: Subject, db_session: Sessio db_session=db_session, participant_role=participant_active_role ) - if subject_type == "incident": - event_service.log_incident_event( - db_session=db_session, - source="Dispatch Core App", - description=f"{participant.individual.name} has been inactivated", - incident_id=subject.id, - ) - if subject_type == "case": - event_service.log_case_event( - db_session=db_session, - source="Dispatch Core App", - description=f"{participant.individual.name} has been inactivated", - case_id=subject.id, - ) + event_service.log_incident_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{participant.individual.name} has been inactivated", + incident_id=incident.id, + ) return True def change_participant_role( - db_session: SessionLocal, - case_id: int, - email: str, - role: ParticipantRoleType = ParticipantRoleType.participant, + db_session: Session, + case: Case, + user_email: str, + from_role: ParticipantRoleType, + to_role: ParticipantRoleType = ParticipantRoleType.participant, ): """Changes a participants role.""" participant = get_by_case_id_and_email( db_session=db_session, - case_id=case_id, - email=email, + case_id=case.id, + email=user_email, ) - log.debug(f"Changing {participant.individual.name}'s role to {role}...") + log.debug(f"Changing {participant.individual.name}'s role from {from_role} to {to_role}...") - participant_role_in = ParticipantRoleCreate(role=role) - participant_role = participant_role_service.create( - db_session=db_session, participant_role_in=participant_role_in - ) - participant.participant_roles.clear() - participant.participant_roles.append(participant_role) + participant_roles = [r.role for r in participant.participant_roles] + if from_role not in participant_roles: + log.debug( + f"{participant.individual.name}'s does not have the role {from_role} that was set to be changed." + ) + return - db_session.add(participant) - db_session.commit() + for role in participant.participant_roles: + if from_role == role.role: + from_participant_role = role + + log.debug(f"{participant.individual.name}'s role changed from {from_role} to {to_role}...") + + participant_role_in = ParticipantRoleUpdate(role=to_role) + participant_role_service.update( + db_session=db_session, + participant_role=from_participant_role, + participant_role_in=participant_role_in, + ) def reactivate_participant( From cc65b154c91d8474d26e9906855fae78d7f90631 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:22:25 -0800 Subject: [PATCH 06/14] remove other old code --- src/dispatch/participant/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatch/participant/service.py b/src/dispatch/participant/service.py index ed2a2d1b7ec9..9fd60947b644 100644 --- a/src/dispatch/participant/service.py +++ b/src/dispatch/participant/service.py @@ -95,7 +95,7 @@ def get_or_create( subject_id: int, subject_type: str, individual_id: int, - service_id: int = None, + service_id: int, participant_roles: List[ParticipantRoleCreate], ) -> Participant: """Gets an existing participant object or creates a new one.""" From cb7dd664bd4acae2fdbc08f1d7369b2dc9c11825 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:44:13 -0800 Subject: [PATCH 07/14] change particpant_role_change flow to role_flow --- src/dispatch/case/service.py | 4 +- src/dispatch/participant/flows.py | 42 +------------------ src/dispatch/participant_role/flows.py | 56 ++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index cfaf2a4f21c9..69254510cca6 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -5,7 +5,6 @@ from pydantic.error_wrappers import ErrorWrapper, ValidationError from typing import List, Optional -from dispatch.auth import service as auth_service from dispatch.auth.models import DispatchUser from dispatch.case.priority import service as case_priority_service from dispatch.case.severity import service as case_severity_service @@ -14,6 +13,7 @@ from dispatch.exceptions import NotFoundError from dispatch.incident import service as incident_service from dispatch.participant import flows as participant_flows +from dispatch.participant_role import flows as role_flows from dispatch.participant_role.models import ParticipantRoleType from dispatch.project import service as project_service from dispatch.service import flows as service_flows @@ -231,7 +231,7 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc if case_in.assignee: if case.assignee.individual.email != case_in.assignee.individual.email: # We change the current assignee role to participant - participant_flows.change_participant_role( + role_flows.change_role_flow( db_session=db_session, case=case, user_email=case.assignee.individual.email, diff --git a/src/dispatch/participant/flows.py b/src/dispatch/participant/flows.py index 7f5f604b89aa..883cfc192b15 100644 --- a/src/dispatch/participant/flows.py +++ b/src/dispatch/participant/flows.py @@ -2,7 +2,7 @@ from typing import TypeVar from dispatch.case.models import Case -from dispatch.database.core import Session, SessionLocal, get_table_name_by_class_instance +from dispatch.database.core import SessionLocal, get_table_name_by_class_instance from dispatch.event import service as event_service from dispatch.incident.models import Incident from dispatch.individual import service as individual_service @@ -11,11 +11,9 @@ from dispatch.participant_role.models import ( ParticipantRoleType, ParticipantRoleCreate, - ParticipantRoleUpdate, ) from dispatch.service import service as service_service - -from .service import get_or_create, get_by_incident_id_and_email, get_by_case_id_and_email +from .service import get_or_create, get_by_incident_id_and_email log = logging.getLogger(__name__) @@ -149,42 +147,6 @@ def inactivate_participant(user_email: str, incident: Incident, db_session: Sess return True -def change_participant_role( - db_session: Session, - case: Case, - user_email: str, - from_role: ParticipantRoleType, - to_role: ParticipantRoleType = ParticipantRoleType.participant, -): - """Changes a participants role.""" - participant = get_by_case_id_and_email( - db_session=db_session, - case_id=case.id, - email=user_email, - ) - log.debug(f"Changing {participant.individual.name}'s role from {from_role} to {to_role}...") - - participant_roles = [r.role for r in participant.participant_roles] - if from_role not in participant_roles: - log.debug( - f"{participant.individual.name}'s does not have the role {from_role} that was set to be changed." - ) - return - - for role in participant.participant_roles: - if from_role == role.role: - from_participant_role = role - - log.debug(f"{participant.individual.name}'s role changed from {from_role} to {to_role}...") - - participant_role_in = ParticipantRoleUpdate(role=to_role) - participant_role_service.update( - db_session=db_session, - participant_role=from_participant_role, - participant_role_in=participant_role_in, - ) - - def reactivate_participant( user_email: str, incident: Incident, db_session: SessionLocal, service_id: int = None ): diff --git a/src/dispatch/participant_role/flows.py b/src/dispatch/participant_role/flows.py index 260a95a625f1..f3a547d73d68 100644 --- a/src/dispatch/participant_role/flows.py +++ b/src/dispatch/participant_role/flows.py @@ -1,14 +1,17 @@ import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from dispatch.models import Incident -from dispatch.database.core import SessionLocal +from dispatch.database.core import Session, SessionLocal, get_table_name_by_class_instance from dispatch.event import service as event_service from dispatch.participant import service as participant_service - -from .models import ParticipantRoleType +from dispatch.participant_role import service as participant_role_service +from dispatch.participant_role.models import ( + ParticipantRoleType, + ParticipantRoleUpdate, +) from .service import get_all_active_roles, add_role, renounce_role @@ -135,3 +138,48 @@ def assign_role_flow( log.debug(f"We were not able to assign the {assignee_role} role to {assignee_email}.") return "role_not_assigned" + + +def change_role_flow( + db_session: Session, + subject: Any, + user_email: str, + from_role: ParticipantRoleType, + to_role: ParticipantRoleType = ParticipantRoleType.participant, +): + """Changes a participants role.""" + subject_type = get_table_name_by_class_instance(subject) + if subject_type == "case": + participant = participant_service.get_by_case_id_and_email( + db_session=db_session, + case_id=subject.id, + email=user_email, + ) + if subject_type == "incident": + participant = participant_service.get_by_incident_id_and_email( + db_session=db_session, + incident_id=subject.id, + email=user_email, + ) + + log.debug(f"Changing {participant.individual.name}'s role from {from_role} to {to_role}...") + + participant_roles = [r.role for r in participant.participant_roles] + if from_role not in participant_roles: + log.debug( + f"{participant.individual.name}'s does not have the role {from_role} that was set to be changed." + ) + return + + for role in participant.participant_roles: + if from_role == role.role: + from_participant_role = role + + log.debug(f"{participant.individual.name}'s role changed from {from_role} to {to_role}...") + + participant_role_in = ParticipantRoleUpdate(role=to_role) + participant_role_service.update( + db_session=db_session, + participant_role=from_participant_role, + participant_role_in=participant_role_in, + ) From 77be5d23ef7511b47d2b9340ab2bc49107e99618 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:48:14 -0800 Subject: [PATCH 08/14] Update src/dispatch/case/service.py --- src/dispatch/case/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index 69254510cca6..f671c4673922 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -233,7 +233,7 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc # We change the current assignee role to participant role_flows.change_role_flow( db_session=db_session, - case=case, + subject=case, user_email=case.assignee.individual.email, from_role=ParticipantRoleType.assignee, to_role=ParticipantRoleType.participant, From 90dcc5d17e2e482b444bcc04175f6349aedd1b73 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:29:52 -0800 Subject: [PATCH 09/14] model participant updates after case flow for extensibility and consistency --- src/dispatch/case/flows.py | 101 +++++++++++++++++++++---- src/dispatch/case/service.py | 26 ------- src/dispatch/case/views.py | 1 + src/dispatch/participant/service.py | 24 ++++++ src/dispatch/participant_role/flows.py | 73 +++++++++++------- 5 files changed, 160 insertions(+), 65 deletions(-) diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py index 5f41a0ac18ce..d1c000e5d015 100644 --- a/src/dispatch/case/flows.py +++ b/src/dispatch/case/flows.py @@ -19,10 +19,10 @@ from dispatch.incident.models import IncidentCreate from dispatch.individual.models import IndividualContactRead from dispatch.models import OrganizationSlug, PrimaryKey -from dispatch.participant import flows as participant_flows -from dispatch.participant_role.models import ParticipantRoleType from dispatch.participant import service as participant_service +from dispatch.participant import flows as participant_flows from dispatch.participant.models import ParticipantUpdate +from dispatch.participant_role.models import ParticipantRoleType from dispatch.plugin import service as plugin_service from dispatch.storage import flows as storage_flows from dispatch.storage.enums import StorageAction @@ -368,6 +368,7 @@ def case_update_flow( *, case_id: int, previous_case: CaseRead, + assignee_email: str, user_email: str, organization_slug: OrganizationSlug, db_session=None, @@ -389,20 +390,30 @@ def case_update_flow( ticket_flows.update_case_ticket(case=case, db_session=db_session) # we update the tactical group if we have a new assignee - if previous_case.assignee.individual.email != case.assignee.individual.email: - group_flows.update_group( - subject=case, - group=case.tactical_group, - group_action=GroupAction.add_member, - group_member=case.assignee.individual.email, - db_session=db_session, + if previous_case.assignee.individual.email != assignee_email: + log.debug( + f"{user_email} updated Case {case_id} assignee from {previous_case.assignee.individual.email} to {assignee_email}" ) - event_service.log_case_event( + case_assign_role_flow( + case_id=case.id, + assignee_email=assignee_email, + assignee_role=ParticipantRoleType.assignee, db_session=db_session, - source="Dispatch Core App", - description="Case group updated", - case_id=case_id, ) + if case.tactical_group: + group_flows.update_group( + subject=case, + group=case.tactical_group, + group_action=GroupAction.add_member, + group_member=case.assignee.individual.email, + db_session=db_session, + ) + event_service.log_case_event( + db_session=db_session, + source="Dispatch Core App", + description="Case group updated", + case_id=case_id, + ) # we send the case updated notification update_conversation(case, db_session) @@ -639,3 +650,67 @@ def case_to_incident_endpoint_escalate_flow( 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", case_id=case.id, ) + + +def case_assign_role_flow( + case_id: int, + assignee_email: str, + assignee_role: str, + db_session: SessionLocal, +): + """Runs the case participant role assignment flow.""" + + from dispatch.participant_role import flows as role_flow + + case = get(case_id=case_id, db_session=db_session) + + # we add the assignee to the incident if they're not a participant + case_add_or_reactivate_participant_flow(assignee_email, case.id, db_session=db_session) + + role_flow.assign_role_flow(case, assignee_email, assignee_role, db_session) + + +@background_task +def case_add_or_reactivate_participant_flow( + user_email: str, + case_id: int, + participant_role: ParticipantRoleType = ParticipantRoleType.participant, + service_id: int = 0, + event: dict = None, + organization_slug: str = None, + db_session=None, +): + """Runs the case add or reactivate participant flow.""" + case = get(db_session=db_session, case_id=case_id) + + if service_id: + # we need to ensure that we don't add another member of a service if one + # already exists (e.g. overlapping oncalls, we assume they will hand-off if necessary) + participant = participant_service.get_by_case_id_and_service_id( + case_id=case_id, service_id=service_id, db_session=db_session + ) + + if participant: + log.debug("Skipping resolved participant. Oncall service member already engaged.") + return + + participant = participant_service.get_by_case_id_and_email( + db_session=db_session, case_id=case.id, email=user_email + ) + + if participant: + if participant.active_roles: + return participant + + if case.status != CaseStatus.closed: + # we reactivate the participant + participant_flows.reactivate_participant( + user_email, case, db_session, service_id=service_id + ) + else: + # we add the participant to the incident + participant = participant_flows.add_participant( + user_email, case, db_session, service_id=service_id, role=participant_role + ) + + return participant diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index f671c4673922..afcddedb1fa7 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -13,7 +13,6 @@ from dispatch.exceptions import NotFoundError from dispatch.incident import service as incident_service from dispatch.participant import flows as participant_flows -from dispatch.participant_role import flows as role_flows from dispatch.participant_role.models import ParticipantRoleType from dispatch.project import service as project_service from dispatch.service import flows as service_flows @@ -228,31 +227,6 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc for field in update_data.keys(): setattr(case, field, update_data[field]) - if case_in.assignee: - if case.assignee.individual.email != case_in.assignee.individual.email: - # We change the current assignee role to participant - role_flows.change_role_flow( - db_session=db_session, - subject=case, - user_email=case.assignee.individual.email, - from_role=ParticipantRoleType.assignee, - to_role=ParticipantRoleType.participant, - ) - # We add the new participant as the assignee - participant_flows.add_participant( - case_in.assignee.individual.email, - case, - db_session, - role=ParticipantRoleType.assignee, - ) - event_service.log_case_event( - db_session=db_session, - source="Dispatch Core App", - description=f"Case assigned to {case_in.assignee.individual.email} by {current_user.email}", - dispatch_user_id=current_user.id, - case_id=case.id, - ) - if case_in.case_type: if case.case_type.name != case_in.case_type.name: case_type = case_type_service.get_by_name( diff --git a/src/dispatch/case/views.py b/src/dispatch/case/views.py index 3b51eef8eca5..7ddb00d78de6 100644 --- a/src/dispatch/case/views.py +++ b/src/dispatch/case/views.py @@ -172,6 +172,7 @@ def update_case( case_update_flow, case_id=case_id, previous_case=previous_case, + assignee_email=case_in.assignee.individual.email, user_email=current_user.email, organization_slug=organization, ) diff --git a/src/dispatch/participant/service.py b/src/dispatch/participant/service.py index 9fd60947b644..31569f6649d6 100644 --- a/src/dispatch/participant/service.py +++ b/src/dispatch/participant/service.py @@ -31,6 +31,18 @@ def get_by_incident_id_and_role( ) +def get_by_case_id_and_role(*, db_session, case_id: int, role: str) -> Optional[Participant]: + """Get a participant by case id and role name.""" + return ( + db_session.query(Participant) + .join(ParticipantRole) + .filter(Participant.case_id == case_id) + .filter(ParticipantRole.renounced_at.is_(None)) + .filter(ParticipantRole.role == role) + .one_or_none() + ) + + def get_by_incident_id_and_email( *, db_session, incident_id: int, email: str ) -> Optional[Participant]: @@ -67,6 +79,18 @@ def get_by_incident_id_and_service_id( ) +def get_by_case_id_and_service_id( + *, db_session, case_id: int, service_id: int +) -> Optional[Participant]: + """Get participant by incident and service id.""" + return ( + db_session.query(Participant) + .filter(Participant.case_id == case_id) + .filter(Participant.service_id == service_id) + .one_or_none() + ) + + def get_by_incident_id_and_conversation_id( *, db_session, incident_id: int, user_conversation_id: str ) -> Optional[Participant]: diff --git a/src/dispatch/participant_role/flows.py b/src/dispatch/participant_role/flows.py index f3a547d73d68..f2cdad00010f 100644 --- a/src/dispatch/participant_role/flows.py +++ b/src/dispatch/participant_role/flows.py @@ -1,8 +1,5 @@ import logging -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from dispatch.models import Incident +from typing import Any from dispatch.database.core import Session, SessionLocal, get_table_name_by_class_instance from dispatch.event import service as event_service @@ -19,7 +16,7 @@ def assign_role_flow( - incident: "Incident", assignee_email: str, assignee_role: str, db_session: SessionLocal + subject: Any, assignee_email: str, assignee_role: str, db_session: SessionLocal ): """Attempts to assign a role to a participant. @@ -31,10 +28,17 @@ def assign_role_flow( """ # we get the participant for the assignee - assignee_participant = participant_service.get_by_incident_id_and_email( - db_session=db_session, incident_id=incident.id, email=assignee_email - ) + subject_type = get_table_name_by_class_instance(subject) + if subject_type == "incident": + assignee_participant = participant_service.get_by_incident_id_and_email( + db_session=db_session, incident_id=subject.id, email=assignee_email + ) + if subject_type == "case": + assignee_participant = participant_service.get_by_case_id_and_email( + db_session=db_session, case_id=subject.id, email=assignee_email + ) + # Cases don't have observers, so we don't need to handle this if assignee_role == ParticipantRoleType.observer: # we make the assignee renounce to the participant role participant_active_roles = get_all_active_roles( @@ -56,18 +60,26 @@ def assign_role_flow( db_session=db_session, source="Dispatch Core App", description=f"{assignee_participant.individual.name} has been assigned the role of {assignee_role}", - incident_id=incident.id, + incident_id=subject.id, ) return "role_assigned" # we get the participant that holds the role assigned to the assignee - participant_with_assignee_role = participant_service.get_by_incident_id_and_role( - db_session=db_session, incident_id=incident.id, role=assignee_role - ) - if participant_with_assignee_role is assignee_participant: - return "assignee_has_role" + if subject_type == "incident": + participant_with_assignee_role = participant_service.get_by_incident_id_and_role( + db_session=db_session, incident_id=subject.id, role=assignee_role + ) + if subject_type == "case": + participant_with_assignee_role = participant_service.get_by_case_id_and_role( + db_session=db_session, case_id=subject.id, role=assignee_role + ) + + if participant_with_assignee_role and assignee_participant: + if participant_with_assignee_role is assignee_participant: + log.debug(f"{assignee_participant.individual.email} already has role: {assignee_role}") + return "assignee_has_role" if participant_with_assignee_role: # we make the participant renounce to the role that has been given to the assignee @@ -114,25 +126,34 @@ def assign_role_flow( # we update the commander, reporter, scribe, or liaison foreign key if assignee_role == ParticipantRoleType.incident_commander: - incident.commander_id = assignee_participant.id + subject.commander_id = assignee_participant.id elif assignee_role == ParticipantRoleType.reporter: - incident.reporter_id = assignee_participant.id + subject.reporter_id = assignee_participant.id elif assignee_role == ParticipantRoleType.scribe: - incident.scribe_id = assignee_participant.id + subject.scribe_id = assignee_participant.id elif assignee_role == ParticipantRoleType.liaison: - incident.liaison_id = assignee_participant.id + subject.liaison_id = assignee_participant.id + elif assignee_role == ParticipantRoleType.assignee: + subject.assignee_id = assignee_participant.id # we add and commit the changes - db_session.add(incident) + db_session.add(subject) db_session.commit() - event_service.log_incident_event( - db_session=db_session, - source="Dispatch Core App", - description=f"{assignee_participant.individual.name} has been assigned the role of {assignee_role}", - incident_id=incident.id, - ) - + if subject_type == "incident": + event_service.log_incident_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{assignee_participant.individual.name} has been assigned the role of {assignee_role}", + incident_id=subject.id, + ) + if subject_type == "case": + event_service.log_case_event( + db_session=db_session, + source="Dispatch Core App", + description=f"{assignee_participant.individual.name} has been assigned the role of {assignee_role}", + case_id=subject.id, + ) return "role_assigned" log.debug(f"We were not able to assign the {assignee_role} role to {assignee_email}.") From b2a33be4a5796abd4635cfae8df6cba69158144f Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:40:16 -0800 Subject: [PATCH 10/14] remove change_role_flow code --- src/dispatch/participant_role/flows.py | 45 -------------------------- 1 file changed, 45 deletions(-) diff --git a/src/dispatch/participant_role/flows.py b/src/dispatch/participant_role/flows.py index f2cdad00010f..031fa0c1672b 100644 --- a/src/dispatch/participant_role/flows.py +++ b/src/dispatch/participant_role/flows.py @@ -159,48 +159,3 @@ def assign_role_flow( log.debug(f"We were not able to assign the {assignee_role} role to {assignee_email}.") return "role_not_assigned" - - -def change_role_flow( - db_session: Session, - subject: Any, - user_email: str, - from_role: ParticipantRoleType, - to_role: ParticipantRoleType = ParticipantRoleType.participant, -): - """Changes a participants role.""" - subject_type = get_table_name_by_class_instance(subject) - if subject_type == "case": - participant = participant_service.get_by_case_id_and_email( - db_session=db_session, - case_id=subject.id, - email=user_email, - ) - if subject_type == "incident": - participant = participant_service.get_by_incident_id_and_email( - db_session=db_session, - incident_id=subject.id, - email=user_email, - ) - - log.debug(f"Changing {participant.individual.name}'s role from {from_role} to {to_role}...") - - participant_roles = [r.role for r in participant.participant_roles] - if from_role not in participant_roles: - log.debug( - f"{participant.individual.name}'s does not have the role {from_role} that was set to be changed." - ) - return - - for role in participant.participant_roles: - if from_role == role.role: - from_participant_role = role - - log.debug(f"{participant.individual.name}'s role changed from {from_role} to {to_role}...") - - participant_role_in = ParticipantRoleUpdate(role=to_role) - participant_role_service.update( - db_session=db_session, - participant_role=from_participant_role, - participant_role_in=participant_role_in, - ) From daf239bb45fdb2a0493054b5142bd3320e2b3f53 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:40:48 -0800 Subject: [PATCH 11/14] remove unused imports --- src/dispatch/participant_role/flows.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/dispatch/participant_role/flows.py b/src/dispatch/participant_role/flows.py index 031fa0c1672b..652e1c3c9ff0 100644 --- a/src/dispatch/participant_role/flows.py +++ b/src/dispatch/participant_role/flows.py @@ -1,14 +1,11 @@ import logging from typing import Any -from dispatch.database.core import Session, SessionLocal, get_table_name_by_class_instance +from dispatch.database.core import SessionLocal, get_table_name_by_class_instance from dispatch.event import service as event_service from dispatch.participant import service as participant_service -from dispatch.participant_role import service as participant_role_service -from dispatch.participant_role.models import ( - ParticipantRoleType, - ParticipantRoleUpdate, -) +from dispatch.participant_role.models import ParticipantRoleType + from .service import get_all_active_roles, add_role, renounce_role From 48485744ffc0f2d41d88f7a901268c57d784037b Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:43:19 -0800 Subject: [PATCH 12/14] remove duplicate case_add_or_remove_participant_flow --- src/dispatch/case/flows.py | 46 -------------------------------------- 1 file changed, 46 deletions(-) diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py index d1c000e5d015..aadd38d5b080 100644 --- a/src/dispatch/case/flows.py +++ b/src/dispatch/case/flows.py @@ -668,49 +668,3 @@ def case_assign_role_flow( case_add_or_reactivate_participant_flow(assignee_email, case.id, db_session=db_session) role_flow.assign_role_flow(case, assignee_email, assignee_role, db_session) - - -@background_task -def case_add_or_reactivate_participant_flow( - user_email: str, - case_id: int, - participant_role: ParticipantRoleType = ParticipantRoleType.participant, - service_id: int = 0, - event: dict = None, - organization_slug: str = None, - db_session=None, -): - """Runs the case add or reactivate participant flow.""" - case = get(db_session=db_session, case_id=case_id) - - if service_id: - # we need to ensure that we don't add another member of a service if one - # already exists (e.g. overlapping oncalls, we assume they will hand-off if necessary) - participant = participant_service.get_by_case_id_and_service_id( - case_id=case_id, service_id=service_id, db_session=db_session - ) - - if participant: - log.debug("Skipping resolved participant. Oncall service member already engaged.") - return - - participant = participant_service.get_by_case_id_and_email( - db_session=db_session, case_id=case.id, email=user_email - ) - - if participant: - if participant.active_roles: - return participant - - if case.status != CaseStatus.closed: - # we reactivate the participant - participant_flows.reactivate_participant( - user_email, case, db_session, service_id=service_id - ) - else: - # we add the participant to the incident - participant = participant_flows.add_participant( - user_email, case, db_session, service_id=service_id, role=participant_role - ) - - return participant From e07e716164de12acefb349d43aa792692ae22da7 Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:46:41 -0800 Subject: [PATCH 13/14] fix func name --- src/dispatch/case/flows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py index aadd38d5b080..6623ec182b13 100644 --- a/src/dispatch/case/flows.py +++ b/src/dispatch/case/flows.py @@ -92,7 +92,7 @@ def add_participants_to_conversation( @background_task -def case_add_or_reactive_participant_flow( +def case_add_or_reactivate_participant_flow( user_email: str, case_id: int, participant_role: ParticipantRoleType = ParticipantRoleType.participant, From 507babf0fe1dc7aea3c1bd22851cf978535d720a Mon Sep 17 00:00:00 2001 From: Will Sheldon <114631109+wssheldon@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:51:28 -0800 Subject: [PATCH 14/14] move import to top of file --- src/dispatch/case/flows.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py index 1a240f755bfc..cb9bc8d282a5 100644 --- a/src/dispatch/case/flows.py +++ b/src/dispatch/case/flows.py @@ -21,6 +21,7 @@ from dispatch.models import OrganizationSlug, PrimaryKey from dispatch.participant import service as participant_service from dispatch.participant import flows as participant_flows +from dispatch.participant_role import flows as role_flow from dispatch.participant.models import ParticipantUpdate from dispatch.participant_role.models import ParticipantRoleType from dispatch.plugin import service as plugin_service @@ -669,9 +670,6 @@ def case_assign_role_flow( db_session: SessionLocal, ): """Runs the case participant role assignment flow.""" - - from dispatch.participant_role import flows as role_flow - case = get(case_id=case_id, db_session=db_session) # we add the assignee to the incident if they're not a participant