Repository: github/issue-metrics
Pull request #373 (2024-09-19 16:21:30)

feat: REPORT_TITLE and OUTPUT_FILE environment variables
Closes #160

# Pull Request

<!--
PR title needs to be prefixed with a conventional commit type
(build,chore,ci,docs,feat,fix,perf,refactor,revert,style,test)

It should also be brief and descriptive for a good changelog entry

examples: "feat: add new logger" or "fix: remove unused imports"
-->

## Proposed Changes

<!-- Describe what the changes are and link to a GitHub Issue if one exists -->

- [x] Adds the ability for users to designate custom report title and output files.
  - Defaults for each:
    - REPORT_TITLE: "Issue Metrics"
    - OUTPUT_FILE: "issue_metrics.md" if markdown and "issue_metrics.json" if json
- [x] Update `.env-example` and include changes there and in docs we forgot in #370 

## Readiness Checklist

### Author/Contributor

- [x] If documentation is needed for this change, has that been included in this pull request
- [x] run `make lint` and fix any issues that you have introduced
- [x] run `make test` and ensure you have test coverage for the lines you are introducing
- [ ] If publishing new data to the public (scorecards, security scan results, code quality results, live dashboards, etc.), please request review from `@jeffrey-luszcz`

### Reviewer

- [x] Label as either `fix`, `documentation`, `enhancement`, `infrastructure`, `maintenance`, or `breaking`



Patch:
diff --git a/.env-example b/.env-example
index c905ee3..c7d8193 100644
--- a/.env-example
+++ b/.env-example
@@ -11,4 +11,7 @@ HIDE_TIME_TO_CLOSE = "false"
 HIDE_TIME_TO_FIRST_RESPONSE = "false"
 IGNORE_USERS = "user1,user2"
 LABELS_TO_MEASURE = "waiting-for-review,waiting-for-manager"
+NON_MENTIONING_LINKS = "false"
+OUTPUT_FILE = ""
+REPORT_TITLE = "Issue Metrics"
 SEARCH_QUERY = "repo:owner/repo is:open is:issue"
diff --git a/README.md b/README.md
index 030ef4d..39c15f4 100644
--- a/README.md
+++ b/README.md
@@ -139,23 +139,25 @@ This action can be configured to authenticate with GitHub App Installation or Pe
 
 #### Other Configuration Options
 
-| field                         | required | default | description                                                                                                                                                                                     |
-| ----------------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `GH_ENTERPRISE_URL`           | False    | `""`    | URL of GitHub Enterprise instance to use for auth instead of github.com                                                                                                                         |
-| `HIDE_AUTHOR`                 | False    | False   | If set to `true`, the author will not be displayed in the generated Markdown file.                                                                                                              |
-| `HIDE_ITEMS_CLOSED_COUNT`     | False    | False   | If set to `true`, the number of items closed metric will not be displayed in the generated Markdown file.                                                                                       |
-| `HIDE_LABEL_METRICS`          | False    | False   | If set to `true`, the time in label metrics will not be displayed in the generated Markdown file.                                                                                               |
-| `HIDE_TIME_TO_ANSWER`         | False    | False   | If set to `true`, the time to answer a discussion will not be displayed in the generated Markdown file.                                                                                         |
-| `HIDE_TIME_TO_CLOSE`          | False    | False   | If set to `true`, the time to close will not be displayed in the generated Markdown file.                                                                                                       |
-| `HIDE_TIME_TO_FIRST_RESPONSE` | False    | False   | If set to `true`, the time to first response will not be displayed in the generated Markdown file.                                                                                              |
-| `IGNORE_USERS`                | False    | False   | A comma separated list of users to ignore when calculating metrics. (ie. `IGNORE_USERS: 'user1,user2'`). To ignore bots, append `[bot]` to the user (ie. `IGNORE_USERS: 'github-actions[bot]'`) |
-| `ENABLE_MENTOR_COUNT`         | False    | False   | If set to 'TRUE' count number of comments users left on discussions, issues and PRs and display number of active mentors                                                                        |
-| `MIN_MENTOR_COMMENTS`         | False    | 10      | Minimum number of comments to count as a mentor                                                                                                                                                 |
-| `MAX_COMMENTS_EVAL`           | False    | 20      | Maximum number of comments per thread to evaluate for mentor stats                                                                                                                              |
-| `HEAVILY_INVOLVED_CUTOFF`     | False    | 3       | Cutoff after which a mentor's comments in one issue are no longer counted against their total score                                                                                             |
-| `LABELS_TO_MEASURE`           | False    | `""`    | A comma separated list of labels to measure how much time the label is applied. If not provided, no labels durations will be measured. Not compatible with discussions at this time.            |
-| `NON_MENTIONING_LINKS`        | False    | False   | If set to `true`, will use non-mentioning GitHub links to avoid linking to the generated issue from the source repository. Links of the form `https://www.github.com` will be used.             |
-| `SEARCH_QUERY`                | True     | `""`    | The query by which you can filter issues/PRs which must contain a `repo:`, `org:`, `owner:`, or a `user:` entry. For discussions, include `type:discussions` in the query.                      |
+| field                         | required | default                                    | description                                                                                                                                                                                     |
+| ----------------------------- | -------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `GH_ENTERPRISE_URL`           | False    | `""`                                       | URL of GitHub Enterprise instance to use for auth instead of github.com                                                                                                                         |
+| `HIDE_AUTHOR`                 | False    | False                                      | If set to `true`, the author will not be displayed in the generated Markdown file.                                                                                                              |
+| `HIDE_ITEMS_CLOSED_COUNT`     | False    | False                                      | If set to `true`, the number of items closed metric will not be displayed in the generated Markdown file.                                                                                       |
+| `HIDE_LABEL_METRICS`          | False    | False                                      | If set to `true`, the time in label metrics will not be displayed in the generated Markdown file.                                                                                               |
+| `HIDE_TIME_TO_ANSWER`         | False    | False                                      | If set to `true`, the time to answer a discussion will not be displayed in the generated Markdown file.                                                                                         |
+| `HIDE_TIME_TO_CLOSE`          | False    | False                                      | If set to `true`, the time to close will not be displayed in the generated Markdown file.                                                                                                       |
+| `HIDE_TIME_TO_FIRST_RESPONSE` | False    | False                                      | If set to `true`, the time to first response will not be displayed in the generated Markdown file.                                                                                              |
+| `IGNORE_USERS`                | False    | False                                      | A comma separated list of users to ignore when calculating metrics. (ie. `IGNORE_USERS: 'user1,user2'`). To ignore bots, append `[bot]` to the user (ie. `IGNORE_USERS: 'github-actions[bot]'`) |
+| `ENABLE_MENTOR_COUNT`         | False    | False                                      | If set to 'TRUE' count number of comments users left on discussions, issues and PRs and display number of active mentors                                                                        |
+| `MIN_MENTOR_COMMENTS`         | False    | 10                                         | Minimum number of comments to count as a mentor                                                                                                                                                 |
+| `MAX_COMMENTS_EVAL`           | False    | 20                                         | Maximum number of comments per thread to evaluate for mentor stats                                                                                                                              |
+| `HEAVILY_INVOLVED_CUTOFF`     | False    | 3                                          | Cutoff after which a mentor's comments in one issue are no longer counted against their total score                                                                                             |
+| `LABELS_TO_MEASURE`           | False    | `""`                                       | A comma separated list of labels to measure how much time the label is applied. If not provided, no labels durations will be measured. Not compatible with discussions at this time.            |
+| `NON_MENTIONING_LINKS`        | False    | False                                      | If set to `true`, will use non-mentioning GitHub links to avoid linking to the generated issue from the source repository. Links of the form `https://www.github.com` will be used.             |
+| `OUTPUT_FILE`                 | False    | `issue_metrics.md` or `issue_metrics.json` | Output filename.                                                                                                                                                                                |
+| `REPORT_TITLE`                | False    | `"Issue Metrics"`                          | Title to have on the report issue.                                                                                                                                                              |
+| `SEARCH_QUERY`                | True     | `""`                                       | The query by which you can filter issues/PRs which must contain a `repo:`, `org:`, `owner:`, or a `user:` entry. For discussions, include `type:discussions` in the query.                      |
 
 ## Further Documentation
 
diff --git a/config.py b/config.py
index 9056abb..b45093a 100644
--- a/config.py
+++ b/config.py
@@ -40,6 +40,9 @@ class EnvVars:
         max_comments_eval (str): If set, defines the maximum number of comments to look at for mentor evaluation
         heavily_involved_cutoff (str): If set, defines the cutoff after which heavily involved commentors in
         search_query (str): Search query used to filter issues/prs/discussions on GitHub
+        non_mentioning_links (bool): If set to TRUE, links do not cause a notification in the desitnation repository
+        report_title (str): The title of the report
+        output_file (str): The name of the file to write the report to
     """
 
     def __init__(
@@ -63,6 +66,8 @@ class EnvVars:
         heavily_involved_cutoff: str,
         search_query: str,
         non_mentioning_links: bool,
+        report_title: str,
+        output_file: str,
     ):
         self.gh_app_id = gh_app_id
         self.gh_app_installation_id = gh_app_installation_id
@@ -83,6 +88,8 @@ class EnvVars:
         self.heavily_involved_cutoff = heavily_involved_cutoff
         self.search_query = search_query
         self.non_mentioning_links = non_mentioning_links
+        self.report_title = report_title
+        self.output_file = output_file
 
     def __repr__(self):
         return (
@@ -106,6 +113,8 @@ class EnvVars:
             f"{self.heavily_involved_cutoff},"
             f"{self.search_query}"
             f"{self.non_mentioning_links}"
+            f"{self.report_title}"
+            f"{self.output_file}"
         )
 
 
@@ -187,6 +196,9 @@ def get_env_vars(test: bool = False) -> EnvVars:
     if ignore_users:
         ignore_users_list = ignore_users.split(",")
 
+    report_title = os.getenv("REPORT_TITLE", "Issue Metrics")
+    output_file = os.getenv("OUTPUT_FILE", "")
+
     # Hidden columns
     hide_author = get_bool_env_var("HIDE_AUTHOR", False)
     hide_items_closed_count = get_bool_env_var("HIDE_ITEMS_CLOSED_COUNT", False)
@@ -220,4 +232,6 @@ def get_env_vars(test: bool = False) -> EnvVars:
         heavily_involved_cutoff,
         search_query,
         non_mentioning_links,
+        report_title,
+        output_file,
     )
diff --git a/issue_metrics.py b/issue_metrics.py
index 88c827f..d9aaf9a 100644
--- a/issue_metrics.py
+++ b/issue_metrics.py
@@ -307,6 +307,8 @@ def main():
     ignore_users = env_vars.ignore_users
     hide_items_closed_count = env_vars.hide_items_closed_count
     non_mentioning_links = env_vars.non_mentioning_links
+    report_title = env_vars.report_title
+    output_file = env_vars.output_file
 
     gh_app_id = env_vars.gh_app_id
     gh_app_installation_id = env_vars.gh_app_installation_id
@@ -401,6 +403,8 @@ def main():
         num_issues_closed,
         num_mentor_count,
         search_query,
+        report_title,
+        output_file,
     )
 
     write_to_markdown(
@@ -416,6 +420,8 @@ def main():
         search_query,
         hide_items_closed_count,
         non_mentioning_links,
+        report_title,
+        output_file,
     )
 
     max_char_count = 65535
diff --git a/json_writer.py b/json_writer.py
index 1b40e01..0b191dc 100644
--- a/json_writer.py
+++ b/json_writer.py
@@ -9,6 +9,7 @@ Functions:
         num_issues_opened: int,
         num_issues_closed: int,
         search_query: str,
+        output_file: str,
     ) -> str:
         Write the issues with metrics to a json file.
 
@@ -32,6 +33,7 @@ def write_to_json(
     num_issues_closed: Union[int, None],
     num_mentor_count: Union[int, None],
     search_query: str,
+    output_file: str,
 ) -> str:
     """
     Write the issues with metrics to a JSON file called issue_metrics.json.
@@ -168,7 +170,8 @@ def write_to_json(
             print(f"metrics={metrics_json}", file=file_handle)
 
     # Write the metrics to a JSON file
-    with open("issue_metrics.json", "w", encoding="utf-8") as file:
+    output_file_name = output_file if output_file else "issue_metrics.json"
+    with open(output_file_name, "w", encoding="utf-8") as file:
         json.dump(metrics, file, indent=4)
 
     return metrics_json
diff --git a/markdown_writer.py b/markdown_writer.py
index d2cde39..e9d8d3b 100644
--- a/markdown_writer.py
+++ b/markdown_writer.py
@@ -86,6 +86,8 @@ def write_to_markdown(
     hide_label_metrics=False,
     hide_items_closed_count=False,
     non_mentioning_links=False,
+    report_title="",
+    output_file="",
 ) -> None:
     """Write the issues with metrics to a markdown file.
 
@@ -105,14 +107,18 @@ def write_to_markdown(
         search_query (str): The search query used to find the issues.
         hide_label_metrics (bool): Represents whether the user has chosen to hide label metrics in the output
         hide_items_closed_count (bool): Represents whether the user has chosen to hide the number of items closed
+        non_mentioning_links (bool): Represents whether links do not cause a notification in the desitnation repository
+        report_title (str): The title of the report
+        output_file (str): The name of the file to write the report to
 
     Returns:
         None.
 
     """
     columns = get_non_hidden_columns(labels)
-    with open("issue_metrics.md", "w", encoding="utf-8") as file:
-        file.write("# Issue Metrics\n\n")
+    output_file_name = output_file if output_file else "issue_metrics.md"
+    with open(output_file_name, "w", encoding="utf-8") as file:
+        file.write(f"# {report_title}\n\n")
 
         # If all the metrics are None, then there are no issues
         if not issues_with_metrics or len(issues_with_metrics) == 0:


Test patch:
diff --git a/test_config.py b/test_config.py
index e52326b..3a96315 100644
--- a/test_config.py
+++ b/test_config.py
@@ -79,6 +79,9 @@ class TestGetEnvVars(unittest.TestCase):
             "HIDE_TIME_TO_FIRST_RESPONSE",
             "IGNORE_USERS",
             "LABELS_TO_MEASURE",
+            "NON_MENTIONING_LINKS",
+            "OUTPUT_FILE",
+            "REPORT_TITLE",
             "SEARCH_QUERY",
         ]
         for key in env_keys:
@@ -101,6 +104,9 @@ class TestGetEnvVars(unittest.TestCase):
             "HIDE_TIME_TO_FIRST_RESPONSE": "",
             "IGNORE_USERS": "",
             "LABELS_TO_MEASURE": "",
+            "NON_MENTIONING_LINKS": "false",
+            "OUTPUT_FILE": "",
+            "REPORT_TITLE": "",
             "SEARCH_QUERY": SEARCH_QUERY,
         },
         clear=True,
@@ -127,6 +133,8 @@ class TestGetEnvVars(unittest.TestCase):
             "3",
             SEARCH_QUERY,
             False,
+            "",
+            "",
         )
         result = get_env_vars(True)
         self.assertEqual(str(result), str(expected_result))
@@ -147,6 +155,9 @@ class TestGetEnvVars(unittest.TestCase):
             "HIDE_TIME_TO_FIRST_RESPONSE": "",
             "IGNORE_USERS": "",
             "LABELS_TO_MEASURE": "",
+            "NON_MENTIONING_LINKS": "false",
+            "OUTPUT_FILE": "",
+            "REPORT_TITLE": "",
             "SEARCH_QUERY": SEARCH_QUERY,
         },
         clear=True,
@@ -173,6 +184,8 @@ class TestGetEnvVars(unittest.TestCase):
             "3",
             SEARCH_QUERY,
             False,
+            "",
+            "",
         )
         result = get_env_vars(True)
         self.assertEqual(str(result), str(expected_result))
@@ -228,6 +241,9 @@ class TestGetEnvVars(unittest.TestCase):
             "HIDE_TIME_TO_FIRST_RESPONSE": "true",
             "IGNORE_USERS": "",
             "LABELS_TO_MEASURE": "waiting-for-review,waiting-for-manager",
+            "NON_MENTIONING_LINKS": "true",
+            "OUTPUT_FILE": "issue_metrics.md",
+            "REPORT_TITLE": "Issue Metrics",
             "SEARCH_QUERY": SEARCH_QUERY,
         },
     )
@@ -252,7 +268,9 @@ class TestGetEnvVars(unittest.TestCase):
             20,
             3,
             SEARCH_QUERY,
-            False,
+            True,
+            "Issue Metrics",
+            "issue_metrics.md",
         )
         result = get_env_vars(True)
         self.assertEqual(str(result), str(expected_result))
@@ -290,6 +308,8 @@ class TestGetEnvVars(unittest.TestCase):
             "3",
             SEARCH_QUERY,
             False,
+            "Issue Metrics",
+            "",
         )
         result = get_env_vars(True)
         self.assertEqual(str(result), str(expected_result))
diff --git a/test_json_writer.py b/test_json_writer.py
index fe1fe1b..5dae93c 100644
--- a/test_json_writer.py
+++ b/test_json_writer.py
@@ -115,6 +115,7 @@ class TestWriteToJson(unittest.TestCase):
                 num_issues_closed=num_issues_closed,
                 num_mentor_count=num_mentor_count,
                 search_query="is:issue repo:owner/repo",
+                output_file="issue_metrics.json",
             ),
             json.dumps(expected_output),
         )
@@ -206,6 +207,7 @@ class TestWriteToJson(unittest.TestCase):
                 num_issues_closed=num_issues_closed,
                 num_mentor_count=num_mentor_count,
                 search_query="is:issue repo:owner/repo",
+                output_file="issue_metrics.json",
             ),
             json.dumps(expected_output),
         )
diff --git a/test_markdown_writer.py b/test_markdown_writer.py
index 0eb5e80..3678d0b 100644
--- a/test_markdown_writer.py
+++ b/test_markdown_writer.py
@@ -92,6 +92,8 @@ class TestWriteToMarkdown(unittest.TestCase):
             num_mentor_count=num_mentor_count,
             labels=["bug"],
             search_query="is:issue is:open label:bug",
+            report_title="Issue Metrics",
+            output_file="issue_metrics.md",
         )
 
         # Check that the function writes the correct markdown file
@@ -191,6 +193,8 @@ class TestWriteToMarkdown(unittest.TestCase):
             num_issues_closed=num_issues_closed,
             num_mentor_count=num_mentor_count,
             labels=["bug"],
+            report_title="Issue Metrics",
+            output_file="issue_metrics.md",
         )
 
         # Check that the function writes the correct markdown file
@@ -227,7 +231,17 @@ class TestWriteToMarkdown(unittest.TestCase):
         """Test that write_to_markdown writes the correct markdown file when no issues are found."""
         # Call the function with no issues
         with patch("builtins.open", mock_open()) as mock_open_file:
-            write_to_markdown(None, None, None, None, None, None, None, None)
+            write_to_markdown(
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                report_title="Issue Metrics",
+            )
 
         # Check that the file was written correctly
         expected_output = [
@@ -316,6 +330,8 @@ class TestWriteToMarkdownWithEnv(unittest.TestCase):
             hide_label_metrics=True,
             hide_items_closed_count=True,
             non_mentioning_links=True,
+            report_title="Issue Metrics",
+            output_file="issue_metrics.md",
         )
 
         # Check that the function writes the correct markdown file