diff --git a/src/dispatch/database/revisions/tenant/versions/2024-09-10_0a6702319f6a.py b/src/dispatch/database/revisions/tenant/versions/2024-09-10_0a6702319f6a.py
new file mode 100644
index 000000000000..bd0a32a7d410
--- /dev/null
+++ b/src/dispatch/database/revisions/tenant/versions/2024-09-10_0a6702319f6a.py
@@ -0,0 +1,31 @@
+"""Adds restriction ability to incident severity
+
+Revision ID: 0a6702319f6a
+Revises: 51eacaf1f62c
+Create Date: 2024-09-10 10:13:04.192475
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+# revision identifiers, used by Alembic.
+revision = "0a6702319f6a"
+down_revision = "51eacaf1f62c"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column(
+ "incident_severity",
+ sa.Column("allowed_for_stable_incidents", sa.Boolean(), server_default="t", nullable=True),
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("incident_severity", "allowed_for_stable_incidents")
+ # ### end Alembic commands ###
diff --git a/src/dispatch/incident/severity/models.py b/src/dispatch/incident/severity/models.py
index 8944a443d06c..94adad99f8ad 100644
--- a/src/dispatch/incident/severity/models.py
+++ b/src/dispatch/incident/severity/models.py
@@ -20,6 +20,7 @@ class IncidentSeverity(Base, ProjectMixin):
color = Column(String)
enabled = Column(Boolean, default=True)
default = Column(Boolean, default=False)
+ allowed_for_stable_incidents = Column(Boolean, default=True, server_default="t")
# This column is used to control how severities should be displayed
# Lower numbers will be shown first.
@@ -46,6 +47,7 @@ class IncidentSeverityBase(DispatchBase):
name: NameStr
project: Optional[ProjectRead]
view_order: Optional[int]
+ allowed_for_stable_incidents: Optional[bool]
class IncidentSeverityCreate(IncidentSeverityBase):
@@ -67,6 +69,7 @@ class IncidentSeverityReadMinimal(DispatchBase):
description: Optional[str] = Field(None, nullable=True)
enabled: Optional[bool]
name: NameStr
+ allowed_for_stable_incidents: Optional[bool]
class IncidentSeverityPagination(Pagination):
diff --git a/src/dispatch/plugins/dispatch_slack/incident/interactive.py b/src/dispatch/plugins/dispatch_slack/incident/interactive.py
index a72d8331ef67..866a316421f5 100644
--- a/src/dispatch/plugins/dispatch_slack/incident/interactive.py
+++ b/src/dispatch/plugins/dispatch_slack/incident/interactive.py
@@ -48,6 +48,7 @@
from dispatch.participant import service as participant_service
from dispatch.participant.models import ParticipantUpdate
from dispatch.participant_role import service as participant_role_service
+from dispatch.incident.severity import service as incident_severity_service
from dispatch.participant_role.enums import ParticipantRoleType
from dispatch.plugin import service as plugin_service
from dispatch.plugins.dispatch_slack import service as dispatch_slack_service
@@ -1011,7 +1012,9 @@ def handle_member_joined_channel(
if participant.added_by:
# Message text when someone @'s a user is not available in body, use generic added by reason
- participant.added_reason = f"Participant added by {participant.added_by.individual.name}"
+ participant.added_reason = (
+ f"Participant added by {participant.added_by.individual.name}"
+ )
else:
# We couldn't find a user to attribute the addition to, add generic reason
participant.added_reason = "Participant added by Dispatch"
@@ -1061,7 +1064,9 @@ def handle_member_joined_channel(
if participant.added_by:
# Message text when someone @'s a user is not available in body, use generic added by reason
- participant.added_reason = f"Participant added by {participant.added_by.individual.name}"
+ participant.added_reason = (
+ f"Participant added by {participant.added_by.individual.name}"
+ )
else:
# We couldn't find a user to attribute the addition to, add generic reason
participant.added_reason = "Participant added by Dispatch"
@@ -1980,6 +1985,20 @@ def handle_update_incident_submission_event(
user: DispatchUser,
) -> None:
"""Handles the update incident submission"""
+ incident_severity_id = form_data[DefaultBlockIds.incident_severity_select]["value"]
+ incident_severity = incident_severity_service.get(
+ db_session=db_session, incident_severity_id=incident_severity_id
+ )
+ status = form_data[DefaultBlockIds.incident_status_select]["name"]
+ if not incident_severity.allowed_for_stable_incidents and (
+ status == IncidentStatus.stable or status == IncidentStatus.closed
+ ):
+ errors = {
+ DefaultBlockIds.incident_severity_select: f"Severity cannot be {incident_severity.name} for {status} incidents"
+ }
+ ack(response_action="errors", errors=errors)
+ return
+
ack_incident_update_submission_event(ack=ack)
incident = incident_service.get(db_session=db_session, incident_id=context["subject"].id)
diff --git a/src/dispatch/static/dispatch/src/case/priority/NewEditSheet.vue b/src/dispatch/static/dispatch/src/case/priority/NewEditSheet.vue
index d7939cca557d..7e76b5289521 100644
--- a/src/dispatch/static/dispatch/src/case/priority/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/case/priority/NewEditSheet.vue
@@ -86,7 +86,7 @@
diff --git a/src/dispatch/static/dispatch/src/case/severity/NewEditSheet.vue b/src/dispatch/static/dispatch/src/case/severity/NewEditSheet.vue
index 764126294af8..58a3481c4d3d 100644
--- a/src/dispatch/static/dispatch/src/case/severity/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/case/severity/NewEditSheet.vue
@@ -79,7 +79,7 @@
diff --git a/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue b/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue
index 1da007fa37c0..bc9b75150613 100644
--- a/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/case/type/NewEditSheet.vue
@@ -130,7 +130,7 @@
diff --git a/src/dispatch/static/dispatch/src/incident/DetailsTab.vue b/src/dispatch/static/dispatch/src/incident/DetailsTab.vue
index 86dd69202c3b..d98c362d30cc 100644
--- a/src/dispatch/static/dispatch/src/incident/DetailsTab.vue
+++ b/src/dispatch/static/dispatch/src/incident/DetailsTab.vue
@@ -62,7 +62,7 @@
-
+
diff --git a/src/dispatch/static/dispatch/src/incident/priority/NewEditSheet.vue b/src/dispatch/static/dispatch/src/incident/priority/NewEditSheet.vue
index 7bc67abb43bc..e397bdc0865b 100644
--- a/src/dispatch/static/dispatch/src/incident/priority/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/incident/priority/NewEditSheet.vue
@@ -112,7 +112,7 @@
diff --git a/src/dispatch/static/dispatch/src/incident/severity/IncidentSeveritySelect.vue b/src/dispatch/static/dispatch/src/incident/severity/IncidentSeveritySelect.vue
index f336f0270516..cedef63a75c7 100644
--- a/src/dispatch/static/dispatch/src/incident/severity/IncidentSeveritySelect.vue
+++ b/src/dispatch/static/dispatch/src/incident/severity/IncidentSeveritySelect.vue
@@ -7,6 +7,7 @@
label="Severity"
return-object
:loading="loading"
+ :error-messages="show_error"
:rules="[is_severity_in_project]"
>
@@ -40,6 +41,10 @@ export default {
type: [Object],
default: null,
},
+ status: {
+ type: String,
+ default: "",
+ },
},
data() {
@@ -64,6 +69,12 @@ export default {
this.validateSeverity()
},
},
+ show_error() {
+ if (this.status != "Active" && this.modelValue?.allowed_for_stable_incidents === false) {
+ return `Severity cannot be ${this.modelValue?.name} for ${this.status} incidents`
+ }
+ return null
+ },
},
methods: {
diff --git a/src/dispatch/static/dispatch/src/incident/severity/NewEditSheet.vue b/src/dispatch/static/dispatch/src/incident/severity/NewEditSheet.vue
index 3c3ae211cdd4..c9aed34e980f 100644
--- a/src/dispatch/static/dispatch/src/incident/severity/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/incident/severity/NewEditSheet.vue
@@ -79,7 +79,14 @@
+
+
+
@@ -126,6 +133,7 @@ export default {
"selected.name",
"selected.project",
"selected.view_order",
+ "selected.allowed_for_stable_incidents",
]),
...mapFields("incident_severity", {
default_incident_severity: "selected.default",
diff --git a/src/dispatch/static/dispatch/src/incident/severity/Table.vue b/src/dispatch/static/dispatch/src/incident/severity/Table.vue
index 11ad3d680cb2..df56e421dd27 100644
--- a/src/dispatch/static/dispatch/src/incident/severity/Table.vue
+++ b/src/dispatch/static/dispatch/src/incident/severity/Table.vue
@@ -47,6 +47,9 @@
+
+
+
@@ -90,6 +93,7 @@ export default {
{ title: "Description", value: "description", sortable: false },
{ title: "Default", value: "default", sortable: true },
{ title: "Enabled", value: "enabled", sortable: true },
+ { title: "Allowed for Stable", value: "allowed_for_stable_incidents", sortable: true },
{ title: "View Order", value: "view_order", sortable: true },
{ title: "", key: "data-table-actions", sortable: false, align: "end" },
],
diff --git a/src/dispatch/static/dispatch/src/incident/type/NewEditSheet.vue b/src/dispatch/static/dispatch/src/incident/type/NewEditSheet.vue
index bd7dfedffd96..35e5cb251b15 100644
--- a/src/dispatch/static/dispatch/src/incident/type/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/incident/type/NewEditSheet.vue
@@ -129,7 +129,7 @@