# What this PR does Semi-related to https://github.com/grafana/oncall-private/issues/2131 Addresses occasional task failures for `apps.slack.tasks.update_slack_user_group_for_schedules` when trying to update a Slack user group for a non-paid Slack account. [Slack's documentation](https://slack.com/help/articles/212906697-Create-a-user-group) mentions this is a paid only feature, hence the error ([logs](https://ops.grafana-ops.net/goto/-AWfsrrIR?orgId=1) from an actual task): ``` 2024-08-08 16:20:36,613 source=engine:celery worker=ForkPoolWorker-16 task_id=6bdaae94-1552-4b6d-93e2-e2fa0bae57b1 task_name=apps.slack.tasks.update_slack_user_group_for_schedules name=apps.slack.models.slack_usergroup level=WARNING Slack usergroup S06LW5GJ88Z update failed: Slack API error! Response: {'ok': False, 'error': 'paid_teams_only'} ``` Updated our docs on our Slack integration to emphasize that this feature _only_ works for paid Slack accounts ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
127 lines
3.3 KiB
Python
127 lines
3.3 KiB
Python
import typing
|
|
|
|
from slack_sdk.web import SlackResponse
|
|
|
|
from apps.slack.constants import SLACK_RATE_LIMIT_DELAY
|
|
|
|
|
|
class UnexpectedResponse(typing.TypedDict):
|
|
status: int
|
|
headers: dict[str, typing.Any]
|
|
body: str
|
|
|
|
|
|
class SlackAPIError(Exception):
|
|
"""
|
|
Base class for Slack API errors. To add a new error class, add a new subclass of SlackAPIError in this file.
|
|
See get_error_class at the end of this file for more details on how these are raised.
|
|
"""
|
|
|
|
errors: tuple[str, ...]
|
|
|
|
def __init__(self, response: UnexpectedResponse | SlackResponse):
|
|
super().__init__(f"Slack API error! Response: {response}")
|
|
self.response = response
|
|
|
|
|
|
class SlackAPIServerError(SlackAPIError):
|
|
errors = ("internal_error", "fatal_error")
|
|
|
|
|
|
class SlackAPITokenError(SlackAPIError):
|
|
errors = ("account_inactive", "token_revoked")
|
|
|
|
|
|
class SlackAPIChannelArchivedError(SlackAPIError):
|
|
errors = ("is_archived",)
|
|
|
|
|
|
class SlackAPIRatelimitError(SlackAPIError):
|
|
errors = ("ratelimited", "rate_limited", "message_limit_exceeded")
|
|
|
|
def __init__(self, response: SlackResponse):
|
|
super().__init__(response)
|
|
self.retry_after = int(response.headers.get("Retry-After", SLACK_RATE_LIMIT_DELAY))
|
|
|
|
|
|
class SlackAPIPlanUpgradeRequiredError(SlackAPIError):
|
|
errors = ("plan_upgrade_required",)
|
|
|
|
|
|
class SlackAPIInvalidAuthError(SlackAPIError):
|
|
errors = ("invalid_auth",)
|
|
|
|
|
|
class SlackAPIUsergroupNotFoundError(SlackAPIError):
|
|
errors = ("no_such_subteam", "subteam_not_found")
|
|
|
|
|
|
class SlackAPIUsergroupPaidTeamOnlyError(SlackAPIError):
|
|
"""
|
|
https://api.slack.com/methods/usergroups.create#:~:text=Name%20too%20long.-,paid_teams_only,-Usergroups%20can%20only
|
|
https://slack.com/help/articles/212906697-Create-a-user-group
|
|
"""
|
|
|
|
errors = ("paid_teams_only",)
|
|
|
|
|
|
class SlackAPIInvalidUsersError(SlackAPIError):
|
|
errors = ("invalid_users",)
|
|
|
|
|
|
class SlackAPIChannelNotFoundError(SlackAPIError):
|
|
errors = ("channel_not_found",)
|
|
|
|
|
|
class SlackAPIMessageNotFoundError(SlackAPIError):
|
|
errors = ("message_not_found",)
|
|
|
|
|
|
class SlackAPICantUpdateMessageError(SlackAPIError):
|
|
errors = ("cant_update_message",)
|
|
|
|
|
|
class SlackAPIUserNotFoundError(SlackAPIError):
|
|
errors = ("user_not_found",)
|
|
|
|
|
|
class SlackAPIChannelInactiveError(SlackAPIError):
|
|
errors = ("is_inactive",)
|
|
|
|
|
|
class SlackAPIRestrictedActionError(SlackAPIError):
|
|
errors = ("restricted_action",)
|
|
|
|
|
|
class SlackAPIPermissionDeniedError(SlackAPIError):
|
|
errors = ("permission_denied",)
|
|
|
|
|
|
class SlackAPIFetchMembersFailedError(SlackAPIError):
|
|
errors = ("fetch_members_failed",)
|
|
|
|
|
|
class SlackAPIViewNotFoundError(SlackAPIError):
|
|
errors = ("not_found",)
|
|
|
|
|
|
class SlackAPICannotDMBotError(SlackAPIError):
|
|
errors = ("cannot_dm_bot",)
|
|
|
|
|
|
class SlackAPIMethodNotSupportedForChannelTypeError(SlackAPIError):
|
|
errors = ("method_not_supported_for_channel_type",)
|
|
|
|
|
|
_error_to_error_class = {
|
|
error: error_class for error_class in SlackAPIError.__subclasses__() for error in error_class.errors
|
|
}
|
|
|
|
|
|
def get_error_class(response: UnexpectedResponse | SlackResponse) -> typing.Type[SlackAPIError]:
|
|
"""Get an appropriate error class for the response"""
|
|
|
|
if isinstance(response, dict): # UnexpectedResponse
|
|
return SlackAPIServerError
|
|
|
|
return _error_to_error_class.get(response["error"], SlackAPIError)
|