Skip to content

Commit df9f0f9

Browse files
authored
Update pr/issue field in fragment/changelog to store URLs (#95)
1 parent be113b9 commit df9f0f9

File tree

10 files changed

+145
-54
lines changed

10 files changed

+145
-54
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: feature
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Add URL support for pr and issue fields in fragments and changelog
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
#description:
19+
20+
# Affected component; a word indicating the component this changeset affects.
21+
component:
22+
23+
# PR number; optional; the PR number that added the changeset.
24+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
25+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
26+
# Please provide it if you are adding a fragment for a different PR.
27+
#pr: 1234
28+
29+
# Issue number; optional; the GitHub issue related to this changeset (either closes or is part of).
30+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
31+
#issue: 1234

internal/changelog/builder.go

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os/exec"
1414
"path"
1515
"path/filepath"
16+
"strconv"
1617
"strings"
1718

1819
"github.com/elastic/elastic-agent-changelog-tool/internal/changelog/fragment"
@@ -110,20 +111,20 @@ func (b Builder) Build(owner, repo string) error {
110111
// Applying heuristics to PR fields
111112
originalPR, err := FindOriginalPR(entry.LinkedPR[0], owner, repo, c)
112113
if err != nil {
113-
log.Printf("%s: check if the PR field is correct in changelog", entry.File.Name)
114+
log.Printf("%s: check if the PR field is correct in changelog: %s", entry.File.Name, err.Error())
114115
continue
115116
}
116117

117-
b.changelog.Entries[i].LinkedPR = []int{originalPR}
118+
b.changelog.Entries[i].LinkedPR = []string{originalPR}
118119
}
119120

120121
if len(entry.LinkedIssue) == 0 && len(b.changelog.Entries[i].LinkedPR) > 0 {
121-
linkedIssues := []int{}
122+
linkedIssues := []string{}
122123

123-
for _, pr := range b.changelog.Entries[i].LinkedPR {
124-
tempIssues, err := FindIssues(graphqlClient, context.Background(), owner, repo, pr, 50)
124+
for _, prURL := range b.changelog.Entries[i].LinkedPR {
125+
tempIssues, err := FindIssues(graphqlClient, context.Background(), owner, repo, prURL, 50)
125126
if err != nil {
126-
log.Printf("%s: could not find linked issues for pr: %s/pull/%d", entry.File.Name, entry.Repository, entry.LinkedPR)
127+
log.Printf("%s: could not find linked issues for pr: %s: %s", entry.File.Name, entry.LinkedPR, err.Error())
127128
continue
128129
}
129130

@@ -134,7 +135,13 @@ func (b Builder) Build(owner, repo string) error {
134135
}
135136

136137
b.changelog.Entries[i].LinkedIssue = linkedIssues
138+
} else if len(entry.LinkedIssue) == 1 {
139+
_, err := ExtractEventNumber("issue", entry.LinkedIssue[0])
140+
if err != nil {
141+
log.Printf("%s: check if the issue field is correct in changelog: %s", entry.File.Name, err.Error())
142+
}
137143
}
144+
138145
}
139146

140147
data, err := yaml.Marshal(&b.changelog)
@@ -161,6 +168,39 @@ func collectFragment(fs afero.Fs, path string, info os.FileInfo, err error, file
161168
return nil
162169
}
163170

171+
func ExtractEventNumber(linkType, eventURL string) (string, error) {
172+
urlParts := strings.Split(eventURL, "/")
173+
174+
if len(urlParts) < 1 {
175+
return "", fmt.Errorf("cant get event number")
176+
}
177+
178+
switch linkType {
179+
// maybe use regex to validate instead of a simple string check
180+
case "pr":
181+
if !strings.Contains(eventURL, "pull") {
182+
return "", fmt.Errorf("link is invalid for pr")
183+
}
184+
case "issue":
185+
if !strings.Contains(eventURL, "issues") {
186+
return "", fmt.Errorf("link is invalid for issue")
187+
}
188+
}
189+
190+
return urlParts[len(urlParts)-1], nil
191+
}
192+
193+
func CreateEventLink(linkType, owner, repo, eventID string) string {
194+
switch linkType {
195+
case "issue":
196+
return fmt.Sprintf("https://github.com/%s/%s/issues/%s", owner, repo, eventID)
197+
case "pr":
198+
return fmt.Sprintf("https://github.com/%s/%s/pull/%s", owner, repo, eventID)
199+
default:
200+
panic("wrong linkType")
201+
}
202+
}
203+
164204
func GetLatestCommitHash(fileName string) (string, error) {
165205
response, err := exec.Command("git", "log", "--diff-filter=A", "--format=%H", "changelog/fragments/"+fileName).Output()
166206
if err != nil {
@@ -170,40 +210,61 @@ func GetLatestCommitHash(fileName string) (string, error) {
170210
return strings.ReplaceAll(string(response), "\n", ""), nil
171211
}
172212

173-
func FindIssues(graphqlClient *github.ClientGraphQL, ctx context.Context, owner, name string, prID, issuesLen int) ([]int, error) {
174-
issues, err := graphqlClient.PR.FindIssues(ctx, owner, name, prID, issuesLen)
213+
func FindIssues(graphqlClient *github.ClientGraphQL, ctx context.Context, owner, name string, prURL string, issuesLen int) ([]string, error) {
214+
prID, err := ExtractEventNumber("pr", prURL)
175215
if err != nil {
176216
return nil, err
177217
}
178218

179-
return issues, nil
219+
prIDInt, _ := strconv.Atoi(prID)
220+
221+
issues, err := graphqlClient.PR.FindIssues(ctx, owner, name, prIDInt, issuesLen)
222+
if err != nil {
223+
return nil, err
224+
}
225+
226+
issueLinks := make([]string, len(issues))
227+
228+
for i, issue := range issues {
229+
issueLinks[i] = CreateEventLink("issue", owner, name, issue)
230+
}
231+
232+
return issueLinks, nil
180233
}
181234

182-
func FillEmptyPRField(commitHash, owner, repo string, c *github.Client) ([]int, error) {
235+
func FillEmptyPRField(commitHash, owner, repo string, c *github.Client) ([]string, error) {
183236
pr, err := github.FindPR(context.Background(), c, owner, repo, commitHash)
184237
if err != nil {
185-
return []int{}, err
238+
return []string{}, err
186239
}
187240

188-
var prIDs []int
241+
prLinks := []string{}
189242

190243
for _, item := range pr.Items {
191-
prIDs = append(prIDs, item.PullRequestID)
244+
prLinks = append(prLinks, CreateEventLink("pr", owner, repo, strconv.Itoa(item.PullRequestID)))
192245
}
193246

194-
return prIDs, nil
247+
return prLinks, nil
195248
}
196249

197-
func FindOriginalPR(linkedPR int, owner, repo string, c *github.Client) (int, error) {
198-
pr, _, err := c.PullRequests.Get(context.Background(), owner, repo, linkedPR)
250+
func FindOriginalPR(prURL string, owner, repo string, c *github.Client) (string, error) {
251+
linkedPR, err := ExtractEventNumber("pr", prURL)
199252
if err != nil {
200-
return 0, err
253+
return "", err
254+
}
255+
256+
linkedPRString, _ := strconv.Atoi(linkedPR)
257+
258+
pr, _, err := c.PullRequests.Get(context.Background(), owner, repo, linkedPRString)
259+
if err != nil {
260+
return "", err
201261
}
202262

203263
prID, err := github.TestStrategies(pr, &github.BackportPRNumber{}, &github.PRNumber{})
204264
if err != nil {
205-
return 0, err
265+
return "", err
206266
}
207267

208-
return prID, nil
268+
prLink := CreateEventLink("pr", owner, repo, strconv.Itoa(prID))
269+
return prLink, nil
209270
}

internal/changelog/builder_test.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ func TestFillEmptyPRField(t *testing.T) {
4141
require.NoError(t, err)
4242
require.Len(t, prIDs, 2)
4343
require.NotEmpty(t, prIDs)
44-
require.ElementsMatch(t, []int{30979, 31279}, prIDs)
44+
require.ElementsMatch(t, []string{
45+
"https://github.com/elastic/beats/pull/30979", "https://github.com/elastic/beats/pull/31279"}, prIDs)
4546
}
4647

4748
func TestFillEmptyPRFieldBadHash(t *testing.T) {
@@ -61,9 +62,15 @@ func TestFindIssues(t *testing.T) {
6162

6263
graphqlClient := github.NewGraphQLClient(hc)
6364

64-
issues, err := changelog.FindIssues(graphqlClient, context.Background(), "elastic", "beats", 32501, 50)
65+
issues, err := changelog.FindIssues(graphqlClient, context.Background(), "elastic", "beats", "https://github.com/elastic/beats/pull/32501", 50)
6566
require.NoError(t, err)
6667
require.NotEmpty(t, issues)
6768
require.Len(t, issues, 1)
68-
require.ElementsMatch(t, issues, []int{32483})
69+
require.ElementsMatch(t, issues, []string{"https://github.com/elastic/beats/issues/32483"})
70+
}
71+
72+
func TestExtractEventNumber(t *testing.T) {
73+
id, err := changelog.ExtractEventNumber("pr", "https://github.com/elastic/elastic-agent-changelog-tool/pull/99")
74+
require.NoError(t, err)
75+
require.Equal(t, id, "99")
6976
}

internal/changelog/entry.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44

55
package changelog
66

7-
import "github.com/elastic/elastic-agent-changelog-tool/internal/changelog/fragment"
7+
import (
8+
"github.com/elastic/elastic-agent-changelog-tool/internal/changelog/fragment"
9+
)
810

911
type FragmentFileInfo struct {
1012
Name string `yaml:"name"`
1113
Checksum string `yaml:"checksum"`
1214
}
1315

1416
type Entry struct {
15-
Kind Kind `yaml:"kind"`
16-
Summary string `yaml:"summary"`
17-
Description string `yaml:"description"`
18-
Component string `yaml:"component" `
19-
LinkedPR []int `yaml:"pr"`
20-
LinkedIssue []int `yaml:"issue"`
21-
Repository string `yaml:"repository"`
17+
Kind Kind `yaml:"kind"`
18+
Summary string `yaml:"summary"`
19+
Description string `yaml:"description"`
20+
Component string `yaml:"component"`
21+
LinkedPR []string `yaml:"pr"`
22+
LinkedIssue []string `yaml:"issue"`
2223

2324
Timestamp int64 `yaml:"timestamp"`
2425
File FragmentFileInfo `yaml:"file"`
@@ -32,22 +33,21 @@ func EntryFromFragment(f fragment.File) Entry {
3233
Summary: f.Fragment.Summary,
3334
Description: f.Fragment.Description,
3435
Component: f.Fragment.Component,
35-
LinkedPR: []int{},
36-
LinkedIssue: []int{},
37-
Repository: f.Fragment.Repository,
36+
LinkedPR: []string{},
37+
LinkedIssue: []string{},
3838
Timestamp: f.Timestamp,
3939
File: FragmentFileInfo{
4040
Name: f.Name,
4141
Checksum: f.Checksum(),
4242
},
4343
}
4444

45-
if f.Fragment.Pr > 0 {
46-
e.LinkedPR = []int{f.Fragment.Pr}
45+
if f.Fragment.Pr != "" {
46+
e.LinkedPR = []string{f.Fragment.Pr}
4747
}
4848

49-
if f.Fragment.Issue > 0 {
50-
e.LinkedIssue = []int{f.Fragment.Issue}
49+
if f.Fragment.Issue != "" {
50+
e.LinkedIssue = []string{f.Fragment.Issue}
5151
}
5252

5353
return e

internal/changelog/fragment/creator_internal_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,6 @@ component:
103103
# Issue number; optional; the GitHub issue related to this changeset (either closes or is part of).
104104
# If not present is automatically filled by the tooling with the issue linked to the PR number.
105105
#issue: 1234
106-
107-
# Repository URL; optional; the repository URL related to this changeset and pr and issue numbers.
108-
# If not present is automatically filled by the tooling based on the repository this file has been committed in.
109-
#repository: https://github.com/elastic/elastic-agent-changelog-tool
110106
`
111107
got := string(content)
112108
assert.Equal(t, expected, got)

internal/changelog/fragment/fragment.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ type Fragment struct {
99
Summary string `yaml:"summary"`
1010
Description string `yaml:"description"`
1111
Component string `yaml:"component"`
12-
Pr int `yaml:"pr"`
13-
Issue int `yaml:"issue"`
14-
Repository string `yaml:"repository"`
12+
Pr string `yaml:"pr"`
13+
Issue string `yaml:"issue"`
1514
}

internal/changelog/fragment/template.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,3 @@ component:
2929
# Issue number; optional; the GitHub issue related to this changeset (either closes or is part of).
3030
# If not present is automatically filled by the tooling with the issue linked to the PR number.
3131
#issue: 1234
32-
33-
# Repository URL; optional; the repository URL related to this changeset and pr and issue numbers.
34-
# If not present is automatically filled by the tooling based on the repository this file has been committed in.
35-
#repository: https://github.com/elastic/elastic-agent-changelog-tool

internal/changelog/renderer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (r Renderer) Render() error {
7474
tmpl, err := template.New("asciidoc-release-notes").
7575
Funcs(template.FuncMap{
7676
// nolint:staticcheck // ignoring for now, supports for multiple component is not implemented
77-
"linkPRSource": func(component string, ids []int) string {
77+
"linkPRSource": func(component string, ids []string) string {
7878
component = "agent" // TODO: remove this when implementing support for multiple components
7979

8080
res := make([]string, len(ids))
@@ -86,7 +86,7 @@ func (r Renderer) Render() error {
8686
return strings.Join(res, " ")
8787
},
8888
// nolint:staticcheck // ignoring for now, supports for multiple component is not implemented
89-
"linkIssueSource": func(component string, ids []int) string {
89+
"linkIssueSource": func(component string, ids []string) string {
9090
component = "agent" // TODO: remove this when implementing support for multiple components
9191
res := make([]string, len(ids))
9292

internal/github/client.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"fmt"
1010
"net/http"
11+
"strconv"
1112

1213
gh "github.com/google/go-github/v32/github"
1314
"github.com/shurcooL/githubv4"
@@ -45,7 +46,7 @@ type graphqlService struct {
4546
client *githubv4.Client
4647
}
4748

48-
func (s *graphqlService) FindIssues(ctx context.Context, owner, repo string, prID, issuesLen int) ([]int, error) {
49+
func (s *graphqlService) FindIssues(ctx context.Context, owner, repo string, prID, issuesLen int) ([]string, error) {
4950
variables := map[string]interface{}{
5051
"issuesLen": githubv4.Int(issuesLen),
5152
"prID": githubv4.Int(prID),
@@ -72,10 +73,10 @@ func (s *graphqlService) FindIssues(ctx context.Context, owner, repo string, prI
7273
return nil, fmt.Errorf("graphql mutate: %w", err)
7374
}
7475

75-
issues := make([]int, len(q.Repository.PullRequest.ClosingIssuesReferences.Edges))
76+
issues := make([]string, len(q.Repository.PullRequest.ClosingIssuesReferences.Edges))
7677

7778
for i, e := range q.Repository.PullRequest.ClosingIssuesReferences.Edges {
78-
issues[i] = int(e.Node.Number)
79+
issues[i] = strconv.FormatInt(int64(e.Node.Number), 10)
7980
}
8081

8182
return issues, nil

internal/github/interfaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ type githubUsersService interface {
3131
}
3232

3333
type githubGraphQLPRService interface {
34-
FindIssues(ctx context.Context, owner, repo string, prID, issuesLen int) ([]int, error)
34+
FindIssues(ctx context.Context, owner, repo string, prID, issuesLen int) ([]string, error)
3535
}

0 commit comments

Comments
 (0)