diff --git a/docs/triage/2025-12-27-duplicate-deployment-issues.md b/docs/triage/2025-12-27-duplicate-deployment-issues.md index 622bebf5a..809720c39 100644 --- a/docs/triage/2025-12-27-duplicate-deployment-issues.md +++ b/docs/triage/2025-12-27-duplicate-deployment-issues.md @@ -43,20 +43,32 @@ Now it only runs when the `deploy-production` job actually fails. A script was created to close the duplicate issues: `scripts/triage-duplicate-issues.sh` -**To run the script:** +**The script now dynamically finds and closes duplicates:** ```bash # Set your GitHub token (needs repo write access) export GITHUB_TOKEN="your_github_token_here" -# Run the script +# Run the script (uses default search pattern) +./scripts/triage-duplicate-issues.sh + +# Or with a custom search pattern +export SEARCH_TITLE="Your custom issue title pattern" ./scripts/triage-duplicate-issues.sh ``` -The script will: -1. Add an explanatory comment to each duplicate issue -2. Close the issue with state_reason "not_planned" -3. Keep issue #124 and #24 open +**The script will:** +1. Search for all open issues matching the title pattern using GitHub API +2. Sort issues by creation date (newest first) +3. Keep the most recent issue open +4. Add an explanatory comment to each older duplicate issue +5. Close duplicate issues with state_reason "not_planned" + +**Key Features:** +- ✅ Dynamic duplicate detection (no hardcoded issue numbers) +- ✅ Automatically keeps the most recent issue open +- ✅ Configurable search pattern via environment variable +- ✅ Uses GitHub API search for accurate results ## Issues Closed diff --git a/docs/triage/TRIAGE_SUMMARY.md b/docs/triage/TRIAGE_SUMMARY.md index 2dff75649..2c9dd4e48 100644 --- a/docs/triage/TRIAGE_SUMMARY.md +++ b/docs/triage/TRIAGE_SUMMARY.md @@ -51,14 +51,26 @@ rollback-preparation: ### 2. Created Automation ✅ **Script:** `scripts/triage-duplicate-issues.sh` -- Bulk-closes 21 duplicate issues (#92-#122) -- Adds explanatory comment to each -- Preserves issues #124 and #24 +- Dynamically finds duplicate issues using GitHub API +- Sorts by creation date and identifies most recent issue +- Bulk-closes all duplicates except the most recent one +- Adds explanatory comment to each closed issue +- Configurable via environment variables + +**Features:** +- ✅ No hardcoded issue numbers - uses API search +- ✅ Automatically keeps most recent issue open +- ✅ Customizable search pattern via `SEARCH_TITLE` env var +- ✅ Comprehensive error handling and rate limiting **Usage:** ```bash export GITHUB_TOKEN="your_token_with_repo_write_access" ./scripts/triage-duplicate-issues.sh + +# Or with custom search pattern: +export SEARCH_TITLE="Custom Issue Title" +./scripts/triage-duplicate-issues.sh ``` ### 3. Created Documentation ✅ diff --git a/scripts/test-triage-logic.sh b/scripts/test-triage-logic.sh new file mode 100755 index 000000000..b099cf33e --- /dev/null +++ b/scripts/test-triage-logic.sh @@ -0,0 +1,168 @@ +#!/bin/bash + +# Comprehensive test for triage-duplicate-issues.sh logic +# This tests the core functions without making actual API calls + +set -e + +echo "🧪 Testing triage-duplicate-issues.sh logic" +echo "=============================================" +echo "" + +# Source the functions we need to test (extract them from the main script) +# For testing, we'll recreate them here + +get_issues_to_close() { + local issues_data="$1" + + if [ -z "$issues_data" ]; then + echo "⚠️ No duplicate issues found" >&2 + return 0 + fi + + local total_count=$(echo "$issues_data" | wc -l) + + if [ "$total_count" -le 1 ]; then + echo "ℹ️ Only one issue found, nothing to close" >&2 + return 0 + fi + + # Skip the first line (most recent issue) and get the rest + echo "$issues_data" | tail -n +2 | cut -d'|' -f1 +} + +# Test 1: Multiple duplicate issues +echo "Test 1: Multiple duplicate issues (should close all except most recent)" +echo "-----------------------------------------------------------------------" +TEST_DATA_1='124|2025-12-27T10:30:00Z|🚨 Production Deployment Failed +122|2025-12-27T10:25:00Z|🚨 Production Deployment Failed +121|2025-12-27T10:20:00Z|🚨 Production Deployment Failed +119|2025-12-27T10:15:00Z|🚨 Production Deployment Failed +117|2025-12-27T10:10:00Z|🚨 Production Deployment Failed' + +TOTAL=$(echo "$TEST_DATA_1" | wc -l) +MOST_RECENT=$(echo "$TEST_DATA_1" | head -1 | cut -d'|' -f1) +TO_CLOSE=$(get_issues_to_close "$TEST_DATA_1") +TO_CLOSE_COUNT=$(echo "$TO_CLOSE" | wc -l) + +echo " Total issues found: $TOTAL" +echo " Most recent issue: #$MOST_RECENT" +echo " Issues to close: $(echo $TO_CLOSE | tr '\n' ' ')" +echo " Count to close: $TO_CLOSE_COUNT" + +if [ "$MOST_RECENT" = "124" ] && [ "$TO_CLOSE_COUNT" = "4" ]; then + echo " ✅ PASS: Correctly identified most recent and 4 issues to close" +else + echo " ❌ FAIL: Expected most recent=#124, count=4" + exit 1 +fi +echo "" + +# Test 2: Two duplicate issues +echo "Test 2: Two duplicate issues (should close oldest, keep newest)" +echo "----------------------------------------------------------------" +TEST_DATA_2='150|2025-12-27T11:00:00Z|Bug in login +148|2025-12-27T10:55:00Z|Bug in login' + +TOTAL=$(echo "$TEST_DATA_2" | wc -l) +MOST_RECENT=$(echo "$TEST_DATA_2" | head -1 | cut -d'|' -f1) +TO_CLOSE=$(get_issues_to_close "$TEST_DATA_2") +TO_CLOSE_COUNT=$(echo "$TO_CLOSE" | wc -l) + +echo " Total issues found: $TOTAL" +echo " Most recent issue: #$MOST_RECENT" +echo " Issues to close: $TO_CLOSE" +echo " Count to close: $TO_CLOSE_COUNT" + +if [ "$MOST_RECENT" = "150" ] && [ "$TO_CLOSE" = "148" ] && [ "$TO_CLOSE_COUNT" = "1" ]; then + echo " ✅ PASS: Correctly keeps newest (#150) and closes oldest (#148)" +else + echo " ❌ FAIL: Expected most recent=#150, to_close=#148" + exit 1 +fi +echo "" + +# Test 3: Single issue +echo "Test 3: Single issue (should not close anything)" +echo "-------------------------------------------------" +TEST_DATA_3='200|2025-12-27T12:00:00Z|Unique issue' + +TOTAL=$(echo "$TEST_DATA_3" | wc -l) +MOST_RECENT=$(echo "$TEST_DATA_3" | head -1 | cut -d'|' -f1) +TO_CLOSE=$(get_issues_to_close "$TEST_DATA_3" 2>&1) + +echo " Total issues found: $TOTAL" +echo " Most recent issue: #$MOST_RECENT" + +if [ -z "$(echo "$TO_CLOSE" | grep -v "Only one issue")" ]; then + echo " ✅ PASS: Correctly identified no issues to close (only 1 issue)" +else + echo " ❌ FAIL: Should not close anything with only 1 issue" + exit 1 +fi +echo "" + +# Test 4: Empty input +echo "Test 4: Empty input (should handle gracefully)" +echo "----------------------------------------------" +TO_CLOSE=$(get_issues_to_close "" 2>&1) + +if [ -z "$(echo "$TO_CLOSE" | grep -v "No duplicate issues")" ]; then + echo " ✅ PASS: Correctly handled empty input" +else + echo " ❌ FAIL: Should handle empty input gracefully" + exit 1 +fi +echo "" + +# Test 5: Date parsing and sorting verification +echo "Test 5: Verify sorting by creation date (newest first)" +echo "-------------------------------------------------------" +TEST_DATA_5='300|2025-12-27T15:00:00Z|Issue C +299|2025-12-27T14:00:00Z|Issue B +298|2025-12-27T13:00:00Z|Issue A' + +MOST_RECENT=$(echo "$TEST_DATA_5" | head -1 | cut -d'|' -f1) +MOST_RECENT_DATE=$(echo "$TEST_DATA_5" | head -1 | cut -d'|' -f2) +OLDEST=$(echo "$TEST_DATA_5" | tail -1 | cut -d'|' -f1) + +echo " Most recent: #$MOST_RECENT at $MOST_RECENT_DATE" +echo " Oldest: #$OLDEST" + +if [ "$MOST_RECENT" = "300" ] && [ "$OLDEST" = "298" ]; then + echo " ✅ PASS: Correctly sorted by date (newest first)" +else + echo " ❌ FAIL: Sorting is incorrect" + exit 1 +fi +echo "" + +# Test 6: jq parsing simulation (test data format) +echo "Test 6: Verify data format compatibility with jq" +echo "-------------------------------------------------" +MOCK_JSON='{"items": [ + {"number": 124, "created_at": "2025-12-27T10:30:00Z", "title": "Test"}, + {"number": 122, "created_at": "2025-12-27T10:25:00Z", "title": "Test"} +]}' + +# Test that jq can parse and format the data correctly +PARSED=$(echo "$MOCK_JSON" | jq -r '.items | sort_by(.created_at) | reverse | .[] | "\(.number)|\(.created_at)|\(.title)"') +FIRST_ISSUE=$(echo "$PARSED" | head -1 | cut -d'|' -f1) + +if [ "$FIRST_ISSUE" = "124" ]; then + echo " ✅ PASS: jq parsing and formatting works correctly" +else + echo " ❌ FAIL: jq parsing failed" + exit 1 +fi +echo "" + +echo "=============================================" +echo "✅ All tests passed!" +echo "" +echo "Summary:" +echo " - Correctly identifies most recent issue" +echo " - Closes all duplicates except the most recent" +echo " - Handles edge cases (single issue, empty input)" +echo " - Date sorting works correctly" +echo " - Data format compatible with GitHub API response" diff --git a/scripts/triage-duplicate-issues.sh b/scripts/triage-duplicate-issues.sh index 672ca327a..963d6fe26 100755 --- a/scripts/triage-duplicate-issues.sh +++ b/scripts/triage-duplicate-issues.sh @@ -1,53 +1,164 @@ #!/bin/bash -# Script to bulk-close duplicate "Production Deployment Failed" issues -# These were created by a misconfigured workflow that triggered rollback issues -# on pre-deployment validation failures rather than actual deployment failures. +# Script to bulk-close duplicate issues found via GitHub API +# Dynamically finds issues with duplicate titles and closes all except the most recent one +# +# Usage: +# export GITHUB_TOKEN="ghp_your_token_here" +# ./triage-duplicate-issues.sh +# +# Or with custom search pattern: +# export GITHUB_TOKEN="ghp_your_token_here" +# export SEARCH_TITLE="Custom Issue Title" +# ./triage-duplicate-issues.sh +# +# The script will: +# 1. Search for all open issues matching the SEARCH_TITLE pattern +# 2. Sort them by creation date (newest first) +# 3. Keep the most recent issue open +# 4. Close all other duplicates with an explanatory comment set -e -GITHUB_TOKEN="${GITHUB_TOKEN}" +usage() { + echo "Usage: $0" + echo "" + echo "Environment variables:" + echo " GITHUB_TOKEN (required) GitHub personal access token with repo access" + echo " SEARCH_TITLE (optional) Issue title pattern to search for" + echo " Default: '🚨 Production Deployment Failed - Rollback Required'" + echo "" + echo "Example:" + echo " export GITHUB_TOKEN='ghp_xxxxxxxxxxxx'" + echo " export SEARCH_TITLE='Duplicate bug report'" + echo " $0" + exit 1 +} + +# Check for help flag +if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + usage +fi + if [ -z "$GITHUB_TOKEN" ]; then echo "❌ GITHUB_TOKEN environment variable is required" - exit 1 + echo "" + usage fi OWNER="johndoe6345789" REPO="metabuilder" -# Issues to close - all the duplicate deployment failure issues except the most recent (#124) -ISSUES_TO_CLOSE=(92 93 95 96 97 98 99 100 101 102 104 105 107 108 111 113 115 117 119 121 122) +# Search pattern for duplicate issues (can be customized) +SEARCH_TITLE="${SEARCH_TITLE:-🚨 Production Deployment Failed - Rollback Required}" + +# Function to fetch issues by title pattern +fetch_duplicate_issues() { + local search_query="$1" + echo "🔍 Searching for issues with title: \"$search_query\"" >&2 + + # Use GitHub API to search for issues by title + # Filter by: is:issue, is:open, repo, and title match + local encoded_query + encoded_query=$(echo "is:issue is:open repo:$OWNER/$REPO in:title $search_query" | jq -sRr @uri) + + local response + response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/search/issues?q=$encoded_query&sort=created&order=desc&per_page=100") + + # Check for API errors + if echo "$response" | jq -e '.message' > /dev/null 2>&1; then + local error_msg + error_msg=$(echo "$response" | jq -r '.message') + echo "❌ GitHub API error: $error_msg" >&2 + return 1 + fi + + # Extract issue numbers and creation dates, sorted by creation date (newest first) + echo "$response" | jq -r '.items | sort_by(.created_at) | reverse | .[] | "\(.number)|\(.created_at)|\(.title)"' +} + +# Function to determine which issues to close (all except the most recent) +get_issues_to_close() { + local issues_data="$1" + + if [ -z "$issues_data" ]; then + echo "⚠️ No duplicate issues found" >&2 + return 0 + fi + + local total_count + total_count=$(echo "$issues_data" | wc -l) + + if [ "$total_count" -le 1 ]; then + echo "ℹ️ Only one issue found, nothing to close" >&2 + return 0 + fi + + # Skip the first line (most recent issue) and get the rest + echo "$issues_data" | tail -n +2 | cut -d'|' -f1 +} + +# Fetch all duplicate issues +ISSUES_DATA=$(fetch_duplicate_issues "$SEARCH_TITLE") + +if [ -z "$ISSUES_DATA" ]; then + echo "✨ No duplicate issues found. Nothing to do!" + exit 0 +fi + +# Parse the data +TOTAL_ISSUES=$(echo "$ISSUES_DATA" | wc -l) +MOST_RECENT=$(echo "$ISSUES_DATA" | head -1 | cut -d'|' -f1) +MOST_RECENT_DATE=$(echo "$ISSUES_DATA" | head -1 | cut -d'|' -f2) + +echo "📊 Found $TOTAL_ISSUES duplicate issues" +echo "📌 Most recent issue: #$MOST_RECENT (created: $MOST_RECENT_DATE)" +echo "" + +# Get list of issues to close +ISSUES_TO_CLOSE_DATA=$(get_issues_to_close "$ISSUES_DATA") + +if [ -z "$ISSUES_TO_CLOSE_DATA" ]; then + echo "✨ No issues need to be closed!" + exit 0 +fi + +# Convert to array +ISSUES_TO_CLOSE=() +while IFS= read -r issue_num; do + ISSUES_TO_CLOSE+=("$issue_num") +done <<< "$ISSUES_TO_CLOSE_DATA" CLOSE_COMMENT='🤖 **Automated Triage: Closing Duplicate Issue** -This issue was automatically created by a misconfigured workflow. The deployment workflow was creating "rollback required" issues when **pre-deployment validation** failed, not when actual deployments failed. - -**Root Cause:** -- The `rollback-preparation` job had `if: failure()` which triggered when ANY upstream job failed -- It should have been `if: needs.deploy-production.result == '"'"'failure'"'"'` to only trigger on actual deployment failures +This issue has been identified as a duplicate. Multiple issues with the same title were found, and this script automatically closes all duplicates except the most recent one. **Resolution:** -- ✅ Fixed the workflow in the latest commit -- ✅ Keeping issue #124 as the canonical tracking issue -- ✅ Closing this and other duplicate issues created by the same root cause +- ✅ Keeping the most recent issue (#'"$MOST_RECENT"') as the canonical tracking issue +- ✅ Closing this and other duplicate issues to maintain a clean issue tracker -**No Action Required** - These were false positives and no actual production deployments failed. +**How duplicates were identified:** +- Search pattern: "'"$SEARCH_TITLE"'" +- Total duplicates found: '"$TOTAL_ISSUES"' +- Keeping most recent: Issue #'"$MOST_RECENT"' (created '"$MOST_RECENT_DATE"') + +**No Action Required** - Please refer to issue #'"$MOST_RECENT"' for continued discussion. --- -*For questions about this automated triage, see the commit that fixed the workflow.*' +*This closure was performed by an automated triage script. For questions, see `scripts/triage-duplicate-issues.sh`*' close_issue() { local issue_number=$1 # Add comment explaining closure echo "📝 Adding comment to issue #${issue_number}..." - curl -s -X POST \ + if curl -s -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/$OWNER/$REPO/issues/$issue_number/comments" \ - -d "{\"body\": $(echo "$CLOSE_COMMENT" | jq -Rs .)}" > /dev/null - - if [ $? -eq 0 ]; then + -d "{\"body\": $(echo "$CLOSE_COMMENT" | jq -Rs .)}" > /dev/null; then echo "✅ Added comment to issue #${issue_number}" else echo "❌ Failed to add comment to issue #${issue_number}" @@ -56,13 +167,11 @@ close_issue() { # Close the issue echo "🔒 Closing issue #${issue_number}..." - curl -s -X PATCH \ + if curl -s -X PATCH \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/$OWNER/$REPO/issues/$issue_number" \ - -d '{"state": "closed", "state_reason": "not_planned"}' > /dev/null - - if [ $? -eq 0 ]; then + -d '{"state": "closed", "state_reason": "not_planned"}' > /dev/null; then echo "✅ Closed issue #${issue_number}" else echo "❌ Failed to close issue #${issue_number}" @@ -76,6 +185,7 @@ main() { echo "🔧 Starting bulk issue triage..." echo "" echo "📋 Planning to close ${#ISSUES_TO_CLOSE[@]} duplicate issues" + echo "📌 Keeping issue #$MOST_RECENT open (most recent)" echo "" for issue_number in "${ISSUES_TO_CLOSE[@]}"; do @@ -86,9 +196,8 @@ main() { echo "✨ Triage complete!" echo "" - echo "📌 Keeping open:" - echo " - Issue #124 (most recent deployment failure - canonical tracking issue)" - echo " - Issue #24 (Renovate Dependency Dashboard - legitimate)" + echo "📌 Kept open: Issue #$MOST_RECENT (most recent, created $MOST_RECENT_DATE)" + echo "🔒 Closed: ${#ISSUES_TO_CLOSE[@]} duplicate issues" } main