feat(conversations): align meta counts

This commit is contained in:
2026-06-07 02:54:41 +08:00
parent 8591fe1d33
commit cdc0978476
7 changed files with 273 additions and 95 deletions
+47 -4
View File
@@ -921,10 +921,53 @@ func (s *ConversationService) UpdatePriority(ctx context.Context, accountID, id
return conversation, nil
}
// GetMeta retrieves aggregated conversation metadata (status counts, label counts) for an account.
// Reference: Chatwoot app/controllers/api/v1/conversations_controller.rb#meta
func (s *ConversationService) GetMeta(ctx context.Context, accountID uint) (*repository.ConversationMeta, error) {
return s.repo.GetMeta(ctx, accountID)
// GetMeta retrieves Chatwoot conversation sidebar counts for an account.
// Reference: ConversationFinder#perform_meta_only and conversations/meta.json.jbuilder.
func (s *ConversationService) GetMeta(ctx context.Context, accountID, userID uint, params FilterParams) (*repository.ConversationMeta, error) {
metaParams := repository.ConversationMetaParams{
Status: params.Status,
InboxID: params.InboxID,
TeamID: params.TeamID,
Labels: splitConversationMetaLabels(params.Labels),
ConversationType: params.ConversationType,
}
if userID != 0 {
var accountUser model.AccountUser
err := s.repo.DB().WithContext(ctx).Where("account_id = ? AND user_id = ?", accountID, userID).First(&accountUser).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
metaParams.RestrictToInboxes = true
return s.repo.GetMeta(ctx, accountID, userID, metaParams)
}
if err != nil {
return nil, err
}
if !accountUser.IsAdministrator() {
ids, err := s.visibleUnreadCountInboxIDs(ctx, accountID, userID)
if err != nil {
return nil, err
}
metaParams.RestrictToInboxes = true
metaParams.RestrictedInboxIDs = ids
}
}
return s.repo.GetMeta(ctx, accountID, userID, metaParams)
}
func splitConversationMetaLabels(labels string) []string {
if strings.TrimSpace(labels) == "" {
return nil
}
parts := strings.Split(labels, ",")
out := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
out = append(out, part)
}
}
return out
}
// MarkUnread marks a conversation as unread by setting agent_last_seen_at to
+39 -11
View File
@@ -277,20 +277,25 @@ func TestConversationService_GetMeta(t *testing.T) {
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
user := &model.User{Name: "Meta Agent", Email: "meta-agent@example.com"}
require.NoError(t, db.Create(user).Error)
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: "administrator"}).Error)
// Create conversations with different statuses
createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
// Chatwoot meta defaults to open conversations and reports assignee buckets.
openUnassigned := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
assert.NotZero(t, openUnassigned.ID)
openAssigned := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
require.NoError(t, db.Model(openAssigned).Update("assignee_id", user.ID).Error)
conv2 := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "resolved")
require.NoError(t, db.Model(conv2).Update("labels", "support").Error)
createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "pending")
meta, err := svc.GetMeta(context.Background(), account.ID)
meta, err := svc.GetMeta(context.Background(), account.ID, user.ID, FilterParams{})
assert.NoError(t, err)
assert.Equal(t, int64(3), meta.TotalCount)
assert.Equal(t, int64(1), meta.StatusCounts["open"])
assert.Equal(t, int64(1), meta.StatusCounts["resolved"])
assert.Equal(t, int64(1), meta.StatusCounts["pending"])
assert.Equal(t, int64(1), meta.LabelCounts["support"])
assert.Equal(t, int64(2), meta.AllCount)
assert.Equal(t, int64(1), meta.MineCount)
assert.Equal(t, int64(1), meta.AssignedCount)
assert.Equal(t, int64(1), meta.UnassignedCount)
}
func TestConversationService_GetMeta_EmptyAccount(t *testing.T) {
@@ -298,10 +303,33 @@ func TestConversationService_GetMeta_EmptyAccount(t *testing.T) {
account := createConversationServiceTestAccount(t, db)
meta, err := svc.GetMeta(context.Background(), account.ID)
meta, err := svc.GetMeta(context.Background(), account.ID, 0, FilterParams{})
assert.NoError(t, err)
assert.Equal(t, int64(0), meta.TotalCount)
assert.Empty(t, meta.StatusCounts)
assert.Equal(t, int64(0), meta.AllCount)
assert.Equal(t, int64(0), meta.UnassignedCount)
}
func TestConversationService_GetMeta_FiltersByLabelAndVisibility(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
hiddenInbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
user := &model.User{Name: "Scoped Meta Agent", Email: "scoped-meta@example.com"}
require.NoError(t, db.Create(user).Error)
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: "agent"}).Error)
require.NoError(t, db.Create(&model.InboxMember{InboxID: inbox.ID, UserID: user.ID}).Error)
visible := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
require.NoError(t, db.Model(visible).Updates(map[string]any{"labels": "vip", "assignee_id": user.ID}).Error)
hidden := createConversationServiceTestConversation(t, db, account.ID, hiddenInbox.ID, contact.ID, "open")
require.NoError(t, db.Model(hidden).Update("labels", "vip").Error)
meta, err := svc.GetMeta(context.Background(), account.ID, user.ID, FilterParams{Labels: "vip"})
require.NoError(t, err)
assert.Equal(t, int64(1), meta.AllCount)
assert.Equal(t, int64(1), meta.MineCount)
}
// ========== MarkUnread Tests ==========