Skip to content

Commit

Permalink
Merge pull request #16 from github/query-name
Browse files Browse the repository at this point in the history
refactor: search_query naming should not specify it is only for issues.
  • Loading branch information
zkoppert authored Jun 9, 2023
2 parents 4354219 + adf65e3 commit 93ddd00
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .env-example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
GH_TOKEN = " "
ISSUE_SEARCH_QUERY = "is:open is:issue"
SEARCH_QUERY = "is:open is:issue"
REPOSITORY_URL = "https://github.com/github/fetch"
82 changes: 75 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
[![CodeQL](https://github.com/github/issue-metrics/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/github/issue-metrics/actions/workflows/codeql-analysis.yml) [![Docker Image CI](https://github.com/github/issue-metrics/actions/workflows/docker-image.yml/badge.svg)](https://github.com/github/issue-metrics/actions/workflows/docker-image.yml) [![Python package](https://github.com/github/issue-metrics/actions/workflows/python-package.yml/badge.svg)](https://github.com/github/issue-metrics/actions/workflows/python-package.yml)

This is a GitHub Action that searches for pull requests/issues in a repository and measures
the time to first response for each issue. It then calculates the average time
to first response and writes the issues with their time to first response and time to close
to a Markdown file. The issues to search for can be filtered by using a search query.
the time to first response for each one. It then calculates the average time
to first response and writes the issues/pull requests with their time to first response and time to close
to a Markdown file. The issues/pull requests to search for can be filtered by using a search query.

This action was developed by the GitHub OSPO for our own use and developed in a way that we could open source it that it might be useful to you as well! If you want to know more about how we use it, reach out in an issue in this repository.

Expand All @@ -18,7 +18,7 @@ If you need support using this project or have questions about it, please [open
## Use as a GitHub Action

1. Create a repository to host this GitHub Action or select an existing repository.
1. Create the env values from the sample workflow below (GH_TOKEN, REPOSITORY_URL, ISSUE_SEARCH_QUERY) with your information as repository secrets. More info on creating secrets can be found [here](https://docs.github.com/en/actions/security-guides/encrypted-secrets).
1. Create the env values from the sample workflow below (GH_TOKEN, REPOSITORY_URL, SEARCH_QUERY) with your information as repository secrets. More info on creating secrets can be found [here](https://docs.github.com/en/actions/security-guides/encrypted-secrets).
Note: Your GitHub token will need to have read access to the repository in the organization that you want evaluated
1. Copy the below example workflow to your repository and put it in the `.github/workflows/` directory with the file extension `.yml` (ie. `.github/workflows/issue-metrics.yml`)

Expand All @@ -45,7 +45,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
REPOSITORY_URL: https://github.com/owner/repo
ISSUE_SEARCH_QUERY: 'is:issue created:2023-05-01..2023-05-31 -reason:"not planned"'
SEARCH_QUERY: 'is:issue created:2023-05-01..2023-05-31 -reason:"not planned"'

- name: Create issue
uses: peter-evans/create-issue-from-file@v4
Expand All @@ -56,7 +56,75 @@ jobs:

```

### Example issue_metrics.md output
## SEARCH_QUERY: Issues or Pull Requests? Open or closed?
This action can be configured to run metrics on pull requests and/or issues. It is also configurable by whether they were open or closed in the specified time window. Here are some search query examples:

Issues opened in May 2023:
- `is:issue created:2023-05-01..2023-05-31`

Issues closed in May 2023 (may have been open in May or earlier):
- `is:issue closed:2023-05-01..2023-05-31`

Pull requests opened in May 2023:
- `is:pr created:2023-05-01..2023-05-31`

Pull requests closed in May 2023 (may have been open in May or earlier):
- `is:pr closed:2023-05-01..2023-05-31`

Both issues and pull requests opened in May 2023:
- `created:2023-05-01..2023-05-31`

Both issues and pull requests closed in May 2023 (may have been open in May or earlier):
- `closed:2023-05-01..2023-05-31`

OK, but what if I want both open or closed issues and pull requests? Due to limitations in issue search (no ability for an or pattern), you will need to run the action twice, once for opened and once for closed. Here is an example workflow that does this:

```yaml
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: '3 2 1 * *'

jobs:
build:
name: issue metrics
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Run issue-metrics tool for issues and prs opened in May 2023
uses: docker://ghcr.io/github/issue-metrics:v1
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
REPOSITORY_URL: https://github.com/owner/repo
SEARCH_QUERY: 'created:2023-05-01..2023-05-31 -reason:"not planned"'

- name: Create issue for opened issues and prs
uses: peter-evans/create-issue-from-file@v4
with:
title: Monthly issue metrics report for opened issues and prs
content-filepath: ./issue_metrics.md
assignees: <YOUR_GITHUB_HANDLE_HERE>

- name: Run issue-metrics tool for issues and prs closed in May 2023
uses: docker://ghcr.io/github/issue-metrics:v1
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
REPOSITORY_URL: https://github.com/owner/repo
SEARCH_QUERY: 'closed:2023-05-01..2023-05-31 -reason:"not planned"'

- name: Create issue for closed issues and prs
uses: peter-evans/create-issue-from-file@v4
with:
title: Monthly issue metrics report for closed issues and prs
content-filepath: ./issue_metrics.md
assignees: <YOUR_GITHUB_HANDLE_HERE>
```
## Example issue_metrics.md output
```markdown
# Issue Metrics
Expand All @@ -82,7 +150,7 @@ jobs:
1. Copy `.env-example` to `.env`
1. Fill out the `.env` file with a _token_ from a user that has access to the organization to scan (listed below). Tokens should have admin:org or read:org access.
1. Fill out the `.env` file with the _repository_url_ of the repository to scan
1. Fill out the `.env` file with the _issue_search_query_ to filter issues by
1. Fill out the `.env` file with the _search_query_ to filter issues by
1. `pip install -r requirements.txt`
1. Run `python3 ./issue_metrics.py`, which will output issue metrics data

Expand Down
22 changes: 11 additions & 11 deletions issue_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
from dotenv import load_dotenv


def search_issues(repository_url, issue_search_query, github_connection):
def search_issues(repository_url, search_query, github_connection):
"""
Searches for issues in a GitHub repository that match the given search query.
Args:
repository_url (str): The URL of the repository to search in.
ie https://github.com/user/repo
issue_search_query (str): The search query to use for finding issues.
search_query (str): The search query to use for finding issues.
github_connection (github3.GitHub): A connection to the GitHub API.
Returns:
Expand All @@ -49,7 +49,7 @@ def search_issues(repository_url, issue_search_query, github_connection):
print(f"owner: {owner}, repo: {repo}")

# Search for issues that match the query
full_query = f"repo:{owner}/{repo} {issue_search_query}"
full_query = f"repo:{owner}/{repo} {search_query}"
issues = github_connection.search_issues(full_query) # type: ignore

# Print the issue titles
Expand Down Expand Up @@ -254,12 +254,12 @@ def main():
"""Run the issue-metrics script.
This function authenticates to GitHub, searches for issues using the
ISSUE_SEARCH_QUERY environment variable, measures the time to first response
SEARCH_QUERY environment variable, measures the time to first response
and close for each issue, calculates the average time to first response,
and writes the results to a markdown file.
Raises:
ValueError: If the ISSUE_SEARCH_QUERY environment variable is not set.
ValueError: If the SEARCH_QUERY environment variable is not set.
ValueError: If the REPOSITORY_URL environment variable is not set.
"""

Expand All @@ -273,10 +273,10 @@ def main():
github_connection = auth_to_github()

# Get the environment variables for use in the script
issue_search_query, repo_url = get_env_vars()
search_query, repo_url = get_env_vars()

# Search for issues
issues = search_issues(repo_url, issue_search_query, github_connection)
issues = search_issues(repo_url, search_query, github_connection)
if len(issues.items) <= 0:
print("No issues found")
write_to_markdown(None, None, None, None, None)
Expand Down Expand Up @@ -324,14 +324,14 @@ def main():

def get_env_vars():
"""Get the environment variables for use in the script."""
issue_search_query = os.getenv("ISSUE_SEARCH_QUERY")
if not issue_search_query:
raise ValueError("ISSUE_SEARCH_QUERY environment variable not set")
search_query = os.getenv("SEARCH_QUERY")
if not search_query:
raise ValueError("SEARCH_QUERY environment variable not set")

repo_url = os.getenv("REPOSITORY_URL")
if not repo_url:
raise ValueError("REPOSITORY_URL environment variable not set")
return issue_search_query, repo_url
return search_query, repo_url


class IssueWithMetrics:
Expand Down
12 changes: 6 additions & 6 deletions test_issue_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ class TestGetEnvVars(unittest.TestCase):
def test_get_env_vars(self):
"""Test that the function correctly retrieves the environment variables."""
# Set the environment variables
os.environ["ISSUE_SEARCH_QUERY"] = "is:issue is:open"
os.environ["SEARCH_QUERY"] = "is:issue is:open"
os.environ["REPOSITORY_URL"] = "https://github.com/user/repo"

# Call the function and check the result
Expand All @@ -340,9 +340,9 @@ def test_get_env_vars(self):

def test_get_env_vars_missing_query(self):
"""Test that the function raises a ValueError
if the ISSUE_SEARCH_QUERY environment variable is not set."""
# Unset the ISSUE_SEARCH_QUERY environment variable
os.environ.pop("ISSUE_SEARCH_QUERY", None)
if the SEARCH_QUERY environment variable is not set."""
# Unset the SEARCH_QUERY environment variable
os.environ.pop("SEARCH_QUERY", None)

# Call the function and check that it raises a ValueError
with self.assertRaises(ValueError):
Expand Down Expand Up @@ -379,7 +379,7 @@ class TestMain(unittest.TestCase):
@patch.dict(
os.environ,
{
"ISSUE_SEARCH_QUERY": "is:open",
"SEARCH_QUERY": "is:open",
"REPOSITORY_URL": "https://github.com/user/repo",
},
)
Expand Down Expand Up @@ -438,7 +438,7 @@ def test_main(
@patch.dict(
os.environ,
{
"ISSUE_SEARCH_QUERY": "is:open",
"SEARCH_QUERY": "is:open",
"REPOSITORY_URL": "https://github.com/user/repo",
},
)
Expand Down

0 comments on commit 93ddd00

Please sign in to comment.