diff --git a/src/sentry/static/sentry/app/actionCreators/events.jsx b/src/sentry/static/sentry/app/actionCreators/events.jsx
index 643080c02b45..f788a43d7371 100644
--- a/src/sentry/static/sentry/app/actionCreators/events.jsx
+++ b/src/sentry/static/sentry/app/actionCreators/events.jsx
@@ -31,6 +31,7 @@ export const doEventsRequest = (
limit,
query,
yAxis,
+ groupId,
}
) => {
const shouldDoublePeriod = canIncludePreviousPeriod(includePrevious, period);
@@ -40,6 +41,7 @@ export const doEventsRequest = (
environment,
query,
yAxis,
+ group: groupId,
};
// Doubling period for absolute dates is not accurate unless starting and
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/organizationEvents/utils/eventsRequest.jsx b/src/sentry/static/sentry/app/views/organizationEvents/utils/eventsRequest.jsx
index 0e5af7be9d57..7bade2ffa895 100644
--- a/src/sentry/static/sentry/app/views/organizationEvents/utils/eventsRequest.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEvents/utils/eventsRequest.jsx
@@ -98,12 +98,18 @@ class EventsRequest extends React.PureComponent {
* The yAxis being plotted
*/
yAxis: PropTypes.string,
+
+ /**
+ * issue group id or groupids to filter results by.
+ */
+ groupId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
};
static defaultProps = {
period: null,
start: null,
end: null,
+ groupId: null,
interval: '1d',
limit: 15,
getCategory: i => i,
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx
index 543c809d70db..32857d61aac0 100644
--- a/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/data.jsx
@@ -131,12 +131,42 @@ export const SPECIAL_FIELDS = {
),
},
error: {
- fields: ['issue_title'],
- renderFunc: data => {data.issue_title},
+ fields: ['issue_title', 'project.name', 'issue.id'],
+ renderFunc: (data, {organization, location}) => {
+ const target = {
+ pathname: `/organizations/${organization.slug}/events/`,
+ query: {
+ ...location.query,
+ groupSlug: `${data['project.name']}:${data['issue.id']}:latest`,
+ },
+ };
+ return (
+
+
+ {data.issue_title}
+
+
+ );
+ },
},
csp: {
- fields: ['issue_title'],
- renderFunc: data => {data.issue_title},
+ fields: ['issue_title', 'project.name', 'issue.id'],
+ renderFunc: (data, {organization, location}) => {
+ const target = {
+ pathname: `/organizations/${organization.slug}/events/`,
+ query: {
+ ...location.query,
+ groupSlug: `${data['project.name']}:${data['issue.id']}:latest`,
+ },
+ };
+ return (
+
+
+ {data.issue_title}
+
+
+ );
+ },
},
event_count: {
title: 'events',
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx
index 3a65a03733ab..2b5fcbde4c2a 100644
--- a/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/eventDetails.jsx
@@ -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,
+ 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 (
);
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx
index c08cac11d039..cb469cf58321 100644
--- a/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/eventModalContent.jsx
@@ -20,6 +20,8 @@ import getDynamicText from 'app/utils/getDynamicText';
import space from 'app/styles/space';
import LinkedIssuePreview from './linkedIssuePreview';
+import ModalPagination from './modalPagination';
+import ModalLineGraph from './modalLineGraph';
import TagsTable from './tagsTable';
const OTHER_SECTIONS = {
@@ -76,15 +78,30 @@ ActiveTab.propTypes = {
* Controlled by the EventDetails View.
*/
const EventModalContent = props => {
- const {event, activeTab, projectId, orgId, onTabChange} = props;
- const eventJsonUrl = `/api/0/projects/${orgId}/${projectId}/events/${
+ const {event, activeTab, projectId, organization, onTabChange, location, view} = props;
+ const isGroupedView = !!view.data.groupby;
+ const eventJsonUrl = `/api/0/projects/${organization.slug}/${projectId}/events/${
event.eventID
}/json/`;
return (
-
+
+ {isGroupedView && }
+ {isGroupedView &&
+ getDynamicText({
+ value: (
+
+ ),
+ fixed: 'events chart',
+ })}
+
+
{event.entries.map(entry => {
if (!INTERFACES.hasOwnProperty(entry.type)) {
@@ -143,7 +160,9 @@ const EventModalContent = props => {
EventModalContent.propTypes = {
...ActiveTab.propTypes,
onTabChange: PropTypes.func.isRequired,
- orgId: PropTypes.string.isRequired,
+ organization: SentryTypes.Organization.isRequired,
+ view: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
};
/**
@@ -197,11 +216,14 @@ const MetadataContainer = styled('div')`
const ColumnGrid = styled('div')`
display: grid;
- grid-template-columns: 70% 1fr;
+ grid-template-columns: 70% 28%;
grid-template-rows: auto;
- grid-column-gap: ${space(3)};
+ grid-column-gap: 2%;
`;
+const HeaderBox = styled('div')`
+ grid-column: 1 / 3;
+`;
const ContentColumn = styled('div')`
grid-column: 1 / 2;
`;
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/index.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/index.jsx
index f191a8d44db6..2ee973adf791 100644
--- a/src/sentry/static/sentry/app/views/organizationEventsV2/index.jsx
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/index.jsx
@@ -48,7 +48,9 @@ export default class OrganizationEventsV2 extends React.Component {
render() {
const {organization, location, router} = this.props;
- const {eventSlug} = location.query;
+ const {eventSlug, groupSlug} = location.query;
+ const currentView = getCurrentView(location.query.view);
+ const showModal = groupSlug || eventSlug;
return (
@@ -64,16 +66,19 @@ export default class OrganizationEventsV2 extends React.Component {
{this.renderTabs()}
- {eventSlug && (
+ {showModal && (
)}
diff --git a/src/sentry/static/sentry/app/views/organizationEventsV2/modalLineGraph.jsx b/src/sentry/static/sentry/app/views/organizationEventsV2/modalLineGraph.jsx
new file mode 100644
index 000000000000..401b6b6b0848
--- /dev/null
+++ b/src/sentry/static/sentry/app/views/organizationEventsV2/modalLineGraph.jsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {t} from 'app/locale';
+import SentryTypes from 'app/sentryTypes';
+import {getInterval, useShortInterval} from 'app/components/charts/utils';
+import {getFormattedDate} from 'app/utils/dates';
+import EventsRequest from 'app/views/organizationEvents/utils/eventsRequest';
+import LineChart from 'app/components/charts/lineChart';
+import {Panel} from 'app/components/panels';
+import withApi from 'app/utils/withApi';
+import withGlobalSelection from 'app/utils/withGlobalSelection';
+
+const defaultGetCategory = () => t('Events');
+
+const ModalLineGraph = props => {
+ const {api, groupId, organization, location, selection} = props;
+
+ const isUtc = selection.datetime.utc;
+ const dateFormat = 'lll';
+
+ const interval = getInterval(selection.datetime, true);
+ const hasShortInterval = useShortInterval(selection.datetime);
+
+ const xAxisOptions = {
+ axisLabel: {
+ formatter: (value, index) => {
+ const firstItem = index === 0;
+ const format = hasShortInterval && !firstItem ? 'LT' : dateFormat;
+ return getFormattedDate(value, format, {local: !isUtc});
+ },
+ },
+ };
+
+ const tooltip = {
+ formatAxisLabel: value => {
+ return getFormattedDate(value, 'lll', {local: !isUtc});
+ },
+ };
+
+ return (
+
+
+ {({loading, reloading, timeseriesData}) => (
+
+ )}
+
+
+ );
+};
+ModalLineGraph.propTypes = {
+ api: PropTypes.object.isRequired,
+ organization: SentryTypes.Organization.isRequired,
+ groupId: PropTypes.string.isRequired,
+ location: PropTypes.object.isRequired,
+ selection: PropTypes.object.isRequired,
+};
+
+export default withGlobalSelection(withApi(ModalLineGraph));
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..77971b120856
--- /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 isPropValid from '@emotion/is-prop-valid';
+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 groupSlug and eventSlug keys as we need to create new ones
+ const query = omit(location.query, ['groupSlug', 'eventSlug']);
+ const previousEventUrl = event.previousEventID
+ ? {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ groupSlug: `${event.projectSlug}:${event.groupID}:${event.previousEventID}`,
+ },
+ }
+ : null;
+ const nextEventUrl = event.nextEventID
+ ? {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ groupSlug: `${event.projectSlug}:${event.groupID}:${event.nextEventID}`,
+ },
+ }
+ : null;
+ const newestUrl = {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ groupSlug: `${event.projectSlug}:${event.groupID}:latest`,
+ },
+ };
+ const oldestUrl = {
+ pathname: location.pathname,
+ query: {
+ ...query,
+ groupSlug: `${event.projectSlug}:${event.groupID}:oldest`,
+ },
+ };
+
+ return (
+
+
+
+
+
+
+ {t('Older Event')}
+
+
+ {t('Newer Event')}
+
+
+
+
+
+
+ );
+};
+ModalPagination.propTypes = {
+ location: PropTypes.object.isRequired,
+ event: SentryTypes.Event.isRequired,
+};
+
+const StyledLink = styled(Link, {shouldForwardProp: isPropValid})`
+ color: ${p => (p.disabled ? p.theme.disabled : p.theme.gray3)};
+ font-size: ${p => p.fontSizeMedium};
+ padding: ${space(0.5)} ${space(1.5)};
+ ${p => (p.isLast ? '' : `border-right: 1px solid ${p.theme.borderDark};`)}
+ ${p => (p.disabled ? 'pointer-events: none;' : '')}
+`;
+
+const Wrapper = styled('div')`
+ display: flex;
+`;
+
+const ShadowBox = 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/setup.js b/tests/js/setup.js
index 3ea003af3407..82606d48b5d4 100644
--- a/tests/js/setup.js
+++ b/tests/js/setup.js
@@ -65,6 +65,7 @@ jest.mock('react-router', () => {
Route: ReactRouter.Route,
withRouter: ReactRouter.withRouter,
browserHistory: {
+ goBack: jest.fn(),
push: jest.fn(),
replace: jest.fn(),
listen: jest.fn(() => {}),
diff --git a/tests/js/spec/views/organizationEventsV2/eventDetails.spec.jsx b/tests/js/spec/views/organizationEventsV2/eventDetails.spec.jsx
new file mode 100644
index 000000000000..a87b6db1d810
--- /dev/null
+++ b/tests/js/spec/views/organizationEventsV2/eventDetails.spec.jsx
@@ -0,0 +1,244 @@
+import React from 'react';
+import {mount} from 'enzyme';
+import {initializeOrg} from 'app-test/helpers/initializeOrg';
+import {browserHistory} from 'react-router';
+
+import EventDetails from 'app/views/organizationEventsV2/eventDetails';
+import {ALL_VIEWS} from 'app/views/organizationEventsV2/data';
+
+describe('OrganizationEventsV2 > EventDetails', function() {
+ const allEventsView = ALL_VIEWS.find(view => view.id === 'all');
+ const errorsView = ALL_VIEWS.find(view => view.id === 'errors');
+
+ beforeEach(function() {
+ MockApiClient.addMockResponse({
+ url: '/organizations/org-slug/events/',
+ body: [
+ {
+ id: 'deadbeef',
+ title: 'Oh no something bad',
+ 'project.name': 'project-slug',
+ timestamp: '2019-05-23T22:12:48+00:00',
+ },
+ ],
+ });
+ MockApiClient.addMockResponse({
+ url: '/organizations/org-slug/events/project-slug:deadbeef/',
+ method: 'GET',
+ body: {
+ id: '1234',
+ size: 1200,
+ projectSlug: 'org-slug',
+ eventID: 'deadbeef',
+ groupID: '123',
+ title: 'Oh no something bad',
+ message: 'It was not good',
+ dateCreated: '2019-05-23T22:12:48+00:00',
+ entries: [
+ {
+ type: 'message',
+ message: 'bad stuff',
+ data: {},
+ },
+ ],
+ tags: [{key: 'browser', value: 'Firefox'}],
+ },
+ });
+ MockApiClient.addMockResponse({
+ url: '/issues/123/',
+ method: 'GET',
+ body: TestStubs.Group({id: '123'}),
+ });
+ MockApiClient.addMockResponse({
+ url: '/organizations/org-slug/events-stats/',
+ method: 'GET',
+ body: {
+ data: [[1234561700, [1]], [1234561800, [1]]],
+ },
+ });
+
+ MockApiClient.addMockResponse({
+ url: '/organizations/org-slug/events/latest/',
+ method: 'GET',
+ body: {
+ id: '1234',
+ size: 1200,
+ projectSlug: 'org-slug',
+ eventID: 'deadbeef',
+ groupID: '123',
+ title: 'Oh no something bad',
+ message: 'It was not good',
+ dateCreated: '2019-05-23T22:12:48+00:00',
+ entries: [
+ {
+ type: 'message',
+ message: 'bad stuff',
+ data: {},
+ },
+ ],
+ tags: [{key: 'browser', value: 'Firefox'}],
+ },
+ });
+ });
+
+ it('renders', function() {
+ const wrapper = mount(
+ ,
+ TestStubs.routerContext()
+ );
+ const content = wrapper.find('EventHeader');
+ expect(content.text()).toContain('Oh no something bad');
+
+ const graph = wrapper.find('ModalLineGraph');
+ expect(graph).toHaveLength(0);
+ });
+
+ it('renders a chart in grouped view', function() {
+ const wrapper = mount(
+ ,
+ TestStubs.routerContext()
+ );
+ const content = wrapper.find('EventHeader');
+ expect(content.text()).toContain('Oh no something bad');
+
+ const graph = wrapper.find('ModalLineGraph');
+ expect(graph).toHaveLength(1);
+ });
+
+ it('renders pagination buttons in grouped view', function() {
+ const wrapper = mount(
+ ,
+ TestStubs.routerContext()
+ );
+ const content = wrapper.find('ModalPagination');
+ expect(content).toHaveLength(1);
+ });
+
+ it('removes eventSlug when close button is clicked', function() {
+ const wrapper = mount(
+ ,
+ TestStubs.routerContext()
+ );
+ const button = wrapper.find('CloseButton');
+ button.simulate('click');
+ expect(browserHistory.push).toHaveBeenCalledWith({
+ pathname: '/organizations/org-slug/events/',
+ query: {},
+ });
+ });
+
+ it('removes groupSlug when close button is clicked', function() {
+ const wrapper = mount(
+ ,
+ TestStubs.routerContext()
+ );
+ const button = wrapper.find('CloseButton');
+ button.simulate('click');
+ expect(browserHistory.push).toHaveBeenCalledWith({
+ pathname: '/organizations/org-slug/events/',
+ query: {},
+ });
+ });
+
+ it('navigates when tag values are clicked', async function() {
+ const {organization, routerContext} = initializeOrg({
+ organization: TestStubs.Organization({projects: [TestStubs.Project()]}),
+ router: {
+ location: {
+ pathname: '/organizations/org-slug/events/',
+ query: {
+ eventSlug: 'project-slug:deadbeef',
+ },
+ },
+ },
+ });
+ const wrapper = mount(
+ ,
+ routerContext
+ );
+ await tick();
+ await wrapper.update();
+
+ // Get the first link as we wrap react-router's link
+ const tagLink = wrapper.find('EventDetails TagsTable TagValue Link').first();
+
+ // Should remove eventSlug and append new tag value causing
+ // the view to re-render
+ expect(tagLink.props().to).toEqual({
+ pathname: '/organizations/org-slug/events/',
+ query: {query: 'browser:"Firefox"'},
+ });
+ });
+
+ it('appends tag value to existing query when clicked', async function() {
+ const {organization, routerContext} = initializeOrg({
+ organization: TestStubs.Organization({projects: [TestStubs.Project()]}),
+ router: {
+ location: {
+ pathname: '/organizations/org-slug/events/',
+ query: {
+ query: 'Dumpster',
+ eventSlug: 'project-slug:deadbeef',
+ },
+ },
+ },
+ });
+ const wrapper = mount(
+ ,
+ routerContext
+ );
+ await tick();
+ await wrapper.update();
+
+ // Get the first link as we wrap react-router's link
+ const tagLink = wrapper.find('EventDetails TagsTable TagValue Link').first();
+
+ // Should remove eventSlug and append new tag value causing
+ // the view to re-render
+ expect(tagLink.props().to).toEqual({
+ pathname: '/organizations/org-slug/events/',
+ query: {query: 'Dumpster browser:"Firefox"'},
+ });
+ });
+});
diff --git a/tests/js/spec/views/organizationEventsV2/index.spec.jsx b/tests/js/spec/views/organizationEventsV2/index.spec.jsx
index f1680b672e7e..6c0cdc390d71 100644
--- a/tests/js/spec/views/organizationEventsV2/index.spec.jsx
+++ b/tests/js/spec/views/organizationEventsV2/index.spec.jsx
@@ -1,6 +1,5 @@
import React from 'react';
import {mount} from 'enzyme';
-import {initializeOrg} from 'app-test/helpers/initializeOrg';
import OrganizationEventsV2 from 'app/views/organizationEventsV2';
@@ -18,7 +17,7 @@ describe('OrganizationEventsV2', function() {
],
});
MockApiClient.addMockResponse({
- url: '/projects/org-slug/project-slug/events/deadbeef/',
+ url: '/organizations/org-slug/events/project-slug:deadbeef/',
method: 'GET',
body: {
id: '1234',
@@ -96,74 +95,19 @@ describe('OrganizationEventsV2', function() {
expect(modal).toHaveLength(1);
});
- it('navigates when tag values are clicked', async function() {
- const {organization, routerContext} = initializeOrg({
- organization: TestStubs.Organization({projects: [TestStubs.Project()]}),
- router: {
- location: {
- pathname: '/organizations/org-slug/events/',
- query: {
- eventSlug: 'project-slug:deadbeef',
- },
- },
- },
- });
- const wrapper = mount(
- ,
- routerContext
- );
- await tick();
- await wrapper.update();
-
- // Get the first link as we wrap react-router's link
- const tagLink = wrapper.find('EventDetails TagsTable TagValue Link').first();
-
- // Should remove eventSlug and append new tag value causing
- // the view to re-render
- expect(tagLink.props().to).toEqual({
- pathname: '/organizations/org-slug/events/',
- query: {query: 'browser:"Firefox"'},
- });
- });
-
- it('appends tag value to existing query when clicked', async function() {
- const {organization, routerContext} = initializeOrg({
- organization: TestStubs.Organization({projects: [TestStubs.Project()]}),
- router: {
- location: {
- pathname: '/organizations/org-slug/events/',
- query: {
- query: 'Dumpster',
- eventSlug: 'project-slug:deadbeef',
- },
- },
- },
- });
+ it('opens a modal when groupSlug is present', async function() {
+ const organization = TestStubs.Organization({projects: [TestStubs.Project()]});
const wrapper = mount(
,
- routerContext
+ TestStubs.routerContext()
);
- await tick();
- await wrapper.update();
-
- // Get the first link as we wrap react-router's link
- const tagLink = wrapper.find('EventDetails TagsTable TagValue Link').first();
- // Should remove eventSlug and append new tag value causing
- // the view to re-render
- expect(tagLink.props().to).toEqual({
- pathname: '/organizations/org-slug/events/',
- query: {query: 'Dumpster browser:"Firefox"'},
- });
+ const modal = wrapper.find('EventDetails');
+ expect(modal).toHaveLength(1);
});
});