PR Review Request Reminders

Walking the Line Between Responsive and Distracted

July 14, 2022, 11:20 a.m.

The OpenShift Test Platform team at Red Hat maintains and operates the CI system for OpenShift. This includes maintaining and enhancing: the kubernetes/test-infra, all of the openshift/ci-tools that go into running a CI system, documentation for the system in openshift/ci-docs, and certain aspects of the CI configuration in openshift/release.

This necessitates that the team review and approve PRs in the preceding repos from contributors both inside and outside of the team itself. The team wants to provide a quick and easy experience for contributors and strives to provide PR reviews in a timely manner. This lofty goal makes relying on GitHub's chatty notification system difficult and distracting.

I thought a lot about how the team could be responsive to our contributors while not distracting ourselves from our other important duties. What if each member of the team received a slack message each day detailing all of the PRs where their input has been requested? Then they could simply review it at a time that made sense for them, and act on each PR. This would ensure that no contributor would ever have to wait for more than a day for their PR to be seen.

A YAML configuration file could be defined for this tool to: enumerate our team members, team name, and repos that we want to check:

teamMembers:
  - sgoeddel
  ''
teamName: test-platform
repos:
  - openshift/ci-tools
  - openshift/ci-docs
  - openshift/release
  - kubernetes/test-infra

GitHub provides a REST API that has an endpoint to list PRs for a repo. Utilizing that, we can obtain every open PR for the repos that we maintain:

prs, err := ghClient.GetPullRequests(org, repo)
if err != nil {
    logrus.Errorf("failed to get pull requests: %v", err)
}

Then check to see which, if any, of our team members have been requested to review the PR:

for _, pr := range prs {
    for i, u := range users {
        if u.requestedToReview(pr) {
            u.PrRequests = append(u.PrRequests, prRequest{
                Repo:        orgRepo,
                Number:      pr.Number,
                Url:         pr.HTMLURL,
                Title:       pr.Title,
                Author:      pr.User.Login,
                Created:     pr.CreatedAt,
                LastUpdated: pr.UpdatedAt,
            })
            users[i] = u
        }
    }
}

Determining if a given user has been requested to review a PR requires checking if the user themselves have been requested or if the configured team has been requested:

func (u *user) requestedToReview(pr github.PullRequest) bool {
    // only check PRs that the user is not the author of, as they could have requested their own team
    if u.GithubId != pr.User.Login {
        for _, team := range pr.RequestedTeams {
            if u.TeamName == team.Slug {
                return true
            }
        }

        for _, reviewer := range pr.RequestedReviewers {
            if u.GithubId == reviewer.Login {
                return true
            }
        }
    }

    return false
}

In order for the resulting reminder message to be most useful we need a good ordering of the PRs:

// sort by most recent update first
sort.Slice(user.PrRequests, func(i, j int) bool {
    return user.PrRequests[i].LastUpdated.After(user.PrRequests[j].LastUpdated)
})

It is also helpful to have a simple colored emoji next to each PR so that they are easily filtered by date created:

const (
    recent  = ":large_green_circle:"
    normal  = ":large_orange_circle:"
    old     = ":red_circle:"
    twoDays = time.Hour * 24 * 2
    oneWeek = time.Hour * 24 * 7
)

func (p prRequest) recency() string {
    now := time.Now()
    if p.Created.After(now.Add(-twoDays)) {
        return recent
    } else if p.Created.After(now.Add(-oneWeek)) {
        return normal
    } else {
        return old
    }
}

Slack also provides an API that allows us to post a message. Using their constructs we can format a nice message to display:

for _, pr := range user.PrRequests {
    message = append(message, &slack.ContextBlock{
        Type: slack.MBTContext,
        ContextElements: slack.ContextElements{
            Elements: []slack.MixedElement{
                &slack.TextBlockObject{
                    Type: slack.MarkdownType,
                    Text: pr.link(),
                },
                &slack.TextBlockObject{
                    Type: slack.MarkdownType,
                    Text: pr.createdUpdatedMessage(),
                },
            },
        },
    })
}

We decided that it was most useful if this tool was scheduled to run daily, and since our team is geographically dispersed we chose to run it at 8 am UTC on weekdays. Putting it all together, each member of our team gets a message like the following at the beginning of their work day. PR Review Reminders

The full source code can be found in ci-tools/cmd/pr-reminder.

Comments about PR Review Request Reminders

No one has left a comment yet, be the first to voice your opinion on PR Review Request Reminders!