-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security Solution][Notes] Make MAX_UNASSOCIATED_NOTES an advanced Kibana setting #194947
Changes from all commits
8d47035
efb7ffe
f26fce3
d4cd1cf
9e64bd4
6e06ae5
07c7223
46bd9a7
52de3b8
d6ba97a
3fa05f8
b0a0e3f
9919cb9
c4a8eb5
815908e
544d68b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
import type { PersistNoteRouteResponse } from '../../../common/api/timeline'; | ||
import { KibanaServices } from '../../common/lib/kibana'; | ||
import * as api from './api'; | ||
|
||
jest.mock('../../common/lib/kibana', () => { | ||
return { | ||
KibanaServices: { | ||
get: jest.fn(), | ||
}, | ||
}; | ||
}); | ||
|
||
describe('Notes API client', () => { | ||
beforeAll(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
describe('create note', () => { | ||
it('should throw an error when a response code other than 200 is returned', async () => { | ||
const errorResponse: PersistNoteRouteResponse = { | ||
data: { | ||
persistNote: { | ||
code: 500, | ||
message: 'Internal server error', | ||
note: { | ||
timelineId: '1', | ||
noteId: '2', | ||
version: '3', | ||
}, | ||
}, | ||
}, | ||
}; | ||
(KibanaServices.get as jest.Mock).mockReturnValue({ | ||
http: { | ||
patch: jest.fn().mockReturnValue(errorResponse), | ||
}, | ||
}); | ||
|
||
expect(async () => | ||
api.createNote({ | ||
note: { | ||
timelineId: '1', | ||
}, | ||
}) | ||
).rejects.toThrow(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,12 @@ | |
* 2.0. | ||
*/ | ||
|
||
import type { BareNote, Note } from '../../../common/api/timeline'; | ||
import type { | ||
BareNote, | ||
DeleteNoteResponse, | ||
GetNotesResponse, | ||
PersistNoteRouteResponse, | ||
} from '../../../common/api/timeline'; | ||
import { KibanaServices } from '../../common/lib/kibana'; | ||
import { NOTE_URL } from '../../../common/constants'; | ||
|
||
|
@@ -16,16 +21,18 @@ import { NOTE_URL } from '../../../common/constants'; | |
*/ | ||
export const createNote = async ({ note }: { note: BareNote }) => { | ||
try { | ||
const response = await KibanaServices.get().http.patch<{ | ||
data: { persistNote: { code: number; message: string; note: Note } }; | ||
}>(NOTE_URL, { | ||
const response = await KibanaServices.get().http.patch<PersistNoteRouteResponse>(NOTE_URL, { | ||
method: 'PATCH', | ||
body: JSON.stringify({ note }), | ||
version: '2023-10-31', | ||
}); | ||
return response.data.persistNote.note; | ||
const noteResponse = response.data.persistNote; | ||
if (noteResponse.code !== 200) { | ||
throw new Error(noteResponse.message); | ||
} | ||
return noteResponse.note; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding unit tests for this case. |
||
} catch (err) { | ||
throw new Error(`Failed to stringify query: ${JSON.stringify(err)}`); | ||
throw new Error(('message' in err && err.message) || 'Request failed'); | ||
} | ||
}; | ||
|
||
|
@@ -44,56 +51,47 @@ export const fetchNotes = async ({ | |
filter: string; | ||
search: string; | ||
}) => { | ||
const response = await KibanaServices.get().http.get<{ totalCount: number; notes: Note[] }>( | ||
NOTE_URL, | ||
{ | ||
query: { | ||
page, | ||
perPage, | ||
sortField, | ||
sortOrder, | ||
filter, | ||
search, | ||
}, | ||
version: '2023-10-31', | ||
} | ||
); | ||
const response = await KibanaServices.get().http.get<GetNotesResponse>(NOTE_URL, { | ||
query: { | ||
page, | ||
perPage, | ||
sortField, | ||
sortOrder, | ||
filter, | ||
search, | ||
}, | ||
version: '2023-10-31', | ||
}); | ||
return response; | ||
}; | ||
|
||
/** | ||
* Fetches all the notes for an array of document ids | ||
*/ | ||
export const fetchNotesByDocumentIds = async (documentIds: string[]) => { | ||
const response = await KibanaServices.get().http.get<{ notes: Note[]; totalCount: number }>( | ||
NOTE_URL, | ||
{ | ||
query: { documentIds }, | ||
version: '2023-10-31', | ||
} | ||
); | ||
const response = await KibanaServices.get().http.get<GetNotesResponse>(NOTE_URL, { | ||
query: { documentIds }, | ||
version: '2023-10-31', | ||
}); | ||
return response; | ||
}; | ||
|
||
/** | ||
* Fetches all the notes for an array of saved object ids | ||
*/ | ||
export const fetchNotesBySaveObjectIds = async (savedObjectIds: string[]) => { | ||
const response = await KibanaServices.get().http.get<{ notes: Note[]; totalCount: number }>( | ||
NOTE_URL, | ||
{ | ||
query: { savedObjectIds }, | ||
version: '2023-10-31', | ||
} | ||
); | ||
const response = await KibanaServices.get().http.get<GetNotesResponse>(NOTE_URL, { | ||
query: { savedObjectIds }, | ||
version: '2023-10-31', | ||
}); | ||
return response; | ||
}; | ||
|
||
/** | ||
* Deletes multiple notes | ||
*/ | ||
export const deleteNotes = async (noteIds: string[]) => { | ||
const response = await KibanaServices.get().http.delete<{ data: unknown }>(NOTE_URL, { | ||
const response = await KibanaServices.get().http.delete<DeleteNoteResponse>(NOTE_URL, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 on adding all the types. |
||
body: JSON.stringify({ noteIds }), | ||
version: '2023-10-31', | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ import { | |
ReqStatus, | ||
selectCreateNoteError, | ||
selectCreateNoteStatus, | ||
userClosedCreateErrorToast, | ||
} from '../store/notes.slice'; | ||
import { MarkdownEditor } from '../../common/components/markdown_editor'; | ||
|
||
|
@@ -101,14 +102,19 @@ export const AddNote = memo( | |
setEditorValue(''); | ||
}, [dispatch, editorValue, eventId, telemetry, timelineId, onNoteAdd]); | ||
|
||
const resetError = useCallback(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. currently if the user tries to close the error toast using the cross icon in the top-right corner nothing happens. You're forced to click on the Screen.Recording.2024-10-10.at.4.35.21.PM.movThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is now fixed, thanks! |
||
dispatch(userClosedCreateErrorToast()); | ||
}, [dispatch]); | ||
|
||
// show a toast if the create note call fails | ||
useEffect(() => { | ||
if (createStatus === ReqStatus.Failed && createError) { | ||
addErrorToast(null, { | ||
addErrorToast(createError, { | ||
title: CREATE_NOTE_ERROR, | ||
}); | ||
resetError(); | ||
} | ||
}, [addErrorToast, createError, createStatus]); | ||
}, [addErrorToast, createError, createStatus, resetError]); | ||
|
||
const buttonDisabled = useMemo( | ||
() => disableButton || editorValue.trim().length === 0 || isMarkdownInvalid, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider renaming this to something as below because current name feels like it is the number of MAX_NOTES but not the setting name.