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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
OrganizationIntegrationBaseEndpoint,
)
from sentry.integrations.api.serializers.models.integration import OrganizationIntegrationResponse
from sentry.integrations.base import INTEGRATION_TYPE_TO_PROVIDER
from sentry.integrations.models.integration import Integration
from sentry.integrations.models.organization_integration import OrganizationIntegration
from sentry.organizations.services.organization.model import (
Expand Down Expand Up @@ -93,6 +94,7 @@ def get(
if provider_key is None:
provider_key = request.GET.get("provider_key", "")
include_config_raw = request.GET.get("includeConfig")
integration_type = request.GET.get("integrationType")

# Include the configurations by default if includeConfig is not present.
# TODO(mgaeta): HACK. We need a consistent way to get booleans from query parameters.
Expand All @@ -109,6 +111,18 @@ def get(
if provider_key:
queryset = queryset.filter(integration__provider=provider_key.lower())

if integration_type:
if integration_type not in INTEGRATION_TYPE_TO_PROVIDER:
return Response(
{"detail": "Invalid integration type"},
status=400,
)
provider_slugs = [
provider.value
for provider in INTEGRATION_TYPE_TO_PROVIDER.get(integration_type, [])
]
queryset = queryset.filter(integration__provider__in=provider_slugs)

def on_results(results: Sequence[OrganizationIntegration]) -> Sequence[Mapping[str, Any]]:
if feature_filters:
results = filter_by_features(results, feature_filters)
Expand Down
50 changes: 50 additions & 0 deletions src/sentry/integrations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,56 @@ class IntegrationFeatures(Enum):
DEPLOYMENT = "deployment"


# Integration Types
Copy link
Member

Choose a reason for hiding this comment

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

hmm i would love to do something so that when you register the integrations here

for integration_path in settings.SENTRY_DEFAULT_INTEGRATIONS:
try:
integration_cls = import_string(integration_path)
except Exception:
import traceback
click.echo(
f"Failed to load integration {integration_path!r}:\n{traceback.format_exc()}",
err=True,
)
else:
integrations.register(integration_cls)
for integration in integrations.all():
try:
integration.setup()
except AttributeError:
pass

you also register the integration type... cc @RyanSkonnord if you have thoughts

MESSAGING = "messaging"
PROJECT_MANAGEMENT = "project_management"
SOURCE_CODE_MANAGEMENT = "source_code_management"
ON_CALL_SCHEDULING = "on_call_scheduling"


class IntegrationProviderSlug(Enum):
SLACK = "slack"
DISCORD = "discord"
MSTeams = "msteams"
JIRA = "jira"
JIRA_SERVER = "jira_server"
AZURE_DEVOPS = "vsts"
GITHUB = "github"
GITHUB_ENTERPRISE = "github_enterprise"
GITLAB = "gitlab"
BITBUCKET = "bitbucket"
PAGERDUTY = "pagerduty"
OPSGENIE = "opsgenie"


INTEGRATION_TYPE_TO_PROVIDER = {
MESSAGING: [
IntegrationProviderSlug.SLACK,
IntegrationProviderSlug.DISCORD,
IntegrationProviderSlug.MSTeams,
],
PROJECT_MANAGEMENT: [
Copy link
Member

Choose a reason for hiding this comment

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

Many (or all) of source code management integrations are also project management ones. At least Github and Azure are. @cathteng can confirm which ones for sure.

Copy link
Member

Choose a reason for hiding this comment

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

are we grouping integrations by the main focus or by a particular feature? i would say the ticketing aspect of SCMs is a feature

Copy link
Member Author

Choose a reason for hiding this comment

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

@sentaur-athena @cathteng I just grouped them based on our integration feature list

Copy link
Member

Choose a reason for hiding this comment

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

@cathteng we can come back and update this list since it's not really used anywhere in code other than messaging. My thought is for example if we want to build onboarding and tell people we have these ticket management integrations we still want to return github in that list.

Copy link
Member

Choose a reason for hiding this comment

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

ok

IntegrationProviderSlug.JIRA,
IntegrationProviderSlug.JIRA_SERVER,
IntegrationProviderSlug.GITHUB,
IntegrationProviderSlug.GITHUB_ENTERPRISE,
IntegrationProviderSlug.GITLAB,
IntegrationProviderSlug.AZURE_DEVOPS,
],
SOURCE_CODE_MANAGEMENT: [
IntegrationProviderSlug.GITHUB,
IntegrationProviderSlug.GITHUB_ENTERPRISE,
IntegrationProviderSlug.GITLAB,
IntegrationProviderSlug.BITBUCKET,
IntegrationProviderSlug.AZURE_DEVOPS,
],
ON_CALL_SCHEDULING: [
IntegrationProviderSlug.PAGERDUTY,
IntegrationProviderSlug.OPSGENIE,
],
}


class IntegrationProvider(PipelineProvider, abc.ABC):
"""
An integration provider describes a third party that can be registered within Sentry.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,29 @@ def setUp(self):
name="Example",
external_id="example:1",
)
self.msteams_integration = self.create_integration(
organization=self.organization,
provider="msteams",
name="MS Teams",
external_id="msteams:1",
)
self.opsgenie = self.create_integration(
organization=self.organization,
provider="opsgenie",
name="Opsgenie",
external_id="opsgenie:1",
)
self.slack_integration = self.create_integration(
organization=self.organization,
provider="slack",
name="Slack",
external_id="slack:1",
)

def test_simple(self):
response = self.get_success_response(self.organization.slug)

assert len(response.data) == 1
assert len(response.data) == 4
assert response.data[0]["id"] == str(self.integration.id)
assert "configOrganization" in response.data[0]

Expand Down Expand Up @@ -51,3 +69,40 @@ def test_provider_key(self):
self.organization.slug, qs_params={"provider_key": "vercel"}
)
assert response.data == []

def test_integration_type(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: maybe add a test for what happens when u pass both providerKey and integrationType
also maybe throw docs in ur backlog

response = self.get_success_response(
self.organization.slug, qs_params={"integrationType": "messaging"}
)
assert len(response.data) == 2
assert response.data[0]["id"] == str(self.msteams_integration.id)
assert response.data[1]["id"] == str(self.slack_integration.id)
response = self.get_success_response(
self.organization.slug, qs_params={"integrationType": "on_call_scheduling"}
)
assert len(response.data) == 1
assert response.data[0]["id"] == str(self.opsgenie.id)
response = self.get_error_response(
self.organization.slug, qs_params={"integrationType": "third_party"}
)
assert response.data == {"detail": "Invalid integration type"}
assert response.status_code == 400

def test_provider_key_and_integration_type(self):
response = self.get_success_response(
self.organization.slug,
qs_params={"providerKey": "slack", "integrationType": "messaging"},
)
assert len(response.data) == 1
assert response.data[0]["id"] == str(self.slack_integration.id)
response = self.get_success_response(
self.organization.slug,
qs_params={"providerKey": "vercel", "integrationType": "messaging"},
)
assert response.data == []
response = self.get_error_response(
self.organization.slug,
qs_params={"providerKey": "slack", "integrationType": "third_party"},
)
assert response.data == {"detail": "Invalid integration type"}
assert response.status_code == 400
Loading