ci,marge_queue: objects to represent the queue

Enhancement of the module with two structures that can encapsulate
functionalities and establish links between data collected.

Signed-off-by: Sergi Blanch Torne <sergi.blanch.torne@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/37395>
This commit is contained in:
Sergi Blanch Torne 2025-09-15 15:38:24 +02:00 committed by Marge Bot
parent 02af2a199b
commit bf5626c82f

View file

@ -12,15 +12,18 @@ Monitors Marge-bot and return number of assigned MRs.
import argparse
import time
import sys
from datetime import datetime, timezone
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
from dateutil import parser
from typing import Optional
import gitlab
from gitlab.v4.objects import Project
from gitlab.v4.objects import Project, ProjectMergeRequest
from gitlab_common import read_token, pretty_duration
REFRESH_WAIT = 30
MARGE_BOT_USER_ID = 9716
ASSIGNED_TO_MARGE = "assigned to @marge-bot"
def parse_args() -> argparse.Namespace:
@ -39,6 +42,88 @@ def parse_args() -> argparse.Namespace:
return parse.parse_args()
@dataclass
class MargeMergeRequest:
"""Represent a Merge Request assigned to Marge"""
id: int = field(init=False)
mr: ProjectMergeRequest = field(repr=False)
updated_at: datetime | None = field(init=False, repr=False)
assigned_at: datetime | None = field(init=False, repr=False)
def __post_init__(self):
self.id = self.mr.iid
self.updated_at = parser.parse(self.mr.updated_at)
self.assigned_at = self.__find_last_assign_to_marge()
def __find_last_assign_to_marge(self) -> Optional[datetime]:
for note in self.mr.notes.list(
iterator=True,
order_by="updated_at",
sort="desc"
): # start with the most recent
if note.body.startswith(ASSIGNED_TO_MARGE):
return parser.parse(note.created_at)
def __eq__(self, other: "MargeMergeRequest") -> bool:
return self.id == other.id
@property
def time_enqueued(self) -> timedelta:
if self.assigned_at is None:
raise ValueError("Assign to marge timestamp not defined")
return datetime.now(timezone.utc) - self.assigned_at
@property
def web_url(self) -> str:
return self.mr.web_url
@property
def title(self) -> str:
return self.mr.title
@dataclass
class MargeQueue:
"""
Collect and sort the merge requests assigned to marge
"""
elements_sorted: dict[datetime, MargeMergeRequest] = field(
init=False, repr=False, default_factory=dict
)
undetermined: list[MargeMergeRequest] = field(
init=False, repr=False, default_factory=list
)
def append(self, mr: MargeMergeRequest) -> None:
if mr.assigned_at is not None:
self.elements_sorted[mr.assigned_at] = mr
else:
self.undetermined.append(mr)
@property
def n_merge_requests_enqueued(self) -> int:
return len(self.elements_sorted) + len(self.undetermined)
@property
def sorted_queue(self) -> list[MargeMergeRequest]:
"""
Provide a list of the elements that can be sorted based on the
assignment to Marge.
"""
return list(dict(sorted(self.elements_sorted.items())).values())
@property
def all_assigned(
self
) -> list[MargeMergeRequest]:
"""
Provide a single list, but in sorted, of all the elements found
assigned to Marge. This include the elements in elements_sorted
and the ones undetermined (expected to be empty).
"""
return self.sorted_queue + self.undetermined
def get_merge_queue(project: Project) -> int:
mrs = project.mergerequests.list(
assignee_id=MARGE_BOT_USER_ID,