diff --git a/src/sentry/api/endpoints/organization_events.py b/src/sentry/api/endpoints/organization_events.py
index 1e43a269352f..a2fec67a37bb 100644
--- a/src/sentry/api/endpoints/organization_events.py
+++ b/src/sentry/api/endpoints/organization_events.py
@@ -18,7 +18,7 @@
from sentry import features
from sentry.models.project import Project
-ALLOWED_GROUPINGS = frozenset(('issue.id', 'project.id'))
+ALLOWED_GROUPINGS = frozenset(('issue.id', 'project.id', 'transaction'))
logger = logging.getLogger(__name__)
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx
index 7b9218e8f868..2f414f8d4da0 100644
--- a/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx
@@ -12,6 +12,7 @@ import UserBadge from 'app/components/idBadge/userBadge';
import DateTime from 'app/components/dateTime';
import pinIcon from 'app/../images/location-pin.png';
+import {t} from 'app/locale';
import {QueryLink} from './styles';
export const MODAL_QUERY_KEYS = ['eventSlug', 'groupSlug'];
@@ -20,7 +21,7 @@ export const PIN_ICON = `image://${pinIcon}`;
export const ALL_VIEWS = deepFreeze([
{
id: 'all',
- name: 'All Events',
+ name: t('All Events'),
data: {
fields: ['event', 'type', 'project', 'user', 'time'],
sort: ['-timestamp', '-id'],
@@ -37,7 +38,7 @@ export const ALL_VIEWS = deepFreeze([
},
{
id: 'errors',
- name: 'Errors',
+ name: t('Errors'),
data: {
fields: ['error', 'event_count', 'user_count', 'project', 'last_seen'],
groupby: ['issue.id', 'project.id'],
@@ -49,7 +50,7 @@ export const ALL_VIEWS = deepFreeze([
},
{
id: 'csp',
- name: 'CSP',
+ name: t('CSP'),
data: {
fields: ['csp', 'event_count', 'user_count', 'project', 'last_seen'],
groupby: ['issue.id', 'project.id'],
@@ -65,6 +66,25 @@ export const ALL_VIEWS = deepFreeze([
],
columnWidths: ['3fr', '70px', '70px', '1fr', '1.5fr'],
},
+ {
+ id: 'transactions',
+ name: t('Transactions'),
+ data: {
+ fields: ['transaction', 'project'],
+ groupby: ['transaction', 'project.id'],
+ sort: ['-transaction'],
+ query: 'event.type:transaction',
+ },
+ tags: [
+ 'event.type',
+ 'release',
+ 'project.name',
+ 'user.email',
+ 'user.ip',
+ 'environment',
+ ],
+ columnWidths: ['3fr', '1fr', '1fr', '1fr', '1fr', '1fr', '1fr'],
+ },
]);
/**
@@ -73,6 +93,26 @@ export const ALL_VIEWS = deepFreeze([
* displays with a custom render function.
*/
export const SPECIAL_FIELDS = {
+ transaction: {
+ fields: ['project.name', 'transaction'],
+ sortField: 'transaction',
+ renderFunc: (data, {organization, location}) => {
+ const target = {
+ pathname: `/organizations/${organization.slug}/events/`,
+ query: {
+ ...location.query,
+ transactionSlug: `${data['project.name']}:${data.transaction}`,
+ },
+ };
+ return (
+
+
+ {data.transaction}
+
+
+ );
+ },
+ },
event: {
fields: ['title', 'id', 'project.name'],
sortField: 'title',
@@ -83,7 +123,7 @@ export const SPECIAL_FIELDS = {
};
return (
-
+
{data.title}
@@ -172,7 +212,7 @@ export const SPECIAL_FIELDS = {
};
return (
-
+
{data.issue_title}
@@ -192,7 +232,7 @@ export const SPECIAL_FIELDS = {
};
return (
-
+
{data.issue_title}
diff --git a/tests/acceptance/test_organization_events_v2.py b/tests/acceptance/test_organization_events_v2.py
index 2c61ad657216..a74afa559473 100644
--- a/tests/acceptance/test_organization_events_v2.py
+++ b/tests/acceptance/test_organization_events_v2.py
@@ -127,7 +127,7 @@ def test_modal_from_all_events(self, mock_now):
self.wait_until_loaded()
# Click the event link to open the modal
- self.browser.element('[data-test-id="event-title"]').click()
+ self.browser.element('[aria-label="{}"]'.format(event.title)).click()
self.wait_until_loaded()
header = self.browser.element('[data-test-id="modal-dialog"] h2')
@@ -169,7 +169,7 @@ def test_modal_from_errors_view(self, mock_now):
self.wait_until_loaded()
# Click the event link to open the modal
- self.browser.element('[data-test-id="event-title"]').click()
+ self.browser.element('[aria-label="{}"]'.format(event.title)).click()
self.wait_until_loaded()
self.browser.snapshot('events-v2 - grouped error modal')
diff --git a/tests/js/spec/views/organizationEventsV2/index.spec.jsx b/tests/js/spec/views/organizationEventsV2/index.spec.jsx
index deddc52d4baf..173b75d6c996 100644
--- a/tests/js/spec/views/organizationEventsV2/index.spec.jsx
+++ b/tests/js/spec/views/organizationEventsV2/index.spec.jsx
@@ -4,13 +4,14 @@ import {mount} from 'enzyme';
import OrganizationEventsV2 from 'app/views/organizationEventsV2';
describe('OrganizationEventsV2', function() {
+ const eventTitle = 'Oh no something bad';
beforeEach(function() {
MockApiClient.addMockResponse({
url: '/organizations/org-slug/events/',
body: [
{
id: 'deadbeef',
- title: 'Oh no something bad',
+ title: eventTitle,
'project.name': 'project-slug',
timestamp: '2019-05-23T22:12:48+00:00',
},
@@ -108,7 +109,7 @@ describe('OrganizationEventsV2', function() {
TestStubs.routerContext()
);
- const link = wrapper.find('Table Link[data-test-id="event-title"]').first();
+ const link = wrapper.find(`Table Link[aria-label="${eventTitle}"]`).first();
expect(link.props().to.query).toEqual({eventSlug: 'project-slug:deadbeef'});
});
diff --git a/tests/snuba/api/endpoints/test_organization_events_v2.py b/tests/snuba/api/endpoints/test_organization_events_v2.py
index fc9fd6d069dd..80f3984b8a3b 100644
--- a/tests/snuba/api/endpoints/test_organization_events_v2.py
+++ b/tests/snuba/api/endpoints/test_organization_events_v2.py
@@ -530,7 +530,7 @@ def test_invalid_groupby(self):
},
)
assert response.status_code == 400, response.content
- assert response.data['detail'] == 'Invalid groupby value requested. Allowed values are project.id, issue.id'
+ assert response.data['detail'] == 'Invalid groupby value requested. Allowed values are transaction, project.id, issue.id'
def test_non_aggregated_fields_with_groupby(self):
self.login_as(user=self.user)