This is translation of Slack Next-gen Platform – Button Interactions – DEV Community π©βπ»π¨βπ»written by Kaz (SDK Engineering & DevRel at Slack), I do not include any personal opinion here except guessing when meaning of original text is not clear for me)
μ΄ κΈμ μ¬λμ SDK Engineering / DevRel μΈ Kaz μ κΈμ λ²μνκ²μ λλ€. μ μ κ°μΈ μ견μ λ€μ΄κ°μ§ μμΌλ©°, μΌλΆ μλ¬Έμ μλ―Έκ° μ λ§€ν κ²½μ°μλ§ λΆμ° μ€λͺ μ λ¬μμ΅λλ€.
μ΄λ² νν 리μΌμμλ λ²νΌμ μ¬μ©νμ¬ μΈν°λ μ μ νλ λ°©λ²μ λ°°μλ³΄κ² μ΅λλ€.
μ°¨μΈλ νλ«νΌμ μ±μμ λ²νΌ μΈν°λ μ μ μΆκ°νλ λ°©λ²μ 2κ°μ§κ° μμ΅λλ€.
- λΉνΈμΈ SendMessage νμ μ interactive_blocks μ μ¬μ©νκ³ , κ±°κΈ°μ μΆκ°λ‘ block_actions μ΄λ²€νΈλ₯Ό λ€λ£° μ μλ 컀μ€ν νμ μ μΆκ°νλ κ²
- λ²νΌμΌλ‘ λ©μΈμ§λ₯Ό λ³΄λΌ μ μλ 컀μ€ν νμ μ λ§λ€κ³ , block_actions μ΄λ²€νΈλ₯Ό μν νΈλ€λ¬λ₯Ό μΆκ°νλ λ°©λ².
μ΄λ² κΈμμλ λκ°μ§ λͺ¨λ λ€ λ€λ£Ήλλ€.
Prerequisites
μ°¨μΈλ νλ«νΌμ μ νλ κ²μ΄ μ²μμ΄μλΌλ©΄, μ΄μ μ νν 리μΌμΈ βThe Simplest Hello Worldβ λΆν° μ½μ΄μ£ΌμΈμ. μμ½νλ©΄ μ λ£ Slack μν¬μ€νμ΄μ€μ ν΄λΉ μν¬μ€νμ΄μ€μμ βbeta featureβ λ₯Ό μ¬μ©ν μ μλ κΆνμ΄ νμν©λλ€. κ°μ§κ³ κ³μλ€λ©΄ Slack CLI μ μν¬μ€νμ΄μ€λ₯Ό μ°κ²°νκΈ°λ§ νλ©΄ λ©λλ€.
μ€λΉλμλ€λ©΄ μ±μ νλ² λ§λ€μ΄λ³΄λλ‘ νμ£ . μμν΄λ³΄κ² μ΅λλ€.
Create a Blank Project
slack create λͺ λ Ήμ΄λ₯Ό μ¬μ©νμ¬, μλ‘μ΄ νλ‘μ νΈλ₯Ό μμν μ μμ΅λλ€. μ΄ νν 리μΌμμλ μ무κ²λ μλ μνμμ μ±μ λ§λ€μ΄λ³΄κ² μ΅λλ€. βBlank Projectβ λ₯Ό μ ννμ¬ μ£Όμμμ.
$ slack create
? Select a template to build from:
Hello World
A simple workflow that sends a greeting
Scaffolded project
A solid foundational project that uses a Slack datastore
> Blank project
A, well.. blank project
To see all available samples, visit github.com/slack-samples.
νλ‘μ νΈκ° μμ±λλ©΄, slack run λͺ λ Ήμ΄κ° μ μμ μΌλ‘ λμνλμ§ νμΈν΄λ³΄μΈμ. μ΄ λͺ λ Ήμ΄λ βdevβ λ²μ μ μ±μ μν¬μ€νμ΄μ€μ μ€μΉν©λλ€. μ΄ μ±μ λ΄ μ μ κ° μμ±λκ³ , μ΄ λ΄μ API νΈμΆμ μν λ΄ ν ν° κ°μ κ°μ§κ³ μμ΅λλ€.
$ cd affectionate-panther-654
$ slack run
? Choose a workspace seratch T03E94MJU
App is not installed to this workspace
Updating dev app install for workspace "Acme Corp"
Outgoing domains
No allowed outgoing domains are configured
If your function makes network requests, you will need to allow the outgoing domains
Learn more about upcoming changes to outgoing domains: https://api.slack.com/future/changelog
seratch of Acme Corp
Connected, awaiting events
Handle button clicks on SendMessage Function’s interactive_blocks
λΉνΈμΈ Schema.slack.functions.SendMessage νμ μ μΈν°λ ν°λΈ Block Kit μ»΄ν¬λνΈλ₯Ό κ°μνμν¨ λ²μ μ μ 곡ν©λλ€. λ©μΈμ§μ κ°λ¨ν λΈλ‘μ μΆκ°ν μ μκ³ , κ·Έ λ€μμ 컀μ€ν νμ μ ν΄λ¦ μ΄λ²€νΈμ λν΄ λ°μ ν μ μμ΅λλ€.
interactive_blocks νΈλ€λ§μ μν λ°λͺ¨ μν¬νλ‘λ₯Ό λ§λ€κ±΄λ°, λκ°μ νμΌμ μμ± νκ² μ΅λλ€.
- μν¬νλ‘μ link νΈλ¦¬κ±°κ° μ μλμ΄ μλ interactive_blocks_demo.ts
- interactive_blocks λ΄μ λ²νΌ ν΄λ¦ μ΄λ²€νΈλ₯Ό λ€λ£¨λ 컀μ€ν νμ μ΄ μ μλμ΄ μλ handle_interactive_blocks.ts
λ€μ μ½λλ₯Ό interactive_blocks_demo.ts λΌλ μ΄λ¦μΌλ‘ μ μ₯ν©λλ€.
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
export const workflow = DefineWorkflow({
callback_id: "demo-workflow",
title: "Demo Workflow",
input_parameters: {
properties: {
channel_id: { type: Schema.slack.types.channel_id },
user_id: { type: Schema.slack.types.user_id },
},
required: ["channel_id", "user_id"],
},
});
// Send a message via SendMessage + interactive_blocks
const sendMessageStep = workflow.addStep(Schema.slack.functions.SendMessage, {
channel_id: workflow.inputs.channel_id,
message: `Do you approve <@${workflow.inputs.user_id}>'s time off request?`,
// Simplified blocks for interactions
interactive_blocks: [
{
"type": "actions",
"block_id": "approve-deny-buttons",
"elements": [
{
type: "button",
action_id: "approve",
text: { type: "plain_text", text: "Approve" },
style: "primary",
},
{
type: "button",
action_id: "deny",
text: { type: "plain_text", text: "Deny" },
style: "danger",
},
],
},
],
});
// Handle the button click events on interactive_blocks
import { def as handleInteractiveBlocks } from "./handle_interactive_blocks.ts";
workflow.addStep(handleInteractiveBlocks, {
// The clicked action's details
action: sendMessageStep.outputs.action,
// For further interactions on a modal
interactivity: sendMessageStep.outputs.interactivity,
// The message's URL
messageLink: sendMessageStep.outputs.message_link,
// The message's unique ID in the channel
messageTs: sendMessageStep.outputs.message_ts,
});
import { Trigger } from "deno-slack-api/types.ts";
const trigger: Trigger<typeof workflow.definition> = {
type: "shortcut",
name: "Interaction Demo Trigger",
workflow: `#/workflows/${workflow.definition.callback_id}`,
inputs: {
channel_id: { value: "{{data.channel_id}}" },
user_id: { value: "{{data.user_id}}" },
},
};
export default trigger;
μμ§ handle_interactive_blocks.ts λ₯Ό μλ§λ€μκΈ° λλ¬Έμ, TS μ»΄νμΌμ μ€ν¨ν©λλ€. handle_interactive_blocks.ts λ₯Ό λ§λ€κ² μ΅λλ€. μ΄ μ»€μ€ν νμ μ SendMessage νμ μΌλ‘λΆν° μ λ¬λλ ν΄λ¦ μ΄λ²€νΈλ₯Ό λ€λ£Ήλλ€.
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
export const def = DefineFunction({
callback_id: "handle_interactive_blocks",
title: "Handle button clicks in interactive_blocks",
source_file: "handle_interactive_blocks.ts",
input_parameters: {
// The input values from the SendMessage function's interactive_blocks
properties: {
action: { type: Schema.types.object },
interactivity: { type: Schema.slack.types.interactivity },
messageLink: { type: Schema.types.string },
messageTs: { type: Schema.types.string },
},
required: ["action", "interactivity"],
},
output_parameters: { properties: {}, required: [] },
});
export default SlackFunction(
def,
// When the worfklow is executed, this handler is called
async ({ inputs, client }) => {
if (inputs.action.action_id === "deny") {
// Only when the click is on "Deny", this function opens a modal
// to ask the reason of the denial
const response = await client.views.open({
interactivity_pointer: inputs.interactivity.interactivity_pointer,
view: buildNewModalView(),
});
if (response.error) {
const error = `Failed to open a modal due to ${response.error}`;
return { error };
}
// Continue the interactions on the modal
return { completed: false };
}
return { completed: true, outputs: {} };
},
)
// Handle the button click events on the modal
.addBlockActionsHandler("clear-inputs", async ({ body, client }) => {
const response = await client.views.update({
interactivity_pointer: body.interactivity.interactivity_pointer,
view_id: body.view.id,
view: buildNewModalView(),
});
if (response.error) {
const error = `Failed to update a modal due to ${response.error}`;
return { error };
}
return { completed: false };
})
// Handle the data submission from the modal
.addViewSubmissionHandler(
["deny-reason-submission"],
({ view }) => {
const values = view.state.values;
const reason = String(Object.values(values)[0]["deny-reason"].value);
if (reason.length <= 5) {
console.log(reason);
const errors: Record<string, string> = {};
const blockId = Object.keys(values)[0];
errors[blockId] = "The reason must be 5 characters or longer";
return { response_action: "errors", errors };
}
return {};
},
)
// Handle the events when the end-user closes the modal
.addViewClosedHandler(
["deny-reason-submission", "deny-reason-confirmation"],
({ view }) => {
console.log(JSON.stringify(view, null, 2));
},
);
/**
* Returns the initial state of the modal view
* @returns the initial modal view
*/
function buildNewModalView() {
return {
"type": "modal",
"callback_id": "deny-reason-submission",
"title": { "type": "plain_text", "text": "Reason for the denial" },
"notify_on_close": true,
"submit": { "type": "plain_text", "text": "Confirm" },
"blocks": [
{
"type": "input",
// If you reuse block_id when refreshing an existing modal view,
// the old block may remain. To avoid this, always set a random value.
"block_id": crypto.randomUUID(),
"label": { "type": "plain_text", "text": "Reason" },
"element": {
"type": "plain_text_input",
"action_id": "deny-reason",
"multiline": true,
"placeholder": {
"type": "plain_text",
"text": "Share the reason why you denied the request in detail",
},
},
},
{
"type": "actions",
"block_id": "clear",
"elements": [
{
type: "button",
action_id: "clear-inputs",
text: { type: "plain_text", text: "Clear all the inputs" },
style: "danger",
},
],
},
],
};
}
manifest.ts μ μΆκ°ν΄μ£Όμꡬμ
import { Manifest } from "deno-slack-sdk/mod.ts";
// Add this
import { workflow as InteractiveBlocksDemo } from "./interactive_blocks_demo.ts";
export default Manifest({
name: "stoic-wolf-344",
description: "Demo workflow",
icon: "assets/default_new_app_icon.png",
workflows: [InteractiveBlocksDemo], // Add this
outgoingDomains: [],
botScopes: ["commands", "chat:write", "chat:write.public"],
});
μ΄μ μ€λΉκ° λμ΅λλ€. slack run 컀맨λλ₯Ό μ€ννμ¬ μλ¬ λ°μ μ¬λΆλ₯Ό νμΈνμΈμ.
$ slack run
? Choose a workspace seratch T03E94MJU
stoic-wolf-344 A04G9S43G2K
Updating dev app install for workspace "Acme Corp"
β οΈ Outgoing domains
No allowed outgoing domains are configured
If your function makes network requests, you will need to allow the outgoing domains
Learn more about upcoming changes to outgoing domains: https://api.slack.com/future/changelog
β¨ seratch of Acme Corp
Connected, awaiting events
κ·Έλ¦¬κ³ λλ€λ₯Έ ν°λ―Έλμ μ΄μ΄μ slack triggers create –trigger-def interactive_blocks_demo.ts λ₯Ό μ€ννμ¬ link νΈλ¦¬κ±°λ₯Ό μμ±ν΄μ€λλ€.
$ slack triggers create --trigger-def interactive_blocks_demo.ts
? Choose an app seratch (dev) T03E94MJU
stoic-wolf-344 (dev) A04G9S43G2K
β‘ Trigger created
Trigger ID: Ft04HCF4SSBB
Trigger Type: shortcut
Trigger Name: Interaction Demo Trigger
URL: https://slack.com/shortcuts/***/***
$
μμ λ link νΈλ¦¬κ±°λ₯Ό μ±λμ λ¨κΈ°κ³ λλ¬λ΄ μλ€. λκ°μ λ²νΌμ΄ μλ λ©μΈμ§λ₯Ό λ³΄μ€ μ μμ κ²λλ€.

“Approve” λ²νΌμ λλ₯΄λ©΄, handle_interactive.ts νμ μ΄ μ΄λ²€νΈ μμ²μ μλ½νκ³ μ무μΌλ μν©λλ€. μ΄ κ²½μ° interactive_blocks λΆλΆμ΄ νλ«νΌμ μν΄ λ체λλ κ² λ§κ³ λ μ무μΌλ λ°μνμ§ μμ΅λλ€.

λ°λλ‘ “Deny” λ²νΌμ λλ₯΄λ©΄, 컀μ€ν νμ μ΄ Deny μ¬μ λ₯Ό λ¬Όμ΄λ³΄λ μλ‘μ΄ λͺ¨λ¬ νλ©΄μ λμλλ€.
λν νλ©΄μ λ°μ΄ν° μλΈλ―Έμ μ μν μΆκ°μ μΈ νΈλ€λ¬κ°, μ λ ₯λ λ°μ΄ν°κ° μ ν¨νμ§ (κΈΈμ΄) 체ν¬νκ³ , λμμ μ λ ₯κ°μ μ§μΈ μ μλ λ²νΌκΉμ§ μ 곡ν©λλ€.

보μλ λ°μ κ°μ΄, μ΄λ¬ν κ°λ¨ν μΉμΈ μ μ°¨λ₯Ό λ§λ€ λ, λΉνΈμΈ interactive_blocks λ₯Ό μ¬μ©νλ©΄ κ°λ¨νκ² κ΅¬νν μ μμ΅λλ€. κ·Έλ μ§λ§, μ΄λ€ λΆλΆμ 컀μ€ν°λ§μ΄μ§ ν μ μλλ°μ, μλ₯Ό λ€λ©΄, λ²νΌμ΄ ν΄λ¦ λμμ λ interactive_blocks ννΈ λΆλΆμ μ λ°μ΄νΈ νλ€κ±°λ νλ λΆλΆμ΄ μλ©λλ€. λ§μ½ μΈν°λ μ μ λν΄μ λͺ¨λ μ μ΄λ₯Ό νκ³ μΆλ€λ©΄, Block Kit μ μ¬μ©ν΄μ μΈν°λ ν°λΈν λ©μμ§ λΈλ‘μ λ§λ€ μ μμ΅λλ€. λ€μ μΉμ μμ λ°°μλ³Ό κ²μ λλ€.
Write Custom Function With Full Interactivity Features
send_interactive_message.ts λΌλ μ΄λ¦μ κ°μ§ νμΌμ λ§λλλ€. μ΄ μμ€νμΌμλ Block Kits μ λΈλ‘μ μ¬μ©ν΄μ μ±λλ‘ λ©μΈμ§λ₯Ό 보λ΄κ³ , λ©μΈμ§ λΈλ‘μ λͺ¨λ μΈν°λ ν°λΈ μ΄λ²€νΈλ₯Ό λ€λ£¨λ 컀μ€ν νμ μ΄ μ μλμ΄ μμ΅λλ€.
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
export const def = DefineFunction({
callback_id: "send_interactive_message",
title: "Send a message with interactive blocks",
source_file: "send_interactive_message.ts",
input_parameters: {
properties: {
user_id: { type: Schema.slack.types.user_id },
channel_id: { type: Schema.slack.types.channel_id },
},
required: ["user_id", "channel_id"],
},
output_parameters: { properties: {}, required: [] },
});
export default SlackFunction(
def,
// When the worfklow is executed, this handler is called
async ({ inputs, client }) => {
const text = `Do you approve <@${inputs.user_id}>'s time off request?`;
// Block Kit elements (https://api.slack.com/block-kit)
const blocks = [
{
type: "section",
text: { type: "mrkdwn", text },
},
{ type: "divider" },
{
type: "actions",
block_id: "approve-deny-buttons",
elements: [
{
type: "button",
action_id: "approve",
text: { type: "plain_text", text: "Approve" },
style: "primary",
},
{
type: "button",
action_id: "deny",
text: { type: "plain_text", text: "Deny" },
style: "danger",
},
],
},
];
const response = await client.chat.postMessage({
channel: inputs.channel_id,
text,
blocks,
});
if (response.error) {
console.log(JSON.stringify(response, null, 2));
const error = `Failed to post a message due to ${response.error}`;
return { error };
}
// To continue with this interaction, return false for the completion
return { completed: false };
},
)
// Handle the "Approve" button clicks
.addBlockActionsHandler("approve", async ({ body, client, inputs }) => {
const text = "Thank you for approving the request!";
const response = await client.chat.update({
channel: inputs.channel_id,
ts: body.container.message_ts,
text,
blocks: [{ type: "section", text: { type: "mrkdwn", text } }],
});
if (response.error) {
const error = `Failed to update the message due to ${response.error}`;
return { error };
}
return { completed: true, outputs: {} };
})
// Handle the "Deny" button clicks
.addBlockActionsHandler("deny", async ({ body, client, inputs }) => {
const text =
"OK, we need more information... Could you share the reason for denial?";
const messageResponse = await client.chat.update({
channel: inputs.channel_id,
ts: body.container.message_ts,
text,
blocks: [{ type: "section", text: { type: "mrkdwn", text } }],
});
if (messageResponse.error) {
const error =
`Failed to update the message due to ${messageResponse.error}`;
return { error };
}
const modalResponse = await client.views.open({
interactivity_pointer: body.interactivity.interactivity_pointer,
view: buildNewModalView(),
});
if (modalResponse.error) {
const error = `Failed to open a modal due to ${modalResponse.error}`;
return { error };
}
return { completed: false };
})
// Handle the button click events on the modal
.addBlockActionsHandler("clear-inputs", async ({ body, client }) => {
const response = await client.views.update({
interactivity_pointer: body.interactivity.interactivity_pointer,
view_id: body.view.id,
view: buildNewModalView(),
});
if (response.error) {
const error = `Failed to update a modal due to ${response.error}`;
return { error };
}
return { completed: false };
})
// Handle the data submission from the modal
.addViewSubmissionHandler(
["deny-reason-submission"],
({ view }) => {
const values = view.state.values;
const reason = String(Object.values(values)[0]["deny-reason"].value);
if (reason.length <= 5) {
console.log(reason);
const errors: Record<string, string> = {};
const blockId = Object.keys(values)[0];
errors[blockId] = "The reason must be 5 characters or longer";
return { response_action: "errors", errors };
}
return {};
},
)
// Handle the events when the end-user closes the modal
.addViewClosedHandler(
["deny-reason-submission", "deny-reason-confirmation"],
({ view }) => {
console.log(JSON.stringify(view, null, 2));
},
);
/**
* Returns the initial state of the modal view
* @returns the initial modal view
*/
function buildNewModalView() {
return {
"type": "modal",
"callback_id": "deny-reason-submission",
"title": { "type": "plain_text", "text": "Reason for the denial" },
"notify_on_close": true,
"submit": { "type": "plain_text", "text": "Confirm" },
"blocks": [
{
"type": "input",
// If you reuse block_id when refreshing an existing modal view,
// the old block may remain. To avoid this, always set a random value.
"block_id": crypto.randomUUID(),
"label": { "type": "plain_text", "text": "Reason" },
"element": {
"type": "plain_text_input",
"action_id": "deny-reason",
"multiline": true,
"placeholder": {
"type": "plain_text",
"text": "Share the reason why you denied the request in detail",
},
},
},
{
"type": "actions",
"block_id": "clear",
"elements": [
{
type: "button",
action_id: "clear-inputs",
text: { type: "plain_text", text: "Clear all the inputs" },
style: "danger",
},
],
},
],
};
}
λ€μμΌλ‘, μμμ λ§λ νμ μ μ¬μ©νλ μν¬νλ‘λ₯Ό λ§λ€κ³ , interactive_message_demo.ts λΌλ μ΄λ¦μΌλ‘ μ μ₯ν©λλ€.
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
export const workflow = DefineWorkflow({
callback_id: "demo-workflow",
title: "Demo Workflow",
input_parameters: {
properties: {
channel_id: { type: Schema.slack.types.channel_id },
user_id: { type: Schema.slack.types.user_id },
},
required: ["channel_id", "user_id"],
},
});
import { def as sendInteractiveMessage } from "./send_interactive_message.ts";
workflow.addStep(sendInteractiveMessage, {
user_id: workflow.inputs.user_id,
channel_id: workflow.inputs.channel_id,
});
import { Trigger } from "deno-slack-api/types.ts";
const trigger: Trigger<typeof workflow.definition> = {
type: "shortcut",
name: "Interaction Demo Trigger",
workflow: `#/workflows/${workflow.definition.callback_id}`,
inputs: {
channel_id: { value: "{{data.channel_id}}" },
user_id: { value: "{{data.user_id}}" },
},
};
export default trigger;
manifest.ts μ μΆκ°νκ³ μ
import { Manifest } from "deno-slack-sdk/mod.ts";
import { workflow as InteractiveBlocksDemo } from "./interactive_blocks_demo.ts";
// Add this
import { workflow as InteractiveMessageDemo } from "./interactive_message_demo.ts";
export default Manifest({
name: "stoic-wolf-344",
description: "Demo workflow",
icon: "assets/default_new_app_icon.png",
workflows: [InteractiveBlocksDemo, InteractiveMessageDemo], // Add this
outgoingDomains: [],
botScopes: ["commands", "chat:write", "chat:write.public"],
});
λ§μ§λ§μΌλ‘, μμμ νλ κ²μ²λΌ λκ°μ λ°©λ²μΌλ‘ link νΈλ¦¬κ±°λ₯Ό λ§λλλ€. μν¬νλ‘λ₯Ό μμνκ² λλ©΄, λ²νΌμ΄ λ¬λ €μλ λ©μΈμ§λ₯Ό λ³΄μ€ κ²λλ€. κ·Έλ¦¬κ³ λ²νΌμ λλ₯΄λ©΄, μ΄μ μ SendMessage μ interactive_blocks κ³Όλ λ€λ₯Έ μ μ λ³΄μ€ μ μμ κ²λλ€. (μ΄μ μλ λ¨μν Approve λ²νΌμ ν΄λ¦ νλ€ λΌλ λ©μΈμ§κ° λμλ€λ©΄, μ΄λ²μλ Approve μμ κ°μ¬νλ€ λΌλ λ©μΈμ§κ° λμ΅λλ€.)

μΆλ ₯λλ λ©μΈμ§λ₯Ό λ³κ²½ν μ½λλ λ€μκ³Ό κ°μ΅λλ€.
.addBlockActionsHandler("approve", async ({ body, client, inputs }) => {
const text = "Thank you for approving the request!";
await client.chat.update({
channel: inputs.channel_id,
ts: body.container.message_ts,
text,
blocks: [{ type: "section", text: { type: "mrkdwn", text } }],
});
return { completed: true, outputs: {} };
})
Deny λ²νΌμ λν΄μλ 첫λ²μ§Έ μμ μ κ°μ΄ λκ°μ΄ λͺ¨λ¬ νλ©΄μ λμλλ€. κ·Έλ μ§λ§ κ·Έμ λμμ μλ―Έμλ λ©μΈμ§λ κ°μ΄ λ¨κΉλλ€.

Deny λ²νΌμ ν΄λ¦νμλμ νΈλ€λ§μ μν μ½λ μ λλ€. μ¬κΈ°μ μκ³ κ°μ μΌ νλ λΆλΆμ interactivity λ₯Ό inputs μμ λ°μμ€λκ² μλλΌ body λ°μ΄ν°μμ κ°μ§κ³ μ¨λ€λ κ²μ λλ€.
.addBlockActionsHandler("deny", async ({ body, client, inputs }) => {
const text =
"OK, we need more information... Could you share the reason for denial?";
await client.chat.update({
channel: inputs.channel_id,
ts: body.container.message_ts,
text,
blocks: [{ type: "section", text: { type: "mrkdwn", text } }],
});
await client.views.open({
interactivity_pointer: body.interactivity.interactivity_pointer,
view: buildNewModalView(),
});
// To continue interactions, return completed: false
return { completed: false };
})
Block Kit μ΄λ λͺ¨λ¬μ μ΅μνμ§ μλ€λ©΄, μ μ΄ν΄κ° μλλ μ½λκ° μμ μ μμ΅λλ€. μλμ μΌλ‘ λ¨μν μ½λλΆν° μμνμ¬ μ½λλ₯Ό λ°κΏλ³΄λ©΄μ λ λ°°μλ³΄μ€ μ μμΌμ€κ²λλ€.
λΈλ‘μ νΈμ§ν λλ, Block Kit Builder λ₯Ό μ¬μ©ν΄ 보μλ©΄ μ’μ΅λλ€. μμ§ μ¬μ©ν΄λ³΄μ§ μμλ€λ©΄, ν΄λΉ μ¬μ΄νΈλ₯Ό λ°©λ¬Ένμ¬ μ’μΈ‘μ μ¬μ© κ°λ₯ν λΈλ‘λ€μ λλ¬λ³΄μΈμ.
Wrapping Up
μ΄λ² νν 리μΌμμλ λ€μκ³Ό κ°μ λ΄μ©μ λ°°μ μ΅λλ€.
- SendMessage μ interactive_blocks μ μ¬μ©νλ λ°©λ²κ³Ό, 컀μ€ν νμ μμ ν΄λΉ μ΄λ²€νΈλ₯Ό λ€λ£¨λ λ°©λ²
- μΈν°λ ν°λΈν λ©μΈμ§λ₯Ό 보λ΄κ³ ,ν΄λΉ λ©μΈμ§μ μΈν°λ ν°λΈ μ΄λ²€νΈλ₯Ό λ€λ£¨λ 컀μ€ν νμ μ λ§λλ λ°©λ²
μ΄ νλ‘μ νΈλ λ€μ μ£Όμμμλ νμΈνμ€ μ μμ΅λλ€. https://github.com/seratch/slack-next-generation-platform-tutorials/tree/main/12_Button_Interactions
μ΄λ² νν 리μΌλ μ¦κ±°μ°μ ¨μΌλ©΄ μ’κ² λ€μ. λ§μ°¬κ°μ§λ‘ νΌλλ°±μ΄λ μ½λ©νΈκ° μμΌμλ€λ©΄ νΈμν° (@seratch) λ‘ μ°λ½μ£Όμκ±°λ, μ¬κΈ° λ¨κ²¨μ£ΌμΈμ.
Happy hacking with Slackβs next-generation platform π