Skip to content

Commit 854d5b5

Browse files
SlashgearRoRoJ
andauthored
ci(reviewBot): implement review bot in python to verify reviews (#3235)
ci(reviewBot): implement review bot in python to verify reviews Co-authored-by: Antoine Caron <[email protected]> Co-authored-by: Rowena <[email protected]> Co-authored-by: Rowena Jones <[email protected]>
1 parent f482336 commit 854d5b5

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

.github/workflows/docs-review-bot.yml

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: 📖 Review Documentation Bot
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
dryRun:
7+
description: 'Run the bot without sending notifications'
8+
required: false
9+
default: 'false'
10+
11+
schedule:
12+
- cron: '0 8 * * 1'
13+
14+
env:
15+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
16+
DRY_RUN: false
17+
18+
jobs:
19+
review-docs:
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Checkout repository
24+
uses: actions/checkout@v2
25+
26+
- name: Set up Python
27+
uses: actions/setup-python@v2
28+
with:
29+
python-version: '3.x'
30+
31+
- name: Install dependencies
32+
run: |
33+
python -m pip install --upgrade pip
34+
pip install -r requirements.txt
35+
36+
- name: Run Documentation Review Bot in dry run mode
37+
if: github.event_name == 'workflow_dispatch' && github.event.inputs.dryRun == 'true'
38+
env:
39+
DRY_RUN: true
40+
run: |
41+
python bin/check-review-dates.py
42+
43+
- name: Run Documentation Review Bot in normal mode
44+
if: github.event_name != 'workflow_dispatch' || github.event.inputs.dryRun != 'true'
45+
run: |
46+
python bin/check-review-dates.py

bin/check-review-dates.py

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import os
2+
import logging
3+
from slack_sdk import WebClient
4+
from datetime import timedelta, date, datetime
5+
6+
DEFAULT_VAL_FREQ = 6
7+
8+
def convert_to_date_and_delta(val_date, val_freq):
9+
"Converts validation date string to datetime and validation frequency string (months) to timedelta."
10+
try:
11+
val_date_conv = datetime.strptime(val_date.rstrip('\n'), '%Y-%m-%d').date()
12+
val_freq_conv = timedelta(days=int(val_freq) * 30.4)
13+
return val_date_conv, val_freq_conv
14+
except ValueError:
15+
# handles the case where validation format is incorrect
16+
return None, None
17+
18+
def needs_review(val_date, val_freq):
19+
"Returns true if doc needs to be reviewed, based on val date and frequency"
20+
val_date_conv, val_freq_conv = convert_to_date_and_delta(val_date, val_freq)
21+
if val_date_conv is None or val_freq_conv is None:
22+
return False
23+
today = date.today()
24+
# calculate how long since doc was reviewed, in days
25+
delta = today - val_date_conv
26+
# return true or false depending on evaluation of data
27+
return delta >= val_freq_conv
28+
29+
def extract_metadata(filepath):
30+
"Extracts validation date and validation frequency from a document."
31+
with open(filepath) as doc:
32+
meta_limiters = 0
33+
has_val_date = False
34+
val_freq = DEFAULT_VAL_FREQ
35+
36+
for line in doc:
37+
if "validation: " in line:
38+
val_date = line.split(": ", 1)[1].strip()
39+
has_val_date = True
40+
if "validation-frequency:" in line:
41+
val_freq = line.split(": ", 1)[1].strip()
42+
if "---" in line:
43+
meta_limiters += 1
44+
# once two --- strings are found, it is the end of the meta section, stop checking file
45+
if meta_limiters >= 2:
46+
break
47+
48+
return has_val_date, val_date if has_val_date else None, val_freq
49+
50+
def process_files(directory):
51+
"Processes files in the content directory to check for those needing review."
52+
print("Processing files to check for those needing review")
53+
docs_to_review=[]
54+
for subdir, dirs, files in os.walk(directory):
55+
for file in files:
56+
filepath = os.path.join(subdir, file)
57+
if filepath.endswith(".mdx"):
58+
has_val_date, val_date, val_freq = extract_metadata(filepath)
59+
if has_val_date and needs_review(val_date, val_freq):
60+
docs_to_review.append(filepath)
61+
return docs_to_review
62+
63+
def get_doc_cat_name(filepath):
64+
"Returns a document-to-review's category and tidied-up filepath, based on its raw filepath."
65+
trimmed_filepath = filepath[2:-4]
66+
filepath_list = trimmed_filepath.split("/")
67+
68+
if filepath_list[0] == "tutorials":
69+
category = filepath_list[0]
70+
elif filepath_list[0] == "faq":
71+
category = filepath_list[1]
72+
else:
73+
category = ' '.join(filepath_list[0:2])
74+
75+
return category, trimmed_filepath
76+
77+
def organize_docs_by_category(docs_to_review):
78+
"Organizes docs to review by category into a dictionary."
79+
print("Organizing docs by category")
80+
dict_by_cat = {}
81+
82+
for filepath in docs_to_review:
83+
category, trimmed_filepath = get_doc_cat_name(filepath)
84+
85+
if category not in dict_by_cat:
86+
dict_by_cat[category] = [trimmed_filepath]
87+
else:
88+
dict_by_cat[category].append(trimmed_filepath)
89+
90+
# sort the dictionary alphabetically by category
91+
dict_by_cat_sorted = {key: value for key, value in sorted(dict_by_cat.items())}
92+
93+
return dict_by_cat_sorted
94+
95+
def prep_message(docs_to_review_by_cat):
96+
"Prepares the message to sent to the Slack channel, containing the docs to review"
97+
print("Preparing message")
98+
message = ":wave: Hi doc team, here are some docs to review: \n \n"
99+
100+
for key in docs_to_review_by_cat:
101+
message += "*" + key.title() + "*" + "\n"
102+
for doc in docs_to_review_by_cat[key]:
103+
message += doc + "\n"
104+
message += "\n"
105+
print(message)
106+
return(message)
107+
108+
def send_message(message):
109+
"Sends the message containing docs to review to the Slack channel"
110+
print("Sending message")
111+
client = WebClient(token=os.environ['SLACK_BOT_TOKEN'])
112+
client.chat_postMessage(
113+
channel = "#review-doc",
114+
text = message,
115+
username = "DocReviewBot"
116+
)
117+
118+
def main():
119+
docs_to_review = process_files(".")
120+
docs_to_review_by_cat = organize_docs_by_category(docs_to_review)
121+
message = prep_message(docs_to_review_by_cat)
122+
if os.environ.get("DRY_RUN") != "true":
123+
send_message(message)
124+
125+
if __name__ == "__main__":
126+
main()

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
slack_sdk == 3.27.2

0 commit comments

Comments
 (0)