feat(reports): filter grouped live metrics by team

This commit is contained in:
2026-06-07 01:01:27 +08:00
parent 64d5e2420e
commit 18f6403128
5 changed files with 69 additions and 22 deletions
+21 -9
View File
@@ -31,14 +31,9 @@ func (h *LiveReportHandler) ConversationMetrics(c *gin.Context) {
return
}
teamID := uint(0)
if teamIDStr := c.Query("team_id"); teamIDStr != "" {
parsed, err := strconv.ParseUint(teamIDStr, 10, 64)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team_id")
return
}
teamID = uint(parsed)
teamID, ok := parseLiveReportTeamID(c)
if !ok {
return
}
result, err := h.svc.GetConversationMetricsForTeam(c.Request.Context(), accountID, teamID)
@@ -68,8 +63,12 @@ func (h *LiveReportHandler) GroupedConversationMetrics(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid group_by"})
return
}
teamID, ok := parseLiveReportTeamID(c)
if !ok {
return
}
result, err := h.svc.GetGroupedConversationMetrics(c.Request.Context(), accountID, groupBy)
result, err := h.svc.GetGroupedConversationMetricsForTeam(c.Request.Context(), accountID, groupBy, teamID)
if err != nil {
applogger.L().Errorf("Grouped conversation metrics: %v", err)
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to get grouped conversation metrics")
@@ -78,3 +77,16 @@ func (h *LiveReportHandler) GroupedConversationMetrics(c *gin.Context) {
c.JSON(http.StatusOK, result)
}
func parseLiveReportTeamID(c *gin.Context) (uint, bool) {
teamIDStr := c.Query("team_id")
if teamIDStr == "" {
return 0, true
}
parsed, err := strconv.ParseUint(teamIDStr, 10, 64)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team_id")
return 0, false
}
return uint(parsed), true
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/gin-gonic/gin"
@@ -25,7 +26,7 @@ type LiveReportHandlerTestSuite struct {
func (s *LiveReportHandlerTestSuite) SetupSuite() {
s.db, _ = gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
s.db.AutoMigrate(&model.Account{}, &model.Conversation{}, &model.ReportingEventsRollup{})
s.db.AutoMigrate(&model.Account{}, &model.Team{}, &model.Conversation{}, &model.ReportingEventsRollup{})
anSvc := service.NewAnalyticsService(
repository.NewReportingEventRepo(s.db),
@@ -42,6 +43,7 @@ func (s *LiveReportHandlerTestSuite) SetupSuite() {
func (s *LiveReportHandlerTestSuite) SetupTest() {
s.db.Exec("DELETE FROM conversations")
s.db.Exec("DELETE FROM teams")
}
func TestLiveReportHandlerTestSuite(t *testing.T) {
@@ -106,3 +108,27 @@ func (s *LiveReportHandlerTestSuite) TestGroupedConversationMetrics_ByAssignee()
s.Equal(float64(1), body[1]["unattended"])
s.Equal(float64(0), body[1]["unassigned"])
}
func (s *LiveReportHandlerTestSuite) TestGroupedConversationMetrics_FiltersByTeamID() {
team := model.Team{AccountID: 1, Name: "Support"}
otherTeam := model.Team{AccountID: 1, Name: "Other"}
s.Require().NoError(s.db.Create(&team).Error)
s.Require().NoError(s.db.Create(&otherTeam).Error)
agentID := uint(7)
teamConversation := model.Conversation{AccountID: 1, TeamID: &team.ID, AssigneeID: &agentID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
otherConversation := model.Conversation{AccountID: 1, TeamID: &otherTeam.ID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
s.Require().NoError(s.db.Create(&teamConversation).Error)
s.Require().NoError(s.db.Create(&otherConversation).Error)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/accounts/1/live_reports/grouped_conversation_metrics?group_by=assignee_id&team_id="+strconv.FormatUint(uint64(team.ID), 10), nil)
s.router.ServeHTTP(w, req)
s.Equal(http.StatusOK, w.Code)
var body []map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
s.Len(body, 1)
s.Equal(float64(agentID), body[0]["assignee_id"])
s.Equal(float64(1), body[0]["open"])
s.Equal(float64(1), body[0]["unattended"])
s.Equal(float64(0), body[0]["unassigned"])
}
+9 -7
View File
@@ -375,7 +375,7 @@ func parseReportDimensionID(raw string) uint {
return uint(parsed)
}
func (s *AnalyticsService) groupedLiveConversationMetrics(ctx context.Context, accountID uint, groupBy string) ([]map[string]interface{}, error) {
func (s *AnalyticsService) groupedLiveConversationMetrics(ctx context.Context, accountID uint, groupBy string, teamID uint) ([]map[string]interface{}, error) {
if groupBy != "team_id" && groupBy != "assignee_id" {
return nil, errors.New("invalid group_by")
}
@@ -389,11 +389,13 @@ func (s *AnalyticsService) groupedLiveConversationMetrics(ctx context.Context, a
}
load := func(where string) (map[uint]int64, error) {
var rows []row
if err := db.WithContext(ctx).Model(&model.Conversation{}).
q := db.WithContext(ctx).Model(&model.Conversation{}).
Select(groupBy+" AS group_id, COUNT(*) AS count").
Where("account_id = ? AND status = ? "+where, accountID, string(model.ConversationStatusOpen)).
Group(groupBy).
Scan(&rows).Error; err != nil {
Where("account_id = ? AND status = ? "+where, accountID, string(model.ConversationStatusOpen))
if teamID > 0 {
q = q.Where("team_id = ?", teamID)
}
if err := q.Group(groupBy).Scan(&rows).Error; err != nil {
return nil, err
}
counts := map[uint]int64{}
@@ -591,9 +593,9 @@ func (s *AnalyticsService) conversationMetricsByType(ctx context.Context, accoun
case "account":
return s.liveConversationMetrics(ctx, accountID, 0)
case "agent":
return s.groupedLiveConversationMetrics(ctx, accountID, "assignee_id")
return s.groupedLiveConversationMetrics(ctx, accountID, "assignee_id", 0)
case "team":
return s.groupedLiveConversationMetrics(ctx, accountID, "team_id")
return s.groupedLiveConversationMetrics(ctx, accountID, "team_id", 0)
default:
return s.liveConversationMetrics(ctx, accountID, 0)
}
+5 -1
View File
@@ -254,7 +254,11 @@ func (s *AnalyticsService) GetConversationMetricsForTeam(ctx context.Context, ac
// GetGroupedConversationMetrics returns conversation metrics grouped by team_id or assignee_id.
// Reference: Chatwoot live_reports#grouped_conversation_metrics
func (s *AnalyticsService) GetGroupedConversationMetrics(ctx context.Context, accountID uint, groupBy string) ([]map[string]interface{}, error) {
return s.groupedLiveConversationMetrics(ctx, accountID, groupBy)
return s.GetGroupedConversationMetricsForTeam(ctx, accountID, groupBy, 0)
}
func (s *AnalyticsService) GetGroupedConversationMetricsForTeam(ctx context.Context, accountID uint, groupBy string, teamID uint) ([]map[string]interface{}, error) {
return s.groupedLiveConversationMetrics(ctx, accountID, groupBy, teamID)
}
// GetReportSummary returns the Chatwoot v2 report summary with previous-period data.