Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
155 lines
6.4 KiB
PowerShell
155 lines
6.4 KiB
PowerShell
<#
|
|
.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
|