Skip to content

fix(np): Adds attachment to Slack render type, updates metric renderer#112312

Merged
GabeVillalobos merged 10 commits intomasterfrom
sl_render_attach
Apr 7, 2026
Merged

fix(np): Adds attachment to Slack render type, updates metric renderer#112312
GabeVillalobos merged 10 commits intomasterfrom
sl_render_attach

Conversation

@GabeVillalobos
Copy link
Copy Markdown
Member

Updates the SlackRenderable type to include attachments as a renderable type, which allows for some legacy functionality, like colored borders on blocks. This is currently needed for feature parity with our metric alerting, and can be used by the default renderer in the future if we so choose.

This PR also adds the metric alert template to our debug UI, since the rendering has been simplified significantly.

@GabeVillalobos GabeVillalobos requested review from a team as code owners April 6, 2026 22:58
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 6, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Direct dict access on NotRequired field causes KeyError
    • Updated Slack integration send_notification to use payload.get("attachments", None) so missing optional attachments no longer raises KeyError.

Create PR

Or push these changes by commenting:

@cursor push a00fda5534
Preview (a00fda5534)
diff --git a/src/sentry/integrations/slack/integration.py b/src/sentry/integrations/slack/integration.py
--- a/src/sentry/integrations/slack/integration.py
+++ b/src/sentry/integrations/slack/integration.py
@@ -117,7 +117,7 @@
                 channel=target.resource_id,
                 blocks=payload["blocks"],
                 text=payload["text"],
-                attachments=payload["attachments"],
+                attachments=payload.get("attachments", None),
                 unfurl_links=False,
                 unfurl_media=False,
             )

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread src/sentry/integrations/slack/integration.py Outdated
Comment thread src/sentry/integrations/slack/integration.py Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Backend Test Failures

Failures on 960b866 in this run:

tests/sentry/integrations/slack/test_integration.py::SlackIntegrationNotificationPlatformTest::test_send_notification_successlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/slack/test_integration.py:543: in test_send_notification_success
    mock_chat_post.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Mock Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'test', 'type': 'mrkdwn'}, 'type': 'section'}>, <slack_sdk.ActionsBlock: {'elements': [{'text': {'emoji': True, 'text': 'Visit Sentry', 'type': 'plain_text'}, 'type': 'button', 'url': 'https://www.sentry.io'}], 'type': 'actions'}>, <slack_sdk.ImageBlock: {'alt_text': 'Bufo Pog', 'image_url': 'https://raw.githubusercontent.com/knobiknows/all-the-bufo/main/all-the-bufo/bufo-pog.png', 'type': 'image'}>, <slack_sdk.ContextBlock: {'elements': [{'text': 'This is a mock footer', 'type': 'mrkdwn'}], 'type': 'context'}>], text='Mock Notification')
E     Actual: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Mock Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'test', 'type': 'mrkdwn'}, 'type': 'section'}>, <slack_sdk.ActionsBlock: {'elements': [{'text': {'emoji': True, 'text': 'Visit Sentry', 'type': 'plain_text'}, 'type': 'button', 'url': 'https://www.sentry.io'}], 'type': 'actions'}>, <slack_sdk.ImageBlock: {'alt_text': 'Bufo Pog', 'image_url': 'https://raw.githubusercontent.com/knobiknows/all-the-bufo/main/all-the-bufo/bufo-pog.png', 'type': 'image'}>, <slack_sdk.ContextBlock: {'elements': [{'text': 'This is a mock footer', 'type': 'mrkdwn'}], 'type': 'context'}>], text='Mock Notification', attachments=None, unfurl_links=False, unfurl_media=False)
tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py::TestSlackMetricAlertHandlerSendAlert::test_send_alert_via_np_sends_to_slack_channellog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py:109: in test_send_alert_via_np_sends_to_slack_channel
    assert len(blocks) >= 1
E   assert 0 >= 1
E    +  where 0 = len([])
tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py::SlackMetricAlertRendererTest::test_render_includes_image_block_when_chart_url_setlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py:125: in test_render_includes_image_block_when_chart_url_set
    assert len(blocks) == 2
E   assert 0 == 2
E    +  where 0 = len([])
tests/sentry/notifications/platform/slack/test_provider.py::SlackNotificationProviderSendTest::test_send_successlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/test_provider.py:150: in test_send_success
    mock_client_instance.chat_postMessage.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification')
E     Actual: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification', attachments=None, unfurl_links=False, unfurl_media=False)
tests/sentry/notifications/platform/slack/test_provider.py::SlackNotificationProviderSendTest::test_send_to_direct_messagelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/test_provider.py:187: in test_send_to_direct_message
    mock_client_instance.chat_postMessage.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postMessage(channel='U1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification')
E     Actual: chat_postMessage(channel='U1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification', attachments=None, unfurl_links=False, unfurl_media=False)
tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py::SlackMetricAlertRendererTest::test_render_produces_blockslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py:100: in test_render_produces_blocks
    assert len(blocks) == 1
E   assert 0 == 1
E    +  where 0 = len([])
tests/sentry/notifications/platform/slack/test_provider.py::SlackNotificationProviderThreadingTest::test_send_with_thread_context_existing_threadlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/test_provider.py:276: in test_send_with_thread_context_existing_thread
    mock_client_instance.chat_postMessage.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification', thread_ts='1111111111.111111')
E     Actual: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification', attachments=None, unfurl_links=False, unfurl_media=False, thread_ts='1111111111.111111')
tests/sentry/notifications/platform/slack/test_provider.py::SlackNotificationProviderThreadingTest::test_send_with_thread_context_no_existing_threadlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/test_provider.py:348: in test_send_with_thread_context_no_existing_thread
    mock_client_instance.chat_postMessage.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification')
E     Actual: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification', attachments=None, unfurl_links=False, unfurl_media=False)
tests/sentry/notifications/platform/slack/test_provider.py::SlackNotificationProviderThreadingTest::test_send_with_thread_context_reply_broadcastlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/test_provider.py:313: in test_send_with_thread_context_reply_broadcast
    mock_client_instance.chat_postMessage.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification', thread_ts='1111111111.111111', reply_broadcast=True)
E     Actual: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Test Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'This is a test message', 'type': 'mrkdwn'}, 'type': 'section'}>], text='Test Notification', attachments=None, unfurl_links=False, unfurl_media=False, thread_ts='1111111111.111111', reply_broadcast=True)
tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py::SlackMetricAlertRendererTest::test_render_without_chart_urllog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/platform/slack/renderers/test_metric_alert.py:139: in test_render_without_chart_url
    assert len(blocks) == 1
E   assert 0 == 1
E    +  where 0 = len([])

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Backend Test Failures

Failures on 8d0ca99 in this run:

tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py::TestSlackMetricAlertHandlerSendAlert::test_send_alert_via_np_sends_to_slack_channellog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py:109: in test_send_alert_via_np_sends_to_slack_channel
    assert len(blocks) >= 1
E   assert 0 >= 1
E    +  where 0 = len([])

serialized_blocks.append(block.to_dict())

return {"blocks": serialized_blocks}
return {"blocks": serialized_blocks, "attachments": message.get("attachments", None)}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: .get()'s default value is already none

Comment thread src/sentry/notifications/platform/slack/renderers/metric_alert.py Outdated
Comment thread src/sentry/integrations/slack/integration.py
Comment thread src/sentry/integrations/slack/integration.py Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Backend Test Failures

Failures on 997e254 in this run:

tests/sentry/integrations/slack/test_integration.py::SlackIntegrationNotificationPlatformTest::test_send_threaded_ephemeral_message_successlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/slack/test_integration.py:599: in test_send_threaded_ephemeral_message_success
    mock_chat_ephemeral.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postEphemeral(channel='C1234567890', thread_ts='1712345678.987654', user='U9876543210', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Mock Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'test', 'type': 'mrkdwn'}, 'type': 'section'}>, <slack_sdk.ActionsBlock: {'elements': [{'text': {'emoji': True, 'text': 'Visit Sentry', 'type': 'plain_text'}, 'type': 'button', 'url': 'https://www.sentry.io'}], 'type': 'actions'}>, <slack_sdk.ImageBlock: {'alt_text': 'Bufo Pog', 'image_url': 'https://raw.githubusercontent.com/knobiknows/all-the-bufo/main/all-the-bufo/bufo-pog.png', 'type': 'image'}>, <slack_sdk.ContextBlock: {'elements': [{'text': 'This is a mock footer', 'type': 'mrkdwn'}], 'type': 'context'}>], text='Mock Notification')
E     Actual: chat_postEphemeral(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Mock Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'test', 'type': 'mrkdwn'}, 'type': 'section'}>, <slack_sdk.ActionsBlock: {'elements': [{'text': {'emoji': True, 'text': 'Visit Sentry', 'type': 'plain_text'}, 'type': 'button', 'url': 'https://www.sentry.io'}], 'type': 'actions'}>, <slack_sdk.ImageBlock: {'alt_text': 'Bufo Pog', 'image_url': 'https://raw.githubusercontent.com/knobiknows/all-the-bufo/main/all-the-bufo/bufo-pog.png', 'type': 'image'}>, <slack_sdk.ContextBlock: {'elements': [{'text': 'This is a mock footer', 'type': 'mrkdwn'}], 'type': 'context'}>], attachments=None, text='Mock Notification', thread_ts='1712345678.987654', user='U9876543210', unfurl_links=False, unfurl_media=False)
tests/sentry/integrations/slack/test_integration.py::SlackIntegrationNotificationPlatformTest::test_send_threaded_message_successlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/slack/test_integration.py:571: in test_send_threaded_message_success
    mock_chat_post.assert_called_once_with(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:989: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
E   AssertionError: expected call not found.
E   Expected: chat_postMessage(channel='C1234567890', thread_ts='1712345678.987654', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Mock Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'test', 'type': 'mrkdwn'}, 'type': 'section'}>, <slack_sdk.ActionsBlock: {'elements': [{'text': {'emoji': True, 'text': 'Visit Sentry', 'type': 'plain_text'}, 'type': 'button', 'url': 'https://www.sentry.io'}], 'type': 'actions'}>, <slack_sdk.ImageBlock: {'alt_text': 'Bufo Pog', 'image_url': 'https://raw.githubusercontent.com/knobiknows/all-the-bufo/main/all-the-bufo/bufo-pog.png', 'type': 'image'}>, <slack_sdk.ContextBlock: {'elements': [{'text': 'This is a mock footer', 'type': 'mrkdwn'}], 'type': 'context'}>], text='Mock Notification')
E     Actual: chat_postMessage(channel='C1234567890', blocks=[<slack_sdk.HeaderBlock: {'text': {'text': 'Mock Notification', 'type': 'plain_text'}, 'type': 'header'}>, <slack_sdk.SectionBlock: {'text': {'text': 'test', 'type': 'mrkdwn'}, 'type': 'section'}>, <slack_sdk.ActionsBlock: {'elements': [{'text': {'emoji': True, 'text': 'Visit Sentry', 'type': 'plain_text'}, 'type': 'button', 'url': 'https://www.sentry.io'}], 'type': 'actions'}>, <slack_sdk.ImageBlock: {'alt_text': 'Bufo Pog', 'image_url': 'https://raw.githubusercontent.com/knobiknows/all-the-bufo/main/all-the-bufo/bufo-pog.png', 'type': 'image'}>, <slack_sdk.ContextBlock: {'elements': [{'text': 'This is a mock footer', 'type': 'mrkdwn'}], 'type': 'context'}>], text='Mock Notification', attachments=None, thread_ts='1712345678.987654', unfurl_links=False, unfurl_media=False)

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0b40258. Configure here.

blocks=renderable["blocks"],
blocks=renderable["blocks"] if len(renderable["blocks"]) > 0 else None,
text=renderable["text"],
attachments=renderable.get("attachments"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent unfurl flags across Slack message methods

Low Severity

This commit adds unfurl_links=False and unfurl_media=False to send_notification, send_notification_with_threading, and update_message, but omits them from send_threaded_message and send_threaded_ephemeral_message. Previously none of these methods set unfurl flags, so they were all consistent. Now threaded messages will still show link previews while non-threaded ones won't.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0b40258. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diverging code paths used by different callers with different expectations.

@GabeVillalobos GabeVillalobos merged commit 4c67d2f into master Apr 7, 2026
59 checks passed
@GabeVillalobos GabeVillalobos deleted the sl_render_attach branch April 7, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants