Skip to content

Commit

Permalink
Adding unique key, adding test case.
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisoelkers committed Jan 7, 2025
1 parent 288122b commit a458aae
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ const BulkEventReplay = ({ initialEventIds, events: _events, onClose }: Props) =
<i>Investigation of {completed}/{total} events completed.</i>
<StyledList>
{eventIds.map(({ id: eventId, status }) => (
<EventListItem event={events?.[eventId]?.event}
<EventListItem key={`bulk-replay-search-item-${eventId}`}
event={events?.[eventId]?.event}
selected={eventId === selectedId}
done={status === 'DONE'}
removeItem={removeItem}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ const EventListItem = ({ done, event, onClick, selected, removeItem, markItemAsD
<Summary $done={done}>{event?.message ?? <i>Unknown</i>}</Summary>

<ButtonGroup>
<IconButton onClick={_removeItem} title="Remove event from list" name="delete" />
<CompletedButton onClick={_markItemAsDone} title="Mark event as investigated" name="check" $done={done} />
<IconButton onClick={_removeItem} title={`Remove event "${event?.id}" from list`} name="delete" />
<CompletedButton onClick={_markItemAsDone} title={`Mark event "${event?.id}" as investigated`} name="check" $done={done} />
</ButtonGroup>
</StyledItem>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as React from 'react';
import { render, screen, waitFor } from 'wrappedTestingLibrary';
import userEvent from '@testing-library/user-event';
import { PluginManifest } from 'graylog-web-plugin/plugin';

import type { Event } from 'components/events/events/types';
import { usePlugin } from 'views/test/testPlugins';
import MenuItem from 'components/bootstrap/menuitem/MenuItem';

import events from './events.fixtures';

import BulkEventReplay from '../BulkEventReplay';

const initialEventIds = [
'01JH007XPDF710TPJZT8K2CN3W',
'01JH006340WP7HQ5E7P71Q9HHX',
'01JH0029TS9PX5ED87TZ1RVRT2',
];

jest.mock('components/events/bulk-replay/ReplaySearch', () => ({ event }: { event: Event }) => <span>Replaying search for event {event.id}</span>);

const markEventAsInvestigated = async (eventId: string) => {
const markAsInvestigatedButton = await screen.findByRole('button', { name: new RegExp(`mark event "${eventId}" as investigated`, 'i') });

return userEvent.click(markAsInvestigatedButton);
};

const removeEvent = async (eventId: string) => {
const removeEventButton = await screen.findByRole('button', { name: new RegExp(`remove event "${eventId}" from list`, 'i') });

return userEvent.click(removeEventButton);
};

const expectReplayingEvent = (eventId: string) => screen.findByText(new RegExp(`replaying search for event ${eventId}`, 'i'));

const eventByIndex = (index: number) => events[initialEventIds[index]].event;
const eventMessage = (index: number) => eventByIndex(index).message;

const SUT = (props: Partial<React.ComponentProps<typeof BulkEventReplay>>) => (
<BulkEventReplay events={events} initialEventIds={initialEventIds} onClose={() => {}} {...props} />
);

const bulkAction = jest.fn();
const testPlugin = new PluginManifest({}, {
'views.components.eventActions': [{
key: 'test-bulk-action',
component: ({ events: _events }) => (
<MenuItem onClick={() => bulkAction(_events)}>Test Action</MenuItem>
),
useCondition: () => true,
isBulk: true,
}],
});

describe('BulkEventReplay', () => {
usePlugin(testPlugin);

it('calls `onClose` when close button is clicked', async () => {
const onClose = jest.fn();
render(<SUT onClose={onClose} />);
const closeButton = await screen.findByRole('button', { name: 'Close' });
userEvent.click(closeButton);

await waitFor(() => {
expect(onClose).toHaveBeenCalled();
});
});

it('renders list of selected events', async () => {
render(<SUT />);
await screen.findByText(eventMessage(0));
await screen.findByText(eventMessage(1));
await screen.findByText(eventMessage(2));

await expectReplayingEvent(initialEventIds[0]);
});

it('clicking delete button removes event from list', async () => {
render(<SUT />);
await removeEvent(initialEventIds[0]);

await screen.findByText(eventMessage(1));
await expectReplayingEvent(initialEventIds[1]);

expect(screen.queryByText(eventMessage(0))).not.toBeInTheDocument();
});

it('marking events as investigated jumps to next one', async () => {
render(<SUT />);
await markEventAsInvestigated(initialEventIds[0]);

await expectReplayingEvent(initialEventIds[1]);

await markEventAsInvestigated(initialEventIds[1]);
await expectReplayingEvent(initialEventIds[2]);

await markEventAsInvestigated(initialEventIds[2]);
await screen.findByText('You are done investigating all events. You can now select a bulk action to apply to all remaining events, or close the page to return to the events list.');
});

it('allows jumping to specific events', async () => {
render(<SUT />);
await expectReplayingEvent(initialEventIds[0]);

userEvent.click(await screen.findByText(eventMessage(1)));

await expectReplayingEvent(initialEventIds[1]);

userEvent.click(await screen.findByText(eventMessage(0)));

await expectReplayingEvent(initialEventIds[0]);
});

it('skips removed event when jumping to next one', async () => {
render(<SUT />);
await removeEvent(initialEventIds[1]);
await markEventAsInvestigated(initialEventIds[0]);
await expectReplayingEvent(initialEventIds[2]);
});

it('skips event marked as investigated when jumping to next one', async () => {
render(<SUT />);
await markEventAsInvestigated(initialEventIds[1]);
await markEventAsInvestigated(initialEventIds[0]);
await expectReplayingEvent(initialEventIds[2]);
});

it('bulk actions get current list of events', async () => {
render(<SUT />);
await removeEvent(initialEventIds[1]);

userEvent.click(await screen.findByRole('button', { name: /bulk actions/i }));
userEvent.click(await screen.findByRole('menuitem', { name: /test action/i }));

await waitFor(() => {
expect(bulkAction).toHaveBeenCalledWith([eventByIndex(0), eventByIndex(2)]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
export default {
'01JH006340WP7HQ5E7P71Q9HHX': {
event: {
id: '01JH006340WP7HQ5E7P71Q9HHX',
event_definition_type: 'aggregation-v1',
event_definition_id: '66d719128a7ffa68df52fd7f',
origin_context: null,
timestamp: '2025-01-07T09:05:23.262Z',
timestamp_processing: '2025-01-07T09:05:29.216Z',
timerange_start: '2025-01-07T09:04:23.262Z',
timerange_end: '2025-01-07T09:05:23.262Z',
streams: [
'000000000000000000000002',
],
source_streams: [
'66e962c1b3a0040a1570faf9',
'000000000000000000000001',
],
message: 'Error Rate: count()=4758.0',
source: 'church',
key_tuple: [
'foo',
'hey there!',
],
key: 'foo|hey there!',
priority: 3,
scores: {
raw_risk: 6,
normalized_risk: 6,
},
associated_assets: [],
alert: false,
fields: {
mightyalert: 'foo',
secondkey: 'hey there!',
},
group_by_fields: {},
replay_info: {
timerange_start: '2025-01-07T09:04:23.262Z',
timerange_end: '2025-01-07T09:05:23.262Z',
query: '',
streams: [
'66e962c1b3a0040a1570faf9',
'000000000000000000000001',
],
filters: [
{
type: 'inlineQueryString',
title: 'Index Actions',
id: 'd1057daf-f0e0-4159-ae00-e7e340629bb3',
description: '',
queryString: 'action:index',
negation: false,
disabled: false,
},
],
},
},
index_name: 'gl-events_13',
index_type: 'message',
},
'01JH0029TS9PX5ED87TZ1RVRT2': {
event: {
id: '01JH0029TS9PX5ED87TZ1RVRT2',
event_definition_type: 'aggregation-v1',
event_definition_id: '66d719128a7ffa68df52fd7f',
origin_context: null,
timestamp: '2025-01-07T09:03:23.262Z',
timestamp_processing: '2025-01-07T09:03:25.017Z',
timerange_start: '2025-01-07T09:02:23.262Z',
timerange_end: '2025-01-07T09:03:23.262Z',
streams: [
'000000000000000000000002',
],
source_streams: [
'66e962c1b3a0040a1570faf9',
'000000000000000000000001',
],
message: 'Error Rate: count()=4760.0',
source: 'church',
key_tuple: [
'foo',
'hey there!',
],
key: 'foo|hey there!',
priority: 3,
scores: {
raw_risk: 6,
normalized_risk: 6,
},
associated_assets: [],
alert: false,
fields: {
mightyalert: 'foo',
secondkey: 'hey there!',
},
group_by_fields: {},
replay_info: {
timerange_start: '2025-01-07T09:02:23.262Z',
timerange_end: '2025-01-07T09:03:23.262Z',
query: '',
streams: [
'66e962c1b3a0040a1570faf9',
'000000000000000000000001',
],
filters: [
{
type: 'inlineQueryString',
title: 'Index Actions',
id: 'd1057daf-f0e0-4159-ae00-e7e340629bb3',
description: '',
queryString: 'action:index',
negation: false,
disabled: false,
},
],
},
},
index_name: 'gl-events_13',
index_type: 'message',
},
'01JH007XPDF710TPJZT8K2CN3W': {
event: {
id: '01JH007XPDF710TPJZT8K2CN3W',
event_definition_type: 'aggregation-v1',
event_definition_id: '66d719128a7ffa68df52fd7f',
origin_context: null,
timestamp: '2025-01-07T09:06:23.262Z',
timestamp_processing: '2025-01-07T09:06:29.197Z',
timerange_start: '2025-01-07T09:05:23.262Z',
timerange_end: '2025-01-07T09:06:23.262Z',
streams: [
'000000000000000000000002',
],
source_streams: [
'66e962c1b3a0040a1570faf9',
'000000000000000000000001',
],
message: 'Error Rate: count()=4718.0',
source: 'church',
key_tuple: [
'foo',
'hey there!',
],
key: 'foo|hey there!',
priority: 3,
scores: {
raw_risk: 6,
normalized_risk: 6,
},
associated_assets: [],
alert: false,
fields: {
mightyalert: 'foo',
secondkey: 'hey there!',
},
group_by_fields: {},
replay_info: {
timerange_start: '2025-01-07T09:05:23.262Z',
timerange_end: '2025-01-07T09:06:23.262Z',
query: '',
streams: [
'66e962c1b3a0040a1570faf9',
'000000000000000000000001',
],
filters: [
{
type: 'inlineQueryString',
title: 'Index Actions',
id: 'd1057daf-f0e0-4159-ae00-e7e340629bb3',
description: '',
queryString: 'action:index',
negation: false,
disabled: false,
},
],
},
},
index_name: 'gl-events_13',
index_type: 'message',
},
};

0 comments on commit a458aae

Please sign in to comment.