feat(reports): align conversation agent metrics
This commit is contained in:
@@ -416,11 +416,17 @@ func (h *AnalyticsHandler) Conversations(c *gin.Context) {
|
||||
reportType := c.Query("type")
|
||||
if reportType == "" {
|
||||
// Reference: Chatwoot returns head :unprocessable_entity if type is blank
|
||||
response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrBadRequest, "type parameter is required")
|
||||
c.Status(http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
page := 1
|
||||
if rawPage := c.Query("page"); rawPage != "" {
|
||||
if parsed, err := strconv.Atoi(rawPage); err == nil && parsed > 0 {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.svc.GetConversationsByType(c.Request.Context(), accountID, reportType, time.Time{}, time.Time{})
|
||||
result, err := h.svc.GetConversationsByType(c.Request.Context(), accountID, reportType, page)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Conversations report: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to generate conversations report")
|
||||
|
||||
@@ -40,6 +40,7 @@ func (s *AnalyticsHandlerTestSuite) SetupSuite() {
|
||||
s.Require().NoError(db.AutoMigrate(
|
||||
&model.Account{},
|
||||
&model.User{},
|
||||
&model.AccountUser{},
|
||||
&model.Inbox{},
|
||||
&model.Team{},
|
||||
&model.Tag{},
|
||||
@@ -70,6 +71,7 @@ func (s *AnalyticsHandlerTestSuite) SetupSuite() {
|
||||
accounts.GET("/reports/labels", s.handler.LabelMetrics)
|
||||
accounts.GET("/reports/teams", s.handler.TeamMetrics)
|
||||
accounts.GET("/reports/conversation_traffic", s.handler.ConversationTraffic)
|
||||
accounts.GET("/reports/conversations", s.handler.Conversations)
|
||||
accounts.GET("/reports/conversations_summary", s.handler.ConversationsSummary)
|
||||
s.router = r
|
||||
}
|
||||
@@ -88,6 +90,7 @@ func (s *AnalyticsHandlerTestSuite) SetupTest() {
|
||||
s.db.Exec("DELETE FROM tags")
|
||||
s.db.Exec("DELETE FROM teams")
|
||||
s.db.Exec("DELETE FROM inboxes")
|
||||
s.db.Exec("DELETE FROM account_users")
|
||||
s.db.Exec("DELETE FROM users")
|
||||
}
|
||||
|
||||
@@ -416,6 +419,56 @@ func (s *AnalyticsHandlerTestSuite) TestConversationsSummary_ReturnsCSVDownload(
|
||||
s.Equal("1", rows[2][2])
|
||||
}
|
||||
|
||||
func (s *AnalyticsHandlerTestSuite) TestConversations_MissingTypeReturnsEmptyUnprocessableEntity() {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/accounts/1/reports/conversations", nil)
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusUnprocessableEntity, w.Code)
|
||||
s.Empty(w.Body.String())
|
||||
}
|
||||
|
||||
func (s *AnalyticsHandlerTestSuite) TestConversations_AgentTypeReturnsChatwootAgentMetrics() {
|
||||
agentOne := model.User{AccountID: s.accountID, Name: "Low", Email: "low@example.com", Password: "secret", AvatarURL: "https://example.com/low.png", Active: true}
|
||||
agentTwo := model.User{AccountID: s.accountID, Name: "High", Email: "high@example.com", Password: "secret", Active: true}
|
||||
s.Require().NoError(s.db.Create(&agentOne).Error)
|
||||
s.Require().NoError(s.db.Create(&agentTwo).Error)
|
||||
s.Require().NoError(s.db.Create(&model.AccountUser{AccountID: s.accountID, UserID: agentOne.ID, Role: "agent", Availability: "offline"}).Error)
|
||||
s.Require().NoError(s.db.Create(&model.AccountUser{AccountID: s.accountID, UserID: agentTwo.ID, Role: "agent", Availability: "online"}).Error)
|
||||
inbox := model.Inbox{AccountID: s.accountID, Name: "Web", ChannelType: "web_widget", ChannelID: 1, Enabled: true}
|
||||
s.Require().NoError(s.db.Create(&inbox).Error)
|
||||
lowFirstReply := int64(1735689700)
|
||||
for i := 0; i < 2; i++ {
|
||||
conv := model.Conversation{AccountID: s.accountID, InboxID: inbox.ID, ContactID: 1, AssigneeID: &agentTwo.ID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(&conv).Error)
|
||||
}
|
||||
lowConv := model.Conversation{AccountID: s.accountID, InboxID: inbox.ID, ContactID: 1, AssigneeID: &agentOne.ID, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget", FirstReplyCreatedAt: &lowFirstReply}
|
||||
s.Require().NoError(s.db.Create(&lowConv).Error)
|
||||
pendingForHigh := model.Conversation{AccountID: s.accountID, InboxID: inbox.ID, ContactID: 1, AssigneeID: &agentTwo.ID, Status: string(model.ConversationStatusPending), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(&pendingForHigh).Error)
|
||||
unassigned := model.Conversation{AccountID: s.accountID, InboxID: inbox.ID, ContactID: 1, Status: string(model.ConversationStatusOpen), ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(&unassigned).Error)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/accounts/1/reports/conversations?type=agent&page=1", nil)
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
s.Equal(http.StatusOK, w.Code)
|
||||
var payload []map[string]interface{}
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &payload))
|
||||
s.Require().Len(payload, 2)
|
||||
s.Equal("High", payload[0]["name"])
|
||||
s.Equal("high@example.com", payload[0]["email"])
|
||||
s.Equal("online", payload[0]["availability"])
|
||||
metric := payload[0]["metric"].(map[string]interface{})
|
||||
s.Equal(float64(2), metric["open"])
|
||||
s.Equal(float64(2), metric["unattended"])
|
||||
s.NotContains(metric, "unassigned")
|
||||
s.NotContains(metric, "pending")
|
||||
s.Equal("Low", payload[1]["name"])
|
||||
s.Equal("https://example.com/low.png", payload[1]["thumbnail"])
|
||||
}
|
||||
|
||||
// ========== Nil service guard ==========
|
||||
|
||||
func (s *AnalyticsHandlerTestSuite) TestNilService() {
|
||||
|
||||
Reference in New Issue
Block a user