X Tutup
Skip to content

Commit 4109af9

Browse files
authored
Merge pull request cli#2915 from cli/issue-edit
Edit issue command
2 parents 6b1e6db + cd9f211 commit 4109af9

File tree

6 files changed

+928
-43
lines changed

6 files changed

+928
-43
lines changed

api/queries_issue.go

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,44 +24,52 @@ type IssuesAndTotalCount struct {
2424
}
2525

2626
type Issue struct {
27-
ID string
28-
Number int
29-
Title string
30-
URL string
31-
State string
32-
Closed bool
33-
Body string
34-
CreatedAt time.Time
35-
UpdatedAt time.Time
36-
Comments Comments
37-
Author Author
38-
Assignees struct {
39-
Nodes []struct {
40-
Login string
41-
}
42-
TotalCount int
27+
ID string
28+
Number int
29+
Title string
30+
URL string
31+
State string
32+
Closed bool
33+
Body string
34+
CreatedAt time.Time
35+
UpdatedAt time.Time
36+
Comments Comments
37+
Author Author
38+
Assignees Assignees
39+
Labels Labels
40+
ProjectCards ProjectCards
41+
Milestone Milestone
42+
ReactionGroups ReactionGroups
43+
}
44+
45+
type Assignees struct {
46+
Nodes []struct {
47+
Login string
48+
}
49+
TotalCount int
50+
}
51+
52+
type Labels struct {
53+
Nodes []struct {
54+
Name string
4355
}
44-
Labels struct {
45-
Nodes []struct {
56+
TotalCount int
57+
}
58+
59+
type ProjectCards struct {
60+
Nodes []struct {
61+
Project struct {
4662
Name string
4763
}
48-
TotalCount int
49-
}
50-
ProjectCards struct {
51-
Nodes []struct {
52-
Project struct {
53-
Name string
54-
}
55-
Column struct {
56-
Name string
57-
}
64+
Column struct {
65+
Name string
5866
}
59-
TotalCount int
60-
}
61-
Milestone struct {
62-
Title string
6367
}
64-
ReactionGroups ReactionGroups
68+
TotalCount int
69+
}
70+
71+
type Milestone struct {
72+
Title string
6573
}
6674

6775
type IssuesDisabledError struct {
@@ -488,6 +496,20 @@ func IssueDelete(client *Client, repo ghrepo.Interface, issue Issue) error {
488496
return err
489497
}
490498

499+
func IssueUpdate(client *Client, repo ghrepo.Interface, params githubv4.UpdateIssueInput) error {
500+
var mutation struct {
501+
UpdateIssue struct {
502+
Issue struct {
503+
ID string
504+
}
505+
} `graphql:"updateIssue(input: $input)"`
506+
}
507+
variables := map[string]interface{}{"input": params}
508+
gql := graphQLClient(client.http, repo.RepoHost())
509+
err := gql.MutateNamed(context.Background(), "IssueUpdate", &mutation, variables)
510+
return err
511+
}
512+
491513
// milestoneNodeIdToDatabaseId extracts the REST Database ID from the GraphQL Node ID
492514
// This conversion is necessary since the GraphQL API requires the use of the milestone's database ID
493515
// for querying the related issues.

api/queries_pr.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,6 @@ type PullRequest struct {
8080
}
8181
}
8282
}
83-
ReviewRequests struct {
84-
Nodes []struct {
85-
RequestedReviewer struct {
86-
TypeName string `json:"__typename"`
87-
Login string
88-
Name string
89-
}
90-
}
91-
TotalCount int
92-
}
9383
Assignees struct {
9484
Nodes []struct {
9585
Login string
@@ -119,6 +109,18 @@ type PullRequest struct {
119109
Comments Comments
120110
ReactionGroups ReactionGroups
121111
Reviews PullRequestReviews
112+
ReviewRequests ReviewRequests
113+
}
114+
115+
type ReviewRequests struct {
116+
Nodes []struct {
117+
RequestedReviewer struct {
118+
TypeName string `json:"__typename"`
119+
Login string
120+
Name string
121+
}
122+
}
123+
TotalCount int
122124
}
123125

124126
type NotFoundError struct {

pkg/cmd/issue/edit/edit.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package edit
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/MakeNowJust/heredoc"
9+
"github.com/cli/cli/api"
10+
"github.com/cli/cli/internal/ghrepo"
11+
shared "github.com/cli/cli/pkg/cmd/issue/shared"
12+
prShared "github.com/cli/cli/pkg/cmd/pr/shared"
13+
"github.com/cli/cli/pkg/cmdutil"
14+
"github.com/cli/cli/pkg/iostreams"
15+
"github.com/shurcooL/githubv4"
16+
"github.com/spf13/cobra"
17+
)
18+
19+
type EditOptions struct {
20+
HttpClient func() (*http.Client, error)
21+
IO *iostreams.IOStreams
22+
BaseRepo func() (ghrepo.Interface, error)
23+
24+
DetermineEditor func() (string, error)
25+
FieldsToEditSurvey func(*prShared.EditableOptions) error
26+
EditableSurvey func(string, *prShared.EditableOptions) error
27+
FetchOptions func(*api.Client, ghrepo.Interface, *prShared.EditableOptions) error
28+
29+
SelectorArg string
30+
Interactive bool
31+
32+
prShared.EditableOptions
33+
}
34+
35+
func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Command {
36+
opts := &EditOptions{
37+
IO: f.IOStreams,
38+
HttpClient: f.HttpClient,
39+
DetermineEditor: func() (string, error) { return cmdutil.DetermineEditor(f.Config) },
40+
FieldsToEditSurvey: prShared.FieldsToEditSurvey,
41+
EditableSurvey: prShared.EditableSurvey,
42+
FetchOptions: prShared.FetchOptions,
43+
}
44+
45+
cmd := &cobra.Command{
46+
Use: "edit {<number> | <url>}",
47+
Short: "Edit an issue",
48+
Example: heredoc.Doc(`
49+
$ gh issue edit 23 --title "I found a bug" --body "Nothing works"
50+
$ gh issue edit 23 --label "bug,help wanted"
51+
$ gh issue edit 23 --label bug --label "help wanted"
52+
$ gh issue edit 23 --assignee monalisa,hubot
53+
$ gh issue edit 23 --assignee @me
54+
$ gh issue edit 23 --project "Roadmap"
55+
`),
56+
Args: cobra.ExactArgs(1),
57+
RunE: func(cmd *cobra.Command, args []string) error {
58+
// support `-R, --repo` override
59+
opts.BaseRepo = f.BaseRepo
60+
61+
opts.SelectorArg = args[0]
62+
63+
flags := cmd.Flags()
64+
if flags.Changed("title") {
65+
opts.EditableOptions.TitleEdited = true
66+
}
67+
if flags.Changed("body") {
68+
opts.EditableOptions.BodyEdited = true
69+
}
70+
if flags.Changed("assignee") {
71+
opts.EditableOptions.AssigneesEdited = true
72+
}
73+
if flags.Changed("label") {
74+
opts.EditableOptions.LabelsEdited = true
75+
}
76+
if flags.Changed("project") {
77+
opts.EditableOptions.ProjectsEdited = true
78+
}
79+
if flags.Changed("milestone") {
80+
opts.EditableOptions.MilestoneEdited = true
81+
}
82+
83+
if !opts.EditableOptions.Dirty() {
84+
opts.Interactive = true
85+
}
86+
87+
if opts.Interactive && !opts.IO.CanPrompt() {
88+
return &cmdutil.FlagError{Err: errors.New("--tile, --body, --assignee, --label, --project, or --milestone required when not running interactively")}
89+
}
90+
91+
if runF != nil {
92+
return runF(opts)
93+
}
94+
95+
return editRun(opts)
96+
},
97+
}
98+
99+
cmd.Flags().StringVarP(&opts.EditableOptions.Title, "title", "t", "", "Revise the issue title.")
100+
cmd.Flags().StringVarP(&opts.EditableOptions.Body, "body", "b", "", "Revise the issue body.")
101+
cmd.Flags().StringSliceVarP(&opts.EditableOptions.Assignees, "assignee", "a", nil, "Set assigned people by their `login`. Use \"@me\" to self-assign.")
102+
cmd.Flags().StringSliceVarP(&opts.EditableOptions.Labels, "label", "l", nil, "Set the issue labels by `name`")
103+
cmd.Flags().StringSliceVarP(&opts.EditableOptions.Projects, "project", "p", nil, "Set the projects the issue belongs to by `name`")
104+
cmd.Flags().StringVarP(&opts.EditableOptions.Milestone, "milestone", "m", "", "Set the milestone the issue belongs to by `name`")
105+
106+
return cmd
107+
}
108+
109+
func editRun(opts *EditOptions) error {
110+
httpClient, err := opts.HttpClient()
111+
if err != nil {
112+
return err
113+
}
114+
apiClient := api.NewClientFromHTTP(httpClient)
115+
116+
issue, repo, err := shared.IssueFromArg(apiClient, opts.BaseRepo, opts.SelectorArg)
117+
if err != nil {
118+
return err
119+
}
120+
121+
editOptions := opts.EditableOptions
122+
editOptions.TitleDefault = issue.Title
123+
editOptions.BodyDefault = issue.Body
124+
editOptions.AssigneesDefault = issue.Assignees
125+
editOptions.LabelsDefault = issue.Labels
126+
editOptions.ProjectsDefault = issue.ProjectCards
127+
editOptions.MilestoneDefault = issue.Milestone
128+
129+
if opts.Interactive {
130+
err = opts.FieldsToEditSurvey(&editOptions)
131+
if err != nil {
132+
return err
133+
}
134+
}
135+
136+
opts.IO.StartProgressIndicator()
137+
err = opts.FetchOptions(apiClient, repo, &editOptions)
138+
opts.IO.StopProgressIndicator()
139+
if err != nil {
140+
return err
141+
}
142+
143+
if opts.Interactive {
144+
editorCommand, err := opts.DetermineEditor()
145+
if err != nil {
146+
return err
147+
}
148+
err = opts.EditableSurvey(editorCommand, &editOptions)
149+
if err != nil {
150+
return err
151+
}
152+
}
153+
154+
opts.IO.StartProgressIndicator()
155+
err = updateIssue(apiClient, repo, issue.ID, editOptions)
156+
opts.IO.StopProgressIndicator()
157+
if err != nil {
158+
return err
159+
}
160+
161+
fmt.Fprintln(opts.IO.Out, issue.URL)
162+
163+
return nil
164+
}
165+
166+
func updateIssue(client *api.Client, repo ghrepo.Interface, id string, options prShared.EditableOptions) error {
167+
params := githubv4.UpdateIssueInput{ID: id}
168+
if options.TitleEdited {
169+
title := githubv4.String(options.Title)
170+
params.Title = &title
171+
}
172+
if options.BodyEdited {
173+
body := githubv4.String(options.Body)
174+
params.Body = &body
175+
}
176+
if options.AssigneesEdited {
177+
meReplacer := prShared.NewMeReplacer(client, repo.RepoHost())
178+
assignees, err := meReplacer.ReplaceSlice(options.Assignees)
179+
if err != nil {
180+
return err
181+
}
182+
ids, err := options.Metadata.MembersToIDs(assignees)
183+
if err != nil {
184+
return err
185+
}
186+
assigneeIDs := make([]githubv4.ID, len(ids))
187+
for i, v := range ids {
188+
assigneeIDs[i] = v
189+
}
190+
params.AssigneeIDs = &assigneeIDs
191+
}
192+
if options.LabelsEdited {
193+
ids, err := options.Metadata.LabelsToIDs(options.Labels)
194+
if err != nil {
195+
return err
196+
}
197+
labelIDs := make([]githubv4.ID, len(ids))
198+
for i, v := range ids {
199+
labelIDs[i] = v
200+
}
201+
params.LabelIDs = &labelIDs
202+
}
203+
if options.ProjectsEdited {
204+
ids, err := options.Metadata.ProjectsToIDs(options.Projects)
205+
if err != nil {
206+
return err
207+
}
208+
projectIDs := make([]githubv4.ID, len(ids))
209+
for i, v := range ids {
210+
projectIDs[i] = v
211+
}
212+
params.ProjectIDs = &projectIDs
213+
}
214+
if options.MilestoneEdited {
215+
id, err := options.Metadata.MilestoneToID(options.Milestone)
216+
if err != nil {
217+
return err
218+
}
219+
milestoneID := githubv4.ID(id)
220+
params.MilestoneID = &milestoneID
221+
}
222+
return api.IssueUpdate(client, repo, params)
223+
}

0 commit comments

Comments
 (0)
X Tutup