X Tutup
Skip to content

Commit f0b60e3

Browse files
authored
Add non-local PR template support (cli#5097)
* Add non-local PR template support * Consolidate template support functions * Change back query name
1 parent ba8c41a commit f0b60e3

File tree

3 files changed

+151
-28
lines changed

3 files changed

+151
-28
lines changed

pkg/cmd/pr/create/create_test.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,16 @@ func TestPRCreate_nonLegacyTemplate(t *testing.T) {
598598

599599
http.StubRepoInfoResponse("OWNER", "REPO", "master")
600600
shared.RunCommandFinder("feature", nil, nil)
601+
http.Register(
602+
httpmock.GraphQL(`query PullRequestTemplates\b`),
603+
httpmock.StringResponse(`
604+
{ "data": { "repository": { "pullRequestTemplates": [
605+
{ "filename": "template1",
606+
"body": "this is a bug" },
607+
{ "filename": "template2",
608+
"body": "this is a enhancement" }
609+
] } } }`),
610+
)
601611
http.Register(
602612
httpmock.GraphQL(`mutation PullRequestCreate\b`),
603613
httpmock.GraphQLMutation(`
@@ -606,7 +616,7 @@ func TestPRCreate_nonLegacyTemplate(t *testing.T) {
606616
} } } }
607617
`, func(input map[string]interface{}) {
608618
assert.Equal(t, "my title", input["title"].(string))
609-
assert.Equal(t, "- commit 1\n- commit 0\n\nFixes a bug and Closes an issue", input["body"].(string))
619+
assert.Equal(t, "- commit 1\n- commit 0\n\nthis is a bug", input["body"].(string))
610620
}))
611621

612622
cs, cmdTeardown := run.Stub()
@@ -615,13 +625,11 @@ func TestPRCreate_nonLegacyTemplate(t *testing.T) {
615625
cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "1234567890,commit 0\n2345678901,commit 1")
616626
cs.Register(`git status --porcelain`, 0, "")
617627

618-
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
619-
as, teardown := prompt.InitAskStubber()
620-
defer teardown()
628+
as := prompt.NewAskStubber(t)
621629

622630
as.StubPrompt("Choose a template").
623-
AssertOptions([]string{"Bug fix", "Open a blank pull request"}).
624-
AnswerWith("Bug fix")
631+
AssertOptions([]string{"template1", "template2", "Open a blank pull request"}).
632+
AnswerWith("template1")
625633
as.StubPrompt("Body").AnswerDefault()
626634
as.StubPrompt("What's next?").
627635
AssertOptions([]string{"Submit", "Continue in browser", "Add metadata", "Cancel"}).

pkg/cmd/pr/shared/templates.go

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ type issueTemplate struct {
2323
Gbody string `graphql:"body"`
2424
}
2525

26+
type pullRequestTemplate struct {
27+
// I would have un-exported these fields, except `cli/shurcool-graphql` then cannot unmarshal them :/
28+
Gname string `graphql:"filename"`
29+
Gbody string `graphql:"body"`
30+
}
31+
2632
func (t *issueTemplate) Name() string {
2733
return t.Gname
2834
}
@@ -35,7 +41,19 @@ func (t *issueTemplate) Body() []byte {
3541
return []byte(t.Gbody)
3642
}
3743

38-
func listIssueTemplates(httpClient *http.Client, repo ghrepo.Interface) ([]issueTemplate, error) {
44+
func (t *pullRequestTemplate) Name() string {
45+
return t.Gname
46+
}
47+
48+
func (t *pullRequestTemplate) NameForSubmit() string {
49+
return ""
50+
}
51+
52+
func (t *pullRequestTemplate) Body() []byte {
53+
return []byte(t.Gbody)
54+
}
55+
56+
func listIssueTemplates(httpClient *http.Client, repo ghrepo.Interface) ([]Template, error) {
3957
var query struct {
4058
Repository struct {
4159
IssueTemplates []issueTemplate
@@ -54,10 +72,44 @@ func listIssueTemplates(httpClient *http.Client, repo ghrepo.Interface) ([]issue
5472
return nil, err
5573
}
5674

57-
return query.Repository.IssueTemplates, nil
75+
ts := query.Repository.IssueTemplates
76+
templates := make([]Template, len(ts))
77+
for i := range templates {
78+
templates[i] = &ts[i]
79+
}
80+
81+
return templates, nil
5882
}
5983

60-
func hasIssueTemplateSupport(httpClient *http.Client, hostname string) (bool, error) {
84+
func listPullRequestTemplates(httpClient *http.Client, repo ghrepo.Interface) ([]Template, error) {
85+
var query struct {
86+
Repository struct {
87+
PullRequestTemplates []pullRequestTemplate
88+
} `graphql:"repository(owner: $owner, name: $name)"`
89+
}
90+
91+
variables := map[string]interface{}{
92+
"owner": githubv4.String(repo.RepoOwner()),
93+
"name": githubv4.String(repo.RepoName()),
94+
}
95+
96+
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(repo.RepoHost()), httpClient)
97+
98+
err := gql.QueryNamed(context.Background(), "PullRequestTemplates", &query, variables)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
ts := query.Repository.PullRequestTemplates
104+
templates := make([]Template, len(ts))
105+
for i := range templates {
106+
templates[i] = &ts[i]
107+
}
108+
109+
return templates, nil
110+
}
111+
112+
func hasTemplateSupport(httpClient *http.Client, hostname string, isPR bool) (bool, error) {
61113
if !ghinstance.IsEnterprise(hostname) {
62114
return true, nil
63115
}
@@ -81,20 +133,29 @@ func hasIssueTemplateSupport(httpClient *http.Client, hostname string) (bool, er
81133
return false, err
82134
}
83135

84-
var hasQuerySupport bool
85-
var hasMutationSupport bool
136+
var hasIssueQuerySupport bool
137+
var hasIssueMutationSupport bool
138+
var hasPullRequestQuerySupport bool
139+
86140
for _, field := range featureDetection.Repository.Fields {
87141
if field.Name == "issueTemplates" {
88-
hasQuerySupport = true
142+
hasIssueQuerySupport = true
143+
}
144+
if field.Name == "pullRequestTemplates" {
145+
hasPullRequestQuerySupport = true
89146
}
90147
}
91148
for _, field := range featureDetection.CreateIssueInput.InputFields {
92149
if field.Name == "issueTemplate" {
93-
hasMutationSupport = true
150+
hasIssueMutationSupport = true
94151
}
95152
}
96153

97-
return hasQuerySupport && hasMutationSupport, nil
154+
if isPR {
155+
return hasPullRequestQuerySupport, nil
156+
} else {
157+
return hasIssueQuerySupport && hasIssueMutationSupport, nil
158+
}
98159
}
99160

100161
type Template interface {
@@ -129,13 +190,10 @@ func NewTemplateManager(httpClient *http.Client, repo ghrepo.Interface, dir stri
129190
}
130191

131192
func (m *templateManager) hasAPI() (bool, error) {
132-
if m.isPR {
133-
return false, nil
134-
}
135193
if m.cachedClient == nil {
136194
m.cachedClient = api.NewCachedClient(m.httpClient, time.Hour*24)
137195
}
138-
return hasIssueTemplateSupport(m.cachedClient, m.repo.RepoHost())
196+
return hasTemplateSupport(m.cachedClient, m.repo.RepoHost(), m.isPR)
139197
}
140198

141199
func (m *templateManager) HasTemplates() (bool, error) {
@@ -201,14 +259,15 @@ func (m *templateManager) fetch() error {
201259
}
202260

203261
if hasAPI {
204-
issueTemplates, err := listIssueTemplates(m.httpClient, m.repo)
262+
lister := listIssueTemplates
263+
if m.isPR {
264+
lister = listPullRequestTemplates
265+
}
266+
templates, err := lister(m.httpClient, m.repo)
205267
if err != nil {
206268
return err
207269
}
208-
m.templates = make([]Template, len(issueTemplates))
209-
for i := range issueTemplates {
210-
m.templates[i] = &issueTemplates[i]
211-
}
270+
m.templates = templates
212271
}
213272

214273
if !m.allowFS {

pkg/cmd/pr/shared/templates_test.go

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,70 @@ func TestTemplateManager_hasAPI(t *testing.T) {
6363

6464
assert.Equal(t, "LEGACY", string(m.LegacyBody()))
6565

66-
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
67-
as, askRestore := prompt.InitAskStubber()
68-
defer askRestore()
66+
as := prompt.NewAskStubber(t)
67+
as.StubPrompt("Choose a template").
68+
AssertOptions([]string{"Bug report", "Feature request", "Open a blank issue"}).
69+
AnswerWith("Feature request")
6970

70-
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
71-
as.StubOne(1) // choose "Feature Request"
7271
tpl, err := m.Choose()
72+
7373
assert.NoError(t, err)
7474
assert.Equal(t, "Feature request", tpl.NameForSubmit())
7575
assert.Equal(t, "I need a feature", string(tpl.Body()))
7676
}
77+
78+
func TestTemplateManager_hasAPI_PullRequest(t *testing.T) {
79+
rootDir := t.TempDir()
80+
legacyTemplateFile := filepath.Join(rootDir, ".github", "PULL_REQUEST_TEMPLATE.md")
81+
_ = os.MkdirAll(filepath.Dir(legacyTemplateFile), 0755)
82+
_ = ioutil.WriteFile(legacyTemplateFile, []byte("LEGACY"), 0644)
83+
84+
tr := httpmock.Registry{}
85+
httpClient := &http.Client{Transport: &tr}
86+
defer tr.Verify(t)
87+
88+
tr.Register(
89+
httpmock.GraphQL(`query IssueTemplates_fields\b`),
90+
httpmock.StringResponse(`{"data":{
91+
"Repository": {
92+
"fields": [
93+
{"name": "foo"},
94+
{"name": "pullRequestTemplates"}
95+
]
96+
}
97+
}}`))
98+
tr.Register(
99+
httpmock.GraphQL(`query PullRequestTemplates\b`),
100+
httpmock.StringResponse(`{"data":{"repository":{
101+
"pullRequestTemplates": [
102+
{"filename": "bug_pr.md", "body": "I fixed a problem"},
103+
{"filename": "feature_pr.md", "body": "I added a feature"}
104+
]
105+
}}}`))
106+
107+
m := templateManager{
108+
repo: ghrepo.NewWithHost("OWNER", "REPO", "example.com"),
109+
rootDir: rootDir,
110+
allowFS: true,
111+
isPR: true,
112+
httpClient: httpClient,
113+
cachedClient: httpClient,
114+
}
115+
116+
hasTemplates, err := m.HasTemplates()
117+
assert.NoError(t, err)
118+
assert.True(t, hasTemplates)
119+
120+
assert.Equal(t, "LEGACY", string(m.LegacyBody()))
121+
122+
as := prompt.NewAskStubber(t)
123+
as.StubPrompt("Choose a template").
124+
AssertOptions([]string{"bug_pr.md", "feature_pr.md", "Open a blank pull request"}).
125+
AnswerWith("bug_pr.md")
126+
127+
tpl, err := m.Choose()
128+
129+
assert.NoError(t, err)
130+
assert.Equal(t, "", tpl.NameForSubmit())
131+
assert.Equal(t, "I fixed a problem", string(tpl.Body()))
132+
}

0 commit comments

Comments
 (0)
X Tutup