Skip to content

Commit a7c1ba5

Browse files
committed
Generate proper link URLs for tag values in the event modal
1 parent 8608e2b commit a7c1ba5

File tree

6 files changed

+111
-68
lines changed

6 files changed

+111
-68
lines changed

src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ class EventDetails extends AsyncComponent {
1414
static propTypes = {
1515
params: PropTypes.object,
1616
eventSlug: PropTypes.string.isRequired,
17-
onTagSelect: PropTypes.func.isRequired,
1817
};
1918

2019
getEndpoints() {

src/sentry/static/sentry/app/views/organizationEventsV2/index.jsx

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
22
import DocumentTitle from 'react-document-title';
33
import PropTypes from 'prop-types';
4-
import {browserHistory} from 'react-router';
54

65
import {t} from 'app/locale';
76
import SentryTypes from 'app/sentryTypes';
@@ -21,26 +20,7 @@ import {getCurrentView} from './utils';
2120
export default class OrganizationEventsV2 extends React.Component {
2221
static propTypes = {
2322
organization: SentryTypes.Organization.isRequired,
24-
location: PropTypes.object,
25-
};
26-
27-
// Called by the event modal tag table when a tag value is clicked.
28-
handleTagSelect = tag => {
29-
const {location} = this.props;
30-
const query = {...location.query};
31-
// Add tag key/value to search
32-
if (query.query) {
33-
query.query += ` ${tag.key}:"${tag.value}"`;
34-
} else {
35-
query.query = `${tag.key}:"${tag.value}"`;
36-
}
37-
// Remove the event slug so the user sees new search results.
38-
delete query.eventSlug;
39-
40-
browserHistory.push({
41-
pathname: location.pathname,
42-
query,
43-
});
23+
location: PropTypes.object.isRequired,
4424
};
4525

4626
renderTabs() {
@@ -91,7 +71,6 @@ export default class OrganizationEventsV2 extends React.Component {
9171
orgId={organization.slug}
9272
params={this.props.params}
9373
eventSlug={eventSlug}
94-
onTagSelect={this.handleTagSelect}
9574
/>
9675
)}
9776
</PageContent>

src/sentry/static/sentry/app/views/organizationEventsV2/tagsTable.jsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import React from 'react';
22
import styled from 'react-emotion';
33
import PropTypes from 'prop-types';
4+
import {withRouter} from 'react-router';
5+
6+
import Link from 'app/components/links/link';
47
import {t} from 'app/locale';
58
import space from 'app/styles/space';
9+
import {eventTagSearchUrl} from './utils';
610

711
const TagsTable = props => {
8-
const handleSelect = tag => {
9-
return event => {
10-
event.preventDefault();
11-
props.onTagSelect(tag);
12-
};
13-
};
14-
1512
return (
1613
<div>
1714
<TagHeading>{t('Tags')}</TagHeading>
@@ -21,9 +18,7 @@ const TagsTable = props => {
2118
<StyledTr key={tag.key}>
2219
<TagKey>{tag.key}</TagKey>
2320
<TagValue>
24-
<a href="#" onClick={handleSelect(tag)}>
25-
{tag.value}
26-
</a>
21+
<Link to={eventTagSearchUrl(tag, props.location)}>{tag.value}</Link>
2722
</TagValue>
2823
</StyledTr>
2924
))}
@@ -34,7 +29,7 @@ const TagsTable = props => {
3429
};
3530
TagsTable.propTypes = {
3631
tags: PropTypes.array.isRequired,
37-
onTagSelect: PropTypes.func.isRequired,
32+
location: PropTypes.object,
3833
};
3934

4035
const TagHeading = styled('h5')`
@@ -60,4 +55,4 @@ const TagValue = styled(TagKey)`
6055
text-align: right;
6156
`;
6257

63-
export default TagsTable;
58+
export default withRouter(TagsTable);

src/sentry/static/sentry/app/views/organizationEventsV2/utils.jsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,28 @@ export function getQuery(view) {
3232

3333
return data;
3434
}
35+
36+
/**
37+
* Return a location object for the current pathname
38+
* with a query string reflected the provided tag.
39+
*
40+
* @param {Object} tag containing key/value properties
41+
* @param {Object} browser location object.
42+
* @return {Object} router target
43+
*/
44+
export function eventTagSearchUrl(tag, location) {
45+
const query = {...location.query};
46+
// Add tag key/value to search
47+
if (query.query) {
48+
query.query += ` ${tag.key}:"${tag.value}"`;
49+
} else {
50+
query.query = `${tag.key}:"${tag.value}"`;
51+
}
52+
// Remove the event slug so the user sees new search results.
53+
delete query.eventSlug;
54+
55+
return {
56+
pathname: location.pathname,
57+
query,
58+
};
59+
}

tests/js/spec/views/organizationEventsV2/index.spec.jsx

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import {mount} from 'enzyme';
3-
import {browserHistory} from 'react-router';
3+
import {initializeOrg} from 'app-test/helpers/initializeOrg';
44

55
import OrganizationEventsV2 from 'app/views/organizationEventsV2';
66

@@ -93,63 +93,71 @@ describe('OrganizationEventsV2', function() {
9393
});
9494

9595
it('navigates when tag values are clicked', async function() {
96-
const organization = TestStubs.Organization({projects: [TestStubs.Project()]});
96+
const {organization, routerContext} = initializeOrg({
97+
organization: TestStubs.Organization({projects: [TestStubs.Project()]}),
98+
router: {
99+
location: {
100+
pathname: '/organizations/org-slug/events/',
101+
query: {
102+
eventSlug: 'project-slug:deadbeef',
103+
},
104+
},
105+
},
106+
});
97107
const wrapper = mount(
98108
<OrganizationEventsV2
99109
organization={organization}
100-
location={{
101-
pathname: '/organizations/org-slug/events/',
102-
query: {eventSlug: 'project-slug:deadbeef'},
103-
}}
104110
params={{orgId: organization.slug}}
111+
location={routerContext.context.location}
105112
/>,
106-
TestStubs.routerContext()
113+
routerContext
107114
);
108115
await tick();
109116
await wrapper.update();
110117

111-
const tagValue = wrapper.find('EventDetails TagsTable TagValue a');
112-
expect(tagValue).toHaveLength(1);
118+
// Get the first link as we wrap react-router's link
119+
const tagLink = wrapper.find('EventDetails TagsTable TagValue Link').first();
113120

114-
tagValue.simulate('click');
115-
expect(browserHistory.push).toHaveBeenCalledWith(
116-
expect.objectContaining({
117-
pathname: '/organizations/org-slug/events/',
118-
query: {query: 'browser:"Firefox"'},
119-
})
120-
);
121+
// Should remove eventSlug and append new tag value causing
122+
// the view to re-render
123+
expect(tagLink.props().to).toEqual({
124+
pathname: '/organizations/org-slug/events/',
125+
query: {query: 'browser:"Firefox"'},
126+
});
121127
});
122128

123129
it('appends tag value to existing query when clicked', async function() {
124-
const organization = TestStubs.Organization({projects: [TestStubs.Project()]});
125-
const wrapper = mount(
126-
<OrganizationEventsV2
127-
organization={organization}
128-
location={{
130+
const {organization, routerContext} = initializeOrg({
131+
organization: TestStubs.Organization({projects: [TestStubs.Project()]}),
132+
router: {
133+
location: {
129134
pathname: '/organizations/org-slug/events/',
130135
query: {
131136
query: 'Dumpster',
132137
eventSlug: 'project-slug:deadbeef',
133138
},
134-
}}
139+
},
140+
},
141+
});
142+
const wrapper = mount(
143+
<OrganizationEventsV2
144+
organization={organization}
135145
params={{orgId: organization.slug}}
146+
location={routerContext.context.location}
136147
/>,
137-
TestStubs.routerContext()
148+
routerContext
138149
);
139150
await tick();
140151
await wrapper.update();
141152

142-
const tagValue = wrapper.find('EventDetails TagsTable TagValue a');
143-
expect(tagValue).toHaveLength(1);
153+
// Get the first link as we wrap react-router's link
154+
const tagLink = wrapper.find('EventDetails TagsTable TagValue Link').first();
144155

145-
tagValue.simulate('click');
146156
// Should remove eventSlug and append new tag value causing
147157
// the view to re-render
148-
expect(browserHistory.push).toHaveBeenCalledWith(
149-
expect.objectContaining({
150-
pathname: '/organizations/org-slug/events/',
151-
query: {query: 'Dumpster browser:"Firefox"'},
152-
})
153-
);
158+
expect(tagLink.props().to).toEqual({
159+
pathname: '/organizations/org-slug/events/',
160+
query: {query: 'Dumpster browser:"Firefox"'},
161+
});
154162
});
155163
});

tests/js/spec/views/organizationEventsV2/utils.spec.jsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import {getCurrentView, getQuery} from 'app/views/organizationEventsV2/utils';
1+
import {
2+
getCurrentView,
3+
getQuery,
4+
eventTagSearchUrl,
5+
} from 'app/views/organizationEventsV2/utils';
26
import {ALL_VIEWS} from 'app/views/organizationEventsV2/data';
37

48
describe('getCurrentView()', function() {
@@ -38,3 +42,36 @@ describe('getQuery()', function() {
3842
]);
3943
});
4044
});
45+
46+
describe('eventTagSearchUrl()', function() {
47+
let location;
48+
beforeEach(function() {
49+
location = {
50+
pathname: '/organization/org-slug/events/',
51+
query: {},
52+
};
53+
});
54+
55+
it('adds a query', function() {
56+
expect(eventTagSearchUrl({key: 'browser', value: 'firefox'}, location)).toEqual({
57+
pathname: location.pathname,
58+
query: {query: 'browser:"firefox"'},
59+
});
60+
});
61+
62+
it('removes eventSlug', function() {
63+
location.query.eventSlug = 'project-slug:deadbeef';
64+
expect(eventTagSearchUrl({key: 'browser', value: 'firefox'}, location)).toEqual({
65+
pathname: location.pathname,
66+
query: {query: 'browser:"firefox"'},
67+
});
68+
});
69+
70+
it('appends to an existing query', function() {
71+
location.query.query = 'failure';
72+
expect(eventTagSearchUrl({key: 'browser', value: 'firefox'}, location)).toEqual({
73+
pathname: location.pathname,
74+
query: {query: 'failure browser:"firefox"'},
75+
});
76+
});
77+
});

0 commit comments

Comments
 (0)