Skip to content

Commit

Permalink
#4 Check done pull request are closed
Browse files Browse the repository at this point in the history
  • Loading branch information
mcsken committed Oct 23, 2024
1 parent 2175b32 commit 147183f
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 109 deletions.
32 changes: 32 additions & 0 deletions check_done/checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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


def check_done_issues_for_warnings() -> list[str | None]:
result = []
done_issues = done_project_items_info()
criteria_checks = [
_check_done_issues_are_closed,
]
for issue in done_issues:
warnings = [check(issue) for check in criteria_checks if check(issue)]
result.extend(warnings)
return result


def _check_done_issues_are_closed(issue: ProjectItemInfo) -> str | None:
result = None
if not issue.closed:
result = _issue_warning_string(
issue,
"closed",
)
return result


def _issue_warning_string(issue: ProjectItemInfo, reason_for_warning: str) -> str:
# TODO: Ponder better wording.
return (
f" Done project item should be {reason_for_warning}."
f" - repository: '{issue.repository.name}', project item: '#{issue.number} {issue.title}'."
)
45 changes: 6 additions & 39 deletions check_done/command.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,25 @@
import logging
import sys

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 IssueInfo, IssueState
from check_done.checks import check_done_issues_for_warnings

logger = logging.getLogger(__name__)


def _issue_warning_string(issue: IssueInfo, reason_for_warning: str) -> str:
# TODO: Ponder better wording.
return (
f" Done issue should be {reason_for_warning}."
f" - repository: '{issue.repository.name}', issue: '#{issue.number} {issue.title}'."
)


def _check_done_issues_are_closed(issue: IssueInfo) -> str | None:
result = None
if issue.state == IssueState.OPEN:
result = _issue_warning_string(
issue,
f"of state {IssueState.CLOSED.value} but is of state {IssueState.OPEN.value}",
)
return result


def _check_done_issues_for_warnings() -> list[str | None]:
result = []
done_issues = done_project_items_info()
criteria_checks = [
_check_done_issues_are_closed,
]
for issue in done_issues:
warnings = [check(issue) for check in criteria_checks if check(issue)]
result.extend(warnings)
return result


def check_done_command():
result = 1
try:
warnings = _check_done_issues_for_warnings()
warnings = check_done_issues_for_warnings()
if len(warnings) == 0:
logging.info("No warnings found.")
logger.info("No warnings found.")
result = 0
else:
for warning in warnings:
logging.warning(warning)
logger.warning(warning)
except KeyboardInterrupt:
logging.exception("Interrupted as requested by user.")
logger.exception("Interrupted as requested by user.")
except Exception:
logging.exception("Cannot check done issues.")
logger.exception("Cannot check done issues.")
return result


Expand Down
57 changes: 26 additions & 31 deletions check_done/done_project_items_info/done_project_items_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
github_organization_name_and_project_number_from_url_if_matches,
)
from check_done.done_project_items_info.info import (
IssueInfo,
GithubContentType,
NodeByIdInfo,
NodesInfo,
OrganizationInfo,
PaginatedQueryInfo,
ProjectItemInfo,
ProjectV2ItemNodeInfo,
ProjectV2NodeInfo,
ProjectV2SingleSelectFieldNodeInfo,
Expand All @@ -32,30 +33,26 @@
_PATH_TO_QUERIES = Path(__file__).parent.parent / "done_project_items_info" / "queries"
_MAX_ENTRIES_PER_PAGE = 100
_GITHUB_PROJECT_STATUS_FIELD_NAME = "Status"
# TODO#4: Turn into an enum class when implementing pull requests
_GITHUB_ISSUE_TYPE_NAME = "ISSUE"
logger = logging.getLogger(__name__)


def done_project_items_info() -> list[IssueInfo]:
result = []
def done_project_items_info() -> list[ProjectItemInfo]:
session = requests.Session()
session.headers = {"Accept": "application/vnd.github+json"}
session.auth = HttpBearerAuth(_ACCESS_TOKEN)

project_id_node_infos = _query_nodes_info(OrganizationInfo, _GraphQlQuery.PROJECTS_IDS.name, session)
project_id = matching_project_id(project_id_node_infos)
projects_ids_nodes_info = _query_nodes_info(OrganizationInfo, _GraphQlQuery.PROJECTS_IDS.name, session)
project_id = matching_project_id(projects_ids_nodes_info)

last_project_state_option_id_node_infos = _query_nodes_info(
last_project_state_option_ids_nodes_info = _query_nodes_info(
NodeByIdInfo, _GraphQlQuery.PROJECT_STATE_OPTIONS_IDS.name, session, project_id
)
last_project_state_option_id = matching_last_project_state_option_id(last_project_state_option_id_node_infos)

done_issues_node_infos = _query_nodes_info(NodeByIdInfo, _GraphQlQuery.PROJECT_V_2_ISSUES.name, session, project_id)
_done_issue_infos = done_issue_infos(done_issues_node_infos, last_project_state_option_id)
last_project_state_option_id = matching_last_project_state_option_id(last_project_state_option_ids_nodes_info)

result.extend(_done_issue_infos)
# TODO#4: Extend done PRs to result
project_items_nodes_info = _query_nodes_info(
NodeByIdInfo, _GraphQlQuery.PROJECT_V_2_ITEMS.name, session, project_id
)
result = filtered_project_item_infos_by_done_status(project_items_nodes_info, last_project_state_option_id)
return result


Expand Down Expand Up @@ -88,25 +85,23 @@ def matching_last_project_state_option_id(node_infos: list[ProjectV2SingleSelect
) from error


def done_issue_infos(node_infos: list[ProjectV2ItemNodeInfo], last_project_state_option_id: str) -> list[IssueInfo]:
def filtered_project_item_infos_by_done_status(
node_infos: list[ProjectV2ItemNodeInfo], last_project_state_option_id: str
) -> list[ProjectItemInfo]:
result = []
for node in node_infos:
has_last_project_state_option = node.field_value_by_name.option_id == last_project_state_option_id
has_type_of_issue = node.type == _GITHUB_ISSUE_TYPE_NAME
if has_last_project_state_option and has_type_of_issue:
assert node.type == GithubContentType.ISSUE or GithubContentType.PULL_REQUEST
if has_last_project_state_option:
result.append(node.content)
if len(result) < 1:
logger.warning(
f"No issues found with the last project status option selected in the GitHub project with number "
f"No project items found with the last project status option selected in the GitHub project with number "
f"`{PROJECT_NUMBER}` in organization '{ORGANIZATION_NAME}'"
)
return result


# TODO#4: Write a function that checks if there are done_issue_infos and done_pull_request_info combined, and if not
# raises an error.


@lru_cache
def minimized_graphql(graphql_query: str) -> str:
single_spaced_query = re.sub(r"\s+", " ", graphql_query)
Expand All @@ -125,7 +120,7 @@ def _graphql_query(item_name: str) -> str:
class _GraphQlQuery(Enum):
PROJECTS_IDS = _graphql_query("projects_ids")
PROJECT_STATE_OPTIONS_IDS = _graphql_query("project_state_options_ids")
PROJECT_V_2_ISSUES = _graphql_query("project_v2_issues")
PROJECT_V_2_ITEMS = _graphql_query("project_v2_items")

@staticmethod
def query_for(name: str):
Expand Down Expand Up @@ -154,22 +149,22 @@ def _query_nodes_info(
response = session.post(_GRAPHQL_ENDPOINT, json=json_payload_map)
response_map = checked_graphql_data_map(response)
response_info = base_model(**response_map)
nodes_info = get_nodes_info_from_response_info(response_info)
nodes = nodes_info.nodes
page_info = nodes_info.page_info
paginated_query_info = get_paginated_query_info_from_response_info(response_info)
nodes_info = paginated_query_info.nodes
page_info = paginated_query_info.page_info
after = page_info.endCursor
has_more_pages = page_info.hasNextPage
result.extend(nodes)
result.extend(nodes_info)
return result


def get_nodes_info_from_response_info(base_model: BaseModel) -> NodesInfo:
if isinstance(base_model, NodesInfo):
def get_paginated_query_info_from_response_info(base_model: BaseModel) -> PaginatedQueryInfo:
if isinstance(base_model, PaginatedQueryInfo):
return base_model
for model in base_model.__fields_set__:
field_value = getattr(base_model, model)
if isinstance(field_value, BaseModel):
page_info = get_nodes_info_from_response_info(field_value)
page_info = get_paginated_query_info_from_response_info(field_value)
if page_info is not None:
return page_info
raise ValueError(f"Could not find nodes info in {base_model}.")
Expand Down
25 changes: 15 additions & 10 deletions check_done/done_project_items_info/info.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import Enum, StrEnum
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, NonNegativeInt, field_validator
Expand All @@ -13,11 +13,16 @@ class NodesTypeName(StrEnum):
SingleUserIssueCustomField = "SingleUserIssueCustomField"


class IssueState(Enum):
class ProjectItemState(StrEnum):
CLOSED = "CLOSED"
OPEN = "OPEN"


class GithubContentType(StrEnum):
ISSUE = "ISSUE"
PULL_REQUEST = "PULL_REQUEST"


class _EmptyDict(BaseModel):
model_config = ConfigDict(extra="forbid")

Expand All @@ -27,7 +32,7 @@ class PageInfo(BaseModel):
hasNextPage: bool


class NodesInfo(BaseModel):
class PaginatedQueryInfo(BaseModel):
nodes: list[Any]
page_info: PageInfo = Field(alias="pageInfo")

Expand All @@ -46,8 +51,8 @@ class ProjectV2NodeInfo(BaseModel):
id: str
number: NonNegativeInt
typename: str = Field(alias="__typename")
fields: NodesInfo | None = None
items: NodesInfo | None = None
fields: PaginatedQueryInfo | None = None
items: PaginatedQueryInfo | None = None


class ProjectV2Options(BaseModel):
Expand All @@ -71,18 +76,18 @@ class RepositoryInfo(BaseModel):
name: str


class IssueInfo(BaseModel):
class ProjectItemInfo(BaseModel):
number: NonNegativeInt
state: IssueState
closed: bool
title: str
repository: RepositoryInfo


class ProjectV2ItemNodeInfo(BaseModel):
type: str
type: GithubContentType
# TODO#4: When including pull requests, implement their info model
# See: https://docs.github.com/en/graphql/reference/unions#projectv2itemcontent
content: IssueInfo | _EmptyDict = None
content: ProjectItemInfo | _EmptyDict = None
field_value_by_name: ProjectV2ItemProjectStatusInfo | None = Field(alias="fieldValueByName", default=None)


Expand All @@ -91,7 +96,7 @@ class NodeByIdInfo(BaseModel):


class _ProjectsV2Info(BaseModel):
projects_v2: NodesInfo = Field(alias="projectsV2")
projects_v2: PaginatedQueryInfo = Field(alias="projectsV2")


class OrganizationInfo(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ query projectV2Issues(
content {
... on Issue {
number
state
closed
title
repository {
name
}
}
... on PullRequest {
number
closed
title
repository {
name
Expand Down
Loading

0 comments on commit 147183f

Please sign in to comment.