-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add incbot command handlers #134
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
Draft
spalmurray
wants to merge
31
commits into
main
Choose a base branch
from
spalmurray/RELENG-517
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
b7e8bea
Add slack-bolt dependency
spalmurray 8d977c2
Add signing_secret to Slack config
spalmurray 331a0a5
Add slack_app Django app with Bolt wiring and /inc help command
spalmurray b431648
Add Datadog metrics instrumentation for slash commands
spalmurray d697a72
Add signing_secret to CI config and fix ruff lint
spalmurray f5de053
Add test values for slack bot
spalmurray 750b4f6
tweaks
spalmurray 31537fe
Rename slack app in help command
spalmurray da4e50a
Remove HTTP-based slack event handling in favor of Socket Mode
spalmurray b80b922
Add app_token config for Slack Socket Mode
spalmurray 52435ac
Add run_slack_bot management command and Docker entrypoint
spalmurray e8c5542
Add slack bot deploy step to GitHub Actions
spalmurray 5a49b4e
Mock Slack auth_test in tests instead of disabling token verification
spalmurray 31a2fef
Add health check server for Cloud Run TCP startup probe
spalmurray 5da2f14
Fix TZ warnings in date filters and tests
spalmurray c363495
Remove unused signing_secret from config
spalmurray 315ee4b
Add prod slack app deploy workflow
spalmurray 93ee7d8
Change /inc to /ft (/testinc -> /ft-test)
spalmurray 2a89131
Scope Slack auth_test patch and normalize metric tags
cursoragent a1e8bdf
Add firetower_base_url config setting
spalmurray c07dddb
Add SlackService methods for channel management
spalmurray 3a1c517
Add incident lifecycle hooks and wire into serializer
spalmurray b05807b
Add /inc new command with modal for creating incidents
spalmurray 3270b58
Tweaks
spalmurray 6c530f3
Fix topic overflow
spalmurray bbc0d56
Add get_incident_from_channel helper and on_title_changed hook
spalmurray 576c10f
Add mitigated, resolved, reopen, severity, and subject command handlers
spalmurray 3f7cf97
Wire new command handlers into bolt.py routing and update help text
spalmurray 68c2b60
Add tests for channel command handlers
spalmurray f6c6cb1
Address warden feedaback
spalmurray 1d8dc3c
Handle validation errors and edge cases in command handlers
spalmurray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| import logging | ||
|
|
||
| from django.conf import settings | ||
| from django.contrib.auth.models import User | ||
|
|
||
| from firetower.auth.models import ExternalProfileType | ||
| from firetower.incidents.models import ExternalLink, ExternalLinkType, Incident | ||
| from firetower.integrations.services import SlackService | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
| _slack_service = SlackService() | ||
|
|
||
|
|
||
| def _build_channel_name(incident: Incident) -> str: | ||
| return incident.incident_number.lower() | ||
|
|
||
|
|
||
| SLACK_TOPIC_MAX_LENGTH = 250 | ||
|
|
||
|
|
||
| def _build_channel_topic(incident: Incident) -> str: | ||
| captain_name = "" | ||
| if incident.captain: | ||
| captain_name = incident.captain.get_full_name() or incident.captain.username | ||
| prefix = f"[{incident.severity}] {incident.incident_number} " | ||
| suffix = f" | IC: @{captain_name}" | ||
| max_title_len = max(SLACK_TOPIC_MAX_LENGTH - len(prefix) - len(suffix), 0) | ||
| title = incident.title | ||
| if len(title) > max_title_len: | ||
| title = title[: max_title_len - 1] + "\u2026" if max_title_len > 0 else "" | ||
| topic = f"{prefix}{title}{suffix}" | ||
| return topic[:SLACK_TOPIC_MAX_LENGTH] | ||
|
|
||
|
|
||
| def _get_channel_id(incident: Incident) -> str | None: | ||
| slack_link = incident.external_links.filter(type=ExternalLinkType.SLACK).first() | ||
| if not slack_link: | ||
| return None | ||
| return _slack_service.parse_channel_id_from_url(slack_link.url) | ||
|
|
||
|
|
||
| def _invite_user_to_channel(channel_id: str, user: User) -> None: | ||
| try: | ||
| slack_profile = user.external_profiles.filter( | ||
| type=ExternalProfileType.SLACK | ||
| ).first() | ||
| if slack_profile: | ||
| _slack_service.invite_to_channel(channel_id, [slack_profile.external_id]) | ||
| except Exception: | ||
| logger.exception(f"Failed to invite user {user.id} to channel {channel_id}") | ||
|
|
||
|
|
||
| def on_incident_created(incident: Incident) -> None: | ||
| try: | ||
| existing_slack_link = incident.external_links.filter( | ||
| type=ExternalLinkType.SLACK | ||
| ).exists() | ||
| if existing_slack_link: | ||
| logger.info( | ||
| f"Incident {incident.id} already has a Slack link, skipping channel creation" | ||
| ) | ||
| return | ||
|
|
||
| channel_id = _slack_service.create_channel(_build_channel_name(incident)) | ||
| if not channel_id: | ||
| logger.warning(f"Failed to create Slack channel for incident {incident.id}") | ||
| return | ||
|
|
||
| channel_url = _slack_service.build_channel_url(channel_id) | ||
| ExternalLink.objects.create( | ||
| incident=incident, | ||
| type=ExternalLinkType.SLACK, | ||
| url=channel_url, | ||
| ) | ||
|
|
||
| _slack_service.set_channel_topic(channel_id, _build_channel_topic(incident)) | ||
|
|
||
| base_url = settings.FIRETOWER_BASE_URL | ||
| incident_url = f"{base_url}/incidents/{incident.incident_number}" | ||
| _slack_service.add_bookmark(channel_id, "Firetower Incident", incident_url) | ||
|
|
||
| _slack_service.post_message( | ||
| channel_id, | ||
| f"*{incident.incident_number}: {incident.title}*\n" | ||
| f"Severity: {incident.severity} | Status: {incident.status}", | ||
| ) | ||
|
|
||
| if incident.captain: | ||
| _invite_user_to_channel(channel_id, incident.captain) | ||
|
|
||
| # TODO: Datadog notebook creation step will be added in RELENG-467 | ||
| except Exception: | ||
| logger.exception(f"Error in on_incident_created for incident {incident.id}") | ||
|
|
||
|
|
||
| def on_status_changed(incident: Incident, old_status: str) -> None: | ||
| try: | ||
| channel_id = _get_channel_id(incident) | ||
| if not channel_id: | ||
| return | ||
|
|
||
| _slack_service.post_message( | ||
| channel_id, | ||
| f"Status changed: {old_status} -> {incident.status}", | ||
| ) | ||
| _slack_service.set_channel_topic(channel_id, _build_channel_topic(incident)) | ||
| except Exception: | ||
| logger.exception(f"Error in on_status_changed for incident {incident.id}") | ||
|
|
||
|
|
||
| def on_severity_changed(incident: Incident, old_severity: str) -> None: | ||
| try: | ||
| channel_id = _get_channel_id(incident) | ||
| if not channel_id: | ||
| return | ||
|
|
||
| _slack_service.post_message( | ||
| channel_id, | ||
| f"Severity changed: {old_severity} -> {incident.severity}", | ||
| ) | ||
| _slack_service.set_channel_topic(channel_id, _build_channel_topic(incident)) | ||
| except Exception: | ||
| logger.exception(f"Error in on_severity_changed for incident {incident.id}") | ||
|
|
||
|
|
||
| def on_title_changed(incident: Incident, old_title: str) -> None: | ||
| try: | ||
| channel_id = _get_channel_id(incident) | ||
| if not channel_id: | ||
| return | ||
|
|
||
| _slack_service.post_message( | ||
| channel_id, | ||
| f"Title changed: {old_title} -> {incident.title}", | ||
| ) | ||
| _slack_service.set_channel_topic(channel_id, _build_channel_topic(incident)) | ||
| except Exception: | ||
| logger.exception(f"Error in on_title_changed for incident {incident.id}") | ||
|
Check warning on line 138 in src/firetower/incidents/hooks.py
|
||
|
|
||
|
|
||
| def on_captain_changed(incident: Incident) -> None: | ||
| try: | ||
| channel_id = _get_channel_id(incident) | ||
| if not channel_id: | ||
| return | ||
|
|
||
| _slack_service.set_channel_topic(channel_id, _build_channel_topic(incident)) | ||
|
|
||
| captain_name = "" | ||
| if incident.captain: | ||
| captain_name = incident.captain.get_full_name() or incident.captain.username | ||
| _slack_service.post_message( | ||
| channel_id, | ||
| f"Incident captain changed to @{captain_name}", | ||
| ) | ||
|
|
||
| if incident.captain: | ||
| _invite_user_to_channel(channel_id, incident.captain) | ||
| except Exception: | ||
| logger.exception(f"Error in on_captain_changed for incident {incident.id}") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New on_title_changed hook lacks unit tests
The new
on_title_changedfunction follows the same pattern ason_status_changedandon_severity_changed, which both have dedicated test classes intest_hooks.py. However,on_title_changedis not imported in the test file and has no corresponding tests. While it's mocked intest_channel_commands.py, this only verifies callers don't crash—it doesn't test the hook's actual behavior (posting messages, updating topics, handling missing Slack links).Identified by Warden [code-review] · HUE-HPX