Expand issue create metadata flags to pr create
- Includes support `pr create --reviewer <login>` - Hide "Preview in browser" menu option when any metadata are set
This commit is contained in:
parent
de59f6a1bf
commit
d3a89b8744
5 changed files with 225 additions and 75 deletions
|
|
@ -20,6 +20,7 @@ type PullRequestAndTotalCount struct {
|
|||
}
|
||||
|
||||
type PullRequest struct {
|
||||
ID string
|
||||
Number int
|
||||
Title string
|
||||
State string
|
||||
|
|
@ -558,6 +559,7 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter
|
|||
mutation CreatePullRequest($input: CreatePullRequestInput!) {
|
||||
createPullRequest(input: $input) {
|
||||
pullRequest {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
|
|
@ -567,7 +569,10 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter
|
|||
"repositoryId": repo.ID,
|
||||
}
|
||||
for key, val := range params {
|
||||
inputParams[key] = val
|
||||
switch key {
|
||||
case "title", "body", "draft", "baseRefName", "headRefName":
|
||||
inputParams[key] = val
|
||||
}
|
||||
}
|
||||
variables := map[string]interface{}{
|
||||
"input": inputParams,
|
||||
|
|
@ -583,8 +588,65 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr := &result.CreatePullRequest.PullRequest
|
||||
|
||||
return &result.CreatePullRequest.PullRequest, nil
|
||||
// metadata parameters aren't currently available in `createPullRequest`,
|
||||
// but they are in `updatePullRequest`
|
||||
updateParams := make(map[string]interface{})
|
||||
for key, val := range params {
|
||||
switch key {
|
||||
case "assigneeIds", "labelIds", "projectIds", "milestoneId":
|
||||
if !isBlank(val) {
|
||||
updateParams[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(updateParams) > 0 {
|
||||
updateQuery := `
|
||||
mutation UpdatePullRequest($input: CreatePullRequestInput!) {
|
||||
updatePullRequest(input: $input)
|
||||
}`
|
||||
updateParams["pullRequestId"] = pr.ID
|
||||
variables := map[string]interface{}{
|
||||
"input": inputParams,
|
||||
}
|
||||
err := client.GraphQL(updateQuery, variables, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// reviewers are requested in yet another additional mutation
|
||||
if ids, ok := params["reviewerIds"]; ok && !isBlank(ids) {
|
||||
reviewQuery := `
|
||||
mutation RequestReviews($input: CreatePullRequestInput!) {
|
||||
requestReviews(input: $input)
|
||||
}`
|
||||
reviewParams := map[string]interface{}{
|
||||
"pullRequestId": pr.ID,
|
||||
"userIds": ids,
|
||||
}
|
||||
variables := map[string]interface{}{
|
||||
"input": reviewParams,
|
||||
}
|
||||
err := client.GraphQL(reviewQuery, variables, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func isBlank(v interface{}) bool {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
return vv != ""
|
||||
case []string:
|
||||
return vv != nil && len(vv) > 0
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func PullRequestList(client *Client, vars map[string]interface{}, limit int) (*PullRequestAndTotalCount, error) {
|
||||
|
|
|
|||
|
|
@ -415,6 +415,69 @@ type RepoMetadataResult struct {
|
|||
} `graphql:"milestones(first: 100, states: [OPEN])"`
|
||||
}
|
||||
|
||||
func (m *RepoMetadataResult) AssigneesToIDs(names []string) ([]string, error) {
|
||||
var ids []string
|
||||
for _, assigneeLogin := range names {
|
||||
found := false
|
||||
for _, u := range m.AssignableUsers.Nodes {
|
||||
if strings.EqualFold(assigneeLogin, u.Login) {
|
||||
ids = append(ids, u.ID)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("'%s' not found", assigneeLogin)
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (m *RepoMetadataResult) LabelsToIDs(names []string) ([]string, error) {
|
||||
var ids []string
|
||||
for _, labelName := range names {
|
||||
found := false
|
||||
for _, l := range m.Labels.Nodes {
|
||||
if strings.EqualFold(labelName, l.Name) {
|
||||
ids = append(ids, l.ID)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("'%s' not found", labelName)
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (m *RepoMetadataResult) ProjectsToIDs(names []string) ([]string, error) {
|
||||
var ids []string
|
||||
for _, projectName := range names {
|
||||
found := false
|
||||
for _, p := range m.Projects.Nodes {
|
||||
if strings.EqualFold(projectName, p.Name) {
|
||||
ids = append(ids, p.ID)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("'%s' not found", projectName)
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (m *RepoMetadataResult) MilestoneToID(title string) (string, error) {
|
||||
for _, m := range m.Milestones.Nodes {
|
||||
if strings.EqualFold(title, m.Title) {
|
||||
return m.ID, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("not found")
|
||||
}
|
||||
|
||||
// RepoMetadata pre-fetches the metadata for attaching to issues and pull requests
|
||||
func RepoMetadata(client *Client, repo ghrepo.Interface) (*RepoMetadataResult, error) {
|
||||
var query struct {
|
||||
|
|
|
|||
100
command/issue.go
100
command/issue.go
|
|
@ -371,6 +371,8 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("could not parse milestone: %w", err)
|
||||
}
|
||||
|
||||
hasMetadata := len(assignees) > 0 || len(labelNames) > 0 || len(projectNames) > 0 || milestoneTitle != ""
|
||||
|
||||
if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb {
|
||||
// TODO: move URL generation into GitHubRepository
|
||||
openURL := fmt.Sprintf("https://github.com/%s/issues/new", ghrepo.FullName(baseRepo))
|
||||
|
|
@ -407,7 +409,7 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
interactive := title == "" || body == ""
|
||||
|
||||
if interactive {
|
||||
tb, err := titleBodySurvey(cmd, title, body, defaults{}, templateFiles)
|
||||
tb, err := titleBodySurvey(cmd, title, body, defaults{}, templateFiles, !hasMetadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not collect title and/or body: %w", err)
|
||||
}
|
||||
|
|
@ -444,72 +446,14 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
"body": body,
|
||||
}
|
||||
|
||||
if len(assignees) > 0 || len(labelNames) > 0 || len(projectNames) > 0 || milestoneTitle != "" {
|
||||
if hasMetadata {
|
||||
metadata, err := api.RepoMetadata(apiClient, baseRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var assigneeIDs []string
|
||||
for _, assigneeLogin := range assignees {
|
||||
var found bool
|
||||
for _, u := range metadata.AssignableUsers.Nodes {
|
||||
if strings.EqualFold(assigneeLogin, u.Login) {
|
||||
assigneeIDs = append(assigneeIDs, u.ID)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("could not find user to assign: '%s'", assigneeLogin)
|
||||
}
|
||||
}
|
||||
params["assigneeIds"] = assigneeIDs
|
||||
|
||||
var labelIDs []string
|
||||
for _, labelName := range labelNames {
|
||||
var found bool
|
||||
for _, l := range metadata.Labels.Nodes {
|
||||
if strings.EqualFold(labelName, l.Name) {
|
||||
labelIDs = append(labelIDs, l.ID)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("could not find label '%s'", labelName)
|
||||
}
|
||||
}
|
||||
params["labelIds"] = labelIDs
|
||||
|
||||
var projectIDs []string
|
||||
for _, projectName := range projectNames {
|
||||
var found bool
|
||||
for _, p := range metadata.Projects.Nodes {
|
||||
if strings.EqualFold(projectName, p.Name) {
|
||||
projectIDs = append(projectIDs, p.ID)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("could not find project '%s'", projectName)
|
||||
}
|
||||
}
|
||||
params["projectIds"] = projectIDs
|
||||
|
||||
if milestoneTitle != "" {
|
||||
var milestoneID string
|
||||
for _, m := range metadata.Milestones.Nodes {
|
||||
if strings.EqualFold(milestoneTitle, m.Title) {
|
||||
milestoneID = m.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
if milestoneID == "" {
|
||||
return fmt.Errorf("could not find milestone '%s'", milestoneTitle)
|
||||
}
|
||||
params["milestoneId"] = milestoneID
|
||||
err = addMetadataToIssueParams(params, metadata, assignees, labelNames, projectNames, milestoneTitle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -526,6 +470,36 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func addMetadataToIssueParams(params map[string]interface{}, metadata *api.RepoMetadataResult, assignees, labelNames, projectNames []string, milestoneTitle string) error {
|
||||
assigneeIDs, err := metadata.AssigneesToIDs(assignees)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not assign user: %w", err)
|
||||
}
|
||||
params["assigneeIds"] = assigneeIDs
|
||||
|
||||
labelIDs, err := metadata.LabelsToIDs(labelNames)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add label: %w", err)
|
||||
}
|
||||
params["labelIds"] = labelIDs
|
||||
|
||||
projectIDs, err := metadata.ProjectsToIDs(projectNames)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add to project: %w", err)
|
||||
}
|
||||
params["projectIds"] = projectIDs
|
||||
|
||||
if milestoneTitle != "" {
|
||||
milestoneID, err := metadata.MilestoneToID(milestoneTitle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add to milestone '%s': %w", milestoneTitle, err)
|
||||
}
|
||||
params["milestoneId"] = milestoneID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printIssues(w io.Writer, prefix string, totalCount int, issues []api.Issue) {
|
||||
table := utils.NewTablePrinter(w)
|
||||
for _, issue := range issues {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,29 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("could not parse body: %w", err)
|
||||
}
|
||||
|
||||
reviewers, err := cmd.Flags().GetStringSlice("reviewer")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse reviewers: %w", err)
|
||||
}
|
||||
assignees, err := cmd.Flags().GetStringSlice("assignee")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse assignees: %w", err)
|
||||
}
|
||||
labelNames, err := cmd.Flags().GetStringSlice("label")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse labels: %w", err)
|
||||
}
|
||||
projectNames, err := cmd.Flags().GetStringSlice("project")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse projects: %w", err)
|
||||
}
|
||||
milestoneTitle, err := cmd.Flags().GetString("milestone")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse milestone: %w", err)
|
||||
}
|
||||
|
||||
hasMetadata := len(reviewers) > 0 || len(assignees) > 0 || len(labelNames) > 0 || len(projectNames) > 0 || milestoneTitle != ""
|
||||
|
||||
baseTrackingBranch := baseBranch
|
||||
if baseRemote, err := remotes.FindByRepo(baseRepo.RepoOwner(), baseRepo.RepoName()); err == nil {
|
||||
baseTrackingBranch = fmt.Sprintf("%s/%s", baseRemote.Name, baseBranch)
|
||||
|
|
@ -187,7 +210,7 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
templateFiles = githubtemplate.Find(rootDir, "PULL_REQUEST_TEMPLATE")
|
||||
}
|
||||
|
||||
tb, err := titleBodySurvey(cmd, title, body, defs, templateFiles)
|
||||
tb, err := titleBodySurvey(cmd, title, body, defs, templateFiles, !hasMetadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not collect title and/or body: %w", err)
|
||||
}
|
||||
|
|
@ -293,6 +316,22 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
"headRefName": headBranchLabel,
|
||||
}
|
||||
|
||||
if hasMetadata {
|
||||
metadata, err := api.RepoMetadata(client, baseRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = addMetadataToIssueParams(params, metadata, assignees, labelNames, projectNames, milestoneTitle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reviewerIDs, err := metadata.AssigneesToIDs(reviewers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not request reviewer: %w", err)
|
||||
}
|
||||
params["reviewerIds"] = reviewerIDs
|
||||
}
|
||||
|
||||
pr, err := api.CreatePullRequest(client, baseRepo, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create pull request: %w", err)
|
||||
|
|
@ -385,4 +424,10 @@ func init() {
|
|||
"The branch into which you want your code merged")
|
||||
prCreateCmd.Flags().BoolP("web", "w", false, "Open the web browser to create a pull request")
|
||||
prCreateCmd.Flags().BoolP("fill", "f", false, "Do not prompt for title/body and just use commit info")
|
||||
|
||||
prCreateCmd.Flags().StringSliceP("reviewer", "r", nil, "Request a review from someone by their `login`")
|
||||
prCreateCmd.Flags().StringSliceP("assignee", "a", nil, "Assign a person by their `login`")
|
||||
prCreateCmd.Flags().StringSliceP("label", "l", nil, "Add a label by `name`")
|
||||
prCreateCmd.Flags().StringSliceP("project", "p", nil, "Add the pull request to a project by `name`")
|
||||
prCreateCmd.Flags().StringP("milestone", "m", "", "Add the pull request to a milestone by `name`")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,13 @@ var SurveyAsk = func(qs []*survey.Question, response interface{}, opts ...survey
|
|||
return survey.Ask(qs, response, opts...)
|
||||
}
|
||||
|
||||
func confirmSubmission() (Action, error) {
|
||||
func confirmSubmission(allowPreview bool) (Action, error) {
|
||||
options := []string{}
|
||||
if allowPreview {
|
||||
options = append(options, "Preview in browser")
|
||||
}
|
||||
options = append(options, "Submit", "Cancel")
|
||||
|
||||
confirmAnswers := struct {
|
||||
Confirmation int
|
||||
}{}
|
||||
|
|
@ -36,11 +42,7 @@ func confirmSubmission() (Action, error) {
|
|||
Name: "confirmation",
|
||||
Prompt: &survey.Select{
|
||||
Message: "What's next?",
|
||||
Options: []string{
|
||||
"Preview in browser",
|
||||
"Submit",
|
||||
"Cancel",
|
||||
},
|
||||
Options: options,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -50,7 +52,11 @@ func confirmSubmission() (Action, error) {
|
|||
return -1, fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
|
||||
return Action(confirmAnswers.Confirmation), nil
|
||||
choice := confirmAnswers.Confirmation
|
||||
if !allowPreview {
|
||||
choice++
|
||||
}
|
||||
return Action(choice), nil
|
||||
}
|
||||
|
||||
func selectTemplate(templatePaths []string) (string, error) {
|
||||
|
|
@ -81,7 +87,7 @@ func selectTemplate(templatePaths []string) (string, error) {
|
|||
return string(templateContents), nil
|
||||
}
|
||||
|
||||
func titleBodySurvey(cmd *cobra.Command, providedTitle, providedBody string, defs defaults, templatePaths []string) (*titleBody, error) {
|
||||
func titleBodySurvey(cmd *cobra.Command, providedTitle, providedBody string, defs defaults, templatePaths []string, allowPreview bool) (*titleBody, error) {
|
||||
var inProgress titleBody
|
||||
inProgress.Title = defs.Title
|
||||
templateContents := ""
|
||||
|
|
@ -136,7 +142,7 @@ func titleBodySurvey(cmd *cobra.Command, providedTitle, providedBody string, def
|
|||
inProgress.Body = templateContents
|
||||
}
|
||||
|
||||
confirmA, err := confirmSubmission()
|
||||
confirmA, err := confirmSubmission(allowPreview)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to confirm: %w", err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue