Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add base concept of entities to signals #2977

Merged
merged 16 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/dispatch/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from dispatch.data.source.views import router as source_router
from dispatch.definition.views import router as definition_router
from dispatch.document.views import router as document_router
from dispatch.entity.views import router as entity_router
from dispatch.entity_type.views import router as entity_type_router
from dispatch.feedback.views import router as feedback_router
from dispatch.incident.priority.views import router as incident_priority_router
from dispatch.incident.severity.views import router as incident_severity_router
Expand Down Expand Up @@ -131,6 +133,12 @@ def get_organization_path(organization: OrganizationSlug):
authenticated_organization_api_router.include_router(
document_router, prefix="/documents", tags=["documents"]
)
authenticated_organization_api_router.include_router(
entity_router, prefix="/entity", tags=["entities"]
)
authenticated_organization_api_router.include_router(
entity_type_router, prefix="/entity_type", tags=["entity_types"]
)
authenticated_organization_api_router.include_router(tag_router, prefix="/tags", tags=["tags"])
authenticated_organization_api_router.include_router(
tag_type_router, prefix="/tag_types", tags=["tag_types"]
Expand Down
2 changes: 2 additions & 0 deletions src/dispatch/case/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from dispatch.database.core import Base
from dispatch.document.models import Document, DocumentRead
from dispatch.enums import Visibility
from dispatch.entity.models import EntityRead
from dispatch.event.models import EventRead
from dispatch.group.models import Group, GroupRead
from dispatch.incident.models import IncidentReadMinimal
Expand Down Expand Up @@ -165,6 +166,7 @@ class SignalRead(DispatchBase):

class SignalInstanceRead(DispatchBase):
signal: SignalRead
entities: Optional[List[EntityRead]] = []
tags: Optional[List[TagRead]] = []
raw: Any
fingerprint: str
Expand Down
13 changes: 7 additions & 6 deletions src/dispatch/case/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,13 @@ def create(*, db_session, case_in: CaseCreate, current_user: DispatchUser = None
)

# add reporter
participant_flows.add_participant(
case_in.reporter.individual.email,
case,
db_session,
role=ParticipantRoleType.reporter,
)
if case_in.reporter:
participant_flows.add_participant(
case_in.reporter.individual.email,
case,
db_session,
role=ParticipantRoleType.reporter,
)

return case

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Adds entity and entity type tables and associations
Revision ID: 8746b4e292d2
Revises: 941efd922446
Create Date: 2023-02-09 23:18:11.326027
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import TSVECTOR, UUID

# revision identifiers, used by Alembic.
revision = "8746b4e292d2"
down_revision = "941efd922446"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust!

# EntityType
op.create_table(
"entity_type",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("field", sa.String(), nullable=True),
sa.Column("regular_expression", sa.String(), nullable=True),
sa.Column("global_find", sa.Boolean(), nullable=True),
sa.Column("enabled", sa.Boolean(), nullable=True),
sa.Column("search_vector", TSVECTOR, nullable=True),
sa.Column("project_id", sa.Integer(), nullable=True),
sa.Column("updated_at", sa.DateTime(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(["project_id"], ["project.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("name", "project_id"),
)
op.create_table(
"assoc_signal_entity_types",
sa.Column("signal_id", sa.Integer(), nullable=False),
sa.Column("entity_type_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(["signal_id"], ["signal.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["entity_type_id"], ["entity_type.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("signal_id", "entity_type_id"),
)
op.create_index(
"entity_type_search_vector_idx",
"entity_type",
["search_vector"],
unique=False,
postgresql_using="gin",
)

# Entity
op.create_table(
"entity",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("value", sa.String(), nullable=True),
sa.Column("source", sa.Boolean(), nullable=True),
sa.Column("entity_type_id", sa.Integer(), nullable=False),
sa.Column("search_vector", TSVECTOR, nullable=True),
sa.Column("project_id", sa.Integer(), nullable=True),
sa.Column("updated_at", sa.DateTime(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["entity_type_id"],
["entity_type.id"],
),
sa.ForeignKeyConstraint(["project_id"], ["project.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("name", "project_id"),
)
op.create_index(
"ix_entity_search_vector",
"entity",
["search_vector"],
unique=False,
postgresql_using="gin",
)
op.create_table(
"assoc_signal_instance_entities",
sa.Column("signal_instance_id", UUID(), nullable=False),
sa.Column("entity_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(["signal_instance_id"], ["signal_instance.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["entity_id"], ["entity.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("signal_instance_id", "entity_id"),
)


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index("ix_entity_search_vector", table_name="entity")
op.drop_table("entity")
op.drop_table("entity_type")
op.drop_index("entity_type_search_vector_idx", table_name="entity", postgresql_using="gin")
op.drop_table("assoc_signal_entity_types")
op.drop_table("assoc_signal_instance_entity_types")
# ### end Alembic commands ###
Empty file added src/dispatch/entity/__init__.py
Empty file.
73 changes: 73 additions & 0 deletions src/dispatch/entity/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import Optional, List
from pydantic import Field

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.schema import UniqueConstraint
from sqlalchemy_utils import TSVectorType

from dispatch.database.core import Base
from dispatch.models import DispatchBase, TimeStampMixin, ProjectMixin, PrimaryKey
from dispatch.project.models import ProjectRead
from dispatch.entity_type.models import (
EntityTypeRead,
EntityTypeCreate,
EntityTypeUpdate,
)


class Entity(Base, TimeStampMixin, ProjectMixin):
__table_args__ = (UniqueConstraint("name", "project_id"),)

# Columns
id = Column(Integer, primary_key=True)
name = Column(String)
description = Column(String)
value = Column(String)
source = Column(String)

# Relationships
entity_type_id = Column(Integer, ForeignKey("entity_type.id"), nullable=False)
entity_type = relationship("EntityType", backref="entity")

# the catalog here is simple to help matching "named entities"
search_vector = Column(TSVectorType("name", regconfig="pg_catalog.simple"))
wssheldon marked this conversation as resolved.
Show resolved Hide resolved


# Pydantic models
class EntityBase(DispatchBase):
name: Optional[str] = Field(None, nullable=True)
source: Optional[str] = Field(None, nullable=True)
value: Optional[str] = Field(None, nullable=True)
description: Optional[str] = Field(None, nullable=True)


class EntityCreate(EntityBase):
id: Optional[PrimaryKey]
entity_type: EntityTypeCreate
project: ProjectRead


class EntityUpdate(EntityBase):
id: Optional[PrimaryKey]
entity_type: Optional[EntityTypeUpdate]


class EntityRead(EntityBase):
id: PrimaryKey
entity_type: Optional[EntityTypeRead]
project: ProjectRead


class EntityReadMinimal(DispatchBase):
id: PrimaryKey
name: Optional[str] = Field(None, nullable=True)
source: Optional[str] = Field(None, nullable=True)
value: Optional[str] = Field(None, nullable=True)
description: Optional[str] = Field(None, nullable=True)
entity_type: Optional[EntityTypeRead]
wssheldon marked this conversation as resolved.
Show resolved Hide resolved


class EntityPagination(DispatchBase):
items: List[EntityRead]
total: int
Loading