Skip to content

Commit 8608e2b

Browse files
committed
feat(events-v2) Make tag values interactive in event modal
Upon clicking tag values should update dismiss the modal and update the current search query with the key/value. Refs SEN-648
1 parent fff8f9d commit 8608e2b

File tree

4 files changed

+98
-1
lines changed

4 files changed

+98
-1
lines changed

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

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

1920
getEndpoints() {

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

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

56
import {t} from 'app/locale';
67
import SentryTypes from 'app/sentryTypes';
@@ -23,6 +24,25 @@ export default class OrganizationEventsV2 extends React.Component {
2324
location: PropTypes.object,
2425
};
2526

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+
});
44+
};
45+
2646
renderTabs() {
2747
const {organization} = this.props;
2848
const currentView = getCurrentView(this.props.location.query.view);
@@ -71,6 +91,7 @@ export default class OrganizationEventsV2 extends React.Component {
7191
orgId={organization.slug}
7292
params={this.props.params}
7393
eventSlug={eventSlug}
94+
onTagSelect={this.handleTagSelect}
7495
/>
7596
)}
7697
</PageContent>

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import {t} from 'app/locale';
55
import space from 'app/styles/space';
66

77
const TagsTable = props => {
8+
const handleSelect = tag => {
9+
return event => {
10+
event.preventDefault();
11+
props.onTagSelect(tag);
12+
};
13+
};
14+
815
return (
916
<div>
1017
<TagHeading>{t('Tags')}</TagHeading>
@@ -13,7 +20,11 @@ const TagsTable = props => {
1320
{props.tags.map(tag => (
1421
<StyledTr key={tag.key}>
1522
<TagKey>{tag.key}</TagKey>
16-
<TagValue>{tag.value}</TagValue>
23+
<TagValue>
24+
<a href="#" onClick={handleSelect(tag)}>
25+
{tag.value}
26+
</a>
27+
</TagValue>
1728
</StyledTr>
1829
))}
1930
</tbody>
@@ -23,6 +34,7 @@ const TagsTable = props => {
2334
};
2435
TagsTable.propTypes = {
2536
tags: PropTypes.array.isRequired,
37+
onTagSelect: PropTypes.func.isRequired,
2638
};
2739

2840
const TagHeading = styled('h5')`

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import {mount} from 'enzyme';
3+
import {browserHistory} from 'react-router';
34

45
import OrganizationEventsV2 from 'app/views/organizationEventsV2';
56

@@ -18,6 +19,7 @@ describe('OrganizationEventsV2', function() {
1819
});
1920
MockApiClient.addMockResponse({
2021
url: '/projects/org-slug/project-slug/events/deadbeef/',
22+
method: 'GET',
2123
body: {
2224
id: '1234',
2325
size: 1200,
@@ -89,4 +91,65 @@ describe('OrganizationEventsV2', function() {
8991
const modal = wrapper.find('EventDetails');
9092
expect(modal).toHaveLength(1);
9193
});
94+
95+
it('navigates when tag values are clicked', async function() {
96+
const organization = TestStubs.Organization({projects: [TestStubs.Project()]});
97+
const wrapper = mount(
98+
<OrganizationEventsV2
99+
organization={organization}
100+
location={{
101+
pathname: '/organizations/org-slug/events/',
102+
query: {eventSlug: 'project-slug:deadbeef'},
103+
}}
104+
params={{orgId: organization.slug}}
105+
/>,
106+
TestStubs.routerContext()
107+
);
108+
await tick();
109+
await wrapper.update();
110+
111+
const tagValue = wrapper.find('EventDetails TagsTable TagValue a');
112+
expect(tagValue).toHaveLength(1);
113+
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+
});
122+
123+
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={{
129+
pathname: '/organizations/org-slug/events/',
130+
query: {
131+
query: 'Dumpster',
132+
eventSlug: 'project-slug:deadbeef',
133+
},
134+
}}
135+
params={{orgId: organization.slug}}
136+
/>,
137+
TestStubs.routerContext()
138+
);
139+
await tick();
140+
await wrapper.update();
141+
142+
const tagValue = wrapper.find('EventDetails TagsTable TagValue a');
143+
expect(tagValue).toHaveLength(1);
144+
145+
tagValue.simulate('click');
146+
// Should remove eventSlug and append new tag value causing
147+
// 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+
);
154+
});
92155
});

0 commit comments

Comments
 (0)