From ac4fd19e9e94f7e0dc4225bcf34326d901b0e203 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Wed, 21 Dec 2022 10:34:12 -0800 Subject: [PATCH 1/9] Tweaking model loading --- src/dispatch/database/core.py | 33 +++++++++++++++---- src/dispatch/incident/models.py | 40 +++++++++++++++++++++--- src/dispatch/incident/priority/models.py | 13 ++++++++ src/dispatch/incident/severity/models.py | 9 ++++++ src/dispatch/incident/type/models.py | 10 ++++++ src/dispatch/individual/models.py | 9 ++++++ src/dispatch/participant/models.py | 9 +++++- src/dispatch/participant_role/models.py | 4 +++ src/dispatch/tag/models.py | 9 ++++++ src/dispatch/tag_type/models.py | 7 +++++ 10 files changed, 131 insertions(+), 12 deletions(-) diff --git a/src/dispatch/database/core.py b/src/dispatch/database/core.py index a1a01e54ac85..8d2cbd559b89 100644 --- a/src/dispatch/database/core.py +++ b/src/dispatch/database/core.py @@ -1,12 +1,15 @@ -import re import functools +import re from typing import Any -from pydantic.error_wrappers import ErrorWrapper, ValidationError -from pydantic import BaseModel +import time +import logging -from sqlalchemy import create_engine, inspect +from pydantic import BaseModel +from pydantic.error_wrappers import ErrorWrapper, ValidationError +from sqlalchemy import create_engine, event, inspect +from sqlalchemy.engine import Engine from sqlalchemy.ext.declarative import declarative_base, declared_attr -from sqlalchemy.orm import sessionmaker, object_session +from sqlalchemy.orm import object_session, sessionmaker from sqlalchemy.sql.expression import true from sqlalchemy_utils import get_mapper from starlette.requests import Request @@ -15,12 +18,30 @@ from dispatch.exceptions import NotFoundError from dispatch.search.fulltext import make_searchable - engine = create_engine( config.SQLALCHEMY_DATABASE_URI, pool_size=config.DATABASE_ENGINE_POOL_SIZE, max_overflow=config.DATABASE_ENGINE_MAX_OVERFLOW, ) + +logging.basicConfig() +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +@event.listens_for(Engine, "before_cursor_execute") +def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): + conn.info.setdefault("query_start_time", []).append(time.time()) + logger.debug("Start Query: %s", statement) + + +@event.listens_for(Engine, "after_cursor_execute") +def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): + total = time.time() - conn.info["query_start_time"].pop(-1) + logger.debug("Query Complete!") + logger.debug("Total Time: %f", total) + + SessionLocal = sessionmaker(bind=engine) diff --git a/src/dispatch/incident/models.py b/src/dispatch/incident/models.py index 0669ef9015a5..f5021f7503a1 100644 --- a/src/dispatch/incident/models.py +++ b/src/dispatch/incident/models.py @@ -1,6 +1,6 @@ from datetime import datetime from collections import Counter, defaultdict -from typing import List, Optional +from typing import List, Optional, Any from pydantic import validator from sqlalchemy import ( @@ -28,23 +28,30 @@ IncidentPriorityBase, IncidentPriorityCreate, IncidentPriorityRead, + IncidentPriorityReadMinimal, ) from dispatch.incident.severity.models import ( IncidentSeverityCreate, IncidentSeverityRead, + IncidentSeverityReadMinimal, IncidentSeverityBase, ) -from dispatch.incident.type.models import IncidentTypeCreate, IncidentTypeRead, IncidentTypeBase +from dispatch.incident.type.models import ( + IncidentTypeCreate, + IncidentTypeRead, + IncidentTypeReadMinimal, + IncidentTypeBase, +) from dispatch.incident_cost.models import IncidentCostRead, IncidentCostUpdate from dispatch.messaging.strings import INCIDENT_RESOLUTION_DEFAULT from dispatch.models import DispatchBase, ProjectMixin, TimeStampMixin from dispatch.models import NameStr, PrimaryKey from dispatch.participant.models import Participant -from dispatch.participant.models import ParticipantRead, ParticipantUpdate +from dispatch.participant.models import ParticipantRead, ParticipantReadMinimal, ParticipantUpdate from dispatch.report.enums import ReportTypes from dispatch.report.models import ReportRead from dispatch.storage.models import StorageRead -from dispatch.tag.models import TagRead +from dispatch.tag.models import TagRead, TagReadMinimal from dispatch.term.models import TermRead from dispatch.ticket.models import TicketRead from dispatch.workflow.models import WorkflowInstanceRead @@ -341,8 +348,31 @@ class IncidentRead(IncidentReadMinimal): workflow_instances: Optional[List[WorkflowInstanceRead]] = [] +class IncidentReadBulk(DispatchBase): + id: PrimaryKey + closed_at: Optional[datetime] = None + commander: Optional[ParticipantReadMinimal] + commanders_location: Optional[str] + created_at: Optional[datetime] = None + incident_costs: Optional[List[IncidentCostRead]] = [] + incident_priority: IncidentPriorityReadMinimal + incident_severity: IncidentSeverityReadMinimal + incident_type: IncidentTypeReadMinimal + name: Optional[NameStr] + participants_location: Optional[str] + participants_team: Optional[str] + project: ProjectRead + reported_at: Optional[datetime] = None + reporter: Optional[ParticipantReadMinimal] + reporters_location: Optional[str] + stable_at: Optional[datetime] = None + tags: Optional[List[TagReadMinimal]] = [] + total_cost: Optional[float] + duplicates: Optional[List[Any]] = [] + + class IncidentPagination(DispatchBase): total: int itemsPerPage: int page: int - items: List[IncidentRead] = [] + items: List[IncidentReadBulk] = [] diff --git a/src/dispatch/incident/priority/models.py b/src/dispatch/incident/priority/models.py index a27923488db3..e310c9bf11c3 100644 --- a/src/dispatch/incident/priority/models.py +++ b/src/dispatch/incident/priority/models.py @@ -62,6 +62,19 @@ class IncidentPriorityRead(IncidentPriorityBase): id: PrimaryKey +class IncidentPriorityReadMinimal(DispatchBase): + id: PrimaryKey + name: NameStr + description: Optional[str] = Field(None, nullable=True) + page_commander: Optional[StrictBool] + tactical_report_reminder: Optional[int] + executive_report_reminder: Optional[int] + default: Optional[bool] + enabled: Optional[bool] + view_order: Optional[int] + color: Optional[Color] = Field(None, nullable=True) + + class IncidentPriorityPagination(DispatchBase): total: int items: List[IncidentPriorityRead] = [] diff --git a/src/dispatch/incident/severity/models.py b/src/dispatch/incident/severity/models.py index fc1a114cb795..b162ea71a323 100644 --- a/src/dispatch/incident/severity/models.py +++ b/src/dispatch/incident/severity/models.py @@ -60,6 +60,15 @@ class IncidentSeverityRead(IncidentSeverityBase): id: PrimaryKey +class IncidentSeverityReadMinimal(DispatchBase): + id: PrimaryKey + color: Optional[Color] = Field(None, nullable=True) + default: Optional[bool] + description: Optional[str] = Field(None, nullable=True) + enabled: Optional[bool] + name: NameStr + + class IncidentSeverityPagination(DispatchBase): total: int items: List[IncidentSeverityRead] = [] diff --git a/src/dispatch/incident/type/models.py b/src/dispatch/incident/type/models.py index 05c1372cf218..51bc5457cf7b 100644 --- a/src/dispatch/incident/type/models.py +++ b/src/dispatch/incident/type/models.py @@ -111,6 +111,16 @@ class IncidentTypeRead(IncidentTypeBase): id: PrimaryKey +class IncidentTypeReadMinimal(DispatchBase): + id: PrimaryKey + name: NameStr + visibility: Optional[str] = Field(None, nullable=True) + description: Optional[str] = Field(None, nullable=True) + enabled: Optional[bool] + exclude_from_metrics: Optional[bool] = False + default: Optional[bool] = False + + class IncidentTypePagination(DispatchBase): total: int items: List[IncidentTypeRead] = [] diff --git a/src/dispatch/individual/models.py b/src/dispatch/individual/models.py index 9dc05b203d7f..d35b7b17efce 100644 --- a/src/dispatch/individual/models.py +++ b/src/dispatch/individual/models.py @@ -77,6 +77,15 @@ class IndividualContactRead(IndividualContactBase): updated_at: Optional[datetime] = None +class IndividualContactReadMinimal(DispatchBase): + id: Optional[PrimaryKey] + weblink: Optional[str] = Field(None, nullable=True) + mobile_phone: Optional[str] = Field(None, nullable=True) + office_phone: Optional[str] = Field(None, nullable=True) + title: Optional[str] = Field(None, nullable=True) + external_id: Optional[str] = Field(None, nullable=True) + + class IndividualContactPagination(DispatchBase): total: int items: List[IndividualContactRead] = [] diff --git a/src/dispatch/participant/models.py b/src/dispatch/participant/models.py index a6a9332baec5..8cbce66301d8 100644 --- a/src/dispatch/participant/models.py +++ b/src/dispatch/participant/models.py @@ -10,10 +10,11 @@ from dispatch.participant_role.models import ( ParticipantRoleCreate, ParticipantRoleRead, + ParticipantRoleReadMinimal, ParticipantRole, ) from dispatch.service.models import ServiceRead -from dispatch.individual.models import IndividualContactRead +from dispatch.individual.models import IndividualContactRead, IndividualContactReadMinimal class Participant(Base): @@ -88,6 +89,12 @@ class ParticipantRead(ParticipantBase): individual: Optional[IndividualContactRead] +class ParticipantReadMinimal(DispatchBase): + id: PrimaryKey + participant_roles: Optional[List[ParticipantRoleReadMinimal]] = [] + individual: Optional[IndividualContactReadMinimal] + + class ParticipantPagination(DispatchBase): total: int items: List[ParticipantRead] = [] diff --git a/src/dispatch/participant_role/models.py b/src/dispatch/participant_role/models.py index 6145cc9289fa..dcef84774f7e 100644 --- a/src/dispatch/participant_role/models.py +++ b/src/dispatch/participant_role/models.py @@ -39,6 +39,10 @@ class ParticipantRoleRead(ParticipantRoleBase): activity: Optional[int] +class ParticipantRoleReadMinimal(ParticipantRoleRead): + pass + + class ParticipantRolePagination(ParticipantRoleBase): total: int items: List[ParticipantRoleRead] = [] diff --git a/src/dispatch/tag/models.py b/src/dispatch/tag/models.py index 2ee97dad2453..de249e75fd51 100644 --- a/src/dispatch/tag/models.py +++ b/src/dispatch/tag/models.py @@ -57,6 +57,15 @@ class TagRead(TagBase): project: ProjectRead +class TagReadMinimal(DispatchBase): + id: PrimaryKey + name: Optional[str] = Field(None, nullable=True) + source: Optional[str] = Field(None, nullable=True) + uri: Optional[str] = Field(None, nullable=True) + discoverable: Optional[bool] = True + description: Optional[str] = Field(None, nullable=True) + + class TagPagination(DispatchBase): items: List[TagRead] total: int diff --git a/src/dispatch/tag_type/models.py b/src/dispatch/tag_type/models.py index 7a000184bd8e..65def62d376f 100644 --- a/src/dispatch/tag_type/models.py +++ b/src/dispatch/tag_type/models.py @@ -40,6 +40,13 @@ class TagTypeRead(TagTypeBase): project: ProjectRead +class TagTypeReadMinimal(DispatchBase): + id: PrimaryKey + name: NameStr + exclusive: Optional[bool] = False + description: Optional[str] = Field(None, nullable=True) + + class TagTypePagination(DispatchBase): items: List[TagTypeRead] total: int From af956b760d475d01be6f2a3c8947caebbc77a91f Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Wed, 21 Dec 2022 10:37:06 -0800 Subject: [PATCH 2/9] lazy --- src/dispatch/incident/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dispatch/incident/models.py b/src/dispatch/incident/models.py index f5021f7503a1..ad87c2f6a802 100644 --- a/src/dispatch/incident/models.py +++ b/src/dispatch/incident/models.py @@ -164,6 +164,7 @@ def last_executive_report(self): "Participant", backref="incident", cascade="all, delete-orphan", + lazy="joined", foreign_keys=[Participant.incident_id], ) reports = relationship("Report", backref="incident", cascade="all, delete-orphan") @@ -173,6 +174,7 @@ def last_executive_report(self): tags = relationship( "Tag", secondary=assoc_incident_tags, + lazy="joined", backref="incidents", ) tasks = relationship("Task", backref="incident", cascade="all, delete-orphan") From 1081467dc9d49a5b0608f0accb67b50d7d28e952 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Wed, 21 Dec 2022 15:39:04 -0800 Subject: [PATCH 3/9] Reduces the number of queries being made and result returned during bulk incident querying --- src/dispatch/case/models.py | 8 +- src/dispatch/conference/models.py | 4 - src/dispatch/conversation/models.py | 4 - src/dispatch/data/source/models.py | 4 +- src/dispatch/database/core.py | 26 +++--- src/dispatch/document/models.py | 4 - src/dispatch/event/models.py | 4 - src/dispatch/feedback/models.py | 4 +- src/dispatch/group/models.py | 4 - src/dispatch/incident/models.py | 105 ++++++++-------------- src/dispatch/incident/priority/models.py | 2 +- src/dispatch/incident/severity/models.py | 2 +- src/dispatch/incident/type/models.py | 2 +- src/dispatch/incident_cost/models.py | 4 - src/dispatch/incident_cost_type/models.py | 4 - src/dispatch/individual/models.py | 11 +-- src/dispatch/participant_role/models.py | 2 +- src/dispatch/service/models.py | 4 - src/dispatch/storage/models.py | 4 - src/dispatch/tag/models.py | 5 +- src/dispatch/tag_type/models.py | 2 +- src/dispatch/task/models.py | 4 +- src/dispatch/ticket/models.py | 4 - src/dispatch/workflow/models.py | 4 - 24 files changed, 72 insertions(+), 149 deletions(-) diff --git a/src/dispatch/case/models.py b/src/dispatch/case/models.py index 1f46b8ec3c3a..fa677b2c9d3c 100644 --- a/src/dispatch/case/models.py +++ b/src/dispatch/case/models.py @@ -183,7 +183,7 @@ class CaseCreate(CaseBase): tags: Optional[List[TagRead]] = [] -class CaseReadNested(CaseBase): +class CaseReadReadMinimal(CaseBase): id: PrimaryKey assignee: Optional[UserRead] case_priority: CasePriorityRead @@ -208,14 +208,14 @@ class CaseRead(CaseBase): closed_at: Optional[datetime] = None created_at: Optional[datetime] = None documents: Optional[List[DocumentRead]] = [] - duplicates: Optional[List[CaseReadNested]] = [] + duplicates: Optional[List[CaseReadReadMinimal]] = [] escalated_at: Optional[datetime] = None events: Optional[List[EventRead]] = [] groups: Optional[List[GroupRead]] = [] incidents: Optional[List[IncidentRead]] = [] name: Optional[NameStr] project: ProjectRead - related: Optional[List[CaseReadNested]] = [] + related: Optional[List[CaseReadReadMinimal]] = [] reported_at: Optional[datetime] = None storage: Optional[StorageRead] = None tags: Optional[List[TagRead]] = [] @@ -255,7 +255,7 @@ def find_exclusive(cls, v): class CasePagination(DispatchBase): - items: List[CaseRead] = [] + items: List[CaseReadReadMinimal] = [] itemsPerPage: int page: int total: int diff --git a/src/dispatch/conference/models.py b/src/dispatch/conference/models.py index f4e4ac6a009e..ed86fa016930 100644 --- a/src/dispatch/conference/models.py +++ b/src/dispatch/conference/models.py @@ -39,7 +39,3 @@ def set_description(cls, v, values): return Template(INCIDENT_CONFERENCE_DESCRIPTION).render( conference_challenge=values["conference_challenge"] ) - - -class ConferenceNested(ConferenceBase): - pass diff --git a/src/dispatch/conversation/models.py b/src/dispatch/conversation/models.py index 4c59fdbd4e12..48a9b4087eef 100644 --- a/src/dispatch/conversation/models.py +++ b/src/dispatch/conversation/models.py @@ -35,7 +35,3 @@ class ConversationRead(ConversationBase): def set_description(cls, v): """Sets the description""" return INCIDENT_CONVERSATION_DESCRIPTION - - -class ConversationNested(ConversationBase): - pass diff --git a/src/dispatch/data/source/models.py b/src/dispatch/data/source/models.py index df694a7c109b..d21ef9b61853 100644 --- a/src/dispatch/data/source/models.py +++ b/src/dispatch/data/source/models.py @@ -90,7 +90,7 @@ class Source(Base, TimeStampMixin, ProjectMixin): search_vector = Column(TSVectorType("name", regconfig="pg_catalog.simple")) -class QueryReadNested(DispatchBase): +class QueryReadMinimal(DispatchBase): id: PrimaryKey name: str description: str @@ -119,7 +119,7 @@ class SourceBase(DispatchBase): links: Optional[List] = [] tags: Optional[List[TagRead]] = [] incidents: Optional[List[IncidentRead]] = [] - queries: Optional[List[QueryReadNested]] = [] + queries: Optional[List[QueryReadMinimal]] = [] alerts: Optional[List[AlertRead]] = [] cost: Optional[float] owner: Optional[ServiceRead] = Field(None, nullable=True) diff --git a/src/dispatch/database/core.py b/src/dispatch/database/core.py index 8d2cbd559b89..8f130751d8cd 100644 --- a/src/dispatch/database/core.py +++ b/src/dispatch/database/core.py @@ -24,22 +24,24 @@ max_overflow=config.DATABASE_ENGINE_MAX_OVERFLOW, ) -logging.basicConfig() -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +# Useful for identifying slow or n + 1 queries. But doesn't need to be enabled in production. +# logging.basicConfig() +# logger = logging.getLogger(__name__) +# logger.setLevel(logging.DEBUG) -@event.listens_for(Engine, "before_cursor_execute") -def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): - conn.info.setdefault("query_start_time", []).append(time.time()) - logger.debug("Start Query: %s", statement) +# @event.listens_for(Engine, "before_cursor_execute") +# def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): +# conn.info.setdefault("query_start_time", []).append(time.time()) +# logger.debug("Start Query: %s", statement) -@event.listens_for(Engine, "after_cursor_execute") -def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): - total = time.time() - conn.info["query_start_time"].pop(-1) - logger.debug("Query Complete!") - logger.debug("Total Time: %f", total) + +# @event.listens_for(Engine, "after_cursor_execute") +# def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): +# total = time.time() - conn.info["query_start_time"].pop(-1) +# logger.debug("Query Complete!") +# logger.debug("Total Time: %f", total) SessionLocal = sessionmaker(bind=engine) diff --git a/src/dispatch/document/models.py b/src/dispatch/document/models.py index 370c524169fd..accb68dbbeb5 100644 --- a/src/dispatch/document/models.py +++ b/src/dispatch/document/models.py @@ -73,10 +73,6 @@ def set_description(cls, v, values): return v -class DocumentNested(DocumentRead): - pass - - class DocumentPagination(DispatchBase): total: int items: List[DocumentRead] = [] diff --git a/src/dispatch/event/models.py b/src/dispatch/event/models.py index af855f54f8df..e6dea33e78d5 100644 --- a/src/dispatch/event/models.py +++ b/src/dispatch/event/models.py @@ -58,7 +58,3 @@ class EventUpdate(EventBase): class EventRead(EventBase): pass - - -class EventNested(EventBase): - id: PrimaryKey diff --git a/src/dispatch/feedback/models.py b/src/dispatch/feedback/models.py index 4a49eccec1e2..52949913d080 100644 --- a/src/dispatch/feedback/models.py +++ b/src/dispatch/feedback/models.py @@ -8,7 +8,7 @@ from dispatch.database.core import Base from dispatch.feedback.enums import FeedbackRating -from dispatch.incident.models import IncidentReadNested +from dispatch.incident.models import IncidentReadMinimal from dispatch.models import DispatchBase, TimeStampMixin, PrimaryKey from dispatch.participant.models import ParticipantRead from dispatch.project.models import ProjectRead @@ -42,7 +42,7 @@ class FeedbackBase(DispatchBase): created_at: Optional[datetime] rating: FeedbackRating = FeedbackRating.very_satisfied feedback: Optional[str] = Field(None, nullable=True) - incident: Optional[IncidentReadNested] + incident: Optional[IncidentReadMinimal] participant: Optional[ParticipantRead] diff --git a/src/dispatch/group/models.py b/src/dispatch/group/models.py index 598909bcd129..1636c9212d66 100644 --- a/src/dispatch/group/models.py +++ b/src/dispatch/group/models.py @@ -41,7 +41,3 @@ class GroupRead(GroupBase): def set_description(cls, v): """Sets the description""" return TACTICAL_GROUP_DESCRIPTION - - -class GroupNested(GroupBase): - id: PrimaryKey diff --git a/src/dispatch/incident/models.py b/src/dispatch/incident/models.py index ad87c2f6a802..c0c83fd4f5a1 100644 --- a/src/dispatch/incident/models.py +++ b/src/dispatch/incident/models.py @@ -131,6 +131,7 @@ def last_executive_report(self): "IncidentCost", backref="incident", cascade="all, delete-orphan", + lazy="subquery", order_by="IncidentCost.created_at", ) @@ -164,7 +165,6 @@ def last_executive_report(self): "Participant", backref="incident", cascade="all, delete-orphan", - lazy="joined", foreign_keys=[Participant.incident_id], ) reports = relationship("Report", backref="incident", cascade="all, delete-orphan") @@ -174,7 +174,7 @@ def last_executive_report(self): tags = relationship( "Tag", secondary=assoc_incident_tags, - lazy="joined", + lazy="subquery", backref="incidents", ) tasks = relationship("Task", backref="incident", cascade="all, delete-orphan") @@ -185,13 +185,19 @@ def last_executive_report(self): ) duplicate_id = Column(Integer, ForeignKey("incident.id")) - duplicates = relationship("Incident", remote_side=[id], uselist=True) + duplicates = relationship( + "Incident", remote_side=[id], lazy="joined", join_depth=2, uselist=True + ) commander_id = Column(Integer, ForeignKey("participant.id")) - commander = relationship("Participant", foreign_keys=[commander_id], post_update=True) + commander = relationship( + "Participant", foreign_keys=[commander_id], lazy="subquery", post_update=True + ) reporter_id = Column(Integer, ForeignKey("participant.id")) - reporter = relationship("Participant", foreign_keys=[reporter_id], post_update=True) + reporter = relationship( + "Participant", foreign_keys=[reporter_id], lazy="subquery", post_update=True + ) liaison_id = Column(Integer, ForeignKey("participant.id")) liaison = relationship("Participant", foreign_keys=[liaison_id], post_update=True) @@ -257,21 +263,6 @@ def description_required(cls, v): return v -class IncidentReadNested(IncidentBase): - id: PrimaryKey - closed_at: Optional[datetime] = None - commander: Optional[ParticipantRead] - created_at: Optional[datetime] = None - incident_priority: IncidentPriorityRead - incident_severity: IncidentSeverityRead - incident_type: IncidentTypeRead - name: Optional[NameStr] - project: ProjectRead - reported_at: Optional[datetime] = None - reporter: Optional[ParticipantRead] - stable_at: Optional[datetime] = None - - class IncidentCreate(IncidentBase): commander: Optional[ParticipantUpdate] incident_priority: Optional[IncidentPriorityCreate] @@ -282,10 +273,33 @@ class IncidentCreate(IncidentBase): tags: Optional[List[TagRead]] = [] +class IncidentReadMinimal(IncidentBase): + id: PrimaryKey + closed_at: Optional[datetime] = None + commander: ParticipantReadMinimal + commanders_location: Optional[str] + created_at: Optional[datetime] = None + incident_priority: IncidentPriorityReadMinimal + incident_severity: IncidentSeverityReadMinimal + incident_type: IncidentTypeReadMinimal + name: Optional[NameStr] + participants_location: Optional[str] + participants_team: Optional[str] + project: ProjectRead + reported_at: Optional[datetime] = None + reporter: ParticipantReadMinimal + reporters_location: Optional[str] + stable_at: Optional[datetime] = None + tags: Optional[List[TagReadMinimal]] = [] + total_cost: Optional[float] + duplicates: Optional[List[Any]] = [] + incident_costs: Optional[List[IncidentCostRead]] = [] + + class IncidentUpdate(IncidentBase): cases: Optional[List[CaseRead]] = [] commander: Optional[ParticipantUpdate] - duplicates: Optional[List[IncidentReadNested]] = [] + duplicates: Optional[List[IncidentReadMinimal]] = [] incident_costs: Optional[List[IncidentCostUpdate]] = [] incident_priority: IncidentPriorityBase incident_severity: IncidentSeverityBase @@ -312,30 +326,6 @@ def find_exclusive(cls, v): return v -class IncidentReadMinimal(IncidentBase): - id: PrimaryKey - cases: Optional[List[CaseRead]] - closed_at: Optional[datetime] = None - commander: Optional[ParticipantRead] - commanders_location: Optional[str] - created_at: Optional[datetime] = None - duplicates: Optional[List[IncidentReadNested]] = [] - incident_costs: Optional[List[IncidentCostRead]] = [] - incident_priority: IncidentPriorityRead - incident_severity: IncidentSeverityRead - incident_type: IncidentTypeRead - name: Optional[NameStr] - participants_location: Optional[str] - participants_team: Optional[str] - project: ProjectRead - reported_at: Optional[datetime] = None - reporter: Optional[ParticipantRead] - reporters_location: Optional[str] - stable_at: Optional[datetime] = None - tags: Optional[List[TagRead]] = [] - total_cost: Optional[float] - - class IncidentRead(IncidentReadMinimal): conference: Optional[ConferenceRead] = None conversation: Optional[ConversationRead] = None @@ -350,31 +340,8 @@ class IncidentRead(IncidentReadMinimal): workflow_instances: Optional[List[WorkflowInstanceRead]] = [] -class IncidentReadBulk(DispatchBase): - id: PrimaryKey - closed_at: Optional[datetime] = None - commander: Optional[ParticipantReadMinimal] - commanders_location: Optional[str] - created_at: Optional[datetime] = None - incident_costs: Optional[List[IncidentCostRead]] = [] - incident_priority: IncidentPriorityReadMinimal - incident_severity: IncidentSeverityReadMinimal - incident_type: IncidentTypeReadMinimal - name: Optional[NameStr] - participants_location: Optional[str] - participants_team: Optional[str] - project: ProjectRead - reported_at: Optional[datetime] = None - reporter: Optional[ParticipantReadMinimal] - reporters_location: Optional[str] - stable_at: Optional[datetime] = None - tags: Optional[List[TagReadMinimal]] = [] - total_cost: Optional[float] - duplicates: Optional[List[Any]] = [] - - class IncidentPagination(DispatchBase): total: int itemsPerPage: int page: int - items: List[IncidentReadBulk] = [] + items: List[IncidentReadMinimal] = [] diff --git a/src/dispatch/incident/priority/models.py b/src/dispatch/incident/priority/models.py index e310c9bf11c3..25de4f0e3517 100644 --- a/src/dispatch/incident/priority/models.py +++ b/src/dispatch/incident/priority/models.py @@ -77,4 +77,4 @@ class IncidentPriorityReadMinimal(DispatchBase): class IncidentPriorityPagination(DispatchBase): total: int - items: List[IncidentPriorityRead] = [] + items: List[IncidentPriorityReadMinimal] = [] diff --git a/src/dispatch/incident/severity/models.py b/src/dispatch/incident/severity/models.py index b162ea71a323..a2c76e5d79f4 100644 --- a/src/dispatch/incident/severity/models.py +++ b/src/dispatch/incident/severity/models.py @@ -71,4 +71,4 @@ class IncidentSeverityReadMinimal(DispatchBase): class IncidentSeverityPagination(DispatchBase): total: int - items: List[IncidentSeverityRead] = [] + items: List[IncidentSeverityReadMinimal] = [] diff --git a/src/dispatch/incident/type/models.py b/src/dispatch/incident/type/models.py index 51bc5457cf7b..e6b92667dabd 100644 --- a/src/dispatch/incident/type/models.py +++ b/src/dispatch/incident/type/models.py @@ -123,4 +123,4 @@ class IncidentTypeReadMinimal(DispatchBase): class IncidentTypePagination(DispatchBase): total: int - items: List[IncidentTypeRead] = [] + items: List[IncidentTypeReadMinimal] = [] diff --git a/src/dispatch/incident_cost/models.py b/src/dispatch/incident_cost/models.py index 70044742b843..867b29e611ae 100644 --- a/src/dispatch/incident_cost/models.py +++ b/src/dispatch/incident_cost/models.py @@ -46,10 +46,6 @@ class IncidentCostRead(IncidentCostBase): incident_cost_type: IncidentCostTypeRead -class IncidentCostNested(IncidentCostBase): - id: PrimaryKey - - class IncidentCostPagination(DispatchBase): total: int items: List[IncidentCostRead] = [] diff --git a/src/dispatch/incident_cost_type/models.py b/src/dispatch/incident_cost_type/models.py index 4778adb90b13..db4c3f7730b5 100644 --- a/src/dispatch/incident_cost_type/models.py +++ b/src/dispatch/incident_cost_type/models.py @@ -55,10 +55,6 @@ class IncidentCostTypeRead(IncidentCostTypeBase): id: PrimaryKey -class IncidentCostTypeNested(IncidentCostTypeBase): - id: PrimaryKey - - class IncidentCostTypePagination(DispatchBase): total: int items: List[IncidentCostTypeRead] = [] diff --git a/src/dispatch/individual/models.py b/src/dispatch/individual/models.py index d35b7b17efce..50948a8462e6 100644 --- a/src/dispatch/individual/models.py +++ b/src/dispatch/individual/models.py @@ -77,15 +77,12 @@ class IndividualContactRead(IndividualContactBase): updated_at: Optional[datetime] = None -class IndividualContactReadMinimal(DispatchBase): +class IndividualContactReadMinimal(IndividualContactBase): id: Optional[PrimaryKey] - weblink: Optional[str] = Field(None, nullable=True) - mobile_phone: Optional[str] = Field(None, nullable=True) - office_phone: Optional[str] = Field(None, nullable=True) - title: Optional[str] = Field(None, nullable=True) - external_id: Optional[str] = Field(None, nullable=True) + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None class IndividualContactPagination(DispatchBase): total: int - items: List[IndividualContactRead] = [] + items: List[IndividualContactReadMinimal] = [] diff --git a/src/dispatch/participant_role/models.py b/src/dispatch/participant_role/models.py index dcef84774f7e..ce100f27e4cc 100644 --- a/src/dispatch/participant_role/models.py +++ b/src/dispatch/participant_role/models.py @@ -45,4 +45,4 @@ class ParticipantRoleReadMinimal(ParticipantRoleRead): class ParticipantRolePagination(ParticipantRoleBase): total: int - items: List[ParticipantRoleRead] = [] + items: List[ParticipantRoleReadMinimal] = [] diff --git a/src/dispatch/service/models.py b/src/dispatch/service/models.py index 8251d69ecb0c..929005c02608 100644 --- a/src/dispatch/service/models.py +++ b/src/dispatch/service/models.py @@ -64,10 +64,6 @@ class ServiceRead(ServiceBase): updated_at: Optional[datetime] = None -class ServiceNested(ServiceBase): - id: PrimaryKey - - class ServicePagination(DispatchBase): total: int items: List[ServiceRead] = [] diff --git a/src/dispatch/storage/models.py b/src/dispatch/storage/models.py index 0bc1542c5789..de811487aef1 100644 --- a/src/dispatch/storage/models.py +++ b/src/dispatch/storage/models.py @@ -35,7 +35,3 @@ class StorageRead(StorageBase): def set_description(cls, v): """Sets the description""" return STORAGE_DESCRIPTION - - -class StorageNested(StorageBase): - pass diff --git a/src/dispatch/tag/models.py b/src/dispatch/tag/models.py index de249e75fd51..8148cab81e9f 100644 --- a/src/dispatch/tag/models.py +++ b/src/dispatch/tag/models.py @@ -9,7 +9,7 @@ from dispatch.database.core import Base from dispatch.models import DispatchBase, TimeStampMixin, ProjectMixin, PrimaryKey from dispatch.project.models import ProjectRead -from dispatch.tag_type.models import TagTypeRead, TagTypeCreate, TagTypeUpdate +from dispatch.tag_type.models import TagTypeRead, TagTypeCreate, TagTypeUpdate, TagTypeReadMinimal class Tag(Base, TimeStampMixin, ProjectMixin): @@ -64,8 +64,9 @@ class TagReadMinimal(DispatchBase): uri: Optional[str] = Field(None, nullable=True) discoverable: Optional[bool] = True description: Optional[str] = Field(None, nullable=True) + tag_type: Optional[TagTypeReadMinimal] class TagPagination(DispatchBase): - items: List[TagRead] + items: List[TagReadMinimal] total: int diff --git a/src/dispatch/tag_type/models.py b/src/dispatch/tag_type/models.py index 65def62d376f..3120bf615453 100644 --- a/src/dispatch/tag_type/models.py +++ b/src/dispatch/tag_type/models.py @@ -48,5 +48,5 @@ class TagTypeReadMinimal(DispatchBase): class TagTypePagination(DispatchBase): - items: List[TagTypeRead] + items: List[TagTypeReadMinimal] total: int diff --git a/src/dispatch/task/models.py b/src/dispatch/task/models.py index ecd63f5c478e..22b088f39352 100644 --- a/src/dispatch/task/models.py +++ b/src/dispatch/task/models.py @@ -18,7 +18,7 @@ from sqlalchemy_utils import TSVectorType from dispatch.database.core import Base -from dispatch.incident.models import IncidentReadNested +from dispatch.incident.models import IncidentReadMinimal from dispatch.models import DispatchBase, ResourceBase, ResourceMixin, PrimaryKey from dispatch.participant.models import ParticipantRead, ParticipantUpdate from dispatch.project.models import ProjectRead @@ -91,7 +91,7 @@ class TaskBase(ResourceBase): created_at: Optional[datetime] creator: Optional[ParticipantRead] description: Optional[str] = Field(None, nullable=True) - incident: IncidentReadNested + incident: IncidentReadMinimal owner: Optional[ParticipantRead] priority: Optional[str] = Field(None, nullable=True) resolve_by: Optional[datetime] diff --git a/src/dispatch/ticket/models.py b/src/dispatch/ticket/models.py index 4046c1bb4d78..ba27766f00f7 100644 --- a/src/dispatch/ticket/models.py +++ b/src/dispatch/ticket/models.py @@ -35,7 +35,3 @@ class TicketRead(TicketBase): def set_description(cls, v): """Sets the description""" return TICKET_DESCRIPTION - - -class TicketNested(TicketBase): - id: PrimaryKey diff --git a/src/dispatch/workflow/models.py b/src/dispatch/workflow/models.py index 83d855aa43c7..10ca3152c3fe 100644 --- a/src/dispatch/workflow/models.py +++ b/src/dispatch/workflow/models.py @@ -141,10 +141,6 @@ def set_description(cls, v, values): return v -class WorkflowNested(WorkflowRead): - pass - - class WorkflowPagination(DispatchBase): total: int items: List[WorkflowRead] = [] From 95678dd3b7ff14c36a82ea52470c748c76802b6b Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Wed, 21 Dec 2022 19:24:26 -0800 Subject: [PATCH 4/9] Fixing tests --- src/dispatch/incident/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatch/incident/models.py b/src/dispatch/incident/models.py index c0c83fd4f5a1..331c1a140e6f 100644 --- a/src/dispatch/incident/models.py +++ b/src/dispatch/incident/models.py @@ -276,7 +276,7 @@ class IncidentCreate(IncidentBase): class IncidentReadMinimal(IncidentBase): id: PrimaryKey closed_at: Optional[datetime] = None - commander: ParticipantReadMinimal + commander: Optional[ParticipantReadMinimal] commanders_location: Optional[str] created_at: Optional[datetime] = None incident_priority: IncidentPriorityReadMinimal @@ -287,7 +287,7 @@ class IncidentReadMinimal(IncidentBase): participants_team: Optional[str] project: ProjectRead reported_at: Optional[datetime] = None - reporter: ParticipantReadMinimal + reporter: Optional[ParticipantReadMinimal] reporters_location: Optional[str] stable_at: Optional[datetime] = None tags: Optional[List[TagReadMinimal]] = [] From d0b3d77e8382de127d78739896667d37404c6519 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Mon, 9 Jan 2023 09:32:12 -0800 Subject: [PATCH 5/9] Fixes naming and adds more correct typing. --- src/dispatch/case/models.py | 8 ++++---- src/dispatch/incident/models.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/dispatch/case/models.py b/src/dispatch/case/models.py index fa677b2c9d3c..11e4ba3883e0 100644 --- a/src/dispatch/case/models.py +++ b/src/dispatch/case/models.py @@ -183,7 +183,7 @@ class CaseCreate(CaseBase): tags: Optional[List[TagRead]] = [] -class CaseReadReadMinimal(CaseBase): +class CaseReadMinimal(CaseBase): id: PrimaryKey assignee: Optional[UserRead] case_priority: CasePriorityRead @@ -208,14 +208,14 @@ class CaseRead(CaseBase): closed_at: Optional[datetime] = None created_at: Optional[datetime] = None documents: Optional[List[DocumentRead]] = [] - duplicates: Optional[List[CaseReadReadMinimal]] = [] + duplicates: Optional[List[CaseReadMinimal]] = [] escalated_at: Optional[datetime] = None events: Optional[List[EventRead]] = [] groups: Optional[List[GroupRead]] = [] incidents: Optional[List[IncidentRead]] = [] name: Optional[NameStr] project: ProjectRead - related: Optional[List[CaseReadReadMinimal]] = [] + related: Optional[List[CaseReadMinimal]] = [] reported_at: Optional[datetime] = None storage: Optional[StorageRead] = None tags: Optional[List[TagRead]] = [] @@ -255,7 +255,7 @@ def find_exclusive(cls, v): class CasePagination(DispatchBase): - items: List[CaseReadReadMinimal] = [] + items: List[CaseReadMinimal] = [] itemsPerPage: int page: int total: int diff --git a/src/dispatch/incident/models.py b/src/dispatch/incident/models.py index 331c1a140e6f..269a8f9417c8 100644 --- a/src/dispatch/incident/models.py +++ b/src/dispatch/incident/models.py @@ -1,6 +1,6 @@ from datetime import datetime from collections import Counter, defaultdict -from typing import List, Optional, Any +from typing import List, Optional, ForwardRef from pydantic import validator from sqlalchemy import ( @@ -27,18 +27,15 @@ from dispatch.incident.priority.models import ( IncidentPriorityBase, IncidentPriorityCreate, - IncidentPriorityRead, IncidentPriorityReadMinimal, ) from dispatch.incident.severity.models import ( IncidentSeverityCreate, - IncidentSeverityRead, IncidentSeverityReadMinimal, IncidentSeverityBase, ) from dispatch.incident.type.models import ( IncidentTypeCreate, - IncidentTypeRead, IncidentTypeReadMinimal, IncidentTypeBase, ) @@ -273,6 +270,9 @@ class IncidentCreate(IncidentBase): tags: Optional[List[TagRead]] = [] +IncidentReadMinimal = ForwardRef("IncidentReadMinimal") + + class IncidentReadMinimal(IncidentBase): id: PrimaryKey closed_at: Optional[datetime] = None @@ -292,10 +292,13 @@ class IncidentReadMinimal(IncidentBase): stable_at: Optional[datetime] = None tags: Optional[List[TagReadMinimal]] = [] total_cost: Optional[float] - duplicates: Optional[List[Any]] = [] + duplicates: Optional[List[IncidentReadMinimal]] = [] incident_costs: Optional[List[IncidentCostRead]] = [] +IncidentReadMinimal.update_forward_refs() + + class IncidentUpdate(IncidentBase): cases: Optional[List[CaseRead]] = [] commander: Optional[ParticipantUpdate] From b52ecc274ea501ccc88e3ac4ba3211c7965ed4ae Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Tue, 10 Jan 2023 10:02:51 -0800 Subject: [PATCH 6/9] Ensures we load all details --- src/dispatch/static/dispatch/src/incident/store.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dispatch/static/dispatch/src/incident/store.js b/src/dispatch/static/dispatch/src/incident/store.js index 77928c55c2ec..baaf64a7c88e 100644 --- a/src/dispatch/static/dispatch/src/incident/store.js +++ b/src/dispatch/static/dispatch/src/incident/store.js @@ -154,7 +154,11 @@ const actions = { ]), }).then((response) => { if (response.data.items.length) { - commit("SET_SELECTED", response.data.items[0]) + // get the full data set + return IncidentApi.get(response.data.items[0].id).then((response) => { + commit("SET_SELECTED", response.data) + commit("SET_SELECTED_LOADING", false) + }) } else { commit( "notification_backend/addBeNotification", From aa9edc873eaf164c2fe6dec56d6934e23211b795 Mon Sep 17 00:00:00 2001 From: Marc Vilanova Date: Tue, 10 Jan 2023 10:52:49 -0800 Subject: [PATCH 7/9] Changes typing and adds missing fields --- src/dispatch/case/models.py | 8 ++++---- src/dispatch/incident/models.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/dispatch/case/models.py b/src/dispatch/case/models.py index 11e4ba3883e0..d71a791943dd 100644 --- a/src/dispatch/case/models.py +++ b/src/dispatch/case/models.py @@ -25,7 +25,7 @@ from dispatch.enums import Visibility from dispatch.event.models import EventRead from dispatch.group.models import Group, GroupRead -from dispatch.incident.models import IncidentRead +from dispatch.incident.models import IncidentReadMinimal from dispatch.messaging.strings import CASE_RESOLUTION_DEFAULT from dispatch.models import DispatchBase, ProjectMixin, TimeStampMixin from dispatch.models import NameStr, PrimaryKey @@ -204,7 +204,6 @@ class CaseRead(CaseBase): case_priority: CasePriorityRead case_severity: CaseSeverityRead case_type: CaseTypeRead - signal_instances: Optional[List[SignalInstanceRead]] = [] closed_at: Optional[datetime] = None created_at: Optional[datetime] = None documents: Optional[List[DocumentRead]] = [] @@ -212,11 +211,12 @@ class CaseRead(CaseBase): escalated_at: Optional[datetime] = None events: Optional[List[EventRead]] = [] groups: Optional[List[GroupRead]] = [] - incidents: Optional[List[IncidentRead]] = [] + incidents: Optional[List[IncidentReadMinimal]] = [] name: Optional[NameStr] project: ProjectRead related: Optional[List[CaseReadMinimal]] = [] reported_at: Optional[datetime] = None + signal_instances: Optional[List[SignalInstanceRead]] = [] storage: Optional[StorageRead] = None tags: Optional[List[TagRead]] = [] ticket: Optional[TicketRead] = None @@ -232,7 +232,7 @@ class CaseUpdate(CaseBase): duplicates: Optional[List[CaseRead]] = [] related: Optional[List[CaseRead]] = [] escalated_at: Optional[datetime] = None - incidents: Optional[List[IncidentRead]] = [] + incidents: Optional[List[IncidentReadMinimal]] = [] reported_at: Optional[datetime] = None tags: Optional[List[TagRead]] = [] triage_at: Optional[datetime] = None diff --git a/src/dispatch/incident/models.py b/src/dispatch/incident/models.py index 269a8f9417c8..0e0c43706b1f 100644 --- a/src/dispatch/incident/models.py +++ b/src/dispatch/incident/models.py @@ -292,7 +292,6 @@ class IncidentReadMinimal(IncidentBase): stable_at: Optional[datetime] = None tags: Optional[List[TagReadMinimal]] = [] total_cost: Optional[float] - duplicates: Optional[List[IncidentReadMinimal]] = [] incident_costs: Optional[List[IncidentCostRead]] = [] @@ -330,9 +329,11 @@ def find_exclusive(cls, v): class IncidentRead(IncidentReadMinimal): + cases: Optional[List[CaseRead]] = [] conference: Optional[ConferenceRead] = None conversation: Optional[ConversationRead] = None documents: Optional[List[DocumentRead]] = [] + duplicates: Optional[List[IncidentReadMinimal]] = [] events: Optional[List[EventRead]] = [] last_executive_report: Optional[ReportRead] last_tactical_report: Optional[ReportRead] From aee4f07277cab9fb5edea3b8da0ffcd8dd8506fa Mon Sep 17 00:00:00 2001 From: Marc Vilanova Date: Tue, 10 Jan 2023 10:59:36 -0800 Subject: [PATCH 8/9] Gets all case details --- src/dispatch/static/dispatch/src/case/store.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dispatch/static/dispatch/src/case/store.js b/src/dispatch/static/dispatch/src/case/store.js index ee4dc3077703..76deefaad1eb 100644 --- a/src/dispatch/static/dispatch/src/case/store.js +++ b/src/dispatch/static/dispatch/src/case/store.js @@ -133,7 +133,11 @@ const actions = { ]), }).then((response) => { if (response.data.items.length) { - commit("SET_SELECTED", response.data.items[0]) + // get the full data set + return CaseApi.get(response.data.items[0].id).then((response) => { + commit("SET_SELECTED", response.data) + commit("SET_SELECTED_LOADING", false) + }) } else { commit( "notification_backend/addBeNotification", From a863873937909811c31cfb35ff93120e1a999992 Mon Sep 17 00:00:00 2001 From: Kevin Glisson Date: Tue, 10 Jan 2023 14:45:16 -0800 Subject: [PATCH 9/9] Adding forward ref --- src/dispatch/case/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dispatch/case/models.py b/src/dispatch/case/models.py index d71a791943dd..808919dcdefa 100644 --- a/src/dispatch/case/models.py +++ b/src/dispatch/case/models.py @@ -1,6 +1,6 @@ from datetime import datetime from collections import defaultdict -from typing import List, Optional, Any +from typing import List, Optional, Any, ForwardRef from pydantic import validator from sqlalchemy import ( @@ -183,6 +183,9 @@ class CaseCreate(CaseBase): tags: Optional[List[TagRead]] = [] +CaseReadMinimal = ForwardRef("CaseReadMinimal") + + class CaseReadMinimal(CaseBase): id: PrimaryKey assignee: Optional[UserRead] @@ -198,6 +201,9 @@ class CaseReadMinimal(CaseBase): triage_at: Optional[datetime] = None +CaseReadMinimal.update_forward_refs() + + class CaseRead(CaseBase): id: PrimaryKey assignee: Optional[UserRead]