mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-31 01:05:26 +01:00 
			
		
		
		
	Feat: interactive issue edit command (#708)
If there are no flags passed to the `issues edit` command, it prompts for changes to the properties like title, description, labels, etc. This is a follow-up to <https://gitea.com/gitea/tea/pulls/506>. Closes: <https://gitea.com/gitea/tea/issues/605> Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com> Reviewed-on: https://gitea.com/gitea/tea/pulls/708 Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com> Co-authored-by: Vincent Neubauer <v.neubauer@darlor.de> Co-committed-by: Vincent Neubauer <v.neubauer@darlor.de>
This commit is contained in:
		 Vincent Neubauer
					Vincent Neubauer
				
			
				
					committed by
					
						 techknowlogick
						techknowlogick
					
				
			
			
				
	
			
			
			 techknowlogick
						techknowlogick
					
				
			
						parent
						
							60636cd7d8
						
					
				
				
					commit
					57e3400f0f
				
			
							
								
								
									
										153
									
								
								modules/interact/issue_edit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								modules/interact/issue_edit.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| // Copyright 2025 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package interact | ||||
|  | ||||
| import ( | ||||
| 	"slices" | ||||
|  | ||||
| 	"code.gitea.io/tea/modules/config" | ||||
| 	"code.gitea.io/tea/modules/context" | ||||
| 	"code.gitea.io/tea/modules/task" | ||||
|  | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| ) | ||||
|  | ||||
| // EditIssue interactively edits an issue | ||||
| func EditIssue(ctx context.TeaContext, index int64) (*task.EditIssueOption, error) { | ||||
| 	var opts = task.EditIssueOption{} | ||||
| 	var err error | ||||
|  | ||||
| 	ctx.Owner, ctx.Repo, err = promptRepoSlug(ctx.Owner, ctx.Repo) | ||||
| 	if err != nil { | ||||
| 		return &opts, err | ||||
| 	} | ||||
|  | ||||
| 	c := ctx.Login.Client() | ||||
| 	i, _, err := c.GetIssue(ctx.Owner, ctx.Repo, index) | ||||
| 	if err != nil { | ||||
| 		return &opts, err | ||||
| 	} | ||||
|  | ||||
| 	opts = task.EditIssueOption{ | ||||
| 		Index:    index, | ||||
| 		Title:    &i.Title, | ||||
| 		Body:     &i.Body, | ||||
| 		Deadline: i.Deadline, | ||||
| 	} | ||||
|  | ||||
| 	if len(i.Assignees) != 0 { | ||||
| 		for _, a := range i.Assignees { | ||||
| 			opts.AddAssignees = append(opts.AddAssignees, a.UserName) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(i.Labels) != 0 { | ||||
| 		for _, l := range i.Labels { | ||||
| 			opts.AddLabels = append(opts.AddLabels, l.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if i.Milestone != nil { | ||||
| 		opts.Milestone = &i.Milestone.Title | ||||
| 	} | ||||
|  | ||||
| 	if err := promptIssueEditProperties(&ctx, &opts); err != nil { | ||||
| 		return &opts, err | ||||
| 	} | ||||
|  | ||||
| 	return &opts, err | ||||
| } | ||||
|  | ||||
| func promptIssueEditProperties(ctx *context.TeaContext, o *task.EditIssueOption) error { | ||||
| 	var milestoneName string | ||||
| 	var labelsSelected []string | ||||
| 	var err error | ||||
|  | ||||
| 	selectableChan := make(chan (issueSelectables), 1) | ||||
| 	go fetchIssueSelectables(ctx.Login, ctx.Owner, ctx.Repo, selectableChan) | ||||
|  | ||||
| 	// title | ||||
| 	promptOpts := survey.WithValidator(survey.Required) | ||||
| 	promptI := &survey.Input{Message: "Issue title:", Default: *o.Title} | ||||
| 	if err = survey.AskOne(promptI, o.Title, promptOpts); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// description | ||||
| 	promptD := NewMultiline(Multiline{ | ||||
| 		Message:             "Issue description:", | ||||
| 		Default:             *o.Body, | ||||
| 		Syntax:              "md", | ||||
| 		UseEditor:           config.GetPreferences().Editor, | ||||
| 		EditorAppendDefault: true, | ||||
| 		EditorHideDefault:   true, | ||||
| 	}) | ||||
|  | ||||
| 	if err = survey.AskOne(promptD, o.Body); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// wait until selectables are fetched | ||||
| 	selectables := <-selectableChan | ||||
| 	if selectables.Err != nil { | ||||
| 		return selectables.Err | ||||
| 	} | ||||
|  | ||||
| 	// skip remaining props if we don't have permission to set them | ||||
| 	if !selectables.Repo.Permissions.Push { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	currAssignees := o.AddAssignees | ||||
| 	newAssignees := selectables.Assignees | ||||
|  | ||||
| 	for _, c := range currAssignees { | ||||
| 		if i := slices.Index(newAssignees, c); i != -1 { | ||||
| 			newAssignees = slices.Delete(newAssignees, i, i+1) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// assignees | ||||
| 	if o.AddAssignees, err = promptMultiSelect("Add Assignees:", newAssignees, "[other]"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// milestone | ||||
| 	if len(selectables.MilestoneList) != 0 { | ||||
| 		var defaultMS string | ||||
| 		if o.Milestone != nil { | ||||
| 			defaultMS = *o.Milestone | ||||
| 		} | ||||
| 		if milestoneName, err = promptSelect("Milestone:", selectables.MilestoneList, "", "[none]", defaultMS); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		o.Milestone = &milestoneName | ||||
| 	} | ||||
|  | ||||
| 	// labels | ||||
| 	if len(selectables.LabelList) != 0 { | ||||
| 		promptL := &survey.MultiSelect{Message: "Labels:", Options: selectables.LabelList, VimMode: true, Default: o.AddLabels} | ||||
| 		if err := survey.AskOne(promptL, &labelsSelected); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// removed labels | ||||
| 		for _, l := range o.AddLabels { | ||||
| 			if !slices.Contains(labelsSelected, l) { | ||||
| 				o.RemoveLabels = append(o.RemoveLabels, l) | ||||
| 			} | ||||
| 		} | ||||
| 		// added labels | ||||
| 		o.AddLabels = make([]string, len(labelsSelected)) | ||||
| 		for i, l := range labelsSelected { | ||||
| 			o.AddLabels[i] = l | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// deadline | ||||
| 	if o.Deadline, err = promptDatetime("Due date:"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user