From 6746a29765246f7247724ce067489b271065fa47 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 7 Jun 2019 16:45:09 -0400 Subject: [PATCH] feat(events-v2) Add pagination controls to event modal In grouped views we want to show pagination buttons to navigate between the events that match the current search filters including the query string. By creating URLs that use eventSlug and the current search criteria we'll end up at the right events. Refs SEN-715 --- .../static/sentry/app/icons/icon-next.svg | 1 + .../static/sentry/app/icons/icon-prev.svg | 1 + .../organizationEventsV2/eventDetails.jsx | 27 +++--- .../eventModalContent.jsx | 4 + .../organizationEventsV2/modalPagination.jsx | 97 +++++++++++++++++++ .../eventDetails.spec.jsx | 26 ++++- 6 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 src/sentry/static/sentry/app/icons/icon-next.svg create mode 100644 src/sentry/static/sentry/app/icons/icon-prev.svg create mode 100644 src/sentry/static/sentry/app/views/organizationEventsV2/modalPagination.jsx 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 000000000000..878c589ca201 --- /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 000000000000..5fe53a4bd2c3 --- /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 09968b7c902a..244764704b78 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 fd4f09f0c05f..76b0777bc95d 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 000000000000..6c6e05a3722f --- /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 8714b6032257..594f85c8a7cf 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() {