Skip to content

Commit c4be90e

Browse files
oiadebayomatan84PeyGis
authored
[Integration][ADO] Added Support for boards and columns (#1034)
# Description What - Added functionality to fetch boards and their respective columns from Azure DevOps. Why - This feature is necessary to support synchronization of Azure DevOps boards and their columns, allowing for more detailed management and tracking of board data within the system. How - Implemented a method that fetches boards from Azure DevOps and retrieves their columns. The method also handles cases where the `columns` field may be missing from the board, ensuring that the system does not break when such cases occur. ## Type of change Please leave one option from the following and delete the rest: - [x] New feature (non-breaking change which adds functionality) <h4> All tests should be run against the port production environment(using a testing org). </h4> ### Core testing checklist - [ ] Integration able to create all default resources from scratch - [ ] Resync finishes successfully - [ ] Resync able to create entities - [ ] Resync able to update entities - [ ] Resync able to detect and delete entities - [ ] Scheduled resync able to abort existing resync and start a new one - [ ] Tested with at least 2 integrations from scratch - [ ] Tested with Kafka and Polling event listeners - [ ] Tested deletion of entities that don't pass the selector ### Integration testing checklist - [x] Integration able to create all default resources from scratch - [x] Resync able to create entities - [x] Resync able to update entities - [x] Resync able to detect and delete entities - [x] Resync finishes successfully - [x] If new resource kind is added or updated in the integration, add example raw data, mapping and expected result to the `examples` folder in the integration directory. - [x] If resource kind is updated, run the integration with the example data and check if the expected result is achieved - [ ] If new resource kind is added or updated, validate that live-events for that resource are working as expected - [ ] Docs PR link [here](#) ### Preflight checklist - [ ] Handled rate limiting - [ ] Handled pagination - [ ] Implemented the code in async - [ ] Support Multi account ## Screenshots Include screenshots from your environment showing how the resources of the integration will look. ![image](https://github.com/user-attachments/assets/aa40ddde-294d-46dd-9ae9-c3238460c9d5) ![image](https://github.com/user-attachments/assets/1e5eb973-188c-48bd-bc0c-a0001bdde3ed) ## API Documentation Provide links to the API documentation used for this integration. [Columns Documentation](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/columns?view=azure-devops-rest-7.1) [Board Documentation](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/boards?view=azure-devops-rest-7.1) --------- Co-authored-by: Matan <51418643+matan84@users.noreply.github.com> Co-authored-by: PagesCoffy <isaac.p.coffie@gmail.com>
1 parent fca40c2 commit c4be90e

File tree

14 files changed

+1125
-2
lines changed

14 files changed

+1125
-2
lines changed

integrations/azure-devops/.port/resources/blueprints.json

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,11 @@
127127
"type": "string",
128128
"icon": "AzureDevops",
129129
"description": "The type of work item (e.g., Bug, Task, User Story)",
130-
"enum": ["Issue", "Epic", "Task"],
130+
"enum": [
131+
"Issue",
132+
"Epic",
133+
"Task"
134+
],
131135
"enumColors": {
132136
"Issue": "green",
133137
"Epic": "orange",
@@ -190,6 +194,66 @@
190194
},
191195
"required": []
192196
},
197+
"mirrorProperties": {
198+
"board": {
199+
"title": "Board",
200+
"path": "column.board.$title"
201+
}
202+
},
203+
"calculationProperties": {},
204+
"aggregationProperties": {},
205+
"relations": {
206+
"project": {
207+
"title": "Project",
208+
"target": "project",
209+
"required": true,
210+
"many": false
211+
},
212+
"column": {
213+
"title": "Column",
214+
"description": "The column the entity belongs",
215+
"target": "column",
216+
"required": true,
217+
"many": false
218+
}
219+
}
220+
},
221+
{
222+
"identifier": "column",
223+
"title": "Column",
224+
"icon": "AzureDevops",
225+
"schema": {
226+
"properties": {},
227+
"required": []
228+
},
229+
"mirrorProperties": {},
230+
"calculationProperties": {},
231+
"aggregationProperties": {},
232+
"relations": {
233+
"board": {
234+
"title": "board",
235+
"target": "board",
236+
"required": true,
237+
"many": false
238+
}
239+
}
240+
},
241+
{
242+
"identifier": "board",
243+
"title": "Board",
244+
"icon": "AzureDevops",
245+
"schema": {
246+
"properties": {
247+
"link": {
248+
"title": "Link",
249+
"type": "string",
250+
"format": "url",
251+
"icon": "AzureDevops",
252+
"description": "Link to the board in Azure DevOps"
253+
}
254+
},
255+
"required": []
256+
},
193257
"mirrorProperties": {},
194258
"calculationProperties": {},
195259
"aggregationProperties": {},

integrations/azure-devops/.port/resources/port-app-config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ resources:
5454
blueprint: '"service"'
5555
properties:
5656
workItemLinking: .isEnabled and .isBlocking
57+
- kind: board
58+
selector:
59+
query: 'true'
60+
port:
61+
entity:
62+
mappings:
63+
identifier: .id | gsub(" "; "")
64+
title: .name
65+
blueprint: '"board"'
66+
properties:
67+
link: .url
68+
relations:
69+
project: .__project.id | gsub(" "; "")
5770
- kind: work-item
5871
selector:
5972
query: 'true'
@@ -76,3 +89,17 @@ resources:
7689
changedDate: .fields."System.ChangedDate"
7790
relations:
7891
project: .__projectId | gsub(" "; "")
92+
column: >-
93+
.fields."System.WorkItemType"+"-"+.fields."System.BoardColumn"+"-"+.__project.id
94+
| gsub(" "; "")
95+
- kind: column
96+
selector:
97+
query: 'true'
98+
port:
99+
entity:
100+
mappings:
101+
identifier: .__stateType+"-"+.name+"-"+.__board.__project.id | gsub(" "; "")
102+
title: .name
103+
blueprint: '"column"'
104+
relations:
105+
board: .__board.id | gsub(" "; "")

integrations/azure-devops/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
<!-- towncrier release notes start -->
99

10+
## 0.1.74 (2024-10-10)
11+
12+
13+
### Improvements
14+
15+
16+
- Added support for ingesting boards and columns
17+
18+
1019
## 0.1.73 (2024-10-09)
1120

1221

1322
### Improvements
1423

24+
1525
- Bumped ocean version to ^0.12.3
1626

1727

@@ -20,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2030

2131
### Improvements
2232

33+
2334
- Bumped ocean version to ^0.12.2
2435

2536

@@ -28,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2839

2940
### Improvements
3041

42+
3143
- Bumped ocean version to ^0.12.1
3244

3345

integrations/azure-devops/azure_devops/client/azure_devops_client.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,52 @@ async def get_repository(self, repository_id: str) -> dict[Any, Any]:
258258
repository_data = response.json()
259259
return repository_data
260260

261+
async def get_columns(self) -> AsyncGenerator[list[dict[str, Any]], None]:
262+
async for boards in self.get_boards_in_organization():
263+
for board in boards:
264+
yield [
265+
{
266+
**column,
267+
"__board": board,
268+
"__stateType": stateType,
269+
"__stateName": stateName,
270+
}
271+
for column in board.get("columns", [])
272+
if column.get("stateMappings")
273+
for stateType, stateName in column.get("stateMappings").items()
274+
]
275+
276+
async def _enrich_boards(
277+
self, boards: list[dict[str, Any]], project_id: str
278+
) -> list[dict[str, Any]]:
279+
for board in boards:
280+
response = await self.send_request(
281+
"GET",
282+
f"{self._organization_base_url}/{project_id}/{API_URL_PREFIX}/work/boards/{board['id']}",
283+
)
284+
board.update(response.json())
285+
return boards
286+
287+
async def _get_boards(self, project_id: str) -> list[dict[str, Any]]:
288+
get_boards_url = (
289+
f"{self._organization_base_url}/{project_id}/{API_URL_PREFIX}/work/boards"
290+
)
291+
response = await self.send_request("GET", get_boards_url)
292+
board_data = response.json().get("value", [])
293+
logger.info(f"Found {len(board_data)} boards for project {project_id}")
294+
return await self._enrich_boards(board_data, project_id)
295+
296+
@cache_iterator_result()
297+
async def get_boards_in_organization(
298+
self,
299+
) -> AsyncGenerator[list[dict[str, Any]], None]:
300+
async for projects in self.generate_projects():
301+
yield [
302+
{**board, "__project": project}
303+
for project in projects
304+
for board in await self._get_boards(project["id"])
305+
]
306+
261307
async def generate_subscriptions_webhook_events(self) -> list[WebhookEvent]:
262308
headers = {"Content-Type": "application/json"}
263309
try:

integrations/azure-devops/azure_devops/misc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class Kind(StrEnum):
1919
TEAM = "team"
2020
PROJECT = "project"
2121
WORK_ITEM = "work-item"
22+
BOARD = "board"
23+
COLUMN = "column"
2224

2325

2426
PULL_REQUEST_SEARCH_CRITERIA: list[dict[str, Any]] = [

0 commit comments

Comments
 (0)