Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions .github/actions/pr_notifier/pr_notifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Script for collecting PRs in need of review, and informing maintainers via
# slack

from __future__ import print_function

import datetime
import os
import sys

import github
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

MAINTAINERS = {
'alyssawilk': 'U78RP48V9',
'mattklein123': 'U5CALEVSL',
'lizan': 'U79E51EQ6',
'snowp': 'U93KTPQP6',
'ggreenway': 'U78MBV869',
'htuch': 'U78E7055Z',
'zuercher': 'U78J72Q82',
'phlax': 'U017PLM0GNQ',
'jmarantz': 'U80HPLBPG',
'antoniovicente': 'UKVNCQ212',
'junr03': 'U79K0Q431',
'wrowe': 'UBQR8NGBS',
'yanavlasov': 'UJHLR5KFS',
'asraa': 'UKZKCFRTP',
}


def get_slo_hours():
# on Monday, allow for 24h + 48h
if datetime.date.today().weekday() == 0:
return 72
return 24


# Return true if the PR has a waiting tag, false otherwise.
def is_waiting(labels):
for label in labels:
if label.name == 'waiting':
return True
return False


# Generate a pr message, bolding the time if it's out-SLO
def pr_message(pr_age, pr_url, pr_title, delta_days, delta_hours):
if pr_age < datetime.timedelta(hours=get_slo_hours()):
return "<%s|%s> has been waiting %s days %s hours\n" % (
pr_url, pr_title, delta_days, delta_hours)
else:
return "<%s|%s> has been waiting *%s days %s hours*\n" % (
pr_url, pr_title, delta_days, delta_hours)


# Adds reminder lines to the appropriate maintainer to review the assigned PRs
def add_reminders(assignees, maintainers_and_prs, message):
has_maintainer_assignee = False
for assignee_info in assignees:
assignee = assignee_info.login
if assignee not in MAINTAINERS:
continue
has_maintainer_assignee = True
if assignee not in maintainers_and_prs.keys():
maintainers_and_prs[
assignee] = "Hello, %s, here are your PR reminders for the day \n" % assignee
maintainers_and_prs[assignee] = maintainers_and_prs[assignee] + message
return has_maintainer_assignee


def track_prs():
git = github.Github()
repo = git.get_repo('envoyproxy/envoy')

# The list of PRs which are not waiting, but are well within review SLO
recent_prs = []
# A dict of maintainer : outstanding_pr_string to be sent to slack
maintainers_and_prs = {}
# A placeholder for unassigned PRs, to be sent to #maintainers eventually
maintainers_and_prs['unassigned'] = ""
# Out-SLO PRs to be sent to #envoy-maintainer-oncall
stalled_prs = ""

# Snag all PRs, including drafts
for pr_info in repo.get_pulls("open", "updated", "desc"):
# If the PR is waiting, continue.
if is_waiting(pr_info.labels):
continue

# Update the time based on the time zone delta from github's
pr_age = pr_info.updated_at - datetime.timedelta(hours=4)
delta = datetime.datetime.now() - pr_age
delta_days = delta.days
delta_hours = delta.seconds // 3600

# If we get to this point, the review may be in SLO - nudge if it's in
# SLO, nudge in bold if not.
message = pr_message(delta, pr_info.html_url, pr_info.title, delta_days, delta_hours)

# If the PR has been out-SLO for over a day, inform on-call
if delta > datetime.timedelta(hours=get_slo_hours() + 36):
stalled_prs = stalled_prs + message

# Add a reminder to each maintainer-assigner on the PR.
has_maintainer_assignee = add_reminders(pr_info.assignees, maintainers_and_prs, message)

# If there was no maintainer, track it as unassigned.
if not has_maintainer_assignee:
# don't bother assigning maintainer WIPs.
if pr_info.draft and pr_info.user.login in maintainers_and_prs.keys():
continue
maintainers_and_prs['unassigned'] = maintainers_and_prs['unassigned'] + message

# Return the dict of {maintainers : PR notifications}, and stalled PRs
return maintainers_and_prs, stalled_prs


def post_to_maintainers(client, maintainers_and_messages):
# Post updates to individual maintainers
for key in maintainers_and_messages:
message = maintainers_and_messages[key]

# Only send messages if we have the maintainer UID
# if key not in ['alyssawilk']: # Use this line for debugging.
if key not in MAINTAINERS:
# Right now we skip "unassigned" but eventually that should go to #maintainers
print("Skipping key %s " % key)
continue
uid = MAINTAINERS[key]

# Ship messages off to slack.
try:
print(maintainers_and_messages[key])
response = client.conversations_open(users=uid, text="hello")
channel_id = response["channel"]["id"]
response = client.chat_postMessage(channel=channel_id, text=message)
except SlackApiError as e:
print("Unexpected error %s", e.response["error"])


def post_to_oncall(client, unassigned_prs, out_slo_prs):
# Post updates to #envoy-maintainer-oncall
unassigned_prs = maintainers_and_messages['unassigned']
if unassigned_prs:
try:
response = client.chat_postMessage(
channel='#envoy-maintainer-oncall',
text=("*'Unassigned' PRs* (PRs with no maintainer assigned)\n%s" % unassigned_prs))
# TODO(alyssawilk) once maintainers start /wait tagging, uncomment this
#response = client.chat_postMessage(
# channel='#envoy-maintainer-oncall', text=("*Stalled PRs*\n\n%s" % out_slo_prs))
except SlackApiError as e:
print("Unexpected error %s", e.response["error"])


if __name__ == '__main__':
SLACK_BOT_TOKEN = os.getenv('SLACK_BOT_TOKEN')
if not SLACK_BOT_TOKEN:
print(
'Missing SLACK_BOT_TOKEN: please export token from https://api.slack.com/apps/A023NPQQ33K/oauth?'
)
sys.exit(1)

maintainers_and_messages, stalled_prs = track_prs()

client = WebClient(token=SLACK_BOT_TOKEN)
post_to_maintainers(client, maintainers_and_messages)
post_to_oncall(client, maintainers_and_messages['unassigned'], stalled_prs)
162 changes: 162 additions & 0 deletions .github/actions/pr_notifier/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --generate-hashes tools/dependency/requirements.txt
#
slack_sdk==3.5.1 \
--hash=sha256:8ceb46f7989d2ba1694dec8555dbda7f750b9216e453da4ad3e32a498bc9ea9a
aiohttp==3.5.3 \
--hash=sha256:7967b760d0e96eb7ac6b20a9143112ce5c03a00b4f74d8a5ee66c8e88e6b6800
async-timeout==3.0.1 \
--hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3
attrs==21.2.0 \
--hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1
certifi==2020.12.5 \
--hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \
--hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830
# via
# -r tools/dependency/requirements.txt
# requests
cffi==1.14.5 \
--hash=sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813 \
--hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \
--hash=sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea \
--hash=sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee \
--hash=sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396 \
--hash=sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73 \
--hash=sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315 \
--hash=sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1 \
--hash=sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49 \
--hash=sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892 \
--hash=sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482 \
--hash=sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058 \
--hash=sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5 \
--hash=sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53 \
--hash=sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045 \
--hash=sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3 \
--hash=sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5 \
--hash=sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e \
--hash=sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c \
--hash=sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369 \
--hash=sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827 \
--hash=sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053 \
--hash=sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa \
--hash=sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4 \
--hash=sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322 \
--hash=sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132 \
--hash=sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62 \
--hash=sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa \
--hash=sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0 \
--hash=sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396 \
--hash=sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e \
--hash=sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991 \
--hash=sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6 \
--hash=sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1 \
--hash=sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406 \
--hash=sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d \
--hash=sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c
# via pynacl
chardet==3.0.4 \
--hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
deprecated==1.2.12 \
--hash=sha256:08452d69b6b5bc66e8330adde0a4f8642e969b9e1702904d137eeb29c8ffc771 \
--hash=sha256:6d2de2de7931a968874481ef30208fd4e08da39177d61d3d4ebdf4366e7dbca1
# via
# -r tools/dependency/requirements.txt
# pygithub
idna==2.10 \
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
--hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
# via
# -r tools/dependency/requirements.txt
# requests
multidict==4.7.6 \
--hash=sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430
pycparser==2.20 \
--hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \
--hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705
# via cffi
pygithub==1.55 \
--hash=sha256:1bbfff9372047ff3f21d5cd8e07720f3dbfdaf6462fcaed9d815f528f1ba7283 \
--hash=sha256:2caf0054ea079b71e539741ae56c5a95e073b81fa472ce222e81667381b9601b
# via -r tools/dependency/requirements.txt
pyjwt==2.1.0 \
--hash=sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1 \
--hash=sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130
# via pygithub
pynacl==1.4.0 \
--hash=sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4 \
--hash=sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4 \
--hash=sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574 \
--hash=sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d \
--hash=sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634 \
--hash=sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25 \
--hash=sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f \
--hash=sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505 \
--hash=sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122 \
--hash=sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7 \
--hash=sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420 \
--hash=sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f \
--hash=sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96 \
--hash=sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6 \
--hash=sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6 \
--hash=sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514 \
--hash=sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff \
--hash=sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80
# via pygithub
pyyaml==5.4.1 \
--hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
--hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
--hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \
--hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \
--hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \
--hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \
--hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \
--hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \
--hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \
--hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \
--hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e \
--hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \
--hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \
--hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \
--hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \
--hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \
--hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \
--hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \
--hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \
--hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \
--hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \
--hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \
--hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \
--hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \
--hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \
--hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
--hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \
--hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \
--hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0
# via -r tools/dependency/requirements.txt
requests==2.25.1 \
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
# via
# -r tools/dependency/requirements.txt
# pygithub
six==1.16.0 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926
# via pynacl
urllib3==1.26.4 \
--hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \
--hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937
# via
# -r tools/dependency/requirements.txt
# requests
wrapt==1.12.1 \
--hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7
# via
# -r tools/dependency/requirements.txt
# deprecated
yarl==1.6.3 \
--hash=sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4

6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
version: 2
updates:

- package-ecosystem: "pip"
directory: "/.github/actions/pr_notifier"
schedule:
interval: "daily"

- package-ecosystem: "pip"
directory: "/test/extensions/filters/network/thrift_proxy"
schedule:
Expand Down Expand Up @@ -85,3 +90,4 @@ updates:
directory: "/.devcontainer"
schedule:
interval: daily

25 changes: 25 additions & 0 deletions .github/workflows/pr_notifier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
on:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would recommend leaving the manual trigger option that I had in the example. This will allow you to manually trigger it from the UI to test it and also if for whatever reason the run fails and we want to run it.

workflow_dispatch:
schedule:
- cron: '0 5 * * 1,2,3,4,5'

jobs:
pr_notifier:
name: PR Notifier
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: '3.8'
architecture: 'x64'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r ./.github/actions/pr_notifier/requirements.txt
- name: Notify about PRs
run: python ./.github/actions/pr_notifier/pr_notifier.py
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}