Skip to content

Commit fe44223

Browse files
committed
improvement(slack-trigger): expose view, message, and state on interactivity payloads
The native Slack trigger flattened every interaction into scalar event.* fields and dropped the structured objects, so view_submission and block-rewrite workflows could not read view.state.values, view.private_metadata, or message.blocks. Pass the full Slack view and message objects through, plus the top-level block_actions state (state.values), and declare them in the trigger outputs so they surface in the editor. Additive and backwards compatible: existing flattened fields are unchanged and new fields default to null.
1 parent 948a5cb commit fe44223

3 files changed

Lines changed: 90 additions & 1 deletion

File tree

apps/sim/lib/webhooks/providers/slack.test.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ describe('slackHandler formatInput - interactivity (block_actions)', () => {
5656
trigger_id: 'trigger-1',
5757
response_url: 'https://hooks.slack.com/actions/abc',
5858
container: { message_ts: '999.000' },
59-
message: { ts: '999.000', text: 'Approve this?', thread_ts: '999.aaa' },
59+
message: {
60+
ts: '999.000',
61+
text: 'Approve this?',
62+
thread_ts: '999.aaa',
63+
blocks: [{ type: 'section', block_id: 'b1', text: { type: 'mrkdwn', text: 'Approve?' } }],
64+
},
65+
state: { values: { reason_block: { reason_input: { value: 'looks good' } } } },
6066
actions: [
6167
{
6268
action_id: 'approve_btn',
@@ -85,6 +91,49 @@ describe('slackHandler formatInput - interactivity (block_actions)', () => {
8591
expect(event.api_app_id).toBe('A123')
8692
expect(Array.isArray(event.actions)).toBe(true)
8793
expect((event.actions as unknown[]).length).toBe(1)
94+
const message = event.message as Record<string, unknown>
95+
expect(message).not.toBeNull()
96+
expect(Array.isArray(message.blocks)).toBe(true)
97+
expect((message.blocks as unknown[]).length).toBe(1)
98+
expect(event.view).toBeNull()
99+
const state = event.state as { values: Record<string, Record<string, { value: string }>> }
100+
expect(state).not.toBeNull()
101+
expect(state.values.reason_block.reason_input.value).toBe('looks good')
102+
})
103+
104+
it('carries the full view (state.values + private_metadata) through for a view_submission', async () => {
105+
const { input } = await slackHandler.formatInput!(
106+
ctx({
107+
type: 'view_submission',
108+
user: { id: 'U1', username: 'alice' },
109+
team: { id: 'T1' },
110+
trigger_id: 'trigger-2',
111+
view: {
112+
id: 'V123',
113+
callback_id: 'create_ticket',
114+
private_metadata: '{"thread_ts":"999.aaa"}',
115+
hash: 'abc.def',
116+
state: {
117+
values: {
118+
summary_block: { summary_input: { type: 'plain_text_input', value: 'Printer down' } },
119+
},
120+
},
121+
},
122+
})
123+
)
124+
const event = eventOf(input)
125+
expect(event.event_type).toBe('view_submission')
126+
expect(event.callback_id).toBe('create_ticket')
127+
const view = event.view as Record<string, unknown>
128+
expect(view).not.toBeNull()
129+
expect(view.private_metadata).toBe('{"thread_ts":"999.aaa"}')
130+
const values = (view.state as Record<string, unknown>).values as Record<
131+
string,
132+
Record<string, Record<string, unknown>>
133+
>
134+
expect(values.summary_block.summary_input.value).toBe('Printer down')
135+
expect(event.message).toBeNull()
136+
expect(event.state).toBeNull()
88137
})
89138

90139
it('normalizes a static_select value and falls back to action value for text', async () => {

apps/sim/lib/webhooks/providers/slack.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,25 @@ interface SlackTriggerEvent {
7474
callback_id: string
7575
api_app_id: string
7676
message_ts: string
77+
/**
78+
* Full Slack view object for modal interactions (view_submission/view_closed):
79+
* `state.values` (submitted input values), `private_metadata`, `id`,
80+
* `callback_id`, `hash`, etc. Null for non-modal interactions and Events API.
81+
*/
82+
view: Record<string, unknown> | null
83+
/**
84+
* Full Slack message object the interaction originated from (block_actions):
85+
* `blocks`, `text`, `ts`, etc. — needed to rewrite the source message's blocks.
86+
* Null when the interaction has no source message and for slash/Events API.
87+
*/
88+
message: Record<string, unknown> | null
89+
/**
90+
* Top-level interactivity `state` for block_actions: the current values of all
91+
* stateful elements in the surface (`state.values`), e.g. inputs read on a
92+
* button click without a modal submit. Distinct from `view.state` (modal
93+
* submissions). Null for non-block_actions payloads.
94+
*/
95+
state: Record<string, unknown> | null
7796
hasFiles: boolean
7897
files: SlackDownloadedFile[]
7998
}
@@ -104,6 +123,9 @@ function createSlackEvent(): SlackTriggerEvent {
104123
callback_id: '',
105124
api_app_id: '',
106125
message_ts: '',
126+
view: null,
127+
message: null,
128+
state: null,
107129
hasFiles: false,
108130
files: [],
109131
}
@@ -206,11 +228,14 @@ function formatSlackInteractive(b: Record<string, unknown>): SlackTriggerEvent {
206228
// Prefer the source message text; fall back to the triggering action's value
207229
// so a blocks-only message still surfaces something useful in `text`.
208230
event.text = asString(message?.text) || event.action_value
231+
event.message = message ?? null
209232

210233
event.response_url = asString(b.response_url)
211234
event.trigger_id = asString(b.trigger_id)
212235
const view = b.view as Record<string, unknown> | undefined
213236
event.callback_id = asString(b.callback_id) || asString(view?.callback_id)
237+
event.view = view ?? null
238+
event.state = (b.state as Record<string, unknown>) ?? null
214239
event.api_app_id = asString(b.api_app_id)
215240

216241
return event

apps/sim/triggers/slack/webhook.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,21 @@ export const slackWebhookTrigger: TriggerConfig = {
177177
description:
178178
'Timestamp of the message the interaction originated from. Present for block_actions',
179179
},
180+
view: {
181+
type: 'json',
182+
description:
183+
'Full Slack view object for modal interactions: state.values (submitted input values), private_metadata, id, callback_id, and hash. Present for view_submission/view_closed; null otherwise',
184+
},
185+
message: {
186+
type: 'json',
187+
description:
188+
'Full source message object the interaction came from, including its blocks and text. Present for block_actions on a message; null otherwise',
189+
},
190+
state: {
191+
type: 'json',
192+
description:
193+
'Current values of all stateful elements in the surface (state.values) at the time of a block action — e.g. inputs read on a button click. Present for block_actions; null otherwise',
194+
},
180195
hasFiles: {
181196
type: 'boolean',
182197
description: 'Whether the message has file attachments',

0 commit comments

Comments
 (0)