Skip to content

Commit c50f5ae

Browse files
armenzgandrewshie-sentry
authored andcommitted
ref(auto_source_config): Move logic around (#88251)
Changes included: * Move some of the endpoint logic to the centralized location * Create repository and platform modules
1 parent 2fa954d commit c50f5ae

File tree

15 files changed

+146
-88
lines changed

15 files changed

+146
-88
lines changed

src/sentry/integrations/source_code_management/repo_trees.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import Any, NamedTuple
77

88
from sentry.integrations.services.integration import RpcOrganizationIntegration
9-
from sentry.issues.auto_source_code_config.utils import get_supported_extensions
9+
from sentry.issues.auto_source_code_config.utils.platform import get_supported_extensions
1010
from sentry.shared_integrations.exceptions import ApiError, IntegrationError
1111
from sentry.utils import metrics
1212
from sentry.utils.cache import cache

src/sentry/issues/auto_source_code_config/code_mapping.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from .constants import METRIC_PREFIX
2222
from .integration_utils import InstallationNotFoundError, get_installation
23-
from .utils import PlatformConfig
23+
from .utils.platform import PlatformConfig
2424

2525
logger = logging.getLogger(__name__)
2626

@@ -74,7 +74,7 @@ def derive_code_mappings(
7474
trees_helper = CodeMappingTreesHelper(trees)
7575
try:
7676
frame_filename = FrameInfo(frame, platform)
77-
return trees_helper.list_file_matches(frame_filename)
77+
return trees_helper.get_file_and_repo_matches(frame_filename)
7878
except NeedsExtension:
7979
logger.warning("Needs extension: %s", frame.get("filename"))
8080

@@ -190,7 +190,7 @@ def generate_code_mappings(
190190

191191
return list(self.code_mappings.values())
192192

193-
def list_file_matches(self, frame_filename: FrameInfo) -> list[dict[str, str]]:
193+
def get_file_and_repo_matches(self, frame_filename: FrameInfo) -> list[dict[str, str]]:
194194
"""List all the files in a repo that match the frame_filename"""
195195
file_matches = []
196196
for repo_full_name in self.trees.keys():
@@ -425,33 +425,31 @@ def convert_stacktrace_frame_path_to_source_path(
425425

426426
def create_code_mapping(
427427
organization: Organization,
428+
code_mapping: CodeMapping,
428429
project: Project,
429-
stacktrace_root: str,
430-
source_path: str,
431-
repo_name: str,
432-
branch: str,
433430
) -> RepositoryProjectPathConfig:
434431
installation = get_installation(organization)
435432
# It helps with typing since org_integration can be None
436433
if not installation.org_integration:
437434
raise InstallationNotFoundError
438435

439436
repository, _ = Repository.objects.get_or_create(
440-
name=repo_name,
437+
name=code_mapping.repo.name,
441438
organization_id=organization.id,
442439
defaults={"integration_id": installation.model.id},
443440
)
444441
new_code_mapping, _ = RepositoryProjectPathConfig.objects.update_or_create(
445442
project=project,
446-
stack_root=stacktrace_root,
443+
stack_root=code_mapping.stacktrace_root,
447444
defaults={
448445
"repository": repository,
449446
"organization_id": organization.id,
450447
"integration_id": installation.model.id,
451448
"organization_integration_id": installation.org_integration.id,
452-
"source_root": source_path,
453-
"default_branch": branch,
454-
"automatically_generated": True,
449+
"source_root": code_mapping.source_path,
450+
"default_branch": code_mapping.repo.branch,
451+
# This function is called from the UI, thus, we know that the code mapping is user generated
452+
"automatically_generated": False,
455453
},
456454
)
457455

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import logging
2+
3+
from rest_framework.request import Request
4+
5+
from sentry.integrations.source_code_management.repo_trees import (
6+
RepoAndBranch,
7+
RepoTreesIntegration,
8+
)
9+
from sentry.models.organization import Organization
10+
11+
from .code_mapping import CodeMapping, CodeMappingTreesHelper, FrameInfo
12+
from .integration_utils import get_installation
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
def get_file_and_repo_matches(request: Request, organization: Organization) -> list[dict[str, str]]:
18+
frame_info = get_frame_info_from_request(request)
19+
installation = get_installation(organization)
20+
if not isinstance(installation, RepoTreesIntegration):
21+
return []
22+
trees = installation.get_trees_for_org()
23+
trees_helper = CodeMappingTreesHelper(trees)
24+
return trees_helper.get_file_and_repo_matches(frame_info)
25+
26+
27+
def get_frame_info_from_request(request: Request) -> FrameInfo:
28+
frame = {
29+
"absPath": request.GET.get("absPath"),
30+
# Currently, the only required parameter, thus, avoiding the `get` method
31+
"filename": request.GET["stacktraceFilename"],
32+
"module": request.GET.get("module"),
33+
}
34+
return FrameInfo(frame, request.GET.get("platform"))
35+
36+
37+
def get_code_mapping_from_request(request: Request) -> CodeMapping:
38+
return CodeMapping(
39+
repo=RepoAndBranch(
40+
name=request.data["repoName"],
41+
branch=request.data["defaultBranch"],
42+
),
43+
stacktrace_root=request.data["stackRoot"],
44+
source_path=request.data["sourceRoot"],
45+
)

src/sentry/issues/auto_source_code_config/in_app_stack_trace_rules.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from collections.abc import Sequence
33

44
from sentry.issues.auto_source_code_config.code_mapping import CodeMapping
5-
from sentry.issues.auto_source_code_config.utils import PlatformConfig
5+
from sentry.issues.auto_source_code_config.utils.platform import PlatformConfig
66
from sentry.models.project import Project
77
from sentry.utils import metrics
88

src/sentry/issues/auto_source_code_config/stacktraces.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sentry.db.models.fields.node import NodeData
77
from sentry.utils.safe import get_path
88

9-
from .utils import PlatformConfig
9+
from .utils.platform import PlatformConfig
1010

1111
logger = logging.getLogger(__name__)
1212

src/sentry/issues/auto_source_code_config/task.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
get_installation,
3333
)
3434
from .stacktraces import get_frames_to_process
35-
from .utils import PlatformConfig
35+
from .utils.platform import PlatformConfig
36+
from .utils.repository import create_repository
3637

3738
logger = logging.getLogger(__name__)
3839

@@ -202,29 +203,6 @@ def create_configurations(
202203
return code_mappings, in_app_stack_trace_rules
203204

204205

205-
def create_repository(
206-
repo_name: str, org_integration: RpcOrganizationIntegration, tags: Mapping[str, str | bool]
207-
) -> Repository | None:
208-
organization_id = org_integration.organization_id
209-
created = False
210-
repository = (
211-
Repository.objects.filter(name=repo_name, organization_id=organization_id)
212-
.order_by("-date_added")
213-
.first()
214-
)
215-
if not repository:
216-
if not tags["dry_run"]:
217-
repository, created = Repository.objects.get_or_create(
218-
name=repo_name,
219-
organization_id=organization_id,
220-
integration_id=org_integration.integration_id,
221-
)
222-
if created or tags["dry_run"]:
223-
metrics.incr(key=f"{METRIC_PREFIX}.repository.created", tags=tags, sample_rate=1.0)
224-
225-
return repository
226-
227-
228206
def create_code_mapping(
229207
code_mapping: CodeMapping,
230208
repository: Repository | None,

src/sentry/issues/auto_source_code_config/utils/__init__.py

Whitespace-only changes.

src/sentry/issues/auto_source_code_config/utils.py renamed to src/sentry/issues/auto_source_code_config/utils/platform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from sentry import features
44
from sentry.models.organization import Organization
55

6-
from .constants import PLATFORMS_CONFIG
6+
from ..constants import PLATFORMS_CONFIG
77

88

99
def supported_platform(platform: str) -> bool:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from collections.abc import Mapping
2+
3+
from sentry.integrations.services.integration.model import RpcOrganizationIntegration
4+
from sentry.models.repository import Repository
5+
from sentry.utils import metrics
6+
7+
from ..constants import METRIC_PREFIX
8+
9+
10+
def create_repository(
11+
repo_name: str, org_integration: RpcOrganizationIntegration, tags: Mapping[str, str | bool]
12+
) -> Repository | None:
13+
organization_id = org_integration.organization_id
14+
created = False
15+
repository = (
16+
Repository.objects.filter(name=repo_name, organization_id=organization_id)
17+
.order_by("-date_added")
18+
.first()
19+
)
20+
if not repository:
21+
if not tags["dry_run"]:
22+
repository, created = Repository.objects.get_or_create(
23+
name=repo_name,
24+
organization_id=organization_id,
25+
integration_id=org_integration.integration_id,
26+
)
27+
if created or tags["dry_run"]:
28+
metrics.incr(key=f"{METRIC_PREFIX}.repository.created", tags=tags, sample_rate=1.0)
29+
30+
return repository

src/sentry/issues/endpoints/organization_derive_code_mappings.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from typing import Literal
23

34
from rest_framework import status
@@ -12,17 +13,21 @@
1213
OrganizationIntegrationsLoosePermission,
1314
)
1415
from sentry.api.serializers import serialize
15-
from sentry.issues.auto_source_code_config.code_mapping import (
16-
create_code_mapping,
17-
derive_code_mappings,
16+
from sentry.issues.auto_source_code_config.code_mapping import NeedsExtension, create_code_mapping
17+
from sentry.issues.auto_source_code_config.derived_code_mappings_endpoint import (
18+
get_code_mapping_from_request,
19+
get_file_and_repo_matches,
1820
)
1921
from sentry.issues.auto_source_code_config.integration_utils import (
2022
InstallationCannotGetTreesError,
2123
InstallationNotFoundError,
24+
get_installation,
2225
)
2326
from sentry.models.organization import Organization
2427
from sentry.models.project import Project
2528

29+
logger = logging.getLogger(__name__)
30+
2631

2732
@region_silo_endpoint
2833
class OrganizationDeriveCodeMappingsEndpoint(OrganizationEndpoint):
@@ -33,39 +38,34 @@ class OrganizationDeriveCodeMappingsEndpoint(OrganizationEndpoint):
3338

3439
owner = ApiOwner.ISSUES
3540
publish_status = {
36-
"GET": ApiPublishStatus.UNKNOWN,
37-
"POST": ApiPublishStatus.UNKNOWN,
41+
"GET": ApiPublishStatus.EXPERIMENTAL,
42+
"POST": ApiPublishStatus.EXPERIMENTAL,
3843
}
3944
permission_classes = (OrganizationIntegrationsLoosePermission,)
4045

4146
def get(self, request: Request, organization: Organization) -> Response:
4247
"""
43-
Get all matches for a stacktrace filename.
48+
Get all files from the customer repositories that match a stack trace frame.
4449
``````````````````
4550
4651
:param organization:
52+
:param string absPath:
53+
:param string module:
4754
:param string stacktraceFilename:
4855
:param string platform:
4956
:auth: required
5057
"""
51-
stacktrace_filename = request.GET.get("stacktraceFilename")
52-
# XXX: The UI will need to pass the platform
53-
platform = request.GET.get("platform")
54-
5558
try:
56-
possible_code_mappings = []
59+
file_repo_matches = []
5760
resp_status: Literal[200, 204, 400] = status.HTTP_400_BAD_REQUEST
5861

59-
if stacktrace_filename:
60-
possible_code_mappings = derive_code_mappings(
61-
organization, {"filename": stacktrace_filename}, platform
62-
)
63-
if possible_code_mappings:
64-
resp_status = status.HTTP_200_OK
65-
else:
66-
resp_status = status.HTTP_204_NO_CONTENT
62+
file_repo_matches = get_file_and_repo_matches(request, organization)
63+
if file_repo_matches:
64+
resp_status = status.HTTP_200_OK
65+
else:
66+
resp_status = status.HTTP_204_NO_CONTENT
6767

68-
return Response(serialize(possible_code_mappings), status=resp_status)
68+
return self.respond(serialize(file_repo_matches), status=resp_status)
6969
except InstallationCannotGetTreesError:
7070
return self.respond(
7171
{"text": "The integration does not support getting trees"},
@@ -76,6 +76,12 @@ def get(self, request: Request, organization: Organization) -> Response:
7676
{"text": "Could not find this integration installed on your organization"},
7777
status=status.HTTP_404_NOT_FOUND,
7878
)
79+
except NeedsExtension:
80+
return self.respond({"text": "Needs extension"}, status=status.HTTP_400_BAD_REQUEST)
81+
except KeyError:
82+
return self.respond(
83+
{"text": "Missing required parameters"}, status=status.HTTP_400_BAD_REQUEST
84+
)
7985

8086
def post(self, request: Request, organization: Organization) -> Response:
8187
"""
@@ -100,19 +106,18 @@ def post(self, request: Request, organization: Organization) -> Response:
100106
if not request.access.has_project_access(project):
101107
return self.respond(status=status.HTTP_403_FORBIDDEN)
102108

103-
repo_name = request.data.get("repoName")
104-
stack_root = request.data.get("stackRoot")
105-
source_root = request.data.get("sourceRoot")
106-
branch = request.data.get("defaultBranch")
107-
if not repo_name or not stack_root or not source_root or not branch:
109+
try:
110+
installation = get_installation(organization)
111+
# It helps with typing since org_integration can be None
112+
if not installation.org_integration:
113+
raise InstallationNotFoundError
114+
115+
code_mapping = get_code_mapping_from_request(request)
116+
new_code_mapping = create_code_mapping(organization, code_mapping, project)
117+
except KeyError:
108118
return self.respond(
109119
{"text": "Missing required parameters"}, status=status.HTTP_400_BAD_REQUEST
110120
)
111-
112-
try:
113-
new_code_mapping = create_code_mapping(
114-
organization, project, stack_root, source_root, repo_name, branch
115-
)
116121
except InstallationNotFoundError:
117122
return self.respond(
118123
{"text": "Could not find this integration installed on your organization"},

0 commit comments

Comments
 (0)