Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.

Commit 875b083

Browse files
authored
Merge branch 'master' into chore/upgrade-node
2 parents 513f4c9 + 053723e commit 875b083

File tree

31 files changed

+730
-214
lines changed

31 files changed

+730
-214
lines changed

docker/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11.4-slim-bullseye as sdist
1+
FROM python:3.13.0-slim-bullseye as sdist
22

33
LABEL maintainer="[email protected]"
44
LABEL org.opencontainers.image.title="Dispatch PyPI Wheel"
@@ -56,7 +56,7 @@ RUN YARN_CACHE_FOLDER="$(mktemp -d)" \
5656
&& mv /usr/src/dispatch/dist /dist
5757

5858
# This is the image to be run
59-
FROM python:3.11.4-slim-bullseye
59+
FROM python:3.13.0-slim-bullseye
6060

6161
LABEL maintainer="[email protected]"
6262
LABEL org.opencontainers.image.title="Dispatch"

docs/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

requirements-base.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ python-dateutil==2.9.0.post0
330330
# pandas
331331
python-jose==3.3.0
332332
# via -r requirements-base.in
333-
python-multipart==0.0.12
333+
python-multipart==0.0.16
334334
# via -r requirements-base.in
335335
python-slugify==8.0.4
336336
# via -r requirements-base.in
@@ -396,9 +396,9 @@ six==1.16.0
396396
# python-dateutil
397397
# sqlalchemy-filters
398398
# validators
399-
slack-bolt==1.20.1
399+
slack-bolt==1.21.2
400400
# via -r requirements-base.in
401-
slack-sdk==3.33.1
401+
slack-sdk==3.33.3
402402
# via
403403
# -r requirements-base.in
404404
# slack-bolt
@@ -496,7 +496,7 @@ wasabi==1.1.2
496496
# weasel
497497
weasel==0.3.4
498498
# via spacy
499-
werkzeug==3.0.3
499+
werkzeug==3.0.6
500500
# via schemathesis
501501
wrapt==1.16.0
502502
# via deprecated

requirements-dev.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ click==8.1.7
1818
# via
1919
# -r requirements-dev.in
2020
# black
21-
coverage==7.6.3
21+
coverage==7.6.4
2222
# via -r requirements-dev.in
2323
decorator==5.1.1
2424
# via ipython
@@ -32,7 +32,7 @@ executing==2.0.1
3232
# stack-data
3333
factory-boy==3.3.1
3434
# via -r requirements-dev.in
35-
faker==30.6.0
35+
faker==30.8.1
3636
# via
3737
# -r requirements-dev.in
3838
# factory-boy
@@ -42,7 +42,7 @@ identify==2.5.33
4242
# via pre-commit
4343
iniconfig==2.0.0
4444
# via pytest
45-
ipython==8.28.0
45+
ipython==8.29.0
4646
# via -r requirements-dev.in
4747
jedi==0.19.1
4848
# via ipython
@@ -86,7 +86,7 @@ python-dateutil==2.9.0.post0
8686
# via faker
8787
pyyaml==6.0.1
8888
# via pre-commit
89-
ruff==0.6.9
89+
ruff==0.7.1
9090
# via -r requirements-dev.in
9191
six==1.16.0
9292
# via

src/dispatch/case/flows.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from dispatch.case.messaging import send_case_welcome_participant_message
99
from dispatch.case.models import CaseRead
1010
from dispatch.conversation import flows as conversation_flows
11-
from dispatch.database.core import SessionLocal
1211
from dispatch.decorators import background_task
1312
from dispatch.document import flows as document_flows
1413
from dispatch.enums import DocumentResourceTypes, EventType, Visibility
@@ -34,18 +33,19 @@
3433
from dispatch.storage.enums import StorageAction
3534
from dispatch.ticket import flows as ticket_flows
3635

36+
from .enums import CaseResolutionReason, CaseStatus
3737
from .messaging import (
3838
send_case_created_notifications,
3939
send_case_rating_feedback_message,
4040
send_case_update_notifications,
4141
)
42-
from .models import Case, CaseStatus
42+
from .models import Case
4343
from .service import get
4444

4545
log = logging.getLogger(__name__)
4646

4747

48-
def get_case_participants_flow(case: Case, db_session: SessionLocal):
48+
def get_case_participants_flow(case: Case, db_session: Session):
4949
"""Get additional case participants based on priority, type and description."""
5050
individual_contacts = []
5151
team_contacts = []
@@ -192,6 +192,30 @@ def update_conversation(case: Case, db_session: Session) -> None:
192192
)
193193

194194

195+
def case_auto_close_flow(case: Case, db_session: Session):
196+
"Runs the case auto close flow."
197+
# we mark the case as closed
198+
case.resolution = "Auto closed via case type auto close configuration."
199+
case.resolution_reason = CaseResolutionReason.user_acknowledge
200+
case.status = CaseStatus.closed
201+
db_session.add(case)
202+
db_session.commit()
203+
204+
# we transition the case from the new to the closed state
205+
case_triage_status_flow(
206+
case=case,
207+
db_session=db_session,
208+
)
209+
case_closed_status_flow(
210+
case=case,
211+
db_session=db_session,
212+
)
213+
214+
if case.conversation and case.has_thread:
215+
# we update the case conversation
216+
update_conversation(case=case, db_session=db_session)
217+
218+
195219
def case_new_create_flow(
196220
*,
197221
case_id: int,
@@ -254,6 +278,10 @@ def case_new_create_flow(
254278
log.warning("Case assignee not paged. No plugin of type oncall enabled.")
255279
return case
256280

281+
if case and case.case_type.auto_close:
282+
# we transition the case to the closed state if its case type has auto close enabled
283+
case_auto_close_flow(case=case, db_session=db_session)
284+
257285
return case
258286

259287

@@ -336,7 +364,11 @@ def case_update_flow(
336364
# we get the case
337365
case = get(db_session=db_session, case_id=case_id)
338366

339-
if reporter_email and case and reporter_email != case.reporter.individual.email:
367+
if not case:
368+
log.warning(f"Case with id {case_id} not found.")
369+
return
370+
371+
if reporter_email and case.reporter and reporter_email != case.reporter.individual.email:
340372
# we run the case assign role flow for the reporter if it changed
341373
case_assign_role_flow(
342374
case_id=case.id,
@@ -345,7 +377,7 @@ def case_update_flow(
345377
db_session=db_session,
346378
)
347379

348-
if assignee_email and case and assignee_email != case.assignee.individual.email:
380+
if assignee_email and case.assignee and assignee_email != case.assignee.individual.email:
349381
# we run the case assign role flow for the assignee if it changed
350382
case_assign_role_flow(
351383
case_id=case.id,
@@ -374,15 +406,15 @@ def case_update_flow(
374406

375407
if case.tactical_group:
376408
# we update the tactical group
377-
if reporter_email and reporter_email != case.reporter.individual.email:
409+
if reporter_email and case.reporter and reporter_email != case.reporter.individual.email:
378410
group_flows.update_group(
379411
subject=case,
380412
group=case.tactical_group,
381413
group_action=GroupAction.add_member,
382414
group_member=reporter_email,
383415
db_session=db_session,
384416
)
385-
if assignee_email and assignee_email != case.assignee.individual.email:
417+
if assignee_email and case.assignee and assignee_email != case.assignee.individual.email:
386418
group_flows.update_group(
387419
subject=case,
388420
group=case.tactical_group,
@@ -405,7 +437,7 @@ def case_update_flow(
405437
send_case_update_notifications(case, previous_case, db_session)
406438

407439

408-
def case_delete_flow(case: Case, db_session: SessionLocal):
440+
def case_delete_flow(case: Case, db_session: Session):
409441
"""Runs the case delete flow."""
410442
# we delete the external ticket
411443
if case.ticket:
@@ -488,6 +520,9 @@ def case_closed_status_flow(case: Case, db_session=None):
488520
if not storage_plugin:
489521
return
490522

523+
# we update the ticket
524+
ticket_flows.update_case_ticket(case=case, db_session=db_session)
525+
491526
# Open document access if configured
492527
if storage_plugin.configuration.open_on_close:
493528
for document in case.documents:

src/dispatch/case/type/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from typing import List, Optional
22

3-
from pydantic import Field, validator, AnyHttpUrl
3+
from pydantic import AnyHttpUrl, Field, validator
44
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String
55
from sqlalchemy.event import listen
66
from sqlalchemy.ext.hybrid import hybrid_method
77
from sqlalchemy.orm import relationship
8+
from sqlalchemy.sql import false
89
from sqlalchemy.sql.schema import UniqueConstraint
910
from sqlalchemy_utils import TSVectorType
1011

@@ -27,6 +28,7 @@ class CaseType(ProjectMixin, Base):
2728
exclude_from_metrics = Column(Boolean, default=False)
2829
plugin_metadata = Column(JSON, default=[])
2930
conversation_target = Column(String)
31+
auto_close = Column(Boolean, default=False, server_default=false())
3032

3133
# the catalog here is simple to help matching "named entities"
3234
search_vector = Column(TSVectorType("name", regconfig="pg_catalog.simple"))
@@ -100,6 +102,7 @@ class CaseTypeBase(DispatchBase):
100102
project: Optional[ProjectRead]
101103
visibility: Optional[str] = Field(None, nullable=True)
102104
cost_model: Optional[CostModelRead] = None
105+
auto_close: Optional[bool] = False
103106

104107
@validator("plugin_metadata", pre=True)
105108
def replace_none_with_empty_list(cls, value):
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Adds configuration to the Dispatch Ticket PluginInstance
2+
3+
Revision ID: 24322617ce9a
4+
Revises: 3c49f62d7914
5+
Create Date: 2024-10-25 15:15:38.078421
6+
7+
"""
8+
9+
from alembic import op
10+
from pydantic import SecretStr, ValidationError
11+
from pydantic.json import pydantic_encoder
12+
13+
from sqlalchemy import Column, Integer, ForeignKey, String
14+
from sqlalchemy.ext.declarative import declarative_base
15+
from sqlalchemy.orm import relationship, Session
16+
from sqlalchemy.ext.hybrid import hybrid_property
17+
from sqlalchemy_utils import StringEncryptedType
18+
from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine
19+
from dispatch.config import DISPATCH_ENCRYPTION_KEY
20+
21+
# revision identifiers, used by Alembic.
22+
revision = "24322617ce9a"
23+
down_revision = "3c49f62d7914"
24+
branch_labels = None
25+
depends_on = None
26+
27+
Base = declarative_base()
28+
29+
30+
def show_secrets_encoder(obj):
31+
if isinstance(obj, SecretStr):
32+
return obj.get_secret_value()
33+
else:
34+
return pydantic_encoder(obj)
35+
36+
37+
def migrate_config(instances, slug, config):
38+
for instance in instances:
39+
if slug == instance.plugin.slug:
40+
instance.configuration = config
41+
42+
43+
class Plugin(Base):
44+
__tablename__ = "plugin"
45+
__table_args__ = {"schema": "dispatch_core"}
46+
id = Column(Integer, primary_key=True)
47+
slug = Column(String, unique=True)
48+
49+
50+
class PluginInstance(Base):
51+
__tablename__ = "plugin_instance"
52+
id = Column(Integer, primary_key=True)
53+
_configuration = Column(
54+
StringEncryptedType(key=str(DISPATCH_ENCRYPTION_KEY), engine=AesEngine, padding="pkcs5")
55+
)
56+
plugin_id = Column(Integer, ForeignKey(Plugin.id))
57+
plugin = relationship(Plugin, backref="instances")
58+
59+
@hybrid_property
60+
def configuration(self):
61+
"""Property that correctly returns a plugins configuration object."""
62+
pass
63+
64+
@configuration.setter
65+
def configuration(self, configuration):
66+
"""Property that correctly sets a plugins configuration object."""
67+
if configuration:
68+
self._configuration = configuration.json(encoder=show_secrets_encoder)
69+
70+
71+
def upgrade():
72+
# ### commands auto generated by Alembic - please adjust! ###
73+
from dispatch.plugins.dispatch_core.config import DispatchTicketConfiguration
74+
75+
bind = op.get_bind()
76+
session = Session(bind=bind)
77+
78+
instances = session.query(PluginInstance).all()
79+
80+
try:
81+
dispatch_ticket_config = DispatchTicketConfiguration(
82+
use_incident_name=False,
83+
)
84+
85+
migrate_config(instances, "dispatch-ticket", dispatch_ticket_config)
86+
87+
except ValidationError:
88+
print(
89+
"Skipping automatic migration of Dispatch ticket plugin, if you are using the Dispatch ticket plugin, please manually migrate."
90+
)
91+
92+
session.commit()
93+
# ### end Alembic commands ###
94+
95+
96+
def downgrade():
97+
# ### commands auto generated by Alembic - please adjust! ###
98+
pass
99+
# ### end Alembic commands ###
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Adds auto_close column to case_type model
2+
3+
Revision ID: 3edb0476365a
4+
Revises: 24322617ce9a
5+
Create Date: 2024-10-29 13:26:29.001448
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "3edb0476365a"
14+
down_revision = "24322617ce9a"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column(
22+
"case_type",
23+
sa.Column("auto_close", sa.Boolean(), server_default=sa.text("false"), nullable=True),
24+
)
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade():
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
op.drop_column("case_type", "auto_close")
31+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)