Skip to content

Commit

Permalink
Timeline enhancements (#3795)
Browse files Browse the repository at this point in the history
  • Loading branch information
whitdog47 authored Oct 6, 2023
1 parent f6a579f commit 319b2fa
Show file tree
Hide file tree
Showing 25 changed files with 1,156 additions and 35 deletions.
33 changes: 33 additions & 0 deletions src/dispatch/auth/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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(
Expand All @@ -331,6 +334,36 @@ def has_required_permissions(
if current_incident.commander.individual.email == current_user.email:
return True

return False


class IncidentCommanderOrScribePermission(BasePermission):
def has_required_permissions(
self,
request: Request,
) -> bool:
current_user = get_current_user(request=request)
pk = PrimaryKeyModel(id=request.path_params["incident_id"])
current_incident = incident_service.get(db_session=request.state.db, incident_id=pk.id)
if not current_incident:
return False

if (
current_incident.commander
and current_incident.commander.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):
def has_required_permissions(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Adds type to events
Revision ID: 3538650dc471
Revises: e875e9544048
Create Date: 2023-09-12 13:43:42.539336
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "3538650dc471"
down_revision = "e875e9544048"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("event", sa.Column("type", sa.String(), nullable=True))
op.add_column("event", sa.Column("owner", sa.String(), nullable=True))
op.add_column("event", sa.Column("pinned", sa.Boolean(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("event", "type")
op.drop_column("event", "pinned")
op.drop_column("event", "owner")
# ### end Alembic commands ###
9 changes: 9 additions & 0 deletions src/dispatch/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ class DocumentResourceTemplateTypes(DispatchEnum):
incident = "dispatch-incident-document-template"
review = "dispatch-incident-review-document-template"
tracking = "dispatch-incident-sheet-template"


class EventType(DispatchEnum):
other = "Other" # default and catch-all (x resource created/updated, etc.)
field_updated = "Field updated" # for fields like title, description, tags, type, etc.
assessment_updated = "Assessment updated" # for priority, status, or severity changes
participant_updated = "Participant updated" # for added/removed users and role changes
imported_message = "Imported message" # for stopwatch-reacted messages from Slack
custom_event = "Custom event" # for user-added events (new feature)
55 changes: 55 additions & 0 deletions src/dispatch/event/flows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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_in.owner = 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,
)
18 changes: 17 additions & 1 deletion src/dispatch/event/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

from typing import Optional

from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Boolean
from sqlalchemy.dialects.postgresql import UUID as SQLAlchemyUUID
from sqlalchemy_utils import TSVectorType, JSONType

from dispatch.database.core import Base
from dispatch.models import DispatchBase, TimeStampMixin
from dispatch.enums import EventType


# SQLAlchemy Model
Expand All @@ -21,6 +22,9 @@ class Event(Base, TimeStampMixin):
source = Column(String, nullable=False)
description = Column(String, nullable=False)
details = Column(JSONType, nullable=True)
type = Column(String, default=EventType.other, nullable=True)
owner = Column(String, nullable=True)
pinned = Column(Boolean, default=False)

# relationships
individual_id = Column(Integer, ForeignKey("individual_contact.id", ondelete="CASCADE"))
Expand All @@ -45,6 +49,9 @@ class EventBase(DispatchBase):
source: str
description: str
details: Optional[dict]
type: Optional[str]
owner: Optional[str]
pinned: Optional[bool]


class EventCreate(EventBase):
Expand All @@ -57,3 +64,12 @@ class EventUpdate(EventBase):

class EventRead(EventBase):
pass


class EventCreateMinimal(DispatchBase):
started_at: datetime
source: str
description: str
details: dict
type: Optional[str]
owner: Optional[str]
44 changes: 39 additions & 5 deletions src/dispatch/event/service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional
from typing import Optional
from uuid import uuid4
import datetime
import logging
Expand All @@ -7,29 +7,35 @@
from dispatch.case import service as case_service
from dispatch.incident import service as incident_service
from dispatch.individual import service as individual_service
from dispatch.enums import EventType

from .models import Event, EventCreate, EventUpdate


logger = logging.getLogger(__name__)
log = logging.getLogger(__name__)


def get(*, db_session, event_id: int) -> Optional[Event]:
"""Get an event by id."""
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_all(*, db_session) -> 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[Event | None]:
"""Get all events."""
return db_session.query(Event)

Expand Down Expand Up @@ -71,6 +77,9 @@ def log_incident_event(
started_at: datetime = None,
ended_at: datetime = None,
details: dict = None,
type: str = EventType.other,
owner: str = "",
pinned: bool = False,
) -> Event:
"""Logs an event in the incident timeline."""
uuid = uuid4()
Expand All @@ -88,6 +97,9 @@ def log_incident_event(
source=source,
description=description,
details=details,
type=type,
owner=owner,
pinned=pinned,
)
event = create(db_session=db_session, event_in=event_in)

Expand Down Expand Up @@ -133,6 +145,7 @@ def log_case_event(
source=source,
description=description,
details=details,
type=EventType.other,
)
event = create(db_session=db_session, event_in=event_in)

Expand All @@ -148,3 +161,24 @@ def log_case_event(
db_session.commit()

return event


def update_incident_event(
db_session,
event_in: EventUpdate,
) -> Event:
"""Updates an event in the incident timeline."""
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


def delete_incident_event(
db_session,
uuid: str,
):
"""Deletes an event."""
event = get_by_uuid(db_session=db_session, uuid=uuid)

delete(db_session=db_session, event_id=event.id)
Loading

0 comments on commit 319b2fa

Please sign in to comment.