Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Configurable column hiding #40

Merged
merged 2 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env-example
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
GH_TOKEN = " "
SEARCH_QUERY = "repo:owner/repo is:open is:issue"
HIDE_TIME_TO_FIRST_RESPONSE = False
HIDE_TIME_TO_CLOSE = False
HIDE_TIME_TO_ANSWER = False
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ Below are the allowed configuration options:

| field | required | default | description |
|-----------------------|----------|---------|-------------|
| `GH_TOKEN` | true | | The GitHub Token used to scan the repository. Must have read access to all repository you are interested in scanning. |
| `SEARCH_QUERY` | true | | The query by which you can filter issues/prs which must contain a `repo:` entry or an `org:` entry. For discussions, include `type:discussions` in the query. |
| `GH_TOKEN` | True | | The GitHub Token used to scan the repository. Must have read access to all repository you are interested in scanning. |
| `SEARCH_QUERY` | True | | The query by which you can filter issues/prs which must contain a `repo:` entry or an `org:` entry. For discussions, include `type:discussions` in the query. |
| `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_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_ANSWER` | False | False | If set to true, the time to answer a discussion will not be displayed in the generated markdown file. |

### Example workflows

Expand Down Expand Up @@ -196,6 +199,7 @@ jobs:

## Example issue_metrics.md output

Here is the output with no hidden columns:
```markdown
# Issue Metrics

Expand All @@ -209,13 +213,31 @@ jobs:
| Total number of items created | 3 |

| Title | URL | Time to first response | Time to close | Time to answer |
| --- | --- | ---: | ---: | ---: |
| --- | --- | --- | --- | --- |
| Discussion Title 1 | https://github.com/user/repo/discussions/1 | 0:00:41 | 6 days, 7:08:52 | 1 day |
| Pull Request Title 2 | https://github.com/user/repo/pulls/2 | 0:05:26 | None | None |
| Issue Title 3 | https://github.com/user/repo/issues/3 | 2:26:07 | None | None |

```

Here is the output with all hidable columns hidden:
```markdown
# Issue Metrics

| Metric | Value |
| --- | ---: |
| Number of items that remain open | 2 |
| Number of items closed | 1 |
| Total number of items created | 3 |

| Title | URL |
| --- | --- |
| Discussion Title 1 | https://github.com/user/repo/discussions/1 |
| Pull Request Title 2 | https://github.com/user/repo/pulls/2 |
| Issue Title 3 | https://github.com/user/repo/issues/3 | 2:26:07 |

```

## Local usage without Docker

1. Copy `.env-example` to `.env`
Expand Down
113 changes: 83 additions & 30 deletions markdown_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,49 @@
file: file object = None
) -> None:
Write the issues with metrics to a markdown file.

get_non_hidden_columns(
average_time_to_first_response: timedelta,
average_time_to_close: timedelta,
average_time_to_answer: timedelta
) -> List[str]:
Get the columns that are not hidden.
"""

import os
from datetime import timedelta
from typing import List, Union

from classes import IssueWithMetrics


def get_non_hidden_columns() -> List[str]:
"""
Get a list of the columns that are not hidden.

Args:
None

Returns:
List[str]: A list of the columns that are not hidden.

"""
columns = ["Title", "URL"]
# Find the number of columns and which are to be hidden
hide_time_to_first_response = os.getenv("HIDE_TIME_TO_FIRST_RESPONSE")
if not hide_time_to_first_response:
columns.append("Time to first response")

hide_time_to_close = os.getenv("HIDE_TIME_TO_CLOSE")
if not hide_time_to_close:
columns.append("Time to close")

hide_time_to_answer = os.getenv("HIDE_TIME_TO_ANSWER")
if not hide_time_to_answer:
columns.append("Time to answer")

return columns


def write_to_markdown(
issues_with_metrics: Union[List[IssueWithMetrics], None],
average_time_to_first_response: Union[timedelta, None],
Expand All @@ -50,40 +85,58 @@ def write_to_markdown(
None.

"""
columns = get_non_hidden_columns()

# If all the metrics are None, then there are no issues
if not issues_with_metrics or len(issues_with_metrics) == 0:
with file or open("issue_metrics.md", "w", encoding="utf-8") as file:
file.write("no issues found for the given search criteria\n\n")
else:
# Sort the issues by time to first response
issues_with_metrics.sort(
key=lambda x: x.time_to_first_response or timedelta.max
)
with file or open("issue_metrics.md", "w", encoding="utf-8") as file:
file.write("# Issue Metrics\n\n")
file.write("| Metric | Value |\n")
file.write("| --- | ---: |\n")
return

# Sort the issues by time to first response
issues_with_metrics.sort(key=lambda x: x.time_to_first_response or timedelta.max)
with file or open("issue_metrics.md", "w", encoding="utf-8") as file:
file.write("# Issue Metrics\n\n")

# Write first table with overall metrics
file.write("| Metric | Value |\n")
file.write("| --- | ---: |\n")
if "Time to first response" in columns:
file.write(
f"| Average time to first response | {average_time_to_first_response} |\n"
)
if "Time to close" in columns:
file.write(f"| Average time to close | {average_time_to_close} |\n")
if "Time to answer" in columns:
file.write(f"| Average time to answer | {average_time_to_answer} |\n")
file.write(f"| Number of items that remain open | {num_issues_opened} |\n")
file.write(f"| Number of items closed | {num_issues_closed} |\n")
file.write(
f"| Total number of items created | {len(issues_with_metrics)} |\n\n"
)
file.write(
"| Title | URL | Time to first response | Time to close | Time to answer |\n"
)
file.write("| --- | --- | ---: | ---: | ---: |\n")
for issue in issues_with_metrics:
file.write(
f"| "
f"{issue.title} | "
f"{issue.html_url} | "
f"{issue.time_to_first_response} |"
f" {issue.time_to_close} |"
f" {issue.time_to_answer} |"
f"\n"
)
print("Wrote issue metrics to issue_metrics.md")
file.write(f"| Number of items that remain open | {num_issues_opened} |\n")
file.write(f"| Number of items closed | {num_issues_closed} |\n")
file.write(
f"| Total number of items created | {len(issues_with_metrics)} |\n\n"
)

# Write second table with individual issue/pr/discussion metrics
# First write the header
file.write("|")
for column in columns:
file.write(f" {column} |")
file.write("\n")

# Then write the column dividers
file.write("|")
for _ in columns:
file.write(" --- |")
file.write("\n")

# Then write the issues/pr/discussions row by row
for issue in issues_with_metrics:
file.write(f"| " f"{issue.title} | " f"{issue.html_url} |")
if "Time to first response" in columns:
file.write(f" {issue.time_to_first_response} |")
if "Time to close" in columns:
file.write(f" {issue.time_to_close} |")
if "Time to answer" in columns:
file.write(f" {issue.time_to_answer} |")
file.write("\n")

print("Wrote issue metrics to issue_metrics.md")
75 changes: 74 additions & 1 deletion test_markdown_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_write_to_markdown(self):
"| Number of items closed | 1 |\n"
"| Total number of items created | 2 |\n\n"
"| Title | URL | Time to first response | Time to close | Time to answer |\n"
"| --- | --- | ---: | ---: | ---: |\n"
"| --- | --- | --- | --- | --- |\n"
"| Issue 1 | https://github.com/user/repo/issues/1 | 1 day, 0:00:00 | "
"2 days, 0:00:00 | 3 days, 0:00:00 |\n"
"| Issue 2 | https://github.com/user/repo/issues/2 | 3 days, 0:00:00 | "
Expand All @@ -93,3 +93,76 @@ def test_write_to_markdown_no_issues(self):
"issue_metrics.md", "w", encoding="utf-8"
)
mock_open_file().write.assert_called_once_with(expected_output)


class TestWriteToMarkdownWithEnv(unittest.TestCase):
"""Test the write_to_markdown function with the HIDE* environment variables set."""

def setUp(self):
# Set the HIDE* environment variables to True
os.environ["HIDE_TIME_TO_FIRST_RESPONSE"] = "True"
os.environ["HIDE_TIME_TO_CLOSE"] = "True"
os.environ["HIDE_TIME_TO_ANSWER"] = "True"

def tearDown(self):
# Unset the HIDE* environment variables
os.environ.pop("HIDE_TIME_TO_FIRST_RESPONSE")
os.environ.pop("HIDE_TIME_TO_CLOSE")
os.environ.pop("HIDE_TIME_TO_ANSWER")

def test_writes_markdown_file_with_non_hidden_columns_only(self):
"""
Test that write_to_markdown writes the correct
markdown file with non-hidden columns only.
"""

# Create mock data
issues_with_metrics = [
IssueWithMetrics(
title="Issue 1",
html_url="https://github.com/user/repo/issues/1",
time_to_first_response=timedelta(minutes=10),
time_to_close=timedelta(days=1),
time_to_answer=timedelta(hours=2),
),
IssueWithMetrics(
title="Issue 2",
html_url="https://github.com/user/repo/issues/2",
time_to_first_response=timedelta(minutes=20),
time_to_close=timedelta(days=2),
time_to_answer=timedelta(hours=4),
),
]
average_time_to_first_response = timedelta(minutes=15)
average_time_to_close = timedelta(days=1.5)
average_time_to_answer = timedelta(hours=3)
num_issues_opened = 2
num_issues_closed = 1

# Call the function
write_to_markdown(
issues_with_metrics,
average_time_to_first_response,
average_time_to_close,
average_time_to_answer,
num_issues_opened,
num_issues_closed,
)

# 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 | Value |\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 |\n"
"| --- | --- |\n"
"| Issue 1 | https://github.com/user/repo/issues/1 |\n"
"| Issue 2 | https://github.com/user/repo/issues/2 |\n"
)
self.assertEqual(content, expected_content)
os.remove("issue_metrics.md")