diff options
| author | Kirill Rysin <[email protected]> | 2026-06-18 21:47:37 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-06-18 22:47:37 +0300 |
| commit | 42b2c71601e033e050d6fa76d674a0ca2cc562a0 (patch) | |
| tree | efb11fc1990f44d244e6ea406311e053c8b8aa57 /.github/scripts/analytics | |
| parent | 8e6218277715bd372ab944a75b6a496292fe77cc (diff) | |
Export release:* labels to info.releaseblocker_state for BI (#43893)
Co-authored-by: Cursor <[email protected]>
Diffstat (limited to '.github/scripts/analytics')
3 files changed, 77 insertions, 22 deletions
diff --git a/.github/scripts/analytics/data_mart_queries/datalens_ds_queries/github_issues_timeline_full.sql b/.github/scripts/analytics/data_mart_queries/datalens_ds_queries/github_issues_timeline_full.sql index bb4c511a813..9c2711e5c4e 100644 --- a/.github/scripts/analytics/data_mart_queries/datalens_ds_queries/github_issues_timeline_full.sql +++ b/.github/scripts/analytics/data_mart_queries/datalens_ds_queries/github_issues_timeline_full.sql @@ -50,6 +50,7 @@ SELECT i.max_branch AS max_branch, i.env AS env, i.priority AS priority, + i.releaseblocker_state AS releaseblocker_state, i.branch AS branch, i.area AS area, CAST( @@ -100,6 +101,7 @@ CROSS JOIN ( COALESCE(JSON_VALUE(t.info, "$.max_branch"), '-') AS max_branch, COALESCE(JSON_VALUE(t.info, "$.env"), 'env:-') AS env, COALESCE(JSON_VALUE(t.info, "$.priority"), 'priority:-') AS priority, + COALESCE(JSON_VALUE(t.info, "$.releaseblocker_state"), 'release:-') AS releaseblocker_state, COALESCE(JSON_VALUE(t.info, "$.branch"), '-') AS branch, COALESCE(JSON_VALUE(t.info, "$.area"), 'area/-') AS area FROM `github_data/issues` AS t diff --git a/.github/scripts/analytics/data_mart_queries/github_issues_timeline.sql b/.github/scripts/analytics/data_mart_queries/github_issues_timeline.sql index e2469b7d366..cbece481272 100644 --- a/.github/scripts/analytics/data_mart_queries/github_issues_timeline.sql +++ b/.github/scripts/analytics/data_mart_queries/github_issues_timeline.sql @@ -60,6 +60,7 @@ SELECT i.max_branch AS max_branch, i.env AS env, i.priority AS priority, + i.releaseblocker_state AS releaseblocker_state, i.branch AS branch, i.area AS area, CAST( @@ -110,6 +111,7 @@ CROSS JOIN ( COALESCE(JSON_VALUE(t.info, "$.max_branch"), '-') AS max_branch, COALESCE(JSON_VALUE(t.info, "$.env"), 'env:-') AS env, COALESCE(JSON_VALUE(t.info, "$.priority"), 'priority:-') AS priority, + COALESCE(JSON_VALUE(t.info, "$.releaseblocker_state"), 'release:-') AS releaseblocker_state, COALESCE(JSON_VALUE(t.info, "$.branch"), '-') AS branch, Coalesce(m.matched_area, CASE diff --git a/.github/scripts/analytics/export_issues_to_ydb.py b/.github/scripts/analytics/export_issues_to_ydb.py index 15d0d4c49d1..0dcbe6a04ce 100755 --- a/.github/scripts/analytics/export_issues_to_ydb.py +++ b/.github/scripts/analytics/export_issues_to_ydb.py @@ -11,33 +11,73 @@ import requests from typing import List, Dict, Any, Optional from ydb_wrapper import YDBWrapper -# Configuration ORG_NAME = 'ydb-platform' REPO_NAME = 'ydb' PROJECT_ID = None #'45' # Optional: set to None to skip project data +ISSUES_PAGE_SIZE = 50 # smaller pages → smaller GraphQL payloads (avoids truncated JSON) # YDB configuration is now handled by ydb_wrapper +_RETRYABLE_GITHUB_STATUS_CODES = frozenset({429, 500, 502, 503, 504}) +_GITHUB_QUERY_MAX_RETRIES = 8 +_GITHUB_QUERY_INITIAL_BACKOFF_SEC = 2.0 +_GITHUB_QUERY_TIMEOUT_SEC = 120 + + +def _github_query_retry_wait(attempt: int, backoff: float, reason: str) -> float: + print(f"{reason}, retry {attempt + 1}/{_GITHUB_QUERY_MAX_RETRIES} in {backoff:.0f}s...") + time.sleep(backoff) + return min(backoff * 2, 60) + + def run_query(query: str, variables: Optional[Dict] = None) -> Dict[str, Any]: - """Execute GraphQL query against GitHub API""" - GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] - HEADERS = {"Authorization": f"Bearer {GITHUB_TOKEN}", "Content-Type": "application/json"} - - request = requests.post( - 'https://api.github.com/graphql', - json={'query': query, 'variables': variables}, - headers=HEADERS - ) - - if request.status_code == 200: - response = request.json() - if 'errors' in response: - for error in response['errors']: - print(f"GraphQL Error: {error.get('message', 'Unknown error')}") - raise Exception(f"GraphQL Error: {error.get('message', 'Unknown error')}") - return response - else: + """Execute GraphQL query against GitHub API (retries transient 5xx/429 and truncated JSON).""" + github_token = os.environ["GITHUB_TOKEN"] + headers = {"Authorization": f"Bearer {github_token}", "Content-Type": "application/json"} + payload = {'query': query, 'variables': variables} + + backoff = _GITHUB_QUERY_INITIAL_BACKOFF_SEC + for attempt in range(_GITHUB_QUERY_MAX_RETRIES + 1): + try: + request = requests.post( + 'https://api.github.com/graphql', + json=payload, + headers=headers, + timeout=_GITHUB_QUERY_TIMEOUT_SEC, + ) + except requests.RequestException as exc: + if attempt >= _GITHUB_QUERY_MAX_RETRIES: + raise + backoff = _github_query_retry_wait( + attempt, backoff, f"GitHub API request error ({exc})" + ) + continue + + if request.status_code == 200: + try: + response = request.json() + except ValueError as exc: + if attempt >= _GITHUB_QUERY_MAX_RETRIES: + raise + backoff = _github_query_retry_wait( + attempt, + backoff, + f"GitHub API truncated/invalid JSON ({exc})", + ) + continue + if 'errors' in response: + for error in response['errors']: + print(f"GraphQL Error: {error.get('message', 'Unknown error')}") + raise Exception(f"GraphQL Error: {response['errors'][0].get('message', 'Unknown error')}") + return response + + if request.status_code in _RETRYABLE_GITHUB_STATUS_CODES and attempt < _GITHUB_QUERY_MAX_RETRIES: + backoff = _github_query_retry_wait( + attempt, backoff, f"GitHub API {request.status_code}" + ) + continue + raise Exception(f"Query failed with status {request.status_code}: {request.text}") def get_last_update_time(ydb_wrapper: YDBWrapper, table_path: str) -> Optional[datetime]: @@ -192,7 +232,7 @@ def fetch_repository_issues(org_name: str = ORG_NAME, repo_name: str = REPO_NAME { organization(login: "%s") { repository(name: "%s") { - issues(first: 100, after: %s, orderBy: {field: UPDATED_AT, direction: DESC}%s) { + issues(first: %d, after: %s, orderBy: {field: UPDATED_AT, direction: DESC}%s) { nodes { id number @@ -282,7 +322,7 @@ def fetch_repository_issues(org_name: str = ORG_NAME, repo_name: str = REPO_NAME total_fetched = 0 while has_next_page: - query = repository_issues_query % (org_name, repo_name, end_cursor, since_filter) + query = repository_issues_query % (org_name, repo_name, ISSUES_PAGE_SIZE, end_cursor, since_filter) result = run_query(query) if result and 'data' in result: @@ -557,6 +597,7 @@ def transform_issues_for_ydb(issues: List[Dict[str, Any]], project_fields: Optio branch_labels = [] env = None priority = None + releaseblocker_state = None area = None for label in issue.get('labels', {}).get('nodes', []): name = label.get('name', '') @@ -574,12 +615,22 @@ def transform_issues_for_ydb(issues: List[Dict[str, Any]], project_fields: Optio # priority detection if name.startswith('prio:'): priority = name + # release blocker detection + if name.startswith('release:'): + releaseblocker_state = name # area detection if name.startswith('area/'): area = name 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} + info = { + 'branch': branch, + 'max_branch': max_branch, + 'env': env, + 'priority': priority, + 'releaseblocker_state': releaseblocker_state, + 'area': area, + } proj_list = projects_for_info_json(issue) if proj_list: info['projects'] = proj_list |
