mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-02 04:04:31 +02:00
ci: improve the dynamic pipeline report
This commit is contained in:
@@ -9,7 +9,7 @@ generate_failed_jobs_report:
|
|||||||
when: always
|
when: always
|
||||||
dependencies: [] # Do not download artifacts from the previous stages
|
dependencies: [] # Do not download artifacts from the previous stages
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 1 week
|
expire_in: 2 week
|
||||||
when: always
|
when: always
|
||||||
paths:
|
paths:
|
||||||
- job_report.html
|
- job_report.html
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -34,6 +34,8 @@ TEST_RELATED_APPS_DOWNLOAD_URLS_FILENAME = 'test_related_apps_download_urls.yml'
|
|||||||
REPORT_TEMPLATE_FILEPATH = os.path.join(
|
REPORT_TEMPLATE_FILEPATH = os.path.join(
|
||||||
IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'report.template.html'
|
IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'report.template.html'
|
||||||
)
|
)
|
||||||
|
CSS_STYLES_FILEPATH = os.path.join(IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'styles.css')
|
||||||
|
JS_SCRIPTS_FILEPATH = os.path.join(IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'scripts.js')
|
||||||
TOP_N_APPS_BY_SIZE_DIFF = 10
|
TOP_N_APPS_BY_SIZE_DIFF = 10
|
||||||
SIZE_DIFFERENCE_BYTES_THRESHOLD = 500
|
SIZE_DIFFERENCE_BYTES_THRESHOLD = 500
|
||||||
BINARY_SIZE_METRIC_NAME = 'binary_size'
|
BINARY_SIZE_METRIC_NAME = 'binary_size'
|
||||||
|
@@ -20,7 +20,10 @@ from idf_ci_local.uploader import AppUploader
|
|||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
from .constants import BINARY_SIZE_METRIC_NAME
|
from .constants import BINARY_SIZE_METRIC_NAME
|
||||||
|
from .constants import CI_DASHBOARD_API
|
||||||
from .constants import COMMENT_START_MARKER
|
from .constants import COMMENT_START_MARKER
|
||||||
|
from .constants import CSS_STYLES_FILEPATH
|
||||||
|
from .constants import JS_SCRIPTS_FILEPATH
|
||||||
from .constants import REPORT_TEMPLATE_FILEPATH
|
from .constants import REPORT_TEMPLATE_FILEPATH
|
||||||
from .constants import RETRY_JOB_PICTURE_LINK
|
from .constants import RETRY_JOB_PICTURE_LINK
|
||||||
from .constants import RETRY_JOB_PICTURE_PATH
|
from .constants import RETRY_JOB_PICTURE_PATH
|
||||||
@@ -56,6 +59,14 @@ class ReportGenerator:
|
|||||||
self.output_filepath = self.title.lower().replace(' ', '_') + '.html'
|
self.output_filepath = self.title.lower().replace(' ', '_') + '.html'
|
||||||
self.additional_info = ''
|
self.additional_info = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_commit_summary(self) -> str:
|
||||||
|
"""Return a formatted commit summary string."""
|
||||||
|
return (
|
||||||
|
f'with CI commit SHA: {self.commit_id[:8]}, '
|
||||||
|
f'local commit SHA: {os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_SHA", "")[:8]}'
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_download_link_for_url(url: str) -> str:
|
def get_download_link_for_url(url: str) -> str:
|
||||||
if url:
|
if url:
|
||||||
@@ -83,14 +94,36 @@ class ReportGenerator:
|
|||||||
report_url: str = get_artifacts_url(job_id, output_filepath)
|
report_url: str = get_artifacts_url(job_id, output_filepath)
|
||||||
return report_url
|
return report_url
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_file_content(filepath: str) -> str:
|
||||||
|
"""
|
||||||
|
Load the content of a file as string
|
||||||
|
|
||||||
|
:param filepath: Path to the file to load
|
||||||
|
:return: Content of the file as string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
|
return f.read()
|
||||||
|
except (IOError, FileNotFoundError) as e:
|
||||||
|
print(f'Warning: Could not read file {filepath}: {e}')
|
||||||
|
return ''
|
||||||
|
|
||||||
def generate_html_report(self, table_str: str) -> str:
|
def generate_html_report(self, table_str: str) -> str:
|
||||||
# we're using bootstrap table
|
# we're using bootstrap table
|
||||||
table_str = table_str.replace(
|
table_str = table_str.replace(
|
||||||
'<table>',
|
'<table>',
|
||||||
'<table data-toggle="table" data-search-align="left" data-search="true" data-sticky-header="true">',
|
'<table data-toggle="table" data-search-align="left" data-search="true" data-sticky-header="true">',
|
||||||
)
|
)
|
||||||
with open(REPORT_TEMPLATE_FILEPATH) as fr:
|
|
||||||
template = fr.read()
|
template = self._load_file_content(REPORT_TEMPLATE_FILEPATH)
|
||||||
|
css_content = self._load_file_content(CSS_STYLES_FILEPATH)
|
||||||
|
js_content = self._load_file_content(JS_SCRIPTS_FILEPATH)
|
||||||
|
|
||||||
|
template = template.replace('{{css_content}}', css_content)
|
||||||
|
template = template.replace('{{js_content}}', js_content)
|
||||||
|
template = template.replace('{{pipeline_id}}', str(self.pipeline_id))
|
||||||
|
template = template.replace('{{apiBaseUrl}}', CI_DASHBOARD_API)
|
||||||
|
|
||||||
return template.replace('{{title}}', self.title).replace('{{table}}', table_str)
|
return template.replace('{{title}}', self.title).replace('{{table}}', table_str)
|
||||||
|
|
||||||
@@ -136,17 +169,23 @@ class ReportGenerator:
|
|||||||
return report_sections
|
return report_sections
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_additional_info_section(title: str, count: int, report_url: t.Optional[str] = None) -> str:
|
def generate_additional_info_section(
|
||||||
|
title: str, count: int, report_url: t.Optional[str] = None, add_permalink: bool = True
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Generate a section for the additional info string.
|
Generate a section for the additional info string.
|
||||||
|
|
||||||
:param title: The title of the section.
|
:param title: The title of the section.
|
||||||
:param count: The count of test cases.
|
:param count: The count of test cases.
|
||||||
:param report_url: The URL of the report. If count = 0, only the count will be included.
|
:param report_url: The URL of the report. If count = 0, only the count will be included.
|
||||||
|
:param add_permalink: Whether to include a permalink in the report URL. Defaults to True.
|
||||||
:return: The formatted additional info section string.
|
:return: The formatted additional info section string.
|
||||||
"""
|
"""
|
||||||
if count != 0 and report_url:
|
if count != 0 and report_url:
|
||||||
return f'- **{title}:** [{count}]({report_url}/#{format_permalink(title)})\n'
|
if add_permalink:
|
||||||
|
return f'- **{title}:** [{count}]({report_url}/#{format_permalink(title)})\n'
|
||||||
|
else:
|
||||||
|
return f'- **{title}:** [{count}]({report_url})\n'
|
||||||
else:
|
else:
|
||||||
return f'- **{title}:** {count}\n'
|
return f'- **{title}:** {count}\n'
|
||||||
|
|
||||||
@@ -673,7 +712,11 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
return skipped_apps_table_section
|
return skipped_apps_table_section
|
||||||
|
|
||||||
def _get_report_str(self) -> str:
|
def _get_report_str(self) -> str:
|
||||||
self.additional_info = f'**Build Summary (with commit {self.commit_id[:8]}):**\n'
|
self.additional_info = (
|
||||||
|
f'**Build Summary ({self.get_commit_summary}):**\n'
|
||||||
|
'\n'
|
||||||
|
'> ℹ️ Note: Binary artifacts stored in MinIO are retained for 4 DAYS from their build date\n'
|
||||||
|
)
|
||||||
failed_apps_report_parts = self.get_failed_apps_report_parts()
|
failed_apps_report_parts = self.get_failed_apps_report_parts()
|
||||||
skipped_apps_report_parts = self.get_skipped_apps_report_parts()
|
skipped_apps_report_parts = self.get_skipped_apps_report_parts()
|
||||||
built_apps_report_parts = self.get_built_apps_report_parts()
|
built_apps_report_parts = self.get_built_apps_report_parts()
|
||||||
@@ -700,8 +743,8 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
self.test_cases = test_cases
|
self.test_cases = test_cases
|
||||||
self._known_failure_cases_set = None
|
self._known_failure_cases_set = None
|
||||||
self.report_titles_map = {
|
self.report_titles_map = {
|
||||||
'failed_yours': 'Failed Test Cases on Your branch (Excludes Known Failure Cases)',
|
'failed_yours': 'Testcases failed ONLY on your branch (known failures are excluded)',
|
||||||
'failed_others': 'Failed Test Cases on Other branches (Excludes Known Failure Cases)',
|
'failed_others': 'Testcases failed on your branch as well as on others (known failures are excluded)',
|
||||||
'failed_known': 'Known Failure Cases',
|
'failed_known': 'Known Failure Cases',
|
||||||
'skipped': 'Skipped Test Cases',
|
'skipped': 'Skipped Test Cases',
|
||||||
'succeeded': 'Succeeded Test Cases',
|
'succeeded': 'Succeeded Test Cases',
|
||||||
@@ -794,7 +837,7 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
'Test Case',
|
'Test Case',
|
||||||
'Test App Path',
|
'Test App Path',
|
||||||
'Failure Reason',
|
'Failure Reason',
|
||||||
'Failures on your branch (40 latest testcases)',
|
'These test cases failed exclusively on your branch in the latest 40 runs',
|
||||||
'Dut Log URL',
|
'Dut Log URL',
|
||||||
'Create Known Failure Case Jira',
|
'Create Known Failure Case Jira',
|
||||||
'Job URL',
|
'Job URL',
|
||||||
@@ -803,9 +846,9 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
row_attrs=['name', 'app_path', 'failure', 'dut_log_url', 'ci_job_url', 'ci_dashboard_url'],
|
row_attrs=['name', 'app_path', 'failure', 'dut_log_url', 'ci_job_url', 'ci_dashboard_url'],
|
||||||
value_functions=[
|
value_functions=[
|
||||||
(
|
(
|
||||||
'Failures on your branch (40 latest testcases)',
|
'These test cases failed exclusively on your branch in the latest 40 runs',
|
||||||
lambda item: f'{getattr(item, "latest_failed_count", "")} '
|
lambda item: f'{getattr(item, "latest_failed_count", "")} / '
|
||||||
f'/ {getattr(item, "latest_total_count", "")}',
|
f'{getattr(item, "latest_total_count", "")}',
|
||||||
),
|
),
|
||||||
('Create Known Failure Case Jira', known_failure_issue_jira_fast_link),
|
('Create Known Failure Case Jira', known_failure_issue_jira_fast_link),
|
||||||
],
|
],
|
||||||
@@ -901,7 +944,10 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
self.succeeded_cases_report_file,
|
self.succeeded_cases_report_file,
|
||||||
)
|
)
|
||||||
self.additional_info += self.generate_additional_info_section(
|
self.additional_info += self.generate_additional_info_section(
|
||||||
self.report_titles_map['succeeded'], len(succeeded_test_cases), succeeded_cases_report_url
|
self.report_titles_map['succeeded'],
|
||||||
|
len(succeeded_test_cases),
|
||||||
|
succeeded_cases_report_url,
|
||||||
|
add_permalink=False,
|
||||||
)
|
)
|
||||||
self.additional_info += '\n'
|
self.additional_info += '\n'
|
||||||
return succeeded_cases_table_section
|
return succeeded_cases_table_section
|
||||||
@@ -911,7 +957,7 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
Generate a complete HTML report string by processing test cases.
|
Generate a complete HTML report string by processing test cases.
|
||||||
:return: Complete HTML report string.
|
:return: Complete HTML report string.
|
||||||
"""
|
"""
|
||||||
self.additional_info = f'**Test Case Summary (with commit {self.commit_id[:8]}):**\n'
|
self.additional_info = f'**Test Case Summary ({self.get_commit_summary}):**\n'
|
||||||
failed_cases_report_parts = self.get_failed_cases_report_parts()
|
failed_cases_report_parts = self.get_failed_cases_report_parts()
|
||||||
skipped_cases_report_parts = self.get_skipped_cases_report_parts()
|
skipped_cases_report_parts = self.get_skipped_cases_report_parts()
|
||||||
succeeded_cases_report_parts = self.get_succeeded_cases_report_parts()
|
succeeded_cases_report_parts = self.get_succeeded_cases_report_parts()
|
||||||
@@ -960,7 +1006,7 @@ class JobReportGenerator(ReportGenerator):
|
|||||||
)
|
)
|
||||||
succeeded_jobs = self._filter_items(self.jobs, lambda job: job.is_success)
|
succeeded_jobs = self._filter_items(self.jobs, lambda job: job.is_success)
|
||||||
|
|
||||||
self.additional_info = f'**Job Summary (with commit {self.commit_id[:8]}):**\n'
|
self.additional_info = f'**Job Summary ({self.get_commit_summary}):**\n'
|
||||||
self.additional_info += self.generate_additional_info_section(
|
self.additional_info += self.generate_additional_info_section(
|
||||||
self.report_titles_map['succeeded'], len(succeeded_jobs)
|
self.report_titles_map['succeeded'], len(succeeded_jobs)
|
||||||
)
|
)
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- target_test_report.html
|
- target_test_report.html
|
||||||
expire_in: 1 week
|
expire_in: 2 week
|
||||||
when: always
|
when: always
|
||||||
|
|
||||||
fast_pipeline:pipeline_ended:always_failed:
|
fast_pipeline:pipeline_ended:always_failed:
|
||||||
|
@@ -9,7 +9,7 @@ generate_pytest_report:
|
|||||||
- failed_cases.html
|
- failed_cases.html
|
||||||
- skipped_cases.html
|
- skipped_cases.html
|
||||||
- succeeded_cases.html
|
- succeeded_cases.html
|
||||||
expire_in: 1 week
|
expire_in: 2 week
|
||||||
when: always
|
when: always
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
@@ -1,8 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{{title}}</title>
|
<title>{{title}}</title>
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
href="https://www.espressif.com/sites/all/themes/espressif/favicon.ico"
|
||||||
|
type="image/vnd.microsoft.icon"
|
||||||
|
/>
|
||||||
|
<!-- External CSS libraries -->
|
||||||
<link
|
<link
|
||||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
@@ -19,114 +26,106 @@
|
|||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
<!-- CSS content will be injected here during template rendering -->
|
||||||
<style>
|
<style>
|
||||||
.text-toggle,
|
/* {{css_content}} */
|
||||||
.full-text {
|
/* Additional styles for disabled tabs */
|
||||||
cursor: pointer;
|
.report-nav-tabs .nav-tab.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
th:nth-child(1),
|
|
||||||
td:nth-child(1) {
|
/* Loading animation for tabs */
|
||||||
width: 5%;
|
.report-nav-tabs .nav-tab.loading:after {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-left-color: #333;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: 8px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
th:nth-child(2),
|
|
||||||
td:nth-child(2),
|
@keyframes spin {
|
||||||
th:nth-child(3),
|
to {
|
||||||
td:nth-child(3) {
|
transform: rotate(360deg);
|
||||||
width: 30%;
|
}
|
||||||
}
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.copy-link-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-left: 10px;
|
|
||||||
color: #8f8f97;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.copy-link-icon:hover {
|
|
||||||
color: #282b2c;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body data-pipeline-id="{{pipeline_id}}">
|
||||||
<div class="container-fluid">{{table}}</div>
|
<!-- Navigation progress bar -->
|
||||||
|
<div class="nav-progress-container">
|
||||||
|
<div class="nav-progress-bar" id="nav-progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<!-- Report header section -->
|
||||||
|
<header class="report-header">
|
||||||
|
<div class="logo-container">
|
||||||
|
<img
|
||||||
|
src="https://www.espressif.com/sites/all/themes/espressif/logo-black.svg"
|
||||||
|
alt="Espressif Logo"
|
||||||
|
class="logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="title-container">
|
||||||
|
<h1>
|
||||||
|
<span style="color: #333">Dynamic Pipeline Report</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Search and controls section -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-8">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-end">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button
|
||||||
|
class="btn btn-esp btn-sm"
|
||||||
|
id="expand-all-tables"
|
||||||
|
>
|
||||||
|
<i class="fas fa-table"></i> Expand All
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary btn-sm ms-2"
|
||||||
|
id="collapse-all-tables"
|
||||||
|
>
|
||||||
|
<i class="fas fa-minus"></i> Collapse All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<div class="table-container">{{table}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Floating action buttons -->
|
||||||
|
<div class="floating-actions">
|
||||||
|
<div class="floating-action-btn back-to-top" id="back-to-top">
|
||||||
|
<i class="fas fa-arrow-up"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript libraries -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://unpkg.com/bootstrap-table@1.22.1/dist/bootstrap-table.min.js"></script>
|
<script src="https://unpkg.com/bootstrap-table@1.22.1/dist/bootstrap-table.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.0/dist/extensions/sticky-header/bootstrap-table-sticky-header.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.0/dist/extensions/sticky-header/bootstrap-table-sticky-header.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/bootstrap-table@1.22.1/dist/extensions/export/bootstrap-table-export.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.10.21/tableExport.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom scripts -->
|
||||||
<script>
|
<script>
|
||||||
$(window).on("load", function () {
|
{{js_content}}
|
||||||
var hash = window.location.hash;
|
|
||||||
if (hash) {
|
|
||||||
setTimeout(function () {
|
|
||||||
$("html, body").animate(
|
|
||||||
{ scrollTop: $(hash).offset().top },
|
|
||||||
100
|
|
||||||
);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function () {
|
|
||||||
scrollToHashLocation();
|
|
||||||
setupTextToggles();
|
|
||||||
setupEventHandlers();
|
|
||||||
});
|
|
||||||
|
|
||||||
function setupEventHandlers() {
|
|
||||||
$(window).on("load", scrollToHashLocation);
|
|
||||||
$("body").on("click", ".toggle-link", toggleText);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToHashLocation() {
|
|
||||||
const hash = window.location.hash;
|
|
||||||
if (hash) {
|
|
||||||
setTimeout(() => {
|
|
||||||
$("html, body").animate(
|
|
||||||
{ scrollTop: $(hash).offset().top },
|
|
||||||
100
|
|
||||||
);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyPermalink(anchorId) {
|
|
||||||
const fullUrl = `${window.location.origin}${window.location.pathname}${anchorId}`;
|
|
||||||
history.pushState(null, null, anchorId);
|
|
||||||
navigator.clipboard.writeText(fullUrl);
|
|
||||||
scrollToHashLocation();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleText(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const link = $(this),
|
|
||||||
textSpan = link.siblings(".full-text"),
|
|
||||||
toggleSpan = link.siblings(".text-toggle");
|
|
||||||
const visible = textSpan.is(":visible");
|
|
||||||
link.text(visible ? "Show More" : "Show Less");
|
|
||||||
textSpan.toggle();
|
|
||||||
toggleSpan.toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupTextToggles() {
|
|
||||||
$("table.table td").each(function () {
|
|
||||||
var cell = $(this);
|
|
||||||
if (cell.text().length > 100) {
|
|
||||||
var originalText = cell.text();
|
|
||||||
var displayText =
|
|
||||||
originalText.substring(0, 100) + "...";
|
|
||||||
cell.html(
|
|
||||||
`<span class="text-toggle">${displayText}</span><span class="full-text" style="display: none;">${originalText}</span><a href="#" class="toggle-link">Show More</a>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
1127
tools/ci/dynamic_pipelines/templates/scripts.js
Normal file
1127
tools/ci/dynamic_pipelines/templates/scripts.js
Normal file
File diff suppressed because it is too large
Load Diff
1095
tools/ci/dynamic_pipelines/templates/styles.css
Normal file
1095
tools/ci/dynamic_pipelines/templates/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,10 +12,11 @@ generate_pytest_build_report:
|
|||||||
- skipped_apps.html
|
- skipped_apps.html
|
||||||
- build_report.html
|
- build_report.html
|
||||||
- test_related_apps_download_urls.yml
|
- test_related_apps_download_urls.yml
|
||||||
expire_in: 1 week
|
expire_in: 2 week
|
||||||
when: always
|
when: always
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- env
|
||||||
- python tools/ci/dynamic_pipelines/scripts/generate_report.py --report-type build
|
- python tools/ci/dynamic_pipelines/scripts/generate_report.py --report-type build
|
||||||
- python tools/ci/previous_stage_job_status.py --stage build
|
- python tools/ci/previous_stage_job_status.py --stage build
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,7 @@ def parse_testcases_from_filepattern(junit_report_filepattern: str) -> t.List[Te
|
|||||||
"""
|
"""
|
||||||
Parses test cases from XML files matching the provided file pattern.
|
Parses test cases from XML files matching the provided file pattern.
|
||||||
|
|
||||||
>>> test_cases = parse_testcases_from_filepattern("path/to/your/junit/reports/*.xml")
|
>>> test_cases = parse_testcases_from_filepattern('path/to/your/junit/reports/*.xml')
|
||||||
|
|
||||||
:param junit_report_filepattern: The file pattern to match XML files containing JUnit test reports.
|
:param junit_report_filepattern: The file pattern to match XML files containing JUnit test reports.
|
||||||
:return: List[TestCase]: A list of TestCase objects parsed from the XML files.
|
:return: List[TestCase]: A list of TestCase objects parsed from the XML files.
|
||||||
@@ -124,7 +124,10 @@ def fetch_failed_jobs(commit_id: str) -> t.List[GitlabJob]:
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
f'{CI_DASHBOARD_API}/jobs/failure_ratio',
|
f'{CI_DASHBOARD_API}/jobs/failure_ratio',
|
||||||
headers={'CI-Job-Token': CI_JOB_TOKEN},
|
headers={'CI-Job-Token': CI_JOB_TOKEN},
|
||||||
json={'job_names': failed_job_names, 'exclude_branches': [os.getenv('CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', '')]},
|
json={
|
||||||
|
'job_names': failed_job_names,
|
||||||
|
'exclude_branches': [os.getenv('CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', '')],
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
print(f'Failed to fetch jobs failure rate data: {response.status_code} with error: {response.text}')
|
print(f'Failed to fetch jobs failure rate data: {response.status_code} with error: {response.text}')
|
||||||
@@ -185,15 +188,14 @@ def fetch_app_metrics(
|
|||||||
json={
|
json={
|
||||||
'source_commit_sha': source_commit_sha,
|
'source_commit_sha': source_commit_sha,
|
||||||
'target_commit_sha': target_commit_sha,
|
'target_commit_sha': target_commit_sha,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
print(f'Failed to fetch build info: {response.status_code} - {response.text}')
|
print(f'Failed to fetch build info: {response.status_code} - {response.text}')
|
||||||
else:
|
else:
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
build_info_map = {
|
build_info_map = {
|
||||||
f"{info['app_path']}_{info['config_name']}_{info['target']}": info
|
f'{info["app_path"]}_{info["config_name"]}_{info["target"]}': info for info in response_data.get('data', [])
|
||||||
for info in response_data.get('data', [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return build_info_map
|
return build_info_map
|
||||||
@@ -253,7 +255,7 @@ def get_repository_file_url(file_path: str) -> str:
|
|||||||
|
|
||||||
def known_failure_issue_jira_fast_link(_item: TestCase) -> str:
|
def known_failure_issue_jira_fast_link(_item: TestCase) -> str:
|
||||||
"""
|
"""
|
||||||
Generate a JIRA fast link for known issues with relevant test case details.
|
Generate a JIRA fast link for known issues with relevant test case details.
|
||||||
"""
|
"""
|
||||||
jira_url = os.getenv('JIRA_SERVER')
|
jira_url = os.getenv('JIRA_SERVER')
|
||||||
jira_pid = os.getenv('JIRA_KNOWN_FAILURE_PID', '10514')
|
jira_pid = os.getenv('JIRA_KNOWN_FAILURE_PID', '10514')
|
||||||
@@ -269,14 +271,14 @@ def known_failure_issue_jira_fast_link(_item: TestCase) -> str:
|
|||||||
'issuetype': jira_issuetype,
|
'issuetype': jira_issuetype,
|
||||||
'summary': f'[Test Case]{_item.name}',
|
'summary': f'[Test Case]{_item.name}',
|
||||||
'description': (
|
'description': (
|
||||||
f"job_url: {quote(_item.ci_job_url, safe=':/')}\n\n"
|
f'job_url: {quote(_item.ci_job_url, safe=":/")}\n\n'
|
||||||
f"dut_log_url: {quote(_item.dut_log_url, safe=':/')}\n\n"
|
f'dut_log_url: {quote(_item.dut_log_url, safe=":/")}\n\n'
|
||||||
f'ci_dashboard_url: {_item.ci_dashboard_url}\n\n'
|
f'ci_dashboard_url: {_item.ci_dashboard_url}\n\n'
|
||||||
),
|
),
|
||||||
'components': jira_component,
|
'components': jira_component,
|
||||||
'priority': jira_priority,
|
'priority': jira_priority,
|
||||||
'assignee': jira_assignee,
|
'assignee': jira_assignee,
|
||||||
'versions': jira_affected_versions
|
'versions': jira_affected_versions,
|
||||||
}
|
}
|
||||||
query_string = urlencode(params)
|
query_string = urlencode(params)
|
||||||
return f'<a href="{base_url}{query_string}">Create</a>'
|
return f'<a href="{base_url}{query_string}">Create</a>'
|
||||||
|
Reference in New Issue
Block a user