Edit issue command

This commit is contained in:
Sam Coe 2021-01-27 14:12:47 -08:00
parent 481ec92ebf
commit b366802aa1
No known key found for this signature in database
GPG key ID: 8E322C20F811D086
6 changed files with 964 additions and 43 deletions

View file

@ -24,44 +24,52 @@ type IssuesAndTotalCount struct {
}
type Issue struct {
ID string
Number int
Title string
URL string
State string
Closed bool
Body string
CreatedAt time.Time
UpdatedAt time.Time
Comments Comments
Author Author
Assignees struct {
Nodes []struct {
Login string
}
TotalCount int
ID string
Number int
Title string
URL string
State string
Closed bool
Body string
CreatedAt time.Time
UpdatedAt time.Time
Comments Comments
Author Author
Assignees Assignees
Labels Labels
ProjectCards ProjectCards
Milestone Milestone
ReactionGroups ReactionGroups
}
type Assignees struct {
Nodes []struct {
Login string
}
Labels struct {
Nodes []struct {
TotalCount int
}
type Labels struct {
Nodes []struct {
Name string
}
TotalCount int
}
type ProjectCards struct {
Nodes []struct {
Project struct {
Name string
}
TotalCount int
}
ProjectCards struct {
Nodes []struct {
Project struct {
Name string
}
Column struct {
Name string
}
Column struct {
Name string
}
TotalCount int
}
Milestone struct {
Title string
}
ReactionGroups ReactionGroups
TotalCount int
}
type Milestone struct {
Title string
}
type IssuesDisabledError struct {
@ -488,6 +496,20 @@ func IssueDelete(client *Client, repo ghrepo.Interface, issue Issue) error {
return err
}
func IssueUpdate(client *Client, repo ghrepo.Interface, params githubv4.UpdateIssueInput) error {
var mutation struct {
UpdateIssue struct {
Issue struct {
ID string
}
} `graphql:"updateIssue(input: $input)"`
}
variables := map[string]interface{}{"input": params}
gql := graphQLClient(client.http, repo.RepoHost())
err := gql.MutateNamed(context.Background(), "IssueUpdate", &mutation, variables)
return err
}
// milestoneNodeIdToDatabaseId extracts the REST Database ID from the GraphQL Node ID
// This conversion is necessary since the GraphQL API requires the use of the milestone's database ID
// for querying the related issues.

View file

@ -80,16 +80,6 @@ type PullRequest struct {
}
}
}
ReviewRequests struct {
Nodes []struct {
RequestedReviewer struct {
TypeName string `json:"__typename"`
Login string
Name string
}
}
TotalCount int
}
Assignees struct {
Nodes []struct {
Login string
@ -119,6 +109,18 @@ type PullRequest struct {
Comments Comments
ReactionGroups ReactionGroups
Reviews PullRequestReviews
ReviewRequests ReviewRequests
}
type ReviewRequests struct {
Nodes []struct {
RequestedReviewer struct {
TypeName string `json:"__typename"`
Login string
Name string
}
}
TotalCount int
}
type NotFoundError struct {

236
pkg/cmd/issue/edit/edit.go Normal file
View file

@ -0,0 +1,236 @@
package edit
import (
"errors"
"fmt"
"net/http"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/ghrepo"
shared "github.com/cli/cli/pkg/cmd/issue/shared"
prShared "github.com/cli/cli/pkg/cmd/pr/shared"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
"github.com/cli/cli/utils"
"github.com/shurcooL/githubv4"
"github.com/spf13/cobra"
)
type EditOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
OpenInBrowser func(string) error
DetermineEditor func() (string, error)
FieldsToEditSurvey func(*prShared.EditableOptions) error
EditableSurvey func(string, *prShared.EditableOptions) error
FetchOptions func(*api.Client, ghrepo.Interface, *prShared.EditableOptions) error
SelectorArg string
Interactive bool
WebMode bool
prShared.EditableOptions
}
func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Command {
opts := &EditOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
OpenInBrowser: utils.OpenInBrowser,
DetermineEditor: func() (string, error) { return cmdutil.DetermineEditor(f.Config) },
FieldsToEditSurvey: prShared.FieldsToEditSurvey,
EditableSurvey: prShared.EditableSurvey,
FetchOptions: prShared.FetchOptions,
}
cmd := &cobra.Command{
Use: "edit {<number> | <url>}",
Short: "Edit an issue",
Example: heredoc.Doc(`
$ gh issue edit 23 --title "I found a bug" --body "Nothing works"
$ gh issue edit 23 --label "bug,help wanted"
$ gh issue edit 23 --label bug --label "help wanted"
$ gh issue edit 23 --assignee monalisa,hubot
$ gh issue edit 23 --assignee @me
$ gh issue edit 23 --project "Roadmap"
`),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// support `-R, --repo` override
opts.BaseRepo = f.BaseRepo
opts.SelectorArg = args[0]
flags := cmd.Flags()
if flags.Changed("title") {
opts.EditableOptions.TitleEdited = true
}
if flags.Changed("body") {
opts.EditableOptions.BodyEdited = true
}
if flags.Changed("assignee") {
opts.EditableOptions.AssigneesEdited = true
}
if flags.Changed("label") {
opts.EditableOptions.LabelsEdited = true
}
if flags.Changed("project") {
opts.EditableOptions.ProjectsEdited = true
}
if flags.Changed("milestone") {
opts.EditableOptions.MilestoneEdited = true
}
if !opts.EditableOptions.Dirty() {
opts.Interactive = true
}
if opts.Interactive && !opts.IO.CanPrompt() {
return &cmdutil.FlagError{Err: errors.New("--tile, --body, --assignee, --label, --project, --milestone, or --web required when not running interactively")}
}
if runF != nil {
return runF(opts)
}
return editRun(opts)
},
}
cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "Open the browser to create an issue")
cmd.Flags().StringVarP(&opts.EditableOptions.Title, "title", "t", "", "Supply a title. Will prompt for one otherwise.")
cmd.Flags().StringVarP(&opts.EditableOptions.Body, "body", "b", "", "Supply a body. Will prompt for one otherwise.")
cmd.Flags().StringSliceVarP(&opts.EditableOptions.Assignees, "assignee", "a", nil, "Assign people by their `login`. Use \"@me\" to self-assign.")
cmd.Flags().StringSliceVarP(&opts.EditableOptions.Labels, "label", "l", nil, "Add labels by `name`")
cmd.Flags().StringSliceVarP(&opts.EditableOptions.Projects, "project", "p", nil, "Add the issue to projects by `name`")
cmd.Flags().StringVarP(&opts.EditableOptions.Milestone, "milestone", "m", "", "Add the issue to a milestone by `name`")
return cmd
}
func editRun(opts *EditOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
apiClient := api.NewClientFromHTTP(httpClient)
issue, repo, err := shared.IssueFromArg(apiClient, opts.BaseRepo, opts.SelectorArg)
if err != nil {
return err
}
if opts.WebMode {
openURL := issue.URL
if opts.IO.IsStdoutTTY() {
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
}
return opts.OpenInBrowser(openURL)
}
editOptions := opts.EditableOptions
editOptions.TitleDefault = issue.Title
editOptions.BodyDefault = issue.Body
editOptions.AssigneesDefault = issue.Assignees
editOptions.LabelsDefault = issue.Labels
editOptions.ProjectsDefault = issue.ProjectCards
editOptions.MilestoneDefault = issue.Milestone
if opts.Interactive {
err = opts.FieldsToEditSurvey(&editOptions)
if err != nil {
return err
}
}
opts.IO.StartProgressIndicator()
err = opts.FetchOptions(apiClient, repo, &editOptions)
opts.IO.StopProgressIndicator()
if err != nil {
return err
}
if opts.Interactive {
editorCommand, err := opts.DetermineEditor()
if err != nil {
return err
}
err = opts.EditableSurvey(editorCommand, &editOptions)
if err != nil {
return err
}
}
opts.IO.StartProgressIndicator()
err = updateIssue(apiClient, repo, issue.ID, editOptions)
opts.IO.StopProgressIndicator()
if err != nil {
return err
}
fmt.Fprintln(opts.IO.Out, issue.URL)
return nil
}
func updateIssue(client *api.Client, repo ghrepo.Interface, id string, options prShared.EditableOptions) error {
params := githubv4.UpdateIssueInput{ID: id}
if options.TitleEdited {
title := githubv4.String(options.Title)
params.Title = &title
}
if options.BodyEdited {
body := githubv4.String(options.Body)
params.Body = &body
}
if options.AssigneesEdited {
meReplacer := prShared.NewMeReplacer(client, repo.RepoHost())
assignees, err := meReplacer.ReplaceSlice(options.Assignees)
if err != nil {
return err
}
ids, err := options.Metadata.MembersToIDs(assignees)
if err != nil {
return err
}
assigneeIDs := make([]githubv4.ID, len(ids))
for i, v := range ids {
assigneeIDs[i] = v
}
params.AssigneeIDs = &assigneeIDs
}
if options.LabelsEdited {
ids, err := options.Metadata.LabelsToIDs(options.Labels)
if err != nil {
return err
}
labelIDs := make([]githubv4.ID, len(ids))
for i, v := range ids {
labelIDs[i] = v
}
params.LabelIDs = &labelIDs
}
if options.ProjectsEdited {
ids, err := options.Metadata.ProjectsToIDs(options.Projects)
if err != nil {
return err
}
projectIDs := make([]githubv4.ID, len(ids))
for i, v := range ids {
projectIDs[i] = v
}
params.ProjectIDs = &projectIDs
}
if options.MilestoneEdited {
id, err := options.Metadata.MilestoneToID(options.Milestone)
if err != nil {
return err
}
milestoneID := githubv4.ID(id)
params.MilestoneID = &milestoneID
}
return api.IssueUpdate(client, repo, params)
}

View file

@ -0,0 +1,349 @@
package edit
import (
"bytes"
"net/http"
"testing"
"github.com/cli/cli/internal/ghrepo"
prShared "github.com/cli/cli/pkg/cmd/pr/shared"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/httpmock"
"github.com/cli/cli/pkg/iostreams"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
)
func TestNewCmdEdit(t *testing.T) {
tests := []struct {
name string
input string
output EditOptions
wantsErr bool
}{
{
name: "no argument",
input: "",
output: EditOptions{},
wantsErr: true,
},
{
name: "issue number argument",
input: "23",
output: EditOptions{
SelectorArg: "23",
Interactive: true,
},
wantsErr: false,
},
{
name: "web flag",
input: "23 --web",
output: EditOptions{
SelectorArg: "23",
Interactive: true,
WebMode: true,
},
wantsErr: false,
},
{
name: "title flag",
input: "23 --title test",
output: EditOptions{
SelectorArg: "23",
EditableOptions: prShared.EditableOptions{
Title: "test",
TitleEdited: true,
},
},
wantsErr: false,
},
{
name: "body flag",
input: "23 --body test",
output: EditOptions{
SelectorArg: "23",
EditableOptions: prShared.EditableOptions{
Body: "test",
BodyEdited: true,
},
},
wantsErr: false,
},
{
name: "assignee flag",
input: "23 --assignee monalisa,hubot",
output: EditOptions{
SelectorArg: "23",
EditableOptions: prShared.EditableOptions{
Assignees: []string{"monalisa", "hubot"},
AssigneesEdited: true,
},
},
wantsErr: false,
},
{
name: "label flag",
input: "23 --label feature,TODO,bug",
output: EditOptions{
SelectorArg: "23",
EditableOptions: prShared.EditableOptions{
Labels: []string{"feature", "TODO", "bug"},
LabelsEdited: true,
},
},
wantsErr: false,
},
{
name: "project flag",
input: "23 --project Cleanup,Roadmap",
output: EditOptions{
SelectorArg: "23",
EditableOptions: prShared.EditableOptions{
Projects: []string{"Cleanup", "Roadmap"},
ProjectsEdited: true,
},
},
wantsErr: false,
},
{
name: "milestone flag",
input: "23 --milestone GA",
output: EditOptions{
SelectorArg: "23",
EditableOptions: prShared.EditableOptions{
Milestone: "GA",
MilestoneEdited: true,
},
},
wantsErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
io, _, _, _ := iostreams.Test()
io.SetStdoutTTY(true)
io.SetStdinTTY(true)
io.SetStderrTTY(true)
f := &cmdutil.Factory{
IOStreams: io,
}
argv, err := shlex.Split(tt.input)
assert.NoError(t, err)
var gotOpts *EditOptions
cmd := NewCmdEdit(f, func(opts *EditOptions) error {
gotOpts = opts
return nil
})
cmd.Flags().BoolP("help", "x", false, "")
cmd.SetArgs(argv)
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
_, err = cmd.ExecuteC()
if tt.wantsErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.output.SelectorArg, gotOpts.SelectorArg)
assert.Equal(t, tt.output.WebMode, gotOpts.WebMode)
assert.Equal(t, tt.output.Interactive, gotOpts.Interactive)
assert.Equal(t, tt.output.EditableOptions, gotOpts.EditableOptions)
})
}
}
func Test_editRun(t *testing.T) {
tests := []struct {
name string
input *EditOptions
httpStubs func(*testing.T, *httpmock.Registry)
stdout string
stderr string
}{
{
name: "web mode",
input: &EditOptions{
SelectorArg: "123",
WebMode: true,
OpenInBrowser: func(string) error { return nil },
},
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
mockIssueGet(t, reg)
},
stderr: "Opening github.com/OWNER/REPO/issue/123 in your browser.\n",
},
{
name: "non-interactive",
input: &EditOptions{
SelectorArg: "123",
Interactive: false,
EditableOptions: prShared.EditableOptions{
Title: "new title",
TitleEdited: true,
Body: "new body",
BodyEdited: true,
Assignees: []string{"monalisa", "hubot"},
AssigneesEdited: true,
Labels: []string{"feature", "TODO", "bug"},
LabelsEdited: true,
Projects: []string{"Cleanup", "Roadmap"},
ProjectsEdited: true,
Milestone: "GA",
MilestoneEdited: true,
},
FetchOptions: prShared.FetchOptions,
},
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
mockIssueGet(t, reg)
mockRepoMetadata(t, reg)
mockIssueUpdate(t, reg)
},
stdout: "https://github.com/OWNER/REPO/issue/123\n",
},
{
name: "interactive",
input: &EditOptions{
SelectorArg: "123",
Interactive: true,
FieldsToEditSurvey: func(eo *prShared.EditableOptions) error {
eo.TitleEdited = true
eo.BodyEdited = true
eo.AssigneesEdited = true
eo.LabelsEdited = true
eo.ProjectsEdited = true
eo.MilestoneEdited = true
return nil
},
EditableSurvey: func(_ string, eo *prShared.EditableOptions) error {
eo.Title = "new title"
eo.Body = "new body"
eo.Assignees = []string{"monalisa", "hubot"}
eo.Labels = []string{"feature", "TODO", "bug"}
eo.Projects = []string{"Cleanup", "Roadmap"}
eo.Milestone = "GA"
return nil
},
FetchOptions: prShared.FetchOptions,
DetermineEditor: func() (string, error) { return "vim", nil },
},
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
mockIssueGet(t, reg)
mockRepoMetadata(t, reg)
mockIssueUpdate(t, reg)
},
stdout: "https://github.com/OWNER/REPO/issue/123\n",
},
}
for _, tt := range tests {
io, _, stdout, stderr := iostreams.Test()
io.SetStdoutTTY(true)
io.SetStdinTTY(true)
io.SetStderrTTY(true)
reg := &httpmock.Registry{}
defer reg.Verify(t)
tt.httpStubs(t, reg)
httpClient := func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }
baseRepo := func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }
tt.input.IO = io
tt.input.HttpClient = httpClient
tt.input.BaseRepo = baseRepo
t.Run(tt.name, func(t *testing.T) {
err := editRun(tt.input)
assert.NoError(t, err)
assert.Equal(t, tt.stdout, stdout.String())
assert.Equal(t, tt.stderr, stderr.String())
})
}
}
func mockIssueGet(_ *testing.T, reg *httpmock.Registry) {
reg.Register(
httpmock.GraphQL(`query IssueByNumber\b`),
httpmock.StringResponse(`
{ "data": { "repository": { "hasIssuesEnabled": true, "issue": {
"number": 123,
"url": "https://github.com/OWNER/REPO/issue/123"
} } } }`),
)
}
func mockRepoMetadata(_ *testing.T, reg *httpmock.Registry) {
reg.Register(
httpmock.GraphQL(`query RepositoryAssignableUsers\b`),
httpmock.StringResponse(`
{ "data": { "repository": { "assignableUsers": {
"nodes": [
{ "login": "hubot", "id": "HUBOTID" },
{ "login": "MonaLisa", "id": "MONAID" }
],
"pageInfo": { "hasNextPage": false }
} } } }
`))
reg.Register(
httpmock.GraphQL(`query RepositoryLabelList\b`),
httpmock.StringResponse(`
{ "data": { "repository": { "labels": {
"nodes": [
{ "name": "feature", "id": "FEATUREID" },
{ "name": "TODO", "id": "TODOID" },
{ "name": "bug", "id": "BUGID" }
],
"pageInfo": { "hasNextPage": false }
} } } }
`))
reg.Register(
httpmock.GraphQL(`query RepositoryMilestoneList\b`),
httpmock.StringResponse(`
{ "data": { "repository": { "milestones": {
"nodes": [
{ "title": "GA", "id": "GAID" },
{ "title": "Big One.oh", "id": "BIGONEID" }
],
"pageInfo": { "hasNextPage": false }
} } } }
`))
reg.Register(
httpmock.GraphQL(`query RepositoryProjectList\b`),
httpmock.StringResponse(`
{ "data": { "repository": { "projects": {
"nodes": [
{ "name": "Cleanup", "id": "CLEANUPID" },
{ "name": "Roadmap", "id": "ROADMAPID" }
],
"pageInfo": { "hasNextPage": false }
} } } }
`))
reg.Register(
httpmock.GraphQL(`query OrganizationProjectList\b`),
httpmock.StringResponse(`
{ "data": { "organization": { "projects": {
"nodes": [
{ "name": "Triage", "id": "TRIAGEID" }
],
"pageInfo": { "hasNextPage": false }
} } } }
`))
}
func mockIssueUpdate(t *testing.T, reg *httpmock.Registry) {
reg.Register(
httpmock.GraphQL(`mutation IssueUpdate\b`),
httpmock.GraphQLMutation(`
{ "data": { "updateIssue": { "issue": {
"id": "123"
} } } }`,
func(inputs map[string]interface{}) {}),
)
}

View file

@ -6,6 +6,7 @@ import (
cmdComment "github.com/cli/cli/pkg/cmd/issue/comment"
cmdCreate "github.com/cli/cli/pkg/cmd/issue/create"
cmdDelete "github.com/cli/cli/pkg/cmd/issue/delete"
cmdEdit "github.com/cli/cli/pkg/cmd/issue/edit"
cmdList "github.com/cli/cli/pkg/cmd/issue/list"
cmdReopen "github.com/cli/cli/pkg/cmd/issue/reopen"
cmdStatus "github.com/cli/cli/pkg/cmd/issue/status"
@ -44,6 +45,7 @@ func NewCmdIssue(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(cmdView.NewCmdView(f, nil))
cmd.AddCommand(cmdComment.NewCmdComment(f, nil))
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
cmd.AddCommand(cmdEdit.NewCmdEdit(f, nil))
return cmd
}

View file

@ -0,0 +1,310 @@
package shared
import (
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/ghrepo"
"github.com/cli/cli/pkg/surveyext"
)
type EditableOptions struct {
Title string
TitleDefault string
TitleEdited bool
Body string
BodyDefault string
BodyEdited bool
Reviewers []string
ReviewersDefault api.ReviewRequests
ReviewersOptions []string
ReviewersEdited bool
ReviewersAllowed bool
Assignees []string
AssigneesDefault api.Assignees
AssigneesOptions []string
AssigneesEdited bool
Labels []string
LabelsDefault api.Labels
LabelsOptions []string
LabelsEdited bool
Projects []string
ProjectsDefault api.ProjectCards
ProjectsOptions []string
ProjectsEdited bool
Milestone string
MilestoneDefault api.Milestone
MilestoneOptions []string
MilestoneEdited bool
Metadata api.RepoMetadataResult
}
func (e EditableOptions) Dirty() bool {
return e.TitleEdited ||
e.BodyEdited ||
e.ReviewersEdited ||
e.AssigneesEdited ||
e.LabelsEdited ||
e.ProjectsEdited ||
e.MilestoneEdited
}
func EditableSurvey(editorCommand string, options *EditableOptions) error {
if options.TitleEdited {
title, err := titleSurvey(options.TitleDefault)
if err != nil {
return err
}
options.Title = title
}
if options.BodyEdited {
body, err := bodySurvey(options.BodyDefault, editorCommand)
if err != nil {
return err
}
options.Body = body
}
if options.AssigneesEdited {
assignees, err := assigneesSurvey(options.AssigneesDefault, options.AssigneesOptions)
if err != nil {
return err
}
options.Assignees = assignees
}
if options.LabelsEdited {
labels, err := labelsSurvey(options.LabelsDefault, options.LabelsOptions)
if err != nil {
return err
}
options.Labels = labels
}
if options.ProjectsEdited {
projects, err := projectsSurvey(options.ProjectsDefault, options.ProjectsOptions)
if err != nil {
return err
}
options.Projects = projects
}
if options.MilestoneEdited {
milestone, err := milestoneSurvey(options.MilestoneDefault, options.MilestoneOptions)
if err != nil {
return err
}
options.Milestone = milestone
}
confirm, err := confirmSurvey()
if err != nil {
return err
}
if !confirm {
return fmt.Errorf("Discarding...")
}
return nil
}
func FieldsToEditSurvey(options *EditableOptions) error {
contains := func(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
results := []string{}
opts := []string{"Title", "Body"}
if options.ReviewersAllowed {
opts = append(opts, "Reviewers")
}
opts = append(opts, "Assignees", "Labels", "Projects", "Milestone")
q := &survey.MultiSelect{
Message: "What would you like to edit?",
Options: opts,
}
err := survey.AskOne(q, &results)
if err != nil {
return err
}
if contains(results, "Title") {
options.TitleEdited = true
}
if contains(results, "Body") {
options.BodyEdited = true
}
if contains(results, "Reviewers") {
options.ReviewersEdited = true
}
if contains(results, "Assignees") {
options.AssigneesEdited = true
}
if contains(results, "Labels") {
options.LabelsEdited = true
}
if contains(results, "Projects") {
options.ProjectsEdited = true
}
if contains(results, "Milestone") {
options.MilestoneEdited = true
}
return nil
}
func FetchOptions(client *api.Client, repo ghrepo.Interface, options *EditableOptions) error {
input := api.RepoMetadataInput{
Reviewers: options.ReviewersEdited,
Assignees: options.AssigneesEdited,
Labels: options.LabelsEdited,
Projects: options.ProjectsEdited,
Milestones: options.MilestoneEdited,
}
metadata, err := api.RepoMetadata(client, repo, input)
if err != nil {
return err
}
var users []string
for _, u := range metadata.AssignableUsers {
users = append(users, u.Login)
}
var teams []string
for _, t := range metadata.Teams {
teams = append(teams, fmt.Sprintf("%s/%s", repo.RepoOwner(), t.Slug))
}
var labels []string
for _, l := range metadata.Labels {
labels = append(labels, l.Name)
}
var projects []string
for _, l := range metadata.Projects {
projects = append(projects, l.Name)
}
milestones := []string{noMilestone}
for _, m := range metadata.Milestones {
milestones = append(milestones, m.Title)
}
options.Metadata = *metadata
options.ReviewersOptions = append(users, teams...)
options.AssigneesOptions = users
options.LabelsOptions = labels
options.ProjectsOptions = projects
options.MilestoneOptions = milestones
return nil
}
func titleSurvey(title string) (string, error) {
var result string
q := &survey.Input{
Message: "Title",
Default: title,
}
err := survey.AskOne(q, &result)
return result, err
}
func bodySurvey(body, editorCommand string) (string, error) {
var result string
q := &surveyext.GhEditor{
BlankAllowed: true,
EditorCommand: editorCommand,
Editor: &survey.Editor{Message: "Body",
FileName: "*.md",
Default: body,
HideDefault: true,
AppendDefault: true,
},
}
err := survey.AskOne(q, &result)
return result, err
}
func assigneesSurvey(assignees api.Assignees, assigneesOpts []string) ([]string, error) {
if len(assigneesOpts) == 0 {
return nil, nil
}
logins := []string{}
for _, a := range assignees.Nodes {
logins = append(logins, a.Login)
}
var results []string
q := &survey.MultiSelect{
Message: "Assignees",
Options: assigneesOpts,
Default: logins,
}
err := survey.AskOne(q, &results)
return results, err
}
func labelsSurvey(labels api.Labels, labelOpts []string) ([]string, error) {
if len(labelOpts) == 0 {
return nil, nil
}
names := []string{}
for _, l := range labels.Nodes {
names = append(names, l.Name)
}
var results []string
q := &survey.MultiSelect{
Message: "Labels",
Options: labelOpts,
Default: names,
}
err := survey.AskOne(q, &results)
return results, err
}
func projectsSurvey(projectCards api.ProjectCards, projectsOpts []string) ([]string, error) {
if len(projectsOpts) == 0 {
return nil, nil
}
names := []string{}
for _, c := range projectCards.Nodes {
names = append(names, c.Project.Name)
}
var results []string
q := &survey.MultiSelect{
Message: "Projects",
Options: projectsOpts,
Default: names,
}
err := survey.AskOne(q, &results)
return results, err
}
func milestoneSurvey(milestone api.Milestone, milestoneOpts []string) (string, error) {
if len(milestoneOpts) == 0 {
return "", nil
}
var result string
q := &survey.Select{
Message: "Milestone",
Options: milestoneOpts,
Default: milestone.Title,
}
err := survey.AskOne(q, &result)
return result, err
}
func confirmSurvey() (bool, error) {
var result bool
q := &survey.Confirm{
Message: "Submit?",
Default: true,
}
err := survey.AskOne(q, &result)
return result, err
}