Merge pull request #908 from cli/issue-metadata-resolve-ids
Faster resolve assignees, reviewers, labels on create via flags
This commit is contained in:
commit
9acaca90e2
8 changed files with 474 additions and 148 deletions
|
|
@ -580,6 +580,108 @@ func RepoMetadata(client *Client, repo ghrepo.Interface, input RepoMetadataInput
|
|||
return &result, err
|
||||
}
|
||||
|
||||
type RepoResolveInput struct {
|
||||
Assignees []string
|
||||
Reviewers []string
|
||||
Labels []string
|
||||
Projects []string
|
||||
Milestones []string
|
||||
}
|
||||
|
||||
// RepoResolveMetadataIDs looks up GraphQL node IDs in bulk
|
||||
func RepoResolveMetadataIDs(client *Client, repo ghrepo.Interface, input RepoResolveInput) (*RepoMetadataResult, error) {
|
||||
users := input.Assignees
|
||||
hasUser := func(target string) bool {
|
||||
for _, u := range users {
|
||||
if strings.EqualFold(u, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var teams []string
|
||||
for _, r := range input.Reviewers {
|
||||
if i := strings.IndexRune(r, '/'); i > -1 {
|
||||
teams = append(teams, r[i+1:])
|
||||
} else if !hasUser(r) {
|
||||
users = append(users, r)
|
||||
}
|
||||
}
|
||||
|
||||
// there is no way to look up projects nor milestones by name, so preload them all
|
||||
mi := RepoMetadataInput{
|
||||
Projects: len(input.Projects) > 0,
|
||||
Milestones: len(input.Milestones) > 0,
|
||||
}
|
||||
result, err := RepoMetadata(client, repo, mi)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(users) == 0 && len(teams) == 0 && len(input.Labels) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
query := &bytes.Buffer{}
|
||||
fmt.Fprint(query, "{\n")
|
||||
for i, u := range users {
|
||||
fmt.Fprintf(query, "u%03d: user(login:%q){id,login}\n", i, u)
|
||||
}
|
||||
if len(input.Labels) > 0 {
|
||||
fmt.Fprintf(query, "repository(owner:%q,name:%q){\n", repo.RepoOwner(), repo.RepoName())
|
||||
for i, l := range input.Labels {
|
||||
fmt.Fprintf(query, "l%03d: label(name:%q){id,name}\n", i, l)
|
||||
}
|
||||
fmt.Fprint(query, "}\n")
|
||||
}
|
||||
if len(teams) > 0 {
|
||||
fmt.Fprintf(query, "organization(login:%q){\n", repo.RepoOwner())
|
||||
for i, t := range teams {
|
||||
fmt.Fprintf(query, "t%03d: team(slug:%q){id,slug}\n", i, t)
|
||||
}
|
||||
fmt.Fprint(query, "}\n")
|
||||
}
|
||||
fmt.Fprint(query, "}\n")
|
||||
|
||||
response := make(map[string]json.RawMessage)
|
||||
err = client.GraphQL(query.String(), nil, &response)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
for key, v := range response {
|
||||
switch key {
|
||||
case "repository":
|
||||
repoResponse := make(map[string]RepoLabel)
|
||||
err := json.Unmarshal(v, &repoResponse)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
for _, l := range repoResponse {
|
||||
result.Labels = append(result.Labels, l)
|
||||
}
|
||||
case "organization":
|
||||
orgResponse := make(map[string]OrgTeam)
|
||||
err := json.Unmarshal(v, &orgResponse)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
for _, t := range orgResponse {
|
||||
result.Teams = append(result.Teams, t)
|
||||
}
|
||||
default:
|
||||
user := RepoAssignee{}
|
||||
err := json.Unmarshal(v, &user)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.AssignableUsers = append(result.AssignableUsers, user)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type RepoProject struct {
|
||||
ID string
|
||||
Name string
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
)
|
||||
|
||||
|
|
@ -45,3 +46,229 @@ func Test_RepoCreate(t *testing.T) {
|
|||
t.Errorf("expected homepageUrl to be %q, got %q", "http://example.com", homepage)
|
||||
}
|
||||
}
|
||||
func Test_RepoMetadata(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
client := NewClient(ReplaceTripper(http))
|
||||
|
||||
repo := ghrepo.FromFullName("OWNER/REPO")
|
||||
input := RepoMetadataInput{
|
||||
Assignees: true,
|
||||
Reviewers: true,
|
||||
Labels: true,
|
||||
Projects: true,
|
||||
Milestones: true,
|
||||
}
|
||||
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bassignableUsers\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "assignableUsers": {
|
||||
"nodes": [
|
||||
{ "login": "hubot", "id": "HUBOTID" },
|
||||
{ "login": "MonaLisa", "id": "MONAID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\blabels\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "labels": {
|
||||
"nodes": [
|
||||
{ "name": "feature", "id": "FEATUREID" },
|
||||
{ "name": "TODO", "id": "TODOID" },
|
||||
{ "name": "bug", "id": "BUGID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bmilestones\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "milestones": {
|
||||
"nodes": [
|
||||
{ "title": "GA", "id": "GAID" },
|
||||
{ "title": "Big One.oh", "id": "BIGONEID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\brepository\(.+\bprojects\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "projects": {
|
||||
"nodes": [
|
||||
{ "name": "Cleanup", "id": "CLEANUPID" },
|
||||
{ "name": "Roadmap", "id": "ROADMAPID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\borganization\(.+\bprojects\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "organization": { "projects": {
|
||||
"nodes": [
|
||||
{ "name": "Triage", "id": "TRIAGEID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\borganization\(.+\bteams\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "organization": { "teams": {
|
||||
"nodes": [
|
||||
{ "slug": "owners", "id": "OWNERSID" },
|
||||
{ "slug": "Core", "id": "COREID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
|
||||
result, err := RepoMetadata(client, repo, input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedMemberIDs := []string{"MONAID", "HUBOTID"}
|
||||
memberIDs, err := result.MembersToIDs([]string{"monalisa", "hubot"})
|
||||
if err != nil {
|
||||
t.Errorf("error resolving members: %v", err)
|
||||
}
|
||||
if !sliceEqual(memberIDs, expectedMemberIDs) {
|
||||
t.Errorf("expected members %v, got %v", expectedMemberIDs, memberIDs)
|
||||
}
|
||||
|
||||
expectedTeamIDs := []string{"COREID", "OWNERSID"}
|
||||
teamIDs, err := result.TeamsToIDs([]string{"OWNER/core", "/owners"})
|
||||
if err != nil {
|
||||
t.Errorf("error resolving teams: %v", err)
|
||||
}
|
||||
if !sliceEqual(teamIDs, expectedTeamIDs) {
|
||||
t.Errorf("expected teams %v, got %v", expectedTeamIDs, teamIDs)
|
||||
}
|
||||
|
||||
expectedLabelIDs := []string{"BUGID", "TODOID"}
|
||||
labelIDs, err := result.LabelsToIDs([]string{"bug", "todo"})
|
||||
if err != nil {
|
||||
t.Errorf("error resolving labels: %v", err)
|
||||
}
|
||||
if !sliceEqual(labelIDs, expectedLabelIDs) {
|
||||
t.Errorf("expected labels %v, got %v", expectedLabelIDs, labelIDs)
|
||||
}
|
||||
|
||||
expectedProjectIDs := []string{"TRIAGEID", "ROADMAPID"}
|
||||
projectIDs, err := result.ProjectsToIDs([]string{"triage", "roadmap"})
|
||||
if err != nil {
|
||||
t.Errorf("error resolving projects: %v", err)
|
||||
}
|
||||
if !sliceEqual(projectIDs, expectedProjectIDs) {
|
||||
t.Errorf("expected projects %v, got %v", expectedProjectIDs, projectIDs)
|
||||
}
|
||||
|
||||
expectedMilestoneID := "BIGONEID"
|
||||
milestoneID, err := result.MilestoneToID("big one.oh")
|
||||
if err != nil {
|
||||
t.Errorf("error resolving milestone: %v", err)
|
||||
}
|
||||
if milestoneID != expectedMilestoneID {
|
||||
t.Errorf("expected milestone %v, got %v", expectedMilestoneID, milestoneID)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RepoResolveMetadataIDs(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
client := NewClient(ReplaceTripper(http))
|
||||
|
||||
repo := ghrepo.FromFullName("OWNER/REPO")
|
||||
input := RepoResolveInput{
|
||||
Assignees: []string{"monalisa", "hubot"},
|
||||
Reviewers: []string{"monalisa", "octocat", "OWNER/core", "/robots"},
|
||||
Labels: []string{"bug", "help wanted"},
|
||||
}
|
||||
|
||||
expectedQuery := `{
|
||||
u000: user(login:"monalisa"){id,login}
|
||||
u001: user(login:"hubot"){id,login}
|
||||
u002: user(login:"octocat"){id,login}
|
||||
repository(owner:"OWNER",name:"REPO"){
|
||||
l000: label(name:"bug"){id,name}
|
||||
l001: label(name:"help wanted"){id,name}
|
||||
}
|
||||
organization(login:"OWNER"){
|
||||
t000: team(slug:"core"){id,slug}
|
||||
t001: team(slug:"robots"){id,slug}
|
||||
}
|
||||
}
|
||||
`
|
||||
responseJSON := `
|
||||
{ "data": {
|
||||
"u000": { "login": "MonaLisa", "id": "MONAID" },
|
||||
"u001": { "login": "hubot", "id": "HUBOTID" },
|
||||
"u002": { "login": "octocat", "id": "OCTOID" },
|
||||
"repository": {
|
||||
"l000": { "name": "bug", "id": "BUGID" },
|
||||
"l001": { "name": "Help Wanted", "id": "HELPID" }
|
||||
},
|
||||
"organization": {
|
||||
"t000": { "slug": "core", "id": "COREID" },
|
||||
"t001": { "slug": "Robots", "id": "ROBOTID" }
|
||||
}
|
||||
} }
|
||||
`
|
||||
|
||||
http.Register(
|
||||
httpmock.MatchAny,
|
||||
httpmock.GraphQLQuery(responseJSON, func(q string, _ map[string]interface{}) {
|
||||
if q != expectedQuery {
|
||||
t.Errorf("expected query %q, got %q", expectedQuery, q)
|
||||
}
|
||||
}))
|
||||
|
||||
result, err := RepoResolveMetadataIDs(client, repo, input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedMemberIDs := []string{"MONAID", "HUBOTID", "OCTOID"}
|
||||
memberIDs, err := result.MembersToIDs([]string{"monalisa", "hubot", "octocat"})
|
||||
if err != nil {
|
||||
t.Errorf("error resolving members: %v", err)
|
||||
}
|
||||
if !sliceEqual(memberIDs, expectedMemberIDs) {
|
||||
t.Errorf("expected members %v, got %v", expectedMemberIDs, memberIDs)
|
||||
}
|
||||
|
||||
expectedTeamIDs := []string{"COREID", "ROBOTID"}
|
||||
teamIDs, err := result.TeamsToIDs([]string{"/core", "/robots"})
|
||||
if err != nil {
|
||||
t.Errorf("error resolving teams: %v", err)
|
||||
}
|
||||
if !sliceEqual(teamIDs, expectedTeamIDs) {
|
||||
t.Errorf("expected members %v, got %v", expectedTeamIDs, teamIDs)
|
||||
}
|
||||
|
||||
expectedLabelIDs := []string{"BUGID", "HELPID"}
|
||||
labelIDs, err := result.LabelsToIDs([]string{"bug", "help wanted"})
|
||||
if err != nil {
|
||||
t.Errorf("error resolving labels: %v", err)
|
||||
}
|
||||
if !sliceEqual(labelIDs, expectedLabelIDs) {
|
||||
t.Errorf("expected members %v, got %v", expectedLabelIDs, labelIDs)
|
||||
}
|
||||
}
|
||||
|
||||
func sliceEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -381,9 +381,11 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("could not parse projects: %w", err)
|
||||
}
|
||||
milestoneTitle, err := cmd.Flags().GetString("milestone")
|
||||
if err != nil {
|
||||
var milestoneTitles []string
|
||||
if milestoneTitle, err := cmd.Flags().GetString("milestone"); err != nil {
|
||||
return fmt.Errorf("could not parse milestone: %w", err)
|
||||
} else if milestoneTitle != "" {
|
||||
milestoneTitles = append(milestoneTitles, milestoneTitle)
|
||||
}
|
||||
|
||||
if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb {
|
||||
|
|
@ -419,10 +421,10 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
|
||||
action := SubmitAction
|
||||
tb := issueMetadataState{
|
||||
Assignees: assignees,
|
||||
Labels: labelNames,
|
||||
Projects: projectNames,
|
||||
Milestone: milestoneTitle,
|
||||
Assignees: assignees,
|
||||
Labels: labelNames,
|
||||
Projects: projectNames,
|
||||
Milestones: milestoneTitles,
|
||||
}
|
||||
|
||||
interactive := !(cmd.Flags().Changed("title") && cmd.Flags().Changed("body"))
|
||||
|
|
@ -469,26 +471,9 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
"body": body,
|
||||
}
|
||||
|
||||
if tb.HasMetadata() {
|
||||
if tb.MetadataResult == nil {
|
||||
metadataInput := api.RepoMetadataInput{
|
||||
Assignees: len(tb.Assignees) > 0,
|
||||
Labels: len(tb.Labels) > 0,
|
||||
Projects: len(tb.Projects) > 0,
|
||||
Milestones: tb.Milestone != "",
|
||||
}
|
||||
|
||||
// TODO: for non-interactive mode, only translate given objects to GraphQL IDs
|
||||
tb.MetadataResult, err = api.RepoMetadata(apiClient, baseRepo, metadataInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = addMetadataToIssueParams(params, tb.MetadataResult, tb.Assignees, tb.Labels, tb.Projects, tb.Milestone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = addMetadataToIssueParams(apiClient, baseRepo, params, &tb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newIssue, err := api.IssueCreate(apiClient, repo, params)
|
||||
|
|
@ -504,33 +489,79 @@ 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.MembersToIDs(assignees)
|
||||
func addMetadataToIssueParams(client *api.Client, baseRepo ghrepo.Interface, params map[string]interface{}, tb *issueMetadataState) error {
|
||||
if !tb.HasMetadata() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if tb.MetadataResult == nil {
|
||||
resolveInput := api.RepoResolveInput{
|
||||
Reviewers: tb.Reviewers,
|
||||
Assignees: tb.Assignees,
|
||||
Labels: tb.Labels,
|
||||
Projects: tb.Projects,
|
||||
Milestones: tb.Milestones,
|
||||
}
|
||||
|
||||
var err error
|
||||
tb.MetadataResult, err = api.RepoResolveMetadataIDs(client, baseRepo, resolveInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
assigneeIDs, err := tb.MetadataResult.MembersToIDs(tb.Assignees)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not assign user: %w", err)
|
||||
}
|
||||
params["assigneeIds"] = assigneeIDs
|
||||
|
||||
labelIDs, err := metadata.LabelsToIDs(labelNames)
|
||||
labelIDs, err := tb.MetadataResult.LabelsToIDs(tb.Labels)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add label: %w", err)
|
||||
}
|
||||
params["labelIds"] = labelIDs
|
||||
|
||||
projectIDs, err := metadata.ProjectsToIDs(projectNames)
|
||||
projectIDs, err := tb.MetadataResult.ProjectsToIDs(tb.Projects)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add to project: %w", err)
|
||||
}
|
||||
params["projectIds"] = projectIDs
|
||||
|
||||
if milestoneTitle != "" {
|
||||
milestoneID, err := metadata.MilestoneToID(milestoneTitle)
|
||||
if len(tb.Milestones) > 0 {
|
||||
milestoneID, err := tb.MetadataResult.MilestoneToID(tb.Milestones[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add to milestone '%s': %w", milestoneTitle, err)
|
||||
return fmt.Errorf("could not add to milestone '%s': %w", tb.Milestones[0], err)
|
||||
}
|
||||
params["milestoneId"] = milestoneID
|
||||
}
|
||||
|
||||
if len(tb.Reviewers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var userReviewers []string
|
||||
var teamReviewers []string
|
||||
for _, r := range tb.Reviewers {
|
||||
if strings.ContainsRune(r, '/') {
|
||||
teamReviewers = append(teamReviewers, r)
|
||||
} else {
|
||||
userReviewers = append(userReviewers, r)
|
||||
}
|
||||
}
|
||||
|
||||
userReviewerIDs, err := tb.MetadataResult.MembersToIDs(userReviewers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not request reviewer: %w", err)
|
||||
}
|
||||
params["userReviewerIds"] = userReviewerIDs
|
||||
|
||||
teamReviewerIDs, err := tb.MetadataResult.TeamsToIDs(teamReviewers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not request reviewer: %w", err)
|
||||
}
|
||||
params["teamReviewerIds"] = teamReviewerIDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -500,27 +500,15 @@ func TestIssueCreate_metadata(t *testing.T) {
|
|||
} } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bassignableUsers\(`),
|
||||
httpmock.GraphQL(`\bu000:`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "assignableUsers": {
|
||||
"nodes": [
|
||||
{ "login": "hubot", "id": "HUBOTID" },
|
||||
{ "login": "MonaLisa", "id": "MONAID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\blabels\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "labels": {
|
||||
"nodes": [
|
||||
{ "name": "feature", "id": "FEATUREID" },
|
||||
{ "name": "TODO", "id": "TODOID" },
|
||||
{ "name": "bug", "id": "BUGID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
{ "data": {
|
||||
"u000": { "login": "MonaLisa", "id": "MONAID" },
|
||||
"repository": {
|
||||
"l000": { "name": "bug", "id": "BUGID" },
|
||||
"l001": { "name": "TODO", "id": "TODOID" }
|
||||
}
|
||||
} }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bmilestones\(`),
|
||||
|
|
@ -568,6 +556,12 @@ func TestIssueCreate_metadata(t *testing.T) {
|
|||
eq(t, inputs["labelIds"], []interface{}{"BUGID", "TODOID"})
|
||||
eq(t, inputs["projectIds"], []interface{}{"ROADMAPID"})
|
||||
eq(t, inputs["milestoneId"], "BIGONEID")
|
||||
if v, ok := inputs["userIds"]; ok {
|
||||
t.Errorf("did not expect userIds: %v", v)
|
||||
}
|
||||
if v, ok := inputs["teamIds"]; ok {
|
||||
t.Errorf("did not expect teamIds: %v", v)
|
||||
}
|
||||
}))
|
||||
|
||||
output, err := RunCommand(`issue create -t TITLE -b BODY -a monalisa -l bug -l todo -p roadmap -m 'big one.oh'`)
|
||||
|
|
|
|||
|
|
@ -140,9 +140,11 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("could not parse projects: %w", err)
|
||||
}
|
||||
milestoneTitle, err := cmd.Flags().GetString("milestone")
|
||||
if err != nil {
|
||||
var milestoneTitles []string
|
||||
if milestoneTitle, err := cmd.Flags().GetString("milestone"); err != nil {
|
||||
return fmt.Errorf("could not parse milestone: %w", err)
|
||||
} else if milestoneTitle != "" {
|
||||
milestoneTitles = append(milestoneTitles, milestoneTitle)
|
||||
}
|
||||
|
||||
baseTrackingBranch := baseBranch
|
||||
|
|
@ -201,11 +203,11 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
tb := issueMetadataState{
|
||||
Reviewers: reviewers,
|
||||
Assignees: assignees,
|
||||
Labels: labelNames,
|
||||
Projects: projectNames,
|
||||
Milestone: milestoneTitle,
|
||||
Reviewers: reviewers,
|
||||
Assignees: assignees,
|
||||
Labels: labelNames,
|
||||
Projects: projectNames,
|
||||
Milestones: milestoneTitles,
|
||||
}
|
||||
|
||||
interactive := !(cmd.Flags().Changed("title") && cmd.Flags().Changed("body"))
|
||||
|
|
@ -323,49 +325,9 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
"headRefName": headBranchLabel,
|
||||
}
|
||||
|
||||
if tb.HasMetadata() {
|
||||
if tb.MetadataResult == nil {
|
||||
metadataInput := api.RepoMetadataInput{
|
||||
Reviewers: len(tb.Reviewers) > 0,
|
||||
Assignees: len(tb.Assignees) > 0,
|
||||
Labels: len(tb.Labels) > 0,
|
||||
Projects: len(tb.Projects) > 0,
|
||||
Milestones: tb.Milestone != "",
|
||||
}
|
||||
|
||||
// TODO: for non-interactive mode, only translate given objects to GraphQL IDs
|
||||
tb.MetadataResult, err = api.RepoMetadata(client, baseRepo, metadataInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = addMetadataToIssueParams(params, tb.MetadataResult, tb.Assignees, tb.Labels, tb.Projects, tb.Milestone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var userReviewers []string
|
||||
var teamReviewers []string
|
||||
for _, r := range tb.Reviewers {
|
||||
if strings.ContainsRune(r, '/') {
|
||||
teamReviewers = append(teamReviewers, r)
|
||||
} else {
|
||||
userReviewers = append(teamReviewers, r)
|
||||
}
|
||||
}
|
||||
|
||||
userReviewerIDs, err := tb.MetadataResult.MembersToIDs(userReviewers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not request reviewer: %w", err)
|
||||
}
|
||||
params["userReviewerIds"] = userReviewerIDs
|
||||
|
||||
teamReviewerIDs, err := tb.MetadataResult.TeamsToIDs(teamReviewers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not request reviewer: %w", err)
|
||||
}
|
||||
params["teamReviewerIds"] = teamReviewerIDs
|
||||
err = addMetadataToIssueParams(client, baseRepo, params, &tb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr, err := api.CreatePullRequest(client, baseRepo, params)
|
||||
|
|
|
|||
|
|
@ -87,27 +87,20 @@ func TestPRCreate_metadata(t *testing.T) {
|
|||
] } } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bassignableUsers\(`),
|
||||
httpmock.GraphQL(`\bteam\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "assignableUsers": {
|
||||
"nodes": [
|
||||
{ "login": "hubot", "id": "HUBOTID" },
|
||||
{ "login": "MonaLisa", "id": "MONAID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\blabels\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "repository": { "labels": {
|
||||
"nodes": [
|
||||
{ "name": "feature", "id": "FEATUREID" },
|
||||
{ "name": "TODO", "id": "TODOID" },
|
||||
{ "name": "bug", "id": "BUGID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
{ "data": {
|
||||
"u000": { "login": "MonaLisa", "id": "MONAID" },
|
||||
"u001": { "login": "hubot", "id": "HUBOTID" },
|
||||
"repository": {
|
||||
"l000": { "name": "bug", "id": "BUGID" },
|
||||
"l001": { "name": "TODO", "id": "TODOID" }
|
||||
},
|
||||
"organization": {
|
||||
"t000": { "slug": "core", "id": "COREID" },
|
||||
"t001": { "slug": "robots", "id": "ROBOTID" }
|
||||
}
|
||||
} }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bmilestones\(`),
|
||||
|
|
@ -139,17 +132,6 @@ func TestPRCreate_metadata(t *testing.T) {
|
|||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\borganization\(.+\bteams\(`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "organization": { "teams": {
|
||||
"nodes": [
|
||||
{ "slug": "owners", "id": "OWNERSID" },
|
||||
{ "slug": "Core", "id": "COREID" }
|
||||
],
|
||||
"pageInfo": { "hasNextPage": false }
|
||||
} } } }
|
||||
`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bcreatePullRequest\(`),
|
||||
httpmock.GraphQLMutation(`
|
||||
|
|
@ -160,6 +142,12 @@ func TestPRCreate_metadata(t *testing.T) {
|
|||
`, func(inputs map[string]interface{}) {
|
||||
eq(t, inputs["title"], "TITLE")
|
||||
eq(t, inputs["body"], "BODY")
|
||||
if v, ok := inputs["assigneeIds"]; ok {
|
||||
t.Errorf("did not expect assigneeIds: %v", v)
|
||||
}
|
||||
if v, ok := inputs["userIds"]; ok {
|
||||
t.Errorf("did not expect userIds: %v", v)
|
||||
}
|
||||
}))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`\bupdatePullRequest\(`),
|
||||
|
|
@ -182,8 +170,8 @@ func TestPRCreate_metadata(t *testing.T) {
|
|||
} } }
|
||||
`, func(inputs map[string]interface{}) {
|
||||
eq(t, inputs["pullRequestId"], "NEWPULLID")
|
||||
eq(t, inputs["userIds"], []interface{}{"HUBOTID"})
|
||||
eq(t, inputs["teamIds"], []interface{}{"COREID"})
|
||||
eq(t, inputs["userIds"], []interface{}{"HUBOTID", "MONAID"})
|
||||
eq(t, inputs["teamIds"], []interface{}{"COREID", "ROBOTID"})
|
||||
eq(t, inputs["union"], true)
|
||||
}))
|
||||
|
||||
|
|
@ -196,7 +184,7 @@ func TestPRCreate_metadata(t *testing.T) {
|
|||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||
cs.Stub("") // git push
|
||||
|
||||
output, err := RunCommand(`pr create -t TITLE -b BODY -a monalisa -l bug -l todo -p roadmap -m 'big one.oh' -r hubot -r /core`)
|
||||
output, err := RunCommand(`pr create -t TITLE -b BODY -a monalisa -l bug -l todo -p roadmap -m 'big one.oh' -r hubot -r monalisa -r /core -r /robots`)
|
||||
eq(t, err, nil)
|
||||
|
||||
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ type issueMetadataState struct {
|
|||
Title string
|
||||
Action Action
|
||||
|
||||
Metadata []string
|
||||
Reviewers []string
|
||||
Assignees []string
|
||||
Labels []string
|
||||
Projects []string
|
||||
Milestone string
|
||||
Metadata []string
|
||||
Reviewers []string
|
||||
Assignees []string
|
||||
Labels []string
|
||||
Projects []string
|
||||
Milestones []string
|
||||
|
||||
MetadataResult *api.RepoMetadataResult
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ func (tb *issueMetadataState) HasMetadata() bool {
|
|||
len(tb.Assignees) > 0 ||
|
||||
len(tb.Labels) > 0 ||
|
||||
len(tb.Projects) > 0 ||
|
||||
tb.Milestone != ""
|
||||
len(tb.Milestones) > 0
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -42,6 +42,8 @@ const (
|
|||
PreviewAction
|
||||
CancelAction
|
||||
MetadataAction
|
||||
|
||||
noMilestone = "(none)"
|
||||
)
|
||||
|
||||
var SurveyAsk = func(qs []*survey.Question, response interface{}, opts ...survey.AskOpt) error {
|
||||
|
|
@ -251,7 +253,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
|||
for _, l := range issueState.MetadataResult.Projects {
|
||||
projects = append(projects, l.Name)
|
||||
}
|
||||
milestones := []string{"(none)"}
|
||||
milestones := []string{noMilestone}
|
||||
for _, m := range issueState.MetadataResult.Milestones {
|
||||
milestones = append(milestones, m.Title)
|
||||
}
|
||||
|
|
@ -315,12 +317,16 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
|||
}
|
||||
if isChosen("Milestone") {
|
||||
if len(milestones) > 1 {
|
||||
var milestoneDefault interface{}
|
||||
if len(issueState.Milestones) > 0 {
|
||||
milestoneDefault = issueState.Milestones[0]
|
||||
}
|
||||
mqs = append(mqs, &survey.Question{
|
||||
Name: "milestone",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Milestone",
|
||||
Options: milestones,
|
||||
Default: issueState.Milestone,
|
||||
Default: milestoneDefault,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
|
|
@ -333,8 +339,8 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
|||
return fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
|
||||
if issueState.Milestone == "(none)" {
|
||||
issueState.Milestone = ""
|
||||
if len(issueState.Milestones) > 0 && issueState.Milestones[0] == noMilestone {
|
||||
issueState.Milestones = issueState.Milestones[0:0]
|
||||
}
|
||||
|
||||
allowPreview = !issueState.HasMetadata()
|
||||
|
|
|
|||
|
|
@ -88,6 +88,22 @@ func GraphQLMutation(body string, cb func(map[string]interface{})) Responder {
|
|||
}
|
||||
}
|
||||
|
||||
func GraphQLQuery(body string, cb func(string, map[string]interface{})) Responder {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
var bodyData struct {
|
||||
Query string
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
err := decodeJSONBody(req, &bodyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cb(bodyData.Query, bodyData.Variables)
|
||||
|
||||
return httpResponse(200, bytes.NewBufferString(body)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func httpResponse(status int, body io.Reader) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: status,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue