diff options
| author | Kirill Rysin <[email protected]> | 2026-04-22 14:26:25 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-22 14:26:25 +0200 |
| commit | 3998280eede013947cdcf934ff1ebbdaa09b04db (patch) | |
| tree | 10ced931c519cdcd04aa5041c56e168712229fa3 /.github/scripts/analytics | |
| parent | 7a7c0968705282c6bd1cad2f36b15b8511fbe238 (diff) | |
MUTE: Fast unmute (#38430)
Diffstat (limited to '.github/scripts/analytics')
3 files changed, 172 insertions, 11 deletions
diff --git a/.github/scripts/analytics/data_mart_queries/muted_tests_with_issue_and_area.sql b/.github/scripts/analytics/data_mart_queries/muted_tests_with_issue_and_area.sql index f6cb1651a27..98b4cefd665 100644 --- a/.github/scripts/analytics/data_mart_queries/muted_tests_with_issue_and_area.sql +++ b/.github/scripts/analytics/data_mart_queries/muted_tests_with_issue_and_area.sql @@ -1,4 +1,12 @@ -$window_days = 365; +-- Mart slice: how far back ``tests_monitor.date_window`` is included (not ``mute_config.json``). +$mart_history_days = 365; + +-- Mart filter: branch/build_type slice for this dashboard (not necessarily all CI matrix branches). +$mart_branch = 'main'; +$mart_build_type = 'relwithdebinfo'; + +-- Must match ``manual_unmute_ttl_calendar_days`` in ``.github/config/mute_config.json`` (fast-unmute deadline). +$manual_unmute_ttl_calendar_days = 3; $normalize = ($raw_area) -> { $parts = String::SplitToList(Cast($raw_area AS String), '/'); @@ -44,6 +52,18 @@ $gim_latest = ( WHERE g_rnk.rn = 1 ); +$mfu = ( + SELECT + full_name AS full_name, + branch AS branch, + build_type AS build_type, + github_issue_number AS mfu_issue_number, + requested_at AS mfu_since, + window_days AS mfu_window_days, + requested_at + $manual_unmute_ttl_calendar_days * Interval("P1D") AS mfu_expires_at + FROM `test_mute/fast_unmute_active` +); + SELECT tm.state_filtered AS state_filtered, tm.test_name AS test_name, @@ -82,7 +102,12 @@ SELECT gim.github_issue_state AS github_issue_state, gim.github_issue_created_at AS github_issue_created_at, gim.area_override AS area_override, - gim.area_override_since AS area_override_since + gim.area_override_since AS area_override_since, + CAST(CASE WHEN mfu.full_name IS NOT NULL THEN 1 ELSE 0 END AS Uint8) AS is_manual_fast_unmute, + mfu.mfu_since AS manual_fast_unmute_since, + mfu.mfu_window_days AS manual_fast_unmute_window_days, + mfu.mfu_expires_at AS manual_fast_unmute_expires_at, + mfu.mfu_issue_number AS manual_fast_unmute_issue_number FROM `test_results/analytics/tests_monitor` AS tm LEFT JOIN $area_fallback AS af ON Unicode::ToLower(Cast(Coalesce(String::ReplaceAll(tm.owner, 'TEAM:@ydb-platform/', ''), '') AS Utf8)) = af.owner_team @@ -90,9 +115,13 @@ LEFT JOIN $gim_latest AS gim ON tm.full_name = gim.full_name AND tm.branch = gim.branch AND tm.build_type = gim.build_type -WHERE tm.date_window >= CurrentUtcDate() - $window_days * Interval("P1D") - AND tm.branch = 'main' - AND tm.build_type = 'relwithdebinfo' +LEFT JOIN $mfu AS mfu + ON tm.full_name = mfu.full_name + AND tm.branch = mfu.branch + AND tm.build_type = mfu.build_type +WHERE tm.date_window >= CurrentUtcDate() - $mart_history_days * Interval("P1D") + AND tm.branch = $mart_branch + AND tm.build_type = $mart_build_type AND tm.is_test_chunk = 0 AND tm.is_muted = 1 AND tm.state != 'Skipped'; diff --git a/.github/scripts/analytics/data_mart_queries/test_muted_monitor_mart_with_issue.sql b/.github/scripts/analytics/data_mart_queries/test_muted_monitor_mart_with_issue.sql index 382392c2340..0c539268286 100644 --- a/.github/scripts/analytics/data_mart_queries/test_muted_monitor_mart_with_issue.sql +++ b/.github/scripts/analytics/data_mart_queries/test_muted_monitor_mart_with_issue.sql @@ -1,6 +1,20 @@ -- GitHub issue fields from github_issue_mapping; analytics area/owner + owner hand-off from tests_monitor. -- COALESCE fallback: when effective_* columns are NULL (not yet populated), fall back to owner string + area_to_owner_mapping. +-- Mart slice: last N calendar days of ``tests_monitor`` (not ``mute_config.json``). +$mart_monitor_date_span_days = 1; + +-- Mart branch filter: ``main`` plus release branches matching this prefix pattern. +$mart_main_branch = 'main'; +$mart_stable_branch_like = 'stable-%'; + +-- ``resolution`` / ``is_muted_or_skipped``: dashboard SLA thresholds (not ``mute_config.json`` windows). +$resolution_skipped_days_threshold = 14; +$resolution_muted_delete_candidate_days = 30; + +-- Must match ``manual_unmute_ttl_calendar_days`` in ``.github/config/mute_config.json`` (fast-unmute deadline). +$manual_unmute_ttl_calendar_days = 3; + $normalize = ($raw_area) -> { $parts = String::SplitToList(Cast($raw_area AS String), '/'); RETURN Cast( @@ -45,6 +59,18 @@ $gim_latest = ( WHERE g_rnk.rn = 1 ); +$mfu = ( + SELECT + full_name AS full_name, + branch AS branch, + build_type AS build_type, + github_issue_number AS mfu_issue_number, + requested_at AS mfu_since, + window_days AS mfu_window_days, + requested_at + $manual_unmute_ttl_calendar_days * Interval("P1D") AS mfu_expires_at + FROM `test_mute/fast_unmute_active` +); + SELECT tm.state_filtered AS state_filtered, tm.test_name AS test_name, @@ -72,8 +98,8 @@ SELECT tm.state_change_date_filtered AS state_change_date_filtered, tm.days_in_state_filtered AS days_in_state_filtered, CAST(CASE - WHEN (tm.state = 'Skipped' AND tm.days_in_state > 14) THEN 'Skipped' - WHEN tm.days_in_mute_state > 30 THEN 'MUTED: delete candidate' + WHEN (tm.state = 'Skipped' AND tm.days_in_state > $resolution_skipped_days_threshold) THEN 'Skipped' + WHEN tm.days_in_mute_state > $resolution_muted_delete_candidate_days THEN 'MUTED: delete candidate' ELSE 'MUTED: in sla' END as String) AS resolution, @@ -86,7 +112,7 @@ SELECT tm.effective_owner_team_changed_date AS effective_owner_team_changed_date, CAST( CASE - WHEN tm.is_muted = 1 OR (tm.state = 'Skipped' AND tm.days_in_state > 14) THEN TRUE + WHEN tm.is_muted = 1 OR (tm.state = 'Skipped' AND tm.days_in_state > $resolution_skipped_days_threshold) THEN TRUE ELSE FALSE END AS Uint8 ) AS is_muted_or_skipped, @@ -95,7 +121,12 @@ SELECT gim.github_issue_state AS github_issue_state, gim.github_issue_created_at AS github_issue_created_at, gim.area_override AS area_override, - gim.area_override_since AS area_override_since + gim.area_override_since AS area_override_since, + CAST(CASE WHEN mfu.full_name IS NOT NULL THEN 1 ELSE 0 END AS Uint8) AS is_manual_fast_unmute, + mfu.mfu_since AS manual_fast_unmute_since, + mfu.mfu_window_days AS manual_fast_unmute_window_days, + mfu.mfu_expires_at AS manual_fast_unmute_expires_at, + mfu.mfu_issue_number AS manual_fast_unmute_issue_number FROM `test_results/analytics/tests_monitor` AS tm LEFT JOIN $area_fallback AS af ON Unicode::ToLower(Cast(Coalesce(String::ReplaceAll(tm.owner, 'TEAM:@ydb-platform/', ''), '') AS Utf8)) = af.owner_team @@ -103,6 +134,10 @@ LEFT JOIN $gim_latest AS gim ON tm.full_name = gim.full_name AND tm.branch = gim.branch AND tm.build_type = gim.build_type -WHERE tm.date_window >= CurrentUtcDate() - 1 * Interval("P1D") - AND (tm.branch = 'main' OR tm.branch LIKE 'stable-%') +LEFT JOIN $mfu AS mfu + ON tm.full_name = mfu.full_name + AND tm.branch = mfu.branch + AND tm.build_type = mfu.build_type +WHERE tm.date_window >= CurrentUtcDate() - $mart_monitor_date_span_days * Interval("P1D") + AND (tm.branch = $mart_main_branch OR tm.branch LIKE $mart_stable_branch_like) AND tm.is_test_chunk = 0; diff --git a/.github/scripts/analytics/export_issues_to_ydb.py b/.github/scripts/analytics/export_issues_to_ydb.py index 5d790a99cd1..15d0d4c49d1 100755 --- a/.github/scripts/analytics/export_issues_to_ydb.py +++ b/.github/scripts/analytics/export_issues_to_ydb.py @@ -125,6 +125,28 @@ def fetch_single_issue(org_name: str, repo_name: str, issue_number: int) -> Opti issueType { name } + timelineItems(last: 20, itemTypes: [CLOSED_EVENT]) { + nodes { + ... on ClosedEvent { + createdAt + actor { + __typename + login + } + } + } + } + projectItems(first: 30) { + nodes { + id + project { + id + number + title + url + } + } + } } } } @@ -225,6 +247,28 @@ def fetch_repository_issues(org_name: str = ORG_NAME, repo_name: str = REPO_NAME issueType { name } + timelineItems(last: 20, itemTypes: [CLOSED_EVENT]) { + nodes { + ... on ClosedEvent { + createdAt + actor { + __typename + login + } + } + } + } + projectItems(first: 30) { + nodes { + id + project { + id + number + title + url + } + } + } } pageInfo { hasNextPage @@ -415,6 +459,45 @@ def parse_datetime(dt_str: Optional[str]) -> Optional[datetime]: except (ValueError, TypeError): return None + +def extract_last_close_actor(issue: Dict[str, Any]) -> Dict[str, Any]: + """Who closed the issue — same timeline rule as ``mute.fast_unmute_github.fetch_issue_closers``.""" + login = '' + actor_type = '' + event_at = None + nodes = (issue.get('timelineItems') or {}).get('nodes') or [] + for event in reversed(nodes): + if not event: + continue + actor = event.get('actor') or {} + cand_login = actor.get('login') or '' + if cand_login: + login = cand_login + actor_type = actor.get('__typename') or '' + event_at = parse_datetime(event.get('createdAt')) + break + return {'login': login, 'actor_type': actor_type, 'event_at': event_at} + + +def projects_for_info_json(issue: Dict[str, Any]) -> List[Dict[str, Any]]: + """Projects (v2) that contain this issue — id/title from GraphQL ``projectItems``.""" + out = [] + for node in (issue.get('projectItems') or {}).get('nodes') or []: + proj = node.get('project') or {} + pid = proj.get('id') + if not pid: + continue + row = { + 'project_id': pid, + 'project_number': proj.get('number'), + 'title': proj.get('title'), + 'url': proj.get('url'), + 'project_item_id': node.get('id'), + } + out.append(row) + return out + + # --- branch version helpers --- def parse_branch(label): if label == 'main': @@ -497,6 +580,9 @@ def transform_issues_for_ydb(issues: List[Dict[str, Any]], project_fields: Optio branch = ';'.join(branch_labels) if branch_labels else None max_branch = get_max_branch(branch_labels) if branch_labels else None info = {'branch': branch, 'max_branch': max_branch, 'env': env, 'priority': priority, 'area': area} + proj_list = projects_for_info_json(issue) + if proj_list: + info['projects'] = proj_list # Issue type: GraphQL issueType.name (Bug/Feature/Task), then project field, then label "bug" issue_type = (issue.get('issueType') or {}).get('name') if issue_type is None: @@ -539,6 +625,17 @@ def transform_issues_for_ydb(issues: List[Dict[str, Any]], project_fields: Optio created_at = parse_datetime(issue.get('createdAt')) updated_at = parse_datetime(issue.get('updatedAt')) closed_at = parse_datetime(issue.get('closedAt')) + + closer = extract_last_close_actor(issue) + if closer['login']: + info['closed_by_login'] = closer['login'] + if closer['actor_type']: + info['closed_by_typename'] = closer['actor_type'] + if closer['event_at'] is not None: + info['closed_event_at_iso'] = closer['event_at'].isoformat() + if closed_at: + info['closed_at_iso'] = closed_at.isoformat() + now = datetime.now(timezone.utc) is_in_project = bool(issue_project_fields) |
