Skip to content

Commit

Permalink
Merge pull request #29 from lsst-sqre/tickets/DM-45383
Browse files Browse the repository at this point in the history
DM-45383: Add bot_message support for Slack messages
  • Loading branch information
jonathansick authored Jul 25, 2024
2 parents 549311b + 21b1303 commit fb639f6
Show file tree
Hide file tree
Showing 15 changed files with 746 additions and 259 deletions.
44 changes: 20 additions & 24 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: CI

env:
PYTHON_VERSION: "3.12"

"on":
merge_group: {}
pull_request: {}
Expand Down Expand Up @@ -30,38 +33,32 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: ${{ env.PYTHON_VERSION }}

- name: Run pre-commit
uses: pre-commit/[email protected].0
uses: pre-commit/[email protected].1

test:
runs-on: ubuntu-latest
timeout-minutes: 5

strategy:
matrix:
python:
- "3.12"
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}

- name: Install nox
run: |
pip install --upgrade pip
pip install --upgrade nox
python-version: ${{ env.PYTHON_VERSION }}

- name: Start Kafka
run: docker compose -f kafka-compose.yaml up -d

- name: Run nox
run: "nox -s typing test"
uses: lsst-sqre/run-nox@v1
with:
cache-dependency: "*/pyproject.toml"
nox-sessions: "typing test"
python-version: ${{ env.PYTHON_VERSION }}

- name: Stop Kafka
run: docker compose -f kafka-compose.yaml down
Expand All @@ -85,15 +82,14 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install nox
run: |
pip install --upgrade pip
pip install --upgrade nox
python-version: ${{ env.PYTHON_VERSION }}

- name: Run nox
run: "nox -s docs"
uses: lsst-sqre/run-nox@v1
with:
cache-dependency: "*/pyproject.toml"
nox-sessions: "docs"
python-version: ${{ env.PYTHON_VERSION }}

# Only attempt documentation uploads for long-lived branches, tagged
# releases, and pull requests from ticket branches. This avoids version
Expand Down Expand Up @@ -150,7 +146,7 @@ jobs:
- name: Build and publish
uses: lsst-sqre/build-and-publish-to-pypi@v2
with:
python-version: "3.12"
python-version: ${{ env.PYTHON_VERSION }}
upload: "false"
working-directory: "client"

Expand All @@ -172,5 +168,5 @@ jobs:
- name: Build and publish
uses: lsst-sqre/build-and-publish-to-pypi@v2
with:
python-version: "3.12"
python-version: ${{ env.PYTHON_VERSION }}
working-directory: "client"
61 changes: 61 additions & 0 deletions .github/workflows/periodic-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This is a separate run of the Python test suite that runs from a schedule,
# doesn't cache the nox environment, and updates pinned dependencies first.
# The purpose is to test compatibility with the latest versions of
# dependencies.

name: Periodic CI

env:
# Current supported Python version. For applications, there is generally no
# reason to support multiple Python versions, so all actions are run with
# this version. Quote the version to avoid interpretation as a floating
# point number.
PYTHON_VERSION: "3.12"

"on":
schedule:
- cron: "0 12 * * 1"
workflow_dispatch: {}

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Update dependencies
run: |
pip install --upgrade uv
uv venv
source .venv/bin/activate
make update-deps
shell: bash

- name: Start Kafka
run: docker compose -f kafka-compose.yaml up -d

- name: Run tests in nox
uses: lsst-sqre/run-nox@v1
with:
python-version: ${{ env.PYTHON_VERSION }}
nox-sessions: "lint typing test"

- name: Stop Kafka
run: docker compose -f kafka-compose.yaml down

- name: Report status
if: failure()
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: "failure"
notification_title: "Periodic test for {repo} failed"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ALERT_WEBHOOK }}
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- id: check-toml

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.1
rev: v0.5.5
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,32 @@

<!-- scriv-insert-here -->

<a id='changelog-0.9.0'></a>

## 0.9.0 (2024-07-25)

### Backwards-incompatible changes

- `SquarebotSlackMessageValue` no longer includes a `bot_id` field. Instead, the `user` field is set to the bot's ID and a new `is_bot` field indicates if the `user` field is a bot or user ID.

### New features

- Add support for Slack messages with `subtype` of `bot_message`. This is implemented in the Slack message model `SlackMessageEventContent` as a new `subtype` field that now takes a `SlackMessageSubtype` enum. The Kafka representation of this message, `SquarebotSlackMessageValue` has been updated to now set the `user` attribute to either the user's ID or the bot's ID as appropriate. A new flag, `is_bot` indicates if the `user` field reflects a user or bot (app integration) identifier.

- `SlackMessageEventContent`, a part of the `SlackkMessageEvent` model, now has support for `attachments`. Slack attachments are deprecated for Block Kit, but are still widely used by app integrations. The `attachments` field is a list of `SlackMessageAttachment` objects. These attachments can have text content or individual fields that have titles and values.

- The `SquarebotSlackMessageValue` Kafka message model's `text` field now includes the combined content of the message and its attachments. This makes it easy for consumers to process message content without needing to check for attachments and Block Kit fields. For consumers that _are_ sensitive to the exact structure of the message, the `slack_event` field is still available, which is the original JSON representation of the Slack message, which can be parsed into a `SlackMessageEvent` object.

### Other changes

- Adopt `ruff-shared.toml` from https://github.com/lsst/templates

- The noxfile now uses `uv` as the backend for pip and pip-compile tasks.

- Added a new weekly GitHub Actions CI workflow to validate that the app will run with future versions of dependencies.

<a id='changelog-0.8.0'></a>

## 0.8.0 (2024-02-28)

### Backwards-incompatible changes
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# - Runs a non-root user.
# - Sets up the entrypoint and port.

FROM python:3.12.2-slim-bullseye as base-image
FROM python:3.12.3-slim-bullseye as base-image

# Update system packages
COPY scripts/install-base-packages.sh .
Expand Down
54 changes: 43 additions & 11 deletions client/src/rubin/squarebot/models/kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

from pydantic import BaseModel, Field

from .slack import SlackChannelType, SlackMessageEvent, SlackMessageType
from .slack import (
SlackChannelType,
SlackMessageEvent,
SlackMessageSubtype,
SlackMessageType,
)

__all__ = [
"SquarebotSlackMessageKey",
Expand Down Expand Up @@ -64,7 +69,11 @@ class SquarebotSlackMessageValue(BaseModel):
)

user: str = Field(
..., description="The ID of the user that sent the message."
...,
description=(
"The ID of the user or bot that sent the message. See the "
"is_bot field to determine if the user is a bot."
),
)

ts: str = Field(
Expand All @@ -90,12 +99,9 @@ class SquarebotSlackMessageValue(BaseModel):
..., description="The original Slack event JSON string."
)

bot_id: str | None = Field(
None,
description=(
"The unique identifier of the bot user that sent the message. "
"This field is only present if the message was sent by a bot."
),
is_bot: bool = Field(
False,
description="Flag that is true if `user` is a bot user ID.",
)

@classmethod
Expand All @@ -119,16 +125,37 @@ def from_event(cls, event: SlackMessageEvent, raw: dict[str, Any]) -> Self:
"Cannot create a SquarebotSlackMessageValue from a Slack "
"event that lacks a channel_type. Is this an app_mention?"
)

if (
event.event.subtype is not None
and event.event.subtype == SlackMessageSubtype.bot_message
):
is_bot = True
if event.event.bot_id is None:
raise ValueError(
"Cannot create a SquarebotSlackMessageValue from a Slack "
"bot_message event that lacks a bot_id."
)
user_id = event.event.bot_id
else:
is_bot = False
if event.event.user is None:
raise ValueError(
"Cannot create a SquarebotSlackMessageValue from a Slack "
"message event that lacks a user."
)
user_id = event.event.user

return cls(
type=event.event.type,
channel=event.event.channel,
channel_type=event.event.channel_type,
user=event.event.user,
user=user_id,
ts=event.event.ts,
thread_ts=event.event.thread_ts,
text=event.event.text,
text=event.event.combined_text_content,
slack_event=json.dumps(raw),
bot_id=event.event.bot_id,
is_bot=is_bot,
)


Expand Down Expand Up @@ -182,6 +209,11 @@ def from_event(cls, event: SlackMessageEvent, raw: dict[str, Any]) -> Self:
value
The Squarebot message value.
"""
if event.event.user is None:
raise ValueError(
"Cannot create a SquarebotSlackAppMentionValue from a Slack "
"app_mention event that lacks a user. Is this a bot message?"
)
return cls(
type=event.event.type,
channel=event.event.channel,
Expand Down
Loading

0 comments on commit fb639f6

Please sign in to comment.