Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/const_var.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ const (
const (
Member = "member"
Tag = "tag"
TagValue = "value"
StartRange = "startRange"
EndRange = "endRange"
)
Expand Down
1 change: 1 addition & 0 deletions taggingapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func routeTaggingServiceApis(r *mux.Router, s *xhttp.WebconfigServer) {
taggingPath.HandleFunc("/{tag}/members", tag.GetTagMembersHandler).Methods("GET").Name("Get-tag-members")

taggingPath.HandleFunc("/members/{member}", tag.GetTagsByMemberHandler).Methods("GET").Name("Get-tags-by-member")
taggingPath.HandleFunc("/members/{member}/values", tag.GetTagsWithValuesByMemberHandler).Methods("GET").Name("Get-tags-with-values-by-member")

paths = append(paths, taggingPath)

Expand Down
27 changes: 27 additions & 0 deletions taggingapi/tag/tag_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,34 @@ func GetTagsByMemberHandler(w http.ResponseWriter, r *http.Request) {
xhttp.WriteXconfResponse(w, http.StatusBadRequest, []byte(fmt.Sprintf(NotSpecifiedErrorMsg, common.Member)))
return
}

tags, err := GetTagsByMember(member)
if err != nil {
xhttp.WriteXconfErrorResponse(w, err)
return
}

respBytes, err := json.Marshal(tags)
if err != nil {
xhttp.WriteXconfErrorResponse(w, err)
return
}
xhttp.WriteXconfResponse(w, http.StatusOK, respBytes)
}

func GetTagsWithValuesByMemberHandler(w http.ResponseWriter, r *http.Request) {
member, found := mux.Vars(r)[common.Member]
if !found {
xhttp.WriteXconfResponse(w, http.StatusBadRequest, []byte(fmt.Sprintf(NotSpecifiedErrorMsg, common.Member)))
return
}

tags, err := GetTagsWithValuesByMember(member)
if err != nil {
xhttp.WriteXconfErrorResponse(w, err)
return
}

respBytes, err := json.Marshal(tags)
if err != nil {
xhttp.WriteXconfErrorResponse(w, err)
Expand Down
19 changes: 19 additions & 0 deletions taggingapi/tag/tag_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ func TestAddMembersToTagHandler_ExceedsBatchSize(t *testing.T) {
assert.Contains(t, recorder.Body.String(), "exceeds maximum")
}

func TestGetTagValueFromRequest_QueryParameterPresent(t *testing.T) {
req := httptest.NewRequest("PUT", "/tags/test-tag/members?value=business", nil)

assert.Equal(t, "business", getTagValueFromRequest(req))
}

func TestGetTagValueFromRequest_EmptyQueryParameter(t *testing.T) {
req := httptest.NewRequest("PUT", "/tags/test-tag/members?value=", nil)

assert.Equal(t, "", getTagValueFromRequest(req))
}

func TestGetTagValueFromRequest_DefaultsToEmpty(t *testing.T) {
req := httptest.NewRequest("PUT", "/tags/test-tag/members", nil)
req = mux.SetURLVars(req, map[string]string{common.Tag: "test-tag"})

assert.Equal(t, "", getTagValueFromRequest(req))
}

func TestRemoveMembersFromTagHandler_MissingTag(t *testing.T) {
setupTestEnvironment()
req := httptest.NewRequest("DELETE", "/tags//members", nil)
Expand Down
37 changes: 31 additions & 6 deletions taggingapi/tag/tag_member_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ func AddMembersToTagHandler(w http.ResponseWriter, r *http.Request) {
return
}

tagValue := getTagValueFromRequest(r)

xw, ok := w.(*xwhttp.XResponseWriter)
if !ok {
xhttp.WriteXconfResponse(w, http.StatusInternalServerError, []byte(ResponseWriterCastErrorMsg))
Expand All @@ -134,9 +136,7 @@ func AddMembersToTagHandler(w http.ResponseWriter, r *http.Request) {
return
}

log.Debugf("AddMembers request: tag=%s, memberCount=%d", tagId, len(members))

stored, err := AddMembersWithXdas(tagId, members)
stored, err := AddMembersWithXdas(tagId, members, tagValue)
if err != nil {
xhttp.WriteXconfErrorResponse(w, err)
return
Expand All @@ -155,6 +155,16 @@ func AddMembersToTagHandler(w http.ResponseWriter, r *http.Request) {
xhttp.WriteXconfResponse(w, http.StatusAccepted, respBytes)
}

func getTagValueFromRequest(r *http.Request) string {
if values, found := r.URL.Query()[common.TagValue]; found {
if len(values) > 0 {
return values[0]
}
}

return ""
}

// RemoveMembersFromTagHandler - Updated with bucketed implementation
func RemoveMembersFromTagHandler(w http.ResponseWriter, r *http.Request) {
id, found := mux.Vars(r)[common.Tag]
Expand Down Expand Up @@ -186,8 +196,6 @@ func RemoveMembersFromTagHandler(w http.ResponseWriter, r *http.Request) {
return
}

log.Debugf("RemoveMembers request: tag=%s, memberCount=%d", id, len(members))

removed, err := RemoveMembersWithXdas(id, members)
if err != nil {
xhttp.WriteXconfErrorResponse(w, err)
Expand Down Expand Up @@ -292,6 +300,12 @@ func GetTagByIdHandler(w http.ResponseWriter, r *http.Request) {

// DeleteTagHandler deletes a tag and all its members from V2 storage asynchronously
func DeleteTagHandler(w http.ResponseWriter, r *http.Request) {
xw, ok := w.(*xwhttp.XResponseWriter)
if !ok {
xhttp.WriteXconfResponse(w, http.StatusInternalServerError, []byte(ResponseWriterCastErrorMsg))
return
}

id, found := mux.Vars(r)[common.Tag]
if !found {
xhttp.WriteXconfResponse(w, http.StatusBadRequest, []byte(fmt.Sprintf(NotSpecifiedErrorMsg, common.Tag)))
Expand All @@ -309,9 +323,20 @@ func DeleteTagHandler(w http.ResponseWriter, r *http.Request) {
return
}

auditId := xw.AuditId()
go func(tagId string) {
if err := DeleteTag(tagId); err != nil {
log.Errorf("Background deletion failed for tag '%s': %v", tagId, err)
log.WithFields(log.Fields{
"audit_id": auditId,
"endpoint": "DeleteTag",
"tag": tagId,
}).Errorf("background deletion failed: %v", err)
} else {
log.WithFields(log.Fields{
"audit_id": auditId,
"endpoint": "DeleteTag",
"tag": tagId,
}).Info("tagging background deletion completed")
}
}(id)

Expand Down
8 changes: 4 additions & 4 deletions taggingapi/tag/tag_member_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ func fetchMembersFromBucketsConcurrent(tagId string, bucketIds []int, totalLimit

// AddMembersWithXdas adds members to both XDAS and Cassandra (XDAS-first approach)
// Returns the count of members actually stored to Cassandra.
func AddMembersWithXdas(tagId string, members []string) (int, error) {
func AddMembersWithXdas(tagId string, members []string, tagValue string) (int, error) {
startTime := time.Now()

if len(members) == 0 {
Expand All @@ -564,7 +564,7 @@ func AddMembersWithXdas(tagId string, members []string) (int, error) {
return 0, fmt.Errorf("batch size %d exceeds maximum %d", len(members), MaxBatchSizeV2)
}

savedToXdasMembers, err := addMembersToXdas(tagId, members)
savedToXdasMembers, err := addMembersToXdas(tagId, members, tagValue)
if err != nil {
return 0, fmt.Errorf("XDAS operation failed: %w", err)
}
Expand Down Expand Up @@ -630,7 +630,7 @@ func RemoveMemberWithXdas(tagId string, member string) error {
}

// addMembersToXdas adds members to Xdas using concurrent workers (similar to V1 pattern)
func addMembersToXdas(tagId string, members []string) ([]string, error) {
func addMembersToXdas(tagId string, members []string, tagValue string) ([]string, error) {
tagId = SetTagPrefix(tagId)

membersChannel := make(chan string, len(members))
Expand All @@ -653,7 +653,7 @@ func addMembersToXdas(tagId string, members []string) ([]string, error) {
}
for i := 0; i < numOfWorkers; i++ {
wg.Add(1)
go storeTagMembersInXdas(tagId, membersChannel, savedMembersChannel, wg)
go storeTagMembersInXdas(tagId, membersChannel, savedMembersChannel, wg, tagValue)
}

go func() {
Expand Down
25 changes: 23 additions & 2 deletions taggingapi/tag/tag_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ func GetTagsByMember(member string) ([]string, error) {
return filterTagEntriesByPrefix(tagsMap.Keys()), err
}

func GetTagsWithValuesByMember(member string) (map[string]string, error) {
member = ToNormalizedEcm(member)
tagsAsHashes, err := GetGroupServiceConnector().GetGroupsMemberBelongsTo(member)
if err != nil {
log.Errorf("xdas error getting members by %s group: %s", member, err.Error())
return map[string]string{}, err
}
tagsMap := util.StringMap(tagsAsHashes.GetFields())
return filterTagEntriesWithValuesByPrefix(tagsMap), err
}

func filterTagEntriesByPrefix(ftEntries []string) []string {
tags := []string{}
for _, ftEntry := range ftEntries {
Expand All @@ -49,10 +60,20 @@ func filterTagEntriesByPrefix(ftEntries []string) []string {
return tags
}

func storeTagMembersInXdas(id string, members <-chan string, savedMembers chan<- string, wg *sync.WaitGroup) {
func filterTagEntriesWithValuesByPrefix(entries util.StringMap) map[string]string {
result := map[string]string{}
for key, value := range entries {
if strings.HasPrefix(key, Prefix) {
result[RemovePrefixFromTag(key)] = value
}
}
return result
}

func storeTagMembersInXdas(id string, members <-chan string, savedMembers chan<- string, wg *sync.WaitGroup, tagValue string) {
defer wg.Done()
xdasMembers := proto.XdasHashes{
Fields: map[string]string{id: ""},
Fields: map[string]string{id: tagValue},
}

successCount := 0
Expand Down
Loading