Skip to content

Commit

Permalink
Adds custom description to new incident Slack channels (#4830)
Browse files Browse the repository at this point in the history
  • Loading branch information
whitdog47 authored Jun 18, 2024
1 parent a9b7aed commit 55671bd
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 1 deletion.
66 changes: 65 additions & 1 deletion src/dispatch/conversation/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from dispatch.plugin import service as plugin_service
from dispatch.storage.models import Storage
from dispatch.ticket.models import Ticket
from dispatch.service.models import Service
from dispatch.project.models import Project
from dispatch.utils import deslug_and_capitalize_resource_type
from dispatch.types import Subject

Expand Down Expand Up @@ -215,7 +217,7 @@ def get_topic_text(subject: Subject) -> str:
)


def set_conversation_topic(subject: Subject, db_session: SessionLocal):
def set_conversation_topic(subject: Subject, db_session: Session) -> None:
"""Sets the conversation topic."""
if not subject.conversation:
log.warning("Conversation topic not set. No conversation available for this incident/case.")
Expand All @@ -242,6 +244,68 @@ def set_conversation_topic(subject: Subject, db_session: SessionLocal):
log.exception(e)


def get_current_oncall_email(project: Project, service: Service, db_session: Session) -> str | None:
"""Notifies oncall about completed form"""
oncall_plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=project.id, plugin_type="oncall"
)
if not oncall_plugin:
log.debug("Unable to send email since oncall plugin is not active.")
else:
return oncall_plugin.instance.get(service.external_id)


def get_description_text(subject: Subject, db_session: Session) -> str | None:
"""Returns the description details based on the subject"""
if not isinstance(subject, Incident):
return

incident_type = subject.incident_type
if not incident_type.channel_description:
return

description_service = incident_type.description_service
if description_service:
oncall_email = get_current_oncall_email(
project=subject.project,
service=description_service,
db_session=db_session
)
if oncall_email:
return incident_type.channel_description.replace("{oncall_email}", oncall_email)

return incident_type.channel_description


def set_conversation_description(subject: Subject, db_session: Session) -> None:
"""Sets the conversation description."""
if not subject.conversation:
log.warning("Conversation topic not set. No conversation available for this incident/case.")
return

plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=subject.project.id, plugin_type="conversation"
)
if not plugin:
log.warning("Conversation topic not set. No conversation plugin enabled.")
return

conversation_description = get_description_text(subject, db_session)
if not conversation_description:
return

try:
plugin.instance.set_description(subject.conversation.channel_id, conversation_description)
except Exception as e:
event_service.log_subject_event(
subject=subject,
db_session=db_session,
source="Dispatch Core App",
description=f"Setting the incident/case conversation description failed. Reason: {e}",
)
log.exception(e)


def add_conversation_bookmark(
db_session: Session,
subject: Subject,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Create new channel description and description service id columns for incident type
Revision ID: 4286dcce0a2d
Revises: a836d4850a75
Create Date: 2024-06-12 17:45:25.556120
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "4286dcce0a2d"
down_revision = "a836d4850a75"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("incident_type", sa.Column("channel_description", sa.String(), nullable=True))
op.add_column("incident_type", sa.Column("description_service_id", sa.Integer(), nullable=True))
op.create_foreign_key("description_service_id_fkey", "incident_type", "service", ["description_service_id"], ["id"])
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint("description_service_id_fkey", "incident_type", type_="foreignkey")
op.drop_column("incident_type", "description_service_id")
op.drop_column("incident_type", "channel_description")
# ### end Alembic commands ###
4 changes: 4 additions & 0 deletions src/dispatch/incident/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ def incident_create_resources(
# we set the conversation topic
conversation_flows.set_conversation_topic(incident, db_session)

# and set the conversation description
if incident.incident_type.channel_description is not None:
conversation_flows.set_conversation_description(incident, db_session)

# we set the conversation bookmarks
bookmarks = [
# resource, title
Expand Down
9 changes: 9 additions & 0 deletions src/dispatch/incident/type/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from dispatch.models import DispatchBase, ProjectMixin, Pagination
from dispatch.plugin.models import PluginMetadata
from dispatch.project.models import ProjectRead
from dispatch.service.models import ServiceRead


class IncidentType(ProjectMixin, Base):
Expand Down Expand Up @@ -64,6 +65,12 @@ class IncidentType(ProjectMixin, Base):
foreign_keys=[cost_model_id],
)

# Sets the channel description for the incidents of this type
channel_description = Column(String, nullable=True)
# Optionally add on-call name to the channel description
description_service_id = Column(Integer, ForeignKey("service.id"))
description_service = relationship("Service", foreign_keys=[description_service_id])

@hybrid_method
def get_meta(self, slug):
if not self.plugin_metadata:
Expand Down Expand Up @@ -101,6 +108,8 @@ class IncidentTypeBase(DispatchBase):
project: Optional[ProjectRead]
plugin_metadata: List[PluginMetadata] = []
cost_model: Optional[CostModelRead] = None
channel_description: Optional[str] = Field(None, nullable=True)
description_service: Optional[ServiceRead]

@validator("plugin_metadata", pre=True)
def replace_none_with_empty_list(cls, value):
Expand Down
17 changes: 17 additions & 0 deletions src/dispatch/incident/type/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from dispatch.document import service as document_service
from dispatch.exceptions import NotFoundError
from dispatch.project import service as project_service
from dispatch.service import service as service_service

from .models import IncidentType, IncidentTypeCreate, IncidentTypeRead, IncidentTypeUpdate

Expand Down Expand Up @@ -138,6 +139,7 @@ def create(*, db_session, incident_type_in: IncidentTypeCreate) -> IncidentType:
"review_template_document",
"cost_model",
"project",
"description_service",
}
),
project=project,
Expand Down Expand Up @@ -173,6 +175,13 @@ def create(*, db_session, incident_type_in: IncidentTypeCreate) -> IncidentType:
)
incident_type.tracking_template_document = tracking_template_document

if incident_type_in.description_service:
service = service_service.get(
db_session=db_session, service_id=incident_type_in.description_service.id
)
if service:
incident_type.description_service_id = service.id

db_session.add(incident_type)
db_session.commit()
return incident_type
Expand Down Expand Up @@ -226,6 +235,13 @@ def update(
)
incident_type.tracking_template_document = tracking_template_document

if incident_type_in.description_service:
service = service_service.get(
db_session=db_session, service_id=incident_type_in.description_service.id
)
if service:
incident_type.description_service_id = service.id

incident_type_data = incident_type.dict()

update_data = incident_type_in.dict(
Expand All @@ -236,6 +252,7 @@ def update(
"tracking_template_document",
"review_template_document",
"cost_model",
"description_service",
},
)

Expand Down
1 change: 1 addition & 0 deletions src/dispatch/plugins/dispatch_slack/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class SlackAPIPostEndpoints(DispatchEnum):
conversations_invite = "conversations.invite"
conversations_rename = "conversations.rename"
conversations_set_topic = "conversations.setTopic"
conversations_set_purpose = "conversations.setPurpose"
conversations_unarchive = "conversations.unarchive"
pins_add = "pins.add"

Expand Down
6 changes: 6 additions & 0 deletions src/dispatch/plugins/dispatch_slack/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
send_ephemeral_message,
send_message,
set_conversation_topic,
set_conversation_description,
unarchive_conversation,
update_message,
)
Expand Down Expand Up @@ -324,6 +325,11 @@ def set_topic(self, conversation_id: str, topic: str):
client = create_slack_client(self.configuration)
return set_conversation_topic(client, conversation_id, topic)

def set_description(self, conversation_id: str, description: str):
"""Sets the conversation description."""
client = create_slack_client(self.configuration)
return set_conversation_description(client, conversation_id, description)

def add_bookmark(self, conversation_id: str, weblink: str, title: str):
"""Adds a bookmark to the conversation."""
client = create_slack_client(self.configuration)
Expand Down
7 changes: 7 additions & 0 deletions src/dispatch/plugins/dispatch_slack/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ def set_conversation_topic(client: WebClient, conversation_id: str, topic: str)
)


def set_conversation_description(client: WebClient, conversation_id: str, description: str) -> SlackResponse:
"""Sets the topic of the specified conversation."""
return make_call(
client, SlackAPIPostEndpoints.conversations_set_purpose, channel=conversation_id, purpose=description
)


def add_conversation_bookmark(
client: WebClient, conversation_id: str, weblink: str, title: str
) -> SlackResponse:
Expand Down
23 changes: 23 additions & 0 deletions src/dispatch/static/dispatch/src/incident/type/NewEditSheet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,25 @@
hint="Determines whether this incident type is availible for new incidents."
/>
</v-col>
<v-col cols="12">
<v-textarea
v-model="channel_description"
label="Channel description"
hint="Set this as the description for new incident channels."
clearable
/>
</v-col>
<v-col cols="12">
<span class="text-body-1 text-medium-emphasis mt-2">
Use {oncall_email} in the description to replace with this oncall email
(optional).
</span>
<service-select
label="Oncall Service"
:project="project"
v-model="description_service"
/>
</v-col>
<v-col cols="12">
<plugin-metadata-input v-model="plugin_metadata" :project="project" />
</v-col>
Expand All @@ -152,6 +171,7 @@ import { mapFields } from "vuex-map-fields"
import CostModelCombobox from "@/cost_model/CostModelCombobox.vue"
import PluginMetadataInput from "@/plugin/PluginMetadataInput.vue"
import TemplateSelect from "@/document/template/TemplateSelect.vue"
import ServiceSelect from "@/service/ServiceSelect.vue"
export default {
setup() {
Expand All @@ -165,6 +185,7 @@ export default {
CostModelCombobox,
PluginMetadataInput,
TemplateSelect,
ServiceSelect,
},
data() {
Expand Down Expand Up @@ -194,6 +215,8 @@ export default {
"selected.cost_model",
"selected.exclude_from_metrics",
"selected.default",
"selected.channel_description",
"selected.description_service",
]),
...mapFields("incident_type", {
default_incident_type: "selected.default",
Expand Down

0 comments on commit 55671bd

Please sign in to comment.