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
120 changes: 120 additions & 0 deletions toolbox/relnotes/hierarchy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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 {
setNoteDict(dict, "nullSig", "nullArea", -1, pr)

Choose a reason for hiding this comment

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

I think we could use extractIssueSIGs(issueMap[pr]) and extractIssueArea(issueMap[pr]) instead of "nullSig" and "nullArea" to still sort these issues, since many prs don't link to an issue.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jennybuckley In our design doc we want the automation to enforce every release-note PR linking to at least one issue. But I agree that the rule seems to be not applied yet. I pushed an update to use SIG & area labels from PRs with no issue associated.

continue
}
for _, i := range issues {
sigs := extractIssueSIGs(issueMap[i])
area := extractIssueArea(issueMap[i])
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(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, " & ")
}