Skip to content

Commit

Permalink
fix: removing the use of username field in searching for users (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
cynthiabaran authored May 22, 2020
1 parent 146229d commit f50cb07
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 55 deletions.
6 changes: 0 additions & 6 deletions issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1365,12 +1365,6 @@ func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID strin
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
} else {
// try fallback deprecated method
user, resp, err = s.client.User.Get(watcher.Name)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
}
result = append(result, *user)
}
Expand Down
11 changes: 5 additions & 6 deletions issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1598,15 +1598,14 @@ func TestIssueService_DeprecatedGetWatchers(t *testing.T) {
testMethod(t, r, "GET")
testRequestURL(t, r, "/rest/api/2/issue/10002/watchers")

fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]}`)
fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "displayName":"Fred F. User","active":false}]}`)
})

testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testRequestURL(t, r, "/rest/api/2/user?username=fred")
testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000")

fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred",
"name":"fred","emailAddress":"[email protected]","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred",
fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000", "accountId": "000000000000000000000000", "key": "", "name": "", "emailAddress":"[email protected]","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",
Expand All @@ -1627,8 +1626,8 @@ func TestIssueService_DeprecatedGetWatchers(t *testing.T) {
t.Errorf("Expected 1 watcher, got: %d", len(*watchers))
return
}
if (*watchers)[0].Name != "fred" {
t.Error("Expected watcher name fred")
if (*watchers)[0].AccountID != "000000000000000000000000" {
t.Error("Expected accountId 000000000000000000000000")
}
}

Expand Down
57 changes: 26 additions & 31 deletions user.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ import (

// UserService handles users for the Jira instance / API.
//
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Users
type UserService struct {
client *Client
}

// User represents a Jira user.
type User struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"`
AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"`
// TODO: name & key are deprecated, see:
// https://developer.atlassian.com/cloud/jira/platform/api-changes-for-user-privacy-announcement/
Self string `json:"self,omitempty" structs:"self,omitempty"`
AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"`
AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
Password string `json:"-"`
Expand Down Expand Up @@ -48,15 +46,11 @@ type userSearch []userSearchParam

type userSearchF func(userSearch) userSearch

// GetWithContext gets user info from Jira
// GetWithContext gets user info from Jira using its Account Id
//
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser
//
// /!\ Deprecation notice: https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-user-privacy-api-migration-guide/
// By 29 April 2019, we will remove personal data from the API that is used to identify users,
// such as username and userKey, and instead use the Atlassian account ID (accountId).
func (s *UserService) GetWithContext(ctx context.Context, username string) (*User, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?username=%s", username)
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get
func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
Expand All @@ -71,12 +65,13 @@ func (s *UserService) GetWithContext(ctx context.Context, username string) (*Use
}

// Get wraps GetWithContext using the background context.
func (s *UserService) Get(username string) (*User, *Response, error) {
return s.GetWithContext(context.Background(), username)
func (s *UserService) Get(accountId string) (*User, *Response, error) {
return s.GetWithContext(context.Background(), accountId)
}

// GetByAccountIDWithContext gets user info from Jira
//
// Searching by another parameter that is not accountId is deprecated,
// but this method is kept for backwards compatibility
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser
func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID)
Expand Down Expand Up @@ -136,9 +131,9 @@ func (s *UserService) Create(user *User) (*User, *Response, error) {
// DeleteWithContext deletes an user from Jira.
// Returns http.StatusNoContent on success.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-user-delete
func (s *UserService) DeleteWithContext(ctx context.Context, username string) (*Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?username=%s", username)
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete
func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId)
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
if err != nil {
return nil, err
Expand All @@ -152,15 +147,15 @@ func (s *UserService) DeleteWithContext(ctx context.Context, username string) (*
}

// Delete wraps DeleteWithContext using the background context.
func (s *UserService) Delete(username string) (*Response, error) {
return s.DeleteWithContext(context.Background(), username)
func (s *UserService) Delete(accountId string) (*Response, error) {
return s.DeleteWithContext(context.Background(), accountId)
}

// GetGroupsWithContext returns the groups which the user belongs to
//
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUserGroups
func (s *UserService) GetGroupsWithContext(ctx context.Context, username string) (*[]UserGroup, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?username=%s", username)
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get
func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
Expand All @@ -175,13 +170,13 @@ func (s *UserService) GetGroupsWithContext(ctx context.Context, username string)
}

// GetGroups wraps GetGroupsWithContext using the background context.
func (s *UserService) GetGroups(username string) (*[]UserGroup, *Response, error) {
return s.GetGroupsWithContext(context.Background(), username)
func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, error) {
return s.GetGroupsWithContext(context.Background(), accountId)
}

// GetSelfWithContext information about the current logged-in user
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-myself-get
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get
func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) {
const apiEndpoint = "rest/api/2/myself"
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
Expand Down Expand Up @@ -234,13 +229,13 @@ func WithInactive(inactive bool) userSearchF {
}

// FindWithContext searches for user info from Jira:
// It can find users by email, username or name
// It can find users by email or display name using the query parameter
//
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-findUsers
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get
func (s *UserService) FindWithContext(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) {
search := []userSearchParam{
{
name: "username",
name: "query",

This comment has been minimized.

Copy link
@shvydok

shvydok Jul 21, 2020

Hello @andygrunwald @cynthiabaran,

this change causing an issue on v2 api.

(Response) 400 Bad Request - Body: {"errorMessages":["The username query parameter was not provided"],"errors":{}}

It seems to perform this type of search "username" param is still required(you can put as value jira user name or email, both works fine), is there any chance to have this updated quickly or maybe have an option to specify property name to be used in search query?

If i use param "query" and use as value user name or email i get error saying that username param is to be provided.

Thanks a lot in advance!

Ihor

This comment has been minimized.

Copy link
@cynthiabaran

cynthiabaran Jul 21, 2020

Author Contributor

Hey

Can you indicate which API endpoint you called that got this error?

This comment has been minimized.

Copy link
@shvydok

shvydok Jul 23, 2020

/rest/api/2/user/search

This comment has been minimized.

Copy link
@dvmdb

dvmdb Jul 7, 2022

2 years later this issue is still actual, but there is a workaround: #385 (comment)
If somebody can export tweaks(search params) from this file - there could be another option to override search in a nicer way.

This comment has been minimized.

Copy link
@andygrunwald

andygrunwald Aug 16, 2022

Owner

I will look into this.
I created #472

value: property,
},
}
Expand Down
24 changes: 12 additions & 12 deletions user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func TestUserService_Get_Success(t *testing.T) {
defer teardown()
testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testRequestURL(t, r, "/rest/api/2/user?username=fred")
testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000")

fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred",
"name":"fred","emailAddress":"[email protected]","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred",
Expand All @@ -22,7 +22,7 @@ func TestUserService_Get_Success(t *testing.T) {
}]},"applicationRoles":{"size":1,"items":[]},"expand":"groups,applicationRoles"}`)
})

if user, _, err := testClient.User.Get("fred"); err != nil {
if user, _, err := testClient.User.Get("000000000000000000000000"); err != nil {
t.Errorf("Error given: %s", err)
} else if user == nil {
t.Error("Expected user. User is nil")
Expand Down Expand Up @@ -84,12 +84,12 @@ func TestUserService_Delete(t *testing.T) {
defer teardown()
testMux.HandleFunc("/rest/api/2/user", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
testRequestURL(t, r, "/rest/api/2/user?username=fred")
testRequestURL(t, r, "/rest/api/2/user?accountId=000000000000000000000000")

w.WriteHeader(http.StatusNoContent)
})

resp, err := testClient.User.Delete("fred")
resp, err := testClient.User.Delete("000000000000000000000000")
if err != nil {
t.Errorf("Error given: %s", err)
}
Expand All @@ -104,13 +104,13 @@ func TestUserService_GetGroups(t *testing.T) {
defer teardown()
testMux.HandleFunc("/rest/api/2/user/groups", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testRequestURL(t, r, "/rest/api/2/user/groups?username=fred")
testRequestURL(t, r, "/rest/api/2/user/groups?accountId=000000000000000000000000")

w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, `[{"name":"jira-software-users","self":"http://www.example.com/jira/rest/api/2/user?username=fred"}]`)
fmt.Fprint(w, `[{"name":"jira-software-users","self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000"}]`)
})

if groups, _, err := testClient.User.GetGroups("fred"); err != nil {
if groups, _, err := testClient.User.GetGroups("000000000000000000000000"); err != nil {
t.Errorf("Error given: %s", err)
} else if groups == nil {
t.Error("Expected user groups. []UserGroup is nil")
Expand All @@ -125,7 +125,7 @@ func TestUserService_GetSelf(t *testing.T) {
testRequestURL(t, r, "/rest/api/2/myself")

w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred",
fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred",
"name":"fred","emailAddress":"[email protected]","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":[
Expand All @@ -150,9 +150,9 @@ func TestUserService_Find_Success(t *testing.T) {
defer teardown()
testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testRequestURL(t, r, "/rest/api/2/user/search?username[email protected]")
testRequestURL(t, r, "/rest/api/2/user/search?query[email protected]")

fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred",
fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?accountId=000000000000000000000000","key":"fred",
"name":"fred","emailAddress":"[email protected]","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":[
Expand All @@ -173,9 +173,9 @@ func TestUserService_Find_SuccessParams(t *testing.T) {
defer teardown()
testMux.HandleFunc("/rest/api/2/user/search", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testRequestURL(t, r, "/rest/api/2/user/search?username[email protected]&startAt=100&maxResults=1000")
testRequestURL(t, r, "/rest/api/2/user/search?query[email protected]&startAt=100&maxResults=1000")

fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","key":"fred",
fmt.Fprint(w, `[{"self":"http://www.example.com/jira/rest/api/2/user?query=fred","key":"fred",
"name":"fred","emailAddress":"[email protected]","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":[
Expand Down

0 comments on commit f50cb07

Please sign in to comment.