feat(conversations): align meta counts
This commit is contained in:
@@ -635,13 +635,25 @@ func (h *ConversationHandler) Meta(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
meta, svcErr := h.conversationSvc.GetMeta(c.Request.Context(), accountID)
|
||||
var params service.FilterParams
|
||||
if err := c.ShouldBindQuery(¶ms); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if params.Labels == "" {
|
||||
labels := append(c.QueryArray("labels"), c.QueryArray("labels[]")...)
|
||||
if len(labels) > 0 {
|
||||
params.Labels = strings.Join(labels, ",")
|
||||
}
|
||||
}
|
||||
|
||||
meta, svcErr := h.conversationSvc.GetMeta(c.Request.Context(), accountID, currentUserID(c), params)
|
||||
if svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, meta)
|
||||
c.JSON(http.StatusOK, gin.H{"meta": meta})
|
||||
}
|
||||
|
||||
// Unread marks a conversation as unread by resetting agent_last_seen_at.
|
||||
|
||||
@@ -158,17 +158,20 @@ func (s *ConversationHandlerTestSuite) TearDownTest() {
|
||||
s.db.Exec("DELETE FROM contact_inboxes")
|
||||
s.db.Exec("DELETE FROM contacts")
|
||||
s.db.Exec("DELETE FROM inbox_members")
|
||||
s.db.Exec("DELETE FROM account_users")
|
||||
s.db.Exec("DELETE FROM inboxes")
|
||||
s.db.Exec("DELETE FROM messages")
|
||||
s.db.Exec("DELETE FROM accounts")
|
||||
|
||||
// Re-seed base data
|
||||
account := &model.Account{Name: "ConvHandlerTestOrg", Locale: "en", Active: true}
|
||||
account := &model.Account{Name: "ConvHandlerTestOrg", Locale: "en", Active: true, FeatureFlags: `{"conversation_unread_counts":true}`}
|
||||
s.Require().NoError(s.db.Create(account).Error)
|
||||
s.testAccount = account
|
||||
s.Require().NoError(s.db.Create(&model.AccountUser{AccountID: account.ID, UserID: s.testUser.ID, Role: "administrator"}).Error)
|
||||
|
||||
inbox := &model.Inbox{AccountID: account.ID, Name: "ConvHandlerTestInbox", ChannelType: "web_widget", ChannelID: 1}
|
||||
s.Require().NoError(s.db.Create(inbox).Error)
|
||||
s.Require().NoError(s.db.Create(&model.InboxMember{InboxID: inbox.ID, UserID: s.testUser.ID}).Error)
|
||||
|
||||
contact := &model.Contact{AccountID: account.ID, Name: "ConvHandlerTestContact"}
|
||||
s.Require().NoError(s.db.Create(contact).Error)
|
||||
@@ -193,17 +196,44 @@ func (s *ConversationHandlerTestSuite) TestMeta_Success() {
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp struct {
|
||||
Success bool `json:"success"`
|
||||
Data struct {
|
||||
TotalCount int64 `json:"total_count"`
|
||||
StatusCounts map[string]int64 `json:"status_counts"`
|
||||
LabelCounts map[string]int64 `json:"label_counts"`
|
||||
} `json:"data"`
|
||||
Meta struct {
|
||||
MineCount int64 `json:"mine_count"`
|
||||
AssignedCount int64 `json:"assigned_count"`
|
||||
UnassignedCount int64 `json:"unassigned_count"`
|
||||
AllCount int64 `json:"all_count"`
|
||||
} `json:"meta"`
|
||||
}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.True(s.T(), resp.Success)
|
||||
assert.Equal(s.T(), int64(1), resp.Data.TotalCount)
|
||||
assert.NotContains(s.T(), w.Body.String(), "success")
|
||||
assert.Equal(s.T(), int64(1), resp.Meta.AllCount)
|
||||
assert.Equal(s.T(), int64(1), resp.Meta.UnassignedCount)
|
||||
}
|
||||
|
||||
func (s *ConversationHandlerTestSuite) TestMeta_FiltersStatusAndIgnoresAssigneeTypeForCounts() {
|
||||
assigned := &model.Conversation{AccountID: s.testAccount.ID, InboxID: s.testConv.InboxID, ContactID: s.testConv.ContactID, AssigneeID: &s.testUser.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(assigned).Error)
|
||||
resolved := &model.Conversation{AccountID: s.testAccount.ID, InboxID: s.testConv.InboxID, ContactID: s.testConv.ContactID, AssigneeID: &s.testUser.ID, Status: "resolved", ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(resolved).Error)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", s.accountURL()+"/conversations/meta?assignee_type=assigned", nil)
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
var resp struct {
|
||||
Meta struct {
|
||||
MineCount int64 `json:"mine_count"`
|
||||
AssignedCount int64 `json:"assigned_count"`
|
||||
UnassignedCount int64 `json:"unassigned_count"`
|
||||
AllCount int64 `json:"all_count"`
|
||||
} `json:"meta"`
|
||||
}
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
assert.Equal(s.T(), int64(1), resp.Meta.MineCount)
|
||||
assert.Equal(s.T(), int64(1), resp.Meta.AssignedCount)
|
||||
assert.Equal(s.T(), int64(1), resp.Meta.UnassignedCount)
|
||||
assert.Equal(s.T(), int64(2), resp.Meta.AllCount)
|
||||
}
|
||||
|
||||
func (s *ConversationHandlerTestSuite) TestMeta_InvalidAccountID() {
|
||||
|
||||
@@ -359,65 +359,104 @@ func (r *ConversationRepo) BulkUpdateStatus(ctx context.Context, accountID uint,
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// ConversationMeta holds aggregated statistics for conversations.
|
||||
// ConversationMeta holds Chatwoot conversation sidebar counts.
|
||||
type ConversationMeta struct {
|
||||
StatusCounts map[string]int64 `json:"status_counts"`
|
||||
LabelCounts map[string]int64 `json:"label_counts"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
MineCount int64 `json:"mine_count"`
|
||||
AssignedCount int64 `json:"assigned_count"`
|
||||
UnassignedCount int64 `json:"unassigned_count"`
|
||||
AllCount int64 `json:"all_count"`
|
||||
}
|
||||
|
||||
// GetMeta retrieves aggregated conversation metadata for an account.
|
||||
// Reference: Chatwoot conversations_controller#meta
|
||||
func (r *ConversationRepo) GetMeta(ctx context.Context, accountID uint) (*ConversationMeta, error) {
|
||||
meta := &ConversationMeta{
|
||||
StatusCounts: make(map[string]int64),
|
||||
LabelCounts: make(map[string]int64),
|
||||
}
|
||||
// Reference: Chatwoot ConversationFinder#perform_meta_only.
|
||||
func (r *ConversationRepo) GetMeta(ctx context.Context, accountID, userID uint, params ConversationMetaParams) (*ConversationMeta, error) {
|
||||
base := r.conversationMetaBaseQuery(ctx, accountID, userID, params)
|
||||
|
||||
// Total count
|
||||
var total int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.Conversation{}).Where("account_id = ?", accountID).Count(&total).Error; err != nil {
|
||||
var mineCount, unassignedCount, allCount int64
|
||||
if err := base.Session(&gorm.Session{}).Where("conversations.assignee_id = ?", userID).Count(&mineCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta.TotalCount = total
|
||||
|
||||
// Status distribution
|
||||
type statusRow struct {
|
||||
Status string
|
||||
Count int64
|
||||
}
|
||||
var statusRows []statusRow
|
||||
if err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
||||
Select("status, count(*) as count").
|
||||
Where("account_id = ?", accountID).
|
||||
Group("status").Find(&statusRows).Error; err != nil {
|
||||
if err := base.Session(&gorm.Session{}).Where("conversations.assignee_id IS NULL").Count(&unassignedCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, row := range statusRows {
|
||||
meta.StatusCounts[row.Status] = row.Count
|
||||
}
|
||||
|
||||
// Label distribution — parse comma-separated labels field
|
||||
type labelRow struct {
|
||||
Labels string
|
||||
}
|
||||
var labelRows []labelRow
|
||||
if err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
||||
Select("labels").
|
||||
Where("account_id = ? AND labels != '' AND labels IS NOT NULL", accountID).
|
||||
Find(&labelRows).Error; err != nil {
|
||||
if err := base.Session(&gorm.Session{}).Count(&allCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, row := range labelRows {
|
||||
for _, label := range strings.Split(row.Labels, ",") {
|
||||
label = strings.TrimSpace(label)
|
||||
if label != "" {
|
||||
meta.LabelCounts[label]++
|
||||
}
|
||||
|
||||
return &ConversationMeta{
|
||||
MineCount: mineCount,
|
||||
AssignedCount: allCount - unassignedCount,
|
||||
UnassignedCount: unassignedCount,
|
||||
AllCount: allCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ConversationMetaParams struct {
|
||||
Status string
|
||||
InboxID *uint
|
||||
TeamID *uint
|
||||
Labels []string
|
||||
ConversationType string
|
||||
RestrictedInboxIDs []uint
|
||||
RestrictToInboxes bool
|
||||
}
|
||||
|
||||
func (r *ConversationRepo) conversationMetaBaseQuery(ctx context.Context, accountID, userID uint, params ConversationMetaParams) *gorm.DB {
|
||||
query := r.db.WithContext(ctx).Model(&model.Conversation{}).Where("conversations.account_id = ?", accountID)
|
||||
|
||||
if params.RestrictToInboxes {
|
||||
if len(params.RestrictedInboxIDs) == 0 {
|
||||
query = query.Where("1 = 0")
|
||||
} else {
|
||||
query = query.Where("conversations.inbox_id IN ?", params.RestrictedInboxIDs)
|
||||
}
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
if params.Status == "all" {
|
||||
// Chatwoot skips status filtering for status=all.
|
||||
} else if params.Status == "" {
|
||||
query = query.Where("conversations.status = ?", model.ConversationStatusOpen)
|
||||
} else {
|
||||
query = query.Where("conversations.status = ?", params.Status)
|
||||
}
|
||||
|
||||
if params.InboxID != nil {
|
||||
query = query.Where("conversations.inbox_id = ?", *params.InboxID)
|
||||
}
|
||||
if params.TeamID != nil {
|
||||
query = query.Where("conversations.team_id = ?", *params.TeamID)
|
||||
}
|
||||
if len(params.Labels) > 0 {
|
||||
conditions := make([]string, 0, len(params.Labels))
|
||||
args := make([]any, 0, len(params.Labels))
|
||||
for _, label := range params.Labels {
|
||||
label = strings.TrimSpace(label)
|
||||
if label == "" {
|
||||
continue
|
||||
}
|
||||
conditions = append(conditions, "conversations.labels LIKE ?")
|
||||
args = append(args, "%"+label+"%")
|
||||
}
|
||||
if len(conditions) > 0 {
|
||||
tagSubquery := r.db.WithContext(ctx).
|
||||
Table("conversation_labels").
|
||||
Select("conversation_labels.conversation_id").
|
||||
Joins("JOIN tags ON tags.id = conversation_labels.tag_id").
|
||||
Where("conversation_labels.account_id = ? AND tags.name IN ?", accountID, params.Labels)
|
||||
query = query.Where(r.db.Where(strings.Join(conditions, " OR "), args...).Or("conversations.id IN (?)", tagSubquery))
|
||||
}
|
||||
}
|
||||
|
||||
switch params.ConversationType {
|
||||
case "mention":
|
||||
query = query.Where("conversations.id IN (SELECT conversation_id FROM mentions WHERE account_id = ? AND user_id = ?)", accountID, userID)
|
||||
case "participating":
|
||||
query = query.Where("conversations.id IN (SELECT conversation_id FROM conversation_participants WHERE user_id = ?)", userID)
|
||||
case "unattended":
|
||||
query = query.Where("conversations.first_reply_created_at IS NULL OR conversations.waiting_since IS NOT NULL")
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// MarkUnread marks a conversation as unread by resetting agent_last_seen_at to nil.
|
||||
|
||||
@@ -435,6 +435,7 @@ func TestConversationRepo_CountByAccount(t *testing.T) {
|
||||
func TestConversationRepo_GetMeta(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := NewConversationRepo(db)
|
||||
userID := uint(42)
|
||||
|
||||
account := &model.Account{Name: "MetaTestOrg", Locale: "en", Active: true}
|
||||
require.NoError(t, db.Create(account).Error)
|
||||
@@ -445,24 +446,20 @@ func TestConversationRepo_GetMeta(t *testing.T) {
|
||||
contact := &model.Contact{AccountID: account.ID, Name: "MetaTestContact"}
|
||||
require.NoError(t, db.Create(contact).Error)
|
||||
|
||||
// Create conversations with different statuses and labels
|
||||
// Chatwoot meta defaults to open conversations and returns assignee buckets.
|
||||
createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
|
||||
assigned := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
|
||||
require.NoError(t, db.Model(assigned).Update("assignee_id", userID).Error)
|
||||
conv2 := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "resolved")
|
||||
// Update labels on conv2
|
||||
require.NoError(t, db.Model(conv2).Update("labels", "bug,critical").Error)
|
||||
createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "pending")
|
||||
conv4 := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
|
||||
require.NoError(t, db.Model(conv4).Update("labels", "bug,feature").Error)
|
||||
|
||||
meta, err := repo.GetMeta(context.Background(), account.ID)
|
||||
meta, err := repo.GetMeta(context.Background(), account.ID, userID, ConversationMetaParams{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(4), meta.TotalCount)
|
||||
assert.Equal(t, int64(2), meta.StatusCounts["open"])
|
||||
assert.Equal(t, int64(1), meta.StatusCounts["resolved"])
|
||||
assert.Equal(t, int64(1), meta.StatusCounts["pending"])
|
||||
assert.Equal(t, int64(2), meta.LabelCounts["bug"])
|
||||
assert.Equal(t, int64(1), meta.LabelCounts["critical"])
|
||||
assert.Equal(t, int64(1), meta.LabelCounts["feature"])
|
||||
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 TestConversationRepo_GetMeta_EmptyAccount(t *testing.T) {
|
||||
@@ -472,11 +469,10 @@ func TestConversationRepo_GetMeta_EmptyAccount(t *testing.T) {
|
||||
account := &model.Account{Name: "MetaEmptyOrg", Locale: "en", Active: true}
|
||||
require.NoError(t, db.Create(account).Error)
|
||||
|
||||
meta, err := repo.GetMeta(context.Background(), account.ID)
|
||||
meta, err := repo.GetMeta(context.Background(), account.ID, 0, ConversationMetaParams{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), meta.TotalCount)
|
||||
assert.Empty(t, meta.StatusCounts)
|
||||
assert.Empty(t, meta.LabelCounts)
|
||||
assert.Equal(t, int64(0), meta.AllCount)
|
||||
assert.Equal(t, int64(0), meta.UnassignedCount)
|
||||
}
|
||||
|
||||
func TestConversationRepo_GetMeta_WrongAccount(t *testing.T) {
|
||||
@@ -495,10 +491,36 @@ func TestConversationRepo_GetMeta_WrongAccount(t *testing.T) {
|
||||
createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
|
||||
|
||||
// Query a non-existent account ID — should return empty meta
|
||||
meta, err := repo.GetMeta(context.Background(), 9999)
|
||||
meta, err := repo.GetMeta(context.Background(), 9999, 0, ConversationMetaParams{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), meta.TotalCount)
|
||||
assert.Empty(t, meta.StatusCounts)
|
||||
assert.Equal(t, int64(0), meta.AllCount)
|
||||
}
|
||||
|
||||
func TestConversationRepo_GetMeta_FiltersStatusLabelsAndConversationType(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := NewConversationRepo(db)
|
||||
userID := uint(77)
|
||||
|
||||
account := &model.Account{Name: "MetaFilterOrg", Locale: "en", Active: true}
|
||||
require.NoError(t, db.Create(account).Error)
|
||||
inbox := &model.Inbox{AccountID: account.ID, Name: "MetaFilterInbox", ChannelType: "web_widget", ChannelID: 1}
|
||||
require.NoError(t, db.Create(inbox).Error)
|
||||
contact := &model.Contact{AccountID: account.ID, Name: "MetaFilterContact"}
|
||||
require.NoError(t, db.Create(contact).Error)
|
||||
|
||||
match := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
|
||||
firstReply := int64(1699999999)
|
||||
require.NoError(t, db.Model(match).Updates(map[string]any{"labels": "vip", "assignee_id": userID, "first_reply_created_at": firstReply}).Error)
|
||||
waiting := int64(1700000000)
|
||||
unattended := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
|
||||
require.NoError(t, db.Model(unattended).Updates(map[string]any{"labels": "vip", "waiting_since": waiting}).Error)
|
||||
resolved := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "resolved")
|
||||
require.NoError(t, db.Model(resolved).Update("labels", "vip").Error)
|
||||
|
||||
meta, err := repo.GetMeta(context.Background(), account.ID, userID, ConversationMetaParams{Labels: []string{"vip"}, ConversationType: "unattended"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), meta.AllCount)
|
||||
assert.Equal(t, int64(1), meta.UnassignedCount)
|
||||
}
|
||||
|
||||
// ========== MarkUnread Tests ==========
|
||||
@@ -642,4 +664,4 @@ func TestConversationRepo_UpdateCustomAttributes_EmptyJSON(t *testing.T) {
|
||||
updated, err := repo.FindByID(context.Background(), conv.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, updated.CustomAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ==========
|
||||
|
||||
Reference in New Issue
Block a user