562 sla to alerts (#563)
* feat: update alert status handling to manage time_closed for CLOSED and OPEN states * feat: add case_closed_time column and update handling in case status * feat: add case_closed_time and time_closed to reports and templates * Implement feature X to enhance user experience and optimize performance * precommit-fixes * feat: update CURRENT_VERSION to 0.1.12
taylor_socfortress committed
Dec 16, 2025 at 09:53 UTC
e43c67b0346fe26554f884703a093ebb4e7f9e5d
9 files changed
+57
-1
backend/alembic/versions/ea1e223c3d62_add_case_closed_time_column.py
new
+31
@@ -0,0 +1,31 @@
1
+"""Add case closed time column
2
+
3
+Revision ID: ea1e223c3d62
4
+Revises: e74210501112
5
+Create Date: 2025-12-16 09:29:33.243448
6
+
7
+"""
8
+from typing import Sequence
9
+from typing import Union
10
+
11
+import sqlalchemy as sa
12
+
13
+from alembic import op
14
+
15
+# revision identifiers, used by Alembic.
16
+revision: str = "ea1e223c3d62"
17
+down_revision: Union[str, None] = "e74210501112"
18
+branch_labels: Union[str, Sequence[str], None] = None
19
+depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.add_column("incident_management_case", sa.Column("case_closed_time", sa.DateTime(), nullable=True))
25
+ # ### end Alembic commands ###
26
+
27
+
28
+def downgrade() -> None:
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ op.drop_column("incident_management_case", "case_closed_time")
31
+ # ### end Alembic commands ###
backend/app/incidents/models.py
+1
@@ -164,6 +164,7 @@ class Case(SQLModel, table=True):
164
case_description: str = Field(sa_column=Text)
165
case_creation_time: datetime = Field(default_factory=datetime.utcnow)
166
case_status: str = Field(max_length=50, nullable=False)
167
+ case_closed_time: Optional[datetime] = Field(default=None, nullable=True)
168
assigned_to: Optional[str] = Field(max_length=50, nullable=True)
169
customer_code: Optional[str] = Field(max_length=50, nullable=True)
170
notification_invoked_number: Optional[int] = Field(default=0, nullable=True)
backend/app/incidents/routes/incident_report.py
+2
@@ -44,6 +44,7 @@ FIELDNAMES = [
44
"Case Description",
45
"Case Creation Time",
46
"Case Status",
47
+ "Case Closed Time",
48
"Case Assigned To",
49
"Case Customer Code",
50
"Alert ID",
@@ -119,6 +120,7 @@ def serialize_case_alert_to_row(case: Case, alert: Alert) -> Dict[str, Any]:
120
"Case Description": case.case_description,
121
"Case Creation Time": case.case_creation_time.strftime("%Y-%m-%d %H:%M:%S"),
122
"Case Status": case.case_status,
123
+ "Case Closed Time": case.case_closed_time.strftime("%Y-%m-%d %H:%M:%S") if case.case_closed_time else "",
124
"Case Assigned To": case.assigned_to or "",
125
"Case Customer Code": case.customer_code or "",
126
"Alert ID": alert.id,
backend/app/incidents/services/db_operations.py
+16
@@ -792,6 +792,14 @@ async def update_alert_status(update_alert_status: UpdateAlertStatus, db: AsyncS
792
if not alert:
793
raise HTTPException(status_code=404, detail="Alert not found")
794
alert.status = update_alert_status.status
795
+
796
+ # Set time_closed if status is CLOSED
797
+ if update_alert_status.status == "CLOSED":
798
+ alert.time_closed = datetime.utcnow()
799
+ # Reset time_closed if status is OPEN
800
+ elif update_alert_status.status == "OPEN":
801
+ alert.time_closed = None
802
+
803
await db.commit()
804
return alert
805
@@ -802,6 +810,14 @@ async def update_case_status(update_case_status: UpdateCaseStatus, db: AsyncSess
810
if not case:
811
raise HTTPException(status_code=404, detail="Case not found")
812
case.case_status = update_case_status.status
813
+
814
+ # Set case_closed_time if status is CLOSED
815
+ if update_case_status.status == "CLOSED":
816
+ case.case_closed_time = datetime.utcnow()
817
+ # Reset case_closed_time if status is OPEN
818
+ elif update_case_status.status == "OPEN":
819
+ case.case_closed_time = None
820
+
821
await db.commit()
822
return case
823
backend/app/incidents/services/reports.py
+2
@@ -22,12 +22,14 @@ def create_case_context(case) -> Dict[str, Dict[str, str]]:
22
"description": case.case_description,
23
"assigned_to": case.assigned_to,
24
"case_creation_time": case.case_creation_time,
25
+ "case_closed_time": case.case_closed_time,
26
"id": case.id,
27
"alerts": [
28
{
29
"alert_name": alert.alert.alert_name,
30
"alert_description": alert.alert.alert_description,
31
"status": alert.alert.status,
32
+ "time_closed": alert.alert.time_closed,
33
"tags": [tag.tag.tag for tag in alert.alert.tags],
34
"assets": [
35
{
backend/app/incidents/services/reports_pdf.py
+2
@@ -28,12 +28,14 @@ def create_case_context_pdf(case) -> Dict[str, Dict[str, str]]:
28
"description": case.case_description,
29
"assigned_to": case.assigned_to,
30
"case_creation_time": case.case_creation_time,
31
+ "case_closed_time": case.case_closed_time,
32
"id": case.id,
33
"alerts": [
34
{
35
"alert_name": alert.alert.alert_name,
36
"alert_description": alert.alert.alert_description,
37
"status": alert.alert.status,
38
+ "time_closed": alert.alert.time_closed,
39
"tags": [tag.tag.tag for tag in alert.alert.tags],
40
"assets": [
41
{
backend/app/incidents/templates/case_report_jinja_template.docx
Binary files a/backend/app/incidents/templates/case_report_jinja_template.docx and b/backend/app/incidents/templates/case_report_jinja_template.docx differ
backend/app/incidents/templates/case_report_jinja_template.html
+2
@@ -15,6 +15,7 @@
15
<p><strong>Description:</strong> {{ case.description }}</p>
16
<p><strong>Assigned To:</strong> {{ case.assigned_to }}</p>
17
<p><strong>Case Creation Time:</strong> {{ case.case_creation_time }}</p>
18
+ <p><strong>Case Closed Time:</strong> {{ case.case_closed_time if case.case_closed_time else 'N/A' }}</p>
19
<p><strong>Case ID:</strong> {{ case.id }}</p>
20
</div>
21
@@ -24,6 +25,7 @@
25
<p><strong>Alert Name:</strong> {{ alert.alert_name }}</p>
26
<p><strong>Description:</strong> {{ alert.alert_description }}</p>
27
<p><strong>Status:</strong> {{ alert.status }}</p>
28
+ <p><strong>Time Closed:</strong> {{ alert.time_closed if alert.time_closed else 'N/A' }}</p>
29
<p><strong>Tags:</strong> {{ alert.tags | join(', ') }}</p>
30
31
<h3>Assets:</h3>
backend/app/version/services/version.py
+1
-1
@@ -7,7 +7,7 @@ from loguru import logger
7
from packaging.version import Version
8
9
# Current version - update this with each release
10
-CURRENT_VERSION = "0.1.11"
10
+CURRENT_VERSION = "0.1.12"
11
VERSION_CHECK_URL = "https://api.github.com/repos/socfortress/CoPilot/releases/latest"
12
13