Skip to content

Commit

Permalink
Merge branch 'master' into enhancement/add-custom-incident-forms
Browse files Browse the repository at this point in the history
  • Loading branch information
whitdog47 authored Dec 20, 2023
2 parents 645073f + f533060 commit 4a84bcc
Show file tree
Hide file tree
Showing 19 changed files with 838 additions and 165 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
],
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": false
"source.organizeImports": "never"
}
},
}
17 changes: 17 additions & 0 deletions src/dispatch/event/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def log_incident_event(
event_service.log_incident_event(
db_session=db_session,
incident_id=incident_id,
individual_id=individual.id,
owner=individual.name,
**event_in.__dict__,
)

Expand All @@ -53,3 +55,18 @@ def delete_incident_event(
db_session=db_session,
uuid=event_uuid,
)


@background_task
def export_timeline(
timeline_filters: dict,
incident_id: int,
db_session=None,
organization_slug: str = None,
):
status = event_service.export_timeline(
db_session=db_session,
timeline_filters=timeline_filters,
incident_id=incident_id,
)
return status
243 changes: 241 additions & 2 deletions src/dispatch/event/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from uuid import uuid4
import datetime
import logging
import json
import pytz

from dispatch.auth import service as auth_service
from dispatch.case import service as case_service
Expand All @@ -10,14 +12,20 @@
from dispatch.enums import EventType

from .models import Event, EventCreate, EventUpdate

from dispatch.document import service as document_service
from dispatch.plugin import service as plugin_service

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()
return (
db_session.query(Event)
.filter(Event.id == event_id)
.order_by(Event.started_at)
.one_or_none()
)


def get_by_case_id(*, db_session, case_id: int) -> list[Event | None]:
Expand All @@ -27,6 +35,7 @@ def get_by_case_id(*, db_session, case_id: int) -> list[Event | None]:

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)


Expand Down Expand Up @@ -182,3 +191,233 @@ def delete_incident_event(
event = get_by_uuid(db_session=db_session, uuid=uuid)

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



def export_timeline(
db_session,
timeline_filters: str,
incident_id: int,
):
incident = incident_service.get(db_session=db_session, incident_id=incident_id)
plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=incident.project_id, plugin_type="document"
)
if not plugin:
log.error("Document not created. No storage plugin enabled.")
return False

"""gets timeline events for incident"""
event = get_by_incident_id(db_session=db_session, incident_id=incident_id)
table_data = []
dates = set()
data_inserted = False

"""Filters events based on user filter"""
for e in event.all():
time_header = "Time (UTC)"
event_timestamp = e.started_at.strftime("%Y-%m-%d %H:%M:%S")
if not e.owner:
e.owner = "Dispatch"
if timeline_filters.get("timezone").strip() == "America/Los_Angeles":
time_header = "Time (PST/PDT)"
event_timestamp = (
pytz.utc.localize(e.started_at)
.astimezone(pytz.timezone(timeline_filters.get("timezone").strip()))
.replace(tzinfo=None)
)
date, time = str(event_timestamp).split(" ")
if e.pinned or timeline_filters.get(e.type):
if date in dates:
table_data.append(
{time_header: time, "Description": e.description, "Owner": e.owner}
)
else:
dates.add(date)
table_data.append({time_header: date, "Description": "\t", "owner": "\t"})
table_data.append(
{time_header: time, "Description": e.description, "Owner": e.owner}
)

if table_data:
table_data = json.loads(json.dumps(table_data))
num_columns = len(table_data[0].keys() if table_data else [])
column_headers = table_data[0].keys()

documents_list = []
if timeline_filters.get("incidentDocument"):
documents = document_service.get_by_incident_id_and_resource_type(
db_session=db_session,
incident_id=incident_id,
project_id=incident.project.id,
resource_type="dispatch-incident-document",
)
if documents:
documents_list.append(documents.resource_id)

if timeline_filters.get("reviewDocument"):
documents = document_service.get_by_incident_id_and_resource_type(
db_session=db_session,
incident_id=incident_id,
project_id=incident.project.id,
resource_type="dispatch-incident-review-document",
)
if documents:
documents_list.append(documents.resource_id)

for doc_id in documents_list:
# Checks for existing table in the document
table_exists, curr_table_start, curr_table_end, _ = plugin.instance.get_table_details(
document_id=doc_id, header="Timeline"
)

# Deletes existing table
if table_exists:
delete_table_request = [
{
"deleteContentRange": {
"range": {
"segmentId": "",
"startIndex": curr_table_start,
"endIndex": curr_table_end,
}
}
}
]
if plugin.instance.delete_table(document_id=doc_id, request=delete_table_request):
log.debug("Existing table in the doc has been deleted")

else:
curr_table_start += 1
# Insert new table with required rows & columns
insert_table_request = [
{
"insertTable": {
"rows": len(table_data) + 1,
"columns": num_columns,
"location": {"index": curr_table_start - 1},
}
}
]
if plugin.instance.insert(document_id=doc_id, request=insert_table_request):
log.debug("Table skeleton inserted successfully")

else:
return False

# Formatting & inserting empty table
insert_data_request = [
{
"updateTableCellStyle": {
"tableCellStyle": {
"backgroundColor": {
"color": {"rgbColor": {"green": 0.4, "red": 0.4, "blue": 0.4}}
}
},
"fields": "backgroundColor",
"tableRange": {
"columnSpan": 3,
"rowSpan": 1,
"tableCellLocation": {
"columnIndex": 0,
"rowIndex": 0,
"tableStartLocation": {"index": curr_table_start},
},
},
}
}
]

if plugin.instance.insert(document_id=doc_id, request=insert_data_request):
log.debug("Table Formatted successfully")

else:
return False

# Calculating table cell indices
_, _, _, cell_indices = plugin.instance.get_table_details(
document_id=doc_id, header="Timeline"
)

data_to_insert = list(column_headers) + [
item for row in table_data for item in row.values()
]
str_len = 0
row_idx = 0
insert_data_request = []
for index, text in zip(cell_indices, data_to_insert, strict=True):
# Adjusting index based on string length
new_idx = index + str_len

insert_data_request.append(
{"insertText": {"location": {"index": new_idx}, "text": text}}
)

# Header field formatting
if text in column_headers:
insert_data_request.append(
{
"updateTextStyle": {
"range": {"startIndex": new_idx, "endIndex": new_idx + len(text)},
"textStyle": {
"bold": True,
"foregroundColor": {
"color": {"rgbColor": {"red": 1, "green": 1, "blue": 1}}
},
"fontSize": {"magnitude": 10, "unit": "PT"},
},
"fields": "bold,foregroundColor",
}
}
)

# Formating for date rows
if text == "\t":
insert_data_request.append(
{
"updateTableCellStyle": {
"tableCellStyle": {
"backgroundColor": {
"color": {
"rgbColor": {"green": 0.8, "red": 0.8, "blue": 0.8}
}
}
},
"fields": "backgroundColor",
"tableRange": {
"columnSpan": 3,
"rowSpan": 1,
"tableCellLocation": {
"tableStartLocation": {"index": curr_table_start},
"columnIndex": 0,
"rowIndex": row_idx // 3,
},
},
}
}
)

# Formating for time column
if row_idx % num_columns == 0:
insert_data_request.append(
{
"updateTextStyle": {
"range": {"startIndex": new_idx, "endIndex": new_idx + len(text)},
"textStyle": {
"bold": True,
},
"fields": "bold",
}
}
)

row_idx += 1
str_len += len(text) if text else 0

data_inserted = plugin.instance.insert(document_id=doc_id, request=insert_data_request)
if not data_inserted:
return False
else:
log.error("No timeline data to export")
return False
return True
9 changes: 0 additions & 9 deletions src/dispatch/incident/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,15 +612,6 @@ def status_flow_dispatcher(
elif previous_status == IncidentStatus.stable:
incident_closed_status_flow(incident=incident, db_session=db_session)

if previous_status != current_status:
event_service.log_incident_event(
db_session=db_session,
source="Dispatch Core App",
description=f"The incident status has been changed from {previous_status.lower()} to {current_status.lower()}", # noqa
incident_id=incident.id,
type=EventType.assessment_updated,
)


@background_task
def incident_update_flow(
Expand Down
9 changes: 7 additions & 2 deletions src/dispatch/incident/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def create(*, db_session, incident_in: IncidentCreate) -> Incident:
"status": incident.status,
"visibility": incident.visibility,
},
individual_id=incident_in.reporter.individual.id,
incident_id=incident.id,
owner=reporter_name,
pinned=True,
Expand Down Expand Up @@ -279,15 +280,19 @@ def create(*, db_session, incident_in: IncidentCreate) -> Incident:
)

# add observer (if engage_next_oncall is enabled)
incident_role = resolve_role(db_session=db_session, role=ParticipantRoleType.incident_commander, incident=incident)
incident_role = resolve_role(
db_session=db_session, role=ParticipantRoleType.incident_commander, incident=incident
)
if incident_role and incident_role.engage_next_oncall:
oncall_plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=incident.project.id, plugin_type="oncall"
)
if not oncall_plugin:
log.debug("Resolved observer role not available since oncall plugin is not active.")
else:
oncall_email = oncall_plugin.instance.get_next_oncall(service_id=incident_role.service.external_id)
oncall_email = oncall_plugin.instance.get_next_oncall(
service_id=incident_role.service.external_id
)
if oncall_email:
participant_flows.add_participant(
oncall_email,
Expand Down
23 changes: 23 additions & 0 deletions src/dispatch/incident/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,29 @@ def update_custom_event(
)


@router.post(
"/{incident_id}/exportTimeline",
summary="Exports timeline events.",
dependencies=[Depends(PermissionsDependency([IncidentCommanderOrScribePermission]))],
)
def export_timeline_event(
db_session: DbSession,
organization: OrganizationSlug,
incident_id: PrimaryKey,
current_incident: CurrentIncident,
timeline_filters: dict,
current_user: CurrentUser,
background_tasks: BackgroundTasks,
):
result = background_tasks.add_task(
event_flows.export_timeline,
timeline_filters=timeline_filters,
incident_id=incident_id,
organization_slug=organization,
)
return result


@router.delete(
"/{incident_id}/event/{event_uuid}",
summary="Deletes a custom event.",
Expand Down
Loading

0 comments on commit 4a84bcc

Please sign in to comment.