Skip to content
Open
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 gittensor/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class PullRequest:
structural_score: float = 0.0
leaf_count: int = 0
leaf_score: float = 0.0
closed_at: Optional[datetime] = None # None for MERGED/OPEN PRs
merged_by_login: Optional[str] = None
file_changes: Optional[List[FileChange]] = None
issues: Optional[List[Issue]] = None
Expand Down Expand Up @@ -266,6 +267,7 @@ def from_graphql_response(cls, pr_data: dict, uid: int, hotkey: str, github_id:
raw_edited_at = pr_data.get('lastEditedAt')
last_edited_at = parse_github_timestamp_to_cst(raw_edited_at) if isinstance(raw_edited_at, str) else None
merged_at = parse_github_timestamp_to_cst(pr_data['mergedAt']) if is_merged else None
closed_at = parse_github_timestamp_to_cst(pr_data['closedAt']) if pr_data.get('closedAt') else None

return cls(
number=pr_data['number'],
Expand All @@ -277,6 +279,7 @@ def from_graphql_response(cls, pr_data: dict, uid: int, hotkey: str, github_id:
author_login=pr_data['author']['login'],
merged_at=merged_at,
created_at=parse_github_timestamp_to_cst(pr_data['createdAt']),
closed_at=closed_at,
pr_state=pr_state,
additions=pr_data['additions'],
deletions=pr_data['deletions'],
Expand Down
3 changes: 2 additions & 1 deletion gittensor/validator/storage/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
BULK_UPSERT_PULL_REQUESTS = """
INSERT INTO pull_requests (
number, repository_full_name, uid, hotkey, github_id, title, author_login,
merged_at, pr_created_at, pr_state,
merged_at, pr_created_at, closed_at, pr_state,
repo_weight_multiplier, base_score, issue_multiplier,
open_pr_spam_multiplier, pioneer_dividend, pioneer_rank, time_decay_multiplier,
credibility_multiplier, review_quality_multiplier,
Expand All @@ -60,6 +60,7 @@
title = EXCLUDED.title,
author_login = EXCLUDED.author_login,
merged_at = EXCLUDED.merged_at,
closed_at = EXCLUDED.closed_at,
pr_state = EXCLUDED.pr_state,
repo_weight_multiplier = EXCLUDED.repo_weight_multiplier,
base_score = EXCLUDED.base_score,
Expand Down
1 change: 1 addition & 0 deletions gittensor/validator/storage/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def store_pull_requests_bulk(self, pull_requests: List[PullRequest]) -> int:
pr.author_login,
pr.merged_at,
pr.created_at,
pr.closed_at,
pr.pr_state.value, # Convert PRState enum to string
pr.repo_weight_multiplier,
pr.base_score,
Expand Down
42 changes: 42 additions & 0 deletions tests/validator/test_pull_request_closed_at.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# The MIT License (MIT)
# Copyright © 2025 Entrius

"""Tests for PullRequest.closed_at field parsing from GraphQL responses."""

from gittensor.classes import PullRequest

_BASE_PR_DATA = {
'number': 1,
'title': 'test',
'author': {'login': 'user'},
'createdAt': '2025-01-01T00:00:00Z',
'additions': 10,
'deletions': 5,
'repository': {'owner': {'login': 'owner'}, 'name': 'repo'},
'closingIssuesReferences': {'nodes': []},
}


def _parse(state: str, closed_at=None, merged_at=None) -> PullRequest:
data = {**_BASE_PR_DATA, 'state': state, 'closedAt': closed_at, 'mergedAt': merged_at}
if merged_at:
data['mergedBy'] = {'login': 'maintainer'}
data['commits'] = {'totalCount': 1}
return PullRequest.from_graphql_response(data, uid=1, hotkey='k', github_id='1')


def test_closed_pr_has_closed_at():
pr = _parse('CLOSED', closed_at='2025-01-15T00:00:00Z')
assert pr.closed_at is not None
assert pr.merged_at is None


def test_merged_pr_has_closed_at():
pr = _parse('MERGED', closed_at='2025-01-20T00:00:00Z', merged_at='2025-01-20T00:00:00Z')
assert pr.closed_at is not None
assert pr.merged_at is not None


def test_open_pr_has_no_closed_at():
pr = _parse('OPEN')
assert pr.closed_at is None