|
491 | 491 |
|
492 | 492 | // Configuration constants |
493 | 493 | const MAX_URL_LENGTH = 8000; // GitHub URL limit is 8201, stay under 8000 to be safe |
494 | | - const LOG_ENTRY_ESTIMATE = 150; // Estimated characters per log entry |
495 | | - const MIN_REMAINING_SPACE = 100; // Minimum space needed for log section |
496 | | - const ERROR_BUFFER_SPACE = 50; // Buffer space when adding error details |
| 494 | + const GITHUB_ISSUE_BASE_URL = "https://github.com/ydb-platform/ydb/issues/new"; // Base URL for GitHub issues |
497 | 495 |
|
498 | 496 | // Utility functions |
| 497 | + function buildGitHubIssueUrl(title, body, labels) { |
| 498 | + return GITHUB_ISSUE_BASE_URL + "?title=" + title + "&body=" + body + "&labels=" + labels + "&type=Bug"; |
| 499 | + } |
| 500 | + |
| 501 | + // Show tooltip stub to avoid runtime errors (visual feedback provided by icon swap) |
| 502 | + function showCopiedTooltip() {} |
| 503 | + |
| 504 | + // Safe percent-encoded truncation helpers |
| 505 | + function isDecodable(s) { |
| 506 | + try { decodeURIComponent(s); return true; } catch (e) { return false; } |
| 507 | + } |
| 508 | + |
| 509 | + function safeTruncateEncoded(encoded, maxLen) { |
| 510 | + if (encoded.length <= maxLen) return encoded; |
| 511 | + let cut = encoded.slice(0, maxLen); |
| 512 | + // Remove incomplete % sequence at the tail |
| 513 | + cut = cut.replace(/%(?:[0-9A-Fa-f]{0,2})$/, ''); |
| 514 | + while (cut && !isDecodable(cut)) { |
| 515 | + if (/%[0-9A-Fa-f]{2}$/.test(cut)) cut = cut.slice(0, -3); |
| 516 | + else cut = cut.slice(0, -1); |
| 517 | + } |
| 518 | + return cut; |
| 519 | + } |
| 520 | + |
499 | 521 | function parseTestName(full_name) { |
500 | 522 | const trimmed = full_name.trim(); |
501 | 523 | let pieces; |
|
611 | 633 | return body; |
612 | 634 | } |
613 | 635 |
|
614 | | - function addOptionalContent(baseBody, logUrls, errorText, remainingSpace) { |
| 636 | + function buildBodyWithLengthLimit(baseBody, logUrls, errorText, title, labels, maxUrlLength) { |
615 | 637 | let body = baseBody; |
616 | | - let availableSpace = remainingSpace; |
617 | 638 |
|
618 | | - // Add logs if space allows |
619 | | - if (logUrls && Object.keys(logUrls).length > 0 && availableSpace > MIN_REMAINING_SPACE) { |
| 639 | + // Add logs first |
| 640 | + if (logUrls && Object.keys(logUrls).length > 0) { |
620 | 641 | let logsSection = "**Logs:**%0A"; |
621 | | - let logCount = 0; |
622 | | - const maxLogs = Math.min(Object.keys(logUrls).length, Math.floor(availableSpace / LOG_ENTRY_ESTIMATE)); |
623 | | - |
624 | 642 | for (const [logName, logUrl] of Object.entries(logUrls)) { |
625 | | - if (logCount >= maxLogs) break; |
626 | | - const logEntry = "- [" + encodeURIComponent(logName) + "](" + encodeURIComponent(logUrl) + ")%0A"; |
627 | | - if (logsSection.length + logEntry.length < availableSpace - ERROR_BUFFER_SPACE) { |
628 | | - logsSection += logEntry; |
629 | | - logCount++; |
630 | | - } else { |
631 | | - break; |
632 | | - } |
633 | | - } |
634 | | - |
635 | | - if (Object.keys(logUrls).length > logCount) { |
636 | | - logsSection += "- (and " + (Object.keys(logUrls).length - logCount) + " more logs in report)%0A"; |
| 643 | + logsSection += "- [" + encodeURIComponent(logName) + "](" + encodeURIComponent(logUrl) + ")%0A"; |
637 | 644 | } |
638 | 645 | logsSection += "%0A"; |
639 | | - |
640 | 646 | body += logsSection; |
641 | | - availableSpace -= logsSection.length; |
642 | 647 | } |
643 | 648 |
|
644 | | - // Add error details if space allows |
645 | | - if (errorText && errorText.trim() !== "" && availableSpace > ERROR_BUFFER_SPACE) { |
646 | | - const maxErrorLength = Math.min(errorText.trim().length, availableSpace - ERROR_BUFFER_SPACE); |
647 | | - const errorToShow = errorText.trim().substring(0, maxErrorLength); |
648 | | - let errorSection = "**Error Summary:**%0A```%0A" + encodeURIComponent(errorToShow); |
649 | | - if (errorText.trim().length > maxErrorLength) { |
650 | | - errorSection += "... (see full error in test report)"; |
| 649 | + // Try to add error text, checking actual URL length |
| 650 | + if (errorText && errorText.trim() !== "") { |
| 651 | + const errorPrefix = "**Error Summary:**%0A```%0A"; |
| 652 | + const errorSuffix = "%0A```%0A%0A"; |
| 653 | + const truncationMsg = "%0A<TRUNCATED>"; |
| 654 | + |
| 655 | + const fullErrorText = errorText.trim(); |
| 656 | + const encodedFullError = encodeURIComponent(fullErrorText); |
| 657 | + |
| 658 | + // Compute minimal overhead URL length (without error content) |
| 659 | + const minimalSection = errorPrefix + errorSuffix; |
| 660 | + const minimalUrl = buildGitHubIssueUrl(title, body + minimalSection, labels); |
| 661 | + if (minimalUrl.length > maxUrlLength) { |
| 662 | + // Can't include error section at all |
| 663 | + } else { |
| 664 | + const allowedDelta = maxUrlLength - minimalUrl.length; |
| 665 | + if (encodedFullError.length <= allowedDelta) { |
| 666 | + body += errorPrefix + encodedFullError + errorSuffix; |
| 667 | + } else { |
| 668 | + const allowedForEncoded = allowedDelta - truncationMsg.length; |
| 669 | + if (allowedForEncoded > 0) { |
| 670 | + const truncatedEncoded = safeTruncateEncoded(encodedFullError, allowedForEncoded); |
| 671 | + if (truncatedEncoded) { |
| 672 | + body += errorPrefix + truncatedEncoded + truncationMsg + errorSuffix; |
| 673 | + } |
| 674 | + } |
| 675 | + } |
651 | 676 | } |
652 | | - errorSection += "%0A```%0A%0A"; |
653 | | - body += errorSection; |
654 | 677 | } |
655 | 678 |
|
656 | 679 | return body; |
|
691 | 714 | // Build labels |
692 | 715 | const labels = buildLabels(actualIssueType, buildPreset, ownerAreaLabel); |
693 | 716 |
|
694 | | - // Calculate base URL length |
695 | | - const baseUrl = "https://github.com/ydb-platform/ydb/issues/new?title=" + title + "&body=" + body + "&labels=" + labels + "&type=Bug"; |
696 | | - const remainingSpace = MAX_URL_LENGTH - baseUrl.length; |
697 | | - |
698 | | - // Add optional content based on available space |
699 | | - const finalBody = addOptionalContent(body, logUrls, errorText, remainingSpace); |
| 717 | + // Build body with exact length limit checking |
| 718 | + const finalBody = buildBodyWithLengthLimit(body, logUrls, errorText, title, labels, MAX_URL_LENGTH); |
700 | 719 |
|
701 | 720 | // Create final URL and open |
702 | | - const finalUrl = "https://github.com/ydb-platform/ydb/issues/new?title=" + title + "&body=" + finalBody + "&labels=" + labels + "&type=Bug"; |
| 721 | + const finalUrl = buildGitHubIssueUrl(title, finalBody, labels); |
703 | 722 | window.open(finalUrl, '_blank'); |
704 | 723 | } |
705 | 724 |
|
|
0 commit comments