Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable users to create tasks from Slack #5240

Merged
merged 33 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2b18566
Enable users to create tasks from Slack.
metroid-samus Sep 23, 2024
e5143b1
Adds dispatch-create-task slug to slack conversation plugin config.
metroid-samus Sep 23, 2024
416db6b
Merge branch 'master' into enhancement/create-task-from-slack
metroid-samus Sep 23, 2024
e83b4cb
Fix a condition where default was execting when it shouldn't (#5194)
kevgliss Sep 24, 2024
effff01
Adds description to database upgrade script.
metroid-samus Sep 24, 2024
a32651b
Relabel assignee field in task creation modal.
metroid-samus Sep 24, 2024
6fdd1a8
Creates error modals.
metroid-samus Sep 24, 2024
a3f37de
Fixes lint error.
metroid-samus Sep 24, 2024
2409387
feat(genai): add llm case summary and historical summary to slack plu…
wssheldon Sep 24, 2024
7acb987
ux/fix(slack): move snooze mfa to new mfa, add emoji to case message,…
wssheldon Sep 24, 2024
6680f26
Fixes access to local variable 'thread_conversation_weblink' (#5244)
metroid-samus Sep 25, 2024
8addd8c
Correcting metadata indexing (#5245)
whitdog47 Sep 25, 2024
685063a
Ensure empty details doesn't throw error (#5246)
whitdog47 Sep 26, 2024
199badd
chore(deps-dev): bump faker from 28.4.1 to 30.0.0 (#5250)
dependabot[bot] Sep 26, 2024
30f5c1c
chore(deps-dev): bump ruff from 0.6.5 to 0.6.8 (#5249)
dependabot[bot] Sep 26, 2024
b9fc4b4
chore(deps): bump @tiptap/pm in /src/dispatch/static/dispatch (#5248)
dependabot[bot] Sep 26, 2024
7ac7073
chore(deps): bump @tiptap/starter-kit in /src/dispatch/static/dispatc…
dependabot[bot] Sep 26, 2024
2631dee
chore(deps): bump rollup in /src/dispatch/static/dispatch (#5241)
dependabot[bot] Sep 26, 2024
3f0980e
chore(deps): bump pandas from 2.2.2 to 2.2.3 (#5235)
dependabot[bot] Sep 26, 2024
3135123
chore(deps): bump python-multipart from 0.0.9 to 0.0.10 (#5234)
dependabot[bot] Sep 26, 2024
03b5e12
chore(deps-dev): bump @playwright/test in /src/dispatch/static/dispat…
dependabot[bot] Sep 26, 2024
8b01ef8
chore(deps): bump slack-sdk from 3.33.0 to 3.33.1 (#5226)
dependabot[bot] Sep 26, 2024
aef9427
chore(deps): bump @formkit/vue in /src/dispatch/static/dispatch (#5223)
dependabot[bot] Sep 26, 2024
609568f
chore(deps): bump @formkit/themes in /src/dispatch/static/dispatch (#…
dependabot[bot] Sep 26, 2024
22b0344
Correcting import for datetime (#5261)
whitdog47 Sep 29, 2024
8f255d7
update(ci): version bumps (#5266)
Ant0wan Sep 30, 2024
662510d
After a task is created from the Slack UI, a confirmation message is …
metroid-samus Sep 30, 2024
0d9ebe9
Adds button for resolving/reopening a task.
metroid-samus Sep 30, 2024
1e713d1
Merge branch 'master' into enhancement/create-task-from-slack
metroid-samus Sep 30, 2024
0130ddc
Fixes task message format. Removes initial value for task description.
metroid-samus Oct 1, 2024
19e4af1
Fixes task message format. Removes initial value for task description.
metroid-samus Oct 1, 2024
c1599ea
Fixes typo
metroid-samus Oct 1, 2024
09403f8
Merge branch 'master' into enhancement/create-task-from-slack
metroid-samus Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ You can override their values if you wish to do so. Included below are their des
| `/dispatch-update-incident` | Opens a modal to update the incident. |
| `/dispatch-notifications-group` | Opens a modal to edit the notifications group. |
| `/dispatch-update-participant` | Opens a modal to update participant metadata. |
| `/dispatch-create-task` | Opens a modal to create an incident task. |

### Contact Information Resolver Plugin

Expand Down
12 changes: 11 additions & 1 deletion docs/docs/user-guide/incidents/commander.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ All Slack commands are listed below, or you may view _groups_ of commands relati
- [`/dispatch-update-incident`](#%2Fdispatch-update-incident)
- [`/dispatch-update-participant`](#%2Fdispatch-update-participant)
- [`/dispatch-run-workflow`](#%2Fdispatch-list-workflow)

- [`/dispatch-create-task`](#%2Fdispatch-create-task)
## People

These commands help manage the people helping resolve the incident.
Expand Down Expand Up @@ -205,3 +205,13 @@ This command will run a pre-configured workflow and associate its artifacts with
![](/img/slack-conversation-run-workflow.png)

</div>

### /dispatch-create-task

This command will create a task for the current incident.
<div style={{textAlign: 'center'}}>

{/* TODO(averyl): replace this image */}
![](/img/slack-conversation-create-task.png)

</div>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""empty message
metroid-samus marked this conversation as resolved.
Show resolved Hide resolved

Revision ID: 32652e0360dd
Revises: 1f4dc687945d
Create Date: 2024-09-23 16:02:50.742796

"""

from alembic import op
from pydantic import SecretStr, ValidationError
from pydantic.json import pydantic_encoder

from sqlalchemy import Column, Integer, ForeignKey, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, Session
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy_utils import StringEncryptedType
from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine
from dispatch.config import config, DISPATCH_ENCRYPTION_KEY


# revision identifiers, used by Alembic.
revision = "32652e0360dd"
down_revision = "1f4dc687945d"
branch_labels = None
depends_on = None

Base = declarative_base()


def show_secrets_encoder(obj):
if isinstance(obj, SecretStr):
return obj.get_secret_value()
else:
return pydantic_encoder(obj)


def migrate_config(instances, slug, config):
for instance in instances:
if slug == instance.plugin.slug:
instance.configuration = config


class Plugin(Base):
__tablename__ = "plugin"
__table_args__ = {"schema": "dispatch_core"}
id = Column(Integer, primary_key=True)
slug = Column(String, unique=True)


class PluginInstance(Base):
__tablename__ = "plugin_instance"
id = Column(Integer, primary_key=True)
_configuration = Column(
StringEncryptedType(key=str(DISPATCH_ENCRYPTION_KEY), engine=AesEngine, padding="pkcs5")
)
plugin_id = Column(Integer, ForeignKey(Plugin.id))
plugin = relationship(Plugin, backref="instances")

@hybrid_property
def configuration(self):
"""Property that correctly returns a plugins configuration object."""
pass

@configuration.setter
def configuration(self, configuration):
"""Property that correctly sets a plugins configuration object."""
if configuration:
self._configuration = configuration.json(encoder=show_secrets_encoder)


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
from dispatch.plugins.dispatch_slack.config import SlackConversationConfiguration

bind = op.get_bind()
session = Session(bind=bind)

instances = session.query(PluginInstance).all()

# Slash commands
SLACK_COMMAND_CREATE_TASK_SLUG = config(
"SLACK_COMMAND_CREATE_TASK_SLUG", default="/dispatch-create-task"
)

try:
slack_conversation_config = SlackConversationConfiguration(
slack_command_create_task=SLACK_COMMAND_CREATE_TASK_SLUG,
)

migrate_config(instances, "slack-conversation", slack_conversation_config)

except ValidationError:
print(
"Skipping automatic migration of slack plugin credentials, if you are using the slack plugin manually migrate credentials."
)

session.commit()
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
5 changes: 5 additions & 0 deletions src/dispatch/plugins/dispatch_slack/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,8 @@ class SlackConversationConfiguration(SlackConfiguration):
title="List Workflows Command String",
description="Defines the string used to list all available workflows. Must match what is defined in Slack",
)
slack_command_create_task: str = Field(
"/dispatch-create-task",
title="Create Task Command String",
description="Defines the string used to create a task. Must match what is defined in Slack.",
)
9 changes: 9 additions & 0 deletions src/dispatch/plugins/dispatch_slack/incident/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ class ReportExecutiveBlockIds(DispatchEnum):
next_steps = "report-executive-next-steps"


class CreateTaskBlockIds(DispatchEnum):
participant_select = "create-task-participant-select"
description = "create-task-description"


class CreateTaskActionIds(DispatchEnum):
submit = "create-task-submit"


class IncidentUpdateActions(DispatchEnum):
submit = "incident-update-submit"
project_select = "incident-update-project-select"
Expand Down
107 changes: 106 additions & 1 deletion src/dispatch/plugins/dispatch_slack/incident/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
AddTimelineEventActions,
AssignRoleActions,
AssignRoleBlockIds,
CreateTaskActionIds,
CreateTaskBlockIds,
EngageOncallActionIds,
EngageOncallActions,
EngageOncallBlockIds,
Expand Down Expand Up @@ -134,7 +136,7 @@
from dispatch.tag import service as tag_service
from dispatch.task import service as task_service
from dispatch.task.enums import TaskStatus
from dispatch.task.models import Task
from dispatch.task.models import Task, TaskCreate
from dispatch.ticket import flows as ticket_flows
from dispatch.messaging.strings import reminder_select_values
from dispatch.plugins.dispatch_slack.messaging import build_unexpected_error_message
Expand Down Expand Up @@ -217,6 +219,7 @@ def configure(config):
app.command(config.slack_command_add_timeline_event, middleware=middleware)(
handle_add_timeline_event_command
)
app.command(config.slack_command_create_task, middleware=middleware)(handle_create_task_command)

app.event(
event="reaction_added",
Expand Down Expand Up @@ -1514,6 +1517,108 @@ def handle_assign_role_submission_event(
)


def handle_create_task_command(
ack: Ack,
body,
dict,
client: WebClient,
context: BoltContext,
db_session: Session,
) -> None:
"""Displays a modal for task creation."""
ack()
if context["subject"].type == CaseSubjects.case:
raise CommandError("Command is not currently available for cases.")
metroid-samus marked this conversation as resolved.
Show resolved Hide resolved

participants = participant_service.get_all_by_incident_id(
db_session=db_session, incident_id=context["subject"].id
)

incident = incident_service.get(db_session=db_session, incident_id=context["subject"].id)

contact_plugin = plugin_service.get_active_instance(
db_session=db_session, project_id=incident.project.id, plugin_type="contact"
)
if not contact_plugin:
raise CommandError(
"Contact plugin is not enabled. Unable to list participants.",
)

active_participants = [p for p in participants if p.active_roles]
participant_list = []

for participant in active_participants:
participant_email = participant.individual.email
participant_info = contact_plugin.instance.get(participant_email, db_session=db_session)
participant_name = participant_info.get("fullname", participant.individual.email)
participant_list.append({"text": participant_name, "value": participant_email})

blocks = [
static_select_block(
label="Participant",
metroid-samus marked this conversation as resolved.
Show resolved Hide resolved
block_id=CreateTaskBlockIds.participant_select,
placeholder="Select Participant",
options=participant_list,
),
Input(
label="Task Description",
element=PlainTextInput(
placeholder="Task description", initial_value="description", multiline=True
),
block_id=CreateTaskBlockIds.description,
),
]

modal = Modal(
title="Create Task",
blocks=blocks,
submit="Create",
close="Close",
callback_id=CreateTaskActionIds.submit,
private_metadata=context["subject"].json(),
).build()
client.views_open(trigger_id=body["trigger_id"], view=modal)
metroid-samus marked this conversation as resolved.
Show resolved Hide resolved


def ack_create_task_submission_event(ack: Ack) -> None:
"""Handles task creation acknowledgment."""
modal = Modal(
title="Create Task", close="Close", blocks=[Section(text="Creating task...")]
).build()
ack(response_action="update", view=modal)


@app.view(
CreateTaskActionIds.submit,
middleware=[action_context_middleware, db_middleware, user_middleware, modal_submit_middleware],
)
def handle_create_task_submission_event(
ack: Ack,
body: dict,
client: WebClient,
context: BoltContext,
db_session: Session,
form_data: dict,
user: DispatchUser,
) -> None:
"""Handles the create task submission."""
ack()

participant_email = form_data.get(CreateTaskBlockIds.participant_select).get("value", "")
owner = participant_service.get_by_incident_id_and_email(
db_session=db_session, incident_id=context["subject"].id, email=participant_email
)
incident = incident_service.get(db_session=db_session, incident_id=context["subject"].id)

task_in = TaskCreate(
assignees=[ParticipantUpdate.from_orm(owner)],
creator={"individual": {"email": user.email}},
description=form_data.get(CreateTaskBlockIds.description, ""),
incident=IncidentRead.from_orm(incident),
)
task_service.create(db_session=db_session, task_in=task_in)
metroid-samus marked this conversation as resolved.
Show resolved Hide resolved


def handle_engage_oncall_command(
ack: Ack,
body: dict,
Expand Down
5 changes: 5 additions & 0 deletions src/dispatch/plugins/dispatch_slack/messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
:copyright: (c) 2019 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
"""

import logging
from typing import Any, List, Optional

Expand Down Expand Up @@ -129,6 +130,10 @@ def get_incident_conversation_command_message(
"response_type": "ephemeral",
"text": "Fetching the list of workflows...",
},
config.slack_command_create_task: {
"response_type": "ephemeral",
"text": "Opening a dialog to create a new incident task...",
},
}

return command_messages.get(command_string, default)
Expand Down
Loading