Skip to content

Commit

Permalink
Merge pull request #15 from mozilla-services/parse-prs-from-release-n…
Browse files Browse the repository at this point in the history
…otes

Parse PRs from Release notes 🚀
  • Loading branch information
hackebrot authored Jan 2, 2024
2 parents f961a88 + 8c961cc commit 56ff822
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 7 deletions.
2 changes: 1 addition & 1 deletion metrics/cmd/fixtures/releases/query.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"IsDraft": false,
"IsLatest": false,
"IsPrerelease": false,
"Description": "Description for 0.2.0",
"Description": "## What's Changed\n* Develop feature by @hackebrot in https://github.com/hackebrot/turtle/pull/123\n* Add tests for feature by @hackebrot in https://github.com/hackebrot/turtle/pull/124\n",
"CreatedAt": "2019-12-15T17:35:58Z",
"PublishedAt": "2019-12-15T20:00:44Z"
}
Expand Down
2 changes: 1 addition & 1 deletion metrics/cmd/fixtures/releases/query_123.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"IsDraft": false,
"IsLatest": false,
"IsPrerelease": false,
"Description": "Description for 0.1.0",
"Description": "## What's Changed\n* Create app by @hackebrot in https://github.com/hackebrot/turtle/pull/22\n",
"CreatedAt": "2018-07-13T15:23:49Z",
"PublishedAt": "2018-07-16T13:30:36Z"
}
Expand Down
9 changes: 7 additions & 2 deletions metrics/cmd/fixtures/releases/want__default.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
name,tagName,isDraft,isLatest,isPrerelease,description,createdAt,publishedAt
20.1.0,20.1.0,false,true,false,Description for 20.1.0,2020-05-04T14:55:36Z,2020-05-04T15:02:21Z
0.2.0,0.2.0,false,false,false,Description for 0.2.0,2019-12-15T17:35:58Z,2019-12-15T20:00:44Z
0.1.0,0.1.0,false,false,false,Description for 0.1.0,2018-07-13T15:23:49Z,2018-07-16T13:30:36Z
0.2.0,0.2.0,false,false,false,"## What's Changed
* Develop feature by @hackebrot in https://github.com/hackebrot/turtle/pull/123
* Add tests for feature by @hackebrot in https://github.com/hackebrot/turtle/pull/124
",2019-12-15T17:35:58Z,2019-12-15T20:00:44Z
0.1.0,0.1.0,false,false,false,"## What's Changed
* Create app by @hackebrot in https://github.com/hackebrot/turtle/pull/22
",2018-07-13T15:23:49Z,2018-07-16T13:30:36Z
4 changes: 2 additions & 2 deletions metrics/cmd/fixtures/releases/want__default.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"IsDraft": false,
"IsLatest": false,
"IsPrerelease": false,
"Description": "Description for 0.2.0",
"Description": "## What's Changed\n* Develop feature by @hackebrot in https://github.com/hackebrot/turtle/pull/123\n* Add tests for feature by @hackebrot in https://github.com/hackebrot/turtle/pull/124\n",
"CreatedAt": "2019-12-15T17:35:58Z",
"PublishedAt": "2019-12-15T20:00:44Z"
},
Expand All @@ -25,7 +25,7 @@
"IsDraft": false,
"IsLatest": false,
"IsPrerelease": false,
"Description": "Description for 0.1.0",
"Description": "## What's Changed\n* Create app by @hackebrot in https://github.com/hackebrot/turtle/pull/22\n",
"CreatedAt": "2018-07-13T15:23:49Z",
"PublishedAt": "2018-07-16T13:30:36Z"
}
Expand Down
9 changes: 9 additions & 0 deletions metrics/cmd/fixtures/releases/want__prs.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name,tagName,isDraft,isLatest,isPrerelease,description,createdAt,publishedAt,prs
20.1.0,20.1.0,false,true,false,Description for 20.1.0,2020-05-04T14:55:36Z,2020-05-04T15:02:21Z,null
0.2.0,0.2.0,false,false,false,"## What's Changed
* Develop feature by @hackebrot in https://github.com/hackebrot/turtle/pull/123
* Add tests for feature by @hackebrot in https://github.com/hackebrot/turtle/pull/124
",2019-12-15T17:35:58Z,2019-12-15T20:00:44Z,"[123,124]"
0.1.0,0.1.0,false,false,false,"## What's Changed
* Create app by @hackebrot in https://github.com/hackebrot/turtle/pull/22
",2018-07-13T15:23:49Z,2018-07-16T13:30:36Z,[22]
40 changes: 40 additions & 0 deletions metrics/cmd/fixtures/releases/want__prs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[
{
"Name": "20.1.0",
"TagName": "20.1.0",
"IsDraft": false,
"IsLatest": true,
"IsPrerelease": false,
"Description": "Description for 20.1.0",
"CreatedAt": "2020-05-04T14:55:36Z",
"PublishedAt": "2020-05-04T15:02:21Z",
"PRs": null
},
{
"Name": "0.2.0",
"TagName": "0.2.0",
"IsDraft": false,
"IsLatest": false,
"IsPrerelease": false,
"Description": "## What's Changed\n* Develop feature by @hackebrot in https://github.com/hackebrot/turtle/pull/123\n* Add tests for feature by @hackebrot in https://github.com/hackebrot/turtle/pull/124\n",
"CreatedAt": "2019-12-15T17:35:58Z",
"PublishedAt": "2019-12-15T20:00:44Z",
"PRs": [
123,
124
]
},
{
"Name": "0.1.0",
"TagName": "0.1.0",
"IsDraft": false,
"IsLatest": false,
"IsPrerelease": false,
"Description": "## What's Changed\n* Create app by @hackebrot in https://github.com/hackebrot/turtle/pull/22\n",
"CreatedAt": "2018-07-13T15:23:49Z",
"PublishedAt": "2018-07-16T13:30:36Z",
"PRs": [
22
]
}
]
14 changes: 13 additions & 1 deletion metrics/cmd/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
)

type ReleasesOptions struct {
Limit int
Limit int
WithPRs bool
}

func newReleasesCmd(f *factory.Factory) *cobra.Command {
Expand All @@ -31,6 +32,8 @@ func newReleasesCmd(f *factory.Factory) *cobra.Command {
},
}
cmd.Flags().IntVarP(&opts.Limit, "limit", "l", 10, "limit for how many Releases to fetch")
cmd.Flags().BoolVar(&opts.WithPRs, "prs", false, "parse PR numbers from auto-generated release notes")

return cmd
}

Expand All @@ -55,5 +58,14 @@ func runReleases(ctx context.Context, f *factory.Factory, opts *ReleasesOptions)
return err
}

if opts.WithPRs {
var releasesWithPRs []github.ReleaseWithPRs

for _, release := range releases {
releasesWithPRs = append(releasesWithPRs, *github.NewReleaseWithPRs(&release))
}
return exporter.Export(releasesWithPRs)
}

return exporter.Export(releases)
}
10 changes: 10 additions & 0 deletions metrics/cmd/releases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ func TestReleases(t *testing.T) {
WantFixture: test.NewFixture("releases", "want__default.json"),
WantFile: filepath.Join(tempDir, "r.json"),
Env: env,
}, {
Name: "releases__prs__json",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "releases", "--prs", "-e", "json"},
WantFixture: test.NewFixture("releases", "want__prs.json"),
Env: env,
}, {
Name: "releases__prs__csv",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "releases", "--prs", "-e", "csv"},
WantFixture: test.NewFixture("releases", "want__prs.csv"),
Env: env,
}}

test.RunTests(t, newRootCmd, tests)
Expand Down
47 changes: 47 additions & 0 deletions metrics/internal/export/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type CSVEncoder struct{}

func (c *CSVEncoder) Encode(w io.Writer, v interface{}) error {
var records [][]string
var err error

csvw := csv.NewWriter(w)

Expand All @@ -51,6 +52,11 @@ func (c *CSVEncoder) Encode(w io.Writer, v interface{}) error {
records = PullRequestsToCSVRecords(v)
case []github.Release:
records = ReleasesToCSVRecords(v)
case []github.ReleaseWithPRs:
records, err = ReleasesWithPRsToCSVRecords(v)
if err != nil {
return err
}
case []github.Deployment:
records = DeploymentsToCSVRecords(v)
default:
Expand Down Expand Up @@ -126,6 +132,47 @@ func ReleasesToCSVRecords(rs []github.Release) [][]string {
return records
}

// ReleasesWithPRsToCSVRecords is identical to ReleasesToCSVRecords with the
// addition of an extra column on the right for a JSON array of PR numbers.
func ReleasesWithPRsToCSVRecords(rs []github.ReleaseWithPRs) ([][]string, error) {
var records [][]string

// Add column headers to records
records = append(records, []string{
"name",
"tagName",
"isDraft",
"isLatest",
"isPrerelease",
"description",
"createdAt",
"publishedAt",
"prs",
})

// Add a record for each release
for _, r := range rs {
prs, err := json.Marshal(r.PRs)
if err != nil {
return nil, fmt.Errorf("error encoding PRs as JSON: %w", err)
}
record := []string{
r.Release.Name,
r.Release.TagName,
strconv.FormatBool(r.Release.IsDraft),
strconv.FormatBool(r.Release.IsLatest),
strconv.FormatBool(r.Release.IsPrerelease),
r.Release.Description,
r.Release.CreatedAt.Format(time.RFC3339),
r.Release.PublishedAt.Format(time.RFC3339),
string(prs),
}
records = append(records, record)
}

return records, nil
}

func DeploymentsToCSVRecords(ds []github.Deployment) [][]string {
var records [][]string

Expand Down
2 changes: 2 additions & 0 deletions metrics/internal/github/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/shurcooL/githubv4"
)

// Release is a GitHub GraphQL API Release object.
// See https://docs.github.com/en/graphql/reference/objects#release
type Release struct {
Name string
TagName string
Expand Down
37 changes: 37 additions & 0 deletions metrics/internal/github/releases_with_prs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package github

import (
"regexp"
"strconv"
)

// ReleaseWithPRs embeds a GitHub GraphQL API Release object with an added slice
// of PR numbers, which we have parsed from its auto-generated release notes.
type ReleaseWithPRs struct {
Release
PRs []int
}

// NewReleaseWithPrs creates a new ReleaseWithPRs by parsing PR numbers from
// auto-generated Release Descriptions.
func NewReleaseWithPRs(r *Release) *ReleaseWithPRs {
var prs []int

// Pattern for auto-generated release notes. For more information see:
// https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
re := regexp.MustCompile(`\* .*by @\w+ in .+\/pull\/(?P<pr>\d+)`)

for _, match := range re.FindAllStringSubmatch(r.Description, -1) {
for i, name := range re.SubexpNames() {
if name == "pr" {
n, err := strconv.Atoi(match[i])
if err != nil {
continue
}
prs = append(prs, n)
}
}
}

return &ReleaseWithPRs{*r, prs}
}

0 comments on commit 56ff822

Please sign in to comment.