Skip to content

feat(integrations): Azure DevOps labels service + tasks (PR 7/N)#7632

Closed
asaphko wants to merge 5 commits into
feat/azure-devops-06-commentsfrom
feat/azure-devops-07-labels
Closed

feat(integrations): Azure DevOps labels service + tasks (PR 7/N)#7632
asaphko wants to merge 5 commits into
feat/azure-devops-06-commentsfrom
feat/azure-devops-07-labels

Conversation

@asaphko
Copy link
Copy Markdown
Contributor

@asaphko asaphko commented May 28, 2026

Summary

PR 7 of the stacked Azure DevOps integration rollout. Adds the Flagsmith → ADO labelling layer:

  • Client functions for the two distinct ADO labelling shapes:
    • PR labels via POST /_apis/git/pullrequests/{id}/labels (project-scoped, no repo GUID) and DELETE with 404 swallowed for idempotency.
    • Work-item tags via GET-then-PATCH on the System.Tags field (ADO's only mechanism — semicolon-separated string, no dedicated tag endpoint). Both add and remove no-op when the desired terminal state already holds.
  • _ado_request generalised to accept a content_type kwarg (ADO PATCH on work items requires application/json-patch+json). json_body widened to accept JSON Patch list bodies.
  • services/labels.py with apply_flagsmith_label_to_resource(resource) and remove_flagsmith_label_from_resource(*, project_id, resource_url, resource_type). Both gated by labeling_enabled, both catch requests.RequestException and log without raising.
  • tasks.py gains apply_azure_devops_label and remove_azure_devops_label @register_task_handler() wrappers.

Stack

Plan: docs/superpowers/plans/2026-05-28-azure-devops-07-labels.md.

Out of scope

  • The vcs/services.py dispatcher wiring that queues apply_azure_devops_label / remove_azure_devops_label on FeatureExternalResource lifecycle events — lands in a later PR.

Test plan

  • make lint clean
  • make typecheck clean
  • make test opts='-n0 tests/unit/integrations/azure_devops/' — 219 passed (PR 6 baseline 198 + 27 new)
  • make test opts='tests/unit/integrations/gitlab tests/unit/integrations/github tests/unit/features/test_unit_feature_external_resources_views.py tests/unit/features/test_migrations.py' — adjacent-integration regression guard
  • make django-make-migrations opts='--check --dry-run' — no drift (PR 7 introduces no schema changes)

🤖 Generated with Claude Code

asaphko and others added 5 commits May 28, 2026 17:02
…gration

Seventh plan in the stacked-PRs rollout. Covers the labels service:
client functions for ADO PR labels and work-item tags (the latter via
read-modify-write on the System.Tags field), the services module with
apply/remove flagsmith-label functions gated by labeling_enabled, and
two task wrappers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
add_tag_to_pull_request POSTs to /_apis/git/pullrequests/{id}/labels
(project-scoped form — no repository GUID required). Idempotent on the
ADO side.

remove_tag_from_pull_request DELETEs and swallows 404 (label already
gone is the desired terminal state).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ADO work items expose tags via a single System.Tags field
(semicolon-separated string), not via a dedicated API. add_tag_to_work_item
and remove_tag_from_work_item implement the GET-then-PATCH dance with
two private helpers (_get_work_item_tags, _patch_work_item_tags). Both
public functions are idempotent — they no-op when the desired terminal
state already holds.

Also generalises _ado_request to accept a content_type kwarg (ADO
requires "application/json-patch+json" for work-item PATCH bodies)
and widens json_body's typing to accept list bodies (JSON Patch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two public functions:
- apply_flagsmith_label_to_resource(resource) parses the URL, dispatches
  to the right client function (PR labels API or work-item Tags PATCH),
  and applies the "flagsmith" tag.
- remove_flagsmith_label_from_resource(...) takes fields directly so it
  can be called from the unlink task after the FER row is gone.

Both are gated by labeling_enabled and never raise — requests.RequestException
is caught and logged as label.apply_failed / label.removal_failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two @register_task_handler() decorated wrappers:
- apply_azure_devops_label(resource_id): loads the FER and forwards to
  apply_flagsmith_label_to_resource.
- remove_azure_devops_label(project_id, resource_url, resource_type):
  takes fields directly (FER may be gone by run time).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment May 28, 2026 4:24pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ignored Ignored May 28, 2026 4:24pm
flagsmith-frontend-staging Ignored Ignored May 28, 2026 4:24pm

Request Review

@github-actions github-actions Bot added api Issue related to the REST API docs Documentation updates feature New feature or request and removed docs Documentation updates labels May 28, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the Azure DevOps labeling layer, adding client functions, a service module, and task wrappers to apply and remove the 'flagsmith' label or tag on pull requests and work items. The feedback recommends optimizing database queries by using 'select_related' when fetching configurations and external resources, and improving error handling by explicitly catching custom 'AzureDevOpsError' exceptions to prevent task processor crashes.

Comment on lines +8 to +13
from integrations.azure_devops.client import (
add_tag_to_pull_request,
add_tag_to_work_item,
remove_tag_from_pull_request,
remove_tag_from_work_item,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Import AzureDevOpsError to allow catching custom client exceptions when applying or removing labels, preventing unhandled exceptions from crashing the task processor.

Suggested change
from integrations.azure_devops.client import (
add_tag_to_pull_request,
add_tag_to_work_item,
remove_tag_from_pull_request,
remove_tag_from_work_item,
)
from integrations.azure_devops.client import (
AzureDevOpsError,
add_tag_to_pull_request,
add_tag_to_work_item,
remove_tag_from_pull_request,
remove_tag_from_work_item,
)

Comment on lines +28 to +30
config: AzureDevOpsConfiguration | None = AzureDevOpsConfiguration.objects.filter(
project_id=project_id
).first()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Use select_related("project") to prefetch the project relation. This avoids an additional database query when accessing config.project.organisation_id later in the logging context binding.

Suggested change
config: AzureDevOpsConfiguration | None = AzureDevOpsConfiguration.objects.filter(
project_id=project_id
).first()
config: AzureDevOpsConfiguration | None = AzureDevOpsConfiguration.objects.select_related("project").filter(
project_id=project_id
).first()

tag=AZURE_DEVOPS_FLAGSMITH_LABEL,
)
log.info("label.applied", ado__resource__id=work_ref.work_item_id)
except requests.RequestException:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Catch AzureDevOpsError in addition to requests.RequestException. Since custom exceptions like AzureDevOpsAuthError and AzureDevOpsNotFoundError are raised by the client and do not inherit from requests.RequestException, they must be explicitly caught to prevent task crashes.

Suggested change
except requests.RequestException:
except (requests.RequestException, AzureDevOpsError):

tag=AZURE_DEVOPS_FLAGSMITH_LABEL,
)
log.info("label.removed", ado__resource__id=work_ref.work_item_id)
except requests.RequestException:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Catch AzureDevOpsError in addition to requests.RequestException to ensure custom client exceptions are safely handled and do not crash the task processor during label removal.

Suggested change
except requests.RequestException:
except (requests.RequestException, AzureDevOpsError):

Dispatched at link time. No-op if labelling is disabled.
"""
try:
resource = FeatureExternalResource.objects.get(id=resource_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Use select_related("feature") when fetching the FeatureExternalResource. This avoids a lazy-loading database query when apply_flagsmith_label_to_resource accesses resource.feature.project_id.

Suggested change
resource = FeatureExternalResource.objects.get(id=resource_id)
resource = FeatureExternalResource.objects.select_related("feature").get(id=resource_id)

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 97.39130% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.53%. Comparing base (e8ae15b) to head (26a5fd4).

Files with missing lines Patch % Lines
api/integrations/azure_devops/services/labels.py 89.09% 6 Missing ⚠️
Additional details and impacted files
@@                        Coverage Diff                        @@
##           feat/azure-devops-06-comments    #7632      +/-   ##
=================================================================
- Coverage                          98.53%   98.53%   -0.01%     
=================================================================
  Files                               1476     1478       +2     
  Lines                              56329    56559     +230     
=================================================================
+ Hits                               55504    55728     +224     
- Misses                               825      831       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

api Issue related to the REST API feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants