From e77bfb2fc6791220fb74eb7303b71b6ffeae2a47 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Thu, 7 Sep 2023 11:00:59 -0700 Subject: [PATCH] Adds signal env (#3756) * Adds signal env --- .../versions/2023-09-05_1dd78f49e303.py | 30 +++++++++++++++++++ src/dispatch/signal/flows.py | 8 ++++- src/dispatch/signal/models.py | 9 ++++++ src/dispatch/signal/service.py | 19 ++++++++++-- src/dispatch/signal/views.py | 10 ++++--- tests/factories.py | 1 + tests/signal/test_signal_flow.py | 23 ++++++++++++-- 7 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 src/dispatch/database/revisions/tenant/versions/2023-09-05_1dd78f49e303.py diff --git a/src/dispatch/database/revisions/tenant/versions/2023-09-05_1dd78f49e303.py b/src/dispatch/database/revisions/tenant/versions/2023-09-05_1dd78f49e303.py new file mode 100644 index 000000000000..f9563d43f66a --- /dev/null +++ b/src/dispatch/database/revisions/tenant/versions/2023-09-05_1dd78f49e303.py @@ -0,0 +1,30 @@ +"""Adds an environment variable for spliting up test and prod signals. + +Revision ID: 1dd78f49e303 +Revises: 4e57f5b1f3f3 +Create Date: 2023-09-05 09:57:18.160124 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "1dd78f49e303" +down_revision = "4e57f5b1f3f3" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("signal", sa.Column("environment", sa.String(), nullable=True, default="prod")) + op.execute("UPDATE signal SET environment = 'prod'") + op.alter_column("signal", "environment", nullable=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("signal", "environment") + # ### end Alembic commands ### diff --git a/src/dispatch/signal/flows.py b/src/dispatch/signal/flows.py index 691a737a2451..7f61241a99a0 100644 --- a/src/dispatch/signal/flows.py +++ b/src/dispatch/signal/flows.py @@ -19,7 +19,12 @@ from dispatch.signal import flows as signal_flows from dispatch.signal import service as signal_service from dispatch.signal.enums import SignalEngagementStatus -from dispatch.signal.models import SignalFilterAction, SignalInstance, SignalInstanceCreate +from dispatch.signal.models import ( + SignalFilterAction, + SignalInstance, + SignalInstanceCreate, + SignalEnvironment, +) from dispatch.workflow import flows as workflow_flows from dispatch.entity_type.models import EntityScopeEnum @@ -167,6 +172,7 @@ def create_signal_instance( signal = signal_service.get_by_variant_or_external_id( db_session=db_session, project_id=project.id, + environment=signal_instance_data.get("environment", SignalEnvironment.PROD), external_id=signal_instance_data.get("id"), variant=signal_instance_data["variant"], ) diff --git a/src/dispatch/signal/models.py b/src/dispatch/signal/models.py index 23dfd127e490..e7d37e5496e4 100644 --- a/src/dispatch/signal/models.py +++ b/src/dispatch/signal/models.py @@ -129,6 +129,11 @@ class SignalFilterAction(DispatchEnum): none = "none" +class SignalEnvironment(DispatchEnum): + PROD = "prod" + TEST = "test" + + class Signal(Base, TimeStampMixin, ProjectMixin): id = Column(Integer, primary_key=True) name = Column(String) @@ -141,6 +146,7 @@ class Signal(Base, TimeStampMixin, ProjectMixin): variant = Column(String) loopin_signal_identity = Column(Boolean, default=False) enabled = Column(Boolean, default=False) + environment = Column(String, default=SignalEnvironment.PROD) case_type_id = Column(Integer, ForeignKey(CaseType.id)) case_type = relationship("CaseType", backref="signals") case_priority_id = Column(Integer, ForeignKey(CasePriority.id)) @@ -304,6 +310,7 @@ class SignalBase(DispatchBase): enabled: Optional[bool] = False external_url: Optional[str] create_case: Optional[bool] = True + environment: Optional[SignalEnvironment] = SignalEnvironment.PROD oncall_service: Optional[Service] source: Optional[SourceBase] created_at: Optional[datetime] = None @@ -330,6 +337,7 @@ class SignalUpdate(SignalBase): class SignalRead(SignalBase): id: PrimaryKey engagements: Optional[List[SignalEngagementRead]] = [] + environment: Optional[SignalEnvironment] = SignalEnvironment.PROD entity_types: Optional[List[EntityTypeRead]] = [] filters: Optional[List[SignalFilterRead]] = [] workflows: Optional[List[WorkflowRead]] = [] @@ -344,6 +352,7 @@ class SignalReadMinimal(DispatchBase): description: Optional[str] variant: Optional[str] external_id: str + environment: Optional[SignalEnvironment] = SignalEnvironment.PROD enabled: Optional[bool] = False external_url: Optional[str] create_case: Optional[bool] = True diff --git a/src/dispatch/signal/service.py b/src/dispatch/signal/service.py index f445763af842..f207ecc0b4d8 100644 --- a/src/dispatch/signal/service.py +++ b/src/dispatch/signal/service.py @@ -225,18 +225,31 @@ def get_by_primary_or_external_id( def get_by_variant_or_external_id( - *, db_session: Session, project_id: int, external_id: str = None, variant: str = None + *, + db_session: Session, + project_id: int, + environment: str, + external_id: str = None, + variant: str = None, ) -> Optional[Signal]: """Gets a signal it's external id (and variant if supplied).""" if variant: return ( db_session.query(Signal) - .filter(Signal.project_id == project_id, Signal.variant == variant) + .filter( + Signal.project_id == project_id, + Signal.environment == environment, + Signal.variant == variant, + ) .one_or_none() ) return ( db_session.query(Signal) - .filter(Signal.project_id == project_id, Signal.external_id == external_id) + .filter( + Signal.project_id == project_id, + Signal.environment == environment, + Signal.external_id == external_id, + ) .one_or_none() ) diff --git a/src/dispatch/signal/views.py b/src/dispatch/signal/views.py index b83aee39ab25..6234ec65f849 100644 --- a/src/dispatch/signal/views.py +++ b/src/dispatch/signal/views.py @@ -1,11 +1,11 @@ import logging from typing import Union -from fastapi import APIRouter, BackgroundTasks, HTTPException, Request, Response, status, Depends +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, Response, status from pydantic.error_wrappers import ErrorWrapper, ValidationError from sqlalchemy.exc import IntegrityError -from dispatch.auth.permissions import SensitiveProjectActionPermission, PermissionsDependency +from dispatch.auth.permissions import PermissionsDependency, SensitiveProjectActionPermission from dispatch.auth.service import CurrentUser from dispatch.database.core import DbSession from dispatch.database.service import CommonParameters, search_filter_sort_paginate @@ -15,11 +15,13 @@ from dispatch.rate_limiter import limiter from dispatch.signal import service as signal_service +from .flows import signal_instance_update_flow from .models import ( SignalCreate, SignalEngagementCreate, SignalEngagementPagination, SignalEngagementRead, + SignalEnvironment, SignalFilterCreate, SignalFilterPagination, SignalFilterRead, @@ -44,8 +46,6 @@ update_signal_filter, ) -from .flows import signal_instance_update_flow - router = APIRouter() log = logging.getLogger(__name__) @@ -75,12 +75,14 @@ def create_signal_instance( if not signal_instance_in.signal: external_id = signal_instance_in.raw.get("externalId") variant = signal_instance_in.raw.get("variant") + environment = signal_instance_in.raw.get("environment", SignalEnvironment.PROD) if external_id or variant: signal = signal_service.get_by_variant_or_external_id( db_session=db_session, project_id=project.id, external_id=external_id, + environment=environment, variant=variant, ) diff --git a/tests/factories.py b/tests/factories.py index bb39718abbc6..2db070a9bd54 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -817,6 +817,7 @@ class SignalFactory(BaseFactory): description = "Test Description" external_url = "https://test.com" external_id = "1234" + environment = "test" variant = "Test Variant" enabled = True loopin_signal_identity = False diff --git a/tests/signal/test_signal_flow.py b/tests/signal/test_signal_flow.py index f4612bbde555..40642f89b765 100644 --- a/tests/signal/test_signal_flow.py +++ b/tests/signal/test_signal_flow.py @@ -12,7 +12,7 @@ def test_create_signal_instance(session, signal, case_severity, case_priority, u case_severity.default = True case_severity.project_id = signal.project_id - instance_data = {"variant": signal.variant} + instance_data = {"variant": signal.variant, "environment": "test"} assert create_signal_instance( db_session=session, @@ -31,6 +31,25 @@ def test_create_signal_instance_no_variant(session, signal, case_severity, case_ case_severity.default = True case_severity.project_id = signal.project_id + instance_data = {"variant": "unknown", "environment": "test"} + with pytest.raises(DispatchException): + create_signal_instance( + db_session=session, + project=signal.project, + signal_instance_data=instance_data, + current_user=user, + ) + + +def test_create_signal_instance_no_environment(session, signal, case_severity, case_priority, user): + from dispatch.signal.flows import create_signal_instance + + case_priority.default = True + case_priority.project_id = signal.project_id + + case_severity.default = True + case_severity.project_id = signal.project_id + instance_data = {"variant": "unknown"} with pytest.raises(DispatchException): create_signal_instance( @@ -51,7 +70,7 @@ def test_create_signal_instance_not_enabled(session, signal, case_severity, case case_severity.project_id = signal.project_id signal.enabled = False - instance_data = {"variant": signal.variant} + instance_data = {"variant": signal.variant, "environment": "test"} with pytest.raises(DispatchException): create_signal_instance( db_session=session,