diff --git a/src/sentry/static/sentry/app/icons/icon-next.svg b/src/sentry/static/sentry/app/icons/icon-next.svg
new file mode 100644
index 00000000000000..878c589ca2014d
--- /dev/null
+++ b/src/sentry/static/sentry/app/icons/icon-next.svg
@@ -0,0 +1 @@
+
diff --git a/src/sentry/static/sentry/app/icons/icon-prev.svg b/src/sentry/static/sentry/app/icons/icon-prev.svg
new file mode 100644
index 00000000000000..5fe53a4bd2c39d
--- /dev/null
+++ b/src/sentry/static/sentry/app/icons/icon-prev.svg
@@ -0,0 +1 @@
+
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx
index 09968b7c902ad6..244764704b789f 100644
--- a/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx
@@ -2,8 +2,9 @@ import React from 'react';
import styled from 'react-emotion';
import {browserHistory} from 'react-router';
import PropTypes from 'prop-types';
-import SentryTypes from 'app/sentryTypes';
+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';
@@ -41,15 +42,12 @@ class EventDetails extends AsyncComponent {
query.query = `issue.id:${groupId}`;
}
- return [
- [
- 'event',
- `/organizations/${organization.slug}/events/latest/`,
- {
- query,
- },
- ],
- ];
+ let path = `/organizations/${organization.slug}/events/latest/`;
+ if (location.query.oldest) {
+ path = `/organizations/${organization.slug}/events/oldest/`;
+ }
+
+ return [['event', path, {query}]];
}
// Get a specific event. This could be coming from
@@ -67,7 +65,14 @@ class EventDetails extends AsyncComponent {
handleClose = event => {
event.preventDefault();
- browserHistory.goBack();
+ const {location} = this.props;
+ // Remove modal related query parameters.
+ const query = omit(location.query, ['groupId', 'eventSlug', 'oldest']);
+
+ browserHistory.push({
+ pathname: location.pathname,
+ query,
+ });
};
handleTabChange = tab => this.setState({activeTab: tab});
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx
index fd4f09f0c05fc3..76b0777bc95db3 100644
--- a/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx
@@ -19,6 +19,7 @@ import utils from 'app/utils';
import {getMessage, getTitle} from 'app/utils/events';
import {INTERFACES} from 'app/components/events/eventEntries';
+import ModalPagination from './modalPagination';
import ModalLineGraph from './modalLineGraph';
import TagsTable from './tagsTable';
import LinkedIssuePreview from './linkedIssuePreview';
@@ -87,6 +88,9 @@ const EventModalContent = props => {
+ {isGroupedView && (
+
+ )}
{isGroupedView &&
getDynamicText({
value: (
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/modalPagination.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/modalPagination.jsx
new file mode 100644
index 00000000000000..6c6e05a3722f76
--- /dev/null
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/modalPagination.jsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from 'react-emotion';
+import {omit} from 'lodash';
+
+import {t} from 'app/locale';
+import Link from 'app/components/links/link';
+import SentryTypes from 'app/sentryTypes';
+import InlineSvg from 'app/components/inlineSvg';
+import space from 'app/styles/space';
+
+const ModalPagination = props => {
+ const {location, event} = props;
+
+ // Remove the groupId and eventSlug keys as we need to create new ones
+ const query = omit(location.query, ['groupId', 'eventSlug', 'oldest']);
+ const previousEventUrl = event.previousEventID
+ ? {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ eventSlug: `${event.projectSlug}:${event.previousEventID}`,
+ },
+ }
+ : null;
+ const nextEventUrl = event.nextEventID
+ ? {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ eventSlug: `${event.projectSlug}:${event.nextEventID}`,
+ },
+ }
+ : null;
+ const newestUrl = {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ groupId: event.groupID,
+ },
+ };
+ const oldestUrl = {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ oldest: 1,
+ groupId: event.groupID,
+ },
+ };
+
+ return (
+
+
+
+
+
+
+ {t('Older Event')}
+
+
+ {t('Newer Event')}
+
+
+
+
+
+
+ );
+};
+ModalPagination.propTypes = {
+ location: PropTypes.object.isRequired,
+ event: SentryTypes.Event.isRequired,
+};
+
+const StyledLink = styled(Link)`
+ color: ${p => (p.disabled ? p.theme.disabled : p.theme.gray3)};
+ font-size: ${p => p.fontSizeMedium};
+ padding: ${space(0.5)} ${space(1.5)};
+ ${p => (p.last ? '' : `border-right: 1px solid ${p.theme.borderDark};`)}
+ ${p => (p.disabled ? 'pointer-events: none;' : '')}
+`;
+
+const Wrapper = styled('div')`
+ display: flex;
+`;
+
+const Container = styled('div')`
+ display: flex;
+ background: ${p => p.theme.offWhite};
+ border: 1px solid ${p => p.theme.borderDark};
+ border-radius: ${p => p.theme.borderRadius};
+ margin-bottom: ${space(3)};
+ box-shadow: 3px 3px 0 ${p => p.theme.offWhite}, 3px 3px 0 1px ${p => p.theme.borderDark},
+ 7px 7px ${p => p.theme.offWhite}, 7px 7px 0 1px ${p => p.theme.borderDark};
+`;
+
+export default ModalPagination;
diff --git a/tests/js/spec/views/organizationEventsV2/eventDetails.spec.jsx b/tests/js/spec/views/organizationEventsV2/eventDetails.spec.jsx
index 8714b60322575c..594f85c8a7cfad 100644
--- a/tests/js/spec/views/organizationEventsV2/eventDetails.spec.jsx
+++ b/tests/js/spec/views/organizationEventsV2/eventDetails.spec.jsx
@@ -115,19 +115,39 @@ describe('OrganizationEventsV2 > EventDetails', function() {
expect(graph).toHaveLength(1);
});
- it('goes back when close button is clicked', function() {
+ it('renders pagination buttons in grouped view', function() {
+ const wrapper = mount(
+ ,
+ TestStubs.routerContext()
+ );
+ const content = wrapper.find('ModalPagination');
+ expect(content).toHaveLength(1);
+ });
+
+ it('changes history when close button is clicked', function() {
const wrapper = mount(
,
TestStubs.routerContext()
);
const button = wrapper.find('CloseButton');
button.simulate('click');
- expect(browserHistory.goBack).toHaveBeenCalled();
+ expect(browserHistory.push).toHaveBeenCalledWith({
+ pathname: '/organizations/org-slug/events/',
+ query: {},
+ });
});
it('navigates when tag values are clicked', async function() {