Repository: slack-go/slack
Pull request #888 (2021-01-21 16:58:07)

add thread_ts to container
When you press a button inside of a thread, Slack sends the thread timestamp inside of the container.  This is not mentioned in the [block actions documentation](https://api.slack.com/reference/interaction-payloads/block-actions), but the field is there and it is named `thread_ts`.

![Screen Shot 2021-01-21 at 09 55 51](https://user-images.githubusercontent.com/13690342/105385404-8c4b4380-5bd0-11eb-93ed-d8dd5df134b2.png)

This commit adds the missing field to Container.  The field is omitted when empty (when you are not posting in a thread).



Patch:
diff --git a/interactions.go b/interactions.go
index 2515e29..84f9b06 100644
--- a/interactions.go
+++ b/interactions.go
@@ -121,6 +121,7 @@ type Container struct {
 	Type         string      `json:"type"`
 	ViewID       string      `json:"view_id"`
 	MessageTs    string      `json:"message_ts"`
+	ThreadTs     string      `json:"thread_ts,omitempty"`
 	AttachmentID json.Number `json:"attachment_id"`
 	ChannelID    string      `json:"channel_id"`
 	IsEphemeral  bool        `json:"is_ephemeral"`


Test patch:
diff --git a/interactions_test.go b/interactions_test.go
index c4f98b8..0d25ba7 100644
--- a/interactions_test.go
+++ b/interactions_test.go
@@ -391,3 +391,87 @@ func TestInteractionCallback_InteractionTypeBlockActions_Unmarshal(t *testing.T)
 		cb.BlockActionState.Values["section_block_id"]["multi_convos"].SelectedConversations,
 		[]string{"G12345"})
 }
+
+func TestInteractionCallback_Container_Marshal_And_Unmarshal(t *testing.T) {
+	// Contrived - you generally won't see all of the fields set in a single message
+	raw := []byte(
+		`
+		{
+			"container": {
+				"type": "message",
+				"view_id": "viewID",
+				"message_ts": "messageTS",
+				"attachment_id": "123",
+				"channel_id": "channelID",
+				"is_ephemeral": false,
+				"is_app_unfurl": false
+			}
+		}
+		`)
+
+	expected := &InteractionCallback{
+		Container: Container{
+			Type:         "message",
+			ViewID:       "viewID",
+			MessageTs:    "messageTS",
+			AttachmentID: "123",
+			ChannelID:    "channelID",
+			IsEphemeral:  false,
+			IsAppUnfurl:  false,
+		},
+		RawState: json.RawMessage(`{}`),
+	}
+
+	actual := new(InteractionCallback)
+	err := json.Unmarshal(raw, actual)
+	assert.NoError(t, err)
+	assert.Equal(t, expected.Container, actual.Container)
+
+	expectedJSON := []byte(`{"type":"message","view_id":"viewID","message_ts":"messageTS","attachment_id":123,"channel_id":"channelID","is_ephemeral":false,"is_app_unfurl":false}`)
+	actualJSON, err := json.Marshal(actual.Container)
+	assert.NoError(t, err)
+	assert.Equal(t, expectedJSON, actualJSON)
+}
+
+func TestInteractionCallback_In_Thread_Container_Marshal_And_Unmarshal(t *testing.T) {
+	// Contrived - you generally won't see all of the fields set in a single message
+	raw := []byte(
+		`
+		{
+			"container": {
+				"type": "message",
+				"view_id": "viewID",
+				"message_ts": "messageTS",
+				"thread_ts": "threadTS",
+				"attachment_id": "123",
+				"channel_id": "channelID",
+				"is_ephemeral": false,
+				"is_app_unfurl": false
+			}
+		}
+		`)
+
+	expected := &InteractionCallback{
+		Container: Container{
+			Type:         "message",
+			ViewID:       "viewID",
+			MessageTs:    "messageTS",
+			ThreadTs:     "threadTS",
+			AttachmentID: "123",
+			ChannelID:    "channelID",
+			IsEphemeral:  false,
+			IsAppUnfurl:  false,
+		},
+		RawState: json.RawMessage(`{}`),
+	}
+
+	actual := new(InteractionCallback)
+	err := json.Unmarshal(raw, actual)
+	assert.NoError(t, err)
+	assert.Equal(t, expected.Container, actual.Container)
+
+	expectedJSON := []byte(`{"type":"message","view_id":"viewID","message_ts":"messageTS","thread_ts":"threadTS","attachment_id":123,"channel_id":"channelID","is_ephemeral":false,"is_app_unfurl":false}`)
+	actualJSON, err := json.Marshal(actual.Container)
+	assert.NoError(t, err)
+	assert.Equal(t, expectedJSON, actualJSON)
+}