Azure DevOps Collector¶
Polls Azure DevOps REST API v7.1 using WIQL (Work Item Query Language) and publishes work items as raw_events to Kafka.
Configuration¶
| Variable | Description | Example |
|---|---|---|
AZURE_DEVOPS_ORG |
Organization name | myorg |
AZURE_DEVOPS_PROJECT |
Project name | MyProject |
AZURE_DEVOPS_PAT |
Personal Access Token | abc123... (read work items scope) |
Poll Interval: 600 seconds (10 min) — configurable in sources.yaml
Authentication¶
PAT as Basic Auth:
Create PAT at:
https://dev.azure.com/{org}/_usersSettings/tokensScope: Work Items (Read)
WIQL Query¶
Default query fetches recently changed work items:
SELECT [System.Id], [System.Title], [System.State], [System.WorkItemType],
[System.AssignedTo], [System.ChangedDate], [System.CreatedDate],
[Microsoft.VSTS.Common.Priority], [System.Tags]
FROM WorkItems
WHERE [System.ChangedDate] >= '@last_cursor'
ORDER BY [System.ChangedDate] DESC
Customization (in sources.yaml):
azuredevops:
poll_interval_seconds: 600
wiql: |
SELECT [System.Id], [System.Title], [System.State]
FROM WorkItems
WHERE [System.WorkItemType] = 'Bug'
AND [System.State] IN ('New', 'Active')
ORDER BY [System.ChangedDate] DESC
Data Mapping¶
| Azure DevOps Field | Canonical Event Field |
|---|---|
id |
tags.ado_id |
fields.System.Title |
message |
fields.System.State |
tags.state |
fields.System.WorkItemType |
tags.work_item_type |
fields.System.AssignedTo.displayName |
tags.assignee |
fields.System.ChangedDate |
timestamp (event time) |
fields.System.CreatedDate |
tags.created_date |
fields.Microsoft.VSTS.Common.Priority |
tags.priority (1-4) |
fields.System.Tags |
tags.ado_tags (semicolon-separated) |
fields.System.AreaPath |
tags.area_path |
fields.System.IterationPath |
tags.iteration_path |
Fixed Fields:
- source = azuredevops.{project}
- type = event
- environment = from AZURE_DEVOPS_ENV or production
- severity = derived from priority: 1→CRITICAL, 2→ERROR, 3→WARNING, 4→INFO
Polling Logic¶
sequenceDiagram
participant SCH as Scheduler
participant ADO as Azure DevOps API
participant KAF as Kafka
participant REDIS as Redis
SCH->>REDIS: GET polling:cursor:azuredevops
REDIS-->>SCH: last_changed_date (or null)
SCH->>ADO: POST /_apis/wit/wiql?api-version=7.1
Note right of ADO: WIQL with ChangedDate filter
ADO-->>SCH: Work item refs (ids)
SCH->>ADO: GET /_apis/wit/workitems?ids=...&fields=...&api-version=7.1
ADO-->>SCH: Full work items
loop For each work item
SCH->>SCH: Map to CanonicalEvent
SCH->>KAF: Produce raw_events (acks=all)
end
KAF-->>SCH: ACK
SCH->>REDIS: SET polling:cursor:azuredevops = max(ChangedDate)
Cursor: Uses System.ChangedDate (ISO 8601). Only items changed since last cursor are fetched.
Pagination¶
- WIQL returns IDs only (max 200 per query)
- Batch GET
/wit/workitems?ids=...fetches full items (max 200 per request) - Automatic pagination for large result sets
Error Handling¶
| Error | Behavior |
|---|---|
| 401 Unauthorized | PAT expired/insufficient scope — alert, stop |
| 403 Forbidden | Project access denied — check PAT project scope |
| 429 Rate Limited | Respect Retry-After header, exponential backoff |
| 5xx / Timeout | Retry with backoff (max 3) |
| Malformed WIQL | Log error, skip cycle |
Sample Output¶
{
"schema_version": "1.0",
"timestamp": "2026-01-15T10:30:00Z",
"source": "azuredevops.MyProject",
"type": "event",
"severity": "ERROR",
"message": "Build pipeline failing intermittently",
"summary": "State: Active | Assigned: john.doe | Priority: 2",
"tags": {
"ado_id": "12345",
"state": "Active",
"work_item_type": "Bug",
"assignee": "john.doe",
"priority": "2",
"ado_tags": "backend;pipeline;urgent",
"area_path": "MyProject\\Backend",
"iteration_path": "MyProject\\Sprint 12"
},
"dedup_key": "sha256:..."
}
Troubleshooting¶
| Symptom | Check |
|---|---|
| No events | Verify PAT has Work Items (Read) scope, correct org/project |
| 401 errors | Regenerate PAT, verify format (no trailing spaces) |
| Missing fields | Ensure WIQL SELECT includes desired fields |
| Duplicate events | Pipeline dedup handles; cursor in Redis |
# View cursor
docker compose exec redis redis-cli GET "polling:cursor:azuredevops"
# Test WIQL manually
curl -u :$PAT \
-X POST "https://dev.azure.com/{org}/{project}/_apis/wit/wiql?api-version=7.1" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT [System.Id] FROM WorkItems WHERE [System.ChangedDate] > \"2026-01-01\" ORDER BY [System.ChangedDate] DESC"}'