Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hierarchical release note feature functions #435

Closed
wants to merge 6 commits into from
Closed
129 changes: 129 additions & 0 deletions toolbox/relnotes/hierarchy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package main

import (
"fmt"
"os"
"regexp"
"strconv"
"strings"

"github.com/google/go-github/github"
)

type dictSIG map[string]dictArea
type dictArea map[string]dictIssue
type dictIssue map[int]dictPR
type dictPR map[int]bool

func hierarchicalNoteLayout(f *os.File, dict dictSIG, issueMap map[int]*github.Issue) {
for sig, areas := range dict {
f.WriteString(fmt.Sprintf(" - %s\n\n", strings.Title(sig)))
for area, issues := range areas {
f.WriteString(fmt.Sprintf(" - %s\n\n", strings.Title(area)))
for issue, prs := range issues {
if issue >= 0 {
f.WriteString(fmt.Sprintf(" - %s (#%d)\n", *issueMap[issue].Title, issue))
} else {
f.WriteString(fmt.Sprintf(" - NullIssue\n"))
}
for pr := range prs {
f.WriteString(fmt.Sprintf(" * %s (#%d, @%s)\n", extractReleaseNote(issueMap[pr]), pr, *issueMap[pr].User.Login))
}
f.WriteString("\n")
}
}
}
}

// createHierarchicalNote given release PRs and issue map, creates hierarchical release note
// map[SIG]map[Area]map[Issue]PR.
func createHierarchicalNote(prs []int, issueMap map[int]*github.Issue) dictSIG {
var dict = dictSIG{}

for _, pr := range prs {
issues := extractFixedIssues(*issueMap[pr].Body)
if len(issues) == 0 {
// In our design doc, the automation should enforce every release-note PR
// with at least one issue. However it's observed that the rule is not
// applied yet. Also old PRs may not link to an issue.
//
// To produce info-richer release note, we try to get SIG and Area label
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, most of PRs don't have either sig or area label. I once felt a bit painful when I wanted to filter PRs by sig. I was thinking we enforce sig labels on PRs as we already do on issues. Then we may get more info here. @enisoc @Bradamant3 WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enforcing sig label on PR will also be helpful for #348.

// from PRs which don't link to any issue.
issues = append(issues, pr)
}
for _, i := range issues {
sigs := extractIssueSIGs(issueMap[i])
area := extractIssueArea(issueMap[i])
// For PRs that don't link to any issue, restore the nullIssue information
if issueMap[i].PullRequestLinks != nil {
i = -1
}
if len(sigs) == 0 {
setNoteDict(dict, "nullSig", area, i, pr)
continue
}
for _, s := range sigs {
setNoteDict(dict, s, area, i, pr)
}
}
}

return dict
}

// setNoteDict sets the entry dict[sig][area][issue][pr] to be true, initializes nil maps along
// the way.
func setNoteDict(dict dictSIG, sig, area string, issue, pr int) {
if dict[sig] == nil {
dict[sig] = dictArea{}
}
if dict[sig][area] == nil {
dict[sig][area] = dictIssue{}
}
if dict[sig][area][issue] == nil {
dict[sig][area][issue] = dictPR{}
}
dict[sig][area][issue][pr] = true
}

// extractFixedIssues parses the fixed issues' id from PR body.
func extractFixedIssues(msg string) []int {
var issues = make([]int, 0)
re, _ := regexp.Compile("fixes #([0-9]+)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that github not only supports fixes but also fixed, closes, closed, etc, though we use fixes in PR template. That means, some guys may use other supported keywords.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and it can also fixes issue in other repo, e.g., fixes kubernetes/kubeadm#123. do we need to respect this case?

matches := re.FindAllStringSubmatch(strings.ToLower(msg), -1)
for _, match := range matches {
id, _ := strconv.Atoi(match[1])
issues = append(issues, id)
}

return issues
}

// extractIssueSIGs gets the SIGs of the input issue (if there is any)
func extractIssueSIGs(i *github.Issue) []string {
var sigs = make([]string, 0)
for _, l := range i.Labels {
if strings.HasPrefix(*l.Name, "sig/") {
sigs = append(sigs, (*l.Name)[4:])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using TrimPrefix here?

}
}

return sigs
}

// extractIssueSIGs gets the Areas of the input issue and returns as a single string. If the issue
// doesn't have any Area label, the function returns "nullArea".
func extractIssueArea(i *github.Issue) string {
var areas = make([]string, 0)
for _, l := range i.Labels {
if strings.HasPrefix(*l.Name, "area/") {
areas = append(areas, (*l.Name)[5:])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does github api guarantee anything about the order of the labels returned? I am wondering because if two issues have labels "area/Alpha" and "area/Beta" and the labels can be in any order, then they might be put into different area buckets in the final document, even though they have the same areas

Copy link
Member Author

@roycaihw roycaihw Oct 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jennybuckley I think it guarantees alphabetical order.

Copy link
Contributor

@xiangpengzhao xiangpengzhao Oct 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also TrimPrefix here.

}
}

if len(areas) == 0 {
return "nullArea"
}

return strings.Join(areas, " & ")
}