Repository: ankitpokhrel/jira-cli
Pull request #748 (2024-07-11 19:42:15)

Feature: add original estimate option to issue creation
This PR intends to add the possibility of specifying the original estimate when creating an issue. 

* New option is called `-e, --original-estimate`
* Will set the `{ timetracking: { originalEstimate: "" }` value in the payload.

```shell 
❯ ./jira issue create --help
Create an issue in a given project with minimal information.

USAGE
  jira issue create [flags]

FLAGS
  -t, --type string                   Issue type
  -P, --parent string                 Parent issue key can be used to attach epic to an issue.
                                      And, this field is mandatory when creating a sub-task.
  -s, --summary string                Issue summary or title
  -b, --body string                   Issue description
  -y, --priority string               Issue priority
  -r, --reporter string               Issue reporter (username, email or display name)
  -a, --assignee string               Issue assignee (username, email or display name)
  -l, --label stringArray             Issue labels
  -C, --component stringArray         Issue components
      --fix-version stringArray       Release info (fixVersions)
      --affects-version stringArray   Release info (affectsVersions)
  -e, --original-estimate string      Issue Original estimate
      --custom stringToString         Set custom fields (default [])
  -T, --template string               Path to a file to read body/description from
      --web                           Open in web browser after successful creation
      --no-input                      Disable prompt for non-required fields
  -h, --help                          help for create

INHERITED FLAGS
  -c, --config string    Config file (default is /Users/rcloutier/.config/.jira/.config.yml)
      --debug            Turn on debug output
  -p, --project string   Jira project to look into (defaults to /Users/rcloutier/.config/.jira/.config.yml)

EXAMPLES
  $ jira issue create

  # Create issue in the configured project
  $ jira issue create -tBug -s"New Bug" -yHigh -lbug -lurgent -b"Bug description"

  # Create issue in another project
  $ jira issue create -pPRJ -tBug -yHigh -s"New Bug" -b$'Bug description\n\nSome more text'

  # Create issue and set custom fields
  # See https://github.com/ankitpokhrel/jira-cli/discussions/346
  $ jira issue create -tStory -s"Issue with custom fields" --custom story-points=3

  # Load description from template file
  $ jira issue create --template /path/to/template.tmpl

  # Get description from standard input
  $ jira issue create --template -

  # Or, use pipe to read input directly from standard input
  $ echo "Description from stdin" | jira issue create -s"Summary" -tTask

  # For issue description, the flag --body/-b takes precedence over the --template flag
  # The example below will add "Body from flag" as an issue description
  $ jira issue create -tTask -sSummary -b"Body from flag" --template /path/to/template.tpl

LEARN MORE
  Use 'jira <command> <subcommand> --help' for more information about a command.
  Read the doc or get help at https://github.com/ankitpokhrel/jira-cli
```


Patch:
diff --git a/internal/cmd/completion/completion.go b/internal/cmd/completion/completion.go
index bdc9dfc..1cf5e10 100644
--- a/internal/cmd/completion/completion.go
+++ b/internal/cmd/completion/completion.go
@@ -28,7 +28,7 @@ Zsh:
   $ echo "autoload -U compinit; compinit" >> ~/.zshrc
 
   # To load completions for each session, execute once:
-  $ jira completion zsh > /usr/local/share/zsh/site-functions/_jira
+  $ jira completion zsh > "${fpath[1]}/_jira"
 
   # You will need to start a new shell for this setup to take effect.
 
diff --git a/internal/cmd/issue/create/create.go b/internal/cmd/issue/create/create.go
index d212268..5e41882 100644
--- a/internal/cmd/issue/create/create.go
+++ b/internal/cmd/issue/create/create.go
@@ -99,20 +99,21 @@ func create(cmd *cobra.Command, _ []string) {
 		defer s.Stop()
 
 		cr := jira.CreateRequest{
-			Project:         project,
-			IssueType:       params.IssueType,
-			ParentIssueKey:  params.ParentIssueKey,
-			Summary:         params.Summary,
-			Body:            params.Body,
-			Reporter:        params.Reporter,
-			Assignee:        params.Assignee,
-			Priority:        params.Priority,
-			Labels:          params.Labels,
-			Components:      params.Components,
-			FixVersions:     params.FixVersions,
-			AffectsVersions: params.AffectsVersions,
-			CustomFields:    params.CustomFields,
-			EpicField:       viper.GetString("epic.link"),
+			Project:          project,
+			IssueType:        params.IssueType,
+			ParentIssueKey:   params.ParentIssueKey,
+			Summary:          params.Summary,
+			Body:             params.Body,
+			Reporter:         params.Reporter,
+			Assignee:         params.Assignee,
+			Priority:         params.Priority,
+			Labels:           params.Labels,
+			Components:       params.Components,
+			FixVersions:      params.FixVersions,
+			AffectsVersions:  params.AffectsVersions,
+			OriginalEstimate: params.OriginalEstimate,
+			CustomFields:     params.CustomFields,
+			EpicField:        viper.GetString("epic.link"),
 		}
 		cr.ForProjectType(projectType)
 		cr.ForInstallationType(installation)
@@ -347,6 +348,9 @@ func parseFlags(flags query.FlagParser) *cmdcommon.CreateParams {
 	affectsVersions, err := flags.GetStringArray("affects-version")
 	cmdutil.ExitIfError(err)
 
+	originalEstimate, err := flags.GetString("original-estimate")
+	cmdutil.ExitIfError(err)
+
 	custom, err := flags.GetStringToString("custom")
 	cmdutil.ExitIfError(err)
 
@@ -360,20 +364,21 @@ func parseFlags(flags query.FlagParser) *cmdcommon.CreateParams {
 	cmdutil.ExitIfError(err)
 
 	return &cmdcommon.CreateParams{
-		IssueType:       issueType,
-		ParentIssueKey:  parentIssueKey,
-		Summary:         summary,
-		Body:            body,
-		Priority:        priority,
-		Assignee:        assignee,
-		Labels:          labels,
-		Reporter:        reporter,
-		Components:      components,
-		FixVersions:     fixVersions,
-		AffectsVersions: affectsVersions,
-		CustomFields:    custom,
-		Template:        template,
-		NoInput:         noInput,
-		Debug:           debug,
+		IssueType:        issueType,
+		ParentIssueKey:   parentIssueKey,
+		Summary:          summary,
+		Body:             body,
+		Priority:         priority,
+		Assignee:         assignee,
+		Labels:           labels,
+		Reporter:         reporter,
+		Components:       components,
+		FixVersions:      fixVersions,
+		AffectsVersions:  affectsVersions,
+		OriginalEstimate: originalEstimate,
+		CustomFields:     custom,
+		Template:         template,
+		NoInput:          noInput,
+		Debug:            debug,
 	}
 }
diff --git a/internal/cmdcommon/create.go b/internal/cmdcommon/create.go
index 47ef993..7615ba7 100644
--- a/internal/cmdcommon/create.go
+++ b/internal/cmdcommon/create.go
@@ -23,22 +23,23 @@ const (
 
 // CreateParams holds parameters for create command.
 type CreateParams struct {
-	Name            string
-	IssueType       string
-	ParentIssueKey  string
-	Summary         string
-	Body            string
-	Priority        string
-	Reporter        string
-	Assignee        string
-	Labels          []string
-	Components      []string
-	FixVersions     []string
-	AffectsVersions []string
-	CustomFields    map[string]string
-	Template        string
-	NoInput         bool
-	Debug           bool
+	Name             string
+	IssueType        string
+	ParentIssueKey   string
+	Summary          string
+	Body             string
+	Priority         string
+	Reporter         string
+	Assignee         string
+	Labels           []string
+	Components       []string
+	FixVersions      []string
+	AffectsVersions  []string
+	OriginalEstimate string
+	CustomFields     map[string]string
+	Template         string
+	NoInput          bool
+	Debug            bool
 }
 
 // SetCreateFlags sets flags supported by create command.
@@ -63,6 +64,7 @@ And, this field is mandatory when creating a sub-task.`)
 	cmd.Flags().StringArrayP("component", "C", []string{}, prefix+" components")
 	cmd.Flags().StringArray("fix-version", []string{}, "Release info (fixVersions)")
 	cmd.Flags().StringArray("affects-version", []string{}, "Release info (affectsVersions)")
+	cmd.Flags().StringP("original-estimate", "e", "", prefix+" Original estimate")
 	cmd.Flags().StringToString("custom", custom, "Set custom fields")
 	cmd.Flags().StringP("template", "T", "", "Path to a file to read body/description from")
 	cmd.Flags().Bool("web", false, "Open in web browser after successful creation")
diff --git a/pkg/jira/create.go b/pkg/jira/create.go
index 0f4edbd..7915dcc 100644
--- a/pkg/jira/create.go
+++ b/pkg/jira/create.go
@@ -24,16 +24,17 @@ type CreateRequest struct {
 	IssueType string
 	// ParentIssueKey is required when creating a sub-task for classic project.
 	// This can also be used to attach epic for next-gen project.
-	ParentIssueKey  string
-	Summary         string
-	Body            interface{} // string in v1/v2 and adf.ADF in v3
-	Reporter        string
-	Assignee        string
-	Priority        string
-	Labels          []string
-	Components      []string
-	FixVersions     []string
-	AffectsVersions []string
+	ParentIssueKey   string
+	Summary          string
+	Body             interface{} // string in v1/v2 and adf.ADF in v3
+	Reporter         string
+	Assignee         string
+	Priority         string
+	Labels           []string
+	Components       []string
+	FixVersions      []string
+	AffectsVersions  []string
+	OriginalEstimate string
 	// EpicField is the dynamic epic field name
 	// that changes per jira installation.
 	EpicField string
@@ -132,6 +133,9 @@ func (*Client) getRequestData(req *CreateRequest) *createRequest {
 		Summary:   req.Summary,
 		Labels:    req.Labels,
 		epicField: req.EpicField,
+		TimeTracking: struct {
+			OriginalEstimate string `json:"originalEstimate,omitempty"`
+		}{OriginalEstimate: req.OriginalEstimate},
 	}
 
 	switch v := req.Body.(type) {
@@ -303,6 +307,9 @@ type createFields struct {
 	AffectsVersions []struct {
 		Name string `json:"name,omitempty"`
 	} `json:"versions,omitempty"`
+	TimeTracking struct {
+		OriginalEstimate string `json:"originalEstimate,omitempty"`
+	} `json:"timetracking,omitempty"`
 	epicField    string
 	customFields customField
 }


Test patch:
diff --git a/pkg/jira/create_test.go b/pkg/jira/create_test.go
index e1320d7..8f8ba49 100644
--- a/pkg/jira/create_test.go
+++ b/pkg/jira/create_test.go
@@ -46,7 +46,8 @@ func (c *createTestServer) statusCode(code int) {
 func TestCreate(t *testing.T) {
 	expectedBody := `{"update":{},"fields":{"project":{"key":"TEST"},"issuetype":{"name":"Bug"},` +
 		`"summary":"Test bug","description":"Test description","priority":{"name":"Normal"},"labels":["test","dev"],` +
-		`"components":[{"name":"BE"},{"name":"FE"}],"fixVersions":[{"name":"v2.0"},{"name":"v2.1-hotfix"}],"versions":[{"name":"v3.0"},{"name":"v3.1-hotfix"}]}}`
+		`"components":[{"name":"BE"},{"name":"FE"}],"fixVersions":[{"name":"v2.0"},{"name":"v2.1-hotfix"}],"versions":[{"name":"v3.0"},{"name":"v3.1-hotfix"}],` +
+		`"timetracking":{"originalEstimate":"2d"}}}`
 	testServer := createTestServer{code: 201}
 	server := testServer.serve(t, expectedBody)
 	defer server.Close()
@@ -54,15 +55,16 @@ func TestCreate(t *testing.T) {
 	client := NewClient(Config{Server: server.URL}, WithTimeout(3*time.Second))
 
 	requestData := CreateRequest{
-		Project:         "TEST",
-		IssueType:       "Bug",
-		Summary:         "Test bug",
-		Body:            "Test description",
-		Priority:        "Normal",
-		Labels:          []string{"test", "dev"},
-		Components:      []string{"BE", "FE"},
-		FixVersions:     []string{"v2.0", "v2.1-hotfix"},
-		AffectsVersions: []string{"v3.0", "v3.1-hotfix"},
+		Project:          "TEST",
+		IssueType:        "Bug",
+		Summary:          "Test bug",
+		Body:             "Test description",
+		Priority:         "Normal",
+		Labels:           []string{"test", "dev"},
+		Components:       []string{"BE", "FE"},
+		FixVersions:      []string{"v2.0", "v2.1-hotfix"},
+		AffectsVersions:  []string{"v3.0", "v3.1-hotfix"},
+		OriginalEstimate: "2d",
 	}
 	actual, err := client.CreateV2(&requestData)
 	assert.NoError(t, err)
@@ -82,7 +84,7 @@ func TestCreate(t *testing.T) {
 
 func TestCreateSubtask(t *testing.T) {
 	expectedBody := `{"update":{},"fields":{"project":{"key":"TEST"},"issuetype":{"name":"Sub-task"},` +
-		`"parent":{"key":"TEST-123"},"summary":"Test sub-task","description":"Test description"}}`
+		`"parent":{"key":"TEST-123"},"summary":"Test sub-task","description":"Test description","timetracking":{}}}`
 	testServer := createTestServer{code: 201}
 	server := testServer.serve(t, expectedBody)
 	defer server.Close()
@@ -114,7 +116,7 @@ func TestCreateSubtask(t *testing.T) {
 
 func TestCreateEpic(t *testing.T) {
 	expectedBody := `{"update":{},"fields":{"customfield_10001":"CLI","description":"Test description","issuetype":{"name":` +
-		`"Bug"},"priority":{"name":"Normal"},"project":{"key":"TEST"},"summary":"Test bug"}}`
+		`"Bug"},"priority":{"name":"Normal"},"project":{"key":"TEST"},"summary":"Test bug", "timetracking":{}}}`
 	testServer := createTestServer{code: 201}
 	server := testServer.serve(t, expectedBody)
 	defer server.Close()
@@ -146,7 +148,7 @@ func TestCreateEpic(t *testing.T) {
 
 func TestCreateEpicNextGen(t *testing.T) {
 	expectedBody := `{"update":{},"fields":{"description":"Test description","issuetype":{"name":"Bug"},` +
-		`"parent":{"key":"TEST-123"},"project":{"key":"TEST"},"summary":"Test bug"}}`
+		`"parent":{"key":"TEST-123"},"project":{"key":"TEST"},"summary":"Test bug","timetracking":{}}}`
 	testServer := createTestServer{code: 201}
 	server := testServer.serve(t, expectedBody)
 	defer server.Close()