Skip to content

Commit 375774a

Browse files
[Security Solution] Fix timeline pin event callback (#73981)
* [Security Solution] Fix timeline pin event callback * - added tests * - restored the original disabled button behavior Co-authored-by: Andrew Goldstein <andrew.goldstein@elastic.co>
1 parent 8f6e055 commit 375774a

File tree

4 files changed

+148
-6
lines changed

4 files changed

+148
-6
lines changed

x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Ecs } from '../../../../graphql/types';
99
import {
1010
eventHasNotes,
1111
eventIsPinned,
12+
getPinOnClick,
1213
getPinTooltip,
1314
stringifyEvent,
1415
isInvestigateInResolverActionEnabled,
@@ -298,4 +299,72 @@ describe('helpers', () => {
298299
expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy();
299300
});
300301
});
302+
303+
describe('getPinOnClick', () => {
304+
const eventId = 'abcd';
305+
306+
test('it invokes `onPinEvent` with the expected eventId when the event is NOT pinned, and allowUnpinning is true', () => {
307+
const isEventPinned = false; // the event is NOT pinned
308+
const allowUnpinning = true;
309+
const onPinEvent = jest.fn();
310+
311+
getPinOnClick({
312+
allowUnpinning,
313+
eventId,
314+
onPinEvent,
315+
onUnPinEvent: jest.fn(),
316+
isEventPinned,
317+
});
318+
319+
expect(onPinEvent).toBeCalledWith(eventId);
320+
});
321+
322+
test('it does NOT invoke `onPinEvent` when the event is NOT pinned, and allowUnpinning is false', () => {
323+
const isEventPinned = false; // the event is NOT pinned
324+
const allowUnpinning = false;
325+
const onPinEvent = jest.fn();
326+
327+
getPinOnClick({
328+
allowUnpinning,
329+
eventId,
330+
onPinEvent,
331+
onUnPinEvent: jest.fn(),
332+
isEventPinned,
333+
});
334+
335+
expect(onPinEvent).not.toBeCalled();
336+
});
337+
338+
test('it invokes `onUnPinEvent` with the expected eventId when the event is pinned, and allowUnpinning is true', () => {
339+
const isEventPinned = true; // the event is pinned
340+
const allowUnpinning = true;
341+
const onUnPinEvent = jest.fn();
342+
343+
getPinOnClick({
344+
allowUnpinning,
345+
eventId,
346+
onPinEvent: jest.fn(),
347+
onUnPinEvent,
348+
isEventPinned,
349+
});
350+
351+
expect(onUnPinEvent).toBeCalledWith(eventId);
352+
});
353+
354+
test('it does NOT invoke `onUnPinEvent` when the event is pinned, and allowUnpinning is false', () => {
355+
const isEventPinned = true; // the event is pinned
356+
const allowUnpinning = false;
357+
const onUnPinEvent = jest.fn();
358+
359+
getPinOnClick({
360+
allowUnpinning,
361+
eventId,
362+
onPinEvent: jest.fn(),
363+
onUnPinEvent,
364+
isEventPinned,
365+
});
366+
367+
expect(onUnPinEvent).not.toBeCalled();
368+
});
369+
});
301370
});

x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
import { get, isEmpty, noop } from 'lodash/fp';
6+
7+
import { get, isEmpty } from 'lodash/fp';
78
import { Dispatch } from 'redux';
89

910
import { Ecs, TimelineItem, TimelineNonEcsData } from '../../../../graphql/types';
@@ -65,11 +66,16 @@ export const getPinOnClick = ({
6566
onPinEvent,
6667
onUnPinEvent,
6768
isEventPinned,
68-
}: GetPinOnClickParams): (() => void) => {
69+
}: GetPinOnClickParams) => {
6970
if (!allowUnpinning) {
70-
return noop;
71+
return;
72+
}
73+
74+
if (isEventPinned) {
75+
onUnPinEvent(eventId);
76+
} else {
77+
onPinEvent(eventId);
7178
}
72-
return isEventPinned ? () => onUnPinEvent(eventId) : () => onPinEvent(eventId);
7379
};
7480

7581
/**

x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.test.tsx

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { getPinIcon } from './';
7+
import { mount } from 'enzyme';
8+
import React from 'react';
9+
10+
import { TimelineType } from '../../../../../common/types/timeline';
11+
12+
import { getPinIcon, Pin } from './';
13+
14+
interface ButtonIcon {
15+
isDisabled: boolean;
16+
}
817

918
describe('pin', () => {
1019
describe('getPinRotation', () => {
@@ -16,4 +25,62 @@ describe('pin', () => {
1625
expect(getPinIcon(false)).toEqual('pin');
1726
});
1827
});
28+
29+
describe('disabled button behavior', () => {
30+
test('the button is enabled when allowUnpinning is true, and timelineType is NOT `template` (the default)', () => {
31+
const allowUnpinning = true;
32+
const wrapper = mount(
33+
<Pin allowUnpinning={allowUnpinning} onClick={jest.fn()} pinned={false} />
34+
);
35+
36+
expect(
37+
(wrapper.find('[data-test-subj="pin"]').first().props() as ButtonIcon).isDisabled
38+
).toBe(false);
39+
});
40+
41+
test('the button is disabled when allowUnpinning is false, and timelineType is NOT `template` (the default)', () => {
42+
const allowUnpinning = false;
43+
const wrapper = mount(
44+
<Pin allowUnpinning={allowUnpinning} onClick={jest.fn()} pinned={false} />
45+
);
46+
47+
expect(
48+
(wrapper.find('[data-test-subj="pin"]').first().props() as ButtonIcon).isDisabled
49+
).toBe(true);
50+
});
51+
52+
test('the button is disabled when allowUnpinning is true, and timelineType is `template`', () => {
53+
const allowUnpinning = true;
54+
const timelineType = TimelineType.template;
55+
const wrapper = mount(
56+
<Pin
57+
allowUnpinning={allowUnpinning}
58+
onClick={jest.fn()}
59+
pinned={false}
60+
timelineType={timelineType}
61+
/>
62+
);
63+
64+
expect(
65+
(wrapper.find('[data-test-subj="pin"]').first().props() as ButtonIcon).isDisabled
66+
).toBe(true);
67+
});
68+
69+
test('the button is disabled when allowUnpinning is false, and timelineType is `template`', () => {
70+
const allowUnpinning = false;
71+
const timelineType = TimelineType.template;
72+
const wrapper = mount(
73+
<Pin
74+
allowUnpinning={allowUnpinning}
75+
onClick={jest.fn()}
76+
pinned={false}
77+
timelineType={timelineType}
78+
/>
79+
);
80+
81+
expect(
82+
(wrapper.find('[data-test-subj="pin"]').first().props() as ButtonIcon).isDisabled
83+
).toBe(true);
84+
});
85+
});
1986
});

x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const Pin = React.memo<Props>(
3434
iconSize={iconSize}
3535
iconType={getPinIcon(pinned)}
3636
onClick={onClick}
37-
isDisabled={isTemplate}
37+
isDisabled={isTemplate || !allowUnpinning}
3838
/>
3939
);
4040
}

0 commit comments

Comments
 (0)