From 01eccf76c630ff4a406403b98255d21d685a0723 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Thu, 2 May 2024 18:19:12 -0700 Subject: [PATCH 01/20] Start creating cmd files for stale and prs needing CI --- cmd/notifyStale.go | 46 ++++++++++++++++++++++++++++++++ internal/github/checkStalePRs.go | 44 ++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 cmd/notifyStale.go create mode 100644 internal/github/checkStalePRs.go diff --git a/cmd/notifyStale.go b/cmd/notifyStale.go new file mode 100644 index 0000000..120be9c --- /dev/null +++ b/cmd/notifyStale.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "log" + "time" + + "github.com/google/go-github/v60/github" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/chia-network/github-bot/internal/config" + github2 "github.com/chia-network/github-bot/internal/github" +) + +var notifyStaleCmd = &cobra.Command{ + Use: "notify-stale", + Short: "Sends a Keybase message to those ", + Run: func(cmd *cobra.Command, args []string) { + cfg, err := config.LoadConfig(viper.GetString("config")) + if err != nil { + log.Fatalf("error loading config: %s\n", err.Error()) + } + client := github.NewClient(nil).WithAuthToken(cfg.GithubToken) + + loop := viper.GetBool("loop") + loopDuration := viper.GetDuration("loop-time") + for { + log.Println("Checking for community PRs that have no update in the last 7 days") + err = github2.CheckStalePRs(client, cfg.InternalTeam, cfg.LabelConfig) + if err != nil { + log.Fatalln(err.Error()) + } + + if !loop { + break + } + + log.Printf("Waiting %s for next iteration\n", loopDuration.String()) + time.Sleep(loopDuration) + } + }, +} + +func init() { + rootCmd.AddCommand(notifyStaleCmd) +} diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go new file mode 100644 index 0000000..3b33a73 --- /dev/null +++ b/internal/github/checkStalePRs.go @@ -0,0 +1,44 @@ +package github + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/google/go-github/v60/github" + + "github.com/chia-network/github-bot/internal/config" +) + +func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]*github.PullRequest, error) { + var stalePRs []*github.PullRequest + cutoffDate := time.Now().AddDate(0, 0, -7) // 7 days ago + + for _, fullRepo := range cfg.LabelCheckRepos { + log.Println("Checking repository:", fullRepo.Name) + parts := strings.Split(fullRepo.Name, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) + } + owner, repo := parts[0], parts[1] + teamMembers, err := GetTeamMemberList(githubClient, internalTeam) + if err != nil { + return nil, err + } + + communityPRs, err := FindCommunityPRs(owner, repo, teamMembers, githubClient) + if err != nil { + return nil, err + } + + for _, pr := range communityPRs { + if pr.UpdatedAt.Before(cutoffDate) { + stalePRs = append(stalePRs, pr) + } + } + } + return stalePRs, nil +} + +// Take the list of PRs and send a message to a keybase channel From f6f5378c77f4eb7fec0555fadf011a6744d1f818 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Fri, 3 May 2024 11:56:30 -0700 Subject: [PATCH 02/20] Add additional commands for checking stale and pending PRs --- cmd/notifyPendingCI.go | 46 +++++++++++++++++++++++++++++++ cmd/notifyStale.go | 4 +-- go.mod | 16 +++++++++++ go.sum | 45 ++++++++++++++++++++++++++++-- internal/github/checkPendingCI.go | 43 +++++++++++++++++++++++++++++ internal/github/checkStalePRs.go | 36 ++++++++++++++++++++---- 6 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 cmd/notifyPendingCI.go create mode 100644 internal/github/checkPendingCI.go diff --git a/cmd/notifyPendingCI.go b/cmd/notifyPendingCI.go new file mode 100644 index 0000000..24b2f41 --- /dev/null +++ b/cmd/notifyPendingCI.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "log" + "time" + + "github.com/google/go-github/v60/github" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/chia-network/github-bot/internal/config" + github2 "github.com/chia-network/github-bot/internal/github" +) + +var notifyPendingCICmd = &cobra.Command{ + Use: "notify-pendingci", + Short: "Sends a Keybase message to a channel, alerting that a community PR is ready for CI to run", + Run: func(cmd *cobra.Command, args []string) { + cfg, err := config.LoadConfig(viper.GetString("config")) + if err != nil { + log.Fatalf("error loading config: %s\n", err.Error()) + } + client := github.NewClient(nil).WithAuthToken(cfg.GithubToken) + + loop := viper.GetBool("loop") + loopDuration := viper.GetDuration("loop-time") + for { + log.Println("Checking for community PRs that are waiting for CI to run") + _, err = github2.CheckForPendingCI(client, cfg.InternalTeam, cfg.LabelConfig) + if err != nil { + log.Fatalln(err.Error()) + } + + if !loop { + break + } + + log.Printf("Waiting %s for next iteration\n", loopDuration.String()) + time.Sleep(loopDuration) + } + }, +} + +func init() { + rootCmd.AddCommand(notifyPendingCICmd) +} diff --git a/cmd/notifyStale.go b/cmd/notifyStale.go index 120be9c..89b8f88 100644 --- a/cmd/notifyStale.go +++ b/cmd/notifyStale.go @@ -14,7 +14,7 @@ import ( var notifyStaleCmd = &cobra.Command{ Use: "notify-stale", - Short: "Sends a Keybase message to those ", + Short: "Sends a Keybase message to a channel, alerting that a community PR has not been updated in 7 days", Run: func(cmd *cobra.Command, args []string) { cfg, err := config.LoadConfig(viper.GetString("config")) if err != nil { @@ -26,7 +26,7 @@ var notifyStaleCmd = &cobra.Command{ loopDuration := viper.GetDuration("loop-time") for { log.Println("Checking for community PRs that have no update in the last 7 days") - err = github2.CheckStalePRs(client, cfg.InternalTeam, cfg.LabelConfig) + _, err = github2.CheckStalePRs(client, cfg.InternalTeam, cfg.LabelConfig) if err != nil { log.Fatalln(err.Error()) } diff --git a/go.mod b/go.mod index 992489e..b594bce 100644 --- a/go.mod +++ b/go.mod @@ -4,29 +4,45 @@ go 1.22.1 require ( github.com/google/go-github/v60 v60.0.0 + github.com/keybase/go-keybase-chat-bot v0.0.0-20231213202706-1bdf50c0adf4 + github.com/keybase/managed-bots v0.0.0-20231213214452-3abce5fddbf2 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 + golang.org/x/sync v0.7.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/aws/aws-sdk-go v1.28.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/keybase/go-codec v0.0.0-20180928230036-164397562123 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stathat/go v1.0.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index d67f4e0..13d67cd 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/aws/aws-sdk-go v1.28.1 h1:aWBD5EJrmGFuHFn9ZdaHqWWZGZYQ5Gzb3j9G0RppLpY= +github.com/aws/aws-sdk-go v1.28.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,7 +9,12 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8= @@ -18,6 +25,16 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/keybase/go-codec v0.0.0-20180928230036-164397562123 h1:yg56lYPqh9suJepqxOMd/liFgU/x+maRPiB30JNYykM= +github.com/keybase/go-codec v0.0.0-20180928230036-164397562123/go.mod h1:r/eVVWCngg6TsFV/3HuS9sWhDkAzGG8mXhiuYA+Z/20= +github.com/keybase/go-keybase-chat-bot v0.0.0-20231213202706-1bdf50c0adf4 h1:PlYdzv1QYDX8aYRJRa5Nz/CP7mqXcUn5kZww5+A3Oaw= +github.com/keybase/go-keybase-chat-bot v0.0.0-20231213202706-1bdf50c0adf4/go.mod h1:ebfhBYCricPJlrhrYBw6Z0kGDoa5vkeUgPCXS1RPp/4= +github.com/keybase/managed-bots v0.0.0-20231213214452-3abce5fddbf2 h1:4p3UBxQl8V2JZdw7I3wzZehOFohMAFwlvXlgh+QCYsM= +github.com/keybase/managed-bots v0.0.0-20231213214452-3abce5fddbf2/go.mod h1:gNFPVoN6ODeYjsQDV8p9RaXigwtAOebSeh0WCWQ6Aso= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -26,6 +43,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -50,6 +69,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stathat/go v1.0.0 h1:HFIS5YkyaI6tXu7JXIRRZBLRvYstdNZm034zcCeaybI= +github.com/stathat/go v1.0.0/go.mod h1:+9Eg2szqkcOGWv6gfheJmBBsmq9Qf5KDbzy8/aYYR0c= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -63,18 +84,38 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go new file mode 100644 index 0000000..e3e6b1e --- /dev/null +++ b/internal/github/checkPendingCI.go @@ -0,0 +1,43 @@ +package github + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/google/go-github/v60/github" + + "github.com/chia-network/github-bot/internal/config" +) + +func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]string, error) { + teamMembers, _ := GetTeamMemberList(githubClient, internalTeam) + var pendingPRs []string + for _, fullRepo := range cfg.LabelCheckRepos { + log.Println("Checking repository:", fullRepo.Name) + parts := strings.Split(fullRepo.Name, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) + } + owner := parts[0] + repo := parts[1] + + // Fetch community PRs using the FindCommunityPRs function + communityPRs, err := FindCommunityPRs(owner, repo, teamMembers, githubClient) + if err != nil { + return nil, err + } + + // Now check if they've been updated in the last 2 hours and are awaiting CI actions + cutoffTime := time.Now().Add(-2 * time.Hour) // 2 hours ago + + for _, pr := range communityPRs { + if pr.CreatedAt.After(cutoffTime) && !pr.GetMergeable() { + log.Printf("PR #%d by %s is pending CI actions since %v", pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) + pendingPRs = append(pendingPRs, pr.GetHTMLURL()) + } + } + } + return pendingPRs, nil +} diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index 3b33a73..e9314ac 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -1,6 +1,7 @@ package github import ( + "context" "fmt" "log" "strings" @@ -14,6 +15,10 @@ import ( func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]*github.PullRequest, error) { var stalePRs []*github.PullRequest cutoffDate := time.Now().AddDate(0, 0, -7) // 7 days ago + teamMembers, err := GetTeamMemberList(githubClient, internalTeam) + if err != nil { + return nil, err + } for _, fullRepo := range cfg.LabelCheckRepos { log.Println("Checking repository:", fullRepo.Name) @@ -22,10 +27,6 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. return nil, fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) } owner, repo := parts[0], parts[1] - teamMembers, err := GetTeamMemberList(githubClient, internalTeam) - if err != nil { - return nil, err - } communityPRs, err := FindCommunityPRs(owner, repo, teamMembers, githubClient) if err != nil { @@ -33,7 +34,7 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. } for _, pr := range communityPRs { - if pr.UpdatedAt.Before(cutoffDate) { + if isStale(githubClient, pr, teamMembers, cutoffDate) { stalePRs = append(stalePRs, pr) } } @@ -41,4 +42,29 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. return stalePRs, nil } +// Checks if a PR is stale based on the last update from team members +func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers map[string]bool, cutoffDate time.Time) bool { + // Retrieve the timeline for the PR to find the latest relevant event + listOptions := &github.ListOptions{PerPage: 100} + for { + // Note: As far as the GitHub API is concerned, every pull request is an issue, + // but not every issue is a pull request. + events, resp, err := githubClient.Issues.ListIssueTimeline(context.TODO(), pr.Base.Repo.Owner.GetLogin(), pr.Base.Repo.GetName(), pr.GetNumber(), listOptions) + if err != nil { + log.Printf("Failed to get timeline for PR #%d: %v", pr.GetNumber(), err) + break // Skip to next PR on error + } + for _, event := range events { + if event.Event != nil && *event.Event == "commented" && teamMembers[*event.Actor.Login] && event.CreatedAt.After(cutoffDate) { + return false // PR has been updated by a team member recently + } + } + if resp.NextPage == 0 { + break + } + listOptions.Page = resp.NextPage + } + return true // No recent updates by team members +} + // Take the list of PRs and send a message to a keybase channel From 6529935866141cb987184ab68b7d90dd5e1885a4 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Fri, 3 May 2024 12:02:30 -0700 Subject: [PATCH 03/20] Return a list of URLS instead of PR name --- internal/github/checkStalePRs.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index e9314ac..c93beba 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -12,8 +12,9 @@ import ( "github.com/chia-network/github-bot/internal/config" ) -func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]*github.PullRequest, error) { - var stalePRs []*github.PullRequest +// CheckstalePRs will return a list of PRs +func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]string, error) { + var stalePRUrls []string cutoffDate := time.Now().AddDate(0, 0, -7) // 7 days ago teamMembers, err := GetTeamMemberList(githubClient, internalTeam) if err != nil { @@ -35,11 +36,11 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. for _, pr := range communityPRs { if isStale(githubClient, pr, teamMembers, cutoffDate) { - stalePRs = append(stalePRs, pr) + stalePRUrls = append(stalePRUrls, pr.GetHTMLURL()) // Collecting URLs instead of PR objects } } } - return stalePRs, nil + return stalePRUrls, nil } // Checks if a PR is stale based on the last update from team members From 32d19c2fa6cb53c8358a04951bf4da916ebe9a2d Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Fri, 3 May 2024 13:03:48 -0700 Subject: [PATCH 04/20] Change the format of checkPendingCI --- internal/github/checkPendingCI.go | 31 +++++++++++++++++++++++++++---- internal/github/checkStalePRs.go | 2 +- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index e3e6b1e..09830a2 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -1,16 +1,19 @@ package github import ( + "context" "fmt" "log" + "strconv" "strings" "time" - "github.com/google/go-github/v60/github" + "github.com/google/go-github/v60/github" // Ensure your go-github library version matches "github.com/chia-network/github-bot/internal/config" ) +// CheckForPendingCI will return a list of PR URLs that are ready for CI to run but haven't started yet. func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]string, error) { teamMembers, _ := GetTeamMemberList(githubClient, internalTeam) var pendingPRs []string @@ -33,11 +36,31 @@ func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg con cutoffTime := time.Now().Add(-2 * time.Hour) // 2 hours ago for _, pr := range communityPRs { - if pr.CreatedAt.After(cutoffTime) && !pr.GetMergeable() { - log.Printf("PR #%d by %s is pending CI actions since %v", pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) - pendingPRs = append(pendingPRs, pr.GetHTMLURL()) + if pr.CreatedAt.After(cutoffTime) { + // Check if any CI runs have occurred using the new function + hasCIRuns, err := checkCIStatus(githubClient, owner, repo, pr.GetNumber()) + if err != nil { + log.Printf("Error checking CI status for PR #%d: %v", pr.GetNumber(), err) + continue // Proceed to the next PR in case of error + } + + // Only consider the PR pending if no CI runs have occurred yet + if !hasCIRuns { + log.Printf("PR #%d by %s is ready for CI since %v but no CI actions have started yet", pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) + pendingPRs = append(pendingPRs, pr.GetHTMLURL()) + } } } } return pendingPRs, nil } + +func checkCIStatus(client *github.Client, owner, repo string, prNumber int) (bool, error) { + checks, _, err := client.Checks.ListCheckRunsForRef(context.Background(), owner, repo, strconv.Itoa(prNumber), &github.ListCheckRunsOptions{}) + if err != nil { + return false, err + } + + hasCIRuns := checks.GetTotal() > 0 + return hasCIRuns, nil +} diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index c93beba..0a17bd8 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -12,7 +12,7 @@ import ( "github.com/chia-network/github-bot/internal/config" ) -// CheckstalePRs will return a list of PRs +// CheckStalePRs will return a list of PR URLs that have not been updated in the last 7 days by internal team members. func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]string, error) { var stalePRUrls []string cutoffDate := time.Now().AddDate(0, 0, -7) // 7 days ago From 3f1850bc709d528c40e70b44ddf9e2fb76487508 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Fri, 3 May 2024 19:03:48 -0700 Subject: [PATCH 05/20] Run go mod tidy --- go.mod | 18 ++---------------- go.sum | 41 ++--------------------------------------- 2 files changed, 4 insertions(+), 55 deletions(-) diff --git a/go.mod b/go.mod index b594bce..b6917e5 100644 --- a/go.mod +++ b/go.mod @@ -4,45 +4,31 @@ go 1.22.1 require ( github.com/google/go-github/v60 v60.0.0 - github.com/keybase/go-keybase-chat-bot v0.0.0-20231213202706-1bdf50c0adf4 - github.com/keybase/managed-bots v0.0.0-20231213214452-3abce5fddbf2 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 - golang.org/x/sync v0.7.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/aws/aws-sdk-go v1.28.1 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/keybase/go-codec v0.0.0-20180928230036-164397562123 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stathat/go v1.0.0 // indirect - github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 13d67cd..e91242c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/aws/aws-sdk-go v1.28.1 h1:aWBD5EJrmGFuHFn9ZdaHqWWZGZYQ5Gzb3j9G0RppLpY= -github.com/aws/aws-sdk-go v1.28.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,12 +7,7 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8= @@ -25,18 +18,10 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/keybase/go-codec v0.0.0-20180928230036-164397562123 h1:yg56lYPqh9suJepqxOMd/liFgU/x+maRPiB30JNYykM= -github.com/keybase/go-codec v0.0.0-20180928230036-164397562123/go.mod h1:r/eVVWCngg6TsFV/3HuS9sWhDkAzGG8mXhiuYA+Z/20= -github.com/keybase/go-keybase-chat-bot v0.0.0-20231213202706-1bdf50c0adf4 h1:PlYdzv1QYDX8aYRJRa5Nz/CP7mqXcUn5kZww5+A3Oaw= -github.com/keybase/go-keybase-chat-bot v0.0.0-20231213202706-1bdf50c0adf4/go.mod h1:ebfhBYCricPJlrhrYBw6Z0kGDoa5vkeUgPCXS1RPp/4= -github.com/keybase/managed-bots v0.0.0-20231213214452-3abce5fddbf2 h1:4p3UBxQl8V2JZdw7I3wzZehOFohMAFwlvXlgh+QCYsM= -github.com/keybase/managed-bots v0.0.0-20231213214452-3abce5fddbf2/go.mod h1:gNFPVoN6ODeYjsQDV8p9RaXigwtAOebSeh0WCWQ6Aso= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -69,8 +54,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= -github.com/stathat/go v1.0.0 h1:HFIS5YkyaI6tXu7JXIRRZBLRvYstdNZm034zcCeaybI= -github.com/stathat/go v1.0.0/go.mod h1:+9Eg2szqkcOGWv6gfheJmBBsmq9Qf5KDbzy8/aYYR0c= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -84,38 +67,18 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From ffcb782ff68f1012c612e7d6c06abb9bc3db1e38 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Tue, 7 May 2024 16:19:06 -0700 Subject: [PATCH 06/20] Added more refinements to the checkPendingCI.go file --- cmd/notifyPendingCI.go | 2 +- cmd/notifyStale.go | 2 +- internal/config/config.go | 12 +++- internal/github/checkPendingCI.go | 91 +++++++++++++++++++++-------- internal/github/checkStalePRs.go | 28 +++------ internal/github/pullRequests.go | 50 +++++++++++----- internal/label/pullrequests.go | 85 ++++++++++++--------------- k8s/label-prs.yml.j2 | 1 - k8s/notify_stale_pending_prs.yml.j2 | 22 +++++++ 9 files changed, 179 insertions(+), 114 deletions(-) create mode 100644 k8s/notify_stale_pending_prs.yml.j2 diff --git a/cmd/notifyPendingCI.go b/cmd/notifyPendingCI.go index 24b2f41..91b9900 100644 --- a/cmd/notifyPendingCI.go +++ b/cmd/notifyPendingCI.go @@ -26,7 +26,7 @@ var notifyPendingCICmd = &cobra.Command{ loopDuration := viper.GetDuration("loop-time") for { log.Println("Checking for community PRs that are waiting for CI to run") - _, err = github2.CheckForPendingCI(client, cfg.InternalTeam, cfg.LabelConfig) + _, err = github2.CheckForPendingCI(client, cfg.InternalTeam, cfg.CheckStalePending) if err != nil { log.Fatalln(err.Error()) } diff --git a/cmd/notifyStale.go b/cmd/notifyStale.go index 89b8f88..aed0149 100644 --- a/cmd/notifyStale.go +++ b/cmd/notifyStale.go @@ -26,7 +26,7 @@ var notifyStaleCmd = &cobra.Command{ loopDuration := viper.GetDuration("loop-time") for { log.Println("Checking for community PRs that have no update in the last 7 days") - _, err = github2.CheckStalePRs(client, cfg.InternalTeam, cfg.LabelConfig) + _, err = github2.CheckStalePRs(client, cfg.InternalTeam, cfg.CheckStalePending) if err != nil { log.Fatalln(err.Error()) } diff --git a/internal/config/config.go b/internal/config/config.go index 3f3d753..e02608b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,9 +2,10 @@ package config // Config defines the config for all aspects of the bot type Config struct { - GithubToken string `yaml:"github_token"` - InternalTeam string `yaml:"internal_team"` - LabelConfig `yaml:",inline"` + GithubToken string `yaml:"github_token"` + InternalTeam string `yaml:"internal_team"` + LabelConfig `yaml:",inline"` + CheckStalePending `yaml:",inline"` } // LabelConfig is the configuration options specific to labeling PRs @@ -21,3 +22,8 @@ type CheckRepo struct { Name string `yaml:"name"` MinimumNumber int `yaml:"minimum_number"` } + +// CheckStalePending are config settings when checking a repo +type CheckStalePending struct { + CheckStalePending []CheckRepo `yaml:"check_stale_pending_repos"` +} diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index 09830a2..356e761 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -13,54 +13,97 @@ import ( "github.com/chia-network/github-bot/internal/config" ) -// CheckForPendingCI will return a list of PR URLs that are ready for CI to run but haven't started yet. -func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]string, error) { +// CheckForPendingCI returns a list of PR URLs that are ready for CI to run but haven't started yet. +func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg config.CheckStalePending) ([]string, error) { teamMembers, _ := GetTeamMemberList(githubClient, internalTeam) var pendingPRs []string - for _, fullRepo := range cfg.LabelCheckRepos { + + for _, fullRepo := range cfg.CheckStalePending { log.Println("Checking repository:", fullRepo.Name) parts := strings.Split(fullRepo.Name, "/") if len(parts) != 2 { return nil, fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) } - owner := parts[0] - repo := parts[1] + owner, repo := parts[0], parts[1] // Fetch community PRs using the FindCommunityPRs function - communityPRs, err := FindCommunityPRs(owner, repo, teamMembers, githubClient) + communityPRs, err := FindCommunityPRs(cfg.CheckStalePending, teamMembers, githubClient) if err != nil { return nil, err } - // Now check if they've been updated in the last 2 hours and are awaiting CI actions - cutoffTime := time.Now().Add(-2 * time.Hour) // 2 hours ago - for _, pr := range communityPRs { - if pr.CreatedAt.After(cutoffTime) { - // Check if any CI runs have occurred using the new function - hasCIRuns, err := checkCIStatus(githubClient, owner, repo, pr.GetNumber()) - if err != nil { - log.Printf("Error checking CI status for PR #%d: %v", pr.GetNumber(), err) - continue // Proceed to the next PR in case of error - } - - // Only consider the PR pending if no CI runs have occurred yet - if !hasCIRuns { - log.Printf("PR #%d by %s is ready for CI since %v but no CI actions have started yet", pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) - pendingPRs = append(pendingPRs, pr.GetHTMLURL()) - } + // Dynamic cutoff time based on the last commit to the PR + lastCommitTime, err := getLastCommitTime(githubClient, owner, repo, pr.GetNumber()) + if err != nil { + log.Printf("Error retrieving last commit time for PR #%d: %v", pr.GetNumber(), err) + continue + } + cutoffTime := lastCommitTime.Add(2 * time.Hour) // 2 hours after the last commit + + if time.Now().Before(cutoffTime) { + log.Printf("Skipping PR #%d as it's still within the 2-hour window from the last commit.", pr.GetNumber()) + continue + } + + hasCIRuns, err := checkCIStatus(githubClient, owner, repo, pr.GetNumber()) + if err != nil { + log.Printf("Error checking CI status for PR #%d: %v", pr.GetNumber(), err) + continue + } + + needsReApproval, err := checkForDismissedReviews(githubClient, owner, repo, pr.GetNumber()) + if err != nil { + log.Printf("Error checking review status for PR #%d: %v", pr.GetNumber(), err) + continue + } + + if !hasCIRuns || needsReApproval { + log.Printf("PR #%d by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) + pendingPRs = append(pendingPRs, pr.GetHTMLURL()) } } } return pendingPRs, nil } +func getLastCommitTime(client *github.Client, owner, repo string, prNumber int) (time.Time, error) { + commits, _, err := client.PullRequests.ListCommits(context.Background(), owner, repo, prNumber, nil) + if err != nil { + return time.Time{}, err // Properly handle API errors + } + if len(commits) == 0 { + return time.Time{}, fmt.Errorf("no commits found for PR #%d", prNumber) // Handle case where no commits are found + } + lastCommit := commits[len(commits)-1] + commitDate := lastCommit.GetCommit().GetAuthor().GetDate() // commitDate is of type Timestamp + + // Since GetDate() returns a Timestamp (not *Timestamp), use the address to call GetTime() + commitTime := commitDate.GetTime() // Correctly accessing GetTime(), which returns *time.Time + + if commitTime == nil { + return time.Time{}, fmt.Errorf("commit time is nil for PR #%d", prNumber) + } + return *commitTime, nil // Safely dereference *time.Time to get time.Time +} + func checkCIStatus(client *github.Client, owner, repo string, prNumber int) (bool, error) { checks, _, err := client.Checks.ListCheckRunsForRef(context.Background(), owner, repo, strconv.Itoa(prNumber), &github.ListCheckRunsOptions{}) if err != nil { return false, err } + return checks.GetTotal() > 0, nil +} - hasCIRuns := checks.GetTotal() > 0 - return hasCIRuns, nil +func checkForDismissedReviews(client *github.Client, owner, repo string, prNumber int) (bool, error) { + reviews, _, err := client.PullRequests.ListReviews(context.Background(), owner, repo, prNumber, nil) + if err != nil { + return false, err + } + for _, review := range reviews { + if review.GetState() == "DISMISSED" { + return true, nil + } + } + return false, err } diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index 0a17bd8..0550597 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -2,9 +2,7 @@ package github import ( "context" - "fmt" "log" - "strings" "time" "github.com/google/go-github/v60/github" @@ -13,31 +11,21 @@ import ( ) // CheckStalePRs will return a list of PR URLs that have not been updated in the last 7 days by internal team members. -func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) ([]string, error) { +func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.CheckStalePending) ([]string, error) { var stalePRUrls []string cutoffDate := time.Now().AddDate(0, 0, -7) // 7 days ago teamMembers, err := GetTeamMemberList(githubClient, internalTeam) if err != nil { return nil, err } + communityPRs, err := FindCommunityPRs(cfg.CheckStalePending, teamMembers, githubClient) + if err != nil { + return nil, err + } - for _, fullRepo := range cfg.LabelCheckRepos { - log.Println("Checking repository:", fullRepo.Name) - parts := strings.Split(fullRepo.Name, "/") - if len(parts) != 2 { - return nil, fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) - } - owner, repo := parts[0], parts[1] - - communityPRs, err := FindCommunityPRs(owner, repo, teamMembers, githubClient) - if err != nil { - return nil, err - } - - for _, pr := range communityPRs { - if isStale(githubClient, pr, teamMembers, cutoffDate) { - stalePRUrls = append(stalePRUrls, pr.GetHTMLURL()) // Collecting URLs instead of PR objects - } + for _, pr := range communityPRs { + if isStale(githubClient, pr, teamMembers, cutoffDate) { + stalePRUrls = append(stalePRUrls, pr.GetHTMLURL()) // Collecting URLs instead of PR objects } } return stalePRUrls, nil diff --git a/internal/github/pullRequests.go b/internal/github/pullRequests.go index 4befce8..4e6b053 100644 --- a/internal/github/pullRequests.go +++ b/internal/github/pullRequests.go @@ -3,38 +3,58 @@ package github import ( "context" "fmt" + "log" + "strings" "github.com/google/go-github/v60/github" + + "github.com/chia-network/github-bot/internal/config" ) -// FindCommunityPRs obtains non-teammember PRs -func FindCommunityPRs(owner string, repo string, teamMembers map[string]bool, githubClient *github.Client) ([]*github.PullRequest, error) { +// FindCommunityPRs obtains PRs based on provided filters +func FindCommunityPRs(repos []config.CheckRepo, teamMembers map[string]bool, githubClient *github.Client) ([]*github.PullRequest, error) { var finalPRs []*github.PullRequest opts := &github.PullRequestListOptions{ State: "open", Sort: "created", Direction: "desc", ListOptions: github.ListOptions{ - Page: 0, + Page: 1, PerPage: 100, }, } - for { - opts.ListOptions.Page++ - pullRequests, resp, err := githubClient.PullRequests.List(context.TODO(), owner, repo, opts) - if err != nil { - return finalPRs, fmt.Errorf("error listing pull requests: %w", err) + + for _, fullRepo := range repos { + log.Println("Checking repository:", fullRepo.Name) + parts := strings.Split(fullRepo.Name, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) } + owner, repo := parts[0], parts[1] - for _, pullRequest := range pullRequests { - user := *pullRequest.User.Login - if !teamMembers[user] { - finalPRs = append(finalPRs, pullRequest) + for { + pullRequests, resp, err := githubClient.PullRequests.List(context.TODO(), owner, repo, opts) + if err != nil { + return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", owner, repo, err) } - } - if resp.NextPage == 0 { - break + for _, pullRequest := range pullRequests { + if *pullRequest.Number < fullRepo.MinimumNumber { + continue + } + if *pullRequest.Draft { + continue + } + user := *pullRequest.User.Login + if !teamMembers[user] { + finalPRs = append(finalPRs, pullRequest) + } + } + + if resp.NextPage == 0 { + break // Exit the loop if there are no more pages + } + opts.Page = resp.NextPage // Set next page number } } return finalPRs, nil diff --git a/internal/label/pullrequests.go b/internal/label/pullrequests.go index 25d08d0..ac7dfff 100644 --- a/internal/label/pullrequests.go +++ b/internal/label/pullrequests.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "strings" "github.com/google/go-github/v60/github" @@ -13,64 +12,52 @@ import ( ) // PullRequests applies internal or community labels to pull requests -// Internal is determined by checking if the PR author is a member of the specified internalTeam func PullRequests(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) error { - teamMembers, _ := github2.GetTeamMemberList(githubClient, internalTeam) - for _, fullRepo := range cfg.LabelCheckRepos { - log.Println("checking repos") - parts := strings.Split(fullRepo.Name, "/") - if len(parts) != 2 { - return fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) + teamMembers, err := github2.GetTeamMemberList(githubClient, internalTeam) + if err != nil { + return fmt.Errorf("error getting team members: %w", err) // Properly handle and return error if team member list fetch fails + } + + pullRequests, err := github2.FindCommunityPRs(cfg.LabelCheckRepos, teamMembers, githubClient) + if err != nil { + return fmt.Errorf("error finding community PRs: %w", err) // Handle error from finding community PRs + } + + for _, pullRequest := range pullRequests { + user := *pullRequest.User.Login + if cfg.LabelSkipMap[user] { + continue } - owner := parts[0] - repo := parts[1] - pullRequests, err := github2.FindCommunityPRs(owner, repo, teamMembers, githubClient) - if err != nil { - return err + var label string + if teamMembers[user] { + label = cfg.LabelInternal + } else { + label = cfg.LabelExternal } - for _, pullRequest := range pullRequests { - if *pullRequest.Number < fullRepo.MinimumNumber { - continue - } - if *pullRequest.Draft { - continue - } - user := *pullRequest.User.Login - if cfg.LabelSkipMap[user] { - continue - } - var label string - if teamMembers[user] { - label = cfg.LabelInternal - } else { - label = cfg.LabelExternal + if label != "" { + log.Printf("Pull Request %d by %s will be labeled %s\n", *pullRequest.Number, user, label) + hasLabel := false + for _, existingLabel := range pullRequest.Labels { + if *existingLabel.Name == label { + log.Println("Already labeled, skipping...") + hasLabel = true + break + } } - if label != "" { - log.Printf("Pull Request %d by %s will be labeled %s\n", *pullRequest.Number, user, label) - hasLabel := false - for _, existingLabel := range pullRequest.Labels { - if *existingLabel.Name == label { - log.Println(" Already labeled, skipping...") - hasLabel = true - break - } + if !hasLabel { + allLabels := []string{label} + for _, labelP := range pullRequest.Labels { + allLabels = append(allLabels, *labelP.Name) } - - if !hasLabel { - allLabels := []string{label} - for _, labelP := range pullRequest.Labels { - allLabels = append(allLabels, *labelP.Name) - } - _, _, err := githubClient.Issues.AddLabelsToIssue(context.TODO(), owner, repo, *pullRequest.Number, allLabels) - if err != nil { - return fmt.Errorf("error adding labels to pull request %d: %w", *pullRequest.Number, err) - } + _, _, err := githubClient.Issues.AddLabelsToIssue(context.TODO(), *pullRequest.Base.Repo.Owner.Login, *pullRequest.Base.Repo.Name, *pullRequest.Number, allLabels) + if err != nil { + return fmt.Errorf("error adding labels to pull request %d: %w", *pullRequest.Number, err) // Ensure error from label adding is handled } } } } - return nil + return nil // Ensure that a nil is returned if the function completes without error } diff --git a/k8s/label-prs.yml.j2 b/k8s/label-prs.yml.j2 index 61ee1f4..bef6d15 100644 --- a/k8s/label-prs.yml.j2 +++ b/k8s/label-prs.yml.j2 @@ -29,4 +29,3 @@ secretFile: label_skip_users: - "dependabot[bot]" - "github-actions[bot]" - diff --git a/k8s/notify_stale_pending_prs.yml.j2 b/k8s/notify_stale_pending_prs.yml.j2 new file mode 100644 index 0000000..46e08fe --- /dev/null +++ b/k8s/notify_stale_pending_prs.yml.j2 @@ -0,0 +1,22 @@ +replicaCount: 1 +image: + repository: ghcr.io/chia-network/github-bot + tag: {{ DOCKER_TAG }} + +deployment: + args: + - label-prs + - --loop + +# Creates a secret with the following values, and mounts as a file into the main deployment container +secretFile: + mountPath: "/config" + stringValues: + config.yml: | + github_token: "{{ BOT_GITHUB_TOKEN }}" + internal_team: "{{ INTERNAL_TEAM_NAME }}" + check_stale_pending_repos: + - name: "Chia-Network/chia-blockchain" + minimum_number: 17788 + - name: "Chia-Network/chia-blockchain-gui" + minimum_number: 2300 From 6310d0a5552e8729a5f415ffd4ddd7c0619d1ee5 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Wed, 8 May 2024 06:38:49 -0700 Subject: [PATCH 07/20] Fix error handling for CheckStalePrs and include a check for if a PR has been reviewed. --- internal/github/checkStalePRs.go | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index 0550597..7feaa5d 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -2,6 +2,7 @@ package github import ( "context" + "fmt" "log" "time" @@ -24,28 +25,36 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. } for _, pr := range communityPRs { - if isStale(githubClient, pr, teamMembers, cutoffDate) { - stalePRUrls = append(stalePRUrls, pr.GetHTMLURL()) // Collecting URLs instead of PR objects + stale, err := isStale(githubClient, pr, teamMembers, cutoffDate) // Handle both returned values + if err != nil { + log.Printf("Error checking if PR is stale: %v", err) // Log or handle the error + continue // Skip this PR or handle the error appropriately + } + if stale { + stalePRUrls = append(stalePRUrls, pr.GetHTMLURL()) // Append if PR is confirmed stale } } return stalePRUrls, nil } // Checks if a PR is stale based on the last update from team members -func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers map[string]bool, cutoffDate time.Time) bool { - // Retrieve the timeline for the PR to find the latest relevant event +func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers map[string]bool, cutoffDate time.Time) (bool, error) { + // Set up a context with a timeout to control all operations within this function + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() // Ensure resources are cleaned up correctly after the function exits + listOptions := &github.ListOptions{PerPage: 100} for { - // Note: As far as the GitHub API is concerned, every pull request is an issue, - // but not every issue is a pull request. - events, resp, err := githubClient.Issues.ListIssueTimeline(context.TODO(), pr.Base.Repo.Owner.GetLogin(), pr.Base.Repo.GetName(), pr.GetNumber(), listOptions) + events, resp, err := githubClient.Issues.ListIssueTimeline(ctx, pr.Base.Repo.Owner.GetLogin(), pr.Base.Repo.GetName(), pr.GetNumber(), listOptions) if err != nil { - log.Printf("Failed to get timeline for PR #%d: %v", pr.GetNumber(), err) - break // Skip to next PR on error + return false, fmt.Errorf("failed to get timeline for PR #%d: %w", pr.GetNumber(), err) } for _, event := range events { - if event.Event != nil && *event.Event == "commented" && teamMembers[*event.Actor.Login] && event.CreatedAt.After(cutoffDate) { - return false // PR has been updated by a team member recently + if event.Event == nil || event.Actor == nil || event.Actor.Login == nil { + continue + } + if (*event.Event == "commented" || *event.Event == "reviewed") && teamMembers[*event.Actor.Login] && event.CreatedAt.After(cutoffDate) { + return false, nil } } if resp.NextPage == 0 { @@ -53,7 +62,7 @@ func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers ma } listOptions.Page = resp.NextPage } - return true // No recent updates by team members + return true, nil } // Take the list of PRs and send a message to a keybase channel From 697cf28df4840d4ebb187eb9cd144aa0cd74e908 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Wed, 8 May 2024 09:33:20 -0700 Subject: [PATCH 08/20] Change the cutoffDate time for checkStalePRs.go --- internal/github/checkStalePRs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index 7feaa5d..a86fe3c 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -14,7 +14,7 @@ import ( // CheckStalePRs will return a list of PR URLs that have not been updated in the last 7 days by internal team members. func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config.CheckStalePending) ([]string, error) { var stalePRUrls []string - cutoffDate := time.Now().AddDate(0, 0, -7) // 7 days ago + cutoffDate := time.Now().Add(7 * 24 * time.Hour) // 7 days ago teamMembers, err := GetTeamMemberList(githubClient, internalTeam) if err != nil { return nil, err From 510e0f6b5157c303fa942baac4c92ac0f5d159e9 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Wed, 8 May 2024 10:09:02 -0700 Subject: [PATCH 09/20] Changed the isStale timeout to 300s --- internal/github/checkStalePRs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index a86fe3c..77ba0ec 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -40,7 +40,7 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. // Checks if a PR is stale based on the last update from team members func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers map[string]bool, cutoffDate time.Time) (bool, error) { // Set up a context with a timeout to control all operations within this function - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) defer cancel() // Ensure resources are cleaned up correctly after the function exits listOptions := &github.ListOptions{PerPage: 100} From 7bfd1c4712aeff64f8897824ba3caa760204211a Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Wed, 8 May 2024 10:20:30 -0700 Subject: [PATCH 10/20] Add the appropriate deployment args to the notify_stale_pending_prs.yml.j2 --- k8s/notify_stale_pending_prs.yml.j2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/k8s/notify_stale_pending_prs.yml.j2 b/k8s/notify_stale_pending_prs.yml.j2 index 46e08fe..10d84db 100644 --- a/k8s/notify_stale_pending_prs.yml.j2 +++ b/k8s/notify_stale_pending_prs.yml.j2 @@ -5,7 +5,8 @@ image: deployment: args: - - label-prs + - notify-pendingci + - notify-stale - --loop # Creates a secret with the following values, and mounts as a file into the main deployment container From fc6e0171812059570a99fc20fd80d0fe9791e67c Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Wed, 8 May 2024 10:28:31 -0700 Subject: [PATCH 11/20] Break out of the for loop when if *pullRequest.Number < fullRepo.MinimumNumber is true --- internal/github/pullRequests.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/github/pullRequests.go b/internal/github/pullRequests.go index 4e6b053..313faa2 100644 --- a/internal/github/pullRequests.go +++ b/internal/github/pullRequests.go @@ -40,7 +40,7 @@ func FindCommunityPRs(repos []config.CheckRepo, teamMembers map[string]bool, git for _, pullRequest := range pullRequests { if *pullRequest.Number < fullRepo.MinimumNumber { - continue + break } if *pullRequest.Draft { continue From b2b5421b39990fb6e39808c7d2d179e59f301ec6 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Wed, 8 May 2024 11:29:39 -0700 Subject: [PATCH 12/20] In the checkForDismissedReviews, the code changes to only check the status of the latest review --- internal/github/checkPendingCI.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index 356e761..6c31695 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -95,15 +95,19 @@ func checkCIStatus(client *github.Client, owner, repo string, prNumber int) (boo return checks.GetTotal() > 0, nil } +// If a new commit is made after an approval, any approval reviews become dismissed. func checkForDismissedReviews(client *github.Client, owner, repo string, prNumber int) (bool, error) { reviews, _, err := client.PullRequests.ListReviews(context.Background(), owner, repo, prNumber, nil) if err != nil { return false, err } - for _, review := range reviews { - if review.GetState() == "DISMISSED" { - return true, nil - } + if len(reviews) == 0 { + return false, err } + lastReview := reviews[len(reviews)-1] + if lastReview.GetState() == "DISMISSED" { + return true, nil + } + return false, err } From 990cd39716cbf324745ecaadf481ff8a80d16d02 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Thu, 9 May 2024 12:16:53 -0700 Subject: [PATCH 13/20] Changed the check for dismissed to include other states of the review --- internal/github/checkPendingCI.go | 43 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index 6c31695..4e64e41 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -52,13 +52,12 @@ func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg con continue } - needsReApproval, err := checkForDismissedReviews(githubClient, owner, repo, pr.GetNumber()) + teamMemberActivity, err := checkTeamMemberActivity(githubClient, owner, repo, pr.GetNumber(), teamMembers, lastCommitTime) if err != nil { - log.Printf("Error checking review status for PR #%d: %v", pr.GetNumber(), err) - continue + log.Printf("Error checking team member activity for PR #%d: %v", pr.GetNumber(), err) + continue // or handle the error as needed } - - if !hasCIRuns || needsReApproval { + if !hasCIRuns || !teamMemberActivity { log.Printf("PR #%d by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) pendingPRs = append(pendingPRs, pr.GetHTMLURL()) } @@ -95,19 +94,33 @@ func checkCIStatus(client *github.Client, owner, repo string, prNumber int) (boo return checks.GetTotal() > 0, nil } -// If a new commit is made after an approval, any approval reviews become dismissed. -func checkForDismissedReviews(client *github.Client, owner, repo string, prNumber int) (bool, error) { - reviews, _, err := client.PullRequests.ListReviews(context.Background(), owner, repo, prNumber, nil) +func checkTeamMemberActivity(client *github.Client, owner, repo string, prNumber int, teamMembers map[string]bool, lastCommitTime time.Time) (bool, error) { + comments, _, err := client.Issues.ListComments(context.Background(), owner, repo, prNumber, nil) if err != nil { - return false, err + return false, fmt.Errorf("failed to fetch comments: %w", err) } - if len(reviews) == 0 { - return false, err + + for _, comment := range comments { + if _, ok := teamMembers[comment.User.GetLogin()]; ok && comment.CreatedAt.After(lastCommitTime) { + // Check if the comment is after the last commit + return true, nil // Active and relevant participation + } } - lastReview := reviews[len(reviews)-1] - if lastReview.GetState() == "DISMISSED" { - return true, nil + + reviews, _, err := client.PullRequests.ListReviews(context.Background(), owner, repo, prNumber, nil) + if err != nil { + return false, fmt.Errorf("failed to fetch reviews: %w", err) + } + + for _, review := range reviews { + if _, ok := teamMembers[review.User.GetLogin()]; ok && review.SubmittedAt.After(lastCommitTime) { + switch review.GetState() { + case "DISMISSED", "CHANGES_REQUESTED", "COMMENTED": + // Check if the review is after the last commit and is in one of the specified states + return true, nil // Active and relevant participation found + } + } } - return false, err + return false, nil // No recent relevant activity from team members } From 803d648a92330977daf9433f28ee3c45cb0da05b Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Thu, 9 May 2024 12:21:47 -0700 Subject: [PATCH 14/20] Add comments --- internal/github/checkPendingCI.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index 4e64e41..94996bf 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -74,6 +74,7 @@ func getLastCommitTime(client *github.Client, owner, repo string, prNumber int) if len(commits) == 0 { return time.Time{}, fmt.Errorf("no commits found for PR #%d", prNumber) // Handle case where no commits are found } + // Requesting a list of commits will return the json list in descending order lastCommit := commits[len(commits)-1] commitDate := lastCommit.GetCommit().GetAuthor().GetDate() // commitDate is of type Timestamp @@ -117,7 +118,7 @@ func checkTeamMemberActivity(client *github.Client, owner, repo string, prNumber switch review.GetState() { case "DISMISSED", "CHANGES_REQUESTED", "COMMENTED": // Check if the review is after the last commit and is in one of the specified states - return true, nil // Active and relevant participation found + return true, nil } } } From 4325eaf4bf39f268900a0140952a57ba4fc341bd Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Mon, 13 May 2024 13:43:07 -0700 Subject: [PATCH 15/20] Add some details when printing errors --- cmd/notifyPendingCI.go | 7 +++++-- cmd/notifyStale.go | 6 ++++-- internal/github/checkPendingCI.go | 13 +++++++------ internal/github/checkStalePRs.go | 5 +++-- internal/label/pullrequests.go | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/cmd/notifyPendingCI.go b/cmd/notifyPendingCI.go index 91b9900..a339f3d 100644 --- a/cmd/notifyPendingCI.go +++ b/cmd/notifyPendingCI.go @@ -24,12 +24,15 @@ var notifyPendingCICmd = &cobra.Command{ loop := viper.GetBool("loop") loopDuration := viper.GetDuration("loop-time") + var listPendingPRs []string for { log.Println("Checking for community PRs that are waiting for CI to run") - _, err = github2.CheckForPendingCI(client, cfg.InternalTeam, cfg.CheckStalePending) + listPendingPRs, err = github2.CheckForPendingCI(client, cfg.InternalTeam, cfg.CheckStalePending) if err != nil { - log.Fatalln(err.Error()) + log.Printf("The following error occurred while obtaining a list of pending PRs: %s", err) + continue } + log.Printf("Pending PRs ready for CI: %v\n", listPendingPRs) if !loop { break diff --git a/cmd/notifyStale.go b/cmd/notifyStale.go index aed0149..5a84db8 100644 --- a/cmd/notifyStale.go +++ b/cmd/notifyStale.go @@ -24,13 +24,15 @@ var notifyStaleCmd = &cobra.Command{ loop := viper.GetBool("loop") loopDuration := viper.GetDuration("loop-time") + var listPendingPRs []string for { log.Println("Checking for community PRs that have no update in the last 7 days") _, err = github2.CheckStalePRs(client, cfg.InternalTeam, cfg.CheckStalePending) if err != nil { - log.Fatalln(err.Error()) + log.Printf("The following error occurred while obtaining a list of stale PRs: %s", err) + continue } - + log.Printf("Stale PRs: %v\n", listPendingPRs) if !loop { break } diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index 94996bf..c90e043 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -22,7 +22,8 @@ func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg con log.Println("Checking repository:", fullRepo.Name) parts := strings.Split(fullRepo.Name, "/") if len(parts) != 2 { - return nil, fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) + log.Printf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) + continue } owner, repo := parts[0], parts[1] @@ -36,29 +37,29 @@ func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg con // Dynamic cutoff time based on the last commit to the PR lastCommitTime, err := getLastCommitTime(githubClient, owner, repo, pr.GetNumber()) if err != nil { - log.Printf("Error retrieving last commit time for PR #%d: %v", pr.GetNumber(), err) + log.Printf("Error retrieving last commit time for PR #%d in %s %s: %v", pr.GetNumber(), owner, repo, err) continue } cutoffTime := lastCommitTime.Add(2 * time.Hour) // 2 hours after the last commit if time.Now().Before(cutoffTime) { - log.Printf("Skipping PR #%d as it's still within the 2-hour window from the last commit.", pr.GetNumber()) + log.Printf("Skipping PR #%d from %s %s repo as it's still within the 2-hour window from the last commit.", pr.GetNumber(), owner, repo) continue } hasCIRuns, err := checkCIStatus(githubClient, owner, repo, pr.GetNumber()) if err != nil { - log.Printf("Error checking CI status for PR #%d: %v", pr.GetNumber(), err) + log.Printf("Error checking CI status for PR #%d in %s %s: %v", pr.GetNumber(), owner, repo, err) continue } teamMemberActivity, err := checkTeamMemberActivity(githubClient, owner, repo, pr.GetNumber(), teamMembers, lastCommitTime) if err != nil { - log.Printf("Error checking team member activity for PR #%d: %v", pr.GetNumber(), err) + log.Printf("Error checking team member activity for PR #%d in %s %s: %v", pr.GetNumber(), owner, repo, err) continue // or handle the error as needed } if !hasCIRuns || !teamMemberActivity { - log.Printf("PR #%d by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) + log.Printf("PR #%d in %s %s by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", owner, repo, pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) pendingPRs = append(pendingPRs, pr.GetHTMLURL()) } } diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index 77ba0ec..1f21c82 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -25,10 +25,11 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. } for _, pr := range communityPRs { + repoName := pr.GetBase().GetRepo().GetFullName() // Get the full name of the repository stale, err := isStale(githubClient, pr, teamMembers, cutoffDate) // Handle both returned values if err != nil { - log.Printf("Error checking if PR is stale: %v", err) // Log or handle the error - continue // Skip this PR or handle the error appropriately + log.Printf("Error checking if PR in repo %s is stale: %v", repoName, err) + continue // Skip this PR or handle the error appropriately } if stale { stalePRUrls = append(stalePRUrls, pr.GetHTMLURL()) // Append if PR is confirmed stale diff --git a/internal/label/pullrequests.go b/internal/label/pullrequests.go index ac7dfff..25e159e 100644 --- a/internal/label/pullrequests.go +++ b/internal/label/pullrequests.go @@ -59,5 +59,5 @@ func PullRequests(githubClient *github.Client, internalTeam string, cfg config.L } } - return nil // Ensure that a nil is returned if the function completes without error + return nil } From 5690aedd4c96356eaf222b44e58ba88eaafdd1c5 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Mon, 13 May 2024 13:51:09 -0700 Subject: [PATCH 16/20] Fix a print line variable --- internal/github/checkPendingCI.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index c90e043..505f7b1 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -59,7 +59,7 @@ func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg con continue // or handle the error as needed } if !hasCIRuns || !teamMemberActivity { - log.Printf("PR #%d in %s %s by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", owner, repo, pr.GetNumber(), pr.User.GetLogin(), pr.CreatedAt) + log.Printf("PR #%d in %s %s by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", pr.GetNumber(), owner, repo, pr.User.GetLogin(), pr.CreatedAt) pendingPRs = append(pendingPRs, pr.GetHTMLURL()) } } From abe79abf8b012e0b153b14ef43129c05b5504025 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Mon, 13 May 2024 14:05:53 -0700 Subject: [PATCH 17/20] Add in a "/" between owner repo --- internal/github/checkPendingCI.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/github/checkPendingCI.go b/internal/github/checkPendingCI.go index 505f7b1..0bb4376 100644 --- a/internal/github/checkPendingCI.go +++ b/internal/github/checkPendingCI.go @@ -37,29 +37,29 @@ func CheckForPendingCI(githubClient *github.Client, internalTeam string, cfg con // Dynamic cutoff time based on the last commit to the PR lastCommitTime, err := getLastCommitTime(githubClient, owner, repo, pr.GetNumber()) if err != nil { - log.Printf("Error retrieving last commit time for PR #%d in %s %s: %v", pr.GetNumber(), owner, repo, err) + log.Printf("Error retrieving last commit time for PR #%d in %s/%s: %v", pr.GetNumber(), owner, repo, err) continue } cutoffTime := lastCommitTime.Add(2 * time.Hour) // 2 hours after the last commit if time.Now().Before(cutoffTime) { - log.Printf("Skipping PR #%d from %s %s repo as it's still within the 2-hour window from the last commit.", pr.GetNumber(), owner, repo) + log.Printf("Skipping PR #%d from %s/%s repo as it's still within the 2-hour window from the last commit.", pr.GetNumber(), owner, repo) continue } hasCIRuns, err := checkCIStatus(githubClient, owner, repo, pr.GetNumber()) if err != nil { - log.Printf("Error checking CI status for PR #%d in %s %s: %v", pr.GetNumber(), owner, repo, err) + log.Printf("Error checking CI status for PR #%d in %s/%s: %v", pr.GetNumber(), owner, repo, err) continue } teamMemberActivity, err := checkTeamMemberActivity(githubClient, owner, repo, pr.GetNumber(), teamMembers, lastCommitTime) if err != nil { - log.Printf("Error checking team member activity for PR #%d in %s %s: %v", pr.GetNumber(), owner, repo, err) + log.Printf("Error checking team member activity for PR #%d in %s/%s: %v", pr.GetNumber(), owner, repo, err) continue // or handle the error as needed } if !hasCIRuns || !teamMemberActivity { - log.Printf("PR #%d in %s %s by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", pr.GetNumber(), owner, repo, pr.User.GetLogin(), pr.CreatedAt) + log.Printf("PR #%d in %s/%s by %s is ready for CI since %v but no CI actions have started yet, or it requires re-approval.", pr.GetNumber(), owner, repo, pr.User.GetLogin(), pr.CreatedAt) pendingPRs = append(pendingPRs, pr.GetHTMLURL()) } } From 33670b63b356bdbf045b8052a7aa7acbe482ec74 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Tue, 14 May 2024 06:23:02 -0700 Subject: [PATCH 18/20] Transition to make the context for the isStale function per iteration of the loop --- internal/github/checkStalePRs.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index 1f21c82..3449cf0 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -40,14 +40,14 @@ func CheckStalePRs(githubClient *github.Client, internalTeam string, cfg config. // Checks if a PR is stale based on the last update from team members func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers map[string]bool, cutoffDate time.Time) (bool, error) { - // Set up a context with a timeout to control all operations within this function - ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) - defer cancel() // Ensure resources are cleaned up correctly after the function exits - listOptions := &github.ListOptions{PerPage: 100} for { + // Create a context for each request + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // 10 seconds timeout for each request + events, resp, err := githubClient.Issues.ListIssueTimeline(ctx, pr.Base.Repo.Owner.GetLogin(), pr.Base.Repo.GetName(), pr.GetNumber(), listOptions) if err != nil { + cancel() // Explicitly cancel the context when an error occurs return false, fmt.Errorf("failed to get timeline for PR #%d: %w", pr.GetNumber(), err) } for _, event := range events { @@ -55,9 +55,11 @@ func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers ma continue } if (*event.Event == "commented" || *event.Event == "reviewed") && teamMembers[*event.Actor.Login] && event.CreatedAt.After(cutoffDate) { + cancel() // Clean up the context when returning within the loop return false, nil } } + cancel() // Clean up the context at the end of the loop iteration if resp.NextPage == 0 { break } @@ -65,5 +67,3 @@ func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers ma } return true, nil } - -// Take the list of PRs and send a message to a keybase channel From 3b6a497f185d487f70861802b326642a37cd0336 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Tue, 14 May 2024 06:24:04 -0700 Subject: [PATCH 19/20] Fix the comment for the context in isStale --- internal/github/checkStalePRs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/github/checkStalePRs.go b/internal/github/checkStalePRs.go index 3449cf0..7a2f729 100644 --- a/internal/github/checkStalePRs.go +++ b/internal/github/checkStalePRs.go @@ -43,7 +43,7 @@ func isStale(githubClient *github.Client, pr *github.PullRequest, teamMembers ma listOptions := &github.ListOptions{PerPage: 100} for { // Create a context for each request - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // 10 seconds timeout for each request + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // 30 seconds timeout for each request events, resp, err := githubClient.Issues.ListIssueTimeline(ctx, pr.Base.Repo.Owner.GetLogin(), pr.Base.Repo.GetName(), pr.GetNumber(), listOptions) if err != nil { From 59c21f2f827c59c1b244c505938a55c4f34e9118 Mon Sep 17 00:00:00 2001 From: Patrick Maslana Date: Tue, 14 May 2024 10:06:38 -0700 Subject: [PATCH 20/20] Add a sleep to the notifyPendingCI and notifyStale files if getting a list fails --- cmd/notifyPendingCI.go | 1 + cmd/notifyStale.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/notifyPendingCI.go b/cmd/notifyPendingCI.go index a339f3d..8f8eceb 100644 --- a/cmd/notifyPendingCI.go +++ b/cmd/notifyPendingCI.go @@ -30,6 +30,7 @@ var notifyPendingCICmd = &cobra.Command{ listPendingPRs, err = github2.CheckForPendingCI(client, cfg.InternalTeam, cfg.CheckStalePending) if err != nil { log.Printf("The following error occurred while obtaining a list of pending PRs: %s", err) + time.Sleep(loopDuration) continue } log.Printf("Pending PRs ready for CI: %v\n", listPendingPRs) diff --git a/cmd/notifyStale.go b/cmd/notifyStale.go index 5a84db8..10a66bb 100644 --- a/cmd/notifyStale.go +++ b/cmd/notifyStale.go @@ -30,6 +30,7 @@ var notifyStaleCmd = &cobra.Command{ _, err = github2.CheckStalePRs(client, cfg.InternalTeam, cfg.CheckStalePending) if err != nil { log.Printf("The following error occurred while obtaining a list of stale PRs: %s", err) + time.Sleep(loopDuration) continue } log.Printf("Stale PRs: %v\n", listPendingPRs)