blob: cc23521a4cb295548bd6dd898735195560df9d96 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Functions to help rerank issues in a lit.
6
7This file contains methods that implement a reranking algorithm for
8issues in a list.
9"""
10
11from __future__ import division
12from __future__ import print_function
13from __future__ import absolute_import
14
15import sys
16
17from framework import exceptions
18
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010019MAX_RANKING = sys.maxsize
Copybara854996b2021-09-07 19:36:02 +000020MIN_RANKING = 0
21
22def GetHotlistRerankChanges(hotlist_items, moved_issue_ids, target_position):
23 # type: (int, Sequence[int], int) -> Collection[Tuple[int, int]]
24 """Computes the new ranks from reranking and or inserting of HotlistItems.
25
26 Args:
27 hotlist_items: The current list of HotlistItems in the Hotlist.
28 moved_issue_ids: A list of issue IDs to be moved/inserted together,
29 in the order
30 they should have the reranking.
31 target_position: The index, starting at 0, of the new position the
32 first issue in moved_issue_ids should occupy in the updated ordering.
33 Therefore this value cannot be greater than
34 (len(hotlist.items) - len(moved_issue_ids)).
35
36 Returns:
37 A list of [(issue_id, rank), ...] of HotlistItems that need to be updated.
38
39 Raises:
40 InputException: If the target_position is not valid.
41 """
42 # Sort hotlist items by rank.
43 sorted_hotlist_items = sorted(hotlist_items, key=lambda item: item.rank)
44 unmoved_hotlist_items = [
45 item for item in sorted_hotlist_items
46 if item.issue_id not in moved_issue_ids]
47 if target_position < 0:
48 raise exceptions.InputException(
49 'given `target_position`: %d, must be non-negative')
50 if target_position > len(unmoved_hotlist_items):
51 raise exceptions.InputException(
52 '`target_position` %d is higher than maximum allowed (%d) for'
53 'moving %d items in a hotlist with %d items total.' % (
54 target_position, len(unmoved_hotlist_items),
55 len(moved_issue_ids), len(sorted_hotlist_items)))
56 lower, higher = [], []
57 for i, item in enumerate(unmoved_hotlist_items):
58 item_tuple = (item.issue_id, item.rank)
59 if i < target_position:
60 lower.append(item_tuple)
61 else:
62 higher.append(item_tuple)
63
64 return GetInsertRankings(lower, higher, moved_issue_ids)
65
66def GetInsertRankings(lower, higher, moved_ids):
67 """Compute rankings for moved_ids to insert between the
68 lower and higher rankings
69
70 Args:
71 lower: a list of [(id, rank),...] of blockers that should have
72 a lower rank than the moved issues. Should be sorted from highest
73 to lowest rank.
74 higher: a list of [(id, rank),...] of blockers that should have
75 a higher rank than the moved issues. Should be sorted from highest
76 to lowest rank.
77 moved_ids: a list of global IDs for issues to re-rank.
78
79 Returns:
80 a list of [(id, rank),...] of blockers that need to be updated. rank
81 is the new rank of the issue with the specified id.
82 """
83 if lower:
84 lower_rank = lower[-1][1]
85 else:
86 lower_rank = MIN_RANKING
87
88 if higher:
89 higher_rank = higher[0][1]
90 else:
91 higher_rank = MAX_RANKING
92
93 slot_count = higher_rank - lower_rank - 1
94 if slot_count >= len(moved_ids):
95 new_ranks = _DistributeRanks(lower_rank, higher_rank, len(moved_ids))
96 return list(zip(moved_ids, new_ranks))
97 else:
98 new_lower, new_higher, new_moved_ids = _ResplitRanks(
99 lower, higher, moved_ids)
100 if not new_moved_ids:
101 return None
102 else:
103 return GetInsertRankings(new_lower, new_higher, new_moved_ids)
104
105
106def _DistributeRanks(low, high, rank_count):
107 """Compute evenly distributed ranks in a range"""
108 bucket_size = (high - low) // rank_count
109 first_rank = low + (bucket_size + 1) // 2
110 return list(range(first_rank, high, bucket_size))
111
112
113def _ResplitRanks(lower, higher, moved_ids):
114 if not (lower or higher):
115 return None, None, None
116
117 if not lower:
118 take_from = 'higher'
119 elif not higher:
120 take_from = 'lower'
121 else:
122 next_lower = lower[-2][1] if len(lower) >= 2 else MIN_RANKING
123 next_higher = higher[1][1] if len(higher) >= 2 else MAX_RANKING
124 if (lower[-1][1] - next_lower) > (next_higher - higher[0][1]):
125 take_from = 'lower'
126 else:
127 take_from = 'higher'
128
129 if take_from == 'lower':
130 return (lower[:-1], higher, [lower[-1][0]] + moved_ids)
131 else:
132 return (lower, higher[1:], moved_ids + [higher[0][0]])