Files
test/tools/triage/weekly_report.py
Campbell Barton e955c94ed3 License Headers: Set copyright to "Blender Authors", add AUTHORS
Listing the "Blender Foundation" as copyright holder implied the Blender
Foundation holds copyright to files which may include work from many
developers.

While keeping copyright on headers makes sense for isolated libraries,
Blender's own code may be refactored or moved between files in a way
that makes the per file copyright holders less meaningful.

Copyright references to the "Blender Foundation" have been replaced with
"Blender Authors", with the exception of `./extern/` since these this
contains libraries which are more isolated, any changed to license
headers there can be handled on a case-by-case basis.

Some directories in `./intern/` have also been excluded:

- `./intern/cycles/` it's own `AUTHORS` file is planned.
- `./intern/opensubdiv/`.

An "AUTHORS" file has been added, using the chromium projects authors
file as a template.

Design task: #110784

Ref !110783.
2023-08-16 00:20:26 +10:00

277 lines
10 KiB
Python

#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# 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:")
print(f"Activities from {start.isoformat()} to {date_end.isoformat()}:")
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
# end_date = datetime.datetime(2020, 3, 14)
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()