<# .SYNOPSIS Bug-Verursachungs-Analyse (Bulk-Ansatz) Holt alle Bugs + Features der letzten 6 Monate in 2 Bulk-Abfragen und korreliert lokal. #> $JiraUrl = "https://arija.jaas.service.deutschebahn.com" $ApiBase = "$JiraUrl/rest" $OutputDir = "project-audit\data\jira-bugs" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Write-Host "============================================================" Write-Host " Bug-Verursachungs-Analyse (Bulk)" Write-Host " 2 API-Calls statt 60+" Write-Host "============================================================" $Token = $null $SecretsPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\.secrets" if (Test-Path $SecretsPath) { Get-Content $SecretsPath | ForEach-Object { if ($_ -match "^JIRA_TOKEN=(.+)$") { $val = $Matches[1].Trim() if ($val -ne "HIER_TOKEN_EINTRAGEN") { $Token = $val } } } } if (-not $Token) { $Token = Read-Host "Jira PAT" } $Headers = @{ "Authorization" = "Bearer $Token"; "Content-Type" = "application/json" } New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null function Invoke-JiraBulk { param([string]$Jql, [int]$MaxTotal = 5000) $AllIssues = @() $StartAt = 0 $PageSize = 1000 do { $Uri = "$ApiBase/api/2/search?jql=$([Uri]::EscapeDataString($Jql))&startAt=$StartAt&maxResults=$PageSize&fields=summary,created,resolutiondate,assignee,issuetype,project,reporter" Write-Host " Lade $StartAt..." -NoNewline try { $R = Invoke-WebRequest -Uri $Uri -Headers $Headers -UseBasicParsing -ErrorAction Stop $Data = $R.Content | ConvertFrom-Json $AllIssues += $Data.issues $Total = $Data.total Write-Host " $($AllIssues.Count)/$Total" -ForegroundColor Green $StartAt += $PageSize Start-Sleep -Seconds 2 } catch { $StatusCode = 0 if ($_.Exception.Response) { $StatusCode = [int]$_.Exception.Response.StatusCode } if ($StatusCode -eq 429) { Write-Host " [429 - warte 30s]" -ForegroundColor Yellow Start-Sleep -Seconds 30 } else { Write-Host " FEHLER: $($_.Exception.Message)" -ForegroundColor Red break } } } while ($AllIssues.Count -lt $Total -and $AllIssues.Count -lt $MaxTotal) return $AllIssues } $Projects = "O2C404, O2CCIB, O2CZERO" # Abfrage 1: Alle Bugs (6 Monate) Write-Host "" Write-Host ">> Bugs (6 Monate)..." -ForegroundColor Yellow $BugJql = "project in ($Projects) AND issuetype = Bug AND created >= -180d ORDER BY created DESC" $Bugs = Invoke-JiraBulk -Jql $BugJql -MaxTotal 2000 Write-Host " $($Bugs.Count) Bugs geladen" # Abfrage 2: Abgeschlossene Features/Stories (6 Monate) Write-Host "" Write-Host ">> Features/Stories (6 Monate, abgeschlossen)..." -ForegroundColor Yellow $FeatureJql = "project in ($Projects) AND issuetype in (Feature, Enabler, Story, Aufgabe, Unteraufgabe) AND status in (Fertig, Done, Closed) AND resolved >= -180d ORDER BY resolved DESC" $Features = Invoke-JiraBulk -Jql $FeatureJql -MaxTotal 3000 Write-Host " $($Features.Count) Features geladen" # Lokale Analyse pro Team Write-Host "" Write-Host ">> Korrelationsanalyse..." -ForegroundColor Yellow foreach ($ProjectKey in @("O2C404", "O2CCIB", "O2CZERO")) { $TeamName = switch ($ProjectKey) { "O2C404" { "Team 404" }; "O2CCIB" { "Team CIB" }; "O2CZERO" { "Team Zero" } } Write-Host "" Write-Host " === $TeamName ===" -ForegroundColor Cyan $TeamBugs = $Bugs | Where-Object { $_.fields.project.key -eq $ProjectKey } $TeamFeatures = $Features | Where-Object { $_.fields.project.key -eq $ProjectKey } Write-Host " $($TeamBugs.Count) Bugs, $($TeamFeatures.Count) Features" # Korrelation: Wer hat in 14 Tagen vor Bug-Erstellung Features abgeschlossen? $Causation = @{} foreach ($Bug in $TeamBugs) { $BugCreated = [DateTime]::Parse($Bug.fields.created) $WindowStart = $BugCreated.AddDays(-14) $RecentFeatures = $TeamFeatures | Where-Object { $_.fields.resolutiondate -and [DateTime]::Parse($_.fields.resolutiondate) -ge $WindowStart -and [DateTime]::Parse($_.fields.resolutiondate) -le $BugCreated } foreach ($F in $RecentFeatures) { if ($F.fields.assignee) { $Dev = $F.fields.assignee.displayName if (-not $Causation[$Dev]) { $Causation[$Dev] = @{ Features = 0; BugKeys = @() } } if ($Causation[$Dev].BugKeys -notcontains $Bug.key) { $Causation[$Dev].BugKeys += $Bug.key } } } } # Feature-Count pro Dev foreach ($F in $TeamFeatures) { if ($F.fields.assignee) { $Dev = $F.fields.assignee.displayName if (-not $Causation[$Dev]) { $Causation[$Dev] = @{ Features = 0; BugKeys = @() } } $Causation[$Dev].Features++ } } # Ausgabe Top 10 Write-Host " --- Potenzielle Bug-Verursachung (14-Tage-Fenster) ---" $Sorted = $Causation.GetEnumerator() | Sort-Object { $_.Value.BugKeys.Count } -Descending foreach ($E in ($Sorted | Select-Object -First 10)) { $UniqueBugs = $E.Value.BugKeys.Count $Feats = $E.Value.Features $Rate = if ($Feats -gt 0) { [Math]::Round($UniqueBugs / $Feats * 100, 0) } else { 0 } Write-Host (" {0,-30} Features:{1,4} | Bugs:{2,4} | Rate:{3,3}%" -f $E.Key, $Feats, $UniqueBugs, $Rate) } # CSV $CsvData = $Sorted | ForEach-Object { [PSCustomObject]@{ Team = $TeamName Developer = $_.Key Features = $_.Value.Features CorrelatedBugs = $_.Value.BugKeys.Count BugRate = if ($_.Value.Features -gt 0) { [Math]::Round($_.Value.BugKeys.Count / $_.Value.Features * 100, 0) } else { 0 } } } $CsvData | Export-Csv "$OutputDir\$ProjectKey-causation.csv" -Delimiter ";" -Encoding UTF8 -NoTypeInformation } Write-Host "" Write-Host "============================================================" -ForegroundColor Green Write-Host " FERTIG! API-Calls: ~$([Math]::Ceiling(($Bugs.Count + $Features.Count) / 1000) + 2)" -ForegroundColor Green Write-Host " Ergebnisse in $OutputDir" -ForegroundColor Green Write-Host "============================================================" -ForegroundColor Green