feat(conversations): align filter query operators
This commit is contained in:
@@ -1052,6 +1052,69 @@ func (s *ConversationCrudTestSuite) TestFilter_ChatwootPayloadStatus() {
|
||||
assert.Equal(s.T(), float64(s.testConv.ID), resp.Payload[0]["id"])
|
||||
}
|
||||
|
||||
func (s *ConversationCrudTestSuite) TestFilter_ChatwootPayloadQueryOperatorChain() {
|
||||
primaryID := uint(12345)
|
||||
s.testConv.Status = "open"
|
||||
s.testConv.Priority = "urgent"
|
||||
s.testConv.DisplayID = &primaryID
|
||||
s.Require().NoError(s.db.Save(s.testConv).Error)
|
||||
|
||||
secondaryID := uint(67890)
|
||||
secondary := &model.Conversation{AccountID: s.testAccount.ID, InboxID: s.testInbox.ID, ContactID: s.testContact.ID, DisplayID: &secondaryID, Status: "resolved", Priority: "low", ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(secondary).Error)
|
||||
|
||||
nonMatchingID := uint(11111)
|
||||
nonMatching := &model.Conversation{AccountID: s.testAccount.ID, InboxID: s.testInbox.ID, ContactID: s.testContact.ID, DisplayID: &nonMatchingID, Status: "resolved", Priority: "low", ChannelType: "web_widget", Channel: "web_widget"}
|
||||
s.Require().NoError(s.db.Create(nonMatching).Error)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"payload": []map[string]interface{}{
|
||||
{"attribute_key": "status", "filter_operator": "equal_to", "values": []string{"open"}, "query_operator": "OR"},
|
||||
{"attribute_key": "priority", "filter_operator": "equal_to", "values": []string{"low"}, "query_operator": "AND"},
|
||||
{"attribute_key": "display_id", "filter_operator": "equal_to", "values": []string{"67890"}},
|
||||
},
|
||||
})
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", s.accountURL()+"/conversations/filter", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
var resp struct {
|
||||
Meta struct {
|
||||
AllCount int64 `json:"all_count"`
|
||||
} `json:"meta"`
|
||||
Payload []map[string]interface{} `json:"payload"`
|
||||
}
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
assert.Equal(s.T(), int64(2), resp.Meta.AllCount)
|
||||
s.Require().Len(resp.Payload, 2)
|
||||
ids := []float64{resp.Payload[0]["id"].(float64), resp.Payload[1]["id"].(float64)}
|
||||
assert.ElementsMatch(s.T(), []float64{float64(primaryID), float64(secondaryID)}, ids)
|
||||
}
|
||||
|
||||
func (s *ConversationCrudTestSuite) TestFilter_ChatwootPayloadInvalidQueryOperator() {
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"payload": []map[string]interface{}{
|
||||
{
|
||||
"attribute_key": "status",
|
||||
"filter_operator": "equal_to",
|
||||
"values": []string{"open"},
|
||||
"query_operator": "XOR",
|
||||
},
|
||||
},
|
||||
})
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", s.accountURL()+"/conversations/filter", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(s.T(), http.StatusUnprocessableEntity, w.Code)
|
||||
var resp map[string]string
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
assert.Equal(s.T(), "Query operator must be either \"AND\" or \"OR\".", resp["error"])
|
||||
}
|
||||
|
||||
func (s *ConversationCrudTestSuite) TestFilter_ChatwootPayloadInvalidAttribute() {
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"payload": []map[string]interface{}{
|
||||
|
||||
@@ -893,9 +893,12 @@ func (s *ConversationService) Filter(ctx context.Context, accountID uint, userID
|
||||
}
|
||||
|
||||
func (s *ConversationService) applyConversationFilterPayload(query *gorm.DB, payload []ConversationFilterCondition) (*gorm.DB, error) {
|
||||
clauses := make([]string, 0, len(payload)*2)
|
||||
args := make([]any, 0, len(payload))
|
||||
for _, condition := range payload {
|
||||
attribute := strings.TrimSpace(condition.AttributeKey)
|
||||
operator := strings.TrimSpace(condition.FilterOperator)
|
||||
queryOperator := strings.ToUpper(strings.TrimSpace(condition.QueryOperator))
|
||||
values := conversationFilterStringValues(condition.Values)
|
||||
|
||||
column, allowedOperators, err := conversationFilterColumn(attribute)
|
||||
@@ -909,16 +912,40 @@ func (s *ConversationService) applyConversationFilterPayload(query *gorm.DB, pay
|
||||
return nil, fmt.Errorf("Invalid value for %s", attribute)
|
||||
}
|
||||
|
||||
if len(clauses) > 0 && !conversationFilterIsQueryOperator(clauses[len(clauses)-1]) {
|
||||
clauses = append(clauses, "AND")
|
||||
}
|
||||
switch operator {
|
||||
case "equal_to":
|
||||
query = query.Where(column+" IN ?", values)
|
||||
clauses = append(clauses, column+" IN ?")
|
||||
args = append(args, values)
|
||||
case "not_equal_to":
|
||||
query = query.Where(column+" NOT IN ?", values)
|
||||
clauses = append(clauses, column+" NOT IN ?")
|
||||
args = append(args, values)
|
||||
}
|
||||
|
||||
if queryOperator != "" {
|
||||
if queryOperator != "AND" && queryOperator != "OR" {
|
||||
return nil, fmt.Errorf("Query operator must be either \"AND\" or \"OR\".")
|
||||
}
|
||||
clauses = append(clauses, queryOperator)
|
||||
}
|
||||
}
|
||||
if len(clauses) == 0 {
|
||||
return query, nil
|
||||
}
|
||||
last := clauses[len(clauses)-1]
|
||||
if conversationFilterIsQueryOperator(last) {
|
||||
clauses = clauses[:len(clauses)-1]
|
||||
}
|
||||
query = query.Where(strings.Join(clauses, " "), args...)
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func conversationFilterIsQueryOperator(value string) bool {
|
||||
return value == "AND" || value == "OR"
|
||||
}
|
||||
|
||||
func conversationFilterColumn(attribute string) (string, []string, error) {
|
||||
switch attribute {
|
||||
case "status":
|
||||
|
||||
Reference in New Issue
Block a user