diff --git a/src/dispatch/case/flows.py b/src/dispatch/case/flows.py
index 3128c8bf7b31..2d58d35cb393 100644
--- a/src/dispatch/case/flows.py
+++ b/src/dispatch/case/flows.py
@@ -33,12 +33,13 @@
from dispatch.storage.enums import StorageAction
from dispatch.ticket import flows as ticket_flows
+from .enums import CaseResolutionReason, CaseStatus
from .messaging import (
send_case_created_notifications,
send_case_rating_feedback_message,
send_case_update_notifications,
)
-from .models import Case, CaseStatus
+from .models import Case
from .service import get
log = logging.getLogger(__name__)
@@ -191,6 +192,26 @@ def update_conversation(case: Case, db_session: Session) -> None:
)
+def case_auto_close_flow(case: Case, db_session: Session):
+ "Runs the case auto close flow."
+ # we mark the case as closed
+ case.resolution = "Auto closed via case type auto close configuration."
+ case.resolution_reason = CaseResolutionReason.user_acknowledge
+ case.status = CaseStatus.closed
+ db_session.add(case)
+ db_session.commit()
+
+ # we transition the case from the new to the closed state
+ case_triage_status_flow(
+ case=case,
+ db_session=db_session,
+ )
+ case_closed_status_flow(
+ case=case,
+ db_session=db_session,
+ )
+
+
def case_new_create_flow(
*,
case_id: int,
@@ -253,6 +274,10 @@ def case_new_create_flow(
log.warning("Case assignee not paged. No plugin of type oncall enabled.")
return case
+ if case and case.case_type.auto_close:
+ # we transition the case to the closed state if its case type has auto close enabled
+ case_auto_close_flow(case=case, db_session=db_session)
+
return case
diff --git a/src/dispatch/case/type/models.py b/src/dispatch/case/type/models.py
index 416bccb121df..ab5e03c2f4f4 100644
--- a/src/dispatch/case/type/models.py
+++ b/src/dispatch/case/type/models.py
@@ -1,10 +1,11 @@
from typing import List, Optional
-from pydantic import Field, validator, AnyHttpUrl
+from pydantic import AnyHttpUrl, Field, validator
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.event import listen
from sqlalchemy.ext.hybrid import hybrid_method
from sqlalchemy.orm import relationship
+from sqlalchemy.sql import false
from sqlalchemy.sql.schema import UniqueConstraint
from sqlalchemy_utils import TSVectorType
@@ -27,6 +28,7 @@ class CaseType(ProjectMixin, Base):
exclude_from_metrics = Column(Boolean, default=False)
plugin_metadata = Column(JSON, default=[])
conversation_target = Column(String)
+ auto_close = Column(Boolean, default=False, server_default=false())
# the catalog here is simple to help matching "named entities"
search_vector = Column(TSVectorType("name", regconfig="pg_catalog.simple"))
@@ -100,6 +102,7 @@ class CaseTypeBase(DispatchBase):
project: Optional[ProjectRead]
visibility: Optional[str] = Field(None, nullable=True)
cost_model: Optional[CostModelRead] = None
+ auto_close: Optional[bool] = False
@validator("plugin_metadata", pre=True)
def replace_none_with_empty_list(cls, value):
diff --git a/src/dispatch/database/revisions/tenant/versions/2024-10-29_3edb0476365a.py b/src/dispatch/database/revisions/tenant/versions/2024-10-29_3edb0476365a.py
new file mode 100644
index 000000000000..53f964fba5fa
--- /dev/null
+++ b/src/dispatch/database/revisions/tenant/versions/2024-10-29_3edb0476365a.py
@@ -0,0 +1,31 @@
+"""Adds auto_close column to case_type model
+
+Revision ID: 3edb0476365a
+Revises: 24322617ce9a
+Create Date: 2024-10-29 13:26:29.001448
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "3edb0476365a"
+down_revision = "24322617ce9a"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column(
+ "case_type",
+ sa.Column("auto_close", sa.Boolean(), server_default=sa.text("false"), nullable=True),
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("case_type", "auto_close")
+ # ### end Alembic commands ###
diff --git a/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue b/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue
index bc9b75150613..6c30938c06e1 100644
--- a/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue
@@ -114,9 +114,9 @@
@@ -128,9 +128,16 @@
+
+
+
@@ -184,6 +191,7 @@ export default {
computed: {
...mapFields("case_type", [
"dialogs.showCreateEdit",
+ "selected.auto_close",
"selected.case_template_document",
"selected.conversation_target",
"selected.cost_model",
diff --git a/src/dispatch/static/dispatch/src/case/type/Table.vue b/src/dispatch/static/dispatch/src/case/type/Table.vue
index c72c6214a6de..7e795a71af4c 100644
--- a/src/dispatch/static/dispatch/src/case/type/Table.vue
+++ b/src/dispatch/static/dispatch/src/case/type/Table.vue
@@ -51,10 +51,13 @@
{{ item.incident_type.name }}
+
+
+
-
+
@@ -103,8 +106,9 @@ export default {
{ title: "Visibility", value: "visibility", sortable: false },
{ title: "Oncall Service", value: "oncall_service.name", sortable: false },
{ title: "Incident Type", value: "incident_type.name", sortable: false },
- { title: "Default", value: "default", sortable: true },
{ title: "Enabled", value: "enabled", sortable: true },
+ { title: "Default", value: "default", sortable: true },
+ { title: "Auto Close", value: "auto_close", sortable: true },
{ title: "", key: "data-table-actions", sortable: false, align: "end" },
],
}
diff --git a/src/dispatch/static/dispatch/src/case/type/store.js b/src/dispatch/static/dispatch/src/case/type/store.js
index 87dc49001a6b..7badfa11dde2 100644
--- a/src/dispatch/static/dispatch/src/case/type/store.js
+++ b/src/dispatch/static/dispatch/src/case/type/store.js
@@ -6,7 +6,9 @@ import SearchUtils from "@/search/utils"
const getDefaultSelectedState = () => {
return {
+ auto_close: null,
case_template_document: null,
+ conversation_target: null,
cost_model: null,
default: false,
description: null,
@@ -20,7 +22,6 @@ const getDefaultSelectedState = () => {
plugin_metadata: [],
project: null,
slug: null,
- conversation_target: null,
visibility: null,
}
}
diff --git a/src/dispatch/static/dispatch/src/service/ServiceSelect.vue b/src/dispatch/static/dispatch/src/service/ServiceSelect.vue
index 0f16fe4ce0fa..dd180aab9a29 100644
--- a/src/dispatch/static/dispatch/src/service/ServiceSelect.vue
+++ b/src/dispatch/static/dispatch/src/service/ServiceSelect.vue
@@ -16,7 +16,8 @@
chips
multiple
closable-chips
- >
+ >
+
Load More