Skip to content

Commit 863126e

Browse files
authored
Feat: Extend to handle issues whose erratum has not been created (#278)
This patch extends the supervisor to handle those issues without errata link. We now fetch issues that contain labels in the following set: - "jotnar_merged": Check if the MR has been merge for more than 24 hours, if yes, flag attention. Otherwise, reschedule it for 1 hour in the future. - "jotnar_backported": Check if the MR has been merged, if yes, label it "jotnar_merged". Otherwise, reschedule it for 3 hours in the future. - "jotnar_rebased": Same as "jotnar_backported".
1 parent f8fd39d commit 863126e

File tree

6 files changed

+142
-14
lines changed

6 files changed

+142
-14
lines changed

common/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class JiraLabels(Enum):
6868

6969
REBASED = "jotnar_rebased"
7070
BACKPORTED = "jotnar_backported"
71+
MERGED = "jotnar_merged"
7172

7273
REBASE_ERRORED = "jotnar_rebase_errored"
7374
BACKPORT_ERRORED = "jotnar_backport_errored"

supervisor/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
from datetime import datetime, timezone
22

3+
# Compares correctly - all our dates are tz-aware
34
DATETIME_MIN_UTC = datetime.min.replace(tzinfo=timezone.utc)
5+
# Groups within the redhat organization where we can find issues
6+
GITLAB_GROUPS = ["rhel/rpms", "centos-stream/rpms"]

supervisor/gitlab_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def search_gitlab_project_mrs(
5353
logger.debug("Searching for MRs for %s in %s", issue_key, project)
5454
path = f"projects/{urlquote(project, safe='')}/merge_requests"
5555

56-
params = {"search": issue_key, "view": "simple"}
56+
params = {"search": issue_key}
5757
if state is not None:
5858
params["state"] = state
5959

@@ -67,4 +67,5 @@ def search_gitlab_project_mrs(
6767
title=mr["title"],
6868
state=mr["state"],
6969
description=mr["description"],
70+
merged_at=mr["merged_at"],
7071
)

supervisor/issue_handler.py

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1+
from datetime import datetime, timezone, timedelta
12
import logging
23

4+
from common.constants import JiraLabels
5+
6+
from .constants import DATETIME_MIN_UTC, GITLAB_GROUPS
37
from .errata_utils import get_erratum_for_link
8+
from .gitlab_utils import search_gitlab_project_mrs
49
from .work_item_handler import WorkItemHandler
510
from .jira_utils import add_issue_label, change_issue_status
611
from .supervisor_types import (
712
FullIssue,
813
IssueStatus,
14+
MergeRequestState,
915
PreliminaryTesting,
10-
TestCoverage,
1116
TestingState,
1217
WorkflowResult,
1318
)
1419
from .testing_analyst import analyze_issue
1520

16-
1721
logger = logging.getLogger(__name__)
1822

1923

@@ -40,29 +44,116 @@ def resolve_set_status(self, status: IssueStatus, why: str):
4044
def resolve_flag_attention(self, why: str):
4145
add_issue_label(
4246
self.issue.key,
43-
"jotnar_needs_attention",
47+
JiraLabels.NEEDS_ATTENTION.value,
4448
why,
4549
dry_run=self.dry_run,
4650
)
4751

4852
return WorkflowResult(status=why, reschedule_in=-1)
4953

50-
async def run(self) -> WorkflowResult:
51-
"""
52-
Runs the workflow for a single issue.
54+
def label_merge_if_needed(self):
55+
"""Add the jotnar_merged label to the issue
56+
57+
This function will only add jotnar_merged label to the issue if it matches
58+
all the following requirements:
59+
1. Issue has either jotnar_backported or jotnar_rebased label.
60+
2. Issue doesn't have jotnar_merged label.
61+
3. A merged MR is found on Gitlab.
62+
63+
Returns:
64+
True if a merge gitlab issue was found and the merged label was added,
65+
otherwise, return False.
5366
"""
5467
issue = self.issue
68+
component = issue.components[0]
69+
70+
if (
71+
JiraLabels.BACKPORTED.value in issue.labels
72+
or JiraLabels.REBASED.value in issue.labels
73+
) and JiraLabels.MERGED.value not in issue.labels:
74+
for group in GITLAB_GROUPS:
75+
merged_mrs = search_gitlab_project_mrs(
76+
f"redhat/{group}/{component}",
77+
issue.key,
78+
state=MergeRequestState.MERGED,
79+
)
5580

56-
logger.info("Running workflow for issue %s", issue.url)
81+
if merged_mr := next(merged_mrs, None):
82+
add_issue_label(
83+
issue.key,
84+
JiraLabels.MERGED.value,
85+
f"A [merge request| {merged_mr.url}]. resolving this issue has been merged; waiting for errata creation and final testing.",
86+
dry_run=self.dry_run,
87+
)
88+
89+
issue.labels.append(JiraLabels.MERGED.value)
90+
return True
5791

58-
if "jotnar_needs_attention" in issue.labels:
92+
return False
93+
94+
def get_latest_merged_timestamp(self):
95+
"""This function will return DATETIME_MIN_UTC if it doesn't find any merged MRs"""
96+
issue = self.issue
97+
component = issue.components[0]
98+
99+
def get_merged_mrs():
100+
for group in GITLAB_GROUPS:
101+
project = f"redhat/{group}/{component}"
102+
yield from search_gitlab_project_mrs(
103+
project,
104+
issue.key,
105+
state=MergeRequestState.MERGED,
106+
)
107+
108+
return max(
109+
(mr.merged_at or DATETIME_MIN_UTC for mr in get_merged_mrs()),
110+
default=DATETIME_MIN_UTC,
111+
)
112+
113+
async def run_before_errata_created(self) -> WorkflowResult:
114+
"""Workflow for issues with no errata link"""
115+
issue = self.issue
116+
117+
if not any(
118+
label
119+
in (
120+
JiraLabels.BACKPORTED.value,
121+
JiraLabels.REBASED.value,
122+
JiraLabels.MERGED.value,
123+
)
124+
for label in issue.labels
125+
):
59126
return self.resolve_remove_work_item(
60-
"Issue has the jotnar_needs_attention label"
127+
f"Issue without target labels: {issue.labels}"
61128
)
62129

63-
if issue.errata_link is None:
64-
return self.resolve_remove_work_item("Issue has no errata_link")
130+
if JiraLabels.MERGED.value not in issue.labels:
131+
self.label_merge_if_needed()
65132

133+
if JiraLabels.MERGED.value not in issue.labels:
134+
return self.resolve_wait(
135+
"No merged MR found, reschedule it for 3 hours",
136+
reschedule_in=60 * 60 * 3,
137+
)
138+
139+
latest_merged_timestamp = self.get_latest_merged_timestamp()
140+
cur_time = datetime.now(tz=timezone.utc)
141+
time_diff = abs(cur_time - latest_merged_timestamp)
142+
if time_diff < timedelta(days=1):
143+
return self.resolve_wait(
144+
"Wait for the associated erratum to be created",
145+
reschedule_in=60 * 60,
146+
)
147+
else:
148+
return self.resolve_flag_attention(
149+
"A merge request was merged for this issue more than 24 hours ago but no errata "
150+
"was created. Please investigate and look for gating failures or other reasons that "
151+
"might have blocked errata creation."
152+
)
153+
154+
async def run_after_errata_created(self) -> WorkflowResult:
155+
issue = self.issue
156+
assert issue.errata_link is not None
66157
if issue.fixed_in_build is None:
67158
return self.resolve_flag_attention(
68159
"Issue has errata_link but no fixed_in_build"
@@ -80,6 +171,10 @@ async def run(self) -> WorkflowResult:
80171
"happened before the gitlab pull request was merged"
81172
)
82173

174+
# We still want the jotnar_merged label for JIRA dashboards even if we never saw
175+
# the merged merge request in the pre-errata-creation state.
176+
self.label_merge_if_needed()
177+
83178
if issue.status in (
84179
IssueStatus.NEW,
85180
IssueStatus.PLANNING,
@@ -121,3 +216,27 @@ async def run(self) -> WorkflowResult:
121216
return self.resolve_remove_work_item(f"Issue status is {issue.status}")
122217
else:
123218
raise ValueError(f"Unknown issue status: {issue.status}")
219+
220+
async def run(self) -> WorkflowResult:
221+
"""
222+
Runs the workflow for a single issue.
223+
"""
224+
issue = self.issue
225+
226+
logger.info("Running workflow for issue %s", issue.url)
227+
228+
if JiraLabels.NEEDS_ATTENTION.value in issue.labels:
229+
return self.resolve_remove_work_item(
230+
"Issue has the jotnar_needs_attention label"
231+
)
232+
233+
if len(issue.components) != 1:
234+
return self.resolve_flag_attention(
235+
"This issue has multiple components. "
236+
"Jotnar only handles issues with single component currently."
237+
)
238+
239+
if issue.errata_link is None:
240+
return await self.run_before_errata_created()
241+
else:
242+
return await self.run_after_errata_created()

supervisor/supervisor_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class MergeRequest(BaseModel):
7777
title: str
7878
description: str
7979
state: MergeRequestState
80+
merged_at: datetime | None
8081

8182

8283
class Issue(BaseModel):

supervisor/work_item_handler.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ def __init__(self, dry_run: bool = False):
1515
def resolve_remove_work_item(self, why: str):
1616
return WorkflowResult(status=why, reschedule_in=-1)
1717

18-
def resolve_wait(self, why: str):
19-
return WorkflowResult(status=why, reschedule_in=WAIT_DELAY)
18+
def resolve_wait(self, why: str, *, reschedule_in: float = WAIT_DELAY):
19+
return WorkflowResult(
20+
status=why,
21+
reschedule_in=reschedule_in,
22+
)
2023

2124
@abstractmethod
2225
async def run(self) -> WorkflowResult:

0 commit comments

Comments
 (0)