-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(events-v2) Add a event graph to the event modal #13568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
da9b4c5
4a718ee
6ad7312
f72a994
0a4129e
fc48f32
c2776f3
ae1bfe7
8218c01
57b5b71
36f6b86
4a8f027
3ac70ae
0b63c48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,25 +2,75 @@ import React from 'react'; | |
import styled from 'react-emotion'; | ||
import {browserHistory} from 'react-router'; | ||
import PropTypes from 'prop-types'; | ||
import {omit} from 'lodash'; | ||
|
||
import SentryTypes from 'app/sentryTypes'; | ||
import AsyncComponent from 'app/components/asyncComponent'; | ||
import InlineSvg from 'app/components/inlineSvg'; | ||
import withApi from 'app/utils/withApi'; | ||
import space from 'app/styles/space'; | ||
|
||
import EventModalContent from './eventModalContent'; | ||
import {getQuery} from './utils'; | ||
|
||
const slugValidator = function(props, propName, componentName) { | ||
const value = props[propName]; | ||
// Accept slugs that look like: | ||
// * project-slug:123:latest | ||
// * project-slug:123:oldest | ||
// * project-slug:123:deadbeef | ||
// * project-slug:deadbeef | ||
if (value && !/^(?:[^:]+:)?(?:[^:]+):(?:[a-f0-9]+|latest|oldest)$/.test(value)) { | ||
return new Error(`Invalid value for ${propName} provided to ${componentName}.`); | ||
} | ||
return null; | ||
}; | ||
|
||
class EventDetails extends AsyncComponent { | ||
static propTypes = { | ||
params: PropTypes.object, | ||
eventSlug: PropTypes.string.isRequired, | ||
organization: SentryTypes.Organization.isRequired, | ||
eventSlug: slugValidator, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to validate these at runtime as well? (I guess receiving a 404 makes sense) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the 404 we display in the modal could use some work, but I can do that separately. |
||
groupSlug: slugValidator, | ||
location: PropTypes.object.isRequired, | ||
view: PropTypes.object.isRequired, | ||
}; | ||
|
||
getEndpoints() { | ||
const {orgId} = this.props.params; | ||
const [projectId, eventId] = this.props.eventSlug.toString().split(':'); | ||
|
||
return [['event', `/projects/${orgId}/${projectId}/events/${eventId}/`]]; | ||
const {organization, eventSlug, groupSlug, view, location} = this.props; | ||
const query = getQuery(view, location); | ||
|
||
// If we're getting an issue/group use the latest endpoint. | ||
// We pass the current query/view state to the API so we get an | ||
// event that matches the current list filters. | ||
if (groupSlug) { | ||
const [projectId, groupId, eventId] = groupSlug.toString().split(':'); | ||
|
||
let url = `/organizations/${organization.slug}/events/`; | ||
// latest / oldest have dedicated endpoints | ||
if (['latest', 'oldest'].includes(eventId)) { | ||
url += 'latest/'; | ||
} else { | ||
url += `${projectId}:${eventId}/`; | ||
} | ||
if (query.query) { | ||
query.query += ` issue.id:${groupId}`; | ||
} else { | ||
query.query = `issue.id:${groupId}`; | ||
} | ||
|
||
return [['event', url, {query}]]; | ||
} | ||
|
||
// Get a specific event. This could be coming from | ||
// a paginated group or standalone event. | ||
const [projectId, eventId] = eventSlug.toString().split(':'); | ||
return [ | ||
[ | ||
'event', | ||
`/organizations/${organization.slug}/events/${projectId}:${eventId}/`, | ||
{query}, | ||
], | ||
]; | ||
} | ||
|
||
onRequestSuccess({data}) { | ||
|
@@ -30,23 +80,45 @@ class EventDetails extends AsyncComponent { | |
|
||
handleClose = event => { | ||
event.preventDefault(); | ||
browserHistory.goBack(); | ||
const {location} = this.props; | ||
// Remove modal related query parameters. | ||
const query = omit(location.query, ['groupSlug', 'eventSlug']); | ||
|
||
browserHistory.push({ | ||
pathname: location.pathname, | ||
query, | ||
}); | ||
}; | ||
|
||
handleTabChange = tab => this.setState({activeTab: tab}); | ||
|
||
get projectId() { | ||
if (this.props.eventSlug) { | ||
const [projectId] = this.props.eventSlug.split(':'); | ||
return projectId; | ||
} | ||
if (this.props.groupSlug) { | ||
const [projectId] = this.props.groupSlug.split(':'); | ||
return projectId; | ||
} | ||
throw new Error('Could not determine projectId'); | ||
} | ||
|
||
renderBody() { | ||
const {orgId} = this.props.params; | ||
const [projectId, _] = this.props.eventSlug.split(':'); | ||
const {organization, view, location} = this.props; | ||
const {event, activeTab} = this.state; | ||
|
||
return ( | ||
<ModalContainer> | ||
<CloseButton onClick={this.handleClose} size={30} /> | ||
<EventModalContent | ||
onTabChange={this.handleTabChange} | ||
event={this.state.event} | ||
activeTab={this.state.activeTab} | ||
projectId={projectId} | ||
orgId={orgId} | ||
event={event} | ||
activeTab={activeTab} | ||
projectId={this.projectId} | ||
organization={organization} | ||
view={view} | ||
location={location} | ||
/> | ||
</ModalContainer> | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:nice:regex:
It'd help to have a comment with what a valid slug looks like. Are special characters (non alphanumerics) allowed in the first two groups?