Skip to content

Commit cbfe07a

Browse files
committed
Implement high priority indicator via PR labels
1 parent dd8326a commit cbfe07a

File tree

12 files changed

+174
-12
lines changed

12 files changed

+174
-12
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,23 @@ To prevent ownership rules from being checked or applied for certain directories
244244
ignore = ["test_project"]
245245
```
246246

247+
### High Priority Labels
248+
249+
You can configure labels that indicate a high priority PR. When a PR has any of these labels, the comment will include a high priority indicator:
250+
251+
```toml
252+
high_priority_labels = ["high-priority", "urgent"]
253+
```
254+
255+
When a PR has any of these labels, the comment will look like this:
256+
```
257+
Codeowners approval required for this PR:
258+
- @user1
259+
- @user2
260+
261+
❗High Prio❗
262+
```
263+
247264
## CLI Tool
248265

249266
A CLI tool is available which provides some utilities for working with `.codeowners` files.

internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Config struct {
1414
UnskippableReviewers []string `toml:"unskippable_reviewers"`
1515
Ignore []string `toml:"ignore"`
1616
Enforcement *Enforcement `toml:"enforcement"`
17+
HighPriorityLabels []string `toml:"high_priority_labels"`
1718
}
1819

1920
type Enforcement struct {
@@ -32,6 +33,7 @@ func ReadConfig(path string) (*Config, error) {
3233
UnskippableReviewers: []string{},
3334
Ignore: []string{},
3435
Enforcement: &Enforcement{Approval: false, FailCheck: true},
36+
HighPriorityLabels: []string{},
3537
}
3638

3739
fileName := path + "codeowners.toml"

internal/config/config_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ ignore = ["ignored/"]
3636
[enforcement]
3737
approval = true
3838
fail_check = false
39+
high_priority_labels = ["high-priority", "urgent"]
3940
`,
4041
path: "testdata/",
4142
expected: &Config{
@@ -44,6 +45,7 @@ fail_check = false
4445
UnskippableReviewers: []string{"@user1", "@user2"},
4546
Ignore: []string{"ignored/"},
4647
Enforcement: &Enforcement{Approval: true, FailCheck: false},
48+
HighPriorityLabels: []string{"high-priority", "urgent"},
4749
},
4850
expectedErr: false,
4951
},
@@ -60,6 +62,7 @@ unskippable_reviewers = ["@user1"]
6062
UnskippableReviewers: []string{"@user1"},
6163
Ignore: []string{},
6264
Enforcement: &Enforcement{Approval: false, FailCheck: true},
65+
HighPriorityLabels: []string{},
6366
},
6467
expectedErr: false,
6568
},

internal/github/approvals.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77

88
"github.com/multimediallc/codeowners-plus/internal/git"
99
"github.com/multimediallc/codeowners-plus/pkg/codeowners"
10-
"github.com/multimediallc/codeowners-plus/pkg/functional"
10+
f "github.com/multimediallc/codeowners-plus/pkg/functional"
1111
)
1212

1313
type approvalWithDiff struct {

internal/github/gh.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
"github.com/google/go-github/v63/github"
1313
"github.com/multimediallc/codeowners-plus/internal/git"
14-
"github.com/multimediallc/codeowners-plus/pkg/functional"
14+
f "github.com/multimediallc/codeowners-plus/pkg/functional"
1515
)
1616

1717
type NoPRError struct{}
@@ -47,6 +47,7 @@ type Client interface {
4747
IsInComments(comment string, since *time.Time) (bool, error)
4848
IsSubstringInComments(substring string, since *time.Time) (bool, error)
4949
CheckApprovals(fileReviewerMap map[string][]string, approvals []*CurrentApproval, originalDiff git.Diff) (approvers []string, staleApprovals []*CurrentApproval)
50+
IsInLabels(labels []string) (bool, error)
5051
}
5152

5253
type GHClient struct {
@@ -414,6 +415,24 @@ func (gh *GHClient) IsSubstringInComments(substring string, since *time.Time) (b
414415
return false, nil
415416
}
416417

418+
// IsInLabels checks if the PR has any of the given labels
419+
func (gh *GHClient) IsInLabels(labels []string) (bool, error) {
420+
if gh.pr == nil {
421+
return false, &NoPRError{}
422+
}
423+
if len(labels) == 0 {
424+
return false, nil
425+
}
426+
for _, label := range gh.pr.Labels {
427+
for _, targetLabel := range labels {
428+
if label.GetName() == targetLabel {
429+
return true, nil
430+
}
431+
}
432+
}
433+
return false, nil
434+
}
435+
417436
// Apply approver satisfaction to the owners map, and return the approvals which should be invalidated
418437
func (gh *GHClient) CheckApprovals(
419438
fileReviewerMap map[string][]string,

internal/github/gh_test.go

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/google/go-github/v63/github"
15-
"github.com/multimediallc/codeowners-plus/pkg/functional"
15+
f "github.com/multimediallc/codeowners-plus/pkg/functional"
1616
)
1717

1818
func setupReviews() *GHClient {
@@ -424,6 +424,13 @@ func TestNilPRErr(t *testing.T) {
424424
return err
425425
},
426426
},
427+
{
428+
name: "IsInLabels",
429+
testFn: func() error {
430+
_, err := gh.IsInLabels([]string{"label"})
431+
return err
432+
},
433+
},
427434
}
428435
for _, tc := range tt {
429436
t.Run(tc.name, func(t *testing.T) {
@@ -1019,3 +1026,94 @@ func TestInitUserReviewerMap(t *testing.T) {
10191026
}
10201027
}
10211028
}
1029+
1030+
func TestIsInLabels(t *testing.T) {
1031+
tt := []struct {
1032+
name string
1033+
pr *github.PullRequest
1034+
labels []string
1035+
expected bool
1036+
expectError bool
1037+
failMessage string
1038+
}{
1039+
{
1040+
name: "has matching label",
1041+
pr: &github.PullRequest{
1042+
Labels: []*github.Label{
1043+
{Name: github.String("high-priority")},
1044+
},
1045+
},
1046+
labels: []string{"high-priority"},
1047+
expected: true,
1048+
expectError: false,
1049+
failMessage: "Should detect matching label",
1050+
},
1051+
{
1052+
name: "has multiple labels but no match",
1053+
pr: &github.PullRequest{
1054+
Labels: []*github.Label{
1055+
{Name: github.String("bug")},
1056+
{Name: github.String("enhancement")},
1057+
},
1058+
},
1059+
labels: []string{"high-priority"},
1060+
expected: false,
1061+
expectError: false,
1062+
failMessage: "Should not detect label when not present",
1063+
},
1064+
{
1065+
name: "empty labels list",
1066+
pr: &github.PullRequest{
1067+
Labels: []*github.Label{
1068+
{Name: github.String("high-priority")},
1069+
},
1070+
},
1071+
labels: []string{},
1072+
expected: false,
1073+
expectError: false,
1074+
failMessage: "Should return false for empty labels list",
1075+
},
1076+
{
1077+
name: "multiple target labels",
1078+
pr: &github.PullRequest{
1079+
Labels: []*github.Label{
1080+
{Name: github.String("urgent")},
1081+
},
1082+
},
1083+
labels: []string{"high-priority", "urgent"},
1084+
expected: true,
1085+
expectError: false,
1086+
failMessage: "Should detect any of the target labels",
1087+
},
1088+
{
1089+
name: "nil PR",
1090+
pr: nil,
1091+
labels: []string{"high-priority"},
1092+
expected: false,
1093+
expectError: true,
1094+
failMessage: "Should return error for nil PR",
1095+
},
1096+
}
1097+
1098+
for _, tc := range tt {
1099+
t.Run(tc.name, func(t *testing.T) {
1100+
client := &GHClient{pr: tc.pr}
1101+
hasLabel, err := client.IsInLabels(tc.labels)
1102+
if tc.expectError {
1103+
if err == nil {
1104+
t.Error("Expected error but got none")
1105+
}
1106+
if _, ok := err.(*NoPRError); !ok {
1107+
t.Errorf("Expected NoPRError, got %T", err)
1108+
}
1109+
return
1110+
}
1111+
if err != nil {
1112+
t.Errorf("Unexpected error: %v", err)
1113+
}
1114+
if hasLabel != tc.expected {
1115+
t.Error(tc.failMessage)
1116+
}
1117+
})
1118+
}
1119+
}

main.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import (
1111
"testing"
1212
"time"
1313

14-
"github.com/multimediallc/codeowners-plus/internal/config"
14+
owners "github.com/multimediallc/codeowners-plus/internal/config"
1515
"github.com/multimediallc/codeowners-plus/internal/git"
16-
"github.com/multimediallc/codeowners-plus/internal/github"
16+
gh "github.com/multimediallc/codeowners-plus/internal/github"
1717
"github.com/multimediallc/codeowners-plus/pkg/codeowners"
18-
"github.com/multimediallc/codeowners-plus/pkg/functional"
18+
f "github.com/multimediallc/codeowners-plus/pkg/functional"
1919
)
2020

2121
// AppConfig holds the application configuration
@@ -214,6 +214,12 @@ func (a *App) processApprovalsAndReviewers() (bool, string, error) {
214214
if len(unapprovedOwners) > 0 {
215215
// Comment on the PR with the codeowner teams that have not approved the PR
216216
comment := allRequiredOwners.ToCommentString()
217+
hasHighPriority, err := a.client.IsInLabels(a.conf.HighPriorityLabels)
218+
if err != nil {
219+
fmt.Fprintf(WarningBuffer, "WARNING: Error checking high priority labels: %v\n", err)
220+
} else if hasHighPriority {
221+
comment += "\n❗High Prio❗"
222+
}
217223
if maxReviewsMet {
218224
comment += "\n\n"
219225
comment += "The PR has received the max number of required reviews. No further action is required."

main_test.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"time"
1010

1111
"github.com/google/go-github/v63/github"
12-
"github.com/multimediallc/codeowners-plus/internal/config"
12+
owners "github.com/multimediallc/codeowners-plus/internal/config"
1313
"github.com/multimediallc/codeowners-plus/internal/git"
14-
"github.com/multimediallc/codeowners-plus/internal/github"
14+
gh "github.com/multimediallc/codeowners-plus/internal/github"
1515
"github.com/multimediallc/codeowners-plus/pkg/codeowners"
1616
f "github.com/multimediallc/codeowners-plus/pkg/functional"
1717
)
@@ -262,6 +262,23 @@ func (m *mockGitHubClient) IsSubstringInComments(substring string, since *time.T
262262
return false, nil
263263
}
264264

265+
func (m *mockGitHubClient) IsInLabels(labels []string) (bool, error) {
266+
if m.pr == nil {
267+
return false, &gh.NoPRError{}
268+
}
269+
if len(labels) == 0 {
270+
return false, nil
271+
}
272+
for _, label := range m.pr.Labels {
273+
for _, targetLabel := range labels {
274+
if label.GetName() == targetLabel {
275+
return true, nil
276+
}
277+
}
278+
}
279+
return false, nil
280+
}
281+
265282
func init() {
266283
// Initialize test flags with default values
267284
flags = &Flags{

pkg/codeowners/codeowners_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"reflect"
66
"testing"
77

8-
"github.com/multimediallc/codeowners-plus/pkg/functional"
8+
f "github.com/multimediallc/codeowners-plus/pkg/functional"
99
)
1010

1111
func TestInitOwnerTree(t *testing.T) {

pkg/codeowners/reviewers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88

99
"github.com/bmatcuk/doublestar/v4"
10-
"github.com/multimediallc/codeowners-plus/pkg/functional"
10+
f "github.com/multimediallc/codeowners-plus/pkg/functional"
1111
)
1212

1313
var commentPrefix = "Codeowners approval required for this PR:\n"

0 commit comments

Comments
 (0)