github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/issue/close/close.go (about) 1 package close 2 3 import ( 4 "fmt" 5 "net/http" 6 "time" 7 8 "github.com/ungtb10d/cli/v2/api" 9 fd "github.com/ungtb10d/cli/v2/internal/featuredetection" 10 "github.com/ungtb10d/cli/v2/internal/ghrepo" 11 "github.com/ungtb10d/cli/v2/pkg/cmd/issue/shared" 12 prShared "github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared" 13 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 14 "github.com/ungtb10d/cli/v2/pkg/iostreams" 15 "github.com/shurcooL/githubv4" 16 "github.com/spf13/cobra" 17 ) 18 19 type CloseOptions struct { 20 HttpClient func() (*http.Client, error) 21 IO *iostreams.IOStreams 22 BaseRepo func() (ghrepo.Interface, error) 23 24 SelectorArg string 25 Comment string 26 Reason string 27 28 Detector fd.Detector 29 } 30 31 func NewCmdClose(f *cmdutil.Factory, runF func(*CloseOptions) error) *cobra.Command { 32 opts := &CloseOptions{ 33 IO: f.IOStreams, 34 HttpClient: f.HttpClient, 35 } 36 37 cmd := &cobra.Command{ 38 Use: "close {<number> | <url>}", 39 Short: "Close issue", 40 Args: cobra.ExactArgs(1), 41 RunE: func(cmd *cobra.Command, args []string) error { 42 // support `-R, --repo` override 43 opts.BaseRepo = f.BaseRepo 44 45 if len(args) > 0 { 46 opts.SelectorArg = args[0] 47 } 48 49 if runF != nil { 50 return runF(opts) 51 } 52 return closeRun(opts) 53 }, 54 } 55 56 cmd.Flags().StringVarP(&opts.Comment, "comment", "c", "", "Leave a closing comment") 57 cmdutil.StringEnumFlag(cmd, &opts.Reason, "reason", "r", "", []string{"completed", "not planned"}, "Reason for closing") 58 59 return cmd 60 } 61 62 func closeRun(opts *CloseOptions) error { 63 cs := opts.IO.ColorScheme() 64 65 httpClient, err := opts.HttpClient() 66 if err != nil { 67 return err 68 } 69 70 issue, baseRepo, err := shared.IssueFromArgWithFields(httpClient, opts.BaseRepo, opts.SelectorArg, []string{"id", "number", "title", "state"}) 71 if err != nil { 72 return err 73 } 74 75 if issue.State == "CLOSED" { 76 fmt.Fprintf(opts.IO.ErrOut, "%s Issue #%d (%s) is already closed\n", cs.Yellow("!"), issue.Number, issue.Title) 77 return nil 78 } 79 80 if opts.Comment != "" { 81 commentOpts := &prShared.CommentableOptions{ 82 Body: opts.Comment, 83 HttpClient: opts.HttpClient, 84 InputType: prShared.InputTypeInline, 85 Quiet: true, 86 RetrieveCommentable: func() (prShared.Commentable, ghrepo.Interface, error) { 87 return issue, baseRepo, nil 88 }, 89 } 90 err := prShared.CommentableRun(commentOpts) 91 if err != nil { 92 return err 93 } 94 } 95 96 err = apiClose(httpClient, baseRepo, issue, opts.Detector, opts.Reason) 97 if err != nil { 98 return err 99 } 100 101 fmt.Fprintf(opts.IO.ErrOut, "%s Closed issue #%d (%s)\n", cs.SuccessIconWithColor(cs.Red), issue.Number, issue.Title) 102 103 return nil 104 } 105 106 func apiClose(httpClient *http.Client, repo ghrepo.Interface, issue *api.Issue, detector fd.Detector, reason string) error { 107 if issue.IsPullRequest() { 108 return api.PullRequestClose(httpClient, repo, issue.ID) 109 } 110 111 if reason != "" { 112 if detector == nil { 113 cachedClient := api.NewCachedHTTPClient(httpClient, time.Hour*24) 114 detector = fd.NewDetector(cachedClient, repo.RepoHost()) 115 } 116 features, err := detector.IssueFeatures() 117 if err != nil { 118 return err 119 } 120 if !features.StateReason { 121 // If StateReason is not supported silently close issue without setting StateReason. 122 reason = "" 123 } 124 } 125 126 switch reason { 127 case "": 128 // If no reason is specified do not set it. 129 case "not planned": 130 reason = "NOT_PLANNED" 131 default: 132 reason = "COMPLETED" 133 } 134 135 var mutation struct { 136 CloseIssue struct { 137 Issue struct { 138 ID githubv4.ID 139 } 140 } `graphql:"closeIssue(input: $input)"` 141 } 142 143 variables := map[string]interface{}{ 144 "input": CloseIssueInput{ 145 IssueID: issue.ID, 146 StateReason: reason, 147 }, 148 } 149 150 gql := api.NewClientFromHTTP(httpClient) 151 return gql.Mutate(repo.RepoHost(), "IssueClose", &mutation, variables) 152 } 153 154 type CloseIssueInput struct { 155 IssueID string `json:"issueId"` 156 StateReason string `json:"stateReason,omitempty"` 157 }