Goで作るSlack Modal Applicationサンプルコード 3/4
STEP3 注文確認画面のModalを送る
つづいて、注文確認画面の作成と送付。STEP2で作った注文Modalの入力情報をもとに作成します。
完成イメージはこんな感じ。
今回は、注文内容の確認だけでなく、Chipを入れるフィールドも作ってみました。せっかくなので、これを使ってバリデーションの方法も学習しておきましょう(詳細はSTEP4で!)。
このSTEPでやることは、
- リクエストの認証(自分のWorkspaceからのリクエストかどうか)
- リクエストをStructにマッピング
- 注文内容を取得
- Modalを作成して送信
の4つです。リクエストの認証と、StructにマッピングはSTEP2と同様なので省きます。
handlerのソースはこちら。(各関数の実装含めた完全版は、GitHub参照)
func handleOrderSubmissionRequest(message slack.InteractionCallback) (events.APIGatewayProxyResponse, error) {
// Get the selected information.
// - radio button
menu := message.View.State.Values["block_id_menu"]["action_id_menu"].SelectedOption.Value
// - static_select
steak := message.View.State.Values["block_id_steak"]["action_id_steak"].SelectedOption.Value
// - text
note := message.View.State.Values["block_id_note"]["action_id_note"].Value
// Create a confirmation modal.
// - apperance
modal := createConfirmationModalBySDK(menu, steak, note)
// - metadata : CallbackID
modal.CallbackID = reqConfirmationModalSubmission
// - metadata : ExternalID
modal.ExternalID = message.User.ID + strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
// - metadata : PrivateMeta
// - Get private metadata of a message
var pMeta privateMeta
if err := json.Unmarshal([]byte(message.View.PrivateMetadata), &pMeta); err != nil {
return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to unmarshal private metadata: %w", err)
}
// - Create new private metadata
params := privateMeta{
ChannelID: pMeta.ChannelID,
order: order{
Menu: menu,
Steak: steak,
Note: note,
Amount: "700",
},
}
pBytes, err := json.Marshal(params)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to marshal private metadata: %w", err)
}
modal.PrivateMetadata = string(pBytes)
// Create response
resAction := slack.NewUpdateViewSubmissionResponse(modal)
rBytes, err := json.Marshal(resAction)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to marshal json: %w", err)
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
IsBase64Encoded: false,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: string(rBytes),
}, nil
}
では、ポイントです。
注文内容を取得
Modalへの入力情報は、messageのView.Stateフィールドにmap形式で格納されているので、block_id、action_idをkeyに取り出します。
// Get the selected information.
// - radio button
menu := message.View.State.Values["block_id_menu"]["action_id_menu"].SelectedOption.Value
// - static_select
steak := message.View.State.Values["block_id_steak"]["action_id_steak"].SelectedOption.Value
// - text
note := message.View.State.Values["block_id_note"]["action_id_note"].Value
input blockの種類によって、取り出し方が微妙に違うので注意。
Modalを作成して送信
Modalの作成手順は基本的にSTEP2と同様ですが、今回は注文内容に合わせて、動的に内容を変えるためSDKでの作成しかできない点に注意。
(一応強引にJSONから作成もできます。JSON内にPlace Holderを置いておいて、strings.Replaceで入力値に置き換える、だったりをすれば可能は可能です。)
func createConfirmationModalBySDK(menu, steak, note string) *slack.ModalViewRequest {
// Create a modal.
// - Text section
titleText := slack.NewTextBlockObject("mrkdwn", ":wave: *Order confirmation*", false, false)
titleTextSection := slack.NewSectionBlock(titleText, nil, nil)
// Divider
dividerBlock := slack.NewDividerBlock()
// - Text section
sMenuText := slack.NewTextBlockObject("mrkdwn", "*Menu :hamburger:*\n"+burgers[menu], false, false)
sMenuTextSection := slack.NewSectionBlock(sMenuText, nil, nil)
// - Text section
sSteakText := slack.NewTextBlockObject("mrkdwn", "*How do you like your steak?*\n"+steak, false, false)
sSteakTextSection := slack.NewSectionBlock(sSteakText, nil, nil)
// - Text section
sNoteText := slack.NewTextBlockObject("mrkdwn", "*Anything else you want to tell us?*\n"+note, false, false)
sNoteTextSection := slack.NewSectionBlock(sNoteText, nil, nil)
// - Text section
amountText := slack.NewTextBlockObject("mrkdwn", "*Amount :moneybag:*\n$ 700", false, false)
amountTextSection := slack.NewSectionBlock(amountText, nil, nil)
// - Input with plain_text_input
chipText := slack.NewTextBlockObject("plain_text", "Chip ($)", false, false)
chipInputElement := slack.NewPlainTextInputBlockElement(nil, "action_id_chip")
chipInput := slack.NewInputBlock("block_id_chip", chipText, chipInputElement)
chipHintText := slack.NewTextBlockObject("plain_text", "Thank you for your kindness!", false, false)
chipInput.Hint = chipHintText
chipInput.Optional = true
// Blocks
blocks := slack.Blocks{
BlockSet: []slack.Block{
titleTextSection,
dividerBlock,
sMenuTextSection,
sSteakTextSection,
sNoteTextSection,
dividerBlock,
amountTextSection,
chipInput,
},
}
// ModalView
modal := slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: slack.NewTextBlockObject("plain_text", "Hungryman Hamburgers", false, false),
Close: slack.NewTextBlockObject("plain_text", "Cancel", false, false),
Submit: slack.NewTextBlockObject("plain_text", "Order!", false, false),
Blocks: blocks,
}
return &modal
}
つづいて、metadataの追加。先程と同様にCallbackID、ExternalID、Private Metadataを追加します。
CallbackID
// - metadata : CallbackID
modal.CallbackID = reqConfirmationModalSubmission
ExternalID
こちらもユーザーID+タイムスタンプから変化なし。
// - metadata : ExternalID
modal.ExternalID = message.User.ID + strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
Private Metadata
Private Metadataには、
- チャンネルID
- 注文内容
の2つを保管しましょう。繰り返しになりますが、Modalアプリの各通信はステートレスなので、次の通信に引き継ぎたい情報はPrivate Metadataに自分で入れる必要があります。
まずは、注文ModalのPrivate Metadataを取り出します。
// - metadata : PrivateMeta
// - Get private metadata of a message
var pMeta privateMeta
if err := json.Unmarshal([]byte(message.View.PrivateMetadata), &pMeta); err != nil {
return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to unmarshal private metadata: %w", err)
}
次にPrivate Metadataから取り出したチャンネルIDと、message.View.State内の注文内容から新たにPrivate Metadataを生成。注文確認Modalに埋め込みましょう。
// - Create new private metadata
params := privateMeta{
ChannelID: pMeta.ChannelID,
order: order{
Menu: menu,
Steak: steak,
Note: note,
Amount: "700",
},
}
pBytes, err := json.Marshal(params)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to marshal private metadata: %w", err)
}
modal.PrivateMetadata = string(pBytes)
ここまでできたら、あとは送信するのみ。
今回はViewの更新なので、NewUpdateViewSubmissionResponseを使って、レスポンスとして返してあげればOKです。
// Create response
resAction := slack.NewUpdateViewSubmissionResponse(modal)
rBytes, err := json.Marshal(resAction)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: 200}, fmt.Errorf("failed to marshal json: %w", err)
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
IsBase64Encoded: false,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: string(rBytes),
}, nil
Discussion