github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/pr/shared/editable.go (about) 1 package shared 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/AlecAivazis/survey/v2" 8 "github.com/cli/cli/api" 9 "github.com/cli/cli/internal/ghrepo" 10 "github.com/cli/cli/pkg/set" 11 "github.com/cli/cli/pkg/surveyext" 12 ) 13 14 type Editable struct { 15 Title EditableString 16 Body EditableString 17 Base EditableString 18 Reviewers EditableSlice 19 Assignees EditableSlice 20 Labels EditableSlice 21 Projects EditableSlice 22 Milestone EditableString 23 Metadata api.RepoMetadataResult 24 } 25 26 type EditableString struct { 27 Value string 28 Default string 29 Options []string 30 Edited bool 31 } 32 33 type EditableSlice struct { 34 Value []string 35 Add []string 36 Remove []string 37 Default []string 38 Options []string 39 Edited bool 40 Allowed bool 41 } 42 43 func (e Editable) Dirty() bool { 44 return e.Title.Edited || 45 e.Body.Edited || 46 e.Base.Edited || 47 e.Reviewers.Edited || 48 e.Assignees.Edited || 49 e.Labels.Edited || 50 e.Projects.Edited || 51 e.Milestone.Edited 52 } 53 54 func (e Editable) TitleValue() *string { 55 if !e.Title.Edited { 56 return nil 57 } 58 return &e.Title.Value 59 } 60 61 func (e Editable) BodyValue() *string { 62 if !e.Body.Edited { 63 return nil 64 } 65 return &e.Body.Value 66 } 67 68 func (e Editable) ReviewerIds() (*[]string, *[]string, error) { 69 if !e.Reviewers.Edited { 70 return nil, nil, nil 71 } 72 if len(e.Reviewers.Add) != 0 || len(e.Reviewers.Remove) != 0 { 73 s := set.NewStringSet() 74 s.AddValues(e.Reviewers.Default) 75 s.AddValues(e.Reviewers.Add) 76 s.RemoveValues(e.Reviewers.Remove) 77 e.Reviewers.Value = s.ToSlice() 78 } 79 var userReviewers []string 80 var teamReviewers []string 81 for _, r := range e.Reviewers.Value { 82 if strings.ContainsRune(r, '/') { 83 teamReviewers = append(teamReviewers, r) 84 } else { 85 userReviewers = append(userReviewers, r) 86 } 87 } 88 userIds, err := e.Metadata.MembersToIDs(userReviewers) 89 if err != nil { 90 return nil, nil, err 91 } 92 teamIds, err := e.Metadata.TeamsToIDs(teamReviewers) 93 if err != nil { 94 return nil, nil, err 95 } 96 return &userIds, &teamIds, nil 97 } 98 99 func (e Editable) AssigneeIds(client *api.Client, repo ghrepo.Interface) (*[]string, error) { 100 if !e.Assignees.Edited { 101 return nil, nil 102 } 103 if len(e.Assignees.Add) != 0 || len(e.Assignees.Remove) != 0 { 104 meReplacer := NewMeReplacer(client, repo.RepoHost()) 105 s := set.NewStringSet() 106 s.AddValues(e.Assignees.Default) 107 add, err := meReplacer.ReplaceSlice(e.Assignees.Add) 108 if err != nil { 109 return nil, err 110 } 111 s.AddValues(add) 112 remove, err := meReplacer.ReplaceSlice(e.Assignees.Remove) 113 if err != nil { 114 return nil, err 115 } 116 s.RemoveValues(remove) 117 e.Assignees.Value = s.ToSlice() 118 } 119 a, err := e.Metadata.MembersToIDs(e.Assignees.Value) 120 return &a, err 121 } 122 123 func (e Editable) LabelIds() (*[]string, error) { 124 if !e.Labels.Edited { 125 return nil, nil 126 } 127 if len(e.Labels.Add) != 0 || len(e.Labels.Remove) != 0 { 128 s := set.NewStringSet() 129 s.AddValues(e.Labels.Default) 130 s.AddValues(e.Labels.Add) 131 s.RemoveValues(e.Labels.Remove) 132 e.Labels.Value = s.ToSlice() 133 } 134 l, err := e.Metadata.LabelsToIDs(e.Labels.Value) 135 return &l, err 136 } 137 138 func (e Editable) ProjectIds() (*[]string, error) { 139 if !e.Projects.Edited { 140 return nil, nil 141 } 142 if len(e.Projects.Add) != 0 || len(e.Projects.Remove) != 0 { 143 s := set.NewStringSet() 144 s.AddValues(e.Projects.Default) 145 s.AddValues(e.Projects.Add) 146 s.RemoveValues(e.Projects.Remove) 147 e.Projects.Value = s.ToSlice() 148 } 149 p, err := e.Metadata.ProjectsToIDs(e.Projects.Value) 150 return &p, err 151 } 152 153 func (e Editable) MilestoneId() (*string, error) { 154 if !e.Milestone.Edited { 155 return nil, nil 156 } 157 if e.Milestone.Value == noMilestone || e.Milestone.Value == "" { 158 s := "" 159 return &s, nil 160 } 161 m, err := e.Metadata.MilestoneToID(e.Milestone.Value) 162 return &m, err 163 } 164 165 func EditFieldsSurvey(editable *Editable, editorCommand string) error { 166 var err error 167 if editable.Title.Edited { 168 editable.Title.Value, err = titleSurvey(editable.Title.Default) 169 if err != nil { 170 return err 171 } 172 } 173 if editable.Body.Edited { 174 editable.Body.Value, err = bodySurvey(editable.Body.Default, editorCommand) 175 if err != nil { 176 return err 177 } 178 } 179 if editable.Reviewers.Edited { 180 editable.Reviewers.Value, err = multiSelectSurvey("Reviewers", editable.Reviewers.Default, editable.Reviewers.Options) 181 if err != nil { 182 return err 183 } 184 } 185 if editable.Assignees.Edited { 186 editable.Assignees.Value, err = multiSelectSurvey("Assignees", editable.Assignees.Default, editable.Assignees.Options) 187 if err != nil { 188 return err 189 } 190 } 191 if editable.Labels.Edited { 192 editable.Labels.Value, err = multiSelectSurvey("Labels", editable.Labels.Default, editable.Labels.Options) 193 if err != nil { 194 return err 195 } 196 } 197 if editable.Projects.Edited { 198 editable.Projects.Value, err = multiSelectSurvey("Projects", editable.Projects.Default, editable.Projects.Options) 199 if err != nil { 200 return err 201 } 202 } 203 if editable.Milestone.Edited { 204 editable.Milestone.Value, err = milestoneSurvey(editable.Milestone.Default, editable.Milestone.Options) 205 if err != nil { 206 return err 207 } 208 } 209 confirm, err := confirmSurvey() 210 if err != nil { 211 return err 212 } 213 if !confirm { 214 return fmt.Errorf("Discarding...") 215 } 216 217 return nil 218 } 219 220 func FieldsToEditSurvey(editable *Editable) error { 221 contains := func(s []string, str string) bool { 222 for _, v := range s { 223 if v == str { 224 return true 225 } 226 } 227 return false 228 } 229 230 opts := []string{"Title", "Body"} 231 if editable.Reviewers.Allowed { 232 opts = append(opts, "Reviewers") 233 } 234 opts = append(opts, "Assignees", "Labels", "Projects", "Milestone") 235 results, err := multiSelectSurvey("What would you like to edit?", []string{}, opts) 236 if err != nil { 237 return err 238 } 239 240 if contains(results, "Title") { 241 editable.Title.Edited = true 242 } 243 if contains(results, "Body") { 244 editable.Body.Edited = true 245 } 246 if contains(results, "Reviewers") { 247 editable.Reviewers.Edited = true 248 } 249 if contains(results, "Assignees") { 250 editable.Assignees.Edited = true 251 } 252 if contains(results, "Labels") { 253 editable.Labels.Edited = true 254 } 255 if contains(results, "Projects") { 256 editable.Projects.Edited = true 257 } 258 if contains(results, "Milestone") { 259 editable.Milestone.Edited = true 260 } 261 262 return nil 263 } 264 265 func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable) error { 266 input := api.RepoMetadataInput{ 267 Reviewers: editable.Reviewers.Edited, 268 Assignees: editable.Assignees.Edited, 269 Labels: editable.Labels.Edited, 270 Projects: editable.Projects.Edited, 271 Milestones: editable.Milestone.Edited, 272 } 273 metadata, err := api.RepoMetadata(client, repo, input) 274 if err != nil { 275 return err 276 } 277 278 var users []string 279 for _, u := range metadata.AssignableUsers { 280 users = append(users, u.Login) 281 } 282 var teams []string 283 for _, t := range metadata.Teams { 284 teams = append(teams, fmt.Sprintf("%s/%s", repo.RepoOwner(), t.Slug)) 285 } 286 var labels []string 287 for _, l := range metadata.Labels { 288 labels = append(labels, l.Name) 289 } 290 var projects []string 291 for _, l := range metadata.Projects { 292 projects = append(projects, l.Name) 293 } 294 milestones := []string{noMilestone} 295 for _, m := range metadata.Milestones { 296 milestones = append(milestones, m.Title) 297 } 298 299 editable.Metadata = *metadata 300 editable.Reviewers.Options = append(users, teams...) 301 editable.Assignees.Options = users 302 editable.Labels.Options = labels 303 editable.Projects.Options = projects 304 editable.Milestone.Options = milestones 305 306 return nil 307 } 308 309 func titleSurvey(title string) (string, error) { 310 var result string 311 q := &survey.Input{ 312 Message: "Title", 313 Default: title, 314 } 315 err := survey.AskOne(q, &result) 316 return result, err 317 } 318 319 func bodySurvey(body, editorCommand string) (string, error) { 320 var result string 321 q := &surveyext.GhEditor{ 322 EditorCommand: editorCommand, 323 Editor: &survey.Editor{ 324 Message: "Body", 325 FileName: "*.md", 326 Default: body, 327 HideDefault: true, 328 AppendDefault: true, 329 }, 330 } 331 err := survey.AskOne(q, &result) 332 return result, err 333 } 334 335 func multiSelectSurvey(message string, defaults, options []string) ([]string, error) { 336 if len(options) == 0 { 337 return nil, nil 338 } 339 var results []string 340 q := &survey.MultiSelect{ 341 Message: message, 342 Options: options, 343 Default: defaults, 344 } 345 err := survey.AskOne(q, &results) 346 return results, err 347 } 348 349 func milestoneSurvey(title string, opts []string) (string, error) { 350 if len(opts) == 0 { 351 return "", nil 352 } 353 var result string 354 q := &survey.Select{ 355 Message: "Milestone", 356 Options: opts, 357 Default: title, 358 } 359 err := survey.AskOne(q, &result) 360 return result, err 361 } 362 363 func confirmSurvey() (bool, error) { 364 var result bool 365 q := &survey.Confirm{ 366 Message: "Submit?", 367 Default: true, 368 } 369 err := survey.AskOne(q, &result) 370 return result, err 371 }