From 6de633bc2b81ce0de571df7721c2342aff4785d8 Mon Sep 17 00:00:00 2001 From: Venu Vardhan Reddy Tekula Date: Sun, 25 Jan 2026 15:28:55 -0500 Subject: [PATCH 1/2] feat: add optional avatar column to contributors table Add SHOW_AVATAR flag plumbing from env to markdown output so avatars render as a column when enabled, update README and tests accordingly. Signed-off-by: Venu Vardhan Reddy Tekula --- README.md | 1 + contributors.py | 2 ++ env.py | 4 +++ markdown.py | 30 ++++++++++++++++++--- test_env.py | 9 +++++++ test_markdown.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 46b9cb3..5cf8d61 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe | `END_DATE` | False | Current Date | The date at which you want to stop gathering contributor information. Must be later than the `START_DATE`. ie. Aug 2nd, 2023 would be `2023-08-02` | | `SPONSOR_INFO` | False | False | If you want to include sponsor information in the output. This will include the sponsor count and the sponsor URL. This will impact action performance. ie. SPONSOR_INFO = "False" or SPONSOR_INFO = "True" | | `LINK_TO_PROFILE` | False | True | If you want to link usernames to their GitHub profiles in the output. ie. LINK_TO_PROFILE = "True" or LINK_TO_PROFILE = "False" | +| `SHOW_AVATAR` | False | False | If you want to show profile images in the markdown output. ie. SHOW_AVATAR = "True" or SHOW_AVATAR = "False" | **Note**: If `start_date` and `end_date` are specified then the action will determine if the contributor is new. A new contributor is one that has contributed in the date range specified but not before the start date. diff --git a/contributors.py b/contributors.py index 96cdd86..50615de 100644 --- a/contributors.py +++ b/contributors.py @@ -27,6 +27,7 @@ def main(): end_date, sponsor_info, link_to_profile, + show_avatar, ) = env.get_env_vars() # Auth to GitHub.com @@ -83,6 +84,7 @@ def main(): sponsor_info, link_to_profile, ghe, + show_avatar=show_avatar, ) json_writer.write_to_json( filename="contributors.json", diff --git a/env.py b/env.py index b160dc2..bcdceb7 100644 --- a/env.py +++ b/env.py @@ -85,6 +85,7 @@ def get_env_vars( str, bool, bool, + bool, ]: """ Get the environment variables for use in the action. @@ -105,6 +106,7 @@ def get_env_vars( end_date (str): The end date to get contributor information to. sponsor_info (str): Whether to get sponsor information on the contributor link_to_profile (str): Whether to link username to Github profile in markdown output + show_avatar (str): Whether to show contributor avatars in markdown output """ if not test: @@ -145,6 +147,7 @@ def get_env_vars( sponsor_info = get_bool_env_var("SPONSOR_INFO", False) link_to_profile = get_bool_env_var("LINK_TO_PROFILE", False) + show_avatar = get_bool_env_var("SHOW_AVATAR", False) # Separate repositories_str into a list based on the comma separator repositories_list = [] @@ -166,4 +169,5 @@ def get_env_vars( end_date, sponsor_info, link_to_profile, + show_avatar, ) diff --git a/markdown.py b/markdown.py index ab3d852..559b89b 100644 --- a/markdown.py +++ b/markdown.py @@ -4,6 +4,12 @@ import os +def _is_truthy(value) -> bool: + if isinstance(value, str): + return value.strip().lower() == "true" + return value is True + + def write_to_markdown( collaborators, filename, @@ -14,6 +20,7 @@ def write_to_markdown( sponsor_info, link_to_profile, ghe, + show_avatar=False, ): """ This function writes a list of collaborators to a markdown file in table format @@ -40,6 +47,8 @@ def write_to_markdown( link_to_profile (str): True if the user wants the username linked to Github profile in the report ghe (str): The GitHub Enterprise instance URL, if applicable. + show_avatar (bool): True if the user wants to show profile images in + the report Returns: None @@ -55,6 +64,7 @@ def write_to_markdown( sponsor_info, link_to_profile, ghe, + show_avatar, ) # Put together the summary table including # of new contributions, @@ -196,6 +206,7 @@ def get_contributor_table( sponsor_info, link_to_profile, ghe, + show_avatar=False, ): """ This function returns a string containing a markdown table of the contributors and the total contribution count. @@ -209,16 +220,21 @@ def get_contributor_table( repository (str): The repository for which the contributors are being listed. sponsor_info (str): True if the user wants the sponsor_url shown in the report link_to_profile (str): True if the user wants the username linked to Github profile in the report + show_avatar (bool): True if the user wants to show profile images in the report Returns: table (str): A string containing a markdown table of the contributors and the total contribution count. total_contributions (int): The total number of contributions made by all of the contributors. """ + sponsor_info = _is_truthy(sponsor_info) + show_avatar = _is_truthy(show_avatar) columns = ["Username", "All Time Contribution Count"] + if show_avatar: + columns.insert(0, "Avatar") if start_date and end_date: columns += ["New Contributor"] - if sponsor_info == "true": + if sponsor_info: columns += ["Sponsor URL"] if start_date and end_date: columns += [f"Commits between {start_date} and {end_date}"] @@ -250,8 +266,16 @@ def get_contributor_table( commit_urls += f"{url}, " new_contributor = collaborator.new_contributor - row = ( - f"| {'' if not link_to_profile else '@'}{username} | {contribution_count} |" + row = "| " + if show_avatar: + avatar_cell = ( + f'' + if collaborator.avatar_url + else "" + ) + row += f"{avatar_cell} | " + row += ( + f"{'' if not link_to_profile else '@'}{username} | {contribution_count} |" ) if "New Contributor" in columns: row += f" {new_contributor} |" diff --git a/test_env.py b/test_env.py index 638e6e5..0746502 100644 --- a/test_env.py +++ b/test_env.py @@ -25,6 +25,7 @@ def setUp(self): "ORGANIZATION", "REPOSITORY", "START_DATE", + "SHOW_AVATAR", ] for key in env_keys: if key in os.environ: @@ -44,6 +45,7 @@ def setUp(self): "END_DATE": "2022-12-31", "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", + "SHOW_AVATAR": "False", }, clear=True, ) @@ -65,6 +67,7 @@ def test_get_env_vars(self): end_date, sponsor_info, link_to_profile, + show_avatar, ) = env.get_env_vars() self.assertEqual(organization, "org") @@ -79,6 +82,7 @@ def test_get_env_vars(self): self.assertEqual(end_date, "2022-12-31") self.assertFalse(sponsor_info) self.assertTrue(link_to_profile) + self.assertFalse(show_avatar) @patch.dict( os.environ, @@ -94,6 +98,7 @@ def test_get_env_vars(self): "END_DATE": "2022-12-31", "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", + "SHOW_AVATAR": "False", }, clear=True, ) @@ -122,6 +127,7 @@ def test_get_env_vars_missing_values(self): "END_DATE": "2022-12-31", "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", + "SHOW_AVATAR": "False", }, clear=True, ) @@ -153,6 +159,7 @@ def test_get_env_vars_invalid_start_date(self): "END_DATE": "", "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", + "SHOW_AVATAR": "False", }, clear=True, ) @@ -175,6 +182,7 @@ def test_get_env_vars_no_dates(self): end_date, sponsor_info, link_to_profile, + show_avatar, ) = env.get_env_vars() self.assertEqual(organization, "org") @@ -189,6 +197,7 @@ def test_get_env_vars_no_dates(self): self.assertEqual(end_date, "") self.assertFalse(sponsor_info) self.assertTrue(link_to_profile) + self.assertFalse(show_avatar) @patch.dict(os.environ, {}) def test_get_env_vars_missing_org_or_repo(self): diff --git a/test_markdown.py b/test_markdown.py index 98c5e1a..1ea8097 100644 --- a/test_markdown.py +++ b/test_markdown.py @@ -144,6 +144,75 @@ def test_write_to_markdown_with_sponsors( ) mock_file().write.assert_called_once_with(expected_content) + @patch( + "markdown.os.environ.get", return_value=None + ) # Mock GITHUB_STEP_SUMMARY to None + @patch("builtins.open", new_callable=mock_open) + def test_write_to_markdown_with_avatars( + self, mock_file, mock_env_get + ): # pylint: disable=unused-argument + """ + Test the write_to_markdown function with avatar images turned on. + """ + person1 = contributor_stats.ContributorStats( + "user1", + False, + "https://avatars.example.com/user1.png", + 100, + "commit url", + "sponsor_url_1", + ) + person2 = contributor_stats.ContributorStats( + "user2", + False, + "https://avatars.example.com/user2.png", + 200, + "commit url2", + "sponsor_url_2", + ) + # Set person2 as a new contributor since this cannot be set on initiatization of the object + person2.new_contributor = True + collaborators = [ + person1, + person2, + ] + ghe = "" + + write_to_markdown( + collaborators, + "filename", + "2023-01-01", + "2023-01-02", + None, + "org/repo", + "false", + True, + ghe, + show_avatar=True, + ) + + mock_file.assert_called_once_with("filename", "w", encoding="utf-8") + # With the new implementation, content is written as a single string + expected_content = ( + "# Contributors\n\n" + "- Date range for contributor list: 2023-01-01 to 2023-01-02\n" + "- Repository: org/repo\n\n" + "| Total Contributors | Total Contributions | % New Contributors |\n" + "| --- | --- | --- |\n" + "| 2 | 300 | 50.0% |\n\n" + "| Avatar | Username | All Time Contribution Count | New Contributor | " + "Commits between 2023-01-01 and 2023-01-02 |\n" + "| --- | --- | --- | --- | --- |\n" + '| | ' + "@user1 | 100 | False | commit url |\n" + '| | ' + "@user2 | 200 | True | commit url2 |\n" + "\n _this file was generated by the " + "[Contributors GitHub Action]" + "(https://github.com/github/contributors)_\n" + ) + mock_file().write.assert_called_once_with(expected_content) + @patch( "markdown.os.environ.get", return_value=None ) # Mock GITHUB_STEP_SUMMARY to None From 8ec1034527b6e7b4e8a3d3a9778b00315d2977dd Mon Sep 17 00:00:00 2001 From: Venu Vardhan Reddy Tekula Date: Sun, 25 Jan 2026 16:32:18 -0500 Subject: [PATCH 2/2] docs: fix type descriptions to main consistency Signed-off-by: Venu Vardhan Reddy Tekula --- README.md | 2 +- contributors.py | 2 +- markdown.py | 5 +++-- test_markdown.py | 12 ++++++------ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5cf8d61..63f44ad 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe | `END_DATE` | False | Current Date | The date at which you want to stop gathering contributor information. Must be later than the `START_DATE`. ie. Aug 2nd, 2023 would be `2023-08-02` | | `SPONSOR_INFO` | False | False | If you want to include sponsor information in the output. This will include the sponsor count and the sponsor URL. This will impact action performance. ie. SPONSOR_INFO = "False" or SPONSOR_INFO = "True" | | `LINK_TO_PROFILE` | False | True | If you want to link usernames to their GitHub profiles in the output. ie. LINK_TO_PROFILE = "True" or LINK_TO_PROFILE = "False" | -| `SHOW_AVATAR` | False | False | If you want to show profile images in the markdown output. ie. SHOW_AVATAR = "True" or SHOW_AVATAR = "False" | +| `SHOW_AVATAR` | False | False | If you want to show profile images in the markdown output. ie. SHOW_AVATAR = "True" or SHOW_AVATAR = "False" | **Note**: If `start_date` and `end_date` are specified then the action will determine if the contributor is new. A new contributor is one that has contributed in the date range specified but not before the start date. diff --git a/contributors.py b/contributors.py index 50615de..659e76e 100644 --- a/contributors.py +++ b/contributors.py @@ -84,7 +84,7 @@ def main(): sponsor_info, link_to_profile, ghe, - show_avatar=show_avatar, + show_avatar, ) json_writer.write_to_json( filename="contributors.json", diff --git a/markdown.py b/markdown.py index 559b89b..60da39f 100644 --- a/markdown.py +++ b/markdown.py @@ -47,7 +47,7 @@ def write_to_markdown( link_to_profile (str): True if the user wants the username linked to Github profile in the report ghe (str): The GitHub Enterprise instance URL, if applicable. - show_avatar (bool): True if the user wants to show profile images in + show_avatar (str): True if the user wants to show profile images in the report Returns: @@ -220,7 +220,7 @@ def get_contributor_table( repository (str): The repository for which the contributors are being listed. sponsor_info (str): True if the user wants the sponsor_url shown in the report link_to_profile (str): True if the user wants the username linked to Github profile in the report - show_avatar (bool): True if the user wants to show profile images in the report + show_avatar (str): True if the user wants to show profile images in the report Returns: table (str): A string containing a markdown table of the contributors and the total contribution count. @@ -229,6 +229,7 @@ def get_contributor_table( """ sponsor_info = _is_truthy(sponsor_info) show_avatar = _is_truthy(show_avatar) + link_to_profile = _is_truthy(link_to_profile) columns = ["Username", "All Time Contribution Count"] if show_avatar: columns.insert(0, "Avatar") diff --git a/test_markdown.py b/test_markdown.py index 1ea8097..8d5e24c 100644 --- a/test_markdown.py +++ b/test_markdown.py @@ -38,7 +38,7 @@ def test_write_to_markdown( "commit url2", "sponsor_url_2", ) - # Set person2 as a new contributor since this cannot be set on initiatization of the object + # Set person2 as a new contributor since this cannot be set on initialization of the object person2.new_contributor = True collaborators = [ person1, @@ -104,7 +104,7 @@ def test_write_to_markdown_with_sponsors( "commit url2", "", ) - # Set person2 as a new contributor since this cannot be set on initiatization of the object + # Set person2 as a new contributor since this cannot be set on initialization of the object person2.new_contributor = True collaborators = [ person1, @@ -170,7 +170,7 @@ def test_write_to_markdown_with_avatars( "commit url2", "sponsor_url_2", ) - # Set person2 as a new contributor since this cannot be set on initiatization of the object + # Set person2 as a new contributor since this cannot be set on initialization of the object person2.new_contributor = True collaborators = [ person1, @@ -239,7 +239,7 @@ def test_write_to_markdown_without_link_to_profile( "commit url2", "sponsor_url_2", ) - # Set person2 as a new contributor since this cannot be set on initiatization of the object + # Set person2 as a new contributor since this cannot be set on initialization of the object person2.new_contributor = True collaborators = [ person1, @@ -304,7 +304,7 @@ def test_write_to_github_summary( "commit url2", "sponsor_url_2", ) - # Set person2 as a new contributor since this cannot be set on initiatization of the object + # Set person2 as a new contributor since this cannot be set on initialization of the object person2.new_contributor = True collaborators = [ person1, @@ -365,7 +365,7 @@ def test_write_to_markdown_with_organization( "https://github.com/org3/repo3/commits?author=user2", "sponsor_url_2", ) - # Set person2 as a new contributor since this cannot be set on initiatization of the object + # Set person2 as a new contributor since this cannot be set on initialization of the object person2.new_contributor = True collaborators = [ person1,