Skip to content

Commit 58398b3

Browse files
authored
ref(stacktrace-link): Transaction tags to stacktrace linking (#22471)
* ref(stacktrace-link): Transaction tags to stacktrace linking
1 parent 7bf28ee commit 58398b3

File tree

8 files changed

+104
-47
lines changed

8 files changed

+104
-47
lines changed

src/sentry/api/endpoints/project_stacktrace_link.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from sentry.models import Integration, RepositoryProjectPathConfig
77
from sentry.api.serializers import serialize
88
from sentry.utils.compat import filter
9-
from sentry.web.decorators import transaction_start
9+
10+
from sentry_sdk import configure_scope
1011

1112

1213
def get_link(config, filepath, default, version=None):
@@ -21,24 +22,24 @@ def get_link(config, filepath, default, version=None):
2122

2223
class ProjectStacktraceLinkEndpoint(ProjectEndpoint):
2324
"""
24-
Returns valid links for source code providers so that
25-
users can go from the file in the stack trace to the
26-
provider of their choice.
25+
Returns valid links for source code providers so that
26+
users can go from the file in the stack trace to the
27+
provider of their choice.
2728
28-
`filepath`: The file path from the strack trace
29-
`commitId` (optional): The commit_id for the last commit of the
30-
release associated to the stack trace's event
29+
`filepath`: The file path from the strack trace
30+
`commitId` (optional): The commit_id for the last commit of the
31+
release associated to the stack trace's event
3132
3233
"""
3334

34-
@transaction_start("ProjectStacktraceLinkEndpoint")
3535
def get(self, request, project):
3636
# should probably feature gate
3737
filepath = request.GET.get("file")
3838
if not filepath:
3939
return Response({"detail": "Filepath is required"}, status=400)
4040

4141
commitId = request.GET.get("commitId")
42+
platform = request.GET.get("platform")
4243
result = {"config": None, "sourceUrl": None}
4344

4445
integrations = Integration.objects.filter(organizations=project.organization_id)
@@ -53,24 +54,34 @@ def get(self, request, project):
5354
# sure that we are still ordering by `id` because we want to make sure
5455
# the ordering is deterministic
5556
configs = RepositoryProjectPathConfig.objects.filter(project=project)
56-
57-
for config in configs:
58-
result["config"] = serialize(config, request.user)
59-
60-
if not filepath.startswith(config.stack_root):
61-
result["error"] = "stack_root_mismatch"
62-
continue
63-
64-
link = get_link(config, filepath, config.default_branch, commitId)
65-
66-
# it's possible for the link to be None, and in that
67-
# case it means we could not find a match for the
68-
# configuration
69-
result["sourceUrl"] = link
70-
if not link:
71-
result["error"] = "file_not_found"
72-
73-
# if we found a match, we can break
74-
break
57+
with configure_scope() as scope:
58+
for config in configs:
59+
60+
result["config"] = serialize(config, request.user)
61+
# use the provider key to be able to spilt up stacktrace
62+
# link metrics by integration type
63+
provider = result["config"]["provider"]["key"]
64+
scope.set_tag("integration_provider", provider)
65+
scope.set_tag("stacktrace_link.platform", platform)
66+
67+
if not filepath.startswith(config.stack_root):
68+
scope.set_tag("stacktrace_link.error", "stack_root_mismatch")
69+
result["error"] = "stack_root_mismatch"
70+
continue
71+
72+
link = get_link(config, filepath, config.default_branch, commitId)
73+
74+
# it's possible for the link to be None, and in that
75+
# case it means we could not find a match for the
76+
# configuration
77+
result["sourceUrl"] = link
78+
if not link:
79+
scope.set_tag("stacktrace_link.found", False)
80+
scope.set_tag("stacktrace_link.error", "file_not_found")
81+
result["error"] = "file_not_found"
82+
else:
83+
scope.set_tag("stacktrace_link.found", True)
84+
# if we found a match, we can break
85+
break
7586

7687
return Response(result)

src/sentry/integrations/github/client.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from sentry.integrations.github.utils import get_jwt
66
from sentry.integrations.client import ApiClient
7-
from sentry.web.decorators import transaction_start
87

98

109
class GitHubClientMixin(ApiClient):
@@ -113,7 +112,6 @@ def create_token(self):
113112
},
114113
)
115114

116-
@transaction_start("GitHubClientMixin.check_file")
117115
def check_file(self, repo, path, version):
118116
repo_name = repo.name
119117
return self.head_cached(

src/sentry/integrations/repositories.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from sentry.constants import ObjectStatus
44
from sentry.models import Repository
55
from sentry.shared_integrations.exceptions import ApiError
6+
from sentry_sdk import configure_scope
67

78

89
class RepositoryMixin(object):
@@ -49,12 +50,16 @@ def get_stacktrace_link(self, repo, filepath, default, version):
4950
If no file was found return `None`, and re-raise for non "Not Found" errors
5051
5152
"""
52-
if version:
53-
source_url = self.check_file(repo, filepath, version)
54-
if source_url:
55-
return source_url
56-
57-
source_url = self.check_file(repo, filepath, default)
53+
with configure_scope() as scope:
54+
scope.set_tag("stacktrace_link.tried_version", False)
55+
if version:
56+
scope.set_tag("stacktrace_link.tried_version", True)
57+
source_url = self.check_file(repo, filepath, version)
58+
if source_url:
59+
scope.set_tag("stacktrace_link.used_version", True)
60+
return source_url
61+
scope.set_tag("stacktrace_link.used_version", False)
62+
source_url = self.check_file(repo, filepath, default)
5863

5964
return source_url
6065

src/sentry/static/sentry/app/components/events/interfaces/stacktraceLink.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,12 @@ class StacktraceLink extends AsyncComponent<Props, State> {
8181
throw new Error('Unable to find project');
8282
}
8383
const commitId = event.release?.lastCommit?.id;
84+
const platform = event.platform;
8485
return [
8586
[
8687
'match',
8788
`/projects/${organization.slug}/${project.slug}/stacktrace-link/`,
88-
{query: {file: frame.filename, commitId}},
89+
{query: {file: frame.filename, platform, commitId}},
8990
],
9091
];
9192
}
@@ -114,6 +115,7 @@ class StacktraceLink extends AsyncComponent<Props, State> {
114115
);
115116
}
116117
}
118+
117119
onReconfigureMapping() {
118120
const provider = this.config?.provider;
119121
const error = this.match.error;
@@ -149,13 +151,24 @@ class StacktraceLink extends AsyncComponent<Props, State> {
149151
renderNoMatch() {
150152
const {organization} = this.props;
151153
const filename = this.props.frame.filename;
154+
const platform = this.props.event.platform;
152155

153156
if (this.project && this.integrations.length > 0 && filename) {
154157
return (
155158
<CodeMappingButtonContainer columnQuantity={2}>
156159
{t('Enable source code stack trace linking by setting up a code mapping.')}
157160
<Button
158-
onClick={() =>
161+
onClick={() => {
162+
trackIntegrationEvent(
163+
{
164+
eventKey: 'integrations.stacktrace_start_setup',
165+
eventName: 'Integrations: Stacktrace Start Setup',
166+
view: 'stacktrace_issue_details',
167+
platform,
168+
},
169+
this.props.organization,
170+
{startSession: true}
171+
);
159172
openModal(
160173
deps =>
161174
this.project && (
@@ -168,8 +181,8 @@ class StacktraceLink extends AsyncComponent<Props, State> {
168181
{...deps}
169182
/>
170183
)
171-
)
172-
}
184+
);
185+
}}
173186
size="xsmall"
174187
>
175188
{t('Setup Stack Trace Linking')}

src/sentry/static/sentry/app/components/events/interfaces/stacktraceLinkModal.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {IconInfo} from 'app/icons';
1111
import {t, tct} from 'app/locale';
1212
import space from 'app/styles/space';
1313
import {Integration, Organization, Project} from 'app/types';
14-
import {getIntegrationIcon} from 'app/utils/integrationUtil';
14+
import {getIntegrationIcon, trackIntegrationEvent} from 'app/utils/integrationUtil';
1515
import InputField from 'app/views/settings/components/forms/inputField';
1616

1717
type Props = AsyncComponent['props'] &
@@ -41,9 +41,31 @@ class StacktraceLinkModal extends AsyncComponent<Props, State> {
4141
});
4242
}
4343

44+
onManualSetup(provider: string) {
45+
trackIntegrationEvent(
46+
{
47+
eventKey: 'integrations.stacktrace_manual_setup',
48+
eventName: 'Integrations: Stacktrace Manual Setup',
49+
view: 'stacktrace_issue_details',
50+
provider,
51+
},
52+
this.props.organization,
53+
{startSession: true}
54+
);
55+
}
56+
4457
handleSubmit = async () => {
4558
const {sourceCodeInput} = this.state;
4659
const {organization, filename, project} = this.props;
60+
trackIntegrationEvent(
61+
{
62+
eventKey: 'integrations.stacktrace_automatic_setup',
63+
eventName: 'Integrations: Stacktrace Automatic Setup',
64+
view: 'stacktrace_issue_details',
65+
},
66+
this.props.organization,
67+
{startSession: true}
68+
);
4769

4870
const parsingEndpoint = `/projects/${organization.slug}/${project.slug}/repo-path-parsing/`;
4971
try {
@@ -133,6 +155,7 @@ class StacktraceLinkModal extends AsyncComponent<Props, State> {
133155
<Button
134156
key={integration.id}
135157
type="button"
158+
onClick={() => this.onManualSetup(integration.provider.key)}
136159
to={`${baseUrl}/${integration.provider.key}/${integration.id}/?tab=codeMappings`}
137160
>
138161
{getIntegrationIcon(integration.provider.key)}

src/sentry/static/sentry/app/utils/integrationUtil.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,18 @@ type IntegrationCategorySelectEvent = {
113113

114114
type IntegrationStacktraceLinkEvent = {
115115
eventKey:
116+
| 'integrations.stacktrace_start_setup'
117+
| 'integrations.stacktrace_automatic_setup'
118+
| 'integrations.stacktrace_manual_setup'
116119
| 'integrations.stacktrace_link_clicked'
117120
| 'integrations.reconfigure_stacktrace_setup';
118121
eventName:
122+
| 'Integrations: Stacktrace Start Setup'
123+
| 'Integrations: Stacktrace Automatic Setup'
124+
| 'Integrations: Stacktrace Manual Setup'
119125
| 'Integrations: Stacktrace Link Clicked'
120126
| 'Integrations: Reconfigure Stacktrace Setup';
121-
provider: string;
127+
provider?: string;
122128
error_reason?: 'file_not_found' | 'stack_root_mismatch';
123129
};
124130

tests/js/spec/components/events/interfaces/stacktraceLink.spec.jsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ describe('StacktraceLink', function () {
1212
const repo = TestStubs.Repository({integrationId: integration.id});
1313

1414
const frame = {filename: '/sentry/app.py', lineNo: 233};
15+
const platform = 'python';
1516
const config = TestStubs.RepositoryProjectPathConfig(project, repo, integration);
1617

1718
beforeEach(function () {
@@ -21,7 +22,7 @@ describe('StacktraceLink', function () {
2122
it('renders setup CTA with integration but no configs', async function () {
2223
MockApiClient.addMockResponse({
2324
url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
24-
query: {file: frame.filename, commitId: 'master'},
25+
query: {file: frame.filename, commitId: 'master', platform},
2526
body: {config: null, sourceUrl: null, integrations: [integration]},
2627
});
2728
mountWithTheme(
@@ -39,7 +40,7 @@ describe('StacktraceLink', function () {
3940
it('renders source url link', async function () {
4041
MockApiClient.addMockResponse({
4142
url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
42-
query: {file: frame.filename, commitId: 'master'},
43+
query: {file: frame.filename, commitId: 'master', platform},
4344
body: {config, sourceUrl: 'https://something.io', integrations: [integration]},
4445
});
4546
const wrapper = mountWithTheme(
@@ -59,7 +60,7 @@ describe('StacktraceLink', function () {
5960
it('renders file_not_found message', async function () {
6061
MockApiClient.addMockResponse({
6162
url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
62-
query: {file: frame.filename, commitId: 'master'},
63+
query: {file: frame.filename, commitId: 'master', platform},
6364
body: {
6465
config,
6566
sourceUrl: null,
@@ -86,7 +87,7 @@ describe('StacktraceLink', function () {
8687
it('renders stack_root_mismatch message', async function () {
8788
MockApiClient.addMockResponse({
8889
url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
89-
query: {file: frame.filename, commitId: 'master'},
90+
query: {file: frame.filename, commitId: 'master', platform},
9091
body: {
9192
config,
9293
sourceUrl: null,

tests/js/spec/components/events/interfaces/stacktraceLinkModal.spec.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('StacktraceLinkModal', function () {
6262

6363
MockApiClient.addMockResponse({
6464
url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
65-
query: {file: filename, commitId: 'master'},
65+
query: {file: filename, commitId: 'master', platform: 'python'},
6666
body: {config, sourceUrl, integrations: [integration]},
6767
});
6868

@@ -111,7 +111,7 @@ describe('StacktraceLinkModal', function () {
111111

112112
MockApiClient.addMockResponse({
113113
url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
114-
query: {file: filename, commitId: 'master'},
114+
query: {file: filename, commitId: 'master', platform: 'python'},
115115
body: {config, sourceUrl, integrations: [integration]},
116116
});
117117

0 commit comments

Comments
 (0)