Skip to content

Commit 81414fb

Browse files
[SIEM] Fix draft timeline can be attached to a case (#67844)
# Conflicts: # x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx # x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx # x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx # x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx # x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx
1 parent a4da8f5 commit 81414fb

6 files changed

Lines changed: 133 additions & 34 deletions

File tree

x-pack/plugins/siem/public/components/flyout/header/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const StatefulFlyoutHeader = React.memo<Props>(
4545
title,
4646
noteIds,
4747
notesById,
48+
status,
4849
timelineId,
4950
toggleLock,
5051
updateDescription,
@@ -68,6 +69,7 @@ const StatefulFlyoutHeader = React.memo<Props>(
6869
isFavorite={isFavorite}
6970
title={title}
7071
noteIds={noteIds}
72+
status={status}
7173
timelineId={timelineId}
7274
toggleLock={toggleLock}
7375
updateDescription={updateDescription}
@@ -100,6 +102,7 @@ const makeMapStateToProps = () => {
100102
kqlQuery,
101103
title = '',
102104
noteIds = emptyNotesId,
105+
status,
103106
} = timeline;
104107

105108
const history = emptyHistory; // TODO: get history from store via selector
@@ -113,6 +116,7 @@ const makeMapStateToProps = () => {
113116
isFavorite,
114117
isDatepickerLocked: globalInput.linkTo.includes('timeline'),
115118
noteIds,
119+
status,
116120
title,
117121
};
118122
};

x-pack/plugins/siem/public/components/timeline/properties/helpers.tsx

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import styled from 'styled-components';
2323
import { useHistory } from 'react-router-dom';
2424
import { useSelector } from 'react-redux';
2525

26+
import { TimelineStatus } from '../../../../common/types/timeline';
2627
import { Note } from '../../../lib/note';
2728
import { Notes } from '../../notes';
2829
import { AssociateNote, UpdateNote } from '../../notes/helpers';
@@ -85,7 +86,7 @@ export const Description = React.memo<DescriptionProps>(
8586
aria-label={i18n.TIMELINE_DESCRIPTION}
8687
data-test-subj="timeline-description"
8788
fullWidth={true}
88-
onChange={(e) => updateDescription({ id: timelineId, description: e.target.value })}
89+
onChange={e => updateDescription({ id: timelineId, description: e.target.value })}
8990
placeholder={i18n.DESCRIPTION}
9091
spellCheck={true}
9192
value={description}
@@ -107,7 +108,7 @@ export const Name = React.memo<NameProps>(({ timelineId, title, updateTitle }) =
107108
<NameField
108109
aria-label={i18n.TIMELINE_TITLE}
109110
data-test-subj="timeline-title"
110-
onChange={(e) => updateTitle({ id: timelineId, title: e.target.value })}
111+
onChange={e => updateTitle({ id: timelineId, title: e.target.value })}
111112
placeholder={i18n.UNTITLED_TIMELINE}
112113
spellCheck={true}
113114
value={title}
@@ -119,40 +120,44 @@ Name.displayName = 'Name';
119120
interface NewCaseProps {
120121
onClosePopover: () => void;
121122
timelineId: string;
123+
timelineStatus: TimelineStatus;
122124
timelineTitle: string;
123125
}
124126

125-
export const NewCase = React.memo<NewCaseProps>(({ onClosePopover, timelineId, timelineTitle }) => {
126-
const history = useHistory();
127-
const { savedObjectId } = useSelector((state: State) =>
128-
timelineSelectors.selectTimeline(state, timelineId)
129-
);
130-
const handleClick = useCallback(() => {
131-
onClosePopover();
132-
history.push({
133-
pathname: `/${SiemPageName.case}/create`,
134-
state: {
135-
insertTimeline: {
136-
timelineId,
137-
timelineSavedObjectId: savedObjectId,
138-
timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE,
127+
export const NewCase = React.memo<NewCaseProps>(
128+
({ onClosePopover, timelineId, timelineStatus, timelineTitle }) => {
129+
const history = useHistory();
130+
const { savedObjectId } = useSelector((state: State) =>
131+
timelineSelectors.selectTimeline(state, timelineId)
132+
);
133+
const handleClick = useCallback(() => {
134+
onClosePopover();
135+
history.push({
136+
pathname: `/${SiemPageName.case}/create`,
137+
state: {
138+
insertTimeline: {
139+
timelineId,
140+
timelineSavedObjectId: savedObjectId,
141+
timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE,
142+
},
139143
},
140-
},
141-
});
142-
}, [onClosePopover, history, timelineId, timelineTitle]);
144+
});
145+
}, [onClosePopover, history, timelineId, timelineTitle]);
143146

144-
return (
145-
<EuiButtonEmpty
146-
data-test-subj="attach-timeline-case"
147-
color="text"
148-
iconSide="left"
149-
iconType="paperClip"
150-
onClick={handleClick}
151-
>
152-
{i18n.ATTACH_TIMELINE_TO_NEW_CASE}
153-
</EuiButtonEmpty>
154-
);
155-
});
147+
return (
148+
<EuiButtonEmpty
149+
data-test-subj="attach-timeline-case"
150+
color="text"
151+
iconSide="left"
152+
iconType="paperClip"
153+
disabled={timelineStatus === TimelineStatus.draft}
154+
onClick={handleClick}
155+
>
156+
{i18n.ATTACH_TIMELINE_TO_NEW_CASE}
157+
</EuiButtonEmpty>
158+
);
159+
}
160+
);
156161
NewCase.displayName = 'NewCase';
157162

158163
interface NewTimelineProps {

x-pack/plugins/siem/public/components/timeline/properties/index.test.tsx

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import { mount } from 'enzyme';
88
import React from 'react';
99
import { Provider as ReduxStoreProvider } from 'react-redux';
1010

11+
import { TimelineStatus } from '../../../../common/types/timeline';
1112
import { mockGlobalState, apolloClientObservable } from '../../../mock';
1213
import { createStore, State } from '../../../store';
1314
import { useThrottledResizeObserver } from '../../utils';
14-
1515
import { Properties, showDescriptionThreshold, showNotesThreshold } from '.';
1616

1717
jest.mock('../../../lib/kibana');
@@ -22,6 +22,24 @@ jest.mock('../../utils');
2222
width: mockedWidth,
2323
}));
2424

25+
jest.mock('react-redux', () => {
26+
const originalModule = jest.requireActual('react-redux');
27+
28+
return {
29+
...originalModule,
30+
useSelector: jest.fn().mockReturnValue({ savedObjectId: '1' }),
31+
};
32+
});
33+
34+
jest.mock('react-router-dom', () => {
35+
const originalModule = jest.requireActual('react-router-dom');
36+
37+
return {
38+
...originalModule,
39+
useHistory: jest.fn(),
40+
};
41+
});
42+
2543
describe('Properties', () => {
2644
const usersViewing = ['elastic'];
2745

@@ -47,6 +65,7 @@ describe('Properties', () => {
4765
description=""
4866
getNotesByIds={jest.fn()}
4967
noteIds={[]}
68+
status={TimelineStatus.active}
5069
timelineId="abc"
5170
toggleLock={jest.fn()}
5271
updateDescription={jest.fn()}
@@ -57,7 +76,51 @@ describe('Properties', () => {
5776
/>
5877
</ReduxStoreProvider>
5978
);
79+
80+
wrapper
81+
.find('[data-test-subj="settings-gear"]')
82+
.at(0)
83+
.simulate('click');
84+
6085
expect(wrapper.find('[data-test-subj="timeline-properties"]').exists()).toEqual(true);
86+
expect(wrapper.find('button[data-test-subj="attach-timeline-case"]').prop('disabled')).toEqual(
87+
false
88+
);
89+
});
90+
91+
test('renders correctly draft timeline', () => {
92+
const wrapper = mount(
93+
<ReduxStoreProvider store={store}>
94+
<Properties
95+
associateNote={jest.fn()}
96+
createTimeline={jest.fn()}
97+
isDataInTimeline={false}
98+
isDatepickerLocked={false}
99+
isFavorite={false}
100+
title=""
101+
description=""
102+
getNotesByIds={jest.fn()}
103+
noteIds={[]}
104+
status={TimelineStatus.draft}
105+
timelineId="abc"
106+
toggleLock={jest.fn()}
107+
updateDescription={jest.fn()}
108+
updateIsFavorite={jest.fn()}
109+
updateTitle={jest.fn()}
110+
updateNote={jest.fn()}
111+
usersViewing={usersViewing}
112+
/>
113+
</ReduxStoreProvider>
114+
);
115+
116+
wrapper
117+
.find('[data-test-subj="settings-gear"]')
118+
.at(0)
119+
.simulate('click');
120+
121+
expect(wrapper.find('button[data-test-subj="attach-timeline-case"]').prop('disabled')).toEqual(
122+
true
123+
);
61124
});
62125

63126
test('it renders an empty star icon when it is NOT a favorite', () => {
@@ -73,6 +136,7 @@ describe('Properties', () => {
73136
description=""
74137
getNotesByIds={jest.fn()}
75138
noteIds={[]}
139+
status={TimelineStatus.active}
76140
timelineId="abc"
77141
toggleLock={jest.fn()}
78142
updateDescription={jest.fn()}
@@ -100,6 +164,7 @@ describe('Properties', () => {
100164
description=""
101165
getNotesByIds={jest.fn()}
102166
noteIds={[]}
167+
status={TimelineStatus.active}
103168
timelineId="abc"
104169
toggleLock={jest.fn()}
105170
updateDescription={jest.fn()}
@@ -129,6 +194,7 @@ describe('Properties', () => {
129194
description=""
130195
getNotesByIds={jest.fn()}
131196
noteIds={[]}
197+
status={TimelineStatus.active}
132198
timelineId="abc"
133199
toggleLock={jest.fn()}
134200
updateDescription={jest.fn()}
@@ -140,7 +206,12 @@ describe('Properties', () => {
140206
</ReduxStoreProvider>
141207
);
142208

143-
expect(wrapper.find('[data-test-subj="timeline-title"]').first().props().value).toEqual(title);
209+
expect(
210+
wrapper
211+
.find('[data-test-subj="timeline-title"]')
212+
.first()
213+
.props().value
214+
).toEqual(title);
144215
});
145216

146217
test('it renders the date picker with the lock icon', () => {
@@ -156,6 +227,7 @@ describe('Properties', () => {
156227
description=""
157228
getNotesByIds={jest.fn()}
158229
noteIds={[]}
230+
status={TimelineStatus.active}
159231
timelineId="abc"
160232
toggleLock={jest.fn()}
161233
updateDescription={jest.fn()}
@@ -188,6 +260,7 @@ describe('Properties', () => {
188260
description=""
189261
getNotesByIds={jest.fn()}
190262
noteIds={[]}
263+
status={TimelineStatus.active}
191264
timelineId="abc"
192265
toggleLock={jest.fn()}
193266
updateDescription={jest.fn()}
@@ -219,6 +292,7 @@ describe('Properties', () => {
219292
description=""
220293
getNotesByIds={jest.fn()}
221294
noteIds={[]}
295+
status={TimelineStatus.active}
222296
timelineId="abc"
223297
toggleLock={jest.fn()}
224298
updateDescription={jest.fn()}
@@ -253,6 +327,7 @@ describe('Properties', () => {
253327
description={description}
254328
getNotesByIds={jest.fn()}
255329
noteIds={[]}
330+
status={TimelineStatus.active}
256331
timelineId="abc"
257332
toggleLock={jest.fn()}
258333
updateDescription={jest.fn()}
@@ -289,6 +364,7 @@ describe('Properties', () => {
289364
description={description}
290365
getNotesByIds={jest.fn()}
291366
noteIds={[]}
367+
status={TimelineStatus.active}
292368
timelineId="abc"
293369
toggleLock={jest.fn()}
294370
updateDescription={jest.fn()}
@@ -323,6 +399,7 @@ describe('Properties', () => {
323399
description=""
324400
getNotesByIds={jest.fn()}
325401
noteIds={[]}
402+
status={TimelineStatus.active}
326403
timelineId="abc"
327404
toggleLock={jest.fn()}
328405
updateDescription={jest.fn()}
@@ -357,6 +434,7 @@ describe('Properties', () => {
357434
description=""
358435
getNotesByIds={jest.fn()}
359436
noteIds={[]}
437+
status={TimelineStatus.active}
360438
timelineId="abc"
361439
toggleLock={jest.fn()}
362440
updateDescription={jest.fn()}
@@ -389,6 +467,7 @@ describe('Properties', () => {
389467
description=""
390468
getNotesByIds={jest.fn()}
391469
noteIds={[]}
470+
status={TimelineStatus.active}
392471
timelineId="abc"
393472
toggleLock={jest.fn()}
394473
updateDescription={jest.fn()}
@@ -418,6 +497,7 @@ describe('Properties', () => {
418497
description=""
419498
getNotesByIds={jest.fn()}
420499
noteIds={[]}
500+
status={TimelineStatus.active}
421501
timelineId="abc"
422502
toggleLock={jest.fn()}
423503
updateDescription={jest.fn()}
@@ -445,6 +525,7 @@ describe('Properties', () => {
445525
description=""
446526
getNotesByIds={jest.fn()}
447527
noteIds={[]}
528+
status={TimelineStatus.active}
448529
timelineId="abc"
449530
toggleLock={jest.fn()}
450531
updateDescription={jest.fn()}

x-pack/plugins/siem/public/components/timeline/properties/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import React, { useState, useCallback, useMemo } from 'react';
88

9+
import { TimelineStatus } from '../../../../common/types/timeline';
910
import { useThrottledResizeObserver } from '../../utils';
1011
import { Note } from '../../../lib/note';
1112
import { InputsModelId } from '../../../store/inputs/constants';
@@ -31,6 +32,7 @@ interface Props {
3132
isFavorite: boolean;
3233
noteIds: string[];
3334
timelineId: string;
35+
status: TimelineStatus;
3436
title: string;
3537
toggleLock: ToggleLock;
3638
updateDescription: UpdateDescription;
@@ -62,6 +64,7 @@ export const Properties = React.memo<Props>(
6264
isDatepickerLocked,
6365
isFavorite,
6466
noteIds,
67+
status,
6568
timelineId,
6669
title,
6770
toggleLock,
@@ -140,6 +143,7 @@ export const Properties = React.memo<Props>(
140143
showNotesFromWidth={width < showNotesThreshold}
141144
showTimelineModal={showTimelineModal}
142145
showUsersView={title.length > 0}
146+
status={status}
143147
timelineId={timelineId}
144148
title={title}
145149
updateDescription={updateDescription}

0 commit comments

Comments
 (0)