diff --git a/.github/scripts/tests/templates/summary.html b/.github/scripts/tests/templates/summary.html index 65e7a02d514b..e01e945f41db 100644 --- a/.github/scripts/tests/templates/summary.html +++ b/.github/scripts/tests/templates/summary.html @@ -491,11 +491,33 @@ // Configuration constants const MAX_URL_LENGTH = 8000; // GitHub URL limit is 8201, stay under 8000 to be safe - const LOG_ENTRY_ESTIMATE = 150; // Estimated characters per log entry - const MIN_REMAINING_SPACE = 100; // Minimum space needed for log section - const ERROR_BUFFER_SPACE = 50; // Buffer space when adding error details + const GITHUB_ISSUE_BASE_URL = "https://github.com/ydb-platform/ydb/issues/new"; // Base URL for GitHub issues // Utility functions + function buildGitHubIssueUrl(title, body, labels) { + return GITHUB_ISSUE_BASE_URL + "?title=" + title + "&body=" + body + "&labels=" + labels + "&type=Bug"; + } + + // Show tooltip stub to avoid runtime errors (visual feedback provided by icon swap) + function showCopiedTooltip() {} + + // Safe percent-encoded truncation helpers + function isDecodable(s) { + try { decodeURIComponent(s); return true; } catch (e) { return false; } + } + + function safeTruncateEncoded(encoded, maxLen) { + if (encoded.length <= maxLen) return encoded; + let cut = encoded.slice(0, maxLen); + // Remove incomplete % sequence at the tail + cut = cut.replace(/%(?:[0-9A-Fa-f]{0,2})$/, ''); + while (cut && !isDecodable(cut)) { + if (/%[0-9A-Fa-f]{2}$/.test(cut)) cut = cut.slice(0, -3); + else cut = cut.slice(0, -1); + } + return cut; + } + function parseTestName(full_name) { const trimmed = full_name.trim(); let pieces; @@ -611,46 +633,47 @@ return body; } - function addOptionalContent(baseBody, logUrls, errorText, remainingSpace) { + function buildBodyWithLengthLimit(baseBody, logUrls, errorText, title, labels, maxUrlLength) { let body = baseBody; - let availableSpace = remainingSpace; - // Add logs if space allows - if (logUrls && Object.keys(logUrls).length > 0 && availableSpace > MIN_REMAINING_SPACE) { + // Add logs first + if (logUrls && Object.keys(logUrls).length > 0) { let logsSection = "**Logs:**%0A"; - let logCount = 0; - const maxLogs = Math.min(Object.keys(logUrls).length, Math.floor(availableSpace / LOG_ENTRY_ESTIMATE)); - for (const [logName, logUrl] of Object.entries(logUrls)) { - if (logCount >= maxLogs) break; - const logEntry = "- [" + encodeURIComponent(logName) + "](" + encodeURIComponent(logUrl) + ")%0A"; - if (logsSection.length + logEntry.length < availableSpace - ERROR_BUFFER_SPACE) { - logsSection += logEntry; - logCount++; - } else { - break; - } - } - - if (Object.keys(logUrls).length > logCount) { - logsSection += "- (and " + (Object.keys(logUrls).length - logCount) + " more logs in report)%0A"; + logsSection += "- [" + encodeURIComponent(logName) + "](" + encodeURIComponent(logUrl) + ")%0A"; } logsSection += "%0A"; - body += logsSection; - availableSpace -= logsSection.length; } - // Add error details if space allows - if (errorText && errorText.trim() !== "" && availableSpace > ERROR_BUFFER_SPACE) { - const maxErrorLength = Math.min(errorText.trim().length, availableSpace - ERROR_BUFFER_SPACE); - const errorToShow = errorText.trim().substring(0, maxErrorLength); - let errorSection = "**Error Summary:**%0A```%0A" + encodeURIComponent(errorToShow); - if (errorText.trim().length > maxErrorLength) { - errorSection += "... (see full error in test report)"; + // Try to add error text, checking actual URL length + if (errorText && errorText.trim() !== "") { + const errorPrefix = "**Error Summary:**%0A```%0A"; + const errorSuffix = "%0A```%0A%0A"; + const truncationMsg = "%0A"; + + const fullErrorText = errorText.trim(); + const encodedFullError = encodeURIComponent(fullErrorText); + + // Compute minimal overhead URL length (without error content) + const minimalSection = errorPrefix + errorSuffix; + const minimalUrl = buildGitHubIssueUrl(title, body + minimalSection, labels); + if (minimalUrl.length > maxUrlLength) { + // Can't include error section at all + } else { + const allowedDelta = maxUrlLength - minimalUrl.length; + if (encodedFullError.length <= allowedDelta) { + body += errorPrefix + encodedFullError + errorSuffix; + } else { + const allowedForEncoded = allowedDelta - truncationMsg.length; + if (allowedForEncoded > 0) { + const truncatedEncoded = safeTruncateEncoded(encodedFullError, allowedForEncoded); + if (truncatedEncoded) { + body += errorPrefix + truncatedEncoded + truncationMsg + errorSuffix; + } + } + } } - errorSection += "%0A```%0A%0A"; - body += errorSection; } return body; @@ -691,15 +714,11 @@ // Build labels const labels = buildLabels(actualIssueType, buildPreset, ownerAreaLabel); - // Calculate base URL length - const baseUrl = "https://github.com/ydb-platform/ydb/issues/new?title=" + title + "&body=" + body + "&labels=" + labels + "&type=Bug"; - const remainingSpace = MAX_URL_LENGTH - baseUrl.length; - - // Add optional content based on available space - const finalBody = addOptionalContent(body, logUrls, errorText, remainingSpace); + // Build body with exact length limit checking + const finalBody = buildBodyWithLengthLimit(body, logUrls, errorText, title, labels, MAX_URL_LENGTH); // Create final URL and open - const finalUrl = "https://github.com/ydb-platform/ydb/issues/new?title=" + title + "&body=" + finalBody + "&labels=" + labels + "&type=Bug"; + const finalUrl = buildGitHubIssueUrl(title, finalBody, labels); window.open(finalUrl, '_blank'); }