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() {