diff --git a/README.md b/README.md index 5a99c79..69bb8e4 100644 --- a/README.md +++ b/README.md @@ -95,17 +95,20 @@ This action can be configured to authenticate with GitHub App Installation or Pe #### Other Configuration Options -| field | required | default | description | -| ------------------- | ----------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | -| `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want the contributor information of all repos from. ie. github.com/github would be `github` | -| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want the contributor information from. ie. `github/contributors` or a comma separated list of multiple repositories `github/contributor,super-linter/super-linter` | -| `START_DATE` | False | Beginning of time | The date from which you want to start gathering contributor information. ie. Aug 1st, 2023 would be `2023-08-01`. | -| `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" | -| `OUTPUT_FILENAME` | False | contributors.md | The output filename for the markdown report. ie. OUTPUT_FILENAME = "my-report.md" | -| `SHOW_AVATAR` | False | False | If you want to show profile images in the markdown output. ie. SHOW_AVATAR = "True" or SHOW_AVATAR = "False" | +| field | required | default | description | +| ------------------- | ----------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | +| `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want the contributor information of all repos from. ie. github.com/github would be `github` | +| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want the contributor information from. ie. `github/contributors` or a comma separated list of multiple repositories `github/contributor,super-linter/super-linter` | +| `START_DATE` | False | Beginning of time | The date from which you want to start gathering contributor information. ie. Aug 1st, 2023 would be `2023-08-01`. | +| `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" | +| `OUTPUT_FILENAME` | False | contributors.md | The output filename for the markdown report. ie. OUTPUT_FILENAME = "my-report.md" | +| `SHOW_AVATAR` | False | False | If you want to show profile images in the markdown output. ie. SHOW_AVATAR = "True" or SHOW_AVATAR = "False" | +| `SORT_COLUMN` | False | contribution_count| The column you want to sort the table with. Possible values: `username` (column of strings), `contribution_count` (column of integers) or `new_contributor` (column of booleans, whether the given username is a new contributor) | +| `SORT_ORDER` | False | desc | The order the SORT_COLUMN column will be sorted. Possibles values: "asc" for ascending order or "desc" for descending order. | +| `SKIP_SUMMARY` | False | False | If you want to hide the summary with total contributions statistics above the contributors table. ie. SKIP_SUMMARY = "True" or SKIP_SUMMARY = "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 ad790bb..be6dff7 100644 --- a/contributors.py +++ b/contributors.py @@ -29,6 +29,9 @@ def main(): link_to_profile, output_filename, show_avatar, + sort_column, + sort_order, + skip_summary, ) = env.get_env_vars() # Auth to GitHub.com @@ -86,6 +89,9 @@ def main(): link_to_profile, ghe, show_avatar, + sort_column, + sort_order, + skip_summary, ) json_writer.write_to_json( filename="contributors.json", @@ -96,6 +102,8 @@ def main(): sponsor_info=sponsor_info, link_to_profile=link_to_profile, contributors=contributors, + sort_column=sort_column, + sort_order=sort_order, ) diff --git a/env.py b/env.py index a36c631..e165f16 100644 --- a/env.py +++ b/env.py @@ -7,6 +7,7 @@ import os import re from os.path import dirname, join +from typing import List from dotenv import load_dotenv @@ -132,6 +133,19 @@ def validate_output_filename(output_filename: str) -> str: return filename +def validate_allowed_values( + env_var_name: str, allowed_values: List[str], default: str +) -> str: + """Check that env variable is contained in a list of defined values or set a default one. + Returns a lowercase value. + """ + allowed_values = [val.strip().lower() for val in allowed_values] + var_to_validate = os.getenv(env_var_name, default).strip().lower() + if var_to_validate in allowed_values: + return var_to_validate + return default.strip().lower() + + def get_env_vars( test: bool = False, ) -> tuple[ @@ -149,6 +163,9 @@ def get_env_vars( bool, str, bool, + str, + str, + bool, ]: """ Get the environment variables for use in the action. @@ -169,6 +186,9 @@ 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 + sort_column (str): The column the table should be sorted by ('username', 'contribution_count' or 'new_contributor'). Case insensitive. + sort_order (str): The sorting order ('asc' or 'desc'). Case insensitive. + skip_summary (str): Whether to print the summary before the table """ if not test: @@ -222,6 +242,14 @@ def get_env_vars( repository.strip() for repository in repositories_str.split(",") ] + sort_column = validate_allowed_values( + "SORT_COLUMN", + ["username", "contribution_count", "new_contributor"], + "contribution_count", + ) + sort_order = validate_allowed_values("SORT_ORDER", ["asc", "desc"], "desc") + skip_summary = get_bool_env_var("SKIP_SUMMARY", False) + return ( organization, repositories_list, @@ -237,4 +265,7 @@ def get_env_vars( link_to_profile, output_filename, show_avatar, + sort_column, + sort_order, + skip_summary, ) diff --git a/json_writer.py b/json_writer.py index 9ae5961..98d0be7 100644 --- a/json_writer.py +++ b/json_writer.py @@ -1,6 +1,7 @@ """This module contains a function that writes data to a JSON file.""" import json +from operator import attrgetter def write_to_json( @@ -12,6 +13,8 @@ def write_to_json( repository_list, sponsor_info, link_to_profile, + sort_column, + sort_order, ): """Write data to a JSON file. @@ -24,6 +27,8 @@ def write_to_json( repository_list (list): A list of repositories for which the contributors are being listed. sponsor_info (str): A string indicating whether sponsor information should be included. link_to_profile (str): A string indicating whether a link to the contributor's profile should be included. + sort_column (str): the column to sort the table with ('username', 'contribution_count' or 'new_contributor'). + sort_order (str): the order to sort the column with (`asc` or `desc`). Returns: None @@ -44,6 +49,8 @@ def write_to_json( # ], # "sponsor_info": false, # "link_to_profile": false, + # "sort_column": "contribution_count" + # "sort_order": "desc" # "contributors": [ # { # "username": "zkoppert", @@ -63,6 +70,11 @@ def write_to_json( # } # ] # } + + # Sort the list by chosen column and order + is_reverse = sort_order == "desc" + contributors = sorted(contributors, key=attrgetter(sort_column), reverse=is_reverse) + data = { "start_date": start_date, "end_date": end_date, @@ -70,6 +82,8 @@ def write_to_json( "repository_list": repository_list, "sponsor_info": sponsor_info, "link_to_profile": link_to_profile, + "sort_column": sort_column, + "sort_order": sort_order, "contributors": [contributor.__dict__ for contributor in contributors], } diff --git a/markdown.py b/markdown.py index a40b276..ccbf406 100644 --- a/markdown.py +++ b/markdown.py @@ -2,6 +2,7 @@ """This module contains the functions needed to write the output to markdown files.""" import os +from operator import attrgetter def _is_truthy(value) -> bool: @@ -20,6 +21,9 @@ def write_to_markdown( sponsor_info, link_to_profile, ghe, + sort_column, + sort_order, + skip_summary, show_avatar=False, ): """ @@ -47,6 +51,10 @@ 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. + sort_column (str): the column to sort the table with ('username', + 'contribution_count' or 'new_contributor') + sort_order (str): the order to sort the column with (`asc` or `desc`) + skip_summary (str): True if the user wants the summary not to be printed show_avatar (str): True if the user wants to show profile images in the report @@ -64,6 +72,8 @@ def write_to_markdown( sponsor_info, link_to_profile, ghe, + sort_column, + sort_order, show_avatar, ) @@ -75,7 +85,13 @@ def write_to_markdown( # Generate the markdown content once content = generate_markdown_content( - start_date, end_date, organization, repository, table, summary_table + start_date, + end_date, + organization, + repository, + table, + summary_table, + skip_summary, ) # Write the markdown file @@ -104,7 +120,7 @@ def write_to_github_summary(content): def generate_markdown_content( - start_date, end_date, organization, repository, table, summary_table + start_date, end_date, organization, repository, table, summary_table, skip_summary ): """ This function generates markdown content as a string. @@ -121,6 +137,7 @@ def generate_markdown_content( and the total contribution count. summary_table (str): A string containing a markdown table of the summary statistics. + skip_summary (str): True if the user wants the summary not to be printed. Returns: str: The complete markdown content as a string. @@ -134,7 +151,8 @@ def generate_markdown_content( if repository: content += f"- Repository: {repository}\n" content += "\n" - content += summary_table + if not skip_summary: + content += summary_table content += table content += ( "\n _this file was generated by the " @@ -204,6 +222,8 @@ def get_contributor_table( sponsor_info, link_to_profile, ghe, + sort_column, + sort_order, show_avatar=False, ): """ @@ -218,6 +238,9 @@ 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 + sort_column (str): the column to sort the table with ('username', + 'contribution_count' or 'new_contributor') + sort_order (str): the order to sort the column with (`asc` or `desc`) show_avatar (str): True if the user wants to show profile images in the report Returns: @@ -249,7 +272,13 @@ def get_contributor_table( table = headers total_contributions = 0 - for collaborator in collaborators: + # Sort the list by chosen column and order + is_reverse = sort_order == "desc" + sorted_collaborators = sorted( + collaborators, key=attrgetter(sort_column), reverse=is_reverse + ) + + for collaborator in sorted_collaborators: total_contributions += collaborator.contribution_count username = collaborator.username contribution_count = collaborator.contribution_count diff --git a/test_contributors.py b/test_contributors.py index a865344..f1d8e11 100644 --- a/test_contributors.py +++ b/test_contributors.py @@ -349,6 +349,9 @@ def test_main_runs_under_main_guard(self): False, "contributors.md", False, + "contribution_count", + "desc", + False, ) mock_auth = MagicMock() @@ -423,6 +426,9 @@ def test_main_sets_new_contributor_flag(self): False, "contributors.md", False, + "contribution_count", + "desc", + False, ) mock_auth_to_github.return_value = MagicMock() mock_get_all_contributors.side_effect = [[contributor], []] @@ -478,6 +484,9 @@ def test_main_fetches_sponsor_info_when_enabled(self): False, "contributors.md", False, + "contribution_count", + "desc", + False, ) mock_auth_to_github.return_value = MagicMock() mock_get_all_contributors.return_value = [contributor] diff --git a/test_env.py b/test_env.py index 6c0feb9..2ce701b 100644 --- a/test_env.py +++ b/test_env.py @@ -27,6 +27,9 @@ def setUp(self): "REPOSITORY", "START_DATE", "SHOW_AVATAR", + "SORT_COLUMN", + "SORT_ORDER", + "SKIP_SUMMARY", ] for key in env_keys: if key in os.environ: @@ -47,6 +50,9 @@ def setUp(self): "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", "SHOW_AVATAR": "False", + "SORT_COLUMN": "username", + "SORT_ORDER": "asc", + "SKIP_SUMMARY": "True", }, clear=True, ) @@ -70,6 +76,9 @@ def test_get_env_vars(self): link_to_profile, _output_filename, _show_avatar, + sort_column, + sort_order, + skip_summary, ) = env.get_env_vars() self.assertEqual(organization, "org") @@ -84,6 +93,9 @@ def test_get_env_vars(self): self.assertEqual(end_date, "2022-12-31") self.assertFalse(sponsor_info) self.assertTrue(link_to_profile) + self.assertEqual(sort_column, "username") + self.assertEqual(sort_order, "asc") + self.assertTrue(skip_summary) @patch.dict( os.environ, @@ -100,6 +112,9 @@ def test_get_env_vars(self): "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", "SHOW_AVATAR": "False", + "SORT_COLUMN": "username", + "SORT_ORDER": "asc", + "SKIP_SUMMARY": "True", }, clear=True, ) @@ -129,6 +144,9 @@ def test_get_env_vars_missing_values(self): "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", "SHOW_AVATAR": "False", + "SORT_COLUMN": "username", + "SORT_ORDER": "asc", + "SKIP_SUMMARY": "True", }, clear=True, ) @@ -161,6 +179,9 @@ def test_get_env_vars_invalid_start_date(self): "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", "SHOW_AVATAR": "False", + "SORT_COLUMN": "username", + "SORT_ORDER": "asc", + "SKIP_SUMMARY": "True", }, clear=True, ) @@ -185,6 +206,9 @@ def test_get_env_vars_no_dates(self): link_to_profile, _output_filename, _show_avatar, + sort_column, + sort_order, + skip_summary, ) = env.get_env_vars() self.assertEqual(organization, "org") @@ -199,6 +223,9 @@ def test_get_env_vars_no_dates(self): self.assertEqual(end_date, "") self.assertFalse(sponsor_info) self.assertTrue(link_to_profile) + self.assertEqual(sort_column, "username") + self.assertEqual(sort_order, "asc") + self.assertTrue(skip_summary) @patch.dict( os.environ, @@ -215,6 +242,9 @@ def test_get_env_vars_no_dates(self): "SPONSOR_INFO": "False", "LINK_TO_PROFILE": "True", "OUTPUT_FILENAME": "custom-report.md", + "SORT_COLUMN": "username", + "SORT_ORDER": "asc", + "SKIP_SUMMARY": "True", }, clear=True, ) @@ -235,6 +265,9 @@ def test_get_env_vars_custom_output_filename(self): _link_to_profile, output_filename, _show_avatar, + _sort_column, + _sort_order, + _skip_summary, ) = env.get_env_vars() self.assertEqual(output_filename, "custom-report.md") @@ -321,6 +354,9 @@ def test_get_env_vars_output_filename_empty_defaults(self): _link_to_profile, output_filename, _show_avatar, + _sort_column, + _sort_order, + _skip_summary, ) = env.get_env_vars() self.assertEqual(output_filename, "contributors.md") @@ -351,6 +387,9 @@ def test_get_env_vars_output_filename_whitespace_defaults(self): _link_to_profile, output_filename, _show_avatar, + _sort_column, + _sort_order, + _skip_summary, ) = env.get_env_vars() self.assertEqual(output_filename, "contributors.md") @@ -471,6 +510,9 @@ def test_get_env_vars_valid_date_range(self): _link_to_profile, _output_filename, _show_avatar, + _sort_column, + _sort_order, + _skip_summary, ) = env.get_env_vars() self.assertEqual(start_date, "2024-01-01") self.assertEqual(end_date, "2025-01-01") @@ -490,6 +532,48 @@ def test_validate_date_range_invalid_date_format_raises(self): "start_date and end_date must be in the format YYYY-MM-DD", ) + @patch.dict( + os.environ, + { + "ORGANIZATION": "org", + "REPOSITORY": "repo,repo2", + "GH_TOKEN": "token", + "SORT_COLUMN": "Whatever", + "SORT_ORDER": "ASc", + }, + clear=True, + ) + def test_get_env_vars_with_absent_or_invalid_sort_and_skip_summary_params(self): + """ + Test the get_env_vars function checking that default sorting and skip_summary params + are correctly applied in case of invalid (sort_column) or absent (skip_summary) definition, and that lowercase + conversion is applied (sort_order) + """ + + ( + _organization, + _repository_list, + _gh_app_id, + _gh_app_installation_id, + _gh_app_private_key, + _gh_app_enterprise_only, + _token, + _ghe, + _start_date, + _end_date, + _sponsor_info, + _link_to_profile, + _output_filename, + _show_avatar, + sort_column, + sort_order, + skip_summary, + ) = env.get_env_vars() + + self.assertEqual(sort_column, "contribution_count") + self.assertEqual(sort_order, "asc") + self.assertFalse(skip_summary) + if __name__ == "__main__": unittest.main() diff --git a/test_json_writer.py b/test_json_writer.py index e5c0052..95b3496 100644 --- a/test_json_writer.py +++ b/test_json_writer.py @@ -21,6 +21,8 @@ def setUp(self): "repository_list": ["repo1", "repo2"], "sponsor_info": False, "link_to_profile": False, + "sort_column": "contribution_count", + "sort_order": "desc", "contributors": [ { "username": "test_user", @@ -54,6 +56,8 @@ def test_write_to_json(self): repository_list=self.data["repository_list"], sponsor_info=self.data["sponsor_info"], link_to_profile=self.data["link_to_profile"], + sort_column=self.data["sort_column"], + sort_order=self.data["sort_order"], ) with open(self.filename, "r", encoding="utf-8") as f: result = json.load(f) diff --git a/test_markdown.py b/test_markdown.py index 006735e..52e0ac7 100644 --- a/test_markdown.py +++ b/test_markdown.py @@ -54,6 +54,9 @@ def test_write_to_markdown( "false", True, ghe, + "contribution_count", + "asc", + False, ) mock_file.assert_called_once_with("filename", "w", encoding="utf-8") @@ -118,6 +121,9 @@ def test_write_to_markdown_with_sponsors( "true", True, ghe, + "contribution_count", + "asc", + False, ) mock_file.assert_called_once_with("filename", "w", encoding="utf-8") @@ -182,6 +188,9 @@ def test_write_to_markdown_with_avatars( "false", True, ghe, + "contribution_count", + "asc", + False, show_avatar=True, ) @@ -249,6 +258,9 @@ def test_write_to_markdown_without_link_to_profile( "false", False, ghe, + "contribution_count", + "asc", + False, ) mock_file.assert_called_once_with("filename", "w", encoding="utf-8") @@ -312,6 +324,9 @@ def test_write_to_github_summary( "false", True, ghe, + "contribution_count", + "asc", + False, ) # Verify that open was called twice - once for the markdown file @@ -371,6 +386,9 @@ def test_write_to_markdown_with_organization( "false", True, ghe, + "contribution_count", + "asc", + False, ) mock_file.assert_called_once_with("filename", "w", encoding="utf-8") @@ -419,6 +437,9 @@ def test_write_to_markdown_empty_collaborators( "false", True, ghe, + "contribution_count", + "asc", + False, ) mock_file.assert_called_once_with("filename", "w", encoding="utf-8") @@ -479,6 +500,9 @@ def test_write_to_markdown_no_dates( "false", True, ghe, + "contribution_count", + "asc", + False, ) mock_file.assert_called_once_with("filename", "w", encoding="utf-8") @@ -529,6 +553,9 @@ def test_write_to_markdown_with_ghe( "false", True, ghe, + "contribution_count", + "asc", + False, ) mock_file.assert_called_once_with("filename", "w", encoding="utf-8") @@ -551,6 +578,257 @@ def test_write_to_markdown_with_ghe( ) 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_sort_username_asc( + self, mock_file, mock_env_get + ): # pylint: disable=unused-argument + """ + Test sorting by username in ascending order. + """ + person1 = contributor_stats.ContributorStats( + "zzz_user1", + "url", + 100, + "commit url", + "sponsor_url_1", + ) + person2 = contributor_stats.ContributorStats( + "aaa_user2", + "url2", + 200, + "commit url2", + "sponsor_url_2", + ) + collaborators = [ + person1, + person2, + ] + ghe = "" + + write_to_markdown( + collaborators, + "filename", + "2023-01-01", + "2023-01-02", + None, + "org/repo", + "false", + True, + ghe, + "username", + "asc", + False, + ) + + 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 | 0.0% |\n\n" + "| Username | Contribution Count | New Contributor | " + "Commits between 2023-01-01 and 2023-01-02 |\n" + "| --- | --- | --- | --- |\n" + "| @aaa_user2 | 200 | False | commit url2 |\n" + "| @zzz_user1 | 100 | False | commit url |\n" + "\n _this file was generated by the " + "[Contributors GitHub Action]" + "(https://github.com/github-community-projects/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 + @patch("builtins.open", new_callable=mock_open) + def test_write_to_markdown_sort_contribution_count_desc( + self, mock_file, mock_env_get + ): # pylint: disable=unused-argument + """ + Test sorting by contribution_count in descending order. + """ + person1 = contributor_stats.ContributorStats( + "user1", + "url", + 100, + "commit url", + "sponsor_url_1", + ) + person2 = contributor_stats.ContributorStats( + "user2", + "url2", + 200, + "commit url2", + "sponsor_url_2", + ) + collaborators = [ + person1, + person2, + ] + ghe = "" + + write_to_markdown( + collaborators, + "filename", + None, + None, + None, + "org/repo", + "false", + True, + ghe, + "contribution_count", + "desc", + False, + ) + + 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" + "- Repository: org/repo\n\n" + "| Total Contributors | Total Contributions |\n" + "| --- | --- |\n" + "| 2 | 300 |\n\n" + "| Username | All Time Contribution Count | All Commits |\n" + "| --- | --- | --- |\n" + "| @user2 | 200 | commit url2 |\n" + "| @user1 | 100 | commit url |\n" + "\n _this file was generated by the " + "[Contributors GitHub Action]" + "(https://github.com/github-community-projects/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 + @patch("builtins.open", new_callable=mock_open) + def test_write_to_markdown_with_organization_sort_new_contributor_desc( + self, mock_file, mock_env_get + ): # pylint: disable=unused-argument + """ + Test the write_to_markdown function with organization specified and sorting + by 'new_contributor' in descending order + """ + person1 = contributor_stats.ContributorStats( + "user1", + "url", + 100, + "https://github.com/org1/repo1/commits?author=user1", + "sponsor_url_1", + ) + person2 = contributor_stats.ContributorStats( + "user2", + "url2", + 200, + "https://github.com/org2/repo2/commits?author=user2, " + "https://github.com/org3/repo3/commits?author=user2", + "sponsor_url_2", + ) + # Set person2 as a new contributor since this cannot be set on initialization of the object + person2.new_contributor = True + collaborators = [ + person1, + person2, + ] + ghe = "" + + write_to_markdown( + collaborators, + "filename", + "2023-01-01", + "2023-01-02", + "test-org", + None, + "false", + True, + ghe, + "new_contributor", + "desc", + False, + ) + + 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" + "- Organization: test-org\n\n" + "| Total Contributors | Total Contributions | % New Contributors |\n" + "| --- | --- | --- |\n" + "| 2 | 300 | 50.0% |\n\n" + "| Username | Contribution Count | New Contributor | " + "Commits between 2023-01-01 and 2023-01-02 |\n" + "| --- | --- | --- | --- |\n" + "| @user2 | 200 | True | " + "[org2/repo2](https://github.com/org2/repo2/commits?author=user2), " + "[org3/repo3](https://github.com/org3/repo3/commits?author=user2), |\n" + "| @user1 | 100 | False | " + "[org1/repo1](https://github.com/org1/repo1/commits?author=user1), |\n" + "\n _this file was generated by the " + "[Contributors GitHub Action]" + "(https://github.com/github-community-projects/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 + @patch("builtins.open", new_callable=mock_open) + def test_write_to_markdown_skipping_summary( + self, mock_file, mock_env_get + ): # pylint: disable=unused-argument + """ + Test skipping the summary table. + """ + person1 = contributor_stats.ContributorStats( + "user1", + "url", + 100, + "commit url", + "sponsor_url_1", + ) + collaborators = [person1] + ghe = "" + + write_to_markdown( + collaborators, + "filename", + "2023-01-01", + "2023-01-02", + None, + "org/repo", + "false", + True, + ghe, + "contribution_count", + "asc", + 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" + "| Username | Contribution Count | New Contributor | " + "Commits between 2023-01-01 and 2023-01-02 |\n" + "| --- | --- | --- | --- |\n" + "| @user1 | 100 | False | commit url |\n" + "\n _this file was generated by the " + "[Contributors GitHub Action]" + "(https://github.com/github-community-projects/contributors)_\n" + ) + mock_file().write.assert_called_once_with(expected_content) + if __name__ == "__main__": unittest.main()