New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chat.update: add support for sending empty blocks #1214
Comments
Can confirm this issue is still present. If you add a block, then the update works great, as intended, but you can't update using text. I've done my best to recreate some reproducible code - but you may need to tweak it a little: package chatbot
import (
"fmt"
"github.com/slack-go/slack"
"github.com/stretchr/testify/assert"
"testing"
)
var slackClient *slack.Client
// ErrChannelNotFound can be used with errors.Is to determine if the channel
// doesn't exist
var ErrChannelNotFound = errors.New("channel_not_found")
func setup(t *testing.T) {
t.Helper()
slackClient = slack.New(slackBotToken,
slack.OptionAppLevelToken(slackAppToken))
}
// ChatMessage is an abstraction, designed to make chat input/output more accessible from other components
// You should instantiate this by using NewChatMessage()
type ChatMessage struct {
ShortMessage string `bson:"shortMessage" json:"shortMessage"`
ChannelName string `bson:"channelName" json:"channelName"`
SuccessResponse string `bson:"successResponse" json:"successResponse"`
FailureResponse string `bson:"failureResponse" json:"failureResponse"`
// AtMentionUser is the user to mention in the message, e.g. topher.sterling
// - there should not be an @prefix
AtMentionUser string `bson:"atMentionUser" json:"atMentionUser"`
Metadata map[string]interface{} `bson:"metadata" json:"metadata"`
Blocks []CFChatbotBlock `bson:"-" json:"-"`
}
func NewChatMessage(shortMessage, channelName string) *ChatMessage {
c := &ChatMessage{
ChannelName: channelName,
ShortMessage: shortMessage,
}
if len(shortMessage) > 0 {
c.AddTitle(shortMessage)
}
return c
}
type CFChatbotBlock struct {
slack.Block
}
// slackBlocks converts the ChatMessage's blocks to a slice of slack.Blocks
func (c *ChatMessage) slackBlocks() []slack.Block {
blocks := make([]slack.Block, len(c.Blocks))
for i := range c.Blocks {
blocks[i] = c.Blocks[i].Block
}
if len(c.AtMentionUser) > 0 {
// We're supposed to be able to reply with a user ID - but we can't. So... we'll have to get the user info
// userInfo, err := bot.SlackClient.GetUserInfo(c.AtMentionUser)
// if err != nil {
// bot.log.WithError(err).Error("Could not get user info")
// } else {
// bot.log.WithField("user", userInfo).Info("Found user info")
// }
// Prepend the @mention to the message
c.Blocks = append([]CFChatbotBlock{
{Block: slack.NewSectionBlock(
slack.NewTextBlockObject(
slack.MarkdownType,
fmt.Sprintf("FYI <@%s>?", c.AtMentionUser),
false, false,
),
nil,
nil,
),
},
}, c.Blocks...)
}
return blocks
}
// Send Posts the message to Slack and saves it to the DB.
// It returns an error if the message could not be sent or saved.
func (c *ChatMessage) Send() (msgID string, err error) {
_, msgID, err = slackClient.PostMessage(c.ChannelName,
slack.MsgOptionEnableLinkUnfurl(),
slack.MsgOptionBlocks(
c.slackBlocks()...,
),
slack.MsgOptionMetadata(slack.SlackMetadata{
EventType: "cf-emitting-chat-message",
EventPayload: c.Metadata,
}),
slack.MsgOptionUsername(fmt.Sprintf("CF %s Chat Bot", "DEV")),
)
if err != nil {
if IsChannelNotFound(err) {
err = ErrChannelNotFound
}
return msgID, fmt.Errorf("could not send message to Slack channel '%s': %w", c.ChannelName, err)
}
return msgID, nil
}
// CompleteInteraction allows you to mark a slack interaction as "completed"
func CompleteInteraction(channelName, msgId, result, completedByUser string) (err error) {
channelID, _, err := ChannelNameToID(channelName)
if err != nil {
return fmt.Errorf("unable to complete slack interaction out of band: %w", err)
}
text := fmt.Sprintf("Thank you for submitting your answer of '%s' %s! (We realize this is a thread - slack does *not* like updating the original message without a user initiated action).", result, completedByUser)
// Note - will post a thread reply
_, _, _, err = slackClient.UpdateMessage(channelID,
msgId,
slack.MsgOptionText(text, false),
slack.MsgOptionAsUser(false),
slack.MsgOptionUsername(fmt.Sprintf("CF %s Chat Bot", consts.ActualCFEnv)))
if err != nil {
return fmt.Errorf("unable to complete slack interaction out of band: %w", err)
}
return nil
}
// ChannelNameToID converts a channel name to a channel ID - a note that this
// will call the Slack API to get the list of channels. Caching has been removed for this example.
func ChannelNameToID(channelName string) (channelID string, isChannelMember bool, err error) {
// Remove the # if it's there
channelName = strings.TrimPrefix(channelName, "#")
// There was a cache here - removing to make it easier
channelNamesToIDs, err := ListChannels()
if err != nil {
return "", false, err
}
channelID, ok := channelNamesToIDs[channelName]
if !ok {
return "", false, ErrChannelNotFound
}
return channelID, false, nil
}
// ListChannels returns a map of channel names to channel IDs for all public and
// private channels. These channels are not auto-joined. Caching has been removed for this example.
func ListChannels() (channelNamesToIDs map[string]string, err error) {
channels, _, err := slackClient.GetConversations(&slack.GetConversationsParameters{
ExcludeArchived: true,
Limit: 1000,
Types: []string{"public_channel", "private_channel"},
})
if err != nil {
return nil, err
}
channelNamesToIDs = make(map[string]string)
for i := range channels {
channelNamesToIDs[channels[i].Name] = channels[i].ID
}
return channelNamesToIDs, nil
}
// TestCompleteInteraction is a test which should first send a message, then
// subsequently update that message. We are able to get this test to sort of work
// by updating the slack.MsgOptionTS(msgId) - but that's not ideal as it doesn't allow us to
// mark the interaction as complete.
// We also can send using ephemeral messages, but that's not ideal either, as we want the message
// to persist until the interaction is either completed from with-in Slack or the
// external system completes the interaction.
func TestCompleteInteraction(t *testing.T) {
is := assert.New(t)
setup(t)
msg := NewChatMessage("My initial message", "cf-chatbot-test-")
// msg.AddControl("How are you?", formtypes.ControlTypeButton, "Good", "Bad")
msgId, err := msg.Send()
is.NoError(err, "Could not send chat message")
is.NoError(CompleteInteraction(msg.ChannelName, msgId, "Good", "Topher Sterling"))
} Note that changing the UpdateMessage code to use blocks rather than _, _, _, err = slackClient.UpdateMessage(channelID,
msgId,
slack.MsgOptionBlocks(slack.NewSectionBlock(
slack.NewTextBlockObject(
"mrkdwn", text, false, false),
nil, nil)),
slack.MsgOptionAsUser(false),
slack.MsgOptionUsername(fmt.Sprintf("CF %s Chat Bot", consts.ActualCFEnv))) So perhaps the SDK could convert the |
What happened
I'm developing a bot for an employee survey. When the employee selects the answer option - yes/no - I want to replace the block with a text field answer, however, slack-go does a check on the length of the blocks field and does not add
?blocks=[]
to the request to the Slack API. Without that field, the API says the update was successful, but really doesn't change the messageExpected behavior
The
?blocks=[]
field is added to the querySteps to reproduce
reproducible code
manifest.yaml
Versions
The text was updated successfully, but these errors were encountered: