Repository: andygrunwald/go-jira (go)

Issue:
Review service "Component" in the Cloud Jira client
In #473 we split the single Jira client into two: OnPremise and Cloud.
We did this by copying the same code into two folders. The reason: The two software products have different APIs.
In v1 of this client, we had a bad/dirty mix of OnPremise and Cloud API endpoints.

## Goal of this task

Review service "Component" in the Cloud Jira client and

1. delete all API endpoints related to the OnPremise Jira version
2. update the documentation to match the latest API links

## Notes

This might reduce the functionality of the client, but this lead to a more stable client.

Missing API endpoints will be documented that we have a clear overview.

Pull request:
Cloud/Component: Review service "Component" in the Cloud Jira client for latest API changes
Fix #550 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fafcabf..6a8deeb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -396,6 +396,7 @@ client, err := jira.NewClient("https://...", tp.Client())
 * Cloud/Authentication: Removed `PATAuthTransport`, because it was a (kind of) duplicate of `BasicAuthTransport`
 * Cloud/Authentication: `BasicAuthTransport.Password` was renamed to `BasicAuthTransport.APIToken`
 * Cloud/Authentication: Removes `CookieAuthTransport` and `AuthenticationService`, because this type of auth is not supported by the Jira cloud offering
+* Cloud/Component: The type `CreateComponentOptions` was renamed to `ComponentCreateOptions`
 
 ### Features
 
diff --git a/cloud/component.go b/cloud/component.go
index 66eb604..61b25b9 100644
--- a/cloud/component.go
+++ b/cloud/component.go
@@ -6,28 +6,60 @@ import (
 	"net/http"
 )
 
-// ComponentService handles components for the Jira instance / API.//
-// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component
+// ComponentService represents project components.
+// Use it to get, create, update, and delete project components.
+// Also get components for project and get a count of issues by component.
+//
+// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-group-project-components
 type ComponentService service
 
-// CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component
-type CreateComponentOptions struct {
-	Name         string `json:"name,omitempty" structs:"name,omitempty"`
-	Description  string `json:"description,omitempty" structs:"description,omitempty"`
-	Lead         *User  `json:"lead,omitempty" structs:"lead,omitempty"`
-	LeadUserName string `json:"leadUserName,omitempty" structs:"leadUserName,omitempty"`
+const (
+	AssigneeTypeProjectLead    = "PROJECT_LEAD"
+	AssigneeTypeComponentLead  = "COMPONENT_LEAD"
+	AssigneeTypeUnassigned     = "UNASSIGNED"
+	AssigneeTypeProjectDefault = "PROJECT_DEFAULT"
+)
+
+// ComponentCreateOptions are passed to the ComponentService.Create function to create a new Jira component
+type ComponentCreateOptions struct {
+	// Name: The unique name for the component in the project.
+	// Required when creating a component.
+	// Optional when updating a component.
+	// The maximum length is 255 characters.
+	Name string `json:"name,omitempty" structs:"name,omitempty"`
+
+	// Description: The description for the component.
+	// Optional when creating or updating a component.
+	Description string `json:"description,omitempty" structs:"description,omitempty"`
+
+	// LeadAccountId: The accountId of the component's lead user.
+	// The accountId uniquely identifies the user across all Atlassian products.
+	// For example, 5b10ac8d82e05b22cc7d4ef5.
+	LeadAccountId string `json:"leadAccountId,omitempty" structs:"leadAccountId,omitempty"`
+
+	// AssigneeType: The nominal user type used to determine the assignee for issues created with this component.
+	// Can take the following values:
+	//	PROJECT_LEAD the assignee to any issues created with this component is nominally the lead for the project the component is in.
+	//	COMPONENT_LEAD the assignee to any issues created with this component is nominally the lead for the component.
+	//	UNASSIGNED an assignee is not set for issues created with this component.
+	//	PROJECT_DEFAULT the assignee to any issues created with this component is nominally the default assignee for the project that the component is in.
+	//
+	// Default value: PROJECT_DEFAULT.
+	// Optional when creating or updating a component.
 	AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"`
-	Assignee     *User  `json:"assignee,omitempty" structs:"assignee,omitempty"`
-	Project      string `json:"project,omitempty" structs:"project,omitempty"`
-	ProjectID    int    `json:"projectId,omitempty" structs:"projectId,omitempty"`
+
+	// Project: The key of the project the component is assigned to.
+	// Required when creating a component.
+	// Can't be updated.
+	Project string `json:"project,omitempty" structs:"project,omitempty"`
 }
 
-// Create creates a new Jira component based on the given options.
+// Create creates a component.
+// Use components to provide containers for issues within a project.
 //
-// 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.
-func (s *ComponentService) Create(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
-	apiEndpoint := "rest/api/2/component"
+// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-post
+func (s *ComponentService) Create(ctx context.Context, options *ComponentCreateOptions) (*ProjectComponent, *Response, error) {
+	apiEndpoint := "rest/api/3/component"
 	req, err := s.client.NewRequest(ctx, http.MethodPost, apiEndpoint, options)
 	if err != nil {
 		return nil, nil, err
@@ -35,7 +67,6 @@ func (s *ComponentService) Create(ctx context.Context, options *CreateComponentO
 
 	component := new(ProjectComponent)
 	resp, err := s.client.Do(req, component)
-
 	if err != nil {
 		return nil, resp, NewJiraError(resp, err)
 	}
@@ -61,3 +92,13 @@ func (s *ComponentService) Get(ctx context.Context, componentID string) (*Projec
 
 	return component, resp, nil
 }
+
+// TODO Add "Update component" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-id-put
+
+// TODO Add "Delete component" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-id-delete
+
+// TODO Add "Get component issues count" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-component-id-relatedissuecounts-get
+
+// TODO Add "Get project components paginated" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-project-projectidorkey-component-get
+
+// TODO Add "Get project components" method. See https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-components/#api-rest-api-3-project-projectidorkey-components-get
diff --git a/cloud/examples/component_create/main.go b/cloud/examples/component_create/main.go
new file mode 100644
index 0000000..5f40a61
--- /dev/null
+++ b/cloud/examples/component_create/main.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+	"context"
+	"fmt"
+
+	jira "github.com/andygrunwald/go-jira/v2/cloud"
+)
+
+func main() {
+	jiraURL := "https://go-jira-opensource.atlassian.net/"
+
+	// Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
+	// Create a new API token: https://id.atlassian.com/manage-profile/security/api-tokens
+	tp := jira.BasicAuthTransport{
+		Username: "<username>",
+		APIToken: "<api-token>",
+	}
+	client, err := jira.NewClient(jiraURL, tp.Client())
+	if err != nil {
+		panic(err)
+	}
+
+	c := &jira.ComponentCreateOptions{
+		Name:         "Dummy component",
+		AssigneeType: jira.AssigneeTypeUnassigned,
+		Project:      "BUG",
+	}
+	component, _, err := client.Component.Create(context.Background(), c)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("component: %+v\n", component)
+	fmt.Println("Success!")
+}
diff --git a/cloud/examples/component_get/main.go b/cloud/examples/component_get/main.go
new file mode 100644
index 0000000..187cf40
--- /dev/null
+++ b/cloud/examples/component_get/main.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+	"context"
+	"fmt"
+
+	jira "github.com/andygrunwald/go-jira/v2/cloud"
+)
+
+func main() {
+	jiraURL := "https://go-jira-opensource.atlassian.net/"
+
+	// Jira docs: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
+	// Create a new API token: https://id.atlassian.com/manage-profile/security/api-tokens
+	tp := jira.BasicAuthTransport{
+		Username: "<username>",
+		APIToken: "<api-token>",
+	}
+	client, err := jira.NewClient(jiraURL, tp.Client())
+	if err != nil {
+		panic(err)
+	}
+
+	component, _, err := client.Component.Get(context.Background(), "10000")
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("component: %+v\n", component)
+	fmt.Println("Success!")
+}


Test patch:
diff --git a/cloud/component_test.go b/cloud/component_test.go
index 4bba036..3c8305a 100644
--- a/cloud/component_test.go
+++ b/cloud/component_test.go
@@ -11,15 +11,17 @@ import (
 func TestComponentService_Create_Success(t *testing.T) {
 	setup()
 	defer teardown()
-	testMux.HandleFunc("/rest/api/2/component", func(w http.ResponseWriter, r *http.Request) {
+	testAPIEndpoint := "/rest/api/3/component"
+
+	testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, http.MethodPost)
-		testRequestURL(t, r, "/rest/api/2/component")
+		testRequestURL(t, r, testAPIEndpoint)
 
 		w.WriteHeader(http.StatusCreated)
 		fmt.Fprint(w, `{ "self": "http://www.example.com/jira/rest/api/2/component/10000", "id": "10000", "name": "Component 1", "description": "This is a Jira component", "lead": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "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": false }, "assigneeType": "PROJECT_LEAD", "assignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "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": false }, "realAssigneeType": "PROJECT_LEAD", "realAssignee": { "self": "http://www.example.com/jira/rest/api/2/user?username=fred", "name": "fred", "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": false }, "isAssigneeTypeValid": false, "project": "HSP", "projectId": 10000 }`)
 	})
 
-	component, _, err := testClient.Component.Create(context.Background(), &CreateComponentOptions{
+	component, _, err := testClient.Component.Create(context.Background(), &ComponentCreateOptions{
 		Name: "foo-bar",
 	})
 	if component == nil {