From 86b7be5df0bdc34f2e1faf08c3c1e128bfd0ea6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Fr=C3=B6hlich?= Date: Wed, 3 Jan 2024 12:10:25 +0100 Subject: [PATCH] Add contributions to acknowledgement page (#217) (cherry picked from commit b1b7e26decb68ae17c0524a7411f2b0ab888de7c) # Conflicts: # .github/workflows/pr-stats.yml --- .github/workflows/ci-format.yml | 2 +- .github/workflows/pr-stats.yml | 50 +++ README.md | 2 +- _static/tabs.js | 147 +++++++ doc/acknowledgements/acknowledgements.rst | 79 ++-- .../{add_review_stats => add_pr_stats} | 2 +- make_help_scripts/add_sub_repos | 2 +- make_help_scripts/add_tmp_commits | 2 +- ...e_reviewer_stats.py => create_pr_stats.py} | 374 +++++++++++++++--- make_help_scripts/deploy_defines | 30 +- 10 files changed, 602 insertions(+), 88 deletions(-) create mode 100644 .github/workflows/pr-stats.yml create mode 100644 _static/tabs.js rename make_help_scripts/{add_review_stats => add_pr_stats} (92%) rename make_help_scripts/{create_reviewer_stats.py => create_pr_stats.py} (55%) diff --git a/.github/workflows/ci-format.yml b/.github/workflows/ci-format.yml index af0c354d63d..04668451909 100644 --- a/.github/workflows/ci-format.yml +++ b/.github/workflows/ci-format.yml @@ -24,7 +24,7 @@ jobs: reviewer-stats path: ~/reviews - name: Copy stats - run: ./make_help_scripts/add_review_stats + run: ./make_help_scripts/add_pr_stats - uses: pre-commit/action@v3.0.0 with: extra_args: --all-files --hook-stage manual diff --git a/.github/workflows/pr-stats.yml b/.github/workflows/pr-stats.yml new file mode 100644 index 00000000000..90c4772936a --- /dev/null +++ b/.github/workflows/pr-stats.yml @@ -0,0 +1,50 @@ +name: "Create PR Stats" + +on: + workflow_dispatch: + pull_request: # run if the file changed on a PR to master + branches: + - master + paths: + - 'make_help_scripts/create_pr_stats.py' + push: # run if the file changed by pushes to master + branches: + - master + paths: + - 'make_help_scripts/create_pr_stats.py' + schedule: + # Run every morning to ensure component documentation is up to date on deployment + - cron: '23 3 * * *' + +jobs: + create-pr-stats: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade --requirement requirements.txt + shell: bash + - name: Get stats + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python make_help_scripts/create_pr_stats.py + shell: bash + - name: Cache stats + uses: actions/cache/save@v3 + with: + key: reviewer-stats-${{ github.run_id }} + path: ~/reviews + - name: Upload Artifacts + uses: actions/upload-artifact@v4.0.0 + with: + name: pr-stats + path: ~/reviews diff --git a/README.md b/README.md index 437ab25628a..a82138f89e6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ First, you need to fetch the reviewer stats from ros2_control org. To do so, you ```bash export GITHUB_TOKEN= -python3 ./make_help_scripts/create_reviewer_stats.py +python3 ./make_help_scripts/create_pr_stats.py ``` which will create `~/reviews/reviewers_stats_with_graph.html`. Then you can build the documentation as usual, it will copy the file from this folder. diff --git a/_static/tabs.js b/_static/tabs.js new file mode 100644 index 00000000000..f6b653f9a09 --- /dev/null +++ b/_static/tabs.js @@ -0,0 +1,147 @@ +try { + var session = window.sessionStorage || {}; +} catch (e) { + var session = {}; +} + +window.addEventListener("DOMContentLoaded", () => { + const allTabs = document.querySelectorAll('.sphinx-tabs-tab'); + const tabLists = document.querySelectorAll('[role="tablist"]'); + + allTabs.forEach(tab => { + tab.addEventListener("click", changeTabs); + }); + + tabLists.forEach(tabList => { + tabList.addEventListener("keydown", keyTabs); + }); + + // Restore group tab selection from session + const lastSelected = session.getItem('sphinx-tabs-last-selected'); + if (lastSelected != null) selectNamedTabs(lastSelected); +}); + +/** + * Key focus left and right between sibling elements using arrows + * @param {Node} e the element in focus when key was pressed + */ +function keyTabs(e) { + const tab = e.target; + let nextTab = null; + if (e.keyCode === 39 || e.keyCode === 37) { + tab.setAttribute("tabindex", -1); + // Move right + if (e.keyCode === 39) { + nextTab = tab.nextElementSibling; + if (nextTab === null) { + nextTab = tab.parentNode.firstElementChild; + } + // Move left + } else if (e.keyCode === 37) { + nextTab = tab.previousElementSibling; + if (nextTab === null) { + nextTab = tab.parentNode.lastElementChild; + } + } + } + + if (nextTab !== null) { + nextTab.setAttribute("tabindex", 0); + nextTab.focus(); + } +} + +/** + * Select or deselect clicked tab. If a group tab + * is selected, also select tab in other tabLists. + * @param {Node} e the element that was clicked + */ +function changeTabs(e) { + // Use this instead of the element that was clicked, in case it's a child + const notSelected = this.getAttribute("aria-selected") === "false"; + const positionBefore = this.parentNode.getBoundingClientRect().top; + const notClosable = !this.parentNode.classList.contains("closeable"); + + deselectTabList(this); + + if (notSelected || notClosable) { + selectTab(this); + const name = this.getAttribute("name"); + selectNamedTabs(name, this.id); + + if (this.classList.contains("group-tab")) { + // Persist during session + session.setItem('sphinx-tabs-last-selected', name); + } + } + + const positionAfter = this.parentNode.getBoundingClientRect().top; + const positionDelta = positionAfter - positionBefore; + // Scroll to offset content resizing + window.scrollTo(0, window.scrollY + positionDelta); +} + +/** + * Select tab and show associated panel. + * @param {Node} tab tab to select + */ +function selectTab(tab) { + tab.setAttribute("aria-selected", true); + + // Show the associated panel + document + .getElementById(tab.getAttribute("aria-controls")) + .removeAttribute("hidden"); +} + +/** + * Hide the panels associated with all tabs within the + * tablist containing this tab. + * @param {Node} tab a tab within the tablist to deselect + */ +function deselectTabList(tab) { + const parent = tab.parentNode; + const grandparent = parent.parentNode; + + Array.from(parent.children) + .forEach(t => t.setAttribute("aria-selected", false)); + + Array.from(grandparent.children) + .slice(1) // Skip tablist + .forEach(panel => panel.setAttribute("hidden", true)); +} + +/** + * Select grouped tabs with the same name, but no the tab + * with the given id. + * @param {Node} name name of grouped tab to be selected + * @param {Node} clickedId id of clicked tab + */ +function selectNamedTabs(name, clickedId=null) { + const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`); + const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode); + + tabLists + .forEach(tabList => { + // Don't want to change the tabList containing the clicked tab + const clickedTab = tabList.querySelector(`[id="${clickedId}"]`); + if (clickedTab === null ) { + // Select first tab with matching name + const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`); + deselectTabList(tab); + selectTab(tab); + } + }) +} + +// TODO(christophfroehlich) this has to be uncommented for jQuery of our code to work on the same page + +// if (typeof exports === 'undefined') { +// exports = {}; +// } + +// exports.keyTabs = keyTabs; +// exports.changeTabs = changeTabs; +// exports.selectTab = selectTab; +// exports.deselectTabList = deselectTabList; +// exports.selectNamedTabs = selectNamedTabs; diff --git a/doc/acknowledgements/acknowledgements.rst b/doc/acknowledgements/acknowledgements.rst index 89d9795f1fd..34c69328728 100644 --- a/doc/acknowledgements/acknowledgements.rst +++ b/doc/acknowledgements/acknowledgements.rst @@ -5,42 +5,71 @@ Acknowledgements Maintainers ---------------- -The following people were maintaining the ``ros2_control`` framework, showing their all-time review activity: +The following people were maintaining the ``ros2_control`` framework, showing their review activity and contributions: -.. raw:: html - :file: maintainers_stats.html +.. tabs:: -Activity during the past 12 months: + .. tab:: Recent Contributions -.. raw:: html - :file: maintainers_stats_recent.html + Contributions during the past 12 months -Reviewers' Stats ----------------- -The following people have contributed to the development of this project by giving valuable reviews for pull requests, see :ref:`doc/contributing/contributing:contributing` for more information. + .. raw:: html + :file: contributors_maintainers_stats_recent.html + + .. tab:: All-Time Contrib + + All-time contributions + + .. raw:: html + :file: contributors_maintainers_stats.html + + .. tab:: Recent Reviews -.. raw:: html - :file: reviewers_stats.html + Reviews during the past 12 months -Activity during the past 12 months: + .. raw:: html + :file: reviewers_maintainers_stats_recent.html -.. raw:: html - :file: reviewers_stats_recent.html + .. tab:: All-Time Reviews + + All-time reviews + + .. raw:: html + :file: reviewers_maintainers_stats.html Contributors ---------------- +The following people have contributed to the development of this project by providing valuable reviews or by submitting pull requests, see :ref:`doc/contributing/contributing:contributing` for more information. + +.. tabs:: + + .. tab:: Recent Contributions + + Contributions during the past 12 months + + .. raw:: html + :file: contributors_stats_recent.html + + .. tab:: All-Time Contrib + + All-time contributions + + .. raw:: html + :file: contributors_stats.html + + .. tab:: Recent Reviews + + Reviews during the past 12 months + + .. raw:: html + :file: reviewers_stats_recent.html + + .. tab:: All-Time Reviews + + All-time reviews -The following links lists people who have contributed to the development of this project by submitting pull requests to the respective repository, see :ref:`doc/contributing/contributing:contributing` for more information. - -* `ros2_control `_ -* `ros2_controllers `_ -* `ros2_control_demos `_ -* `control_toolbox `_ -* `gazebo_ros2_control `_ -* `gz_ros2_control `_ -* `realtime_tools `_ -* `kinematics_interface `_ -* `control_msgs `_ + .. raw:: html + :file: reviewers_stats.html Companies and Institutions diff --git a/make_help_scripts/add_review_stats b/make_help_scripts/add_pr_stats similarity index 92% rename from make_help_scripts/add_review_stats rename to make_help_scripts/add_pr_stats index e4cb5af2673..4dbe1442763 100755 --- a/make_help_scripts/add_review_stats +++ b/make_help_scripts/add_pr_stats @@ -6,4 +6,4 @@ DIR="${BASH_SOURCE%/*}" if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi . "$DIR/deploy_defines" || { echo "Could not source deploy_defines script. This is needed for correct execution. Exiting!"; exit; } -add_reviewer_stats_file +add_pr_stats_file diff --git a/make_help_scripts/add_sub_repos b/make_help_scripts/add_sub_repos index 2095593e969..99b548e473e 100755 --- a/make_help_scripts/add_sub_repos +++ b/make_help_scripts/add_sub_repos @@ -23,4 +23,4 @@ add_sub_repositories () { } add_sub_repositories -add_reviewer_stats_file +add_pr_stats_file diff --git a/make_help_scripts/add_tmp_commits b/make_help_scripts/add_tmp_commits index affc1fee5b3..d785f0c6c81 100755 --- a/make_help_scripts/add_tmp_commits +++ b/make_help_scripts/add_tmp_commits @@ -46,7 +46,7 @@ add_sub_repositories_and_commit () { rm -rf .git/ cd ../../ done - add_reviewer_stats_file + add_pr_stats_file git add . # we don't want to use-precommit to check if subrepos are correct git commit -m "Add temporary changes for multi version" --no-verify 1> /dev/null diff --git a/make_help_scripts/create_reviewer_stats.py b/make_help_scripts/create_pr_stats.py similarity index 55% rename from make_help_scripts/create_reviewer_stats.py rename to make_help_scripts/create_pr_stats.py index 29c24a7c082..b18427392d2 100644 --- a/make_help_scripts/create_reviewer_stats.py +++ b/make_help_scripts/create_pr_stats.py @@ -99,7 +99,7 @@ def get_all_pages(url): url = None -def get_user_name(user): +def get_user_details(user): """ Retrieves the name of a GitHub user. @@ -108,25 +108,27 @@ def get_user_name(user): Returns: str: The name of the GitHub user, or an empty string if the name is not available. + avatar_url: The name of the GitHub user, or an empty string if the name is not available. """ url = f"https://api.github.com/users/{user}" - json, response = get_api_response_wait(url) + json, _ = get_api_response_wait(url) if json: - return json["name"] + return json["name"], json["avatar_url"] else: return "" -def get_reviewers_stats(owner, repos, branches, whitelist, earliest_date=""): +def get_pr_stats(owner, repos, branches, whitelist, blacklist, earliest_date=""): """ - Retrieves statistics about reviewers' activity on pull requests. + Retrieves statistics about PRs: contributors' and reviewers' activity on pull requests. Args: owner (str): The owner of the repositories. repos (list): The list of repositories. branches (dict): The dictionary mapping repositories to their branches. - whitelist (list): The list of whitelisted reviewers. + whitelist (list): The list of whitelisted users. + blacklist (list): The list of blacklisted users. earliest_date (str, optional): The earliest date to consider for reviews. Defaults to "". Returns: @@ -135,6 +137,10 @@ def get_reviewers_stats(owner, repos, branches, whitelist, earliest_date=""): - reviewers_whitelist (dict): A dictionary containing statistics for whitelisted reviewers. - reviewers_filter (dict): A dictionary containing filtered statistics for all reviewers not in whitelist. - reviewers_filter_whitelist (dict): A dictionary containing filtered statistics for whitelisted reviewers. + - contributors (dict): A dictionary containing statistics for all contributors not in whitelist. + - contributors_whitelist (dict): A dictionary containing statistics for whitelisted contributors. + - contributors_filter (dict): A dictionary containing filtered statistics for all contributors not in whitelist. + - contributors_filter_whitelist (dict): A dictionary containing filtered statistics for whitelisted contributors. - ct_pull (int): The total number of pull requests processed. """ @@ -142,6 +148,10 @@ def get_reviewers_stats(owner, repos, branches, whitelist, earliest_date=""): reviewers_whitelist = {} reviewers_filter = {} reviewers_filter_whitelist = {} + contributors = {} + contributors_whitelist = {} + contributors_filter = {} + contributors_filter_whitelist = {} ct_pull = 0 for repo in repos: @@ -159,38 +169,37 @@ def get_reviewers_stats(owner, repos, branches, whitelist, earliest_date=""): for reviewer in reviewers_list: reviewer_login = reviewer["login"] - if reviewer_login in whitelist: - current_dict = reviewers_whitelist - else: - current_dict = reviewers - - if reviewer_login in current_dict: - current_dict[reviewer_login]["assigned_reviews"] += 1 - else: - current_dict[reviewer_login] = { - "avatar_url": reviewer["avatar_url"], - "assigned_reviews": 1, - "finished_reviews": 0, - "last_review_date": "0000-00-00T00:00:00Z" - } - - # if filter is set, only count reviews after earliest_date - if earliest_date and pull["created_at"] > earliest_date: + if reviewer_login not in blacklist: if reviewer_login in whitelist: - current_dict = reviewers_filter_whitelist + current_dict = reviewers_whitelist else: - current_dict = reviewers_filter + current_dict = reviewers if reviewer_login in current_dict: current_dict[reviewer_login]["assigned_reviews"] += 1 else: current_dict[reviewer_login] = { - "avatar_url": reviewer["avatar_url"], "assigned_reviews": 1, "finished_reviews": 0, "last_review_date": "0000-00-00T00:00:00Z" } + # if filter is set, only count reviews after earliest_date + if earliest_date and pull["created_at"] > earliest_date: + if reviewer_login in whitelist: + current_dict = reviewers_filter_whitelist + else: + current_dict = reviewers_filter + + if reviewer_login in current_dict: + current_dict[reviewer_login]["assigned_reviews"] += 1 + else: + current_dict[reviewer_login] = { + "assigned_reviews": 1, + "finished_reviews": 0, + "last_review_date": "0000-00-00T00:00:00Z" + } + # Get reviews for the pull request, but count only once per PR pull_reviews_url = pull["url"] + "/reviews" pull_reviews, _ = get_api_response_wait(pull_reviews_url) @@ -201,6 +210,8 @@ def get_reviewers_stats(owner, repos, branches, whitelist, earliest_date=""): reviewer_login = review["user"]["login"] date = review["submitted_at"] + if reviewer_login in blacklist: + continue if reviewer_login in whitelist: current_dict = reviewers_whitelist else: @@ -215,7 +226,6 @@ def get_reviewers_stats(owner, repos, branches, whitelist, earliest_date=""): current_dict[reviewer_login]["last_review_date"] = date else: current_dict[reviewer_login] = { - "avatar_url": review["user"]["avatar_url"], "assigned_reviews": 1, "finished_reviews": 1, "last_review_date": date @@ -238,17 +248,70 @@ def get_reviewers_stats(owner, repos, branches, whitelist, earliest_date=""): current_dict[reviewer_login]["last_review_date"] = date else: current_dict[reviewer_login] = { - "avatar_url": review["user"]["avatar_url"], "assigned_reviews": 1, "finished_reviews": 1, "last_review_date": date } local_reviewers_filter[reviewer_login] = True - return reviewers, reviewers_whitelist, reviewers_filter, reviewers_filter_whitelist, ct_pull - - -def create_reviewers_table_with_graph(reviewers_stats, user_names, table_name): + # parse line contributions from commits + print(f"Getting contributors' stats for {owner}/{repo} on branch {branches[repo]}") + commits_url = f"https://api.github.com/repos/{owner}/{repo}/commits?branch={branches[repo]}" + + for commits in get_all_pages(commits_url): + for commit in commits: + if commit['author'] is None: + # print('No author in commit: ' + commit['url']) + continue + contributor_login = commit['author']['login'] + if contributor_login in blacklist: + continue + if contributor_login in whitelist: + current_dict = contributors_whitelist + else: + current_dict = contributors + + commit_details, _ = get_api_response_wait(commit['url']) + if 'stats' in commit_details.keys(): + additions = commit_details['stats']['additions'] + deletions = commit_details['stats']['deletions'] + total_changes = additions + deletions + else: + # print('No stats in commit details: ' + commit['url']) + total_changes = 0 + date = commit_details['commit']['author']['date'] + if contributor_login in current_dict: + current_dict[contributor_login]["total_changes"] += total_changes + current_dict[contributor_login]["ct_commit"] += 1 + current_dict[contributor_login]["last_commit_date"] = date + else: + current_dict[contributor_login] = { + "total_changes": total_changes, + "ct_commit": 1, + "last_commit_date": date + } + # if filter is set, only count reviews after earliest_date + if earliest_date and date > earliest_date: + if contributor_login in whitelist: + current_dict = contributors_filter_whitelist + else: + current_dict = contributors_filter + + if contributor_login in current_dict: + current_dict[contributor_login]["total_changes"] += total_changes + current_dict[contributor_login]["ct_commit"] += 1 + current_dict[contributor_login]["last_commit_date"] = date + else: + current_dict[contributor_login] = { + "total_changes": total_changes, + "ct_commit": 1, + "last_commit_date": date + } + + return reviewers, reviewers_whitelist, reviewers_filter, reviewers_filter_whitelist, contributors, contributors_whitelist, contributors_filter, contributors_filter_whitelist, ct_pull + + +def create_reviewers_table_with_graph(reviewers_stats, user_details, table_name): """ Creates an HTML table with reviewer statistics and graphs. @@ -256,11 +319,12 @@ def create_reviewers_table_with_graph(reviewers_stats, user_names, table_name): reviewers_stats (dict): A dictionary containing reviewer statistics. The keys are reviewer names and the values are dictionaries containing the following keys: - - 'avatar_url' (str): The URL of the reviewer's avatar image. - 'assigned_reviews' (int): The number of reviews assigned to the reviewer. - 'finished_reviews' (int): The number of reviews finished by the reviewer. - 'last_review_date' (str): The date of the last review by the reviewer. - user_names (dict): A dictionary mapping reviewer names to their corresponding user names. + user_details (dict): A dictionary mapping login names to + - 'name' (str): The name of the reviewer. + - 'avatar_url' (str): The URL of the reviewer's avatar image. table_name (str): The ID of the HTML table. Returns: @@ -364,11 +428,11 @@ def create_reviewers_table_with_graph(reviewers_stats, user_names, table_name):
- {reviewer} + {reviewer}
- {user_names[reviewer]}
+ {user_details[reviewer]['name']}
{reviewer}{reviewer}
@@ -408,6 +472,164 @@ def create_reviewers_table_with_graph(reviewers_stats, user_names, table_name): return html_content +def create_contributors_table_with_graph(contributors_stats, user_details, table_name): + """ + Creates an HTML table with contributors statistics and graphs. + + Args: + contributors_stats (dict): A dictionary containing contributors statistics. + The keys are contributors names and the values are dictionaries + containing the following keys: + - 'total_changes' (int): The number of line changes by the contributor. + - 'ct_commit' (int): The number of reviews finished by the contributor. + - 'last_commit_date' (str): The date of the last pr by the contributor. + user_details (dict): A dictionary mapping login names to + - 'name' (str): The name of the reviewer. + - 'avatar_url' (str): The URL of the reviewer's avatar image. + table_name (str): The ID of the HTML table. + + Returns: + str: The HTML content of the table with contributors statistics and graphs. + + style sheet for the table, copy into css file: + + + """ + + html_content = f""" + + + + + Contributors' Stats + + + + + +

+ + + + + + + + + + + + """ + if contributors_stats: + # Find the contributors with the highest number of line_changes + max_total_changes = max(stats['total_changes'] for stats in contributors_stats.values()) + max_pr_count = max(stats['ct_commit'] for stats in contributors_stats.values()) + + # Sort contributors by total change + sorted_contributors = sorted(contributors_stats.items(), key=lambda x: x[1]['total_changes'], reverse=True) + + for idx, (contributor, stats) in enumerate(sorted_contributors): + total_changes_bar_len = (stats['total_changes'] / max_total_changes) * 100 + ct_commit_bar_len = (stats['ct_commit'] / max_pr_count) * 100 + + # Add emojis for the first three contributors + medal = "" + if idx == 0: + medal = "🥇" + elif idx == 1: + medal = "🥈" + elif idx == 2: + medal = "🥉" + + html_content += f""" + + + + + + + """ + + html_content += f""" + +
ContributorCommit CountLine Changes
{medal} +
+
+ {contributor} +
+
+
+ {user_details[contributor]['name']}
+ {contributor}{contributor} +
+
+
+
{stats['ct_commit']} +
+
+
+
{stats['total_changes']} +
+
+
+
+ Fetched on {current_date.strftime("%Y-%m-%d %H:%M:%S")} UTC +

+ """ + html_content +=f""" + + + + """ + + return html_content + + def print_reviewers_stats(reviewers_stats): """ Prints the statistics of the reviewers. @@ -421,6 +643,19 @@ def print_reviewers_stats(reviewers_stats): for reviewer, stats in sorted(reviewers_stats.items(), key=lambda x: x[1]['finished_reviews'], reverse=True)[:10]: print(f"Reviewer: {reviewer}, Assigned Reviews: {stats['assigned_reviews']}, Finished Reviews: {stats['finished_reviews']}, rate of finished: {stats['finished_reviews']/stats['assigned_reviews']:.2f}, Last Review Date: {stats['last_review_date']}") +def print_contributors_stats(contributors_stats): + """ + Prints the statistics of the contributors. + + Args: + contributors_stats (dict): A dictionary containing the statistics of the contributors. + + Returns: + None + """ + for contributor, stats in sorted(contributors_stats.items(), key=lambda x: x[1]['total_changes'], reverse=True)[:10]: + print(f"Contributor: {contributor}, Number of PRs: {stats['ct_commit']}, Total Line Change: {stats['total_changes']}, Last PR Date: {stats['last_commit_date']}") + # Replace with your GitHub repository owner and name owner = "ros-controls" @@ -451,6 +686,7 @@ def print_reviewers_stats(reviewers_stats): } maintainers = ["bmagyar", "destogl", "christophfroehlich"] +blacklist = ["dependabot[bot]", "mergify[bot]"] # Get the current date and time current_date = datetime.utcnow() @@ -467,7 +703,7 @@ def print_reviewers_stats(reviewers_stats): print(f"API limit: {limit}, next reset: {datetime.fromtimestamp(reset)}") print("----------------------------------") print(f"Fetch pull requests, all-time and after {formatted_date}:") -reviewers_stats, maintainers_stats, reviewers_stats_recent, maintainers_stats_recent, ct_pulls = get_reviewers_stats(owner, repos, branches, maintainers, formatted_date) +reviewers_stats, maintainers_stats, reviewers_stats_recent, maintainers_stats_recent, contributors_stats, contributors_maintainers_stats, contributors_stats_recent, contributors_maintainers_stats_recent, ct_pulls = get_pr_stats(owner, repos, branches, maintainers, blacklist, formatted_date) print("----------------------------------") print("------------ Get User ------------") print("----------------------------------") @@ -476,10 +712,17 @@ def print_reviewers_stats(reviewers_stats): + list(maintainers_stats_recent.keys()) + list(reviewers_stats.keys()) + list(maintainers_stats.keys()) + + list(contributors_stats.keys()) + + list(contributors_maintainers_stats.keys()) ) -user_names = {} +user_details = {} for reviewer_login in unique_reviewers: - user_names[reviewer_login] = get_user_name(reviewer_login) + name, avatar_url = get_user_details(reviewer_login) + user_details[reviewer_login] = { + "name": name, + "avatar_url": avatar_url + } + print(f"Got {len(unique_reviewers)} user names") @@ -501,6 +744,22 @@ def print_reviewers_stats(reviewers_stats): print("-------- not maintainers ---------") print_reviewers_stats(reviewers_stats) print("----------------------------------") + +print(f"Contributors' Stats, after {formatted_date}:") +print("---------- maintainers -----------") +print_contributors_stats(contributors_maintainers_stats_recent) + +print("-------- not maintainers ---------") +print_contributors_stats(contributors_stats_recent) + +print(f"Contributors' Stats, all-time:") +print("---------- maintainers -----------") +print_contributors_stats(contributors_maintainers_stats) + +print("-------- not maintainers ---------") +print_contributors_stats(contributors_stats) +print("----------------------------------") + limit, reset = get_api_limit(); print(f"API limit remaining: {limit}, next reset: {datetime.fromtimestamp(reset)}") print("----------------------------------") @@ -508,17 +767,26 @@ def print_reviewers_stats(reviewers_stats): print("----------------------------------") # Create the HTML content -html_maintainers_stats_recent = create_reviewers_table_with_graph(maintainers_stats_recent, user_names, "maintainers_stats_recent") -html_reviewers_stats_recent = create_reviewers_table_with_graph(reviewers_stats_recent, user_names, "reviewers_stats_recent") -html_maintainers_stats = create_reviewers_table_with_graph(maintainers_stats, user_names, "maintainers_stats") -html_reviewers_stats = create_reviewers_table_with_graph(reviewers_stats, user_names, "reviewers_stats") +html_maintainers_stats_recent = create_reviewers_table_with_graph(maintainers_stats_recent, user_details, "maintainers_stats_recent") +html_reviewers_stats_recent = create_reviewers_table_with_graph(reviewers_stats_recent, user_details, "reviewers_stats_recent") +html_maintainers_stats = create_reviewers_table_with_graph(maintainers_stats, user_details, "maintainers_stats") +html_reviewers_stats = create_reviewers_table_with_graph(reviewers_stats, user_details, "reviewers_stats") + +html_contributors_maintainers_stats_recent = create_contributors_table_with_graph(contributors_maintainers_stats_recent, user_details, "contributors_maintainers_stats_recent") +html_contributors_stats_recent = create_contributors_table_with_graph(contributors_stats_recent, user_details, "contributors_stats_recent") +html_contributors_maintainers_stats = create_contributors_table_with_graph(contributors_maintainers_stats, user_details, "contributors_maintainers_stats") +html_contributors_stats = create_contributors_table_with_graph(contributors_stats, user_details, "contributors_stats") # Save the HTML content to a file named "reviewers_stats_with_graph.html" home_directory = os.path.expanduser( '~' ) -filename_maintainers_stats_recent = os.path.join(home_directory, 'reviews', 'maintainers_stats_recent.html') +filename_maintainers_stats_recent = os.path.join(home_directory, 'reviews', 'reviewers_maintainers_stats_recent.html') filename_reviewers_stats_recent = os.path.join(home_directory, 'reviews', 'reviewers_stats_recent.html') -filename_maintainers_stats = os.path.join(home_directory, 'reviews', 'maintainers_stats.html') +filename_maintainers_stats = os.path.join(home_directory, 'reviews', 'reviewers_maintainers_stats.html') filename_reviewers_stats = os.path.join(home_directory, 'reviews', 'reviewers_stats.html') +filename_contributors_maintainers_stats_recent = os.path.join(home_directory, 'reviews', 'contributors_maintainers_stats_recent.html') +filename_contributors_stats_recent = os.path.join(home_directory, 'reviews', 'contributors_stats_recent.html') +filename_contributors_maintainers_stats = os.path.join(home_directory, 'reviews', 'contributors_maintainers_stats.html') +filename_contributors_stats = os.path.join(home_directory, 'reviews', 'contributors_stats.html') os.makedirs(os.path.dirname(filename_maintainers_stats_recent), exist_ok=True) with open(filename_maintainers_stats_recent, 'w') as file: @@ -536,3 +804,19 @@ def print_reviewers_stats(reviewers_stats): with open(filename_reviewers_stats, 'w') as file: file.write(html_reviewers_stats) print(f"HTML file {filename_reviewers_stats} has been created.") + +with open(filename_contributors_maintainers_stats_recent, 'w') as file: + file.write(html_contributors_maintainers_stats_recent) +print(f"HTML file {filename_contributors_maintainers_stats_recent} has been created.") + +with open(filename_contributors_stats_recent, 'w') as file: + file.write(html_contributors_stats_recent) +print(f"HTML file {filename_contributors_stats_recent} has been created.") + +with open(filename_contributors_maintainers_stats, 'w') as file: + file.write(html_contributors_maintainers_stats) +print(f"HTML file {filename_contributors_maintainers_stats} has been created.") + +with open(filename_contributors_stats, 'w') as file: + file.write(html_contributors_stats) +print(f"HTML file {filename_contributors_stats} has been created.") diff --git a/make_help_scripts/deploy_defines b/make_help_scripts/deploy_defines index 2466df2b7e2..b0c6ee5efa1 100755 --- a/make_help_scripts/deploy_defines +++ b/make_help_scripts/deploy_defines @@ -10,24 +10,28 @@ base_dir="$(dirname "$script_base_dir")" base_branch="iron" build_dir="_build" -# reviewer stats -reviewer_stats_files=("maintainers_stats_recent.html" +# pr stats +pr_stats_files=("reviewers_maintainers_stats_recent.html" "reviewers_stats_recent.html" - "maintainers_stats.html" - "reviewers_stats.html") -reviewer_stats_cache_folder="reviews" -reviewer_stats_target_folder="./doc/acknowledgements" + "reviewers_maintainers_stats.html" + "reviewers_stats.html", + "contributors_maintainers_stats_recent.html" + "contributors_stats_recent.html" + "contributors_maintainers_stats.html" + "contributors_stats.html") +pr_stats_cache_folder="reviews" +pr_stats_target_folder="./doc/acknowledgements" -add_reviewer_stats_file () { - for reviewer_stats_filename in "${reviewer_stats_files[@]}"; do - ORIGFILE="$HOME/$reviewer_stats_cache_folder/$reviewer_stats_filename" +add_pr_stats_file () { + for pr_stats_filename in "${pr_stats_files[@]}"; do + ORIGFILE="$HOME/$pr_stats_cache_folder/$pr_stats_filename" # echo $ORIGFILE if test -f "$ORIGFILE"; then - echo "Copy reviewer stats file '${reviewer_stats_filename}' to '${reviewer_stats_target_folder}'" - cp ${ORIGFILE} ${reviewer_stats_target_folder} + echo "Copy PR stats file '${pr_stats_filename}' to '${pr_stats_target_folder}'" + cp ${ORIGFILE} ${pr_stats_target_folder} else - echo "Create empty reviewer stats file '${reviewer_stats_filename}' in '${reviewer_stats_target_folder}'" - echo "No review statistics available yet." > ${reviewer_stats_target_folder}/${reviewer_stats_filename} + echo "Create empty PR stats file '${pr_stats_filename}' in '${pr_stats_target_folder}'" + echo "No pr statistics available yet." > ${pr_stats_target_folder}/${pr_stats_filename} fi done }