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)