Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 16 additions & 24 deletions oar/notificator/jira_notificator.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,48 +628,42 @@ def get_on_qa_filter(self, from_date: Optional[datetime] = None) -> str:

return base_filter + date_suffix

def get_on_qa_issues(self, search_batch_size: int, from_date: Optional[datetime]) -> List[Issue]:
def get_on_qa_issues(self, from_date: Optional[datetime]) -> List[Issue]:
"""
Retrieves ON_QA issues from JIRA in batches, optionally filtered by a date.
Retrieves all ON_QA issues from JIRA, optionally filtered by a date.

Args:
search_batch_size (int): Number of issues to fetch per batch.
from_date (Optional[datetime]): If provided, fetches issues transitioned to ON_QA after this date.

Returns:
List[Issue]: A list of JIRA issues matching the ON_QA criteria.
"""

start_at = 0
on_qa_issues = []

while True:
# FIXME: OCPERT-135 Find a solution to access Jira tickets with limited permissions
issues = self.jira.search_issues(
self.get_on_qa_filter(from_date), startAt=start_at, maxResults=search_batch_size, expand="changelog"
)
if not issues:
break
on_qa_issues.extend(issues)
start_at += len(issues)
# FIXME: OCPERT-135 Find a solution to access Jira tickets with limited permissions
# Use enhanced_search_issues for Jira Cloud compatibility (search_issues deprecated for Cloud)
# maxResults=0 fetches all pages automatically via nextPageToken pagination (100 per page)
on_qa_issues = self.jira.enhanced_search_issues(
self.get_on_qa_filter(from_date),
maxResults=0,
expand="changelog",
)

return on_qa_issues
return list(on_qa_issues)

def process_on_qa_issues(self, search_batch_size: int, from_date: Optional[datetime]) -> List[Notification]:
def process_on_qa_issues(self, from_date: Optional[datetime]) -> List[Notification]:
"""
Processes ON_QA issues by checking and notifying responsible people.

Args:
search_batch_size (int): Number of issues to fetch per batch.
from_date (Optional[datetime]): If provided, process issues transitioned to ON_QA after this date.

Returns:
List[Notification]: List of successfully sent notifications.
"""
sent_notifications: list[Notification] = []
error_occurred = False

for issue in self.get_on_qa_issues(search_batch_size, from_date):
for issue in self.get_on_qa_issues(from_date):
logger.info(f"Processing issue: {issue.key}")
try:
notification = self.check_issue_and_notify_responsible_people(issue)
Expand All @@ -685,15 +679,13 @@ def process_on_qa_issues(self, search_batch_size: int, from_date: Optional[datet
return sent_notifications

@click.command()
@click.option("--search-batch-size", default=100, type=int, help="Maximum number of results to retrieve in each search iteration or batch.")
@click.option("--dry-run", is_flag=True, default=False, help="Run without sending Jira notifications.")
@click.option("--from-date", default=None, type=click.DateTime(formats=["%Y-%m-%d"]), required=False, help="Filters issues that changed to ON_QA state after this date.")
def jira_notificator(search_batch_size: int, dry_run: bool, from_date: Optional[datetime]) -> None:
def jira_notificator(dry_run: bool, from_date: Optional[datetime]) -> None:
"""
CLI entry point to process ON_QA issues and notify responsible people.

Args:
search_batch_size (int): Number of issues to fetch per batch.
dry_run (bool): If True, simulate notifications without sending.
from_date (Optional[datetime]): Filter issues transitioned to ON_QA after this date.

Expand All @@ -709,7 +701,7 @@ def jira_notificator(search_batch_size: int, dry_run: bool, from_date: Optional[
jira = JIRA(server="https://issues.redhat.com", token_auth=jira_token)

ns = NotificationService(jira, dry_run)
ns.process_on_qa_issues(search_batch_size, from_date)
ns.process_on_qa_issues(from_date)

if __name__ == "__main__":
jira_notificator()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies = [
"errata-tool >= 1.29.0",
"slack_sdk >= 3.21.3",
"requests-gssapi ~= 1.2.3",
"jira >= 3.4.1",
"jira >= 3.8.0",
"python-jenkins >= 1.8.0",
"python-gitlab >= 3.15.0",
"ldap3 >= 2.9.1",
Expand Down
16 changes: 8 additions & 8 deletions tests/test_jira_notificator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def setUp(self):

self.test_issue = self.jira.issue("OCPBUGS-59288", expand="changelog")
self.test_issue_without_qa = self.jira.issue("OCPBUGS-8760", expand="changelog")
self.test_issue_without_assignee = self.jira.issue("OCPBUGS-1542", expand="changelog")
self.test_issue_without_assignee = self.jira.issue("OCPBUGS-78840", expand="changelog")
self.test_issue_on_qa = self.jira.issue("OCPBUGS-46472", expand="changelog")

self.test_user = Mock()
Expand Down Expand Up @@ -343,25 +343,25 @@ def test_get_on_qa_filter(self):
self.ns.get_on_qa_filter(None),
(
"project = OCPBUGS AND issuetype in (Bug, Vulnerability) "
"AND status = ON_QA AND 'Target Version' in (4.12.z, 4.13.z, 4.14.z, 4.15.z, 4.16.z, 4.17.z, 4.18.z, 4.19.z)"
"AND status = ON_QA AND 'Target Version' in (4.12.z, 4.13.z, 4.14.z, 4.15.z, 4.16.z, 4.17.z, 4.18.z, 4.19.z, 4.20.z, 4.21.z)"
)
)
self.assertEqual(
self.ns.get_on_qa_filter(datetime(2025, 7, 17, tzinfo=timezone.utc)),
(
"project = OCPBUGS AND issuetype in (Bug, Vulnerability) "
"AND status = ON_QA AND 'Target Version' in (4.12.z, 4.13.z, 4.14.z, 4.15.z, 4.16.z, 4.17.z, 4.18.z, 4.19.z)"
"AND status = ON_QA AND 'Target Version' in (4.12.z, 4.13.z, 4.14.z, 4.15.z, 4.16.z, 4.17.z, 4.18.z, 4.19.z, 4.20.z, 4.21.z)"
" AND status changed to ON_QA after 2025-07-17"
)
)

def test_get_on_qa_issues(self):
issues = self.ns.get_on_qa_issues(100, None)
issues = self.ns.get_on_qa_issues(None)
self.assertNotEqual(len(issues), 0)
for i in issues:
self.assertTrue(i.key.startswith("OCPBUGS-"))

issues_after_date = self.ns.get_on_qa_issues(100, datetime(2025, 7, 17, tzinfo=timezone.utc))
issues_after_date = self.ns.get_on_qa_issues(datetime(2025, 7, 17, tzinfo=timezone.utc))
self.assertNotEqual(len(issues_after_date), 0)
self.assertGreater(len(issues), len(issues_after_date))
for iad in issues_after_date:
Expand All @@ -383,10 +383,10 @@ def test_check_issue_and_notify_responsible_people(self):

def test_process_on_qa_issues(self):
day_ago = datetime.now() - timedelta(hours=24)
self.assertEqual(len(self.ns.process_on_qa_issues(100, day_ago)), 0)
self.assertEqual(len(self.ns.process_on_qa_issues(day_ago)), 0)

week_ago = datetime.now() - timedelta(weeks=1)
self.assertLess(
len(self.ns.process_on_qa_issues(100, week_ago)),
len(self.ns.process_on_qa_issues(100, None))
len(self.ns.process_on_qa_issues(week_ago)),
len(self.ns.process_on_qa_issues(None))
)