Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions src/sentry/integrations/vsts/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from sentry.integrations.types import IntegrationProviderSlug
from sentry.integrations.utils.metrics import IntegrationWebhookEvent, IntegrationWebhookEventType
from sentry.integrations.utils.sync import sync_group_assignee_inbound
from sentry.integrations.utils.webhook_viewer_context import webhook_viewer_context
from sentry.ratelimits.config import RateLimitConfig
from sentry.types.ratelimit import RateLimit, RateLimitCategory
from sentry.utils.email import parse_email
Expand Down Expand Up @@ -173,24 +174,25 @@ def handle_status_change(
"status_change": status_change,
}
for org_integration in org_integrations:
installation = integration.get_installation(
organization_id=org_integration.organization_id
)
if isinstance(installation, IssueSyncIntegration):
installation.sync_status_inbound(
external_issue_key,
{
"new_state": status_change["newValue"],
# old_state is None when the issue is New
"old_state": status_change.get("oldValue"),
"project": project,
},
)
else:
lifecycle.record_halt(
ProjectManagementHaltReason.SYNC_NON_SYNC_INTEGRATION_PROVIDED,
extra=logging_context,
with webhook_viewer_context(org_integration.organization_id):
installation = integration.get_installation(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

will we eventually add the installation token to the viewer context as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We could if it's worthwhile to be able to fetch it directly from VC, but may also be too webhook-specific

organization_id=org_integration.organization_id
)
if isinstance(installation, IssueSyncIntegration):
installation.sync_status_inbound(
external_issue_key,
{
"new_state": status_change["newValue"],
# old_state is None when the issue is New
"old_state": status_change.get("oldValue"),
"project": project,
},
)
else:
lifecycle.record_halt(
ProjectManagementHaltReason.SYNC_NON_SYNC_INTEGRATION_PROVIDED,
extra=logging_context,
)


def handle_updated_workitem(data: Mapping[str, Any], integration: RpcIntegration) -> None:
Expand Down
48 changes: 48 additions & 0 deletions tests/sentry/integrations/vsts/test_webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
from sentry.silo.base import SiloMode
from sentry.testutils.asserts import assert_failure_metric, assert_success_metric
from sentry.testutils.cases import APITestCase
from sentry.testutils.helpers.options import override_options
from sentry.testutils.silo import assume_test_silo_mode
from sentry.users.models.identity import Identity
from sentry.utils.http import absolute_uri
from sentry.viewer_context import ActorType, get_viewer_context


class VstsWebhookWorkItemTest(APITestCase):
Expand Down Expand Up @@ -295,3 +297,49 @@ def test_inbound_status_sync_new_workitem(self) -> None:
assert Group.objects.get(id=group.id).status == GroupStatus.UNRESOLVED
# no change happened. no activity should be created here
assert len(Activity.objects.filter(group_id=group.id)) == 0

@responses.activate
@override_options({"viewer-context.enabled": True})
def test_status_change_sets_viewer_context(self) -> None:
"""ViewerContext is set with correct org_id and actor_type during status sync."""
captured_contexts: list = []

original_sync = VstsIntegration.sync_status_inbound

def capturing_sync(self_integration, *args, **kwargs):
captured_contexts.append(get_viewer_context())
return original_sync(self_integration, *args, **kwargs)

work_item_id = 33
ExternalIssue.objects.create(
organization_id=self.organization.id,
integration_id=self.model.id,
key=work_item_id,
)

responses.add(
responses.GET,
"https://instance.visualstudio.com/c0bf429a-c03c-4a99-9336-d45be74db5a6/_apis/wit/workitemtypes/Bug/states",
json=WORK_ITEM_STATES,
)

work_item = self.set_workitem_state("Active", "Resolved")

with (
patch.object(VstsIntegration, "sync_status_inbound", capturing_sync),
self.feature("organizations:integrations-issue-sync"),
self.tasks(),
):
resp = self.client.post(
absolute_uri("/extensions/vsts/issue-updated/"),
data=work_item,
HTTP_SHARED_SECRET=self.shared_secret,
)

assert resp.status_code == 200
assert len(captured_contexts) == 1
ctx = captured_contexts[0]
assert ctx is not None
assert ctx.organization_id == self.organization.id
assert ctx.actor_type == ActorType.INTEGRATION
assert ctx.user_id is None
Loading