From 6630f916b538ac6121ba754a94912b4ab98b95d0 Mon Sep 17 00:00:00 2001 From: Alex Mackay Date: Wed, 17 Sep 2025 07:49:52 +1000 Subject: [PATCH 1/3] feat(cloud): Add GetAllUsers endpoint wrapper --- cloud/user.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cloud/user.go b/cloud/user.go index 1a8fea84..c00e58f8 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -209,6 +209,25 @@ func (s *UserService) GetCurrentUser(ctx context.Context) (*User, *Response, err return &user, resp, nil } +// GetAllUsers a list of all users, including active users, inactive users and previously deleted users that have an Atlassian account. +// +// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-users/#api-rest-api-2-users-search-get +func (s *UserService) GetAllUsers(ctx context.Context) ([]User, *Response, error) { + const apiEndpoint = "rest/api/2/users/search" + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + var users []User + resp, err := s.client.Do(req, &users) + if err != nil { + return nil, resp, NewJiraError(resp, err) + } + + return users, resp, nil +} + // WithMaxResults sets the max results to return func WithMaxResults(maxResults int) UserSearchF { return func(s UserSearch) UserSearch { @@ -268,7 +287,7 @@ func WithProperty(property string) UserSearchF { // Find searches for user info from Jira: // It can find users by email or display name using the query parameter // -// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get +// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-user-search/#api-rest-api-2-user-search-get // // TODO Double check this method if this works as expected, is using the latest API and the response is complete // This double check effort is done for v2 - Remove this two lines if this is completed. From cab80c9863e91c2c29dc3351042c6d19d8df9863 Mon Sep 17 00:00:00 2001 From: Alex Mackay Date: Wed, 17 Sep 2025 08:11:41 +1000 Subject: [PATCH 2/3] add unit test --- cloud/user.go | 6 +++--- cloud/user_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/cloud/user.go b/cloud/user.go index c00e58f8..d8509d6b 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -209,11 +209,11 @@ func (s *UserService) GetCurrentUser(ctx context.Context) (*User, *Response, err return &user, resp, nil } -// GetAllUsers a list of all users, including active users, inactive users and previously deleted users that have an Atlassian account. +// GetAllUsers returns a list of all users, including active users, inactive users and previously deleted users that have an Atlassian account. // -// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-users/#api-rest-api-2-users-search-get +// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-users/#api-rest-api-2-users-get func (s *UserService) GetAllUsers(ctx context.Context) ([]User, *Response, error) { - const apiEndpoint = "rest/api/2/users/search" + const apiEndpoint = "rest/api/2/users" req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) if err != nil { return nil, nil, err diff --git a/cloud/user_test.go b/cloud/user_test.go index 7e5fdb26..b57af5df 100644 --- a/cloud/user_test.go +++ b/cloud/user_test.go @@ -282,3 +282,26 @@ func TestUserService_Find_SuccessParams(t *testing.T) { t.Error("Expected user. User is nil") } } + +func TestUserService_GetAllUsers_SuccessParams(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, "/rest/api/2/users") + + fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/users","key":"fred", + "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) + }) + + if user, _, err := testClient.User.GetAllUsers(context.Background()); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } +} From 153e7d15bf2c588a1d1a37af0a1b819f92a2aec8 Mon Sep 17 00:00:00 2001 From: Alex Mackay Date: Thu, 25 Sep 2025 09:36:09 +1000 Subject: [PATCH 3/3] add search options --- cloud/user.go | 21 ++++++++++++++++++--- cloud/user_test.go | 40 +++++++++++++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/cloud/user.go b/cloud/user.go index d8509d6b..de75de09 100644 --- a/cloud/user.go +++ b/cloud/user.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "strconv" ) // UserService handles users for the Jira instance / API. @@ -212,9 +214,22 @@ func (s *UserService) GetCurrentUser(ctx context.Context) (*User, *Response, err // GetAllUsers returns a list of all users, including active users, inactive users and previously deleted users that have an Atlassian account. // // JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-users/#api-rest-api-2-users-get -func (s *UserService) GetAllUsers(ctx context.Context) ([]User, *Response, error) { - const apiEndpoint = "rest/api/2/users" - req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) +func (s *UserService) GetAllUsers(ctx context.Context, options *SearchOptions) ([]User, *Response, error) { + u := url.URL{ + Path: "rest/api/2/users", + } + uv := url.Values{} + if options != nil { + if options.StartAt != 0 { + uv.Add("startAt", strconv.Itoa(options.StartAt)) + } + if options.MaxResults != 0 { + uv.Add("maxResults", strconv.Itoa(options.MaxResults)) + } + } + u.RawQuery = uv.Encode() + + req, err := s.client.NewRequest(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, nil, err } diff --git a/cloud/user_test.go b/cloud/user_test.go index b57af5df..210d5fa2 100644 --- a/cloud/user_test.go +++ b/cloud/user_test.go @@ -286,22 +286,44 @@ func TestUserService_Find_SuccessParams(t *testing.T) { func TestUserService_GetAllUsers_SuccessParams(t *testing.T) { setup() defer teardown() - testMux.HandleFunc("/rest/api/2/users", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, http.MethodGet) - testRequestURL(t, r, "/rest/api/2/users") + t.Run("no-params", func(t *testing.T) { + testMux.HandleFunc("/rest/api/2/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, "/rest/api/2/users") - fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/users","key":"fred", + fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/users","key":"fred", "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) + }) + + if user, _, err := testClient.User.GetAllUsers(context.Background(), nil); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } }) + t.Run("with-params", func(t *testing.T) { + testMux.HandleFunc("/rest/api/2/users?maxResults=50&startAt=1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, "/rest/api/2/users?maxResults=50&startAt=1") - if user, _, err := testClient.User.GetAllUsers(context.Background()); err != nil { - t.Errorf("Error given: %s", err) - } else if user == nil { - t.Error("Expected user. User is nil") - } + fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/users?maxResults=50&startAt=1","key":"fred", + "name":"fred","emailAddress":"fred@example.com","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred", + "24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred", + "32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":true,"timeZone":"Australia/Sydney","groups":{"size":3,"items":[ + {"name":"jira-user","self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-user"},{"name":"jira-admin", + "self":"http://www.example.com/jira/rest/api/2/group?groupname=jira-admin"},{"name":"important","self":"http://www.example.com/jira/rest/api/2/group?groupname=important" + }]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}]`) + }) + + if user, _, err := testClient.User.GetAllUsers(context.Background(), &SearchOptions{StartAt: 1, MaxResults: 50}); err != nil { + t.Errorf("Error given: %s", err) + } else if user == nil { + t.Error("Expected user. User is nil") + } + }) }