Skip to content
Open
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
59 changes: 36 additions & 23 deletions backend/kernelCI_app/helpers/issueExtras.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@

from kernelCI_app.constants.general import UNCATEGORIZED_STRING
from kernelCI_app.helpers.logger import log_message
from kernelCI_app.queries.issues import get_issue_first_seen_data, get_issue_trees_data
from kernelCI_app.queries.issues import (
get_issue_first_seen_data,
get_issue_last_seen_data,
get_issue_trees_data,
)
from kernelCI_app.typeModels.issues import (
ExtraIssuesData,
FirstIncident,
Incident,
IssueWithExtraInfo,
ProcessedExtraDetailedIssues,
TreeSetItem,
Expand All @@ -29,6 +33,19 @@ def parse_issue(issue_str: Optional[str]) -> tuple[str, Optional[int]]:
return (issue_id, issue_version)


def _incident_from_record(record: dict) -> Incident:
return Incident(
first_seen=record["first_seen"],
git_commit_hash=record["git_commit_hash"],
git_repository_url=record["git_repository_url"],
git_repository_branch=record["git_repository_branch"],
git_commit_name=record["git_commit_name"],
tree_name=record["tree_name"],
issue_version=record["issue_version"],
checkout_id=record["checkout_id"],
)


def process_issues_extra_details(
*,
issue_key_list: List[Tuple[str, int]],
Expand All @@ -38,7 +55,7 @@ def process_issues_extra_details(
return

# TODO: combine both queries into one
assign_issue_first_seen(
assign_issue_incidents(
issue_key_list=issue_key_list,
processed_issues_table=processed_issues_table,
)
Expand All @@ -48,47 +65,43 @@ def process_issues_extra_details(
)


def assign_issue_first_seen(
def assign_issue_incidents(
*,
issue_key_list: List[Tuple[str, int]],
processed_issues_table: ProcessedExtraDetailedIssues,
) -> None:
"""
Assigns the first seen data to the processed_issues_table by querying with the issue_key_list.
Assigns first and last seen data to the processed_issues_table
by querying with the issue_key_list.
"""
issue_id_set: set[str] = set()
issue_id_set = {issue_id for issue_id, _ in issue_key_list}
versions_per_issue: dict[str, set[int]] = defaultdict(set)

for issue_id, issue_version in issue_key_list:
issue_id_set.add(issue_id)
versions_per_issue[issue_id].add(issue_version)

incident_records = get_issue_first_seen_data(issue_id_list=list(issue_id_set))
first_incident_records = get_issue_first_seen_data(issue_id_list=list(issue_id_set))
last_incident_records = get_issue_last_seen_data(issue_id_list=list(issue_id_set))
last_incident_by_id = {
record["issue_id"]: record for record in last_incident_records
}

for record in incident_records:
record_issue_id = record["issue_id"]
first_seen = record["first_seen"]
for record in first_incident_records:
issue_id = record["issue_id"]
last_record = last_incident_by_id.get(issue_id)

processed_issue_from_id = processed_issues_table.setdefault(
record_issue_id,
issue_id,
ExtraIssuesData(
first_incident=FirstIncident(
first_seen=first_seen,
git_commit_hash=record["git_commit_hash"],
git_repository_url=record["git_repository_url"],
git_repository_branch=record["git_repository_branch"],
git_commit_name=record["git_commit_name"],
tree_name=record["tree_name"],
issue_version=record["issue_version"],
checkout_id=record["checkout_id"],
),
first_incident=_incident_from_record(record),
last_incident=_incident_from_record(last_record),
versions={},
),
)

# Initialize the versions table with null because that version may or may not exist.
# If an issue_version exists, the trees can be assigned with `assign_issue_trees`
for version in versions_per_issue[record_issue_id]:
for version in versions_per_issue[issue_id]:
processed_issue_from_id.versions.setdefault(version, None)


Expand Down
4 changes: 2 additions & 2 deletions backend/kernelCI_app/management/commands/helpers/summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.conf import settings

from kernelCI_app.constants.general import DEFAULT_ORIGIN
from kernelCI_app.helpers.issueExtras import assign_issue_first_seen
from kernelCI_app.helpers.issueExtras import assign_issue_incidents
from kernelCI_app.helpers.logger import log_message
from kernelCI_app.queries.notifications import (
get_issues_summary_data,
Expand Down Expand Up @@ -134,7 +134,7 @@ def get_build_issues_from_checkout(
issues_id_and_version_set.add((issue_id, issue_version))

processed_issues_table: ProcessedExtraDetailedIssues = {}
assign_issue_first_seen(
assign_issue_incidents(
issue_key_list=list(issues_id_and_version_set),
processed_issues_table=processed_issues_table,
)
Expand Down
57 changes: 36 additions & 21 deletions backend/kernelCI_app/queries/issues.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Any, Optional
from typing import Any, Literal, Optional

from django.db import connection, connections

Expand Down Expand Up @@ -228,39 +228,36 @@ def get_test_issues(*, test_id: str) -> list[dict]:
return rows


def get_issue_first_seen_data(*, issue_id_list: list[str]) -> list[dict]:
"""
Retrieves the incident and checkout data
of the first incident of a list of issues
through a list of `issue_id`s.
def get_issue_seen_data(
*, issue_id_list: list[str], mode: Literal["first", "last"] = "first"
) -> list[dict]:
"""
Retrieves the incident and checkout data of either the first or last
incident of a list of issues through a list of `issue_id`s.

:param mode: Either 'first' to get oldest incidents or 'last' to get the newest ones.
"""
if not issue_id_list:
return []

cache_key = "issue_first_seen"
order_direction = "ASC" if mode == "first" else "DESC"
cache_key = f"issue_{mode}_seen"
params = {"issue_id_list": issue_id_list}
records = get_query_cache(key=cache_key, params=params)

if records is None:
if len(issue_id_list) == 1:
comparison = "= %s"
else:
placeholders = ", ".join(["%s"] * len(issue_id_list))
comparison = f"IN ({placeholders})"

query = f"""
WITH first_incident AS (
SELECT DISTINCT
ON (IC.issue_id) IC.id
WITH target_incident AS (
SELECT DISTINCT ON (IC.issue_id)
IC.id
FROM
incidents IC
WHERE
IC.issue_id {comparison}
IC.issue_id = ANY(%(issue_id_list)s)
ORDER BY
IC.issue_id,
IC.issue_version ASC,
IC._timestamp ASC
IC.issue_version {order_direction},
IC._timestamp {order_direction}
)
SELECT
IC.id,
Expand All @@ -281,18 +278,36 @@ def get_issue_first_seen_data(*, issue_id_list: list[str]) -> list[dict]:
OR T.build_id = B.id
)
LEFT JOIN checkouts C ON B.checkout_id = C.id
JOIN first_incident FI ON IC.id = FI.id
JOIN target_incident TI ON IC.id = TI.id
"""

with connection.cursor() as cursor:
cursor.execute(query, issue_id_list)
cursor.execute(query, params)
records = dict_fetchall(cursor)

set_query_cache(key=cache_key, params=params, rows=records)

return records


def get_issue_first_seen_data(*, issue_id_list: list[str]) -> list[dict]:
"""
Retrieves the incident and checkout data
of the first incident of a list of issues
through a list of `issue_id`s.
"""
return get_issue_seen_data(issue_id_list=issue_id_list, mode="first")


def get_issue_last_seen_data(*, issue_id_list: list[str]) -> list[dict]:
"""
Retrieves the incident and checkout data
of the last incident of a list of issues
through a list of `issue_id`s.
"""
return get_issue_seen_data(issue_id_list=issue_id_list, mode="last")


def get_issue_trees_data(
*, issue_key_list: list[tuple[str, int]]
) -> list[dict[str, Any]]:
Expand Down
Loading