Skip to content

Conversation

dev-habib-nuhu
Copy link
Contributor

@dev-habib-nuhu dev-habib-nuhu commented Oct 16, 2025

User description

Description

What

  • Added support for ingesting branches as a new kind in the Azure DevOps integration, allowing users to sync branches from their Azure DevOps repositories into Port.

Why

  • Enables visibility into repository branch structure within Port
  • Allows teams to track active branches, feature branches, and branch relationships

How -

Type of change

  • New feature (non-breaking change which adds functionality)

All tests should be run against the port production environment(using a testing org).

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

  • Integration able to create all default resources from scratch
  • Completed a full resync from a freshly installed integration and it completed successfully
  • Resync able to create entities
  • Resync able to update entities
  • Resync able to detect and delete entities
  • Resync finishes successfully
  • 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.
  • 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.

API Documentation

Provide links to the API documentation used for this integration.


PR Type

Enhancement


Description

  • Added branch generation and resync functionality to Azure DevOps integration

  • Implemented generate_branches() method to fetch branches from repositories

  • Added BRANCH kind to support branch entities in Port

  • Created resync handler and example mappings for branch resources


Diagram Walkthrough

flowchart LR
  A["Azure DevOps API"] -->|"fetch branches"| B["generate_branches()"]
  B -->|"enrich with metadata"| C["Branch entities"]
  C -->|"resync"| D["Port"]
  E["Example mappings"] -->|"configure"| D
Loading

File Walkthrough

Relevant files
Enhancement
azure_devops_client.py
Add branch generation methods to client                                   

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

  • Added generate_branches() async generator to fetch branches from all
    repositories
  • Implemented _get_branches_for_repository() to retrieve and enrich
    branch data
  • Branches are filtered by refs/heads/ prefix and enriched with
    repository context
  • Includes error handling and logging for branch fetching operations
+68/-0   
misc.py
Add branch kind to enum                                                                   

integrations/azure-devops/azure_devops/misc.py

  • Added BRANCH = "branch" to the Kind enum
+1/-0     
main.py
Add branch resync handler                                                               

integrations/azure-devops/main.py

  • Added resync_branches() handler decorated with
    @ocean.on_resync(Kind.BRANCH)
  • Handler calls generate_branches() and yields branch batches for
    resyncing
+9/-0     
Tests
test_azure_devops_client.py
Add branch generation tests                                                           

integrations/azure-devops/tests/azure_devops/client/test_azure_devops_client.py

  • Added EXPECTED_BRANCHES_RAW test data with raw branch references
  • Added EXPECTED_BRANCHES test data with enriched branch objects
    including repository and project context
  • Implemented test_generate_branches() test case with mocked repository
    and branch generation
  • Test verifies branch name extraction, enrichment, and metadata
    preservation
+98/-0   
Documentation
example-mappings.yaml
Add branch resource mapping example                                           

integrations/azure-devops/examples/example-mappings.yaml

  • Added branch resource mapping configuration with selector query:
    'true'
  • Configured branch entity identifier combining project, repository, and
    branch names
  • Mapped branch properties including name, repository name, project
    name, and repository URL
  • Established relation between branch and repository entities
+20/-0   

@dev-habib-nuhu dev-habib-nuhu requested a review from a team as a code owner October 16, 2025 22:53
Copy link
Contributor

qodo-merge-pro bot commented Oct 16, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
- [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true -->

</details></td></tr>
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Copy link
Contributor

qodo-merge-pro bot commented Oct 16, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Implement repository filtering for branch synchronization

To improve performance and scalability, add a configuration option for users to
filter which repositories have their branches synchronized. This avoids fetching
branches from all repositories, which is inefficient for large organizations.

Examples:

integrations/azure-devops/azure_devops/client/azure_devops_client.py [224-248]
    async def generate_branches(
        self,
    ) -> AsyncGenerator[list[dict[str, Any]], None]:
        """
        Generate branches for all repositories in all projects.

        API: GET {org}/{project}/_apis/git/repositories/{repoId}/refs?filter=heads/
        https://learn.microsoft.com/en-us/rest/api/azure/devops/git/refs/list?view=azure-devops-rest-7.1
        """
        async for repositories in self.generate_repositories(

 ... (clipped 15 lines)

Solution Walkthrough:

Before:

# file: integrations/azure-devops/azure_devops/client/azure_devops_client.py

class AzureDevopsClient:
    async def generate_branches(self):
        # Iterates over ALL repositories from generate_repositories
        async for repositories in self.generate_repositories(include_disabled_repositories=False):
            tasks = [
                self._get_branches_for_repository(repository)
                for repository in repositories
            ]
            async for branches in stream_async_iterators_tasks(*tasks):
                yield branches

After:

# file: integrations/azure-devops/main.py

@ocean.on_resync(Kind.BRANCH)
async def resync_branches(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
    azure_devops_client = AzureDevopsClient.create_from_ocean_config()
    
    # Example of getting a repository filter from the resource config
    spec = ocean.resource_config.get_port_app_config_by_kind(kind)
    repo_filter = spec.selector.query.get("repositories")

    async for branches in azure_devops_client.generate_branches(repo_filter):
        logger.info(f"Resyncing {len(branches)} branches")
        yield branches

# file: integrations/azure-devops/azure_devops/client/azure_devops_client.py

class AzureDevopsClient:
    async def generate_branches(self, repo_filter: list[str] | None = None):
        async for repositories in self.generate_repositories(include_disabled_repositories=False):
            
            # Filter repositories based on the provided filter
            selected_repos = [
                repo for repo in repositories 
                if not repo_filter or any(fnmatch.fnmatch(repo['name'], pattern) for pattern in repo_filter)
            ]

            tasks = [
                self._get_branches_for_repository(repository)
                for repository in selected_repos
            ]
            async for branches in stream_async_iterators_tasks(*tasks):
                yield branches
Suggestion importance[1-10]: 8

__

Why: This suggestion addresses a significant scalability issue by proposing repository filtering, which is crucial for making the new branch synchronization feature practical and performant in large-scale Azure DevOps organizations.

Medium
Possible issue
Improve error handling for missing keys

To prevent unhandled KeyError exceptions, move the dictionary key access for
repository['project']['id'] inside the try...except block in the
_get_branches_for_repository method.

integrations/azure-devops/azure_devops/client/azure_devops_client.py [250-290]

 async def _get_branches_for_repository(
     self, repository: dict[str, Any]
 ) -> AsyncGenerator[list[dict[str, Any]], None]:
     """Get branches for a single repository."""
-    project_id = repository["project"]["id"]
-    repository_id = repository["id"]
-    repository_name = repository["name"]
-
-    branches_url = f"{self._organization_base_url}/{project_id}/{API_URL_PREFIX}/git/repositories/{repository_id}/refs"
-    params = {
-        "filter": "heads/",
-    }
+    repository_id = repository.get("id")
+    repository_name = repository.get("name")
 
     try:
+        project_id = repository["project"]["id"]
+        branches_url = f"{self._organization_base_url}/{project_id}/{API_URL_PREFIX}/git/repositories/{repository_id}/refs"
+        params = {
+            "filter": "heads/",
+        }
+
         async for refs in self._get_paginated_by_top_and_continuation_token(
             branches_url, additional_params=params
         ):
             enriched_branches = []
             for ref in refs:
                 ref_name = ref["name"]
                 if ref_name.startswith("refs/heads/"):
                     branch_name = ref_name.replace("refs/heads/", "")
 
                     enriched_branch = {
                         "name": branch_name,
                         "refName": ref_name,
                         "objectId": ref["objectId"],
                         "__repository": repository
                     }
                     enriched_branches.append(enriched_branch)
 
             if enriched_branches:
                 logger.info(
                     f"Found {len(enriched_branches)} branches for repository {repository_name}"
                 )
                 yield enriched_branches
 
     except Exception as e:
+        project_id_log = repository.get("project", {}).get("id", "unknown")
         logger.error(
-            f"Failed to fetch branches for repository {repository_name} in project {project_id}: {str(e)}"
+            f"Failed to fetch branches for repository {repository_name} in project {project_id_log}: {str(e)}"
         )
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that a KeyError could occur before the try-except block, and proposes a valid change to improve the method's robustness and error handling.

Medium
  • Update

@github-actions github-actions bot added size/L and removed size/M labels Oct 17, 2025
Copy link
Member

@mk-armah mk-armah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does ADO Support branches live events ?

Copy link
Member

@mk-armah mk-armah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants