Skip to content

Commit 4ba4369

Browse files
committed
display button and link
1 parent 8ba346f commit 4ba4369

File tree

4 files changed

+163
-3
lines changed

4 files changed

+163
-3
lines changed

src/sentry/api/serializers/models/sentry_app_component.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@ def serialize(self, obj, attrs, user):
1313
'uuid': six.binary_type(obj.uuid),
1414
'type': obj.type,
1515
'schema': obj.schema,
16-
'sentryAppId': obj.sentry_app_id,
16+
'sentryApp': {
17+
'uuid': obj.sentry_app.uuid,
18+
'slug': obj.sentry_app.slug,
19+
'name': obj.sentry_app.name,
20+
}
1721
}

src/sentry/static/sentry/app/components/events/interfaces/frame.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import StrictClick from 'app/components/strictClick';
1616
import Tooltip from 'app/components/tooltip';
1717
import Truncate from 'app/components/truncate';
1818
import space from 'app/styles/space';
19+
import OpenInButton from 'app/components/events/interfaces/openInButton';
1920

2021
export function trimPackage(pkg) {
2122
const pieces = pkg.split(/^([a-z]:\\|\\\\)/i.test(pkg) ? '\\' : '/');
@@ -260,8 +261,14 @@ const Frame = createReactClass({
260261

261262
{data.context &&
262263
contextLines.map((line, index) => {
264+
const isActive = data.lineNo === line[0];
263265
return (
264-
<ContextLine key={index} line={line} isActive={data.lineNo === line[0]} />
266+
<React.Fragment key={index}>
267+
<ContextLine key={index} line={line} isActive={isActive} />
268+
{isActive && (
269+
<OpenInButton filename={data.filename} lineNo={data.lineNo} />
270+
)}
271+
</React.Fragment>
265272
);
266273
})}
267274

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import SentryTypes from 'app/sentryTypes';
4+
import Button from 'app/components/button';
5+
import styled from 'react-emotion';
6+
7+
8+
import withApi from 'app/utils/withApi';
9+
import withLatestContext from 'app/utils/withLatestContext';
10+
11+
class OpenInButton extends React.Component {
12+
static propTypes = {
13+
api: PropTypes.object,
14+
organization: SentryTypes.Organization,
15+
project: SentryTypes.Project,
16+
lineNo: PropTypes.number,
17+
filename: PropTypes.string,
18+
};
19+
20+
constructor(props) {
21+
super(props);
22+
this.state = {
23+
loading: false,
24+
error: false,
25+
components: [],
26+
sentryApps: [],
27+
installs: [],
28+
};
29+
}
30+
31+
componentWillMount() {
32+
this.fetchIssueLinkComponents();
33+
}
34+
35+
fetchIssueLinkComponents() {
36+
const {api, organization, project} = this.props;
37+
api
38+
.requestPromise(
39+
`/organizations/${organization.slug}/sentry-app-components/?filter=stracktrace-link&projectId=${project.id}`
40+
)
41+
.then(data => {
42+
if (data.length) {
43+
this.fetchInstallations();
44+
this.setState({components: data});
45+
}
46+
})
47+
.catch(error => {
48+
return;
49+
});
50+
}
51+
52+
fetchInstallations() {
53+
const {api, organization} = this.props;
54+
api
55+
.requestPromise(`/organizations/${organization.slug}/sentry-app-installations/`)
56+
.then(data => {
57+
if (data.length) {
58+
this.fetchSentryApps();
59+
this.setState({installs: data});
60+
}
61+
})
62+
.catch(error => {
63+
return;
64+
});
65+
}
66+
67+
fetchSentryApps() {
68+
const {api, organization} = this.props;
69+
api
70+
.requestPromise(`/organizations/${organization.slug}/sentry-apps/`)
71+
.then(data => {
72+
this.setState({sentryApps: data});
73+
})
74+
.catch(error => {
75+
return;
76+
});
77+
}
78+
79+
getInstallforComponent(component) {
80+
const appId = component.sentryApp.uuid;
81+
return this.state.installs.filter(install => install.app.uuid == appId)[0];
82+
}
83+
84+
getAppforComponent(component) {
85+
const appId = component.sentryApp.uuid;
86+
return this.state.sentryApps.filter(app => app.uuid == appId)[0];
87+
}
88+
89+
getLinkUrl() {
90+
//won't need to filter once other PR is in
91+
const components = (this.state.components || []).filter(
92+
c => c.type == 'stacktrace-link'
93+
);
94+
const {filename, lineNo, project} = this.props;
95+
96+
if (components.length) {
97+
const uri = components[0].schema.uri;
98+
const install = this.getInstallforComponent(components[0]);
99+
const sentryApp = this.getAppforComponent(components[0]);
100+
const baseUrl = sentryApp.webhookUrl;
101+
const file = encodeURIComponent(filename);
102+
return `${baseUrl}${uri}?filename=${file}&lineNo=${lineNo}&project=${project.slug}&installationId=${install.uuid}`;
103+
}
104+
return '';
105+
}
106+
107+
getName() {
108+
const {components} = this.state;
109+
if (components.length) {
110+
return components[0].sentryApp.name;
111+
}
112+
return '';
113+
}
114+
115+
render() {
116+
const {components, installs, sentryApps} = this.state;
117+
if (!components.length || !installs.length || !sentryApps.length) {
118+
return null;
119+
}
120+
121+
const url = this.getLinkUrl();
122+
const name = this.getName();
123+
return (
124+
<StyledButtonContainer>
125+
<StyledButton href={url} size="small" priority="primary">
126+
{`Debug In ${name}`}
127+
</StyledButton>
128+
</StyledButtonContainer>
129+
);
130+
}
131+
}
132+
133+
const OpenInButtonComponent = withLatestContext(OpenInButton);
134+
export default withApi(OpenInButtonComponent);
135+
136+
const StyledButtonContainer = styled('div')`
137+
height: 0;
138+
position: relative;
139+
`;
140+
141+
const StyledButton = styled(Button)`
142+
position: absolute;
143+
z-index: 1000;
144+
height: 36px;
145+
line-height: 1.5;
146+
padding: 0px 5px;
147+
top: -31px;
148+
right: 30px;
149+
`;

src/sentry/static/sentry/app/data/forms/sentryApplication.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const forms = [
4949
autosize: true,
5050
help: 'Schema for your UI components',
5151
getValue: val => {
52-
return val == '' ? val : JSON.parse(val);
52+
return val == '' ? '' : JSON.parse(val);
5353
},
5454
setValue: val => {
5555
const schema = JSON.stringify(val, null, 2);

0 commit comments

Comments
 (0)