feat(search): align time filters

This commit is contained in:
2026-06-07 07:49:07 +08:00
parent 7fb1cdab22
commit 9a7e455e1c
8 changed files with 213 additions and 37 deletions
+23 -9
View File
@@ -176,7 +176,7 @@ func (r *SearchRepo) searchConversationsInternal(ctx context.Context, accountID
q = applyConversationFilters(q, filter)
// Apply date range filter
q = applyDateRangeFilter(q, filter)
q = applyDateRangeFilter(q, filter, "last_activity_at", true)
// Count
if err := q.Count(&total).Error; err != nil {
@@ -230,7 +230,7 @@ func (r *SearchRepo) searchMessagesInternal(ctx context.Context, accountID uint,
q = applyMessageFilters(q, filter)
// Apply date range filter
q = applyDateRangeFilter(q, filter)
q = applyDateRangeFilter(q, filter, "created_at", false)
// Count
if err := q.Count(&total).Error; err != nil {
@@ -323,7 +323,7 @@ func (r *SearchRepo) searchContactsInternal(ctx context.Context, accountID uint,
q = applyContactFilters(q, filter)
// Apply date range filter
q = applyDateRangeFilter(q, filter)
q = applyDateRangeFilter(q, filter, "last_activity_at", true)
// Count
if err := q.Count(&total).Error; err != nil {
@@ -363,7 +363,7 @@ func (r *SearchRepo) searchCompaniesInternal(ctx context.Context, accountID uint
}
}
q = applyDateRangeFilter(q, filter)
q = applyDateRangeFilter(q, filter, "created_at", false)
if err := q.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("search companies count: %w", err)
}
@@ -408,7 +408,7 @@ func (r *SearchRepo) searchArticlesInternal(ctx context.Context, accountID uint,
q = applyArticleFilters(q, filter)
// Apply date range filter
q = applyDateRangeFilter(q, filter)
q = applyDateRangeFilter(q, filter, "updated_at", false)
// Count
if err := q.Count(&total).Error; err != nil {
@@ -583,18 +583,32 @@ func applyArticleFilters(q *gorm.DB, filter *RepoSearchFilter) *gorm.DB {
return q
}
// applyDateRangeFilter applies a date range filter on created_at.
func applyDateRangeFilter(q *gorm.DB, filter *RepoSearchFilter) *gorm.DB {
// applyDateRangeFilter applies Chatwoot SearchService time filters using the
// entity-specific column selected by the caller.
func applyDateRangeFilter(q *gorm.DB, filter *RepoSearchFilter, columnName string, unixSeconds bool) *gorm.DB {
if filter == nil || !filter.AdvancedSearchEnabled {
return q
}
if columnName == "" {
columnName = "created_at"
}
if filter.DateFrom != nil {
q = q.Where("created_at >= ?", filter.DateFrom)
q = q.Where(columnName+" >= ?", dateRangeValue(filter.DateFrom, unixSeconds))
}
if filter.DateTo != nil {
q = q.Where("created_at <= ?", filter.DateTo)
q = q.Where(columnName+" <= ?", dateRangeValue(filter.DateTo, unixSeconds))
}
return q
}
func dateRangeValue(value *time.Time, unixSeconds bool) any {
if value == nil {
return nil
}
if unixSeconds {
return value.Unix()
}
return value
}
+66 -2
View File
@@ -350,6 +350,28 @@ func TestSearchRepo_SearchConversations_WithStatusFilter(t *testing.T) {
assert.Equal(t, "open", results[0].Status)
}
func TestSearchRepo_SearchConversations_DateFilterUsesLastActivityAt(t *testing.T) {
db := setupTestDB(t)
repo := NewSearchRepo(db)
account := createTestAccountForSearch(t, db)
cutoff := time.Date(2026, 6, 7, 12, 0, 0, 0, time.UTC)
oldActivity := cutoff.Add(-time.Hour).Unix()
recentActivity := cutoff.Add(time.Hour).Unix()
oldConv := createTestConversationForSearch(t, db, account.ID, "open", "date-filter")
recentConv := createTestConversationForSearch(t, db, account.ID, "open", "date-filter")
require.NoError(t, db.Model(oldConv).Update("last_activity_at", oldActivity).Error)
require.NoError(t, db.Model(recentConv).Update("last_activity_at", recentActivity).Error)
filter := &search.SearchFilter{Page: 1, PerPage: 25, AdvancedSearchEnabled: true, DateFrom: &cutoff}
results, total, err := repo.SearchConversations(context.Background(), account.ID, "", filter)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
require.Len(t, results, 1)
assert.Equal(t, recentConv.ID, results[0].ID)
}
// ========== SearchMessages DB tests (PG only for ILIKE) ==========
func TestSearchRepo_SearchMessages_ContentMatch(t *testing.T) {
@@ -463,11 +485,33 @@ func TestSearchRepo_SearchContacts_ResolvedScopeCRMV2(t *testing.T) {
assert.Equal(t, lead.ID, results[0].ID)
}
func TestSearchRepo_SearchContacts_DateFilterUsesLastActivityAt(t *testing.T) {
db := setupTestDB(t)
repo := NewSearchRepo(db)
account := createTestAccountForSearch(t, db)
cutoff := time.Date(2026, 6, 7, 12, 0, 0, 0, time.UTC)
oldActivity := cutoff.Add(-time.Hour).Unix()
recentActivity := cutoff.Add(time.Hour).Unix()
oldContact := createTestContactForSearch(t, db, account.ID, "Old Activity", "old@example.com", "")
recentContact := createTestContactForSearch(t, db, account.ID, "Recent Activity", "recent@example.com", "")
require.NoError(t, db.Model(oldContact).Update("last_activity_at", oldActivity).Error)
require.NoError(t, db.Model(recentContact).Update("last_activity_at", recentActivity).Error)
filter := &search.SearchFilter{Page: 1, PerPage: 25, AdvancedSearchEnabled: true, DateFrom: &cutoff}
results, total, err := repo.SearchContacts(context.Background(), account.ID, "", filter)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
require.Len(t, results, 1)
assert.Equal(t, recentContact.ID, results[0].ID)
}
// ========== SearchArticles DB tests (PG only for ILIKE) ==========
func TestSearchRepo_SearchArticles_TitleMatch(t *testing.T) {
skipIfSQLite(t)
db := setupTestDB(t)
db := setupTestDB(t, &model.Article{})
repo := NewSearchRepo(db)
account := createTestAccountForSearch(t, db)
@@ -483,7 +527,7 @@ func TestSearchRepo_SearchArticles_TitleMatch(t *testing.T) {
func TestSearchRepo_SearchArticles_WithStatusFilter(t *testing.T) {
skipIfSQLite(t)
db := setupTestDB(t)
db := setupTestDB(t, &model.Article{})
repo := NewSearchRepo(db)
account := createTestAccountForSearch(t, db)
@@ -499,6 +543,26 @@ func TestSearchRepo_SearchArticles_WithStatusFilter(t *testing.T) {
}
}
func TestSearchRepo_SearchArticles_DateFilterUsesUpdatedAt(t *testing.T) {
db := setupTestDB(t, &model.Article{})
repo := NewSearchRepo(db)
account := createTestAccountForSearch(t, db)
cutoff := time.Date(2026, 6, 7, 12, 0, 0, 0, time.UTC)
oldArticle := createTestArticleForSearch(t, db, account.ID, 1, "Old Updated Article", "desc", "content", "published")
recentArticle := createTestArticleForSearch(t, db, account.ID, 1, "Recent Updated Article", "desc", "content", "published")
require.NoError(t, db.Model(oldArticle).UpdateColumn("updated_at", cutoff.Add(-time.Hour)).Error)
require.NoError(t, db.Model(recentArticle).UpdateColumn("updated_at", cutoff.Add(time.Hour)).Error)
filter := &search.SearchFilter{Page: 1, PerPage: 25, AdvancedSearchEnabled: true, DateFrom: &cutoff}
results, total, err := repo.SearchArticles(context.Background(), account.ID, "", filter)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
require.Len(t, results, 1)
assert.Equal(t, recentArticle.ID, results[0].ID)
}
// ========== Interface satisfaction test ==========
func TestSearchRepo_SatisfiesSearchRepoInterface(t *testing.T) {