123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- import {GroupFixture} from 'sentry-fixture/group';
- import {LocationFixture} from 'sentry-fixture/locationFixture';
- import {OrganizationFixture} from 'sentry-fixture/organization';
- import {RouterFixture} from 'sentry-fixture/routerFixture';
- import {
- render,
- screen,
- userEvent,
- waitFor,
- waitForElementToBeRemoved,
- } from 'sentry-test/reactTestingLibrary';
- import {type Group, IssueCategory} from 'sentry/types/group';
- import type {Organization} from 'sentry/types/organization';
- import {browserHistory} from 'sentry/utils/browserHistory';
- import GroupEvents from 'sentry/views/issueDetails/groupEvents';
- describe('groupEvents', () => {
- const requests: {[requestName: string]: jest.Mock} = {};
- let group!: Group;
- let organization: Organization;
- let router: ReturnType<typeof RouterFixture>;
- beforeEach(() => {
- group = GroupFixture();
- organization = OrganizationFixture({features: ['event-attachments']});
- router = RouterFixture({
- params: {orgId: 'org-slug', groupId: group.id},
- location: LocationFixture({
- pathname: '/organizations/org-slug/issues/123/events/',
- action: 'REPLACE',
- key: 'okjkey',
- query: {
- query: '',
- },
- }),
- });
- requests.discover = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/',
- headers: {
- Link: `<https://sentry.io/api/0/issues/1/events/?limit=50&cursor=0:0:1>; rel="previous"; results="true"; cursor="0:0:1", <https://sentry.io/api/0/issues/1/events/?limit=50&cursor=0:200:0>; rel="next"; results="true"; cursor="0:200:0"`,
- },
- body: {
- data: [
- {
- timestamp: '2022-09-11T15:01:10+00:00',
- transaction: '/api',
- release: 'backend@1.2.3',
- 'transaction.duration': 1803,
- environment: 'prod',
- 'user.display': 'sentry@sentry.sentry',
- id: 'id123',
- trace: 'trace123',
- 'project.name': 'project123',
- },
- ],
- meta: {
- fields: {
- timestamp: 'date',
- transaction: 'string',
- release: 'string',
- 'transaction.duration': 'duration',
- environment: 'string',
- 'user.display': 'string',
- id: 'string',
- trace: 'string',
- 'project.name': 'string',
- },
- units: {
- timestamp: null,
- transaction: null,
- release: null,
- 'transaction.duration': 'millisecond',
- environment: null,
- 'user.display': null,
- id: null,
- trace: null,
- 'project.name': null,
- },
- isMetricsData: false,
- tips: {query: null, columns: null},
- },
- },
- });
- requests.attachments = MockApiClient.addMockResponse({
- url: '/api/0/issues/1/attachments/?per_page=50&types=event.minidump&event_id=id123',
- body: [],
- });
- requests.recentSearches = MockApiClient.addMockResponse({
- method: 'POST',
- url: '/organizations/org-slug/recent-searches/',
- body: [],
- });
- requests.latestEvent = MockApiClient.addMockResponse({
- method: 'GET',
- url: '/issues/1/events/latest/',
- body: {},
- });
- requests.tags = MockApiClient.addMockResponse({
- method: 'GET',
- url: '/organizations/org-slug/issues/1/tags/',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: `/organizations/org-slug/issues/${group.id}/`,
- body: group,
- });
- });
- afterEach(() => {
- MockApiClient.clearMockResponses();
- jest.clearAllMocks();
- });
- it('fetches and renders a table of events', async () => {
- render(<GroupEvents />, {router, organization});
- expect(await screen.findByText('id123')).toBeInTheDocument();
- // Transaction
- expect(screen.getByText('/api')).toBeInTheDocument();
- // Environment
- expect(screen.getByText('prod')).toBeInTheDocument();
- // Release
- expect(screen.getByText('1.2.3')).toBeInTheDocument();
- // User email
- expect(screen.getByText('sentry@sentry.sentry')).toBeInTheDocument();
- });
- it('pushes new query parameter when searching', async () => {
- render(<GroupEvents />, {organization, router});
- const input = await screen.findByPlaceholderText('Search events\u2026');
- await userEvent.click(input);
- await userEvent.keyboard('foo');
- await userEvent.keyboard('{enter}');
- await waitFor(() => {
- expect(browserHistory.push).toHaveBeenCalledWith(
- expect.objectContaining({
- query: {query: 'foo'},
- })
- );
- });
- });
- it('displays event filters and tags', async () => {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/issues/1/tags/',
- body: [{key: 'custom_tag', name: 'custom_tag', totalValues: 1}],
- });
- render(<GroupEvents />, {organization, router});
- const input = await screen.findByPlaceholderText('Search events\u2026');
- await userEvent.click(input);
- expect(
- await screen.findByRole('button', {name: 'Event Filters'})
- ).toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Event Tags'})).toBeInTheDocument();
- // Should show custom_tag fetched from group tags
- expect(screen.getByRole('option', {name: 'custom_tag'})).toBeInTheDocument();
- // Should hardcoded event filters
- expect(screen.getByRole('option', {name: 'event.type'})).toBeInTheDocument();
- });
- it('handles environment filtering', async () => {
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitFor(() => {
- expect(requests.discover).toHaveBeenCalledWith(
- '/organizations/org-slug/events/',
- expect.objectContaining({
- query: expect.objectContaining({environment: ['prod', 'staging']}),
- })
- );
- });
- expect(await screen.findByText('Transaction')).toBeInTheDocument();
- });
- it('renders events table for performance issue', async () => {
- group.issueCategory = IssueCategory.PERFORMANCE;
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitFor(() => {
- expect(requests.discover).toHaveBeenCalledWith(
- '/organizations/org-slug/events/',
- expect.objectContaining({
- query: expect.objectContaining({
- query: 'performance.issue_ids:1 event.type:transaction ',
- }),
- })
- );
- });
- expect(await screen.findByText('Transaction')).toBeInTheDocument();
- });
- it('renders event and trace link correctly', async () => {
- group.issueCategory = IssueCategory.PERFORMANCE;
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- const eventIdATag = (await screen.findByText('id123')).closest('a');
- expect(eventIdATag).toHaveAttribute(
- 'href',
- '/organizations/org-slug/issues/1/events/id123/'
- );
- });
- it('does not make attachments request, async when feature not enabled', async () => {
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization: {...organization, features: []}});
- await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
- const attachmentsColumn = screen.queryByText('Attachments');
- expect(attachmentsColumn).not.toBeInTheDocument();
- expect(requests.attachments).not.toHaveBeenCalled();
- });
- it('does not display attachments column with no attachments', async () => {
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
- const attachmentsColumn = screen.queryByText('Attachments');
- expect(attachmentsColumn).not.toBeInTheDocument();
- expect(requests.attachments).toHaveBeenCalled();
- });
- it('does not display minidump column with no minidumps', async () => {
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
- const minidumpColumn = screen.queryByText('Minidump');
- expect(minidumpColumn).not.toBeInTheDocument();
- });
- it('displays minidumps', async () => {
- requests.attachments = MockApiClient.addMockResponse({
- url: '/api/0/issues/1/attachments/?per_page=50&types=event.minidump&event_id=id123',
- body: [
- {
- id: 'id123',
- name: 'dc42a8b9-fc22-4de1-8a29-45b3006496d8.dmp',
- headers: {
- 'Content-Type': 'application/octet-stream',
- },
- mimetype: 'application/octet-stream',
- size: 1294340,
- sha1: '742127552a1191f71fcf6ba7bc5afa0a837350e2',
- dateCreated: '2022-09-28T09:04:38.659307Z',
- type: 'event.minidump',
- event_id: 'd54cb9246ee241ffbdb39bf7a9fafbb7',
- },
- ],
- });
- render(<GroupEvents />, {router, organization});
- const minidumpColumn = await screen.findByText('Minidump');
- expect(minidumpColumn).toBeInTheDocument();
- });
- it('does not display attachments but displays minidump', async () => {
- requests.attachments = MockApiClient.addMockResponse({
- url: '/api/0/issues/1/attachments/?per_page=50&types=event.minidump&event_id=id123',
- body: [
- {
- id: 'id123',
- name: 'dc42a8b9-fc22-4de1-8a29-45b3006496d8.dmp',
- headers: {
- 'Content-Type': 'application/octet-stream',
- },
- mimetype: 'application/octet-stream',
- size: 1294340,
- sha1: '742127552a1191f71fcf6ba7bc5afa0a837350e2',
- dateCreated: '2022-09-28T09:04:38.659307Z',
- type: 'event.minidump',
- event_id: 'd54cb9246ee241ffbdb39bf7a9fafbb7',
- },
- ],
- });
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
- const minidumpColumn = await screen.findByText('Minidump');
- expect(minidumpColumn).toBeInTheDocument();
- const attachmentsColumn = screen.queryByText('Attachments');
- expect(attachmentsColumn).not.toBeInTheDocument();
- expect(requests.attachments).toHaveBeenCalled();
- });
- it('renders events table for error', async () => {
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitFor(() => {
- expect(requests.discover).toHaveBeenCalledWith(
- '/organizations/org-slug/events/',
- expect.objectContaining({
- query: expect.objectContaining({
- query: 'issue.id:1 ',
- field: expect.not.arrayContaining(['attachments', 'minidump']),
- }),
- })
- );
- });
- await waitFor(() => {
- expect(screen.getByText('Transaction')).toBeInTheDocument();
- });
- });
- it('removes sort if unsupported by the events table', async () => {
- router.location.query = {
- ...router.location.query,
- environment: ['prod', 'staging'],
- sort: 'user',
- };
- render(<GroupEvents />, {router, organization});
- await waitFor(() => {
- expect(requests.discover).toHaveBeenCalledWith(
- '/organizations/org-slug/events/',
- expect.objectContaining({query: expect.not.objectContaining({sort: 'user'})})
- );
- });
- await waitFor(() => {
- expect(screen.getByText('Transaction')).toBeInTheDocument();
- });
- });
- it('only request for a single projectId', async () => {
- router.location.query = {
- ...router.location.query,
- environment: ['prod', 'staging'],
- sort: 'user',
- project: [group.project.id, '456'],
- };
- render(<GroupEvents />, {router, organization});
- await waitFor(() => {
- expect(requests.discover).toHaveBeenCalledWith(
- '/organizations/org-slug/events/',
- expect.objectContaining({
- query: expect.objectContaining({project: [group.project.id]}),
- })
- );
- });
- await waitFor(() => {
- expect(screen.getByText('Transaction')).toBeInTheDocument();
- });
- });
- it('shows discover query error message', async () => {
- requests.discover = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/',
- statusCode: 500,
- body: {
- detail: 'Internal Error',
- errorId: '69ab396e73704cdba9342ff8dcd59795',
- },
- });
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
- expect(screen.getByTestId('loading-error')).toHaveTextContent('Internal Error');
- });
- it('requests for backend columns if backend project', async () => {
- group.project.platform = 'node-express';
- router.location.query.environment = ['prod', 'staging'];
- render(<GroupEvents />, {router, organization});
- await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
- await waitFor(() => {
- expect(requests.discover).toHaveBeenCalledWith(
- '/organizations/org-slug/events/',
- expect.objectContaining({
- query: expect.objectContaining({
- field: expect.arrayContaining(['url', 'runtime']),
- }),
- })
- );
- });
- expect(requests.discover).toHaveBeenCalledWith(
- '/organizations/org-slug/events/',
- expect.objectContaining({
- query: expect.objectContaining({
- field: expect.not.arrayContaining(['browser']),
- }),
- })
- );
- });
- });
|