diff --git a/pkg/cmd/issue/edit/edit.go b/pkg/cmd/issue/edit/edit.go index 11921096a..c0cd0c89c 100644 --- a/pkg/cmd/issue/edit/edit.go +++ b/pkg/cmd/issue/edit/edit.go @@ -12,6 +12,7 @@ import ( fd "github.com/cli/cli/v2/internal/featuredetection" "github.com/cli/cli/v2/internal/gh" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/internal/text" shared "github.com/cli/cli/v2/pkg/cmd/issue/shared" prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared" @@ -248,6 +249,13 @@ func editRun(opts *EditOptions) error { // Fetch editable shared fields once for all issues. apiClient := api.NewClientFromHTTP(httpClient) + + // Wire up search function for assignees when ActorIsAssignable is available. + // Interactive mode only supports a single issue, so we use its ID for the search query. + if issueFeatures.ActorIsAssignable && opts.Interactive && len(issues) == 1 { + editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, baseRepo, issues[0].ID) + } + opts.IO.StartProgressIndicatorWithLabel("Fetching repository information") err = opts.FetchOptions(apiClient, baseRepo, &editable, opts.Detector.ProjectsV1()) opts.IO.StopProgressIndicator() @@ -351,3 +359,36 @@ func editRun(opts *EditOptions) error { return nil } + +func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, assignableID string) func(string) prompter.MultiSelectSearchResult { + return func(input string) prompter.MultiSelectSearchResult { + actors, availableAssigneesCount, err := api.SuggestedAssignableActors( + apiClient, + repo, + assignableID, + input) + if err != nil { + return prompter.MultiSelectSearchResult{Err: err} + } + + logins := make([]string, 0, len(actors)) + displayNames := make([]string, 0, len(actors)) + + for _, a := range actors { + if a.Login() == "" { + continue + } + logins = append(logins, a.Login()) + if a.DisplayName() != "" { + displayNames = append(displayNames, a.DisplayName()) + } else { + displayNames = append(displayNames, a.Login()) + } + } + return prompter.MultiSelectSearchResult{ + Keys: logins, + Labels: displayNames, + MoreResults: availableAssigneesCount, + } + } +} diff --git a/pkg/cmd/issue/edit/edit_test.go b/pkg/cmd/issue/edit/edit_test.go index c529f5ae2..d4dcce399 100644 --- a/pkg/cmd/issue/edit/edit_test.go +++ b/pkg/cmd/issue/edit/edit_test.go @@ -607,17 +607,6 @@ func Test_editRun(t *testing.T) { mockIssueGet(t, reg) mockIssueProjectItemsGet(t, reg) mockRepoMetadata(t, reg) - reg.Register( - httpmock.GraphQL(`query RepositoryAssignableActors\b`), - httpmock.StringResponse(` - { "data": { "repository": { "suggestedActors": { - "nodes": [ - { "login": "hubot", "id": "HUBOTID", "__typename": "Bot" }, - { "login": "monalisa", "id": "MONAID", "name": "Mona Display Name", "__typename": "User" } - ], - "pageInfo": { "hasNextPage": false } - } } } } - `)) mockIssueUpdate(t, reg) mockIssueUpdateActorAssignees(t, reg) mockIssueUpdateLabels(t, reg) @@ -649,17 +638,6 @@ func Test_editRun(t *testing.T) { }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { mockIsssueNumberGetWithAssignedActors(t, reg, 123) - reg.Register( - httpmock.GraphQL(`query RepositoryAssignableActors\b`), - httpmock.StringResponse(` - { "data": { "repository": { "suggestedActors": { - "nodes": [ - { "login": "hubot", "id": "HUBOTID", "__typename": "Bot" }, - { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name", "__typename": "User" } - ], - "pageInfo": { "hasNextPage": false } - } } } } - `)) mockIssueUpdate(t, reg) reg.Register( httpmock.GraphQL(`mutation ReplaceActorsForAssignable\b`), diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 3a6a9d083..ea0712491 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -303,7 +303,7 @@ func editRun(opts *EditOptions) error { // to legacy reviewer/assignee fetching. // TODO actorIsAssignableCleanup if issueFeatures.ActorIsAssignable { - editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) + editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, pr.ID) editable.ReviewerSearchFunc = reviewerSearchFunc(apiClient, repo, &editable, pr.ID) } @@ -348,7 +348,7 @@ func editRun(opts *EditOptions) error { // assigneeSearchFunc is intended to be an arg for MultiSelectWithSearch // to return potential assignee actors. -func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable *shared.Editable, assignableID string) func(string) prompter.MultiSelectSearchResult { +func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, assignableID string) func(string) prompter.MultiSelectSearchResult { searchFunc := func(input string) prompter.MultiSelectSearchResult { actors, availableAssigneesCount, err := api.SuggestedAssignableActors( apiClient, diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index a26c2a024..115d765f1 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -267,14 +267,16 @@ func (e Editable) MilestoneId() (*string, error) { // go routines. Fields that would be mutated will be copied. func (e *Editable) Clone() Editable { return Editable{ - Title: e.Title.clone(), - Body: e.Body.clone(), - Base: e.Base.clone(), - Reviewers: e.Reviewers.clone(), - Assignees: e.Assignees.clone(), - Labels: e.Labels.clone(), - Projects: e.Projects.clone(), - Milestone: e.Milestone.clone(), + Title: e.Title.clone(), + Body: e.Body.clone(), + Base: e.Base.clone(), + Reviewers: e.Reviewers.clone(), + ReviewerSearchFunc: e.ReviewerSearchFunc, + Assignees: e.Assignees.clone(), + AssigneeSearchFunc: e.AssigneeSearchFunc, + Labels: e.Labels.clone(), + Projects: e.Projects.clone(), + Milestone: e.Milestone.clone(), // Shallow copy since no mutation. Metadata: e.Metadata, }