From d87d4e6673c51c210e077e446fe4c0e9e259bb7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:07:38 +0000 Subject: [PATCH 01/33] chore(deps): bump the dependencies group with 2 updates Bumps the dependencies group with 2 updates: [flake8](https://github.com/pycqa/flake8) and [pytest](https://github.com/pytest-dev/pytest). Updates `flake8` from 7.2.0 to 7.3.0 - [Commits](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0) Updates `pytest` from 8.4.0 to 8.4.1 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.0...8.4.1) --- updated-dependencies: - dependency-name: flake8 dependency-version: 7.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: pytest dependency-version: 8.4.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-test.txt b/requirements-test.txt index b69e850..3b294cb 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,9 +1,9 @@ black==25.1.0 -flake8==7.2.0 +flake8==7.3.0 mypy==1.16.1 mypy-extensions==1.1.0 pylint==3.3.7 -pytest==8.4.0 +pytest==8.4.1 pytest-cov==6.2.1 types-pytz==2025.2.0.20250516 types-requests==2.32.4.20250611 From 06a4304ebc5f4b5ca08e6af2cd533257451a1dcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 20:38:30 +0000 Subject: [PATCH 02/33] chore(deps): bump python-dotenv in the dependencies group --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.1.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index efe5a96..2d48a84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ github3.py==4.0.1 numpy==2.2.4 -python-dotenv==1.1.0 +python-dotenv==1.1.1 pytz==2025.2 requests==2.32.4 From 4dad97e7abb2bbcc0dd66359f3d76c85a393bcc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 20:43:41 +0000 Subject: [PATCH 03/33] chore(deps): bump github/codeql-action in the dependencies group Bumps the dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.29.0 to 3.29.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ce28f5bb42b7a9f2c824e633a3f6ee835bab6858...181d5eefc20863364f96762470ba6f862bdef56b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b1ce6af..047161a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -42,6 +42,6 @@ jobs: path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.24.9 + uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.24.9 with: sarif_file: results.sarif From cd8e18d66f5a327ccef171f4633825be8ea1c602 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:22:21 +0000 Subject: [PATCH 04/33] chore(deps): bump python from `f2fdaec` to `6544e0e` Bumps python from `f2fdaec` to `6544e0e`. --- updated-dependencies: - dependency-name: python dependency-version: 3.13-slim dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f7112a5..26a5817 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ #checkov:skip=CKV_DOCKER_2 #checkov:skip=CKV_DOCKER_3 -FROM python:3.13-slim@sha256:f2fdaec50160418e0c2867ba3e254755edd067171725886d5d303fd7057bbf81 +FROM python:3.13-slim@sha256:6544e0e002b40ae0f59bc3618b07c1e48064c4faed3a15ae2fbd2e8f663e8283 LABEL com.github.actions.name="issue-metrics" \ com.github.actions.description="Gather metrics on issues/prs/discussions such as time to first response, count of issues opened, closed, etc." \ com.github.actions.icon="check-square" \ From fb669c9330d041c418139860d16458494f378b42 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 17 Jul 2025 23:20:40 +0000 Subject: [PATCH 05/33] feat: add status column feature with configs to hide it Signed-off-by: Zack Koppert --- README.md | 5 +++-- config.py | 6 ++++++ markdown_writer.py | 8 ++++++++ test_assignee_functionality.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e63018c..12e498b 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe ##### GitHub App Installation | field | required | default | description | -| ---------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ---------------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GH_APP_ID` | True | `""` | GitHub Application ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | | `GH_APP_INSTALLATION_ID` | True | `""` | GitHub Application Installation ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | | `GH_APP_PRIVATE_KEY` | True | `""` | GitHub Application Private Key. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | @@ -154,7 +154,8 @@ This action can be configured to authenticate with GitHub App Installation or Pe | `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. | -| `HIDE_CREATED_AT` | False | True | If set to `true`, the creation timestmap will not be displayed in the generated Markdown file. | +| `HIDE_STATUS` | False | True | If set to `true`, the status column will not be shown | +| `HIDE_CREATED_AT` | False | True | If set to `true`, the creation timestamp will not be displayed in the generated Markdown file. | | `DRAFT_PR_TRACKING` | False | False | If set to `true`, draft PRs will be included in the metrics as a new column and in the summary stats. | | `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]'`) Users in this list will also have their authored issues and pull requests removed from the Markdown table. | | `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 | diff --git a/config.py b/config.py index 55768dc..baa0097 100644 --- a/config.py +++ b/config.py @@ -39,6 +39,8 @@ class EnvVars: hide_time_to_close (bool): If true, the time to close metric is hidden in the output hide_time_to_first_response (bool): If true, the time to first response metric is hidden in the output + hide_created_at (bool): If true, the created at timestamp is hidden in the output + hide_status (bool): If true, the status column is hidden in the output ignore_users (List[str]): List of usernames to ignore when calculating metrics labels_to_measure (List[str]): List of labels to measure how much time the label is applied enable_mentor_count (bool): If set to TRUE, compute number of mentors @@ -73,6 +75,7 @@ def __init__( hide_time_to_close: bool, hide_time_to_first_response: bool, hide_created_at: bool, + hide_status: bool, # New attribute ignore_user: List[str], labels_to_measure: List[str], enable_mentor_count: bool, @@ -102,6 +105,7 @@ def __init__( self.hide_time_to_close = hide_time_to_close self.hide_time_to_first_response = hide_time_to_first_response self.hide_created_at = hide_created_at + self.hide_status = hide_status # Initialize the new attribute self.enable_mentor_count = enable_mentor_count self.min_mentor_comments = min_mentor_comments self.max_comments_eval = max_comments_eval @@ -238,6 +242,7 @@ def get_env_vars(test: bool = False) -> EnvVars: hide_time_to_close = get_bool_env_var("HIDE_TIME_TO_CLOSE", False) hide_time_to_first_response = get_bool_env_var("HIDE_TIME_TO_FIRST_RESPONSE", False) hide_created_at = get_bool_env_var("HIDE_CREATED_AT", True) + hide_status = get_bool_env_var("HIDE_STATUS", True) # New attribute enable_mentor_count = get_bool_env_var("ENABLE_MENTOR_COUNT", False) min_mentor_comments = os.getenv("MIN_MENTOR_COMMENTS", "10") max_comments_eval = os.getenv("MAX_COMMENTS_EVAL", "20") @@ -259,6 +264,7 @@ def get_env_vars(test: bool = False) -> EnvVars: hide_time_to_close, hide_time_to_first_response, hide_created_at, + hide_status, ignore_users_list, labels_to_measure_list, enable_mentor_count, diff --git a/markdown_writer.py b/markdown_writer.py index efaf0ac..e83fa23 100644 --- a/markdown_writer.py +++ b/markdown_writer.py @@ -75,6 +75,10 @@ def get_non_hidden_columns(labels) -> List[str]: if not hide_time_to_answer: columns.append("Time to answer") + hide_status = env_vars.hide_status # Check the new attribute + if not hide_status: + columns.append("Status") # Add the 'status' column + enable_time_in_draft = env_vars.draft_pr_tracking if enable_time_in_draft: columns.append("Time in draft") @@ -232,6 +236,8 @@ def write_to_markdown( file.write(f" {issue.label_metrics[label]} |") if "Created At" in columns: file.write(f" {issue.created_at} |") + if "Status" in columns: # Check if 'Status' column is to be displayed + file.write(f" {issue.status} |") # Write the status of the issue file.write("\n") file.write( "\n_This report was generated with the \ @@ -324,6 +330,8 @@ def write_overall_metrics_tables( f"| {stats_time_in_labels['med'][label]} " f"| {stats_time_in_labels['90p'][label]} |\n" ) + if "Status" in columns: # Add logic for the 'status' column + file.write("| Status | | | |\n") file.write("\n") # Write count stats to a separate table diff --git a/test_assignee_functionality.py b/test_assignee_functionality.py index 1c12a9b..890fc62 100644 --- a/test_assignee_functionality.py +++ b/test_assignee_functionality.py @@ -75,6 +75,34 @@ def test_get_non_hidden_columns_hides_both_assignee_and_author(self): self.assertNotIn("Assignee", columns) self.assertNotIn("Author", columns) + @patch.dict( + os.environ, + { + "GH_TOKEN": "test_token", + "SEARCH_QUERY": "is:issue is:open repo:user/repo", + "HIDE_STATUS": "false", + }, + clear=True, + ) + def test_get_non_hidden_columns_includes_status_by_default(self): + """Test that status column is included by default.""" + columns = get_non_hidden_columns(labels=None) + self.assertIn("Status", columns) + + @patch.dict( + os.environ, + { + "GH_TOKEN": "test_token", + "SEARCH_QUERY": "is:issue is:open repo:user/repo", + "HIDE_STATUS": "true", + }, + clear=True, + ) + def test_get_non_hidden_columns_hides_status_when_env_set(self): + """Test that status column is hidden when HIDE_STATUS is true.""" + columns = get_non_hidden_columns(labels=None) + self.assertNotIn("Status", columns) + def test_assignee_column_position(self): """Test that assignee column appears before author column.""" with patch.dict( From 155da571f82b5998414aae45ba10d8b603931111 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 17 Jul 2025 16:28:31 -0700 Subject: [PATCH 06/33] fix: add hide_status to the env class and fix tests Signed-off-by: Zack Koppert --- config.py | 7 ++++--- test_config.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index baa0097..475aa34 100644 --- a/config.py +++ b/config.py @@ -75,7 +75,7 @@ def __init__( hide_time_to_close: bool, hide_time_to_first_response: bool, hide_created_at: bool, - hide_status: bool, # New attribute + hide_status: bool, ignore_user: List[str], labels_to_measure: List[str], enable_mentor_count: bool, @@ -105,7 +105,7 @@ def __init__( self.hide_time_to_close = hide_time_to_close self.hide_time_to_first_response = hide_time_to_first_response self.hide_created_at = hide_created_at - self.hide_status = hide_status # Initialize the new attribute + self.hide_status = hide_status self.enable_mentor_count = enable_mentor_count self.min_mentor_comments = min_mentor_comments self.max_comments_eval = max_comments_eval @@ -134,6 +134,7 @@ def __repr__(self): f"{self.hide_time_to_close}," f"{self.hide_time_to_first_response}," f"{self.hide_created_at}," + f"{self.hide_status}," f"{self.ignore_users}," f"{self.labels_to_measure}," f"{self.enable_mentor_count}," @@ -242,7 +243,7 @@ def get_env_vars(test: bool = False) -> EnvVars: hide_time_to_close = get_bool_env_var("HIDE_TIME_TO_CLOSE", False) hide_time_to_first_response = get_bool_env_var("HIDE_TIME_TO_FIRST_RESPONSE", False) hide_created_at = get_bool_env_var("HIDE_CREATED_AT", True) - hide_status = get_bool_env_var("HIDE_STATUS", True) # New attribute + hide_status = get_bool_env_var("HIDE_STATUS", True) enable_mentor_count = get_bool_env_var("ENABLE_MENTOR_COUNT", False) min_mentor_comments = os.getenv("MIN_MENTOR_COMMENTS", "10") max_comments_eval = os.getenv("MAX_COMMENTS_EVAL", "20") diff --git a/test_config.py b/test_config.py index 537d157..49435fa 100644 --- a/test_config.py +++ b/test_config.py @@ -131,6 +131,7 @@ def test_get_env_vars_with_github_app(self): hide_time_to_close=False, hide_time_to_first_response=False, hide_created_at=True, + hide_status=True, ignore_user=[], labels_to_measure=[], enable_mentor_count=False, @@ -186,6 +187,7 @@ def test_get_env_vars_with_token(self): hide_time_to_close=False, hide_time_to_first_response=False, hide_created_at=True, + hide_status=True, ignore_user=[], labels_to_measure=[], enable_mentor_count=False, @@ -276,6 +278,7 @@ def test_get_env_vars_optional_values(self): hide_time_to_close=True, hide_time_to_first_response=True, hide_created_at=True, + hide_status=True, ignore_user=[], labels_to_measure=["waiting-for-review", "waiting-for-manager"], enable_mentor_count=False, @@ -320,6 +323,7 @@ def test_get_env_vars_optionals_are_defaulted(self): hide_time_to_close=False, hide_time_to_first_response=False, hide_created_at=True, + hide_status=True, ignore_user=[], labels_to_measure=[], enable_mentor_count=False, From 38ca2915327c8ae6a9cc2a1e06d7acd883df950c Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 17 Jul 2025 17:02:33 -0700 Subject: [PATCH 07/33] feat: connect status up to actual api call Signed-off-by: Zack Koppert --- classes.py | 3 +++ issue_metrics.py | 4 ++++ markdown_writer.py | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/classes.py b/classes.py index 20ab9b3..d24f430 100644 --- a/classes.py +++ b/classes.py @@ -24,6 +24,7 @@ class IssueWithMetrics: label_metrics (dict, optional): A dictionary containing the label metrics mentor_activity (dict, optional): A dictionary containing active mentors created_at (datetime, optional): The time the issue was created. + status (str, optional): The status of the issue, e.g., "open", "closed as completed", """ # pylint: disable=too-many-instance-attributes @@ -42,6 +43,7 @@ def __init__( created_at=None, assignee=None, assignees=None, + status=None, ): self.title = title self.html_url = html_url @@ -55,3 +57,4 @@ def __init__( self.label_metrics = labels_metrics self.mentor_activity = mentor_activity self.created_at = created_at + self.status = status diff --git a/issue_metrics.py b/issue_metrics.py index e7f5982..6664535 100644 --- a/issue_metrics.py +++ b/issue_metrics.py @@ -175,8 +175,12 @@ def get_per_issue_metrics( issue_with_metrics.time_to_close = measure_time_to_close( issue, None ) + if env_vars.hide_status is False: + issue_with_metrics.status = f"{issue.issue.state} as {issue.issue.state_reason}" # type: ignore elif issue.state == "open": # type: ignore num_issues_open += 1 + if env_vars.hide_status is False: + issue_with_metrics.status = f"{issue.issue.state}" # type: ignore if not env_vars.hide_created_at: if isinstance(issue, github3.search.IssueSearchResult): # type: ignore issue_with_metrics.created_at = issue.issue.created_at # type: ignore diff --git a/markdown_writer.py b/markdown_writer.py index e83fa23..7a88a8a 100644 --- a/markdown_writer.py +++ b/markdown_writer.py @@ -75,9 +75,9 @@ def get_non_hidden_columns(labels) -> List[str]: if not hide_time_to_answer: columns.append("Time to answer") - hide_status = env_vars.hide_status # Check the new attribute + hide_status = env_vars.hide_status if not hide_status: - columns.append("Status") # Add the 'status' column + columns.append("Status") enable_time_in_draft = env_vars.draft_pr_tracking if enable_time_in_draft: @@ -236,8 +236,8 @@ def write_to_markdown( file.write(f" {issue.label_metrics[label]} |") if "Created At" in columns: file.write(f" {issue.created_at} |") - if "Status" in columns: # Check if 'Status' column is to be displayed - file.write(f" {issue.status} |") # Write the status of the issue + if "Status" in columns: + file.write(f" {issue.status} |") file.write("\n") file.write( "\n_This report was generated with the \ From f6ab3d4ff6eb802a7e66ddbfe62040594377ccb5 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 17 Jul 2025 18:33:58 -0700 Subject: [PATCH 08/33] test: fix tests and add test for hide status is true Signed-off-by: Zack Koppert --- test_markdown_writer.py | 140 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 129 insertions(+), 11 deletions(-) diff --git a/test_markdown_writer.py b/test_markdown_writer.py index bf3612c..0fbcad3 100644 --- a/test_markdown_writer.py +++ b/test_markdown_writer.py @@ -23,6 +23,7 @@ "GH_TOKEN": "test_token", "DRAFT_PR_TRACKING": "True", "HIDE_CREATED_AT": "False", + "HIDE_STATUS": "False", }, ) class TestWriteToMarkdown(unittest.TestCase): @@ -128,20 +129,21 @@ def test_write_to_markdown(self): "| Time to answer | 4 days, 0:00:00 | 4 days, 0:00:00 | 4 days, 0:00:00 |\n" "| Time in draft | 1 day, 0:00:00 | 1 day, 0:00:00 | 1 day, 0:00:00 |\n" "| Time spent in bug | 1 day, 12:00:00 | 1 day, 12:00:00 | 1 day, 12:00:00 |\n" + "| Status | | | |\n" "\n" "| Metric | Count |\n" "| --- | ---: |\n" "| Number of items that remain open | 2 |\n" "| Number of items closed | 1 |\n" "| Total number of items created | 2 |\n\n" - "| Title | URL | Assignee | Author | Time to first response | Time to close |" - " Time to answer | Time in draft | Time spent in bug | Created At |\n" - "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n" + "| Title | URL | Assignee | Author | Time to first response | Time to close | " + "Time to answer | Status | Time in draft | Time spent in bug | Created At |\n" + "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n" "| Issue 1 | https://github.com/user/repo/issues/1 | [charlie](https://github.com/charlie) | " "[alice](https://github.com/alice) | 1 day, 0:00:00 | 2 days, 0:00:00 | 3 days, 0:00:00 | " - "1 day, 0:00:00 | 4 days, 0:00:00 | -5 days, 0:00:00 |\n" + "1 day, 0:00:00 | 4 days, 0:00:00 | -5 days, 0:00:00 | None |\n" "| Issue 2 | https://github.com/user/repo/issues/2 | None | [bob](https://github.com/bob) | 3 days, 0:00:00 | " - "4 days, 0:00:00 | 5 days, 0:00:00 | 1 day, 0:00:00 | 2 days, 0:00:00 | -5 days, 0:00:00 |\n\n" + "4 days, 0:00:00 | 5 days, 0:00:00 | 1 day, 0:00:00 | 2 days, 0:00:00 | -5 days, 0:00:00 | None |\n\n" "_This report was generated with the [Issue Metrics Action](https://github.com/github/issue-metrics)_\n" "Search query used to find these items: `is:issue is:open label:bug`\n" ) @@ -182,6 +184,7 @@ def test_write_to_markdown_with_vertical_bar_in_title(self): time_to_first_response=timedelta(days=3), time_to_close=timedelta(days=4), time_to_answer=timedelta(days=5), + time_in_draft=None, labels_metrics={"bug": timedelta(days=2)}, ), ] @@ -243,21 +246,22 @@ def test_write_to_markdown_with_vertical_bar_in_title(self): "| Time to answer | 4 days, 0:00:00 | 4 days, 0:00:00 | 4 days, 0:00:00 |\n" "| Time in draft | 1 day, 0:00:00 | 1 day, 0:00:00 | 1 day, 0:00:00 |\n" "| Time spent in bug | 1 day, 12:00:00 | 1 day, 12:00:00 | 1 day, 12:00:00 |\n" + "| Status | | | |\n" "\n" "| Metric | Count |\n" "| --- | ---: |\n" "| Number of items that remain open | 2 |\n" "| Number of items closed | 1 |\n" "| Total number of items created | 2 |\n\n" - "| Title | URL | Assignee | Author | Time to first response | Time to close |" - " Time to answer | Time in draft | Time spent in bug | Created At |\n" - "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n" + "| Title | URL | Assignee | Author | Time to first response | Time to close | " + "Time to answer | Status | Time in draft | Time spent in bug | Created At |\n" + "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n" "| Issue 1 | https://github.com/user/repo/issues/1 | [charlie](https://github.com/charlie) | " "[alice](https://github.com/alice) | 1 day, 0:00:00 | 2 days, 0:00:00 | 3 days, 0:00:00 | " - "1 day, 0:00:00 | 1 day, 0:00:00 | -5 days, 0:00:00 |\n" + "1 day, 0:00:00 | 1 day, 0:00:00 | -5 days, 0:00:00 | None |\n" "| feat| Issue 2 | https://github.com/user/repo/issues/2 | None | " "[bob](https://github.com/bob) | 3 days, 0:00:00 | " - "4 days, 0:00:00 | 5 days, 0:00:00 | None | 2 days, 0:00:00 | -5 days, 0:00:00 |\n\n" + "4 days, 0:00:00 | 5 days, 0:00:00 | None | 2 days, 0:00:00 | -5 days, 0:00:00 | None |\n\n" "_This report was generated with the [Issue Metrics Action](https://github.com/github/issue-metrics)_\n" ) self.assertEqual(content, expected_content) @@ -308,6 +312,7 @@ def test_write_to_markdown_no_issues(self): "HIDE_LABEL_METRICS": "True", "NON_MENTIONING_LINKS": "True", "GH_ENTERPRISE_URL": "https://ghe.com", + "HIDE_STATUS": "False", }, ) class TestWriteToMarkdownWithEnv(unittest.TestCase): @@ -400,7 +405,116 @@ def test_writes_markdown_file_with_non_hidden_columns_only(self): "| Number of items that remain open | 2 |\n" "| Number of most active mentors | 5 |\n" "| Total number of items created | 2 |\n\n" - "| Title | URL | Assignee | Author | Created At |\n" + "| Title | URL | Assignee | Author | Status | Created At |\n" + "| --- | --- | --- | --- | --- | --- |\n" + "| Issue 1 | https://www.ghe.com/user/repo/issues/1 | [charlie](https://ghe.com/charlie) | " + "[alice](https://ghe.com/alice) | -5 days, 0:00:00 | None |\n" + "| Issue 2 | https://www.ghe.com/user/repo/issues/2 | None | [bob](https://ghe.com/bob) | -5 days, 0:00:00 | None |\n\n" + "_This report was generated with the [Issue Metrics Action](https://github.com/github/issue-metrics)_\n" + "Search query used to find these items: `repo:user/repo is:issue`\n" + ) + self.assertEqual(content, expected_content) + os.remove("issue_metrics.md") + + @patch.dict( + os.environ, + { + "SEARCH_QUERY": "is:open repo:user/repo", + "GH_TOKEN": "test_token", + "HIDE_CREATED_AT": "False", + "HIDE_TIME_TO_FIRST_RESPONSE": "True", + "HIDE_TIME_TO_CLOSE": "True", + "HIDE_TIME_TO_ANSWER": "True", + "HIDE_LABEL_METRICS": "True", + "NON_MENTIONING_LINKS": "True", + "GH_ENTERPRISE_URL": "https://ghe.com", + "HIDE_STATUS": "True", # Status column should be hidden + }, + ) + def test_writes_markdown_file_with_hidden_status_column(self): + """ + Test that write_to_markdown writes the correct markdown file + when HIDE_STATUS is set to True, ensuring the Status column + is not present in the output. + """ + # Create mock data + issues_with_metrics = [ + IssueWithMetrics( + title="Issue 1", + html_url="https://ghe.com/user/repo/issues/1", + author="alice", + assignee="charlie", + assignees=["charlie"], + created_at=timedelta(days=-5), + time_to_first_response=timedelta(minutes=10), + time_to_close=timedelta(days=1), + time_to_answer=timedelta(hours=2), + time_in_draft=timedelta(days=1), + labels_metrics={ + "label1": timedelta(days=1), + }, + ), + IssueWithMetrics( + title="Issue 2", + html_url="https://ghe.com/user/repo/issues/2", + author="bob", + assignee=None, + assignees=[], + created_at=timedelta(days=-5), + time_to_first_response=timedelta(minutes=20), + time_to_close=timedelta(days=2), + time_to_answer=timedelta(hours=4), + labels_metrics={ + "label1": timedelta(days=1), + }, + ), + ] + average_time_to_first_response = timedelta(minutes=15) + average_time_to_close = timedelta(days=1.5) + average_time_to_answer = timedelta(hours=3) + average_time_in_draft = timedelta(days=1) + average_time_in_labels = { + "label1": timedelta(days=1), + } + num_issues_opened = 2 + num_issues_closed = 2 + num_mentor_count = 5 + ghe = "https://ghe.com" + + # Call the function + write_to_markdown( + issues_with_metrics=issues_with_metrics, + average_time_to_first_response=average_time_to_first_response, + average_time_to_close=average_time_to_close, + average_time_to_answer=average_time_to_answer, + average_time_in_labels=average_time_in_labels, + average_time_in_draft=average_time_in_draft, + num_issues_opened=num_issues_opened, + num_issues_closed=num_issues_closed, + num_mentor_count=num_mentor_count, + labels=["label1"], + search_query="repo:user/repo is:issue", + hide_label_metrics=True, + hide_items_closed_count=True, + enable_mentor_count=True, + non_mentioning_links=True, + report_title="Issue Metrics", + output_file="issue_metrics.md", + ghe=ghe, + ) + + # Check that the function writes the correct markdown file + with open("issue_metrics.md", "r", encoding="utf-8") as file: + content = file.read() + + expected_content = ( + "# Issue Metrics\n\n" + "| Metric | Count |\n" + "| --- | ---: |\n" + "| Number of items that remain open | 2 |\n" + "| Number of most active mentors | 5 |\n" + "| Total number of items created | 2 |\n\n" + "| Title | URL | Assignee | Author | Created At |\n" # Status column should be missing "| --- | --- | --- | --- | --- |\n" "| Issue 1 | https://www.ghe.com/user/repo/issues/1 | [charlie](https://ghe.com/charlie) | " "[alice](https://ghe.com/alice) | -5 days, 0:00:00 |\n" @@ -410,3 +524,7 @@ def test_writes_markdown_file_with_non_hidden_columns_only(self): ) self.assertEqual(content, expected_content) os.remove("issue_metrics.md") + + +if __name__ == "__main__": + unittest.main() From 354444a7d5846ba085332bb3e1f94a6ec49c05c0 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 17 Jul 2025 18:41:11 -0700 Subject: [PATCH 09/33] docs: fix formatting Signed-off-by: Zack Koppert --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12e498b..596bfc3 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe ##### GitHub App Installation | field | required | default | description | -| ---------------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ---------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GH_APP_ID` | True | `""` | GitHub Application ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | | `GH_APP_INSTALLATION_ID` | True | `""` | GitHub Application Installation ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | | `GH_APP_PRIVATE_KEY` | True | `""` | GitHub Application Private Key. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | @@ -154,7 +154,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe | `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. | -| `HIDE_STATUS` | False | True | If set to `true`, the status column will not be shown | +| `HIDE_STATUS` | False | True | If set to `true`, the status column will not be shown | | `HIDE_CREATED_AT` | False | True | If set to `true`, the creation timestamp will not be displayed in the generated Markdown file. | | `DRAFT_PR_TRACKING` | False | False | If set to `true`, draft PRs will be included in the metrics as a new column and in the summary stats. | | `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]'`) Users in this list will also have their authored issues and pull requests removed from the Markdown table. | From 117b899213b701849ea2ad27032d93ba08d59dad Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 17 Jul 2025 21:00:16 -0700 Subject: [PATCH 10/33] fix: pythonic logic Co-authored-by: Jason Meridth <35014+jmeridth@users.noreply.github.com> Signed-off-by: Zack Koppert --- issue_metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issue_metrics.py b/issue_metrics.py index 6664535..53df4fa 100644 --- a/issue_metrics.py +++ b/issue_metrics.py @@ -175,7 +175,7 @@ def get_per_issue_metrics( issue_with_metrics.time_to_close = measure_time_to_close( issue, None ) - if env_vars.hide_status is False: + if not env_vars.hide_status: issue_with_metrics.status = f"{issue.issue.state} as {issue.issue.state_reason}" # type: ignore elif issue.state == "open": # type: ignore num_issues_open += 1 From 543e100b8d0ab9112849b4a09dfe4b0a0a5badfc Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 17 Jul 2025 21:01:46 -0700 Subject: [PATCH 11/33] fix: pythonic logic Co-authored-by: Jason Meridth <35014+jmeridth@users.noreply.github.com> Signed-off-by: Zack Koppert --- issue_metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issue_metrics.py b/issue_metrics.py index 53df4fa..0c86912 100644 --- a/issue_metrics.py +++ b/issue_metrics.py @@ -179,7 +179,7 @@ def get_per_issue_metrics( issue_with_metrics.status = f"{issue.issue.state} as {issue.issue.state_reason}" # type: ignore elif issue.state == "open": # type: ignore num_issues_open += 1 - if env_vars.hide_status is False: + if not env_vars.hide_status: issue_with_metrics.status = f"{issue.issue.state}" # type: ignore if not env_vars.hide_created_at: if isinstance(issue, github3.search.IssueSearchResult): # type: ignore From 6948431e7477b9cab893a49f216f939891129434 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:51:04 +0000 Subject: [PATCH 12/33] chore(deps): bump github/codeql-action in the dependencies group Bumps the dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.29.2 to 3.29.3 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/181d5eefc20863364f96762470ba6f862bdef56b...d6bbdef45e766d081b84a2def353b0055f728d3e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 047161a..b916d75 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -42,6 +42,6 @@ jobs: path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.24.9 + uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.24.9 with: sarif_file: results.sarif From b677265a3be4095c3d1a75aa55345f08e031669f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:58:26 +0000 Subject: [PATCH 13/33] chore(deps): bump super-linter/super-linter from 7.4.0 to 8.0.0 Bumps [super-linter/super-linter](https://github.com/super-linter/super-linter) from 7.4.0 to 8.0.0. - [Release notes](https://github.com/super-linter/super-linter/releases) - [Changelog](https://github.com/super-linter/super-linter/blob/main/CHANGELOG.md) - [Commits](https://github.com/super-linter/super-linter/compare/12150456a73e248bdc94d0794898f94e23127c88...5119dcd8011e92182ce8219d9e9efc82f16fddb6) --- updated-dependencies: - dependency-name: super-linter/super-linter dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/linter.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 9a7a3df..9069eb7 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -30,7 +30,7 @@ jobs: run: | pip install -r requirements.txt -r requirements-test.txt - name: Lint Code Base - uses: super-linter/super-linter@12150456a73e248bdc94d0794898f94e23127c88 # v7.4.0 + uses: super-linter/super-linter@5119dcd8011e92182ce8219d9e9efc82f16fddb6 # v8.0.0 env: DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c46b8513f39bb658d2ccf07ecf23ee8d252c954f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:24:55 +0000 Subject: [PATCH 14/33] chore(deps): bump mypy from 1.16.1 to 1.17.0 in the dependencies group Bumps the dependencies group with 1 update: [mypy](https://github.com/python/mypy). Updates `mypy` from 1.16.1 to 1.17.0 - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.16.1...v1.17.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 3b294cb..ee1e9b9 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,6 +1,6 @@ black==25.1.0 flake8==7.3.0 -mypy==1.16.1 +mypy==1.17.0 mypy-extensions==1.1.0 pylint==3.3.7 pytest==8.4.1 From e0357967dfab60c6d253d9168a8ac73e673ee7ab Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Mon, 21 Jul 2025 12:45:58 -0700 Subject: [PATCH 15/33] docs: specific descriptive link text Signed-off-by: Zack Koppert --- README.md | 4 ++-- docs/verify-token-access-to-repository.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 596bfc3..baddaba 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,9 @@ All feedback regarding our GitHub Actions, as a whole, should be communicated th - Do this by creating a [GitHub API token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) with permissions to read the repository and write issues. - Then take the value of the API token you just created, and [create a repository secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets) where the name of the secret is `GH_TOKEN` and the value of the secret the API token. - Then finally update the workflow file to use that repository secret by changing `GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}` to `GH_TOKEN: ${{ secrets.GH_TOKEN }}`. The name of the secret can really be anything. It just needs to match between when you create the secret name and when you refer to it in the workflow file. - - Help on verifying your token's access to your repository [here](docs/verify-token-access-to-repository.md) + - Help on verifying your token's access to your repository [in the docs directory](docs/verify-token-access-to-repository.md) 6. If you want the resulting issue with the metrics in it to appear in a different repository other than the one the workflow file runs in, update the line `token: ${{ secrets.GITHUB_TOKEN }}` with your own GitHub API token stored as a repository secret. - - This process is the same as described in the step above. More info on creating secrets can be found [here](https://docs.github.com/en/actions/security-guides/encrypted-secrets). + - This process is the same as described in the step above. More info on creating secrets can be found [in the GitHub docs security guide on encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets). 7. Commit the workflow file to the default branch (often `master` or `main`) 8. Wait for the action to trigger based on the `schedule` entry or manually trigger the workflow as shown in the [documentation](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow). diff --git a/docs/verify-token-access-to-repository.md b/docs/verify-token-access-to-repository.md index 25a4f1b..e6d7d54 100644 --- a/docs/verify-token-access-to-repository.md +++ b/docs/verify-token-access-to-repository.md @@ -4,7 +4,7 @@ GitHub PAT token access can be confusing. Here's a quick way to test if the toke **Remove this snippet after you've verified your token.** -- Make sure you follow the token setup instructions [here](https://github.com/github/issue-metrics/tree/main?tab=readme-ov-file#use-as-a-github-action) first. +- Make sure you follow the token setup instructions [in the `README.md`](https://github.com/github/issue-metrics/tree/main?tab=readme-ov-file#use-as-a-github-action) first. - Replace `{owner/repo}` with your own repository information. From 3c75ad40ae98254439df9aa6957e18baf661a077 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 20:24:39 +0000 Subject: [PATCH 16/33] chore(deps): bump python from `6544e0e` to `4c2cf99` Bumps python from `6544e0e` to `4c2cf99`. --- updated-dependencies: - dependency-name: python dependency-version: 3.13-slim dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 26a5817..d54ffa4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ #checkov:skip=CKV_DOCKER_2 #checkov:skip=CKV_DOCKER_3 -FROM python:3.13-slim@sha256:6544e0e002b40ae0f59bc3618b07c1e48064c4faed3a15ae2fbd2e8f663e8283 +FROM python:3.13-slim@sha256:4c2cf9917bd1cbacc5e9b07320025bdb7cdf2df7b0ceaccb55e9dd7e30987419 LABEL com.github.actions.name="issue-metrics" \ com.github.actions.description="Gather metrics on issues/prs/discussions such as time to first response, count of issues opened, closed, etc." \ com.github.actions.icon="check-square" \ From dd2c4b8c2a817cc9ddc0d5850d408eeb145d1827 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:27:53 +0000 Subject: [PATCH 17/33] chore(deps): bump the dependencies group with 2 updates Bumps the dependencies group with 2 updates: [github/contributors](https://github.com/github/contributors) and [github/codeql-action](https://github.com/github/codeql-action). Updates `github/contributors` from 1.5.9 to 1.5.10 - [Release notes](https://github.com/github/contributors/releases) - [Commits](https://github.com/github/contributors/compare/4d90d92531d4c5775be5a70c119ca7c0be165964...34fad9a6770332457ef52dfefc24a282ff47e929) Updates `github/codeql-action` from 3.29.3 to 3.29.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/d6bbdef45e766d081b84a2def353b0055f728d3e...4e828ff8d448a8a6e532957b1811f387a63867e8) --- updated-dependencies: - dependency-name: github/contributors dependency-version: 1.5.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: github/codeql-action dependency-version: 3.29.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/contributor_report.yaml | 2 +- .github/workflows/scorecard.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/contributor_report.yaml b/.github/workflows/contributor_report.yaml index d15c184..d31decf 100644 --- a/.github/workflows/contributor_report.yaml +++ b/.github/workflows/contributor_report.yaml @@ -27,7 +27,7 @@ jobs: echo "END_DATE=$end_date" >> "$GITHUB_ENV" - name: Run contributor action - uses: github/contributors@4d90d92531d4c5775be5a70c119ca7c0be165964 # v1.5.9 + uses: github/contributors@34fad9a6770332457ef52dfefc24a282ff47e929 # v1.5.10 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} START_DATE: ${{ env.START_DATE }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b916d75..6968215 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -42,6 +42,6 @@ jobs: path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.24.9 + uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.24.9 with: sarif_file: results.sarif From 5e0646ef30c121d4d8f40f89b91a788d0b3f0116 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 23:20:01 +0000 Subject: [PATCH 18/33] chore(deps): bump github/codeql-action in the dependencies group Bumps the dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.29.4 to 3.29.5 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/4e828ff8d448a8a6e532957b1811f387a63867e8...51f77329afa6477de8c49fc9c7046c15b9a4e79d) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 6968215..50a3a4a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -42,6 +42,6 @@ jobs: path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.24.9 + uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.24.9 with: sarif_file: results.sarif From 14419bb7695cf4d9afe2757ee7f2540d373b996d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 00:10:22 +0000 Subject: [PATCH 19/33] chore(deps): bump mypy from 1.17.0 to 1.17.1 in the dependencies group Bumps the dependencies group with 1 update: [mypy](https://github.com/python/mypy). Updates `mypy` from 1.17.0 to 1.17.1 - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.17.0...v1.17.1) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.17.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index ee1e9b9..e3e4353 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,6 +1,6 @@ black==25.1.0 flake8==7.3.0 -mypy==1.17.0 +mypy==1.17.1 mypy-extensions==1.1.0 pylint==3.3.7 pytest==8.4.1 From 0b29e611550fd715f1dd1954c1a819ebc478ee2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 01:29:55 +0000 Subject: [PATCH 20/33] chore(deps): bump the dependencies group with 3 updates Bumps the dependencies group with 3 updates: [github/ospo-reusable-workflows](https://github.com/github/ospo-reusable-workflows), [github/contributors](https://github.com/github/contributors) and [github/codeql-action](https://github.com/github/codeql-action). Updates `github/ospo-reusable-workflows` from 0.5.1 to 0.5.2 - [Release notes](https://github.com/github/ospo-reusable-workflows/releases) - [Changelog](https://github.com/github/ospo-reusable-workflows/blob/main/docs/release-image.md) - [Commits](https://github.com/github/ospo-reusable-workflows/compare/6f158f242fe68adb5a2698ef47e06dac07ac7e71...ebb4e218b75c6043139fd69a4c9bb5a465fb696b) Updates `github/contributors` from 1.5.10 to 1.5.11 - [Release notes](https://github.com/github/contributors/releases) - [Commits](https://github.com/github/contributors/compare/34fad9a6770332457ef52dfefc24a282ff47e929...69e531b620b7e5b0fad2e9823681607b54db447a) Updates `github/codeql-action` from 3.29.7 to 3.29.8 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/51f77329afa6477de8c49fc9c7046c15b9a4e79d...76621b61decf072c1cee8dd1ce2d2a82d33c17ed) --- updated-dependencies: - dependency-name: github/ospo-reusable-workflows dependency-version: 0.5.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: github/contributors dependency-version: 1.5.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: github/codeql-action dependency-version: 3.29.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/auto-labeler.yml | 2 +- .github/workflows/contributor_report.yaml | 2 +- .github/workflows/pr-title.yml | 2 +- .github/workflows/release.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/auto-labeler.yml b/.github/workflows/auto-labeler.yml index 43fe779..48d4f70 100644 --- a/.github/workflows/auto-labeler.yml +++ b/.github/workflows/auto-labeler.yml @@ -11,7 +11,7 @@ jobs: permissions: contents: write pull-requests: write - uses: github/ospo-reusable-workflows/.github/workflows/auto-labeler.yaml@6f158f242fe68adb5a2698ef47e06dac07ac7e71 + uses: github/ospo-reusable-workflows/.github/workflows/auto-labeler.yaml@ebb4e218b75c6043139fd69a4c9bb5a465fb696b with: config-name: release-drafter.yml secrets: diff --git a/.github/workflows/contributor_report.yaml b/.github/workflows/contributor_report.yaml index d31decf..123abb1 100644 --- a/.github/workflows/contributor_report.yaml +++ b/.github/workflows/contributor_report.yaml @@ -27,7 +27,7 @@ jobs: echo "END_DATE=$end_date" >> "$GITHUB_ENV" - name: Run contributor action - uses: github/contributors@34fad9a6770332457ef52dfefc24a282ff47e929 # v1.5.10 + uses: github/contributors@69e531b620b7e5b0fad2e9823681607b54db447a # v1.5.11 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} START_DATE: ${{ env.START_DATE }} diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index e06a1fb..77afc54 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -12,6 +12,6 @@ jobs: contents: read pull-requests: read statuses: write - uses: github/ospo-reusable-workflows/.github/workflows/pr-title.yaml@6f158f242fe68adb5a2698ef47e06dac07ac7e71 + uses: github/ospo-reusable-workflows/.github/workflows/pr-title.yaml@ebb4e218b75c6043139fd69a4c9bb5a465fb696b secrets: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f89b0a..5cc0ca0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: write pull-requests: read - uses: github/ospo-reusable-workflows/.github/workflows/release.yaml@6f158f242fe68adb5a2698ef47e06dac07ac7e71 + uses: github/ospo-reusable-workflows/.github/workflows/release.yaml@ebb4e218b75c6043139fd69a4c9bb5a465fb696b with: publish: true release-config-name: release-drafter.yml @@ -25,7 +25,7 @@ jobs: packages: write id-token: write attestations: write - uses: github/ospo-reusable-workflows/.github/workflows/release-image.yaml@6f158f242fe68adb5a2698ef47e06dac07ac7e71 + uses: github/ospo-reusable-workflows/.github/workflows/release-image.yaml@ebb4e218b75c6043139fd69a4c9bb5a465fb696b with: image-name: ${{ github.repository_owner }}/issue_metrics full-tag: ${{ needs.release.outputs.full-tag }} @@ -40,7 +40,7 @@ jobs: permissions: contents: read discussions: write - uses: github/ospo-reusable-workflows/.github/workflows/release-discussion.yaml@6f158f242fe68adb5a2698ef47e06dac07ac7e71 + uses: github/ospo-reusable-workflows/.github/workflows/release-discussion.yaml@ebb4e218b75c6043139fd69a4c9bb5a465fb696b with: full-tag: ${{ needs.release.outputs.full-tag }} body: ${{ needs.release.outputs.body }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 50a3a4a..8e75ea4 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -42,6 +42,6 @@ jobs: path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.24.9 + uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.24.9 with: sarif_file: results.sarif From 99209880fdfb60e3c5037fb3f058109e7f211644 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 06:17:27 -0500 Subject: [PATCH 21/33] chore(deps): bump python from `4c2cf99` to `6f79e7a` (#563) Bumps python from `4c2cf99` to `6f79e7a`. --- updated-dependencies: - dependency-name: python dependency-version: 3.13-slim dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d54ffa4..07fb1a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ #checkov:skip=CKV_DOCKER_2 #checkov:skip=CKV_DOCKER_3 -FROM python:3.13-slim@sha256:4c2cf9917bd1cbacc5e9b07320025bdb7cdf2df7b0ceaccb55e9dd7e30987419 +FROM python:3.13-slim@sha256:6f79e7a10bb7d0b0a50534a70ebc78823f941fba26143ecd7e6c5dca9d7d7e8a LABEL com.github.actions.name="issue-metrics" \ com.github.actions.description="Gather metrics on issues/prs/discussions such as time to first response, count of issues opened, closed, etc." \ com.github.actions.icon="check-square" \ From 2fd323a2f81a70e719d1035f7b7c7535c5bc53ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:21:01 +0000 Subject: [PATCH 22/33] chore(deps): bump actions/checkout from 4.2.2 to 5.0.0 (#565) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 5.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.2.2...v5.0.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-image.yml | 2 +- .github/workflows/linter.yaml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/scorecard.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 3c2f8ec..9f65a35 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -14,6 +14,6 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v5.0.0 - name: Build the Docker image run: docker build . --file Dockerfile --platform linux/amd64 --tag issue-metrics:"$(date +%s)" diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 9069eb7..fe5ec0d 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -18,7 +18,7 @@ jobs: statuses: write steps: - name: Checkout Code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 with: # Full git history is needed to get a proper # list of changed files within `super-linter` diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2853bb1..07a7ff7 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -21,7 +21,7 @@ jobs: python-version: [3.11, 3.12] steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v5.0.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5.6.0 with: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 8e75ea4..0b95c7e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -25,7 +25,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 with: persist-credentials: false From 65f43f43c5aeb79492241360d485df9fc362648f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 10:45:53 -0500 Subject: [PATCH 23/33] fix: Status and Created At column header/data alignment issue (#569) --- markdown_writer.py | 8 +-- test_column_order_fix.py | 123 +++++++++++++++++++++++++++++++++++++++ test_markdown_writer.py | 6 +- 3 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 test_column_order_fix.py diff --git a/markdown_writer.py b/markdown_writer.py index 7a88a8a..67cc241 100644 --- a/markdown_writer.py +++ b/markdown_writer.py @@ -75,10 +75,6 @@ def get_non_hidden_columns(labels) -> List[str]: if not hide_time_to_answer: columns.append("Time to answer") - hide_status = env_vars.hide_status - if not hide_status: - columns.append("Status") - enable_time_in_draft = env_vars.draft_pr_tracking if enable_time_in_draft: columns.append("Time in draft") @@ -91,6 +87,10 @@ def get_non_hidden_columns(labels) -> List[str]: if not hide_created_at: columns.append("Created At") + hide_status = env_vars.hide_status + if not hide_status: + columns.append("Status") + return columns diff --git a/test_column_order_fix.py b/test_column_order_fix.py new file mode 100644 index 0000000..c186ad7 --- /dev/null +++ b/test_column_order_fix.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +""" +Test to verify that the Status and Created At columns have their content aligned with headers. + +This test specifically validates the fix for issue #568 where the Status and Created At +columns had their data swapped. +""" + +import os +import unittest +from datetime import timedelta +from unittest.mock import patch + +from classes import IssueWithMetrics +from markdown_writer import get_non_hidden_columns, write_to_markdown + + +@patch.dict( + os.environ, + { + "SEARCH_QUERY": "is:open repo:user/repo", + "GH_TOKEN": "test_token", + "HIDE_CREATED_AT": "False", + "HIDE_STATUS": "False", + }, +) +class TestColumnOrderFix(unittest.TestCase): + """Test that Status and Created At columns have correct data.""" + + def test_status_and_created_at_columns_alignment(self): + """Test that Status and Created At columns show correct data values. + + This test specifically validates that: + 1. The Status column contains actual status values (not dates) + 2. The Created At column contains actual date values (not status) + """ + # Create test data with clearly distinguishable Status and Created At values + issues_with_metrics = [ + IssueWithMetrics( + title="Test Issue", + html_url="https://github.com/user/repo/issues/1", + author="testuser", + assignee="assignee1", + assignees=["assignee1"], + created_at="2023-01-01T00:00:00Z", # This should appear in Created At column + status="open", # This should appear in Status column + time_to_first_response=timedelta(days=1), + time_to_close=timedelta(days=2), + time_to_answer=timedelta(days=3), + ) + ] + + # Call the function + write_to_markdown( + issues_with_metrics=issues_with_metrics, + average_time_to_first_response=None, + average_time_to_close=None, + average_time_to_answer=None, + average_time_in_draft=None, + average_time_in_labels=None, + num_issues_opened=1, + num_issues_closed=0, + num_mentor_count=0, + labels=None, + search_query="is:issue is:open repo:user/repo", + hide_label_metrics=True, + hide_items_closed_count=False, + enable_mentor_count=False, + non_mentioning_links=False, + report_title="Test Report", + output_file="test_column_order.md", + ) + + # Read the generated markdown + with open("test_column_order.md", "r", encoding="utf-8") as file: + content = file.read() + + # The table should have the columns in the correct order + # and the data should be properly aligned + expected_header = ( + "| Title | URL | Assignee | Author | Time to first response | " + "Time to close | Time to answer | Created At | Status |" + ) + self.assertIn(expected_header, content) + + # Verify the data row has correct values in correct positions + # The Created At column should contain the date value + # The Status column should contain the status value + expected_row = ( + "| Test Issue | https://github.com/user/repo/issues/1 | " + "[assignee1](https://github.com/assignee1) | " + "[testuser](https://github.com/testuser) | 1 day, 0:00:00 | " + "2 days, 0:00:00 | 3 days, 0:00:00 | 2023-01-01T00:00:00Z | open |" + ) + self.assertIn(expected_row, content) + + # Clean up + os.remove("test_column_order.md") + + def test_get_non_hidden_columns_order(self): + """Test that get_non_hidden_columns returns columns in the correct order.""" + columns = get_non_hidden_columns(labels=None) + + # Find the indices of the Status and Created At columns + try: + created_at_index = columns.index("Created At") + status_index = columns.index("Status") + + # Status should come after Created At + self.assertGreater( + status_index, + created_at_index, + "Status column should come after Created At column", + ) + except ValueError: + # If one of the columns is hidden, that's fine, but we shouldn't get here + # given our environment variables + self.fail("Both Status and Created At columns should be present") + + +if __name__ == "__main__": + unittest.main() diff --git a/test_markdown_writer.py b/test_markdown_writer.py index 0fbcad3..c79536b 100644 --- a/test_markdown_writer.py +++ b/test_markdown_writer.py @@ -137,7 +137,7 @@ def test_write_to_markdown(self): "| Number of items closed | 1 |\n" "| Total number of items created | 2 |\n\n" "| Title | URL | Assignee | Author | Time to first response | Time to close | " - "Time to answer | Status | Time in draft | Time spent in bug | Created At |\n" + "Time to answer | Time in draft | Time spent in bug | Created At | Status |\n" "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n" "| Issue 1 | https://github.com/user/repo/issues/1 | [charlie](https://github.com/charlie) | " "[alice](https://github.com/alice) | 1 day, 0:00:00 | 2 days, 0:00:00 | 3 days, 0:00:00 | " @@ -254,7 +254,7 @@ def test_write_to_markdown_with_vertical_bar_in_title(self): "| Number of items closed | 1 |\n" "| Total number of items created | 2 |\n\n" "| Title | URL | Assignee | Author | Time to first response | Time to close | " - "Time to answer | Status | Time in draft | Time spent in bug | Created At |\n" + "Time to answer | Time in draft | Time spent in bug | Created At | Status |\n" "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n" "| Issue 1 | https://github.com/user/repo/issues/1 | [charlie](https://github.com/charlie) | " "[alice](https://github.com/alice) | 1 day, 0:00:00 | 2 days, 0:00:00 | 3 days, 0:00:00 | " @@ -405,7 +405,7 @@ def test_writes_markdown_file_with_non_hidden_columns_only(self): "| Number of items that remain open | 2 |\n" "| Number of most active mentors | 5 |\n" "| Total number of items created | 2 |\n\n" - "| Title | URL | Assignee | Author | Status | Created At |\n" + "| Title | URL | Assignee | Author | Created At | Status |\n" "| --- | --- | --- | --- | --- | --- |\n" "| Issue 1 | https://www.ghe.com/user/repo/issues/1 | [charlie](https://ghe.com/charlie) | " "[alice](https://ghe.com/alice) | -5 days, 0:00:00 | None |\n" From e9b5de2d907c7293b3608b8af3f4a731bf73683a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 17:44:49 +0000 Subject: [PATCH 24/33] Initial plan From e517b06752f83df23aa29b7b21adda7ced379294 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 17:53:59 +0000 Subject: [PATCH 25/33] Fix DRAFT_PR_TRACKING AttributeError by correcting event access pattern Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> --- test_time_in_draft.py | 38 ++++++++++++++++++++++++++++++++------ time_in_draft.py | 2 +- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/test_time_in_draft.py b/test_time_in_draft.py index 2475147..1a3fc05 100644 --- a/test_time_in_draft.py +++ b/test_time_in_draft.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta from unittest.mock import MagicMock +import github3 import pytz from time_in_draft import get_stats_time_in_draft, measure_time_in_draft @@ -18,13 +19,14 @@ def setUp(self): Setup common test data and mocks. """ self.issue = MagicMock() + self.issue.issue = MagicMock(spec=github3.issues.Issue) self.issue.issue.state = "open" def test_time_in_draft_with_ready_for_review(self): """ Test measure_time_in_draft with one draft and review interval. """ - self.issue.events.return_value = [ + self.issue.issue.events.return_value = [ MagicMock( event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), @@ -42,7 +44,7 @@ def test_time_in_draft_without_ready_for_review(self): """ Test measure_time_in_draft when ready_for_review_at is not provided and issue is still open. """ - self.issue.events.return_value = [ + self.issue.issue.events.return_value = [ MagicMock( event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), @@ -59,7 +61,7 @@ def test_time_in_draft_multiple_intervals(self): """ Test measure_time_in_draft with multiple draft intervals. """ - self.issue.events.return_value = [ + self.issue.issue.events.return_value = [ MagicMock( event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), @@ -85,7 +87,7 @@ def test_time_in_draft_ongoing_draft(self): """ Test measure_time_in_draft with an ongoing draft interval. """ - self.issue.events.return_value = [ + self.issue.issue.events.return_value = [ MagicMock( event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), @@ -103,7 +105,7 @@ def test_time_in_draft_no_draft_events(self): """ Test measure_time_in_draft with no draft-related events. """ - self.issue.events.return_value = [] + self.issue.issue.events.return_value = [] result = measure_time_in_draft(self.issue) self.assertIsNone( result, "The result should be None when there are no draft events." @@ -113,7 +115,7 @@ def test_time_in_draft_without_ready_for_review_and_closed(self): """ Test measure_time_in_draft for a closed issue with an ongoing draft and ready_for_review_at is not provided. """ - self.issue.events.return_value = [ + self.issue.issue.events.return_value = [ MagicMock( event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), @@ -126,6 +128,30 @@ def test_time_in_draft_without_ready_for_review_and_closed(self): "The result should be None for a closed issue with an ongoing draft.", ) + def test_time_in_draft_with_attribute_error_scenario(self): + """ + Test measure_time_in_draft to ensure it doesn't raise AttributeError when called + with issue structure similar to what get_per_issue_metrics passes. + This test reproduces the original bug scenario. + """ + # This simulates the actual issue structure passed from get_per_issue_metrics + issue_search_result = MagicMock() + issue_search_result.issue = MagicMock(spec=github3.issues.Issue) + issue_search_result.issue.state = "open" + issue_search_result.issue.events.return_value = [ + MagicMock( + event="converted_to_draft", + created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), + ), + ] + + # This should NOT raise AttributeError: events + with unittest.mock.patch("time_in_draft.datetime") as mock_datetime: + mock_datetime.now.return_value = datetime(2021, 1, 4, tzinfo=pytz.utc) + result = measure_time_in_draft(issue_search_result) + expected = timedelta(days=3) + self.assertEqual(result, expected, "The time in draft should be 3 days.") + class TestGetStatsTimeInDraft(unittest.TestCase): """ diff --git a/time_in_draft.py b/time_in_draft.py index 9244145..98f01e7 100644 --- a/time_in_draft.py +++ b/time_in_draft.py @@ -22,7 +22,7 @@ def measure_time_in_draft( returns: Union[timedelta, None]: Total time the pull request has spent in draft state. """ - events = issue.events() + events = issue.issue.events() draft_start = None total_draft_time = timedelta(0) From 80d86493c2edc9a175a9fb9565569dabf0e19435 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:09:34 +0000 Subject: [PATCH 26/33] Fix linting issues: remove trailing whitespace in test file Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> --- test_time_in_draft.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_time_in_draft.py b/test_time_in_draft.py index 1a3fc05..8a442cc 100644 --- a/test_time_in_draft.py +++ b/test_time_in_draft.py @@ -130,7 +130,7 @@ def test_time_in_draft_without_ready_for_review_and_closed(self): def test_time_in_draft_with_attribute_error_scenario(self): """ - Test measure_time_in_draft to ensure it doesn't raise AttributeError when called + Test measure_time_in_draft to ensure it doesn't raise AttributeError when called with issue structure similar to what get_per_issue_metrics passes. This test reproduces the original bug scenario. """ @@ -144,7 +144,7 @@ def test_time_in_draft_with_attribute_error_scenario(self): created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), ), ] - + # This should NOT raise AttributeError: events with unittest.mock.patch("time_in_draft.datetime") as mock_datetime: mock_datetime.now.return_value = datetime(2021, 1, 4, tzinfo=pytz.utc) From 9f2ad7e5b0353ace5de24bc74187ed0ad21473d7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:17:17 -0500 Subject: [PATCH 27/33] fix: label metrics overcounting for closed issues when labels removed before closure (#571) * Initial plan * Fix label metrics overcounting bug and add comprehensive tests Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> * Add 3 additional test cases for label metrics with creation-time labels and varied timeframes Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> * Fix black linting issue with long function name formatting Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> Co-authored-by: Zack Koppert --- labels.py | 5 +- test_labels.py | 175 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 1 deletion(-) diff --git a/labels.py b/labels.py index b9502b4..ee8656b 100644 --- a/labels.py +++ b/labels.py @@ -85,11 +85,14 @@ def get_label_metrics(issue: github3.issues.Issue, labels: List[str]) -> dict: if label in labeled: # if the issue is closed, add the time from the issue creation to the closed_at time if issue.state == "closed": + # Only add the final (closed_at - created_at) span if the label was still applied at closure. + if label_last_event_type.get(label) != "labeled": + continue label_metrics[label] += datetime.fromisoformat( issue.closed_at ) - datetime.fromisoformat(issue.created_at) else: - # skip label if last labeling event is 'unlabled' and issue is still open + # skip label if last labeling event is 'unlabeled' and issue is still open if label_last_event_type[label] == "unlabeled": continue diff --git a/test_labels.py b/test_labels.py index 9c49b2b..bea6edf 100644 --- a/test_labels.py +++ b/test_labels.py @@ -93,6 +93,181 @@ def test_get_label_metrics_closed_issue_labeled_past_closed_at(self): metrics = get_label_metrics(self.issue, labels) self.assertEqual(metrics["foo"], None) + def test_get_label_metrics_closed_issue_label_removed_before_closure(self): + """Test get_label_metrics for a closed issue where label was removed before closure""" + # Create a mock issue that reproduces the problem scenario: + # Issue created: day 0 (2021-01-01) + # Label added: day 5 (2021-01-06) + # Label removed: day 10 (2021-01-11) + # Issue closed: day 15 (2021-01-16) + # Expected duration: 5 days (from day 5 to day 10) + + issue = MagicMock() + issue.issue = MagicMock(spec=github3.issues.Issue) + issue.created_at = "2021-01-01T00:00:00Z" + issue.closed_at = "2021-01-16T00:00:00Z" # 15 days after creation + issue.state = "closed" + issue.issue.events.return_value = [ + MagicMock( + event="labeled", + label={"name": "test-label"}, + created_at=datetime(2021, 1, 6, tzinfo=pytz.UTC), # day 5 + ), + MagicMock( + event="unlabeled", + label={"name": "test-label"}, + created_at=datetime(2021, 1, 11, tzinfo=pytz.UTC), # day 10 + ), + ] + + labels = ["test-label"] + metrics = get_label_metrics(issue, labels) + + # Should be 5 days (from day 5 to day 10), not 15 days (full issue duration) + expected_duration = timedelta(days=5) + self.assertEqual(metrics["test-label"], expected_duration) + + def test_get_label_metrics_closed_issue_label_remains_through_closure(self): + """Test get_label_metrics for a closed issue where label remains applied through closure""" + # Test scenario where label is applied and never removed: + # Issue created: day 0 (2021-01-01) + # Label added: day 2 (2021-01-03) + # Issue closed: day 10 (2021-01-11) + # Expected duration: 10 days (from issue creation to closure) + + issue = MagicMock() + issue.issue = MagicMock(spec=github3.issues.Issue) + issue.created_at = "2021-01-01T00:00:00Z" + issue.closed_at = "2021-01-11T00:00:00Z" # 10 days after creation + issue.state = "closed" + issue.issue.events.return_value = [ + MagicMock( + event="labeled", + label={"name": "stays-applied"}, + created_at=datetime(2021, 1, 3, tzinfo=pytz.UTC), # day 2 + ), + # No unlabel event - label remains applied + ] + + labels = ["stays-applied"] + metrics = get_label_metrics(issue, labels) + + # Should be 8 days (from day 2 when label was applied to day 10 when issue closed) + expected_duration = timedelta(days=8) + self.assertEqual(metrics["stays-applied"], expected_duration) + + def test_get_label_metrics_label_applied_at_creation_and_removed_before_closure( + self, + ): + """Test get_label_metrics for a label applied at issue creation and removed before closure""" + # Test scenario where label is applied at creation and later removed: + # Issue created: day 0 (2021-01-01) with label applied + # Label removed: day 7 (2021-01-08) + # Issue closed: day 20 (2021-01-21) + # Expected duration: 7 days (from creation to removal) + + issue = MagicMock() + issue.issue = MagicMock(spec=github3.issues.Issue) + issue.created_at = "2021-01-01T00:00:00Z" + issue.closed_at = "2021-01-21T00:00:00Z" # 20 days after creation + issue.state = "closed" + issue.issue.events.return_value = [ + MagicMock( + event="labeled", + label={"name": "creation-label"}, + created_at=datetime(2021, 1, 1, tzinfo=pytz.UTC), # day 0 - at creation + ), + MagicMock( + event="unlabeled", + label={"name": "creation-label"}, + created_at=datetime(2021, 1, 8, tzinfo=pytz.UTC), # day 7 + ), + ] + + labels = ["creation-label"] + metrics = get_label_metrics(issue, labels) + + # Should be 7 days (from creation to removal), not 20 days (full issue duration) + expected_duration = timedelta(days=7) + self.assertEqual(metrics["creation-label"], expected_duration) + + def test_get_label_metrics_label_applied_at_creation_remains_through_closure(self): + """Test get_label_metrics for a label applied at creation and kept through closure""" + # Test scenario where label is applied at creation and never removed: + # Issue created: day 0 (2021-01-01) with label applied + # Issue closed: day 30 (2021-01-31) + # Expected duration: 30 days (full issue duration) + + issue = MagicMock() + issue.issue = MagicMock(spec=github3.issues.Issue) + issue.created_at = "2021-01-01T00:00:00Z" + issue.closed_at = "2021-01-31T00:00:00Z" # 30 days after creation + issue.state = "closed" + issue.issue.events.return_value = [ + MagicMock( + event="labeled", + label={"name": "permanent-label"}, + created_at=datetime(2021, 1, 1, tzinfo=pytz.UTC), # day 0 - at creation + ), + # No unlabel event - label remains applied + ] + + labels = ["permanent-label"] + metrics = get_label_metrics(issue, labels) + + # Should be 30 days (full issue duration since label was applied at creation) + expected_duration = timedelta(days=30) + self.assertEqual(metrics["permanent-label"], expected_duration) + + def test_get_label_metrics_multiple_labels_different_timeframes(self): + """Test get_label_metrics with multiple labels having different application patterns and longer timeframes""" + # Test scenario with multiple labels and longer timeframes: + # Issue created: day 0 (2021-01-01) + # Label A applied: day 0 (at creation) + # Label B applied: day 14 (2021-01-15) + # Label A removed: day 21 (2021-01-22) + # Label B removed: day 35 (2021-02-05) + # Issue closed: day 60 (2021-03-02) + # Expected: Label A = 21 days, Label B = 21 days + + issue = MagicMock() + issue.issue = MagicMock(spec=github3.issues.Issue) + issue.created_at = "2021-01-01T00:00:00Z" + issue.closed_at = "2021-03-02T00:00:00Z" # 60 days after creation + issue.state = "closed" + issue.issue.events.return_value = [ + MagicMock( + event="labeled", + label={"name": "label-a"}, + created_at=datetime(2021, 1, 1, tzinfo=pytz.UTC), # day 0 - at creation + ), + MagicMock( + event="labeled", + label={"name": "label-b"}, + created_at=datetime(2021, 1, 15, tzinfo=pytz.UTC), # day 14 + ), + MagicMock( + event="unlabeled", + label={"name": "label-a"}, + created_at=datetime(2021, 1, 22, tzinfo=pytz.UTC), # day 21 + ), + MagicMock( + event="unlabeled", + label={"name": "label-b"}, + created_at=datetime(2021, 2, 5, tzinfo=pytz.UTC), # day 35 + ), + ] + + labels = ["label-a", "label-b"] + metrics = get_label_metrics(issue, labels) + + # Label A: 21 days (from day 0 to day 21) + # Label B: 21 days (from day 14 to day 35) + expected_duration_a = timedelta(days=21) + expected_duration_b = timedelta(days=21) + self.assertEqual(metrics["label-a"], expected_duration_a) + self.assertEqual(metrics["label-b"], expected_duration_b) + class TestGetAverageTimeInLabels(unittest.TestCase): """Unit tests for get_stats_time_in_labels""" From 8198e455a45bcf36c264dac9c611ae8b42337d69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 00:45:28 +0000 Subject: [PATCH 28/33] chore(deps): bump types-pytz from 2025.2.0.20250516 to 2025.2.0.20250809 Bumps [types-pytz](https://github.com/typeshed-internal/stub_uploader) from 2025.2.0.20250516 to 2025.2.0.20250809. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-pytz dependency-version: 2025.2.0.20250809 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index e3e4353..fd3844c 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -5,5 +5,5 @@ mypy-extensions==1.1.0 pylint==3.3.7 pytest==8.4.1 pytest-cov==6.2.1 -types-pytz==2025.2.0.20250516 +types-pytz==2025.2.0.20250809 types-requests==2.32.4.20250611 From aa316544c437e82710c55de0fe789b5b1c629c47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 05:07:13 +0000 Subject: [PATCH 29/33] chore(deps): bump types-requests from 2.32.4.20250611 to 2.32.4.20250809 Bumps [types-requests](https://github.com/typeshed-internal/stub_uploader) from 2.32.4.20250611 to 2.32.4.20250809. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.32.4.20250809 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index fd3844c..1defdd4 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -6,4 +6,4 @@ pylint==3.3.7 pytest==8.4.1 pytest-cov==6.2.1 types-pytz==2025.2.0.20250809 -types-requests==2.32.4.20250611 +types-requests==2.32.4.20250809 From 740926ce6e5341914ae473ee48d5a0dcf2ec223d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:19:55 -0500 Subject: [PATCH 30/33] chore(deps): bump the dependencies group across 1 directory with 2 updates (#579) Bumps the dependencies group with 2 updates in the / directory: [requests](https://github.com/psf/requests) and [pylint](https://github.com/pylint-dev/pylint). Updates `requests` from 2.32.4 to 2.32.5 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.4...v2.32.5) Updates `pylint` from 3.3.7 to 3.3.8 - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.7...v3.3.8) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: pylint dependency-version: 3.3.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-test.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-test.txt b/requirements-test.txt index 1defdd4..c0be31e 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -2,7 +2,7 @@ black==25.1.0 flake8==7.3.0 mypy==1.17.1 mypy-extensions==1.1.0 -pylint==3.3.7 +pylint==3.3.8 pytest==8.4.1 pytest-cov==6.2.1 types-pytz==2025.2.0.20250809 diff --git a/requirements.txt b/requirements.txt index 2d48a84..5ba9ab5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ github3.py==4.0.1 numpy==2.2.4 python-dotenv==1.1.1 pytz==2025.2 -requests==2.32.4 +requests==2.32.5 From 6e412dd6ba6b32853f0213450fb1ba5154c267f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:20:13 -0500 Subject: [PATCH 31/33] chore(deps): bump github/codeql-action in the dependencies group (#574) Bumps the dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.29.8 to 3.29.10 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/76621b61decf072c1cee8dd1ce2d2a82d33c17ed...96f518a34f7a870018057716cc4d7a5c014bd61c) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0b95c7e..c9ca2c6 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -42,6 +42,6 @@ jobs: path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.24.9 + uses: github/codeql-action/upload-sarif@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.24.9 with: sarif_file: results.sarif From c0a53522b8ca7eeb6bceeac441ce665a64b37861 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 20:21:26 +0000 Subject: [PATCH 32/33] chore(deps): bump python from `6f79e7a` to `27f90d7` (#578) * chore(deps): bump python from `6f79e7a` to `27f90d7` Bumps python from `6f79e7a` to `27f90d7`. --- updated-dependencies: - dependency-name: python dependency-version: 3.13-slim dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix: git version in Dockerfile Signed-off-by: jmeridth <35014+jmeridth@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Signed-off-by: jmeridth <35014+jmeridth@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: jmeridth <35014+jmeridth@users.noreply.github.com> --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 07fb1a1..b12d6ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ #checkov:skip=CKV_DOCKER_2 #checkov:skip=CKV_DOCKER_3 -FROM python:3.13-slim@sha256:6f79e7a10bb7d0b0a50534a70ebc78823f941fba26143ecd7e6c5dca9d7d7e8a +FROM python:3.13-slim@sha256:27f90d79cc85e9b7b2560063ef44fa0e9eaae7a7c3f5a9f74563065c5477cc24 LABEL com.github.actions.name="issue-metrics" \ com.github.actions.description="Gather metrics on issues/prs/discussions such as time to first response, count of issues opened, closed, etc." \ com.github.actions.icon="check-square" \ @@ -17,7 +17,7 @@ COPY requirements.txt *.py /action/workspace/ RUN python3 -m pip install --no-cache-dir -r requirements.txt \ && apt-get -y update \ - && apt-get -y install --no-install-recommends git=1:2.39.5-0+deb12u2 \ + && apt-get -y install --no-install-recommends git=1:2.47.2-0.2 \ && rm -rf /var/lib/apt/lists/* CMD ["/action/workspace/issue_metrics.py"] From e53376f8b964250d19b0aa7d846f5ff7e93dbfc9 Mon Sep 17 00:00:00 2001 From: jmeridth <35014+jmeridth@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:49:24 -0500 Subject: [PATCH 33/33] feat: copilot files (instructions and setup steps) This ensures that when we have the copilot agent writing PRs, it sets up its environment correctly and understands our standards for coding. - Copilot Instructions file ([docs](https://docs.github.com/en/enterprise-cloud@latest/copilot/how-tos/agents/copilot-coding-agent/best-practices-for-using-copilot-to-work-on-tasks#adding-custom-instructions-to-your-repository)) - Copilot Setup Steps ([docs](https://docs.github.com/en/copilot/how-tos/agents/copilot-coding-agent/customizing-the-development-environment-for-copilot-coding-agent#preinstalling-tools-or-dependencies-in-copilots-environment)) Already added to [Evergreen](https://github.com/github/evergreen/pull/382) Signed-off-by: jmeridth <35014+jmeridth@users.noreply.github.com> --- .github/copilot-instructions.md | 30 ++++++++++++++++++ .github/workflows/copilot-setup-steps.yml | 38 +++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..fd51988 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,30 @@ +# Copilot Instructions + +This is a GitHub Action that searches for issues/pull requests/discussions in a repository, measures several metrics, and generates a report in form of a GitHub issue. The issues/pull requests/discussions to search for can be filtered by using a search query. + +## Code Standards + +### Required Before Each Commit + +- Run `make lint` before committing any changes to ensure proper code linting and formatting. + +### Development Flow + +- Lint: `make lint` +- Test: `make test` + +## Repository Structure + +- `Makefile`: Contains commands for linting, testing, and other tasks +- `requirements.txt`: Python dependencies for the project +- `requirements-test.txt`: Python dependencies for testing +- `README.md`: Project documentation and setup instructions +- `setup.py`: Python package setup configuration +- `test_*.py`: Python test files matching the naming convention for test discovery + +## Key Guidelines + +1. Follow Python best practices and idiomatic patterns +2. Maintain existing code structure and organization +3. Write unit tests for new functionality. +4. Document changes to environment variables in the `README.md` file. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000..cddb8d9 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,38 @@ +name: "Copilot Setup Steps" + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +# Set the permissions to the lowest permissions possible needed for your steps. +# Copilot will be given its own token for its operations. +permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - name: Checkout code + uses: actions/checkout@v5.0.0 + + - name: Set up Python + uses: actions/setup-python@v5.6.0 + with: + python-version: 3.12 + + - name: Install dependencies + run: | + pip install -r requirements.txt -r requirements-test.txt