test_eq(ActionIdManager.generate_action_id("button", 0), "tk_interaction_btn_0")
test_eq(ActionIdManager.parse_action_id("tk_interaction_btn_0"),{'type': 'btn', 'idx': '0', 'action_type': 'button', 'index': 0})slack_actions
ActionHandler
ActionHandler
ActionHandler ()
*Handles Slack interactive actions, including acknowledgment, response, and storing results in Snowflake.
Implements a singleton pattern to ensure only one handler exists.*
Responding to Actions:
Storing Action in Snowflake
When the user interacts with the bot, the action is stored in Snowflake. The action is stored in a table called user_interaction. The table has the following columns:
We also need a way to get information about a view in Snowflake:
ActionIdManager
ActionIdManager
ActionIdManager ()
Manages action IDs for Slack interactive elements to ensure uniqueness and provide routing capabilities for action handlers.
Let’s now update the ActionHandler class to use the new ActionIDManager class.
Let’s create a method to allow the user to setup the action handler in their slackbot app, and this handler will need to use an instance of the ActionIDManager class to generate a unique action ID for each action. The action ID will be used to identify the action in the Snowflake table
ActionHandler.setup_slack_action_handler
ActionHandler.setup_slack_action_handler (app)
*Set up a single Slack action handler with the Bolt app.
Args: app: Slack Bolt app snowflake_connector: Connector for Snowflake operations
Returns: Initialized ActionHandler instance*
We also need to update the ActionHandler class to use the new ActionIDManager class. When processing actions from the app, it will need to call parse_action_id to get the information from the id
body_example = {'type': 'block_actions', 'user': {'id': 'U08SWJ7MGDC', 'username': 'cooperrichason', 'name': 'cooperrichason', 'team_id': 'T08SWJ7MGAJ'}, 'api_app_id': 'A08SWJJGW1L', 'token': 'wjBURx2qV2cSMgnUlvDvI7D9', 'container': {'type': 'message', 'message_ts': '1747869345.068909', 'channel_id': 'C08TFTZHY4R', 'is_ephemeral': False}, 'trigger_id': '8931953858965.8914619730358.3acc5cd59b50a162f407f5d3a049a08b', 'team': {'id': 'T08SWJ7MGAJ', 'domain': 'datatistics'}, 'enterprise': None, 'is_enterprise_install': False, 'channel': {'id': 'C08TFTZHY4R', 'name': 'tk_slack'}, 'message': {'user': 'U08TFUY0HND', 'type': 'message', 'ts': '1747869345.068909', 'bot_id': 'B08TFUXV0M7', 'app_id': 'A08SWJJGW1L', 'text': 'Example Data Alert', 'team': 'T08SWJ7MGAJ', 'metadata': {'event_type': 'example_view_notification', 'event_payload': {'view_name': 'example_view', 'view_group': 'examples', 'response_type': 'ephemeral', 'response_message': 'Thanks {user}! You selected "{text}" ({value})', 'replace_original': False, 'custom_row_index': 2}}, 'blocks': [{'type': 'section', 'block_id': 'DpeTS', 'text': {'type': 'mrkdwn', 'text': '*Feature Z Implementation*', 'verbatim': False}}, {'type': 'section', 'block_id': 'BOqKS', 'text': {'type': 'mrkdwn', 'text': 'Planning for Feature Z implementation', 'verbatim': False}}, {'type': 'section', 'block_id': '3rZsp', 'fields': [{'type': 'mrkdwn', 'text': '*Name*\nFeature Z Implementation', 'verbatim': False}, {'type': 'mrkdwn', 'text': '*Text*\nPlanning for Feature Z implementation', 'verbatim': False}, {'type': 'mrkdwn', 'text': '*Status*\nIn Progress', 'verbatim': False}, {'type': 'mrkdwn', 'text': '*Priority*\nHigh', 'verbatim': False}, {'type': 'mrkdwn', 'text': '*Due Date*\nJun 15, 2025', 'verbatim': False}, {'type': 'mrkdwn', 'text': '*Option Name*\nStart Work, Assign to Me, Request More Info', 'verbatim': False}, {'type': 'mrkdwn', 'text': '*Option Value*\nstart, assign_self, request_info', 'verbatim': False}]}, {'type': 'actions', 'block_id': 'WKbX2', 'elements': [{'type': 'button', 'action_id': 'tk_interaction_btn_0', 'text': {'type': 'plain_text', 'text': 'Start Work', 'emoji': True}, 'style': 'primary', 'value': 'start'}, {'type': 'button', 'action_id': 'tk_interaction_btn_1', 'text': {'type': 'plain_text', 'text': 'Assign to Me', 'emoji': True}, 'value': 'assign_self'}, {'type': 'button', 'action_id': 'tk_interaction_btn_2', 'text': {'type': 'plain_text', 'text': 'Request More Info', 'emoji': True}, 'value': 'request_info'}]}, {'type': 'divider', 'block_id': 'MnqyR'}]}, 'state': {'values': {}}, 'response_url': 'https://hooks.slack.com/actions/T08SWJ7MGAJ/8931953776821/BeYdqU9hrCKACdosBdn9GVBA', 'actions': [{'action_id': 'tk_interaction_btn_0', 'block_id': 'WKbX2', 'text': {'type': 'plain_text', 'text': 'Start Work', 'emoji': True}, 'value': 'start', 'style': 'primary', 'type': 'button', 'action_ts': '1747870391.878433'}]}
body_example{'type': 'block_actions',
'user': {'id': 'U08SWJ7MGDC',
'username': 'cooperrichason',
'name': 'cooperrichason',
'team_id': 'T08SWJ7MGAJ'},
'api_app_id': 'A08SWJJGW1L',
'token': 'wjBURx2qV2cSMgnUlvDvI7D9',
'container': {'type': 'message',
'message_ts': '1747869345.068909',
'channel_id': 'C08TFTZHY4R',
'is_ephemeral': False},
'trigger_id': '8931953858965.8914619730358.3acc5cd59b50a162f407f5d3a049a08b',
'team': {'id': 'T08SWJ7MGAJ', 'domain': 'datatistics'},
'enterprise': None,
'is_enterprise_install': False,
'channel': {'id': 'C08TFTZHY4R', 'name': 'tk_slack'},
'message': {'user': 'U08TFUY0HND',
'type': 'message',
'ts': '1747869345.068909',
'bot_id': 'B08TFUXV0M7',
'app_id': 'A08SWJJGW1L',
'text': 'Example Data Alert',
'team': 'T08SWJ7MGAJ',
'metadata': {'event_type': 'example_view_notification',
'event_payload': {'view_name': 'example_view',
'view_group': 'examples',
'response_type': 'ephemeral',
'response_message': 'Thanks {user}! You selected "{text}" ({value})',
'replace_original': False,
'custom_row_index': 2}},
'blocks': [{'type': 'section',
'block_id': 'DpeTS',
'text': {'type': 'mrkdwn',
'text': '*Feature Z Implementation*',
'verbatim': False}},
{'type': 'section',
'block_id': 'BOqKS',
'text': {'type': 'mrkdwn',
'text': 'Planning for Feature Z implementation',
'verbatim': False}},
{'type': 'section',
'block_id': '3rZsp',
'fields': [{'type': 'mrkdwn',
'text': '*Name*\nFeature Z Implementation',
'verbatim': False},
{'type': 'mrkdwn',
'text': '*Text*\nPlanning for Feature Z implementation',
'verbatim': False},
{'type': 'mrkdwn', 'text': '*Status*\nIn Progress', 'verbatim': False},
{'type': 'mrkdwn', 'text': '*Priority*\nHigh', 'verbatim': False},
{'type': 'mrkdwn', 'text': '*Due Date*\nJun 15, 2025', 'verbatim': False},
{'type': 'mrkdwn',
'text': '*Option Name*\nStart Work, Assign to Me, Request More Info',
'verbatim': False},
{'type': 'mrkdwn',
'text': '*Option Value*\nstart, assign_self, request_info',
'verbatim': False}]},
{'type': 'actions',
'block_id': 'WKbX2',
'elements': [{'type': 'button',
'action_id': 'tk_interaction_btn_0',
'text': {'type': 'plain_text', 'text': 'Start Work', 'emoji': True},
'style': 'primary',
'value': 'start'},
{'type': 'button',
'action_id': 'tk_interaction_btn_1',
'text': {'type': 'plain_text', 'text': 'Assign to Me', 'emoji': True},
'value': 'assign_self'},
{'type': 'button',
'action_id': 'tk_interaction_btn_2',
'text': {'type': 'plain_text',
'text': 'Request More Info',
'emoji': True},
'value': 'request_info'}]},
{'type': 'divider', 'block_id': 'MnqyR'}]},
'state': {'values': {}},
'response_url': 'https://hooks.slack.com/actions/T08SWJ7MGAJ/8931953776821/BeYdqU9hrCKACdosBdn9GVBA',
'actions': [{'action_id': 'tk_interaction_btn_0',
'block_id': 'WKbX2',
'text': {'type': 'plain_text', 'text': 'Start Work', 'emoji': True},
'value': 'start',
'style': 'primary',
'type': 'button',
'action_ts': '1747870391.878433'}]}
ActionHandler.process_slack_action
ActionHandler.process_slack_action (body:Dict[str,Any], respond:Callable)
*Process a Slack action event.
Args: body: Slack event body respond: Slack respond function
Returns: Processed action data*