feat(reports): align outgoing message counts

This commit is contained in:
2026-06-07 01:41:09 +08:00
parent f3fda02a9a
commit 2453a84098
5 changed files with 76 additions and 9 deletions
+1 -1
View File
@@ -595,7 +595,7 @@ func (h *AnalyticsHandler) OutgoingMessagesCount(c *gin.Context) {
}
groupBy := c.Query("group_by")
if groupBy != "agent" && groupBy != "team" && groupBy != "inbox" && groupBy != "label" {
response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrBadRequest, "invalid group_by")
c.Status(http.StatusUnprocessableEntity)
return
}
@@ -75,6 +75,7 @@ func (s *AnalyticsHandlerTestSuite) SetupSuite() {
accounts.GET("/reports/conversations", s.handler.Conversations)
accounts.GET("/reports/conversations_summary", s.handler.ConversationsSummary)
accounts.GET("/reports/inbox_label_matrix", s.handler.InboxLabelMatrix)
accounts.GET("/reports/outgoing_messages_count", s.handler.OutgoingMessagesCount)
s.router = r
}
@@ -504,6 +505,15 @@ func (s *AnalyticsHandlerTestSuite) TestInboxLabelMatrix_ParsesFrontendFilters()
s.Require().Len(payload["labels"], 1)
}
func (s *AnalyticsHandlerTestSuite) TestOutgoingMessagesCount_InvalidGroupByReturnsEmptyUnprocessableEntity() {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/accounts/1/reports/outgoing_messages_count?since=1735689600&until=1735776000&group_by=bad", nil)
s.router.ServeHTTP(w, req)
s.Equal(http.StatusUnprocessableEntity, w.Code)
s.Empty(w.Body.String())
}
// ========== Nil service guard ==========
func (s *AnalyticsHandlerTestSuite) TestNilService() {
+25
View File
@@ -212,6 +212,31 @@ func TestAnalyticsInboxLabelMatrixHonorsFiltersAndRange(t *testing.T) {
require.Len(t, payload["labels"], 1)
}
func TestAnalyticsOutgoingMessagesCountLabelUsesAccountLabelName(t *testing.T) {
db, svc, account, inbox, contact, user, _ := setupAnalyticsP513Test(t)
since := time.Date(2026, 6, 8, 0, 0, 0, 0, time.UTC)
until := since.Add(24 * time.Hour)
accountLabel := &model.Tag{AccountID: account.ID, Name: "vip"}
require.NoError(t, db.Create(accountLabel).Error)
otherAccount := &model.Account{Name: "Other"}
require.NoError(t, db.Create(otherAccount).Error)
foreignLabel := &model.Tag{AccountID: otherAccount.ID, Name: "vip"}
require.NoError(t, db.Create(foreignLabel).Error)
conversation := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, AssigneeID: &user.ID, ChannelType: inbox.ChannelType, Channel: inbox.ChannelType}
require.NoError(t, db.Create(conversation).Error)
senderID := user.ID
require.NoError(t, db.Create(&model.Message{Base: model.Base{CreatedAt: since.Add(time.Hour)}, AccountID: account.ID, InboxID: inbox.ID, ConversationID: conversation.ID, SenderID: &senderID, SenderType: "User", MessageType: string(model.MessageTypeOutgoing), Content: "hello"}).Error)
require.NoError(t, db.Create(&model.ConversationLabel{AccountID: account.ID, ConversationID: conversation.ID, TagID: foreignLabel.ID}).Error)
result, err := svc.GetOutgoingMessagesCountGrouped(context.Background(), account.ID, since, until, "label")
require.NoError(t, err)
rows := result.([]map[string]interface{})
require.Len(t, rows, 1)
assert.Equal(t, accountLabel.ID, rows[0]["id"])
assert.Equal(t, "vip", rows[0]["name"])
assert.Equal(t, int64(1), rows[0]["outgoing_messages_count"])
}
func TestAnalyticsTimeseriesAndRollupWorker(t *testing.T) {
db, svc, account, inbox, contact, user, _ := setupAnalyticsP513Test(t)
since := time.Date(2026, 6, 2, 0, 0, 0, 0, time.UTC)
+33 -4
View File
@@ -979,15 +979,44 @@ func (s *AnalyticsService) outgoingMessagesByInbox(ctx context.Context, db *gorm
}
func (s *AnalyticsService) outgoingMessagesByLabel(ctx context.Context, db *gorm.DB, accountID uint, since, until time.Time) ([]map[string]interface{}, error) {
var rows []outgoingCountRow
type labelRow struct {
Name string
Count int64
}
var rows []labelRow
err := db.WithContext(ctx).Table("messages").
Select("tags.id AS id, tags.name AS name, COUNT(*) AS count").
Select("tags.name AS name, COUNT(*) AS count").
Joins("INNER JOIN conversations ON conversations.id = messages.conversation_id").
Joins("INNER JOIN conversation_labels ON conversation_labels.conversation_id = conversations.id").
Joins("INNER JOIN tags ON tags.id = conversation_labels.tag_id").
Where("messages.account_id = ? AND messages.message_type = ? AND messages.created_at >= ? AND messages.created_at < ?", accountID, string(model.MessageTypeOutgoing), since, until).
Group("tags.id, tags.name").Scan(&rows).Error
return outgoingRows(rows, "label"), err
Group("tags.name").Scan(&rows).Error
if err != nil {
return nil, err
}
names := make([]string, 0, len(rows))
for _, row := range rows {
names = append(names, row.Name)
}
var labels []model.Tag
if len(names) > 0 {
if err := db.WithContext(ctx).Where("account_id = ? AND name IN ?", accountID, names).Find(&labels).Error; err != nil {
return nil, err
}
}
labelIDs := map[string]uint{}
for _, label := range labels {
labelIDs[label.Name] = label.ID
}
result := make([]map[string]interface{}, 0, len(rows))
for _, row := range rows {
var id interface{}
if labelID, ok := labelIDs[row.Name]; ok {
id = labelID
}
result = append(result, map[string]interface{}{"id": id, "name": row.Name, "outgoing_messages_count": row.Count})
}
return result, nil
}
func outgoingRows(rows []outgoingCountRow, _ string) []map[string]interface{} {