Skip to content

Commit 28ecd14

Browse files
authored
Merge pull request #32 from github/discussions
Allow for measuring discussions
2 parents 8fd63e4 + 13fc119 commit 28ecd14

16 files changed

+1181
-569
lines changed

.env-example

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
GH_TOKEN = " "
2-
SEARCH_QUERY = "is:open is:issue"
3-
REPOSITORY_URL = "https://github.com/github/fetch"
2+
SEARCH_QUERY = "repo:owner/repo is:open is:issue"

.pylintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
disable=
33
redefined-argument-from-local,
44
too-many-arguments,
5-
too-few-public-methods,
5+
too-few-public-methods,
6+
duplicate-code,

README.md

+21-26
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
[![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)
44

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

1010
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.
1111

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

2727
1. Create a repository to host this GitHub Action or select an existing repository.
28-
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).
28+
1. Create the env values from the sample workflow below (GH_TOKEN, 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).
2929
Note: Your GitHub token will need to have read access to the repository in the organization that you want evaluated
3030
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`)
3131

@@ -36,8 +36,7 @@ Below are the allowed configuration options:
3636
| field | required | default | description |
3737
|-----------------------|----------|---------|-------------|
3838
| `GH_TOKEN` | true | | The GitHub Token used to scan the repository. Must have read access to all repository you are interested in scanning. |
39-
| `REPOSITORY_URL` | true | | The repository to scan for issues. |
40-
| `SEARCH_QUERY` | true | | The query by which you can filter issues/prs |
39+
| `SEARCH_QUERY` | true | | The query by which you can filter issues/prs which must contain a `repo:` entry or an `org:` entry. |
4140

4241
### Example workflows
4342

@@ -84,8 +83,7 @@ jobs:
8483
uses: github/issue-metrics@v1
8584
env:
8685
GH_TOKEN: ${{ secrets.GH_TOKEN }}
87-
REPOSITORY_URL: https://github.com/owner/repo
88-
SEARCH_QUERY: 'is:issue created:${{ env.last_month }} -reason:"not planned"'
86+
SEARCH_QUERY: 'repo:owner/repo is:issue created:${{ env.last_month }} -reason:"not planned"'
8987

9088
- name: Create issue
9189
uses: peter-evans/create-issue-from-file@v4
@@ -115,8 +113,7 @@ jobs:
115113
uses: github/issue-metrics@v1
116114
env:
117115
GH_TOKEN: ${{ secrets.GH_TOKEN }}
118-
REPOSITORY_URL: https://github.com/owner/repo
119-
SEARCH_QUERY: 'is:issue created:2023-05-01..2023-05-31 -reason:"not planned"'
116+
SEARCH_QUERY: 'repo:owner/repo is:issue created:2023-05-01..2023-05-31 -reason:"not planned"'
120117

121118
- name: Create issue
122119
uses: peter-evans/create-issue-from-file@v4
@@ -131,22 +128,22 @@ jobs:
131128
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. Further query options are listed in [the search documentation](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests). Here are some search query examples:
132129

133130
Issues opened in May 2023:
134-
- `is:issue created:2023-05-01..2023-05-31`
131+
- `repo:owner/repo is:issue created:2023-05-01..2023-05-31`
135132

136133
Issues closed in May 2023 (may have been open in May or earlier):
137-
- `is:issue closed:2023-05-01..2023-05-31`
134+
- `repo:owner/repo is:issue closed:2023-05-01..2023-05-31`
138135

139136
Pull requests opened in May 2023:
140-
- `is:pr created:2023-05-01..2023-05-31`
137+
- `repo:owner/repo is:pr created:2023-05-01..2023-05-31`
141138

142139
Pull requests closed in May 2023 (may have been open in May or earlier):
143-
- `is:pr closed:2023-05-01..2023-05-31`
140+
- `repo:owner/repo is:pr closed:2023-05-01..2023-05-31`
144141

145142
Both issues and pull requests opened in May 2023:
146-
- `created:2023-05-01..2023-05-31`
143+
- `repo:owner/repo created:2023-05-01..2023-05-31`
147144

148145
Both issues and pull requests closed in May 2023 (may have been open in May or earlier):
149-
- `closed:2023-05-01..2023-05-31`
146+
- `repo:owner/repo closed:2023-05-01..2023-05-31`
150147

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

@@ -168,8 +165,7 @@ jobs:
168165
uses: github/issue-metrics:v1
169166
env:
170167
GH_TOKEN: ${{ secrets.GH_TOKEN }}
171-
REPOSITORY_URL: https://github.com/owner/repo
172-
SEARCH_QUERY: 'created:2023-05-01..2023-05-31 -reason:"not planned"'
168+
SEARCH_QUERY: 'repo:owner/repo created:2023-05-01..2023-05-31 -reason:"not planned"'
173169

174170
- name: Create issue for opened issues and prs
175171
uses: peter-evans/create-issue-from-file@v4
@@ -182,8 +178,7 @@ jobs:
182178
uses: github/issue-metrics:v1
183179
env:
184180
GH_TOKEN: ${{ secrets.GH_TOKEN }}
185-
REPOSITORY_URL: https://github.com/owner/repo
186-
SEARCH_QUERY: 'closed:2023-05-01..2023-05-31 -reason:"not planned"'
181+
SEARCH_QUERY: 'repo:owner/repo closed:2023-05-01..2023-05-31 -reason:"not planned"'
187182

188183
- name: Create issue for closed issues and prs
189184
uses: peter-evans/create-issue-from-file@v4
@@ -202,23 +197,23 @@ jobs:
202197
| --- | ---: |
203198
| Average time to first response | 0:50:44.666667 |
204199
| Average time to close | 6 days, 7:08:52 |
200+
| Average time to answer | 1 day |
205201
| Number of issues that remain open | 2 |
206202
| Number of issues closed | 1 |
207203
| Total number of issues created | 3 |
208204

209-
| Title | URL | Time to first response | Time to close
210-
| --- | --- | ---: | ---: |
211-
| Issue Title 1 | https://github.com/user/repo/issues/1 | 0:00:41 | 6 days, 7:08:52 |
212-
| Issue Title 2 | https://github.com/user/repo/issues/2 | 0:05:26 | None |
213-
| Issue Title 3 | https://github.com/user/repo/issues/3 | 2:26:07 | None |
205+
| Title | URL | Time to first response | Time to close | Time to answer |
206+
| --- | --- | ---: | ---: | ---: |
207+
| Discussion Title 1 | https://github.com/user/repo/discussions/1 | 0:00:41 | 6 days, 7:08:52 | 1 day |
208+
| Pull Request Title 2 | https://github.com/user/repo/pulls/2 | 0:05:26 | None | None |
209+
| Issue Title 3 | https://github.com/user/repo/issues/3 | 2:26:07 | None | None |
214210

215211
```
216212

217213
## Local usage without Docker
218214

219215
1. Copy `.env-example` to `.env`
220216
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.
221-
1. Fill out the `.env` file with the _repository_url_ of the repository to scan
222217
1. Fill out the `.env` file with the _search_query_ to filter issues by
223218
1. `pip install -r requirements.txt`
224219
1. Run `python3 ./issue_metrics.py`, which will output issue metrics data

classes.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""A module containing classes for representing GitHub issues and their metrics.
2+
3+
Classes:
4+
IssueWithMetrics: A class to represent a GitHub issue with metrics.
5+
6+
"""
7+
8+
9+
class IssueWithMetrics:
10+
"""A class to represent a GitHub issue with metrics.
11+
12+
Attributes:
13+
title (str): The title of the issue.
14+
html_url (str): The URL of the issue on GitHub.
15+
time_to_first_response (timedelta, optional): The time it took to
16+
get the first response to the issue.
17+
time_to_close (timedelta, optional): The time it took to close the issue.
18+
time_to_answer (timedelta, optional): The time it took to answer the
19+
discussions in the issue.
20+
21+
"""
22+
23+
def __init__(
24+
self,
25+
title,
26+
html_url,
27+
time_to_first_response=None,
28+
time_to_close=None,
29+
time_to_answer=None,
30+
):
31+
self.title = title
32+
self.html_url = html_url
33+
self.time_to_first_response = time_to_first_response
34+
self.time_to_close = time_to_close
35+
self.time_to_answer = time_to_answer

discussions.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
This module provides functions for working with discussions in a GitHub repository.
3+
4+
Functions:
5+
get_discussions(repo_url: str, token: str, search_query: str) -> List[Dict]:
6+
Get a list of discussions in a GitHub repository that match the search query.
7+
8+
"""
9+
import requests
10+
11+
12+
def get_discussions(token: str, search_query: str):
13+
"""Get a list of discussions in a GitHub repository that match the search query.
14+
15+
Args:
16+
token (str): A personal access token for GitHub.
17+
search_query (str): The search query to filter discussions by.
18+
19+
Returns:
20+
list: A list of discussions in the repository that match the search query.
21+
22+
"""
23+
# Construct the GraphQL query
24+
query = """
25+
query($query: String!) {
26+
search(query: $query, type: DISCUSSION, first: 100) {
27+
edges {
28+
node {
29+
... on Discussion {
30+
title
31+
url
32+
createdAt
33+
comments(first: 1) {
34+
nodes {
35+
createdAt
36+
}
37+
}
38+
answerChosenAt
39+
closedAt
40+
}
41+
}
42+
}
43+
}
44+
}
45+
"""
46+
47+
# Remove the type:discussions filter from the search query
48+
search_query = search_query.replace("type:discussions ", "")
49+
# Set the variables for the GraphQL query
50+
variables = {"query": search_query}
51+
52+
# Send the GraphQL request
53+
headers = {"Authorization": f"Bearer {token}"}
54+
response = requests.post(
55+
"https://api.github.com/graphql",
56+
json={"query": query, "variables": variables},
57+
headers=headers,
58+
timeout=60,
59+
)
60+
61+
# Check for errors in the GraphQL response
62+
if response.status_code != 200 or "errors" in response.json():
63+
raise ValueError("GraphQL query failed")
64+
65+
data = response.json()["data"]
66+
67+
# Extract the discussions from the GraphQL response
68+
discussions = []
69+
for edge in data["search"]["edges"]:
70+
discussions.append(edge["node"])
71+
72+
return discussions

0 commit comments

Comments
 (0)