1919"""
2020
2121import logging
22+ from concurrent .futures import ThreadPoolExecutor
2223from typing import cast , Optional
2324
2425from github import Github
@@ -70,45 +71,31 @@ def generate(self, data: MinedData) -> dict[str, Record]:
7071 Returns:
7172 dict[str, Record]: A dictionary of records indexed by their IDs.
7273 """
73- logger .debug ("Creation of records started..." )
74+ logger .info ("Creation of records started..." )
7475
75- # Before the loop, compute a flat set of all sub-issue IDs
76- all_sub_issue_ids = {iid for sublist in data .parents_sub_issues .values () for iid in sublist }
77-
78- for issue , repo in data .issues .items ():
79- iid = get_id (issue , repo )
80-
81- if len (data .parents_sub_issues .get (iid , [])) > 0 :
82- # issue has sub-issues - it is either hierarchy issue or sub-hierarchy issue
83- self ._create_record_for_hierarchy_issue (issue , iid )
84-
85- elif iid in all_sub_issue_ids :
86- # issue has no sub-issues - it is sub-issue
87- self ._create_record_for_sub_issue (issue , iid )
88-
89- else :
90- # issue is not sub-issue and has no sub-issues - it is issue
91- self ._create_record_for_issue (issue , iid )
76+ built = build_issue_records_parallel (self , data , max_workers = 8 )
77+ self ._records .update (built )
78+ self .__registered_issues .update (built .keys ())
9279
9380 # dev note: Each issue is now in records dict by its issue number - all on same level - no hierarchy
9481 # --> This is useful for population by PRs and commits
9582
96- logger .debug ("Registering Commits to Pull Requests and Pull Requests to Issues..." )
83+ logger .info ("Registering Commits to Pull Requests and Pull Requests to Issues..." )
9784 for pull , repo in data .pull_requests .items ():
9885 self ._register_pull_and_its_commits_to_issue (pull , get_id (pull , repo ), data , target_repository = repo )
9986
10087 if data .pull_requests_of_fetched_cross_issues :
101- logger .debug ("Register cross-repo Pull Requests to its issues" )
88+ logger .info ("Register cross-repo Pull Requests to its issues" )
10289 for iid , prs in data .pull_requests_of_fetched_cross_issues .items ():
10390 self ._register_cross_repo_prs_to_issue (iid , prs )
10491
105- logger .debug ("Registering direct commits to records..." )
92+ logger .info ("Registering direct commits to records..." )
10693 for commit , repo in data .commits .items ():
10794 if commit .sha not in self .__registered_commits :
10895 self ._records [get_id (commit , repo )] = CommitRecord (commit )
10996
11097 # dev note: now we have all PRs and commits registered to issues or as stand-alone records
111- logger .debug ("Building issues hierarchy..." )
98+ logger .info ("Building issues hierarchy..." )
11299
113100 sub_i_ids = list ({iid for sublist in data .parents_sub_issues .values () for iid in sublist })
114101 sub_i_prts = {sub_issue : parent for parent , sublist in data .parents_sub_issues .items () for sub_issue in sublist }
@@ -193,26 +180,6 @@ def _register_cross_repo_prs_to_issue(self, iid: str, prs: list[PullRequest]) ->
193180 for pr in prs :
194181 cast (IssueRecord , self ._records [iid ]).register_pull_request (pr )
195182
196- def _create_record_for_hierarchy_issue (self , i : Issue , iid : str , issue_labels : Optional [list [str ]] = None ) -> None :
197- """
198- Create a hierarchy issue record and register sub-issues.
199-
200- Parameters:
201- i: The issue to create the record for.
202- issue_labels: The labels of the issue.
203-
204- Returns:
205- None
206- """
207- # check for skip labels presence and skip when detected
208- if issue_labels is None :
209- issue_labels = self ._get_issue_labels_mix_with_type (i )
210- skip_record = any (item in issue_labels for item in ActionInputs .get_skip_release_notes_labels ())
211-
212- self ._records [iid ] = HierarchyIssueRecord (issue = i , skip = skip_record , issue_labels = issue_labels )
213- self .__registered_issues .add (iid )
214- logger .debug ("Created record for hierarchy issue %s: %s" , iid , i .title )
215-
216183 def _get_issue_labels_mix_with_type (self , issue : Issue ) -> list [str ]:
217184 labels : list [str ] = [label .name for label in issue .get_labels ()]
218185
@@ -223,20 +190,6 @@ def _get_issue_labels_mix_with_type(self, issue: Issue) -> list[str]:
223190
224191 return labels
225192
226- def _create_record_for_sub_issue (self , issue : Issue , iid : str , issue_labels : Optional [list [str ]] = None ) -> None :
227- if issue_labels is None :
228- issue_labels = self ._get_issue_labels_mix_with_type (issue )
229-
230- skip_record = any (item in issue_labels for item in ActionInputs .get_skip_release_notes_labels ())
231- logger .debug ("Created record for sub issue %s: %s" , iid , issue .title )
232- self .__registered_issues .add (iid )
233- self ._records [iid ] = SubIssueRecord (issue , issue_labels , skip_record )
234-
235- if iid .split ("#" )[0 ] == self ._home_repository .full_name :
236- return
237-
238- self ._records [iid ].is_cross_repo = True
239-
240193 def _re_register_hierarchy_issues (self , sub_issues_ids : list [str ], sub_issue_parents : dict [str , str ]):
241194 logger .debug ("Re-registering hierarchy issues ..." )
242195 reduced_sub_issue_ids : list [str ] = sub_issues_ids [:]
@@ -292,3 +245,88 @@ def order_hierarchy_levels(self, level: int = 0) -> None:
292245 top_hierarchy_records = [rec for rec in self ._records .values () if isinstance (rec , HierarchyIssueRecord )]
293246 for rec in top_hierarchy_records :
294247 rec .order_hierarchy_levels (level = level )
248+
249+ def build_record_for_hierarchy_issue (self , issue : Issue , issue_labels : Optional [list [str ]] = None ) -> Record :
250+ """
251+ Build a hierarchy issue record.
252+
253+ Parameters:
254+ issue (Issue): The issue to build.
255+ issue_labels (list[str]): The labels to use for this issue.
256+ Returns:
257+ Record: The built record.
258+ """
259+ if issue_labels is None :
260+ issue_labels = self ._get_issue_labels_mix_with_type (issue )
261+ skip_record = any (lbl in ActionInputs .get_skip_release_notes_labels () for lbl in issue_labels )
262+ return HierarchyIssueRecord (issue = issue , skip = skip_record , issue_labels = issue_labels )
263+
264+ def build_record_for_sub_issue (self , issue : Issue , iid : str , issue_labels : Optional [list [str ]] = None ) -> Record :
265+ """
266+ Build a sub issue record.
267+
268+ Parameters:
269+ issue (Issue): The issue to build.
270+ iid (str): The id to use for this issue.
271+ issue_labels (list[str]): The labels to use for this issue.
272+ Returns:
273+ Record: The built record.
274+ """
275+ if issue_labels is None :
276+ issue_labels = self ._get_issue_labels_mix_with_type (issue )
277+ skip_record = any (lbl in ActionInputs .get_skip_release_notes_labels () for lbl in issue_labels )
278+ rec = SubIssueRecord (issue , issue_labels , skip_record )
279+ # preserve cross-repo flag behavior
280+ if iid .split ("#" )[0 ] != self ._home_repository .full_name :
281+ rec .is_cross_repo = True
282+ return rec
283+
284+ def build_record_for_issue (self , issue : Issue , issue_labels : Optional [list [str ]] = None ) -> Record :
285+ """
286+ Build an issue record.
287+
288+ Parameters:
289+ issue (Issue): The issue to build.
290+ issue_labels (list[str]): The labels to use for this issue.
291+ Returns:
292+ Record: The built record.
293+ """
294+ if issue_labels is None :
295+ issue_labels = self ._get_issue_labels_mix_with_type (issue )
296+ skip_record = any (lbl in ActionInputs .get_skip_release_notes_labels () for lbl in issue_labels )
297+ return IssueRecord (issue = issue , skip = skip_record , issue_labels = issue_labels )
298+
299+
300+ def build_issue_records_parallel (gen , data , max_workers : int = 8 ) -> dict [str , "Record" ]:
301+ """
302+ Build issue records in parallel with no side effects on `gen`.
303+ Returns: {iid: Record}
304+ """
305+ parents_sub_issues = data .parents_sub_issues # read-only snapshot for this phase
306+ all_sub_issue_ids = {iid for subs in parents_sub_issues .values () for iid in subs }
307+ issues_items = list (data .issues .items ()) # snapshot
308+
309+ def _classify_and_build (issue , repo ) -> tuple [str , "Record" ]:
310+ iid = get_id (issue , repo )
311+
312+ # classification
313+ if len (parents_sub_issues .get (iid , [])) > 0 :
314+ # hierarchy node (has sub-issues)
315+ rec = gen .build_record_for_hierarchy_issue (issue )
316+ elif iid in all_sub_issue_ids :
317+ # leaf sub-issue
318+ rec = gen .build_record_for_sub_issue (issue , iid )
319+ else :
320+ # plain issue
321+ rec = gen .build_record_for_issue (issue )
322+ return iid , rec
323+
324+ results : dict [str , "Record" ] = {}
325+ if not issues_items :
326+ return results
327+
328+ with ThreadPoolExecutor (max_workers = max_workers , thread_name_prefix = "build-issue-rec" ) as ex :
329+ for iid , rec in ex .map (lambda ir : _classify_and_build (* ir ), issues_items ):
330+ results [iid ] = rec
331+
332+ return results
0 commit comments