Skip to content

Commit

Permalink
Implement Slack view submission interactions
Browse files Browse the repository at this point in the history
- Parse the view_submission interaction type into a Pydantic model (this
  model needs more work because it'll need to contain a copy of the
  view, but it'd be easy to base that model off the Block Kit models
  that we'll eventually put in Safir)
- Add a topic configuration for view submissions
- Add a publisher for view submissions, including models for the Kafka
  key and topic.
  • Loading branch information
jonathansick committed Sep 25, 2024
1 parent c208c4e commit b2cceae
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 9 deletions.
86 changes: 83 additions & 3 deletions client/src/rubin/squarebot/models/kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
SlackMessageEvent,
SlackMessageSubtype,
SlackMessageType,
SlackViewSubmissionPayload,
)

__all__ = [
"SquarebotSlackAppMentionValue",
"SquarebotSlackBlockActionsKey",
"SquarebotSlackBlockActionsValue",
"SquarebotSlackViewSubmissionKey",
"SquarebotSlackViewSubmissionValue",
"SquarebotSlackMessageKey",
"SquarebotSlackMessageValue",
]
Expand Down Expand Up @@ -283,7 +286,9 @@ def to_key_bytes(self) -> bytes:


class SquarebotSlackBlockActionsValue(SlackBlockActionsPayload):
"""Kafka message value model for Slack block actions sent by Squarebot."""
"""Kafka message value model for Slack block actions interactions sent by
Squarebot.
"""

slack_interaction: str = Field(
..., description="The original Slack block actions JSON string."
Expand All @@ -298,12 +303,87 @@ def from_block_actions(
Parameters
----------
payload
The Slack block actions payload.
The Slack block action payload.
raw
The raw Slack block actions JSON.
Returns
-------
value
The Squarebot block actions message value.
"""
return cls(
**payload.model_dump(),
slack_interaction=json.dumps(raw),
)


class SquarebotSlackViewSubmissionKey(BaseModel):
"""Kafka message key model for Slack view submissions sent by Squarebot."""

user_id: str = Field(
..., description="The Slack user ID that triggered the action."
)

team: str = Field(..., description="The Slack team ID.")

@classmethod
def from_view_submission(cls, payload: SlackViewSubmissionPayload) -> Self:
"""Create a Kafka key for a Slack view submission from a payload.
Parameters
----------
payload
The Slack view_submission payload.
Returns
-------
key
The Squarebot view submission message key key.
"""
return cls(
user_id=payload.user.id,
team=payload.team.id,
)

def to_key_bytes(self) -> bytes:
"""Serialize the key to bytes for use as a Kafka key.
Returns
-------
bytes
The serialized key.
"""
key_str = f"{self.user_id}:{self.team}"
return key_str.encode("utf-8")


class SquarebotSlackViewSubmissionValue(SlackViewSubmissionPayload):
"""Kafka message value model for Slack view_submission events sent by
Squarebot.
"""

slack_interaction: str = Field(
..., description="The original Slack view_submission JSON string."
)

@classmethod
def from_view_submission(
cls, payload: SlackViewSubmissionPayload, raw: dict[str, Any]
) -> Self:
"""Create a Kafka value for a Slack view submission from a payload.
Parameters
----------
payload
The Slack view submission payload.
raw
The raw Slack view submission JSON.
Returns
-------
value
The Squarebot block actions value.
The Squarebot view_submission message value.
"""
return cls(
**payload.model_dump(),
Expand Down
36 changes: 34 additions & 2 deletions client/src/rubin/squarebot/models/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from pydantic import BaseModel, Field

__all__ = [
"SlackPlainTextObject",
"SlackMrkdwnTextObject",
"BaseSlackEvent",
"SlackBlockActionBase",
"SlackBlockActionsMessage",
Expand All @@ -25,11 +23,14 @@
"SlackMessageEventContent",
"SlackMessageSubtype",
"SlackMessageType",
"SlackMrkdwnTextObject",
"SlackPlainTextObject",
"SlackStaticSelectAction",
"SlackStaticSelectActionSelectedOption",
"SlackTeam",
"SlackUrlVerificationEvent",
"SlackUser",
"SlackViewSubmissionPayload",
]


Expand Down Expand Up @@ -537,3 +538,34 @@ class SlackBlockActionsPayload(BaseModel):
actions: list[SlackStaticSelectAction] = Field(
description="The actions that were triggered."
)


class SlackViewSubmissionPayload(BaseModel):
"""A model for a Slack view submission payload.
This isn't yet a full model for a view submission payload.
See https://api.slack.com/reference/interaction-payloads/views#view_submission
"""

type: Literal["view_submission"] = Field(
description="Interaction payload type."
)

team: SlackTeam = Field(description="Information about the Slack team.")

user: SlackUser = Field(
description=(
"Information about the user that triggered the interaction."
)
)

api_app_id: str = Field(
description=(
"The unique identifier of your installed Slack application. Use "
"this to distinguish which app the event belongs to if you use "
"multiple apps with the same Request URL."
)
)

view: dict = Field(description="The view that was submitted.")
10 changes: 10 additions & 0 deletions server/src/squarebot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ class Configuration(BaseSettings):
),
)

view_submission_topic: str = Field(
"squarebot.interaction.view_submission",
title="View Submission interaction Kafka topic",
alias="SQUAREBOT_TOPIC_VIEW_SUBMISSION",
description=(
"Kafka topic name for `interaction` Slack events of type "
"`view_submission`."
),
)

# Slack signing secret
slack_signing_secret: SecretStr = Field(
title="Slack signing secret", alias="SQUAREBOT_SLACK_SIGNING"
Expand Down
8 changes: 8 additions & 0 deletions server/src/squarebot/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class ProcessContext:
block_actions_publisher: Publisher
"""A Kafka publisher for the Slack block actions topic."""

view_submission_publisher: Publisher
"""A Kafka publisher for the Slack view submissions topic."""

@classmethod
async def create(cls) -> Self:
broker = kafka_router.broker
Expand Down Expand Up @@ -68,6 +71,10 @@ async def create(cls) -> Self:
config.block_actions_topic,
description="Slack block actions.",
),
view_submission_publisher=broker.publisher(
config.view_submission_topic,
description="Slack view submission.",
),
)

async def aclose(self) -> None:
Expand Down Expand Up @@ -97,4 +104,5 @@ def create_slack_service(self) -> SlackService:
mpim_publisher=self._process_context.mpim_publisher,
groups_publisher=self._process_context.groups_publisher,
block_actions_publisher=self._process_context.block_actions_publisher,
view_submission_publisher=self._process_context.view_submission_publisher,
)
31 changes: 27 additions & 4 deletions server/src/squarebot/services/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
SquarebotSlackBlockActionsValue,
SquarebotSlackMessageKey,
SquarebotSlackMessageValue,
SquarebotSlackViewSubmissionKey,
SquarebotSlackViewSubmissionValue,
)
from rubin.squarebot.models.slack import (
SlackBlockActionsPayload,
SlackChannelType,
SlackMessageEvent,
SlackMessageType,
SlackViewSubmissionPayload,
)

from ..config import Configuration
Expand All @@ -43,6 +46,7 @@ def __init__(
im_publisher: Publisher,
mpim_publisher: Publisher,
block_actions_publisher: Publisher,
view_submission_publisher: Publisher,
) -> None:
self._logger = logger
self._config = config
Expand All @@ -52,6 +56,7 @@ def __init__(
self._im_publisher = im_publisher
self._mpim_publisher = mpim_publisher
self._block_actions_publisher = block_actions_publisher
self._view_submission_publisher = view_submission_publisher

@staticmethod
def compute_slack_signature(
Expand Down Expand Up @@ -318,8 +323,6 @@ async def publish_interaction(
block_action = SlackBlockActionsPayload.model_validate(
interaction_payload
)
# Temporary placeholder; will serialize and publish to Kafka
# in reality.
self._logger.debug(
"Got a Slack interaction",
type=block_action.type,
Expand All @@ -341,6 +344,26 @@ async def publish_interaction(
key=key.to_key_bytes(),
headers={"content-type": "application/json"},
)
elif (
"type" in interaction_payload
and interaction_payload["type"] == "view_submission"
):
payload = SlackViewSubmissionPayload.model_validate(
interaction_payload
)
await self._view_submission_publisher.publish(
message=SquarebotSlackViewSubmissionValue.from_view_submission(
payload, interaction_payload
),
key=SquarebotSlackViewSubmissionKey.from_view_submission(
payload
).to_key_bytes(),
headers={"content-type": "application/json"},
)

self._logger.debug("Got a Slack view submission")
else:
self._logger.debug("Did not parse Slack interaction")
print(interaction_payload) # noqa: T201
self._logger.debug(
"Did not parse Slack interaction",
raw_interaction=interaction_payload,
)

0 comments on commit b2cceae

Please sign in to comment.