diff --git a/README.md b/README.md index 1aaf39a..ae1af25 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Contribution, in any kind of way, is highly welcome! It doesn't matter if you ar - Sharing the love of go-clickup and help people to get use to it - Writing test code -If you are new to pull requests, checkout Collaborating on projects using issues and pull requests / Creating a pull request. +If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests). ## Progress - [x] Rate Limit @@ -209,6 +209,11 @@ If you are new to pull requests, checkout Collaborating on projects using issues - [ ] Edit User On Workspace - [ ] Remove User From Workspace - [ ] Get User +- [x] User Groups + - [x] Get User Group + - [x] Create User Group + - [x] Update User Group + - [x] Delete User Group - [x] Views - [x] Create Team View - [x] Create Space View diff --git a/clickup/user_groups.go b/clickup/user_groups.go index 58a0677..92aaeff 100644 --- a/clickup/user_groups.go +++ b/clickup/user_groups.go @@ -2,6 +2,7 @@ package clickup import ( "context" + "fmt" ) type UserGroupsService service @@ -11,15 +12,22 @@ type GetUserGroupsResponse struct { } type UserGroup struct { - ID string `json:"id"` - TeamID string `json:"team_id"` - UserID int `json:"userid"` - Name string `json:"name"` - Handle string `json:"handle"` - DateCreated string `json:"date_created"` - Initials string `json:"initials"` - Members []GroupMember `json:"members"` - Avatar interface{} `json:"avatar"` + ID string `json:"id"` + TeamID string `json:"team_id"` + UserID int `json:"userid"` + Name string `json:"name"` + Handle string `json:"handle"` + DateCreated string `json:"date_created"` + Initials string `json:"initials"` + Members []GroupMember `json:"members"` + Avatar UserGroupAvatar `json:"avatar"` +} + +type UserGroupAvatar struct { + AttachmentId *string `json:"attachment_id"` + Color *string `json:"color"` + Source *string `json:"source"` + Icon *string `json:"icon"` } type GroupMember struct { @@ -31,11 +39,32 @@ type GroupMember struct { ProfilePicture string `json:"profilePicture"` } +type UserGroupRequest struct { + Name string `json:"name"` + Members []int `json:"members"` +} + type GetUserGroupsOptions struct { TeamID string `url:"team_id,omitempty"` GroupIDs []string `url:"group_ids,omitempty"` } +type UpdateUserGroupMember struct { + Add []int `json:"add"` + Remove []int `json:"rem"` +} + +type UpdateUserGroupRequest struct { + Name string `json:"name"` + Handle string `json:"handle"` + Members UpdateUserGroupMember `json:"members"` +} + +type CreateUserGroupRequest struct { + Name string `json:"name"` + Members []int `json:"add"` +} + func (s *UserGroupsService) GetUserGroups(ctx context.Context, opts *GetUserGroupsOptions) ([]UserGroup, *Response, error) { u, err := addOptions("group", opts) if err != nil { @@ -47,11 +76,58 @@ func (s *UserGroupsService) GetUserGroups(ctx context.Context, opts *GetUserGrou return nil, nil, err } - gugr := new(GetUserGroupsResponse) - resp, err := s.client.Do(ctx, req, gugr) + response := new(GetUserGroupsResponse) + resp, err := s.client.Do(ctx, req, response) + if err != nil { + return nil, resp, err + } + + return response.UserGroups, resp, nil +} + +func (s *UserGroupsService) CreateUserGroup(ctx context.Context, teamID string, createUserGroupRequest *CreateUserGroupRequest) (*UserGroup, *Response, error) { + u := fmt.Sprintf("team/%v/group", teamID) + req, err := s.client.NewRequest("POST", u, createUserGroupRequest) + if err != nil { + return nil, nil, err + } + + group := new(UserGroup) + resp, err := s.client.Do(ctx, req, group) if err != nil { return nil, resp, err } - return gugr.UserGroups, resp, nil + return group, resp, nil +} + +func (s *UserGroupsService) UpdateUserGroup(ctx context.Context, groupID string, updateUserGroupRequest *UpdateUserGroupRequest) (*UserGroup, *Response, error) { + u := fmt.Sprintf("group/%v", groupID) + req, err := s.client.NewRequest("PUT", u, updateUserGroupRequest) + if err != nil { + return nil, nil, err + } + + group := new(UserGroup) + resp, err := s.client.Do(ctx, req, group) + if err != nil { + return nil, resp, err + } + + return group, resp, nil +} + +func (s *UserGroupsService) DeleteUserGroup(ctx context.Context, groupID string) (*Response, error) { + u := fmt.Sprintf("group/%v", groupID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil } diff --git a/clickup/user_groups_test.go b/clickup/user_groups_test.go index 7a8afe5..6d2e41a 100644 --- a/clickup/user_groups_test.go +++ b/clickup/user_groups_test.go @@ -2,8 +2,11 @@ package clickup import ( "context" + "encoding/json" "fmt" + "io" "net/http" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -19,7 +22,7 @@ func TestUserGroupsService_GetUserGroups(t *testing.T) { `{ "groups": [ { - "id": "d899ba3a-1cbf-4188-ae39-11e6acc5f93d", + "id": "a-b-c-d-e", "team_id": "123", "userid": 321, "name": "Test Team", @@ -33,9 +36,15 @@ func TestUserGroupsService_GetUserGroups(t *testing.T) { "email": "testuser@test.com", "color": "#7b68ee", "initials": "TU", - "profilePicture": "https://attachments-public.clickup.com/profilePictures/812_nx1.jpg" + "profilePicture": "test.jpg" } - ] + ], + "avatar": { + "attachment_id": null, + "color": null, + "source": null, + "icon": null + } } ] }`, @@ -57,10 +66,10 @@ func TestUserGroupsService_GetUserGroups(t *testing.T) { Email: "testuser@test.com", Color: "#7b68ee", Initials: "TU", - ProfilePicture: "https://attachments-public.clickup.com/profilePictures/812_nx1.jpg", + ProfilePicture: "test.jpg", } ug := UserGroup{ - ID: "d899ba3a-1cbf-4188-ae39-11e6acc5f93d", + ID: "a-b-c-d-e", TeamID: "123", UserID: 321, Name: "Test Team", @@ -68,9 +77,233 @@ func TestUserGroupsService_GetUserGroups(t *testing.T) { DateCreated: "1675407473787", Initials: "TT", Members: []GroupMember{m}, + Avatar: UserGroupAvatar{ + AttachmentId: nil, + Color: nil, + Source: nil, + Icon: nil, + }, } want := []UserGroup{ug} if !cmp.Equal(artifacts, want) { t.Errorf("Actions.ListArtifacts returned %+v, want %+v", artifacts, want) } } + +func TestUserGroupsService_CreateUserGroup(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/team/123/group", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, + `{ + "id": "a-b-c-d-e", + "team_id": "123", + "userid": 321, + "name": "Test Team", + "handle": "test-team", + "date_created": "1675407473787", + "initials": "TT", + "members": [ + { + "id": 321, + "username": "Test User", + "email": "testuser@test.com", + "color": "#7b68ee", + "initials": "TU", + "profilePicture": "test.jpg" + } + ], + "avatar": { + "attachment_id": null, + "color": null, + "source": null, + "icon": null + } + }`, + ) + }) + ctx := context.Background() + artifacts, _, err := client.UserGroups.CreateUserGroup(ctx, "123", &CreateUserGroupRequest{ + Name: "Test Team", + Members: []int{321}, + }) + if err != nil { + t.Errorf("Actions.ListArtifacts returned error: %v", err) + } + + m := GroupMember{ + ID: 321, + Username: "Test User", + Email: "testuser@test.com", + Color: "#7b68ee", + Initials: "TU", + ProfilePicture: "test.jpg", + } + want := &UserGroup{ + ID: "a-b-c-d-e", + TeamID: "123", + UserID: 321, + Name: "Test Team", + Handle: "test-team", + DateCreated: "1675407473787", + Initials: "TT", + Members: []GroupMember{m}, + Avatar: UserGroupAvatar{ + AttachmentId: nil, + Color: nil, + Source: nil, + Icon: nil, + }, + } + if !cmp.Equal(artifacts, want) { + t.Errorf("Actions.ListArtifacts returned %+v, want %+v", artifacts, want) + } +} + +func TestUserGroupsService_UpdateUserGroup(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/group/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body", http.StatusInternalServerError) + return + } + + var requestBody UpdateUserGroupRequest + err = json.Unmarshal(body, &requestBody) + if err != nil { + http.Error(w, "Error parsing request body", http.StatusBadRequest) + return + } + + userIds := requestBody.Members.Add + + members := make([]map[string]interface{}, len(userIds)) + for i, id := range userIds { + members[i] = map[string]interface{}{ + "id": id, + "username": fmt.Sprintf("Test User %d", id), + "email": fmt.Sprintf("testuser%d@test.com", id), + "color": "#7b68ee", + "initials": "TU", + "profilePicture": "test.jpg", + } + } + + // Generate the response + response := map[string]interface{}{ + "id": "a-b-c-d-e", + "team_id": "123", + "userid": 321, + "name": requestBody.Name, + "handle": requestBody.Handle, + "date_created": "1675407473787", + "initials": "TT", + "members": members, + "avatar": map[string]interface{}{ + "attachment_id": nil, + "color": nil, + "source": nil, + "icon": nil, + }, + } + + responseJSON, err := json.Marshal(response) + if err != nil { + http.Error(w, "Error generating response", http.StatusInternalServerError) + return + } + + fmt.Fprint(w, string(responseJSON)) + }) + ctx := context.Background() + artifacts, _, err := client.UserGroups.UpdateUserGroup(ctx, "123", &UpdateUserGroupRequest{ + Name: "Test Team Updated", + Handle: "New Handle", + Members: UpdateUserGroupMember{ + Add: []int{1, 2}, + }, + }) + if err != nil { + t.Errorf("Actions.ListArtifacts returned error: %v", err) + } + + m1 := GroupMember{ + ID: 1, + Username: "Test User 1", + Email: "testuser1@test.com", + Color: "#7b68ee", + Initials: "TU", + ProfilePicture: "test.jpg", + } + m2 := GroupMember{ + ID: 2, + Username: "Test User 2", + Email: "testuser2@test.com", + Color: "#7b68ee", + Initials: "TU", + ProfilePicture: "test.jpg", + } + want := &UserGroup{ + ID: "a-b-c-d-e", + TeamID: "123", + UserID: 321, + Name: "Test Team Updated", + Handle: "New Handle", + DateCreated: "1675407473787", + Initials: "TT", + Members: []GroupMember{m1, m2}, + Avatar: UserGroupAvatar{ + AttachmentId: nil, + Color: nil, + Source: nil, + Icon: nil, + }, + } + if !cmp.Equal(artifacts, want) { + t.Errorf("Actions.ListArtifacts returned %+v, want %+v", artifacts, want) + } +} + +func TestUserGroupsService_DeleteUserGroup(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/group/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + fmt.Fprint(w, nil) + }) + ctx := context.Background() + _, err := client.UserGroups.DeleteUserGroup(ctx, "123") + if err != nil { + t.Errorf("Actions.ListArtifacts returned error: %v", err) + } +} + +func TestUserGroupsService_DeleteUserGroupError(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/group/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, "Internal server error") + }) + ctx := context.Background() + response, err := client.UserGroups.DeleteUserGroup(ctx, "123") + + if err == nil { + t.Errorf("Actions.ListArtifacts did not return error: %v", response) + } + + expectedError := "api/v2/group/123: 500" + if !strings.Contains(err.Error(), expectedError) { + t.Errorf("UserGroups.DeleteUserGroup returned error: %v, want %v", err, expectedError) + } +} diff --git a/example/user-groups/main.go b/example/user-groups/main.go index 82cacec..c4ffb6d 100644 --- a/example/user-groups/main.go +++ b/example/user-groups/main.go @@ -6,24 +6,66 @@ package main import ( "context" "fmt" - "os" "github.com/raksul/go-clickup/clickup" ) func fetchUserGroups() ([]clickup.UserGroup, error) { - api_key := os.Getenv("CLICKUP_API_KEY") + api_key := "CLICKUP_API_KEY" + team_id := "TEAM_ID" client := clickup.NewClient(nil, api_key) opts := &clickup.GetUserGroupsOptions{ - TeamID: "123", - GroupIDs: []string{"321", "456"}, // optional parameter + TeamID: team_id, + GroupIDs: []string{"GROUP_ID"}, // optional parameter } groups, _, err := client.UserGroups.GetUserGroups(context.Background(), opts) return groups, err } +func createUserGroup() (*clickup.UserGroup, error) { + api_key := "CLICKUP_API_KEY" + team_id := "TEAM_ID" + member_id_to_add := 0 + client := clickup.NewClient(nil, api_key) + + opts := clickup.CreateUserGroupRequest{ + Name: "test", + Members: []int{member_id_to_add}, // optional parameter + } + group, _, err := client.UserGroups.CreateUserGroup(context.Background(), team_id, &opts) + + return group, err +} + +func updateUserGroup() (*clickup.UserGroup, error) { + api_key := "CLICKUP_API_KEY" + group_id := "GROUP_ID" + client := clickup.NewClient(nil, api_key) + + opts := clickup.UpdateUserGroupRequest{ + // Name: "new name", // optional parameter + Members: clickup.UpdateUserGroupMember{ + Add: []int{0}, // optional parameter + // Remove: []int{0}, // optional parameter + }, + } + group, response, err := client.UserGroups.UpdateUserGroup(context.Background(), group_id, &opts) + fmt.Println(response) + return group, err +} + +func deleteUserGroup() error { + api_key := "CLICKUP_API_KEY" + group_id := "GROUP_ID" + client := clickup.NewClient(nil, api_key) + + _, err := client.UserGroups.DeleteUserGroup(context.Background(), group_id) + + return err +} + func main() { groups, err := fetchUserGroups() @@ -33,6 +75,6 @@ func main() { } for _, group := range groups { - fmt.Println(group.Name) + fmt.Println(group.Name, group.ID, group.Members) } }