mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-26 21:27:40 -05:00
ES search: include footer and author name in indexed fields. (#35603)
* Include footer and author name in indexed fields.
This commit is contained in:
@@ -133,7 +133,7 @@ func ESPostFromPostForIndexing(post *model.PostForIndexing) *ESPost {
|
||||
if !mOk {
|
||||
continue
|
||||
}
|
||||
for _, key := range []string{"text", "title", "pretext", "fallback"} {
|
||||
for _, key := range []string{"text", "title", "pretext", "fallback", "footer", "author_name"} {
|
||||
if s, _ := m[key].(string); s != "" {
|
||||
searchAttachments = append(searchAttachments, s)
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func ESPostFromPostForIndexing(post *model.PostForIndexing) *ESPost {
|
||||
if attachment == nil {
|
||||
continue
|
||||
}
|
||||
for _, s := range []string{attachment.Text, attachment.Title, attachment.Pretext, attachment.Fallback} {
|
||||
for _, s := range []string{attachment.Text, attachment.Title, attachment.Pretext, attachment.Fallback, attachment.Footer, attachment.AuthorName} {
|
||||
if s != "" {
|
||||
searchAttachments = append(searchAttachments, s)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -319,6 +320,64 @@ func TestESPostFromPostForIndexing(t *testing.T) {
|
||||
assert.Contains(t, espost.Attachments, "99.5")
|
||||
})
|
||||
|
||||
t.Run("any form indexes footer and author_name", func(t *testing.T) {
|
||||
post := model.PostForIndexing{
|
||||
TeamId: model.NewId(),
|
||||
Post: model.Post{
|
||||
Id: model.NewId(),
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
CreateAt: model.GetMillis(),
|
||||
Message: "",
|
||||
Type: "slack_attachment",
|
||||
Props: map[string]any{
|
||||
model.PostPropsAttachments: []any{
|
||||
map[string]any{
|
||||
"text": "body text",
|
||||
"footer": "Opportunity #OPP-000035341 • United States",
|
||||
"author_name": "Salesforce",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
espost := ESPostFromPostForIndexing(&post)
|
||||
|
||||
assert.Contains(t, espost.Attachments, "body text")
|
||||
assert.Contains(t, espost.Attachments, "Opportunity #OPP-000035341 • United States")
|
||||
assert.Contains(t, espost.Attachments, "Salesforce")
|
||||
})
|
||||
|
||||
t.Run("MessageAttachment form indexes footer and author_name", func(t *testing.T) {
|
||||
post := model.PostForIndexing{
|
||||
TeamId: model.NewId(),
|
||||
Post: model.Post{
|
||||
Id: model.NewId(),
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
CreateAt: model.GetMillis(),
|
||||
Message: "",
|
||||
Type: "slack_attachment",
|
||||
Props: map[string]any{
|
||||
model.PostPropsAttachments: []*model.MessageAttachment{
|
||||
{
|
||||
Text: "body text",
|
||||
Footer: "Opportunity #OPP-000035341 • United States",
|
||||
AuthorName: "Salesforce",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
espost := ESPostFromPostForIndexing(&post)
|
||||
|
||||
assert.Contains(t, espost.Attachments, "body text")
|
||||
assert.Contains(t, espost.Attachments, "Opportunity #OPP-000035341 • United States")
|
||||
assert.Contains(t, espost.Attachments, "Salesforce")
|
||||
})
|
||||
|
||||
t.Run("multiple attachments are combined", func(t *testing.T) {
|
||||
post := model.PostForIndexing{
|
||||
TeamId: model.NewId(),
|
||||
@@ -353,6 +412,121 @@ func TestESPostFromPostForIndexing(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestESPostFromPost_CreatePostJSONRoundTrip simulates the exact flow that happens
|
||||
// in production: App.CreatePost converts []*MessageAttachment to []any via JSON
|
||||
// marshal/unmarshal (Post.Attachments in post.go), then the search layer calls
|
||||
// ESPostFromPost which does ShallowCopy → ESPostFromPostForIndexing.
|
||||
func TestESPostFromPost_CreatePostJSONRoundTrip(t *testing.T) {
|
||||
// Step 1: Start with typed MessageAttachment (as a plugin/webhook would create)
|
||||
original := &model.Post{
|
||||
Id: model.NewId(),
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
CreateAt: model.GetMillis(),
|
||||
Message: "#closedwon #renewal",
|
||||
Type: "slack_attachment",
|
||||
}
|
||||
original.AddProp(model.PostPropsAttachments, []*model.MessageAttachment{
|
||||
{
|
||||
Title: "Account: Acme Corp / Widget Industries, LLC",
|
||||
Text: "Renewal ARR: $49,140.00",
|
||||
Pretext: "#closedwon #renewal",
|
||||
Footer: "Opportunity #OPP-000035341 • United States",
|
||||
AuthorName: "CRM Bot",
|
||||
Fields: []*model.MessageAttachmentField{
|
||||
{Title: "Sales Rep", Value: "Jane Smith"},
|
||||
{Title: "Account Manager", Value: "John Doe"},
|
||||
{Title: "Opportunity", Value: "Acme Corp / Widget Industries, LLC - Enterprise - 350 Seats - '26 Renewal"},
|
||||
{Title: "Seats", Value: 350.0},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Step 2: Simulate CreatePost JSON round-trip (post.go:305-315)
|
||||
if attachments, ok := original.GetProp(model.PostPropsAttachments).([]*model.MessageAttachment); ok {
|
||||
jsonAttachments, err := json.Marshal(attachments)
|
||||
require.NoError(t, err)
|
||||
attachmentsInterface := []any{}
|
||||
err = json.Unmarshal(jsonAttachments, &attachmentsInterface)
|
||||
require.NoError(t, err)
|
||||
original.AddProp(model.PostPropsAttachments, attachmentsInterface)
|
||||
} else {
|
||||
require.Fail(t, "expected []*MessageAttachment type assertion to succeed")
|
||||
}
|
||||
|
||||
// Step 3: Simulate ESPostFromPost (what the search layer calls)
|
||||
teamId := model.NewId()
|
||||
esPost, err := ESPostFromPost(original, teamId, "O")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Step 4: Verify all attachment fields are indexed
|
||||
assert.Contains(t, esPost.Attachments, "Account: Acme Corp / Widget Industries, LLC", "title should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Renewal ARR: $49,140.00", "text should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "#closedwon #renewal", "pretext should be indexed")
|
||||
|
||||
// Field titles and values
|
||||
assert.Contains(t, esPost.Attachments, "Sales Rep", "field title should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Jane Smith", "field value should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Account Manager", "field title should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "John Doe", "field value should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Opportunity", "field title should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Acme Corp / Widget Industries, LLC - Enterprise - 350 Seats - '26 Renewal", "field value should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "350", "numeric field value should be indexed")
|
||||
|
||||
// Footer and AuthorName
|
||||
assert.Contains(t, esPost.Attachments, "Opportunity #OPP-000035341 • United States", "footer should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "CRM Bot", "author_name should be indexed")
|
||||
|
||||
t.Logf("Attachments field content: %s", esPost.Attachments)
|
||||
}
|
||||
|
||||
// TestESPostFromPost_APIJSONUnmarshal simulates a post created via the REST API
|
||||
// where Props are deserialized from JSON (attachments arrive as []any directly).
|
||||
func TestESPostFromPost_APIJSONUnmarshal(t *testing.T) {
|
||||
// Simulate what happens when a bot POSTs JSON to /api/v4/posts
|
||||
postJSON := `{
|
||||
"channel_id": "` + model.NewId() + `",
|
||||
"message": "",
|
||||
"props": {
|
||||
"attachments": [{
|
||||
"title": "Account: Acme Corp",
|
||||
"text": "Renewal ARR: $49,140.00",
|
||||
"footer": "Opportunity #OPP-000035341",
|
||||
"author_name": "CRM Bot",
|
||||
"fields": [
|
||||
{"title": "Sales Rep", "value": "Jane Smith"},
|
||||
{"title": "Seats", "value": 350}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}`
|
||||
|
||||
var post model.Post
|
||||
err := json.Unmarshal([]byte(postJSON), &post)
|
||||
require.NoError(t, err)
|
||||
post.Id = model.NewId()
|
||||
post.UserId = model.NewId()
|
||||
post.CreateAt = model.GetMillis()
|
||||
|
||||
// The CreatePost conversion at post.go:305-315 would NOT trigger here
|
||||
// because the type is already []any, not []*MessageAttachment
|
||||
_, typeOk := post.GetProp(model.PostPropsAttachments).([]*model.MessageAttachment)
|
||||
assert.False(t, typeOk, "API-created post should have []any, not []*MessageAttachment")
|
||||
|
||||
esPost, err := ESPostFromPost(&post, model.NewId(), "O")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, esPost.Attachments, "Account: Acme Corp", "title should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Renewal ARR: $49,140.00", "text should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Sales Rep", "field title should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Jane Smith", "field value should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "350", "numeric field value should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "Opportunity #OPP-000035341", "footer should be indexed")
|
||||
assert.Contains(t, esPost.Attachments, "CRM Bot", "author_name should be indexed")
|
||||
|
||||
t.Logf("Attachments field content: %s", esPost.Attachments)
|
||||
}
|
||||
|
||||
func TestGetMatchesForHit(t *testing.T) {
|
||||
snippets := map[string][]string{
|
||||
"message": {
|
||||
|
||||
Reference in New Issue
Block a user