2023-08-04 19:32:25 +02:00
|
|
|
#!/usr/bin/env python3
|
2023-08-16 00:20:26 +10:00
|
|
|
# SPDX-FileCopyrightText: 2023 Blender Authors
|
2023-08-06 18:57:56 +10:00
|
|
|
#
|
2023-08-04 19:32:25 +02:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
# Generates the weekly report containing information on:
|
|
|
|
|
# - Pull Requests created,
|
|
|
|
|
# - Pull Requests revised,
|
|
|
|
|
# - Issues closed,
|
|
|
|
|
# - Issues confirmed,
|
|
|
|
|
# - Commits,
|
|
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
|
|
|
|
|
python ./weekly_report.py --username mano-wii
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import datetime
|
|
|
|
|
import json
|
|
|
|
|
import re
|
|
|
|
|
from gitea_utils import gitea_json_activities_get, gitea_json_issue_get, gitea_json_issue_events_filter, git_username_detect
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def argparse_create():
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description="Generate Weekly Report",
|
|
|
|
|
epilog="This script is typically used to help write weekly reports")
|
|
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--username",
|
|
|
|
|
dest="username",
|
|
|
|
|
metavar='USERNAME',
|
|
|
|
|
type=str,
|
|
|
|
|
required=False,
|
|
|
|
|
help="")
|
|
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-v",
|
|
|
|
|
"--verbose",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="increase output verbosity")
|
|
|
|
|
|
|
|
|
|
return parser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def report_personal_weekly_get(username, start, verbose=True):
|
|
|
|
|
|
|
|
|
|
data_cache = {}
|
|
|
|
|
|
|
|
|
|
def gitea_json_issue_get_cached(issue_fullname):
|
|
|
|
|
if issue_fullname not in data_cache:
|
|
|
|
|
data_cache[issue_fullname] = gitea_json_issue_get(issue_fullname)
|
|
|
|
|
|
|
|
|
|
return data_cache[issue_fullname]
|
|
|
|
|
|
|
|
|
|
pulls_closed = set()
|
|
|
|
|
pulls_commented = set()
|
|
|
|
|
pulls_created = set()
|
|
|
|
|
|
|
|
|
|
issues_closed = set()
|
|
|
|
|
issues_commented = set()
|
|
|
|
|
issues_created = set()
|
|
|
|
|
|
|
|
|
|
pulls_reviewed = []
|
|
|
|
|
|
|
|
|
|
issues_confirmed = []
|
|
|
|
|
issues_needing_user_info = []
|
|
|
|
|
issues_needing_developer_info = []
|
|
|
|
|
issues_fixed = []
|
|
|
|
|
issues_duplicated = []
|
|
|
|
|
issues_archived = []
|
|
|
|
|
|
|
|
|
|
commits_main = []
|
|
|
|
|
|
|
|
|
|
for i in range(7):
|
|
|
|
|
date_curr = start + datetime.timedelta(days=i)
|
|
|
|
|
date_curr_str = date_curr.strftime("%Y-%m-%d")
|
|
|
|
|
print(f"Requesting activity of {date_curr_str}", end="\r", flush=True)
|
|
|
|
|
for activity in gitea_json_activities_get(username, date_curr_str):
|
|
|
|
|
op_type = activity["op_type"]
|
|
|
|
|
if op_type == "close_issue":
|
|
|
|
|
fullname = activity["repo"]["full_name"] + "/issues/" + activity["content"].split('|')[0]
|
|
|
|
|
issues_closed.add(fullname)
|
|
|
|
|
elif op_type == "comment_issue":
|
|
|
|
|
fullname = activity["repo"]["full_name"] + "/issues/" + activity["content"].split('|')[0]
|
|
|
|
|
issues_commented.add(fullname)
|
|
|
|
|
elif op_type == "create_issue":
|
|
|
|
|
fullname = activity["repo"]["full_name"] + "/issues/" + activity["content"].split('|')[0]
|
|
|
|
|
issues_created.add(fullname)
|
|
|
|
|
elif op_type == "merge_pull_request":
|
|
|
|
|
fullname = activity["repo"]["full_name"] + "/pulls/" + activity["content"].split('|')[0]
|
|
|
|
|
pulls_closed.add(fullname)
|
|
|
|
|
elif op_type == "comment_pull":
|
|
|
|
|
fullname = activity["repo"]["full_name"] + "/pulls/" + activity["content"].split('|')[0]
|
|
|
|
|
pulls_commented.add(fullname)
|
|
|
|
|
elif op_type == "create_pull_request":
|
|
|
|
|
fullname = activity["repo"]["full_name"] + "/pulls/" + activity["content"].split('|')[0]
|
|
|
|
|
pulls_created.add(fullname)
|
|
|
|
|
elif op_type == "commit_repo":
|
|
|
|
|
if activity["ref_name"] == "refs/heads/main":
|
|
|
|
|
content_json = json.loads(activity["content"])
|
|
|
|
|
repo_name = activity["repo"]["name"]
|
|
|
|
|
for commits in content_json["Commits"]:
|
|
|
|
|
title = commits["Message"].split('\n', 1)[0]
|
|
|
|
|
|
|
|
|
|
# Substitute occurrences of "#\d+" with "{{Issue|\d+|repo}}"
|
|
|
|
|
title = re.sub(r"#(\d+)", rf"{{{{Issue|\1|{repo_name}}}}}", title)
|
|
|
|
|
|
|
|
|
|
hash_value = commits["Sha1"][:10]
|
|
|
|
|
commits_main.append(f"{title} ({{{{GitCommit|{hash_value}|{repo_name}}}}})")
|
|
|
|
|
|
|
|
|
|
date_end = date_curr
|
|
|
|
|
len_total = len(issues_closed) + len(issues_commented) + len(pulls_commented)
|
|
|
|
|
process = 0
|
|
|
|
|
for issue in issues_commented:
|
|
|
|
|
print(f"[{int(100 * (process / len_total))}%] Checking issue {issue} ", end="\r", flush=True)
|
|
|
|
|
process += 1
|
|
|
|
|
|
|
|
|
|
issue_events = gitea_json_issue_events_filter(issue,
|
|
|
|
|
date_start=start,
|
|
|
|
|
date_end=date_end,
|
|
|
|
|
username=username,
|
|
|
|
|
labels={
|
|
|
|
|
"Status/Confirmed",
|
|
|
|
|
"Status/Needs Information from User",
|
|
|
|
|
"Status/Needs Info from Developers"})
|
|
|
|
|
|
|
|
|
|
for event in issue_events:
|
|
|
|
|
label_name = event["label"]["name"]
|
|
|
|
|
if label_name == "Status/Confirmed":
|
|
|
|
|
issues_confirmed.append(issue)
|
|
|
|
|
elif label_name == "Status/Needs Information from User":
|
|
|
|
|
issues_needing_user_info.append(issue)
|
|
|
|
|
elif label_name == "Status/Needs Info from Developers":
|
|
|
|
|
issues_needing_developer_info.append(issue)
|
|
|
|
|
|
|
|
|
|
for issue in issues_closed:
|
|
|
|
|
print(f"[{int(100 * (process / len_total))}%] Checking issue {issue} ", end="\r", flush=True)
|
|
|
|
|
process += 1
|
|
|
|
|
|
|
|
|
|
issue_events = gitea_json_issue_events_filter(issue,
|
|
|
|
|
date_start=start,
|
|
|
|
|
date_end=date_end,
|
|
|
|
|
username=username,
|
|
|
|
|
event_type={"close", "commit_ref"},
|
|
|
|
|
labels={"Status/Duplicate"})
|
|
|
|
|
|
|
|
|
|
for event in issue_events:
|
|
|
|
|
event_type = event["type"]
|
|
|
|
|
if event_type == "commit_ref":
|
|
|
|
|
issues_fixed.append(issue)
|
|
|
|
|
elif event_type == "label":
|
|
|
|
|
issues_duplicated.append(issue)
|
|
|
|
|
else:
|
|
|
|
|
issues_archived.append(issue)
|
|
|
|
|
|
|
|
|
|
for pull in pulls_commented:
|
|
|
|
|
print(f"[{int(100 * (process / len_total))}%] Checking pull {pull} ", end="\r", flush=True)
|
|
|
|
|
process += 1
|
|
|
|
|
|
|
|
|
|
pull_events = gitea_json_issue_events_filter(pull.replace("pulls", "issues"),
|
|
|
|
|
date_start=start,
|
|
|
|
|
date_end=date_end,
|
|
|
|
|
username=username,
|
|
|
|
|
event_type={"comment"})
|
|
|
|
|
|
|
|
|
|
if pull_events:
|
|
|
|
|
pull_data = gitea_json_issue_get_cached(pull)
|
|
|
|
|
if pull_data["user"]["login"] != username:
|
|
|
|
|
pulls_reviewed.append(pull)
|
|
|
|
|
|
|
|
|
|
# Print triaging stats
|
|
|
|
|
|
|
|
|
|
issues_involved = issues_closed | issues_commented | issues_created
|
|
|
|
|
|
|
|
|
|
print("\'\'\'Involved in %s reports:\'\'\' " % len(issues_involved))
|
|
|
|
|
print("* Confirmed: %s" % len(issues_confirmed))
|
|
|
|
|
print("* Closed as Resolved: %s" % len(issues_fixed))
|
|
|
|
|
print("* Closed as Archived: %s" % len(issues_archived))
|
|
|
|
|
print("* Closed as Duplicate: %s" % len(issues_duplicated))
|
|
|
|
|
print("* Needs Info from User: %s" % len(issues_needing_user_info))
|
|
|
|
|
print("* Needs Info from Developers: %s" % len(issues_needing_developer_info))
|
|
|
|
|
print("* Actions total: %s" % (len(issues_closed) + len(issues_commented) + len(issues_created)))
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Print review stats
|
|
|
|
|
def print_pulls(pulls):
|
|
|
|
|
for pull in pulls:
|
|
|
|
|
pull_data = gitea_json_issue_get_cached(pull)
|
|
|
|
|
title = pull_data["title"]
|
|
|
|
|
_, repo, _, number = pull.split('/')
|
|
|
|
|
print(f"* {{{{PullRequest|{number}|{repo}}}}}: {title}")
|
|
|
|
|
|
|
|
|
|
print("'''Review: %s'''" % len(pulls_reviewed))
|
|
|
|
|
print_pulls(pulls_reviewed)
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Print created diffs
|
|
|
|
|
print("'''Created pulls: %s'''" % len(pulls_created))
|
|
|
|
|
print_pulls(pulls_created)
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Print commits
|
|
|
|
|
print("'''Commits:'''")
|
|
|
|
|
for commit in commits_main:
|
|
|
|
|
print("*", commit)
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
if verbose:
|
|
|
|
|
# Debug
|
|
|
|
|
|
|
|
|
|
def print_links(issues):
|
|
|
|
|
for fullname in issues:
|
|
|
|
|
print(f"https://projects.blender.org/{fullname}")
|
|
|
|
|
|
|
|
|
|
print("Debug:")
|
2023-08-04 16:27:36 -03:00
|
|
|
print(f"Activities from {start.isoformat()} to {date_end.isoformat()}:")
|
2023-08-04 19:32:25 +02:00
|
|
|
print()
|
|
|
|
|
print("Pulls Created:")
|
|
|
|
|
print_links(pulls_created)
|
|
|
|
|
print("Pulls Reviewed:")
|
|
|
|
|
print_links(pulls_reviewed)
|
|
|
|
|
print("Issues Confirmed:")
|
|
|
|
|
print_links(issues_confirmed)
|
|
|
|
|
print("Issues Closed as Resolved:")
|
|
|
|
|
print_links(issues_fixed)
|
|
|
|
|
print("Issues Closed as Archived:")
|
|
|
|
|
print_links(issues_closed)
|
|
|
|
|
print("Issues Closed as Duplicate:")
|
|
|
|
|
print_links(issues_duplicated)
|
|
|
|
|
print("Issues Needing Info from User:")
|
|
|
|
|
print_links(issues_needing_user_info)
|
|
|
|
|
print("Issues Needing Info from Developers:")
|
|
|
|
|
print_links(issues_needing_developer_info)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
|
# ----------
|
|
|
|
|
# Parse Args
|
|
|
|
|
args = argparse_create().parse_args()
|
|
|
|
|
username = args.username
|
|
|
|
|
if not username:
|
|
|
|
|
username = git_username_detect()
|
|
|
|
|
if not username:
|
|
|
|
|
return
|
|
|
|
|
|
2023-08-09 20:21:07 +10:00
|
|
|
# end_date = datetime.datetime(2020, 3, 14)
|
2023-08-04 19:32:25 +02:00
|
|
|
end_date = datetime.datetime.now()
|
|
|
|
|
weekday = end_date.weekday()
|
|
|
|
|
|
|
|
|
|
# Assuming I am lazy and making this at last moment or even later in worst case
|
|
|
|
|
if weekday < 2:
|
|
|
|
|
time_delta = 7 + weekday
|
|
|
|
|
start_date = end_date - datetime.timedelta(days=time_delta, hours=end_date.hour)
|
|
|
|
|
end_date -= datetime.timedelta(days=weekday, hours=end_date.hour)
|
|
|
|
|
else:
|
|
|
|
|
time_delta = weekday
|
|
|
|
|
start_date = end_date - datetime.timedelta(days=time_delta, hours=end_date.hour)
|
|
|
|
|
|
|
|
|
|
# Ensure friday :)
|
|
|
|
|
friday = start_date + datetime.timedelta(days=4)
|
|
|
|
|
week = start_date.isocalendar()[1]
|
|
|
|
|
start_date_str = start_date.strftime('%b %d')
|
|
|
|
|
end_date_str = friday.strftime('%b %d')
|
|
|
|
|
|
|
|
|
|
print("== Week %d (%s - %s) ==\n\n" % (week, start_date_str, end_date_str))
|
|
|
|
|
report_personal_weekly_get(username, start_date, verbose=args.verbose)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|
|
|
|
|
|
|
|
|
|
# wait for input to close window
|
|
|
|
|
input()
|