diff --git a/src/dispatch/auth/permissions.py b/src/dispatch/auth/permissions.py
index 9b3f5e1d0d23..b06ad6af5f1b 100644
--- a/src/dispatch/auth/permissions.py
+++ b/src/dispatch/auth/permissions.py
@@ -15,6 +15,7 @@
from dispatch.models import PrimaryKeyModel
from dispatch.organization import service as organization_service
from dispatch.organization.models import OrganizationRead
+from dispatch.participant_role.enums import ParticipantRoleType
log = logging.getLogger(__name__)
@@ -315,6 +316,8 @@ def has_required_permissions(
if current_incident.reporter.individual.email == current_user.email:
return True
+ return False
+
class IncidentCommanderPermission(BasePermission):
def has_required_permissions(
@@ -331,8 +334,10 @@ def has_required_permissions(
if current_incident.commander.individual.email == current_user.email:
return True
+ return False
+
-class IncidentCommanderOrReporterPermission(BasePermission):
+class IncidentCommanderOrScribePermission(BasePermission):
def has_required_permissions(
self,
request: Request,
@@ -343,13 +348,18 @@ def has_required_permissions(
if not current_incident:
return False
- if current_incident.commander:
- if current_incident.commander.individual.email == current_user.email:
- return True
+ if current_incident.commander and current_incident.commander.individual.email == current_user.email:
+ return True
- if current_incident.reporter:
- if current_incident.reporter.individual.email == current_user.email:
- return True
+ scribes = [
+ participant.individual.email
+ for participant in current_incident.participants
+ if ParticipantRoleType.scribe in [role.role for role in participant.participant_roles]
+ ]
+ if current_user.email in scribes:
+ return True
+
+ return False
class IncidentParticipantPermission(BasePermission):
diff --git a/src/dispatch/event/flows.py b/src/dispatch/event/flows.py
new file mode 100644
index 000000000000..f6123404c0f8
--- /dev/null
+++ b/src/dispatch/event/flows.py
@@ -0,0 +1,54 @@
+import logging
+
+from dispatch.decorators import background_task
+from dispatch.event import service as event_service
+from dispatch.incident import service as incident_service
+from dispatch.individual import service as individual_service
+from dispatch.event.models import EventUpdate, EventCreateMinimal
+
+log = logging.getLogger(__name__)
+
+
+@background_task
+def log_incident_event(
+ user_email: str,
+ incident_id: int,
+ event_in: EventCreateMinimal,
+ db_session=None,
+ organization_slug: str = None,
+):
+ incident = incident_service.get(db_session=db_session, incident_id=incident_id)
+ individual = individual_service.get_by_email_and_project(
+ db_session=db_session, email=user_email, project_id=incident.project.id
+ )
+ event_in.source = f"Custom event created by {individual.name}"
+
+ event_service.log_incident_event(
+ db_session=db_session,
+ incident_id=incident_id,
+ **event_in.__dict__,
+ )
+
+
+@background_task
+def update_incident_event(
+ event_in: EventUpdate,
+ db_session=None,
+ organization_slug: str = None,
+):
+ event_service.update_incident_event(
+ db_session=db_session,
+ event_in=event_in,
+ )
+
+
+@background_task
+def delete_incident_event(
+ event_uuid: str,
+ db_session=None,
+ organization_slug: str = None,
+):
+ event_service.delete_incident_event(
+ db_session=db_session,
+ uuid=event_uuid,
+ )
diff --git a/src/dispatch/event/models.py b/src/dispatch/event/models.py
index da61aeb201f3..f8d3c1b38c5a 100644
--- a/src/dispatch/event/models.py
+++ b/src/dispatch/event/models.py
@@ -60,3 +60,11 @@ class EventUpdate(EventBase):
class EventRead(EventBase):
pass
+
+
+class EventCreateMinimal(DispatchBase):
+ started_at: datetime
+ source: str
+ description: str
+ details: dict
+ type: Optional[str]
diff --git a/src/dispatch/event/service.py b/src/dispatch/event/service.py
index 5d729b1e9078..d80a1152add4 100644
--- a/src/dispatch/event/service.py
+++ b/src/dispatch/event/service.py
@@ -20,22 +20,22 @@ def get(*, db_session, event_id: int) -> Optional[Event]:
return db_session.query(Event).filter(Event.id == event_id).one_or_none()
-def get_by_case_id(*, db_session, case_id: int) -> List[Optional[Event]]:
+def get_by_case_id(*, db_session, case_id: int) -> list[Event | None]:
"""Get events by case id."""
return db_session.query(Event).filter(Event.case_id == case_id)
-def get_by_incident_id(*, db_session, incident_id: int) -> List[Optional[Event]]:
+def get_by_incident_id(*, db_session, incident_id: int) -> list[Event | None]:
"""Get events by incident id."""
return db_session.query(Event).filter(Event.incident_id == incident_id)
-def get_by_uuid(*, db_session, uuid: str) -> List[Optional[Event]]:
+def get_by_uuid(*, db_session, uuid: str) -> list[Event | None]:
"""Get events by uuid."""
return db_session.query(Event).filter(Event.uuid == uuid).one_or_none()
-def get_all(*, db_session) -> List[Optional[Event]]:
+def get_all(*, db_session) -> list[Event | None]:
"""Get all events."""
return db_session.query(Event)
@@ -161,29 +161,10 @@ def log_case_event(
def update_incident_event(
db_session,
- uuid: str,
- source: str,
- description: str,
- started_at: datetime = None,
- ended_at: datetime = None,
- details: dict = None,
- type: str = EventType.other,
+ event_in: EventUpdate,
) -> Event:
"""Updates an event in the incident timeline."""
- event = get_by_uuid(db_session=db_session, uuid=uuid)
- if not ended_at:
- ended_at = started_at
-
- event_in = EventUpdate(
- uuid=uuid,
- started_at=started_at,
- ended_at=ended_at,
- source=source,
- details=details,
- description=description,
- type=type,
- )
-
+ event = get_by_uuid(db_session=db_session, uuid=event_in.uuid)
event = update(db_session=db_session, event=event, event_in=event_in)
return event
diff --git a/src/dispatch/incident/views.py b/src/dispatch/incident/views.py
index 37ff3434497a..caa9acb430f6 100644
--- a/src/dispatch/incident/views.py
+++ b/src/dispatch/incident/views.py
@@ -13,7 +13,7 @@
from dispatch.auth.permissions import (
IncidentEditPermission,
- IncidentCommanderOrReporterPermission,
+ IncidentCommanderOrScribePermission,
IncidentJoinOrSubscribePermission,
IncidentViewPermission,
PermissionsDependency,
@@ -27,7 +27,9 @@
from dispatch.models import OrganizationSlug, PrimaryKey
from dispatch.participant.models import ParticipantUpdate
from dispatch.report import flows as report_flows
+from dispatch.event import flows as event_flows
from dispatch.report.models import ExecutiveReportCreate, TacticalReportCreate
+from dispatch.event.models import EventUpdate, EventCreateMinimal
from .flows import (
incident_add_or_reactivate_participant_flow,
@@ -311,16 +313,16 @@ def create_custom_event(
organization: OrganizationSlug,
incident_id: PrimaryKey,
current_incident: CurrentIncident,
- event_in: dict,
+ event_in: EventCreateMinimal,
current_user: CurrentUser,
background_tasks: BackgroundTasks,
):
- event_in.update(
- {"details": {"created_by": current_user.email, "added_on": str(datetime.utcnow())}}
+ event_in.details.update(
+ {"created_by": current_user.email, "added_on": str(datetime.utcnow())}
)
"""Creates a custom event."""
background_tasks.add_task(
- report_flows.log_incident_event,
+ event_flows.log_incident_event,
user_email=current_user.email,
incident_id=current_incident.id,
event_in=event_in,
@@ -331,34 +333,32 @@ def create_custom_event(
@router.patch(
"/{incident_id}/event",
summary="Updates a custom event.",
- dependencies=[Depends(PermissionsDependency([IncidentCommanderOrReporterPermission]))],
+ dependencies=[Depends(PermissionsDependency([IncidentCommanderOrScribePermission]))],
)
def update_custom_event(
db_session: DbSession,
organization: OrganizationSlug,
incident_id: PrimaryKey,
current_incident: CurrentIncident,
- event_in: dict,
+ event_in: EventUpdate,
current_user: CurrentUser,
background_tasks: BackgroundTasks,
):
- if event_in.get("details"):
- event_in.update(
+ if event_in.details:
+ event_in.details.update(
{
- "details": {
- **event_in["details"],
- "updated_by": current_user.email,
- "updated_on": str(datetime.utcnow()),
- }
+ **event_in.details,
+ "updated_by": current_user.email,
+ "updated_on": str(datetime.utcnow()),
}
)
else:
- event_in.update(
- {"details": {"updated_by": current_user.email, "updated_on": str(datetime.utcnow())}}
+ event_in.details.update(
+ {"updated_by": current_user.email, "updated_on": str(datetime.utcnow())}
)
"""Updates a custom event."""
background_tasks.add_task(
- report_flows.update_incident_event,
+ event_flows.update_incident_event,
event_in=event_in,
organization_slug=organization,
)
@@ -367,7 +367,7 @@ def update_custom_event(
@router.delete(
"/{incident_id}/event/{event_uuid}",
summary="Deletes a custom event.",
- dependencies=[Depends(PermissionsDependency([IncidentCommanderOrReporterPermission]))],
+ dependencies=[Depends(PermissionsDependency([IncidentCommanderOrScribePermission]))],
)
def delete_custom_event(
db_session: DbSession,
@@ -380,7 +380,7 @@ def delete_custom_event(
):
"""Deletes a custom event."""
background_tasks.add_task(
- report_flows.delete_incident_event,
+ event_flows.delete_incident_event,
event_uuid=event_uuid,
organization_slug=organization,
)
diff --git a/src/dispatch/plugins/dispatch_slack/incident/interactive.py b/src/dispatch/plugins/dispatch_slack/incident/interactive.py
index c47af6a68f54..b9e0c26d383d 100644
--- a/src/dispatch/plugins/dispatch_slack/incident/interactive.py
+++ b/src/dispatch/plugins/dispatch_slack/incident/interactive.py
@@ -691,7 +691,7 @@ def handle_timeline_added_event(
incident_id=context["subject"].id,
individual_id=individual.id,
started_at=message_ts_utc,
- type=EventType.custom_event,
+ type=EventType.imported_message,
)
@@ -1048,7 +1048,7 @@ def handle_add_timeline_submission_event(
description=f'"{event_description}," said {participant.individual.name}',
incident_id=context["subject"].id,
individual_id=participant.individual.id,
- type=EventType.custom_event,
+ type=EventType.imported_message,
)
send_success_modal(
diff --git a/src/dispatch/report/flows.py b/src/dispatch/report/flows.py
index b724fba50eca..8fca3c5bfc5a 100644
--- a/src/dispatch/report/flows.py
+++ b/src/dispatch/report/flows.py
@@ -12,7 +12,6 @@
from dispatch.exceptions import InvalidConfigurationError
from dispatch.incident import service as incident_service
from dispatch.participant import service as participant_service
-from dispatch.individual import service as individual_service
from dispatch.plugin import service as plugin_service
from .enums import ReportTypes
@@ -80,60 +79,6 @@ def create_tactical_report(
return tactical_report
-@background_task
-def log_incident_event(
- user_email: str,
- incident_id: int,
- event_in: dict,
- organization_slug: str = None,
- db_session=None,
-):
- incident = incident_service.get(db_session=db_session, incident_id=incident_id)
- individual = individual_service.get_by_email_and_project(
- db_session=db_session, email=user_email, project_id=incident.project.id
- )
- event_in["source"] = f"Custom event created by {individual.name}"
-
- event_service.log_incident_event(
- db_session=db_session,
- source=event_in["source"],
- description=event_in["description"],
- incident_id=incident_id,
- started_at=event_in["started_at"],
- details=event_in["details"],
- type=event_in["type"],
- )
-
-
-@background_task
-def update_incident_event(
- event_in: dict,
- organization_slug: str = None,
- db_session=None,
-):
- event_service.update_incident_event(
- db_session=db_session,
- uuid=event_in["uuid"],
- source=event_in["source"],
- description=event_in["description"],
- started_at=event_in["started_at"],
- details=event_in["details"],
- type=event_in["type"],
- )
-
-
-@background_task
-def delete_incident_event(
- event_uuid: str,
- organization_slug: str = None,
- db_session=None,
-):
- event_service.delete_incident_event(
- db_session=db_session,
- uuid=event_uuid,
- )
-
-
@background_task
def create_executive_report(
user_email: str,
diff --git a/src/dispatch/static/dispatch/src/incident/TimelineTab.vue b/src/dispatch/static/dispatch/src/incident/TimelineTab.vue
index 667db8e6f0dc..0158c6fea5e2 100644
--- a/src/dispatch/static/dispatch/src/incident/TimelineTab.vue
+++ b/src/dispatch/static/dispatch/src/incident/TimelineTab.vue
@@ -41,7 +41,7 @@
:icon="iconItem(event)"
:key="event.id"
class="mb-4"
- :class="event.type == 'Custom event' ? 'custom-event' : null"
+ :class="isEditable(event) ? 'custom-event' : null"
color="blue"
>
+