Skip to content

Commit 21ae0a2

Browse files
authored
Initial work on socket mode (Netflix#795)
* Initial work on socket mode * Fixes * fixing linting * Adding docs
1 parent 72f2869 commit 21ae0a2

20 files changed

Lines changed: 454 additions & 262 deletions

File tree

‎docs/configuration/plugins/configuring-slack.md‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,40 @@ description: Configuration options for the Slack plugin.
77
{% hint style="info" %}
88
Dispatch ships with support for Slack. Below, is how to configure the Slack plugin to work with `Dispatch`. This plugin is required for core functionality.
99

10-
The plugin relies on the [Events API](https://api.slack.com/events-api) to receive events for the event types the app is subscribed to. `Dispatch` receives these events at the `/api/v1/events/slack/event` API endpoint. Slack will also send Web requests to the `/api/v1/events/slack/command` and `/api/v1/events/slack/action` API endpoints for slash \(`/`\) commands and interactive components like dialogs and modals. These endpoints must be publicly available in order for the `Dispatch` Slack app to work correctly.
11-
{% endhint %}
10+
Dispatch supports both the [Event Mode](https://api.slack.com/events-api) and [Socket Mode](https://api.slack.com/apis/connections/socket).
11+
12+
Which mode should you choose?
13+
14+
- If you have Dispatch deployed behind a firewall use **socket mode**
15+
- If you are deploying Dispatch publically use **event mode**
16+
17+
## Events Mode
18+
19+
To enable event mode:
20+
21+
- Ensure that the following configuration items are set:
22+
- `SLACK_SIGNING_SECRET`
23+
- `SLACK_API_BOT_TOKEN`
24+
25+
Additional, event mode requires specific url mapping that must be **publically** available:
26+
27+
- `Dispatch` receives general events at the `/api/v1/events/slack/event` endpoint (reactions).
28+
- `Dispatch` receives command events at the `/api/v1/events/slack/command` endpoint (`/dispatch-*` commands)
29+
- `Dispatch` recieves action events at the `/api/v1/events/slack/action` (dialogs and modals) endpoint.
30+
31+
## Socket Mode
32+
33+
To enable socket mode:
34+
35+
- Ensure the following configuration items are set:
36+
- `SLACK_SOCKET_MODE_APP_TOKEN`
37+
38+
Socket mode does not require the mapping of endpoints and these values are ingored in socket mode.
1239

1340
## Slack Configuration
1441

42+
Both `socket mode` or `event mode` you will need set the following configuration items within slack itself.
43+
1544
### Event Subscriptions
1645

1746
To enable Dispatch to process Slack events, ensure your bot is subscribed to the following events:
@@ -90,6 +119,10 @@ groups:read
90119

91120
> Secret used to verify signatures included on each HTTP request that Slack sends. Slack refers to this as your "Signing Secret."
92121
122+
#### `SLACK_SOCKET_MODE_APP_TOKEN` \[Required. Secret: True\]
123+
124+
> Socket specific app token, used to authenticate to the websocket api.
125+
93126
#### `SLACK_APP_USER_SLUG` \[Required\]
94127

95128
> Specifies the Slack app's bot user id, so Dispatch can filter events generated by the app's user \(e.g. message posted in the channel\). You can use the [auth.test](https://api.slack.com/methods/auth.test/test) endpoint and the app's bot token \(`xoxb-*`\) to get the bot user id \(`UXXXXXXXX`\)."

‎requirements-base.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ schedule
3434
sentry-asgi
3535
sentry-sdk
3636
sh
37-
slackclient
37+
slack_sdk
3838
spacy
3939
sqlalchemy
4040
sqlalchemy-filters

‎src/dispatch/alembic/versions/72c7fa0ce0a3_.py‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,25 @@
1010

1111

1212
# revision identifiers, used by Alembic.
13-
revision = '72c7fa0ce0a3'
14-
down_revision = '466823f3046e'
13+
revision = "72c7fa0ce0a3"
14+
down_revision = "466823f3046e"
1515
branch_labels = None
1616
depends_on = None
1717

1818

1919
def upgrade():
2020
# ### commands auto generated by Alembic - please adjust! ###
21-
op.add_column('workflow', sa.Column('plugin_id', sa.Integer(), nullable=True))
22-
op.create_foreign_key(None, 'workflow', 'plugin', ['plugin_id'], ['id'])
23-
op.drop_column('workflow', 'plugin_slug')
21+
op.add_column("workflow", sa.Column("plugin_id", sa.Integer(), nullable=True))
22+
op.create_foreign_key(None, "workflow", "plugin", ["plugin_id"], ["id"])
23+
op.drop_column("workflow", "plugin_slug")
2424
# ### end Alembic commands ###
2525

2626

2727
def downgrade():
2828
# ### commands auto generated by Alembic - please adjust! ###
29-
op.add_column('workflow', sa.Column('plugin_slug', sa.VARCHAR(), autoincrement=False, nullable=True))
30-
op.drop_constraint(None, 'workflow', type_='foreignkey')
31-
op.drop_column('workflow', 'plugin_id')
29+
op.add_column(
30+
"workflow", sa.Column("plugin_slug", sa.VARCHAR(), autoincrement=False, nullable=True)
31+
)
32+
op.drop_constraint(None, "workflow", type_="foreignkey")
33+
op.drop_column("workflow", "plugin_id")
3234
# ### end Alembic commands ###

‎src/dispatch/alembic/versions/99f3dec93615_.py‎

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,57 +10,94 @@
1010

1111

1212
# revision identifiers, used by Alembic.
13-
revision = '99f3dec93615'
14-
down_revision = '9f927a8e4679'
13+
revision = "99f3dec93615"
14+
down_revision = "9f927a8e4679"
1515
branch_labels = None
1616
depends_on = None
1717

1818

1919
def upgrade():
2020
# ### commands auto generated by Alembic - please adjust! ###
21-
op.create_table('workflow_incident_priority',
22-
sa.Column('incident_priority_id', sa.Integer(), nullable=False),
23-
sa.Column('workflow_id', sa.Integer(), nullable=False),
24-
sa.ForeignKeyConstraint(['incident_priority_id'], ['incident_priority.id'], ),
25-
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ),
26-
sa.PrimaryKeyConstraint('incident_priority_id', 'workflow_id')
21+
op.create_table(
22+
"workflow_incident_priority",
23+
sa.Column("incident_priority_id", sa.Integer(), nullable=False),
24+
sa.Column("workflow_id", sa.Integer(), nullable=False),
25+
sa.ForeignKeyConstraint(
26+
["incident_priority_id"],
27+
["incident_priority.id"],
28+
),
29+
sa.ForeignKeyConstraint(
30+
["workflow_id"],
31+
["workflow.id"],
32+
),
33+
sa.PrimaryKeyConstraint("incident_priority_id", "workflow_id"),
2734
)
28-
op.create_table('workflow_incident_type',
29-
sa.Column('incident_type_id', sa.Integer(), nullable=False),
30-
sa.Column('workflow_id', sa.Integer(), nullable=False),
31-
sa.ForeignKeyConstraint(['incident_type_id'], ['incident_type.id'], ),
32-
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ),
33-
sa.PrimaryKeyConstraint('incident_type_id', 'workflow_id')
35+
op.create_table(
36+
"workflow_incident_type",
37+
sa.Column("incident_type_id", sa.Integer(), nullable=False),
38+
sa.Column("workflow_id", sa.Integer(), nullable=False),
39+
sa.ForeignKeyConstraint(
40+
["incident_type_id"],
41+
["incident_type.id"],
42+
),
43+
sa.ForeignKeyConstraint(
44+
["workflow_id"],
45+
["workflow.id"],
46+
),
47+
sa.PrimaryKeyConstraint("incident_type_id", "workflow_id"),
3448
)
35-
op.create_table('workflow_term',
36-
sa.Column('term_id', sa.Integer(), nullable=False),
37-
sa.Column('workflow_id', sa.Integer(), nullable=False),
38-
sa.ForeignKeyConstraint(['term_id'], ['term.id'], ),
39-
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ),
40-
sa.PrimaryKeyConstraint('term_id', 'workflow_id')
49+
op.create_table(
50+
"workflow_term",
51+
sa.Column("term_id", sa.Integer(), nullable=False),
52+
sa.Column("workflow_id", sa.Integer(), nullable=False),
53+
sa.ForeignKeyConstraint(
54+
["term_id"],
55+
["term.id"],
56+
),
57+
sa.ForeignKeyConstraint(
58+
["workflow_id"],
59+
["workflow.id"],
60+
),
61+
sa.PrimaryKeyConstraint("term_id", "workflow_id"),
4162
)
42-
op.create_table('workflow_instance_artifact',
43-
sa.Column('document_id', sa.Integer(), nullable=False),
44-
sa.Column('workflow_instance_id', sa.Integer(), nullable=False),
45-
sa.ForeignKeyConstraint(['document_id'], ['document.id'], ),
46-
sa.ForeignKeyConstraint(['workflow_instance_id'], ['workflow_instance.id'], ),
47-
sa.PrimaryKeyConstraint('document_id', 'workflow_instance_id')
63+
op.create_table(
64+
"workflow_instance_artifact",
65+
sa.Column("document_id", sa.Integer(), nullable=False),
66+
sa.Column("workflow_instance_id", sa.Integer(), nullable=False),
67+
sa.ForeignKeyConstraint(
68+
["document_id"],
69+
["document.id"],
70+
),
71+
sa.ForeignKeyConstraint(
72+
["workflow_instance_id"],
73+
["workflow_instance.id"],
74+
),
75+
sa.PrimaryKeyConstraint("document_id", "workflow_instance_id"),
4876
)
49-
op.drop_table('workflow_instance_artifacts')
77+
op.drop_table("workflow_instance_artifacts")
5078
# ### end Alembic commands ###
5179

5280

5381
def downgrade():
5482
# ### commands auto generated by Alembic - please adjust! ###
55-
op.create_table('workflow_instance_artifacts',
56-
sa.Column('document_id', sa.INTEGER(), autoincrement=False, nullable=False),
57-
sa.Column('workflow_instance_id', sa.INTEGER(), autoincrement=False, nullable=False),
58-
sa.ForeignKeyConstraint(['document_id'], ['document.id'], name='workflow_instance_artifacts_document_id_fkey'),
59-
sa.ForeignKeyConstraint(['workflow_instance_id'], ['workflow_instance.id'], name='workflow_instance_artifacts_workflow_instance_id_fkey'),
60-
sa.PrimaryKeyConstraint('document_id', 'workflow_instance_id', name='workflow_instance_artifacts_pkey')
83+
op.create_table(
84+
"workflow_instance_artifacts",
85+
sa.Column("document_id", sa.INTEGER(), autoincrement=False, nullable=False),
86+
sa.Column("workflow_instance_id", sa.INTEGER(), autoincrement=False, nullable=False),
87+
sa.ForeignKeyConstraint(
88+
["document_id"], ["document.id"], name="workflow_instance_artifacts_document_id_fkey"
89+
),
90+
sa.ForeignKeyConstraint(
91+
["workflow_instance_id"],
92+
["workflow_instance.id"],
93+
name="workflow_instance_artifacts_workflow_instance_id_fkey",
94+
),
95+
sa.PrimaryKeyConstraint(
96+
"document_id", "workflow_instance_id", name="workflow_instance_artifacts_pkey"
97+
),
6198
)
62-
op.drop_table('workflow_instance_artifact')
63-
op.drop_table('workflow_term')
64-
op.drop_table('workflow_incident_type')
65-
op.drop_table('workflow_incident_priority')
99+
op.drop_table("workflow_instance_artifact")
100+
op.drop_table("workflow_term")
101+
op.drop_table("workflow_incident_type")
102+
op.drop_table("workflow_incident_priority")
66103
# ### end Alembic commands ###

‎src/dispatch/alembic/versions/9f927a8e4679_.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@
1010

1111

1212
# revision identifiers, used by Alembic.
13-
revision = '9f927a8e4679'
14-
down_revision = '995a59897e01'
13+
revision = "9f927a8e4679"
14+
down_revision = "995a59897e01"
1515
branch_labels = None
1616
depends_on = None
1717

1818

1919
def upgrade():
2020
# ### commands auto generated by Alembic - please adjust! ###
21-
op.add_column('workflow_instance', sa.Column('parameters', sa.JSON(), nullable=True))
21+
op.add_column("workflow_instance", sa.Column("parameters", sa.JSON(), nullable=True))
2222
# ### end Alembic commands ###
2323

2424

2525
def downgrade():
2626
# ### commands auto generated by Alembic - please adjust! ###
27-
op.drop_column('workflow_instance', 'parameters')
27+
op.drop_column("workflow_instance", "parameters")
2828
# ### end Alembic commands ###

‎src/dispatch/cli.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import click
66
import uvicorn
7+
import asyncio
78
from alembic import command as alembic_command
89
from alembic.config import Config as AlembicConfig
910
from tabulate import tabulate
@@ -18,6 +19,8 @@
1819
from .scheduler import scheduler
1920
from .logging import configure_logging
2021

22+
from dispatch.plugins.dispatch_slack import socket_mode
23+
2124
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
2225

2326
log = logging.getLogger(__name__)
@@ -585,6 +588,12 @@ def run_server(log_level):
585588
dispatch_server.add_command(uvicorn.main, name="start")
586589

587590

591+
@dispatch_server.command("slack")
592+
def run_slack_websocket():
593+
"""Runs the slack websocket process."""
594+
asyncio.run(socket_mode.run_websocket_process())
595+
596+
588597
@dispatch_server.command("shell")
589598
@click.argument("ipython_args", nargs=-1, type=click.UNPROCESSED)
590599
def shell(ipython_args):

‎src/dispatch/conversation/service.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ def get_by_channel_id_ignoring_channel_type(db_session, channel_id: str) -> Opti
3333
# The channel type has changed. We update the channel id in the database
3434
conversation_in = ConversationUpdate(channel_id=channel_id)
3535
update(
36-
db_session=db_session, conversation=conversation, conversation_in=conversation_in,
36+
db_session=db_session,
37+
conversation=conversation,
38+
conversation_in=conversation_in,
3739
)
3840

3941
event_service.log(

‎src/dispatch/main.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -
135135
# we print all the registered API routes to the console
136136
table = []
137137
for r in api_router.routes:
138-
auth = any(d.dependency.__name__ == "get_current_user" for d in r.dependencies) # TODO this is fragile
138+
auth = any(
139+
d.dependency.__name__ == "get_current_user" for d in r.dependencies
140+
) # TODO this is fragile
139141
table.append([r.path, auth, ",".join(r.methods)])
140142

141143
log.debug("Available Endpoints \n" + tabulate(table, headers=["Path", "Authenticated", "Methods"]))

‎src/dispatch/metrics.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class Metrics(object):
1212

1313
def __init__(self):
1414
if not METRIC_PROVIDERS:
15-
log.info("No metric providers defined via METRIC_PROVIDERS env var. Metrics will not be sent.")
15+
log.info(
16+
"No metric providers defined via METRIC_PROVIDERS env var. Metrics will not be sent."
17+
)
1618
else:
1719
self._providers = METRIC_PROVIDERS
1820

‎src/dispatch/notification/views.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def update_notification(
7373
*,
7474
db_session: Session = Depends(get_db),
7575
notification_id: int,
76-
notification_in: NotificationUpdate
76+
notification_in: NotificationUpdate,
7777
):
7878
"""
7979
Update a notification by id.

0 commit comments

Comments
 (0)