Skip to content

Commit

Permalink
Merge pull request #23 from siisurit/7-check-goals
Browse files Browse the repository at this point in the history
#7 Check goals
  • Loading branch information
mcsken authored Nov 1, 2024
2 parents 21ad983 + 29dd249 commit b8c62ec
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 24 deletions.
9 changes: 5 additions & 4 deletions check_done/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import requests

from check_done.common import (
CHECK_DONE_GITHUB_APP_ID,
CHECK_DONE_GITHUB_APP_PRIVATE_KEY,
AuthenticationError,
HttpBearerAuth,
config_info,
)

_CHECK_DONE_GITHUB_APP_ID = config_info().check_done_github_app_id
_CHECK_DONE_GITHUB_APP_PRIVATE_KEY = config_info().check_done_github_app_private_key
_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
_SECONDS_PER_MINUTE = 60
_ISSUED_AT = int(time.time())
Expand Down Expand Up @@ -50,9 +51,9 @@ def _generated_jwt_token() -> str:
payload = {
"exp": _EXPIRES_AT,
"iat": _ISSUED_AT,
"iss": CHECK_DONE_GITHUB_APP_ID,
"iss": _CHECK_DONE_GITHUB_APP_ID,
}
return jwt.encode(payload, CHECK_DONE_GITHUB_APP_PRIVATE_KEY, algorithm="RS256")
return jwt.encode(payload, _CHECK_DONE_GITHUB_APP_PRIVATE_KEY, algorithm="RS256")
except Exception as error:
raise AuthenticationError(f"Cannot generate JWT token: {error}") from error

Expand Down
27 changes: 26 additions & 1 deletion check_done/checks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from html.parser import HTMLParser

from check_done.done_project_items_info.done_project_items_info import done_project_items_info
from check_done.done_project_items_info.info import ProjectItemInfo

Expand Down Expand Up @@ -33,15 +35,38 @@ def _is_not_closed(project_item: ProjectItemInfo) -> bool:


def _is_not_assigned(project_item: ProjectItemInfo) -> bool:
return project_item.assignees.total_count < 1
return project_item.assignees.total_count == 0


def _has_no_milestone(project_item: ProjectItemInfo) -> bool:
return project_item.milestone is None


def has_unfinished_goals(project_item: ProjectItemInfo) -> bool:
class _GoalsHTMLParser(HTMLParser):
def __init__(self):
super().__init__()
self.is_any_goal_unchecked = False

def handle_starttag(self, tag, attrs):
if tag == "input":
attr_dict = dict(attrs)
is_checkbox = attr_dict.get("type") == "checkbox"
is_unchecked = "checked" not in attr_dict
if is_checkbox and is_unchecked:
self.is_any_goal_unchecked = True

def has_unfinished_goals(self):
return self.is_any_goal_unchecked

parser = _GoalsHTMLParser()
parser.feed(project_item.body_html)
return parser.has_unfinished_goals()


CONDITION_CHECK_AND_WARNING_REASON_LIST = [
(_is_not_closed, "not closed"),
(_is_not_assigned, "missing assignee"),
(_has_no_milestone, "missing milestone"),
(has_unfinished_goals, "missing finished goals"),
]
27 changes: 12 additions & 15 deletions check_done/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
from requests.auth import AuthBase

load_dotenv()
_ENVVAR_CHECK_DONE_GITHUB_APP_ID = "CHECK_DONE_GITHUB_APP_ID"
_ENVVAR_CHECK_DONE_GITHUB_APP_PRIVATE_KEY = "CHECK_DONE_GITHUB_APP_PRIVATE_KEY"
CHECK_DONE_GITHUB_APP_ID = os.environ.get(_ENVVAR_CHECK_DONE_GITHUB_APP_ID)
CHECK_DONE_GITHUB_APP_PRIVATE_KEY = os.environ.get(_ENVVAR_CHECK_DONE_GITHUB_APP_PRIVATE_KEY)

_GITHUB_ORGANIZATION_NAME_AND_PROJECT_NUMBER_URL_REGEX = re.compile(
r"https://github\.com/orgs/(?P<organization_name>[a-zA-Z0-9\-]+)/projects/(?P<project_number>[0-9]+).*"
Expand All @@ -22,20 +18,21 @@


class _ConfigInfo(BaseModel):
board_url: str
api_key: str

@field_validator("api_key", mode="before")
def api_key_from_env(cls, api_key: Any | None):
if isinstance(api_key, str):
stripped_api_key = api_key.strip()
project_board_url: str
check_done_github_app_id: str
check_done_github_app_private_key: str

@field_validator("check_done_github_app_id", "check_done_github_app_private_key", mode="before")
def value_from_env(cls, value: Any | None):
if isinstance(value, str):
stripped_value = value.strip()
result = (
resolved_environment_variables(api_key)
if stripped_api_key.startswith("${") and stripped_api_key.endswith("}")
else stripped_api_key
resolved_environment_variables(value)
if stripped_value.startswith("${") and stripped_value.endswith("}")
else stripped_value
)
else:
result = api_key
result = value
return result


Expand Down
4 changes: 2 additions & 2 deletions check_done/done_project_items_info/done_project_items_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
)

_GRAPHQL_ENDPOINT = "https://api.github.com/graphql"
_BOARD_URL = config_info().board_url
ORGANIZATION_NAME, PROJECT_NUMBER = github_organization_name_and_project_number_from_url_if_matches(_BOARD_URL)
_PROJECT_BOARD_URL = config_info().project_board_url
ORGANIZATION_NAME, PROJECT_NUMBER = github_organization_name_and_project_number_from_url_if_matches(_PROJECT_BOARD_URL)
_ACCESS_TOKEN = github_app_access_token(ORGANIZATION_NAME)
_PATH_TO_QUERIES = Path(__file__).parent.parent / "done_project_items_info" / "queries"
_MAX_ENTRIES_PER_PAGE = 100
Expand Down
1 change: 1 addition & 0 deletions check_done/done_project_items_info/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class MilestoneInfo(BaseModel):

class ProjectItemInfo(BaseModel):
assignees: AssigneesInfo
body_html: str = Field(alias="bodyHTML", default=None)
closed: bool
number: NonNegativeInt
repository: RepositoryInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ query projectV2Issues(
assignees {
totalCount
}
bodyHTML
number
milestone {
id
Expand All @@ -33,6 +34,7 @@ query projectV2Issues(
assignees {
totalCount
}
bodyHTML
number
milestone {
id
Expand Down
5 changes: 3 additions & 2 deletions data/.check_done.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
board_url: "https://github.com/orgs/siisurit/projects/2"
api_key: ${GITHUB_API_KEY}
project_board_url: "https://github.com/orgs/siisurit/projects/2"
check_done_github_app_id: ${CHECK_DONE_GITHUB_APP_ID}
check_done_github_app_private_key: ${CHECK_DONE_GITHUB_APP_PRIVATE_KEY}
59 changes: 59 additions & 0 deletions tests/test_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from check_done.checks import has_unfinished_goals
from check_done.done_project_items_info.info import AssigneesInfo, MilestoneInfo, ProjectItemInfo, RepositoryInfo


def test_can_check_for_unfinished_goals():
html_with_an_unfinished_goals = """
<h2 dir="auto">Goals</h2>
<ul class="contains-task-list">
<li class="task-list-item">
<input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> Test 1.
</li>
<li class="task-list-item">
<input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> Test. 2
</li>
</ul>
"""
project_item_with_unfinished_goals = ProjectItemInfo(
assignees=AssigneesInfo(totalCount=1),
bodyHTML=html_with_an_unfinished_goals,
closed=True,
number=1,
repository=RepositoryInfo(name="test_repo"),
milestone=MilestoneInfo(id="1"),
title="Test",
)
html_with_an_finished_goals = """
<h2 dir="auto">Goals</h2>
<ul class="contains-task-list">
<li class="task-list-item">
<input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> Test 1.
</li>
<li class="task-list-item">
<input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> Test. 2
</li>
</ul>
"""
project_item_with_finished_goals = ProjectItemInfo(
assignees=AssigneesInfo(totalCount=1),
bodyHTML=html_with_an_finished_goals,
closed=True,
number=1,
repository=RepositoryInfo(name="test_repo"),
milestone=MilestoneInfo(id="1"),
title="Test",
)
empty_html_body = ""
project_item_with_empty_html_body = ProjectItemInfo(
assignees=AssigneesInfo(totalCount=1),
bodyHTML=empty_html_body,
closed=True,
number=1,
repository=RepositoryInfo(name="test_repo"),
milestone=MilestoneInfo(id="1"),
title="Test",
)

assert has_unfinished_goals(project_item_with_unfinished_goals) is True
assert has_unfinished_goals(project_item_with_finished_goals) is False
assert has_unfinished_goals(project_item_with_empty_html_body) is False

0 comments on commit b8c62ec

Please sign in to comment.