diff --git a/src/sentry/integrations/slack/message_builder/prompt.py b/src/sentry/integrations/slack/message_builder/prompt.py index d7436dc21373c..17f44d30221da 100644 --- a/src/sentry/integrations/slack/message_builder/prompt.py +++ b/src/sentry/integrations/slack/message_builder/prompt.py @@ -4,19 +4,30 @@ from .base.block import BlockSlackMessageBuilder -LINK_IDENTITY_MESSAGE = "Link your Slack identity to Sentry to unfurl Discover charts." +EPHEMERAL_LINK_IDENTITY_MESSAGE = "Link your Slack identity to Sentry to unfurl Discover charts." +NON_EPHEMERAL_LINK_IDENTITY_MESSAGE = "You sent an unfurlable link in <#{}>. Please link your Slack identity to Sentry to unfurl Discover charts." class SlackPromptLinkMessageBuilder(BlockSlackMessageBuilder): - def __init__(self, url: str) -> None: + def __init__(self, url: str, ephemeral: bool = True, channel_name: str | None = None) -> None: super().__init__() self.url = url + # message_channel is the channel the user sent the unfurl link in + # if passed, we will add this additional context to the message + self.ephemeral = ephemeral + self.channel_name = channel_name def build(self) -> SlackBody: + message = ( + EPHEMERAL_LINK_IDENTITY_MESSAGE + if self.ephemeral + else NON_EPHEMERAL_LINK_IDENTITY_MESSAGE.format(self.channel_name) + ) + return { "blocks": orjson.dumps( [ - self.get_markdown_block(LINK_IDENTITY_MESSAGE), + self.get_markdown_block(message), self.get_action_block([("Link", self.url, "link"), ("Cancel", None, "ignore")]), ], ).decode() diff --git a/src/sentry/integrations/slack/webhooks/event.py b/src/sentry/integrations/slack/webhooks/event.py index 74c927403a2dd..e6d7606b305c8 100644 --- a/src/sentry/integrations/slack/webhooks/event.py +++ b/src/sentry/integrations/slack/webhooks/event.py @@ -24,8 +24,10 @@ from sentry.integrations.slack.requests.base import SlackDMRequest, SlackRequestError from sentry.integrations.slack.requests.event import COMMANDS, SlackEventRequest from sentry.integrations.slack.sdk_client import SlackSdkClient +from sentry.integrations.slack.service import SlackService from sentry.integrations.slack.unfurl.handlers import link_handlers, match_link from sentry.integrations.slack.unfurl.types import LinkType, UnfurlableUrl +from sentry.integrations.slack.utils.errors import CHANNEL_NOT_FOUND, unpack_slack_api_error from sentry.integrations.slack.views.link_identity import build_linking_url from sentry.organizations.services.organization import organization_service from sentry.utils import metrics @@ -116,7 +118,25 @@ def prompt_link(self, slack_request: SlackDMRequest) -> None: text=payload["text"], **SlackPromptLinkMessageBuilder(associate_url).as_payload(), ) - except SlackApiError: + except SlackApiError as e: + # If the channel is not found, it could be because the bot doesn't have access to the channel to send an ephemeral message + # In this case, we should send the message to the user in the bot <> user DM + if unpack_slack_api_error(e) == CHANNEL_NOT_FOUND: + service = SlackService.default() + service.send_message_to_slack_channel( + integration_id=slack_request.integration.id, + payload={ + "text": payload["text"], + **SlackPromptLinkMessageBuilder( + associate_url, ephemeral=False, channel_name=slack_request.channel_id + ).as_payload(), + # by passing the user_id as the channel, we send the message to the user in the bot <> user DM + # https://api.slack.com/methods/chat.postMessage#dm + "channel": slack_request.user_id, + }, + log_error_message="prompt_link.post-message-error", + log_params=logger_params, + ) _logger.exception("prompt_link.post-ephemeral-error", extra=logger_params) metrics.incr(self._METRIC_FAILURE_KEY + ".prompt_link.post_ephemeral", sample_rate=1.0)