<# .SYNOPSIS Findet das aelteste und neueste Ticket pro Person (Tenure-Analyse) .NOTES BULK-Ansatz: Nur 2-3 API-Calls statt 70+ Holt ALLE Issues der relevanten Projekte und gruppiert lokal nach Assignee #> $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 " Tenure-Analyse (Bulk-Abfrage)" Write-Host " Strategie: Alle Issues holen, lokal nach Assignee gruppieren" 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=assignee,created,updated,project" Write-Host " Lade Issues $StartAt - $($StartAt + $PageSize)..." -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 } # EINE Bulk-Abfrage: Alle Issues aus allen Team-Projekten $Projects = "O2C404, O2CCIB, O2CZERO, O2COS, O2CDEVOPS, O2CSYS, O2CBS, O2CQST, TTTI" $Jql = "project in ($Projects) ORDER BY created ASC" Write-Host "" Write-Host ">> Bulk-Abfrage: Alle Issues aus $Projects" -ForegroundColor Yellow $AllIssues = Invoke-JiraBulk -Jql $Jql -MaxTotal 25000 Write-Host "" Write-Host " Gesamt: $($AllIssues.Count) Issues geladen" -ForegroundColor Cyan # Lokal nach Assignee gruppieren Write-Host "" Write-Host ">> Gruppiere nach Assignee..." -ForegroundColor Yellow $ByAssignee = @{} foreach ($Issue in $AllIssues) { if ($Issue.fields.assignee) { $Name = $Issue.fields.assignee.displayName if (-not $ByAssignee[$Name]) { $ByAssignee[$Name] = @{ Issues = @(); FirstCreated = $null; LastUpdated = $null } } $Created = $Issue.fields.created.Substring(0, 10) $Updated = $Issue.fields.updated.Substring(0, 10) $ByAssignee[$Name].Issues += $Issue.key if (-not $ByAssignee[$Name].FirstCreated -or $Created -lt $ByAssignee[$Name].FirstCreated) { $ByAssignee[$Name].FirstCreated = $Created $ByAssignee[$Name].FirstTicket = $Issue.key $ByAssignee[$Name].FirstProject = $Issue.fields.project.key } if (-not $ByAssignee[$Name].LastUpdated -or $Updated -gt $ByAssignee[$Name].LastUpdated) { $ByAssignee[$Name].LastUpdated = $Updated $ByAssignee[$Name].LastTicket = $Issue.key } } } Write-Host " $($ByAssignee.Count) verschiedene Assignees gefunden" # Ergebnisse aufbereiten $Results = @() foreach ($Entry in ($ByAssignee.GetEnumerator() | Sort-Object { $_.Value.FirstCreated })) { $Name = $Entry.Key $Data = $Entry.Value $DaysAgo = ((Get-Date) - [DateTime]::Parse($Data.LastUpdated)).Days $Status = if ($DaysAgo -le 30) { "Aktiv" } elseif ($DaysAgo -le 90) { "Wenig aktiv" } else { "INAKTIV ($DaysAgo Tage)" } $Results += [PSCustomObject]@{ Person = $Name FirstTicket = $Data.FirstTicket FirstDate = $Data.FirstCreated LastDate = $Data.LastUpdated LastTicket = $Data.LastTicket Status = $Status Project = $Data.FirstProject IssueCount = $Data.Issues.Count } } # CSV speichern $Results | Export-Csv "$OutputDir\tenure.csv" -Delimiter ";" -Encoding UTF8 -NoTypeInformation # Ausgabe Write-Host "" Write-Host "=== Sortiert nach Startdatum (Top 40) ===" $Results | Select-Object -First 40 | ForEach-Object { $Color = if ($_.Status -eq "Aktiv") { "Green" } elseif ($_.Status -match "INAKTIV") { "Red" } else { "Yellow" } Write-Host (" {0,-35} {1} bis {2} [{3}] ({4} Issues)" -f $_.Person, $_.FirstDate, $_.LastDate, $_.Status, $_.IssueCount) -ForegroundColor $Color } Write-Host "" Write-Host "FERTIG! $($Results.Count) Personen in $OutputDir\tenure.csv" -ForegroundColor Green Write-Host "API-Calls: ~$([Math]::Ceiling($AllIssues.Count / 1000) + 1) (statt $($ByAssignee.Count * 2))" -ForegroundColor Cyan