1616
1717from datetime import datetime , timezone
1818
19+ from scripts .reviewer_bot_lib import review_read_support
20+
1921from . import reviewer_review_helpers
2022
23+ _UNSET = object ()
24+
2125
2226def _record_timestamp (record : dict | None , * , parse_timestamp ) -> datetime | None :
2327 if not isinstance (record , dict ):
@@ -68,31 +72,29 @@ def _contributor_revision_handoff_record(review_data: dict, current_head: str |
6872 return contributor_revision
6973
7074
71- def compute_reviewer_response_state (
72- bot ,
73- issue_number : int ,
75+ def derive_reviewer_response_state (
7476 review_data : dict ,
7577 * ,
76- issue_snapshot : dict | None = None ,
77- pull_request : dict | None = None ,
78- reviews : list [dict ] | None = None ,
78+ issue_is_pull_request : bool ,
79+ current_head : str | None = None ,
80+ reviewer_comment : dict | None | object = _UNSET ,
81+ reviewer_review : dict | None | object = _UNSET ,
82+ contributor_comment : dict | None | object = _UNSET ,
83+ had_reviewer_review : bool = False ,
84+ approval_result : dict [str , object ] | None = None ,
7985) -> dict [str , object ]:
80- from scripts .reviewer_bot_lib import reviews as legacy_reviews
81-
82- if issue_snapshot is None :
83- issue_snapshot = bot .github .get_issue_or_pr_snapshot (issue_number )
84- if not isinstance (issue_snapshot , dict ):
85- return {"state" : "projection_failed" , "reason" : "issue_snapshot_unavailable" }
86- is_pr = isinstance (issue_snapshot .get ("pull_request" ), dict )
8786 current_reviewer = review_data .get ("current_reviewer" )
8887 if not isinstance (current_reviewer , str ) or not current_reviewer .strip ():
8988 return {"state" : "untracked" , "reason" : "no_current_reviewer" }
9089
91- reviewer_comment = review_data .get ("reviewer_comment" , {}).get ("accepted" )
92- reviewer_review = review_data .get ("reviewer_review" , {}).get ("accepted" )
93- contributor_comment = review_data .get ("contributor_comment" , {}).get ("accepted" )
90+ if reviewer_comment is _UNSET :
91+ reviewer_comment = review_data .get ("reviewer_comment" , {}).get ("accepted" )
92+ if reviewer_review is _UNSET :
93+ reviewer_review = review_data .get ("reviewer_review" , {}).get ("accepted" )
94+ if contributor_comment is _UNSET :
95+ contributor_comment = review_data .get ("contributor_comment" , {}).get ("accepted" )
9496
95- if not is_pr :
97+ if not issue_is_pull_request :
9698 if not reviewer_comment and not reviewer_review :
9799 return {
98100 "state" : "awaiting_reviewer_response" ,
@@ -107,7 +109,7 @@ def compute_reviewer_response_state(
107109 if reviewer_review_helpers .compare_records (
108110 reviewer_review ,
109111 latest_reviewer_response ,
110- parse_timestamp = legacy_reviews .parse_github_timestamp ,
112+ parse_timestamp = review_read_support .parse_github_timestamp ,
111113 ) > 0 :
112114 latest_reviewer_response = reviewer_review
113115 completion = review_data .get ("current_cycle_completion" )
@@ -121,33 +123,11 @@ def compute_reviewer_response_state(
121123 }
122124 return {"state" : "done" , "reason" : None }
123125
124- pull_request_result = legacy_reviews ._pull_request_read_result (bot , issue_number , pull_request )
125- if not pull_request_result .get ("ok" ):
126- return {"state" : "projection_failed" , "reason" : str (pull_request_result .get ("reason" ))}
127- pull_request = pull_request_result ["pull_request" ]
128- head = pull_request .get ("head" )
129- current_head = head .get ("sha" ) if isinstance (head , dict ) else None
130126 if not isinstance (current_head , str ) or not current_head .strip ():
131127 return {"state" : "projection_failed" , "reason" : "pull_request_head_unavailable" }
132128
133129 if not reviewer_comment and not reviewer_review :
134- reviews_result = legacy_reviews .get_pull_request_reviews_result (bot , issue_number , reviews )
135- if not reviews_result .get ("ok" ):
136- return {"state" : "projection_failed" , "reason" : str (reviews_result .get ("reason" ))}
137- reviews = reviews_result ["reviews" ]
138- preferred_live_review = reviewer_review_helpers .get_preferred_current_reviewer_review_for_cycle (
139- bot ,
140- issue_number ,
141- review_data ,
142- pull_request = pull_request ,
143- reviews = reviews ,
144- )
145- if preferred_live_review is not None :
146- reviewer_review = reviewer_review_helpers .build_reviewer_review_record_from_live_review (
147- preferred_live_review ,
148- actor = current_reviewer ,
149- )
150- else :
130+ if not had_reviewer_review :
151131 return {
152132 "state" : "awaiting_reviewer_response" ,
153133 "reason" : "no_reviewer_activity" ,
@@ -158,37 +138,11 @@ def compute_reviewer_response_state(
158138 "contributor_handoff" : None ,
159139 }
160140
161- stored_review_head = reviewer_review .get ("reviewed_head_sha" ) if isinstance (reviewer_review , dict ) else None
162- refresh_live_review = reviews is not None or reviewer_review is None
163- if not refresh_live_review :
164- refresh_live_review = not isinstance (stored_review_head , str ) or stored_review_head != current_head
165-
166- preferred_live_review = None
167- if refresh_live_review :
168- reviews_result = legacy_reviews .get_pull_request_reviews_result (bot , issue_number , reviews )
169- if not reviews_result .get ("ok" ):
170- return {"state" : "projection_failed" , "reason" : str (reviews_result .get ("reason" ))}
171- reviews = reviews_result ["reviews" ]
172- preferred_live_review = reviewer_review_helpers .get_preferred_current_reviewer_review_for_cycle (
173- bot ,
174- issue_number ,
175- review_data ,
176- pull_request = pull_request ,
177- reviews = reviews ,
178- )
179- if preferred_live_review is not None :
180- reviewer_review = reviewer_review_helpers .build_reviewer_review_record_from_live_review (
181- preferred_live_review ,
182- actor = current_reviewer ,
183- )
184- elif refresh_live_review :
185- reviewer_review = None
186-
187141 latest_reviewer_response = reviewer_comment
188142 if reviewer_review_helpers .compare_records (
189143 reviewer_review ,
190144 latest_reviewer_response ,
191- parse_timestamp = legacy_reviews .parse_github_timestamp ,
145+ parse_timestamp = review_read_support .parse_github_timestamp ,
192146 ) > 0 :
193147 latest_reviewer_response = reviewer_review
194148
@@ -201,14 +155,14 @@ def compute_reviewer_response_state(
201155 if reviewer_review_helpers .compare_records (
202156 contributor_revision ,
203157 contributor_handoff ,
204- parse_timestamp = legacy_reviews .parse_github_timestamp ,
158+ parse_timestamp = review_read_support .parse_github_timestamp ,
205159 ) > 0 :
206160 contributor_handoff = contributor_revision
207161
208162 if _compare_cross_channel_conversation (
209163 contributor_handoff ,
210164 latest_reviewer_response ,
211- parse_timestamp = legacy_reviews .parse_github_timestamp ,
165+ parse_timestamp = review_read_support .parse_github_timestamp ,
212166 ) > 0 :
213167 reason = "contributor_comment_newer"
214168 if isinstance (contributor_handoff , dict ) and str (contributor_handoff .get ("semantic_key" , "" )).startswith (
@@ -239,17 +193,9 @@ def compute_reviewer_response_state(
239193 "contributor_handoff" : contributor_handoff ,
240194 }
241195
242- from scripts .reviewer_bot_core import approval_policy
243-
244- approval_result = approval_policy .compute_pr_approval_state_result (
245- bot ,
246- issue_number ,
247- review_data ,
248- pull_request = pull_request ,
249- reviews = reviews ,
250- )
251- if not approval_result .get ("ok" ):
196+ if not isinstance (approval_result , dict ) or not approval_result .get ("ok" ):
252197 return {"state" : "projection_failed" , "reason" : "live_review_state_unknown" }
198+
253199 completion = approval_result ["completion" ]
254200 write_approval = approval_result ["write_approval" ]
255201 if not completion .get ("completed" ):
@@ -284,3 +230,111 @@ def compute_reviewer_response_state(
284230 "contributor_comment" : contributor_comment ,
285231 "contributor_handoff" : contributor_handoff ,
286232 }
233+
234+
235+ def compute_reviewer_response_state (
236+ bot ,
237+ issue_number : int ,
238+ review_data : dict ,
239+ * ,
240+ issue_snapshot : dict | None = None ,
241+ pull_request : dict | None = None ,
242+ reviews : list [dict ] | None = None ,
243+ ) -> dict [str , object ]:
244+ if issue_snapshot is None :
245+ issue_snapshot = bot .github .get_issue_or_pr_snapshot (issue_number )
246+ if not isinstance (issue_snapshot , dict ):
247+ return {"state" : "projection_failed" , "reason" : "issue_snapshot_unavailable" }
248+ is_pr = isinstance (issue_snapshot .get ("pull_request" ), dict )
249+
250+ reviewer_comment = review_data .get ("reviewer_comment" , {}).get ("accepted" )
251+ reviewer_review = review_data .get ("reviewer_review" , {}).get ("accepted" )
252+ contributor_comment = review_data .get ("contributor_comment" , {}).get ("accepted" )
253+ had_reviewer_review = isinstance (reviewer_review , dict )
254+
255+ if not is_pr :
256+ return derive_reviewer_response_state (
257+ review_data ,
258+ issue_is_pull_request = False ,
259+ reviewer_comment = reviewer_comment ,
260+ reviewer_review = reviewer_review ,
261+ contributor_comment = contributor_comment ,
262+ had_reviewer_review = had_reviewer_review ,
263+ )
264+
265+ pull_request_result = review_read_support ._pull_request_read_result (bot , issue_number , pull_request )
266+ if not pull_request_result .get ("ok" ):
267+ return {"state" : "projection_failed" , "reason" : str (pull_request_result .get ("reason" ))}
268+ pull_request = pull_request_result ["pull_request" ]
269+ head = pull_request .get ("head" )
270+ current_head = head .get ("sha" ) if isinstance (head , dict ) else None
271+ if not isinstance (current_head , str ) or not current_head .strip ():
272+ return {"state" : "projection_failed" , "reason" : "pull_request_head_unavailable" }
273+
274+ if not reviewer_comment and not reviewer_review :
275+ reviews_result = review_read_support .get_pull_request_reviews_result (bot , issue_number , reviews )
276+ if not reviews_result .get ("ok" ):
277+ return {"state" : "projection_failed" , "reason" : str (reviews_result .get ("reason" ))}
278+ reviews = reviews_result ["reviews" ]
279+ preferred_live_review = reviewer_review_helpers .get_preferred_current_reviewer_review_for_cycle (
280+ bot ,
281+ issue_number ,
282+ review_data ,
283+ pull_request = pull_request ,
284+ reviews = reviews ,
285+ )
286+ if preferred_live_review is not None :
287+ reviewer_review = reviewer_review_helpers .build_reviewer_review_record_from_live_review (
288+ preferred_live_review ,
289+ actor = review_data .get ("current_reviewer" ),
290+ )
291+
292+ stored_review_head = reviewer_review .get ("reviewed_head_sha" ) if isinstance (reviewer_review , dict ) else None
293+ refresh_live_review = reviews is not None or reviewer_review is None
294+ if not refresh_live_review :
295+ refresh_live_review = not isinstance (stored_review_head , str ) or stored_review_head != current_head
296+
297+ preferred_live_review = None
298+ if refresh_live_review :
299+ reviews_result = review_read_support .get_pull_request_reviews_result (bot , issue_number , reviews )
300+ if not reviews_result .get ("ok" ):
301+ return {"state" : "projection_failed" , "reason" : str (reviews_result .get ("reason" ))}
302+ reviews = reviews_result ["reviews" ]
303+ preferred_live_review = reviewer_review_helpers .get_preferred_current_reviewer_review_for_cycle (
304+ bot ,
305+ issue_number ,
306+ review_data ,
307+ pull_request = pull_request ,
308+ reviews = reviews ,
309+ )
310+ if preferred_live_review is not None :
311+ reviewer_review = reviewer_review_helpers .build_reviewer_review_record_from_live_review (
312+ preferred_live_review ,
313+ actor = review_data .get ("current_reviewer" ),
314+ )
315+ elif refresh_live_review :
316+ reviewer_review = None
317+
318+ approval_result = None
319+ reviewed_head_sha = reviewer_review .get ("reviewed_head_sha" ) if isinstance (reviewer_review , dict ) else None
320+ if isinstance (reviewed_head_sha , str ) and reviewed_head_sha == current_head :
321+ from scripts .reviewer_bot_core import approval_policy
322+
323+ approval_result = approval_policy .compute_pr_approval_state_result (
324+ bot ,
325+ issue_number ,
326+ review_data ,
327+ pull_request = pull_request ,
328+ reviews = reviews ,
329+ )
330+
331+ return derive_reviewer_response_state (
332+ review_data ,
333+ issue_is_pull_request = True ,
334+ current_head = current_head ,
335+ reviewer_comment = reviewer_comment ,
336+ reviewer_review = reviewer_review ,
337+ contributor_comment = contributor_comment ,
338+ had_reviewer_review = had_reviewer_review ,
339+ approval_result = approval_result ,
340+ )
0 commit comments