Skip to content

Commit 5d173e3

Browse files
authored
feat(eventsv2) Remove transactionSlug and groupSlug (#14257)
The upcoming work to adopt discover style queries means that we can no longer have special slugs. We can also use `argMax()` in snuba to get to the latest event when viewing an aggregated view. This removes the special slug value handling, and relies entirely on eventSlug. Discover2 will require that all modal states need to be reachable from just the eventSlug and query state. This change will break pagination in the short term but I'll be working on new endpoints that will allow that behavior to be restored in the short term. Refs SEN-875
1 parent 0a20e34 commit 5d173e3

File tree

7 files changed

+53
-226
lines changed

7 files changed

+53
-226
lines changed

src/sentry/api/bases/organization_events.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
'user_count': {
2727
'aggregations': [['uniq', 'user', 'user_count']],
2828
},
29+
'latest_event': {
30+
'fields': [
31+
['argMax', ['id', 'timestamp'], 'latest_event'],
32+
],
33+
},
2934
}
3035

3136

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import space from 'app/styles/space';
1515

1616
import {QueryLink} from './styles';
1717

18-
export const MODAL_QUERY_KEYS = ['eventSlug', 'groupSlug', 'transactionSlug'];
18+
export const MODAL_QUERY_KEYS = ['eventSlug'];
1919
export const PIN_ICON = `image://${pinIcon}`;
2020

2121
export const ALL_VIEWS = deepFreeze([
@@ -94,14 +94,14 @@ export const ALL_VIEWS = deepFreeze([
9494
*/
9595
export const SPECIAL_FIELDS = {
9696
transaction: {
97-
fields: ['project.name', 'transaction'],
97+
fields: ['project.name', 'transaction', 'latest_event'],
9898
sortField: 'transaction',
9999
renderFunc: (data, {organization, location}) => {
100100
const target = {
101101
pathname: `/organizations/${organization.slug}/events/`,
102102
query: {
103103
...location.query,
104-
transactionSlug: `${data['project.name']}:${data.transaction}:latest`,
104+
eventSlug: `${data['project.name']}:${data.latest_event}`,
105105
},
106106
};
107107
return (
@@ -207,14 +207,14 @@ export const SPECIAL_FIELDS = {
207207
),
208208
},
209209
error: {
210-
fields: ['issue_title', 'project.name', 'issue.id'],
210+
fields: ['issue_title', 'project.name', 'latest_event'],
211211
sortField: 'issue_title',
212212
renderFunc: (data, {organization, location}) => {
213213
const target = {
214214
pathname: `/organizations/${organization.slug}/events/`,
215215
query: {
216216
...location.query,
217-
groupSlug: `${data['project.name']}:${data['issue.id']}:latest`,
217+
eventSlug: `${data['project.name']}:${data.latest_event}`,
218218
},
219219
};
220220
return (
@@ -227,14 +227,14 @@ export const SPECIAL_FIELDS = {
227227
},
228228
},
229229
csp: {
230-
fields: ['issue_title', 'project.name', 'issue.id'],
230+
fields: ['issue_title', 'project.name', 'latest_event'],
231231
sortField: 'issue_title',
232232
renderFunc: (data, {organization, location}) => {
233233
const target = {
234234
pathname: `/organizations/${organization.slug}/events/`,
235235
query: {
236236
...location.query,
237-
groupSlug: `${data['project.name']}:${data['issue.id']}:latest`,
237+
eventSlug: `${data['project.name']}:${data.latest_event}`,
238238
},
239239
};
240240
return (

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

Lines changed: 20 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,10 @@ import {getQuery} from './utils';
1717
const slugValidator = function(props, propName, componentName) {
1818
const value = props[propName];
1919
// Accept slugs that look like:
20-
// * project-slug:123:latest
21-
// * project-slug:123:oldest
22-
// * project-slug:123:deadbeef
20+
// * project-slug:deadbeef:latest
21+
// * project-slug:deadbeef:oldest
2322
// * project-slug:deadbeef
24-
if (value && !/^(?:[^:]+:)?(?:[^:]+):(?:[a-f0-9]+|latest|oldest)$/.test(value)) {
25-
return new Error(`Invalid value for ${propName} provided to ${componentName}.`);
26-
}
27-
return null;
28-
};
29-
30-
const transactionValidator = function(props, propName, componentName) {
31-
const value = props[propName];
32-
// Accept slugs that look like:
33-
// * project-slug:/some/pathname:latest
34-
// * project-slug:a_bare_string:oldest
35-
// * project-slug:/some/otherpath:deadbeef
36-
if (value && !/^(?:[^:]+):(?:[^:]+):(?:[^:]+|latest|oldest)$/.test(value)) {
23+
if (value && !/^(?:[^:]+):(?:[a-f0-9]+)(?:\:latest|oldest)?$/.test(value)) {
3724
return new Error(`Invalid value for ${propName} provided to ${componentName}.`);
3825
}
3926
return null;
@@ -56,86 +43,36 @@ class EventDetails extends AsyncComponent {
5643
static propTypes = {
5744
organization: SentryTypes.Organization.isRequired,
5845
eventSlug: slugValidator,
59-
groupSlug: slugValidator,
60-
transactionSlug: transactionValidator,
6146
location: PropTypes.object.isRequired,
6247
view: PropTypes.object.isRequired,
6348
};
6449

6550
getEndpoints() {
66-
const {
67-
organization,
68-
eventSlug,
69-
groupSlug,
70-
transactionSlug,
71-
view,
72-
location,
73-
} = this.props;
51+
const {organization, eventSlug, view, location} = this.props;
7452
const query = getQuery(view, location);
7553

76-
// If we're getting an issue/group use the latest endpoint.
77-
// We pass the current query/view state to the API so we get an
78-
// event that matches the current list filters.
79-
if (groupSlug) {
80-
const [projectId, groupId, eventId] = groupSlug.toString().split(':');
81-
82-
let url = `/organizations/${organization.slug}/events/`;
83-
// latest / oldest have dedicated endpoints
84-
if (['latest', 'oldest'].includes(eventId)) {
85-
url += `${eventId}/`;
86-
} else {
87-
url += `${projectId}:${eventId}/`;
88-
}
89-
if (query.query) {
90-
query.query += ` issue.id:${groupId}`;
91-
} else {
92-
query.query = `issue.id:${groupId}`;
93-
}
94-
95-
return [['event', url, {query}]];
96-
}
97-
98-
// If we're looking at a transaction aggregate we need to do a search
99-
// by transaction to find the latest event for the transaction.
100-
if (transactionSlug) {
101-
const [projectId, transactionName, eventId] = transactionSlug.toString().split(':');
102-
103-
let url = `/organizations/${organization.slug}/events/`;
104-
// latest / oldest have dedicated endpoints
105-
if (['latest', 'oldest'].includes(eventId)) {
106-
url += `${eventId}/`;
107-
} else {
108-
url += `${projectId}:${eventId}/`;
109-
}
110-
if (query.query) {
111-
query.query += ` transaction:${transactionName}`;
112-
} else {
113-
query.query = `transaction:${transactionName}`;
114-
}
115-
116-
return [['event', url, {query}]];
54+
// Check the eventid for the latest/oldest keyword and use that to choose
55+
// the endpoint as oldest/latest have special endpoints.
56+
const [projectId, eventId, keyword] = eventSlug.toString().split(':');
57+
58+
let url = `/organizations/${organization.slug}/events/`;
59+
// TODO the latest/oldest links are currently broken as they require a
60+
// new endpoint that works with the upcoming discover2 queries.
61+
if (['latest', 'oldest'].includes(keyword)) {
62+
url += `${keyword}/`;
63+
} else {
64+
url += `${projectId}:${eventId}/`;
11765
}
11866

119-
if (eventSlug) {
120-
// Get a specific event. This could be coming from
121-
// a paginated group or standalone event.
122-
const [projectId, eventId] = eventSlug.toString().split(':');
123-
return [
124-
[
125-
'event',
126-
`/organizations/${organization.slug}/events/${projectId}:${eventId}/`,
127-
{query},
128-
],
129-
];
130-
}
131-
132-
throw new Error('No valid datasource property found.');
67+
// Get a specific event. This could be coming from
68+
// a paginated group or standalone event.
69+
return [['event', url, {query}]];
13370
}
13471

13572
onDismiss = () => {
13673
const {location} = this.props;
13774
// Remove modal related query parameters.
138-
const query = omit(location.query, ['transactionSlug', 'groupSlug', 'eventSlug']);
75+
const query = omit(location.query, ['eventSlug']);
13976

14077
browserHistory.push({
14178
pathname: location.pathname,
@@ -144,12 +81,7 @@ class EventDetails extends AsyncComponent {
14481
};
14582

14683
get projectId() {
147-
const source =
148-
this.props.eventSlug || this.props.groupSlug || this.props.transactionSlug;
149-
if (!source) {
150-
throw new Error('Could not determine projectId');
151-
}
152-
return source.split(':')[0];
84+
return this.props.eventSlug.split(':')[0];
15385
}
15486

15587
renderBody() {

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,8 @@ export default class OrganizationEventsV2 extends React.Component {
5353

5454
render() {
5555
const {organization, location, router} = this.props;
56-
const {eventSlug, groupSlug, transactionSlug} = location.query;
56+
const {eventSlug} = location.query;
5757
const currentView = getCurrentView(location.query.view);
58-
const showModal = transactionSlug || groupSlug || eventSlug;
5958

6059
return (
6160
<DocumentTitle title={`Events - ${organization.slug} - Sentry`}>
@@ -76,13 +75,11 @@ export default class OrganizationEventsV2 extends React.Component {
7675
router={router}
7776
/>
7877
</NoProjectMessage>
79-
{showModal && (
78+
{eventSlug && (
8079
<EventDetails
8180
organization={organization}
8281
params={this.props.params}
8382
eventSlug={eventSlug}
84-
groupSlug={groupSlug}
85-
transactionSlug={transactionSlug}
8683
view={currentView}
8784
location={location}
8885
/>

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

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,17 @@ import {MODAL_QUERY_KEYS} from './data';
1515
/**
1616
* Generate a mapping of link names => link targets for pagination
1717
*/
18-
function buildTargets(event, location, view) {
18+
function buildTargets(event, location) {
1919
// Remove slug related keys as we need to create new ones
2020
const baseQuery = omit(location.query, MODAL_QUERY_KEYS);
2121

22-
let queryKey, aggregateValue;
23-
if (view.id === 'transactions') {
24-
queryKey = 'transactionSlug';
25-
aggregateValue = event.location;
26-
} else {
27-
queryKey = 'groupSlug';
28-
aggregateValue = event.groupID;
29-
}
3022
const urlMap = {
3123
previous: event.previousEventID,
3224
next: event.nextEventID,
33-
latest: 'latest',
34-
oldest: 'oldest',
25+
// TODO(mark) Make latest, oldest work once we have new endpoints.
26+
// `${event.eventID}:latest`,
27+
latest: null,
28+
oldest: null,
3529
};
3630

3731
const links = {};
@@ -44,7 +38,7 @@ function buildTargets(event, location, view) {
4438
pathname: location.pathname,
4539
query: {
4640
...baseQuery,
47-
[queryKey]: `${event.projectSlug}:${aggregateValue}:${value}`,
41+
eventSlug: `${event.projectSlug}:${value}`,
4842
},
4943
};
5044
}
@@ -54,13 +48,16 @@ function buildTargets(event, location, view) {
5448
}
5549

5650
const ModalPagination = props => {
57-
const {event, location, view} = props;
58-
const links = buildTargets(event, location, view);
51+
const {event, location} = props;
52+
const links = buildTargets(event, location);
5953

6054
return (
6155
<Wrapper>
6256
<ShadowBox>
63-
<StyledLink to={links.oldest} disabled={links.previous === null}>
57+
<StyledLink
58+
to={links.oldest}
59+
disabled={links.previous === null || links.oldest === null}
60+
>
6461
<InlineSvg src="icon-prev" size="14px" />
6562
</StyledLink>
6663
<StyledLink
@@ -77,7 +74,11 @@ const ModalPagination = props => {
7774
>
7875
{t('Newer Event')}
7976
</StyledLink>
80-
<StyledLink to={links.latest} disabled={links.next === null} isLast>
77+
<StyledLink
78+
to={links.latest}
79+
disabled={links.next === null || links.latest === null}
80+
isLast
81+
>
8182
<InlineSvg src="icon-next" size="14px" />
8283
</StyledLink>
8384
</ShadowBox>
@@ -87,7 +88,6 @@ const ModalPagination = props => {
8788
ModalPagination.propTypes = {
8889
location: PropTypes.object.isRequired,
8990
event: SentryTypes.Event.isRequired,
90-
view: PropTypes.object.isRequired,
9191
};
9292

9393
const StyledLink = styled(Link, {shouldForwardProp: isPropValid})`

0 commit comments

Comments
 (0)