From c15cdaf16655b709a6bafe5b0b349bd4c4f3ba76 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Justo Date: Wed, 2 Sep 2026 15:37:14 -0300 Subject: [PATCH] Feature: Filter activities by project, user or excluded user Adds `ProjectIDs`, `UserIDs` and `ExcludeUserIDs` to the activity list filters, mapping to the `projectIds`, `userIds` and `excludeUserIds` query parameters, and moves the filter's apply method onto the shared `querySet` helpers. --- projects/activity.go | 56 +++++++++++--------------- projects/activity_test.go | 83 ++++++++++++++++++++++++--------------- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/projects/activity.go b/projects/activity.go index 73f505d..ee6124f 100644 --- a/projects/activity.go +++ b/projects/activity.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "net/http" - "strconv" "strings" "time" @@ -214,6 +213,18 @@ type ActivityListRequestFilters struct { // share an identifier. ItemIDs []int64 + // ProjectIDs filters activities by the projects they belong to. The + // project-scoped route takes its project from the path and ignores this + // filter. + ProjectIDs []int64 + + // UserIDs filters activities by the users that created them. + UserIDs []int64 + + // ExcludeUserIDs filters out activities created by these users. Pass the + // logged-in user's identifier to drop their own activity from the feed. + ExcludeUserIDs []int64 + // OrderBy is the field to sort the results by. Use the ActivityOrderBy // constants. The endpoint defaults to date. OrderBy ActivityOrderBy @@ -243,38 +254,17 @@ type ActivityListRequestFilters struct { func (a ActivityListRequestFilters) apply(req *http.Request) { query := req.URL.Query() - if !a.StartDate.IsZero() { - query.Set("startDate", a.StartDate.Format(time.RFC3339)) - } - if !a.EndDate.IsZero() { - query.Set("endDate", a.EndDate.Format(time.RFC3339)) - } - if len(a.LogItemTypes) > 0 { - logItemTypes := make([]string, len(a.LogItemTypes)) - for i, logType := range a.LogItemTypes { - logItemTypes[i] = string(logType) - } - query.Set("activityTypes", strings.Join(logItemTypes, ",")) - } - if len(a.ItemIDs) > 0 { - itemIDs := make([]string, len(a.ItemIDs)) - for i, itemID := range a.ItemIDs { - itemIDs[i] = strconv.FormatInt(itemID, 10) - } - query.Set("itemIds", strings.Join(itemIDs, ",")) - } - if a.OrderBy != "" { - query.Set("orderBy", string(a.OrderBy)) - } - if a.OrderMode != "" { - query.Set("orderMode", string(a.OrderMode)) - } - if a.Page > 0 { - query.Set("page", strconv.FormatInt(a.Page, 10)) - } - if a.PageSize > 0 { - query.Set("pageSize", strconv.FormatInt(a.PageSize, 10)) - } + querySetTimestamp(query, "startDate", &a.StartDate) + querySetTimestamp(query, "endDate", &a.EndDate) + querySetStrings(query, "activityTypes", a.LogItemTypes) + querySetInt64s(query, "itemIds", a.ItemIDs) + querySetInt64s(query, "projectIds", a.ProjectIDs) + querySetInt64s(query, "userIds", a.UserIDs) + querySetInt64s(query, "excludeUserIds", a.ExcludeUserIDs) + querySetString(query, "orderBy", a.OrderBy) + querySetString(query, "orderMode", a.OrderMode) + querySetInt64(query, "page", a.Page) + querySetInt64(query, "pageSize", a.PageSize) a.Fields.apply(query) a.CountMode.Apply(query) req.URL.RawQuery = query.Encode() diff --git a/projects/activity_test.go b/projects/activity_test.go index 94d6573..845e9b9 100644 --- a/projects/activity_test.go +++ b/projects/activity_test.go @@ -114,45 +114,66 @@ func TestLogItemType_UnmarshalText(t *testing.T) { }) } -func TestActivityListItemIDs(t *testing.T) { +func TestActivityListIDFilters(t *testing.T) { + filters := []struct { + param string + set func(*projects.ActivityListRequestFilters, []int64) + }{{ + param: "itemIds", + set: func(f *projects.ActivityListRequestFilters, ids []int64) { f.ItemIDs = ids }, + }, { + param: "projectIds", + set: func(f *projects.ActivityListRequestFilters, ids []int64) { f.ProjectIDs = ids }, + }, { + param: "userIds", + set: func(f *projects.ActivityListRequestFilters, ids []int64) { f.UserIDs = ids }, + }, { + param: "excludeUserIds", + set: func(f *projects.ActivityListRequestFilters, ids []int64) { f.ExcludeUserIDs = ids }, + }} + tests := []struct { - name string - itemIDs []int64 - want string + name string + ids []int64 + want string }{{ - name: "unset item ids are not sent", + name: "unset ids are not sent", }, { - name: "single item id", - itemIDs: []int64{777}, - want: "777", + name: "single id", + ids: []int64{777}, + want: "777", }, { - name: "multiple item ids are comma-separated", - itemIDs: []int64{777, 12345}, - want: "777,12345", + name: "multiple ids are comma-separated", + ids: []int64{777, 12345}, + want: "777,12345", }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req := projects.NewActivityListRequest() - req.Filters.ItemIDs = tt.itemIDs + for _, filter := range filters { + t.Run(filter.param, func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := projects.NewActivityListRequest() + filter.set(&req.Filters, tt.ids) - httpReq, err := req.HTTPRequest(context.Background(), "https://test.com") - if err != nil { - t.Fatalf("unexpected error creating HTTP request: %s", err) - } - query, err := url.ParseQuery(httpReq.URL.RawQuery) - if err != nil { - t.Fatalf("failed to parse query string: %s", err) - } + httpReq, err := req.HTTPRequest(context.Background(), "https://test.com") + if err != nil { + t.Fatalf("unexpected error creating HTTP request: %s", err) + } + query, err := url.ParseQuery(httpReq.URL.RawQuery) + if err != nil { + t.Fatalf("failed to parse query string: %s", err) + } - if tt.want == "" { - if _, ok := query["itemIds"]; ok { - t.Errorf("expected no itemIds parameter, got %q", query.Get("itemIds")) - } - return - } - if got := query.Get("itemIds"); got != tt.want { - t.Errorf("itemIds = %q, want %q", got, tt.want) + if tt.want == "" { + if _, ok := query[filter.param]; ok { + t.Errorf("expected no %s parameter, got %q", filter.param, query.Get(filter.param)) + } + return + } + if got := query.Get(filter.param); got != tt.want { + t.Errorf("%s = %q, want %q", filter.param, got, tt.want) + } + }) } }) }