diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx
index 88c77b5d09160c..5e4e390ac50776 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx
@@ -16,6 +16,7 @@ import {
EuiIcon,
EuiSpacer,
EuiToolTip,
+ useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { useDispatch, useSelector } from 'react-redux';
@@ -40,31 +41,34 @@ import {
} from '../../../../notes/store/notes.slice';
import { MarkdownEditor } from '../../../../common/components/markdown_editor';
-const timelineCheckBoxId = 'xpack.securitySolution.notes.attachToTimelineCheckboxId';
+const timelineCheckBoxId = 'xpack.securitySolution.flyout.left.notes.attachToTimelineCheckboxId';
export const MARKDOWN_ARIA_LABEL = i18n.translate(
- 'xpack.securitySolution.notes.markdownAriaLabel',
+ 'xpack.securitySolution.flyout.left.notes.markdownAriaLabel',
{
defaultMessage: 'Note',
}
);
-export const ADD_NOTE_BUTTON = i18n.translate('xpack.securitySolution.notes.addNoteBtnLabel', {
- defaultMessage: 'Add note',
-});
+export const ADD_NOTE_BUTTON = i18n.translate(
+ 'xpack.securitySolution.flyout.left.notes.addNoteBtnLabel',
+ {
+ defaultMessage: 'Add note',
+ }
+);
export const CREATE_NOTE_ERROR = i18n.translate(
- 'xpack.securitySolution.notes.createNoteErrorLabel',
+ 'xpack.securitySolution.flyout.left.notes.createNoteErrorLabel',
{
defaultMessage: 'Error create note',
}
);
export const ATTACH_TO_TIMELINE_CHECKBOX = i18n.translate(
- 'xpack.securitySolution.notes.attachToTimelineCheckboxLabel',
+ 'xpack.securitySolution.flyout.left.notes.attachToTimelineCheckboxLabel',
{
defaultMessage: 'Attach to active timeline',
}
);
export const ATTACH_TO_TIMELINE_INFO = i18n.translate(
- 'xpack.securitySolution.notes.attachToTimelineInfoLabel',
+ 'xpack.securitySolution.flyout.left.notes.attachToTimelineInfoLabel',
{
defaultMessage: 'The active timeline must be saved before a note can be associated with it',
}
@@ -83,6 +87,7 @@ export interface AddNewNoteProps {
*/
export const AddNote = memo(({ eventId }: AddNewNoteProps) => {
const { telemetry } = useKibana().services;
+ const { euiTheme } = useEuiTheme();
const dispatch = useDispatch();
const { addError: addErrorToast } = useAppToasts();
const [editorValue, setEditorValue] = useState('');
@@ -171,7 +176,7 @@ export const AddNote = memo(({ eventId }: AddNewNoteProps) => {
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx
index 6fb9241973bac0..088dc9cdad3239 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx
@@ -42,23 +42,29 @@ import {
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { useUserPrivileges } from '../../../../common/components/user_privileges';
-export const ADDED_A_NOTE = i18n.translate('xpack.securitySolution.notes.addedANoteLabel', {
- defaultMessage: 'added a note',
-});
+export const ADDED_A_NOTE = i18n.translate(
+ 'xpack.securitySolution.flyout.left.notes.addedANoteLabel',
+ {
+ defaultMessage: 'added a note',
+ }
+);
export const FETCH_NOTES_ERROR = i18n.translate(
- 'xpack.securitySolution.notes.fetchNotesErrorLabel',
+ 'xpack.securitySolution.flyout.left.notes.fetchNotesErrorLabel',
{
defaultMessage: 'Error fetching notes',
}
);
-export const NO_NOTES = i18n.translate('xpack.securitySolution.notes.noNotesLabel', {
+export const NO_NOTES = i18n.translate('xpack.securitySolution.flyout.left.notes.noNotesLabel', {
defaultMessage: 'No notes have been created for this document',
});
-export const DELETE_NOTE = i18n.translate('xpack.securitySolution.notes.deleteNoteLabel', {
- defaultMessage: 'Delete note',
-});
+export const DELETE_NOTE = i18n.translate(
+ 'xpack.securitySolution.flyout.left.notes.deleteNoteLabel',
+ {
+ defaultMessage: 'Delete note',
+ }
+);
export const DELETE_NOTE_ERROR = i18n.translate(
- 'xpack.securitySolution.notes.deleteNoteErrorLabel',
+ 'xpack.securitySolution.flyout.left.notes.deleteNoteErrorLabel',
{
defaultMessage: 'Error deleting note',
}
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_block.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_block.tsx
index 84259ffcf0eb62..fac083f90de5f6 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_block.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_block.tsx
@@ -30,7 +30,7 @@ export interface AlertHeaderBlockProps {
export const AlertHeaderBlock: VFC = memo(
({ title, children, 'data-test-subj': dataTestSubj }) => (
-
+
{title}
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx
index 437fb5d9e21b24..f70cd2aae3e8de 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx
@@ -8,12 +8,23 @@
import React from 'react';
import { render } from '@testing-library/react';
import { DocumentDetailsContext } from '../../shared/context';
-import { NOTES_COUNT_TEST_ID, NOTES_LOADING_TEST_ID, NOTES_TITLE_TEST_ID } from './test_ids';
+import {
+ NOTES_ADD_NOTE_BUTTON_TEST_ID,
+ NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID,
+ NOTES_COUNT_TEST_ID,
+ NOTES_LOADING_TEST_ID,
+ NOTES_TITLE_TEST_ID,
+} from './test_ids';
import { FETCH_NOTES_ERROR, Notes } from './notes';
import { mockContextValue } from '../../shared/mocks/mock_context';
import { createMockStore, mockGlobalState, TestProviders } from '../../../../common/mock';
import { ReqStatus } from '../../../../notes';
import type { Note } from '../../../../../common/api/timeline';
+import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys';
+import { LeftPanelNotesTab } from '../../left';
+
+jest.mock('@kbn/expandable-flyout');
const mockAddError = jest.fn();
jest.mock('../../../../common/hooks/use_app_toasts', () => ({
@@ -33,6 +44,8 @@ jest.mock('react-redux', () => {
describe('', () => {
it('should render loading spinner', () => {
+ (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: jest.fn() });
+
const store = createMockStore({
...mockGlobalState,
notes: {
@@ -58,7 +71,38 @@ describe('', () => {
expect(getByTestId(NOTES_LOADING_TEST_ID)).toBeInTheDocument();
});
- it('should render number of notes', () => {
+ it('should render Add note button if no notes are present', () => {
+ const mockOpenLeftPanel = jest.fn();
+ (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
+
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+
+ const button = getByTestId(NOTES_ADD_NOTE_BUTTON_TEST_ID);
+ expect(button).toBeInTheDocument();
+
+ button.click();
+
+ expect(mockOpenLeftPanel).toHaveBeenCalledWith({
+ id: DocumentDetailsLeftPanelKey,
+ path: { tab: LeftPanelNotesTab },
+ params: {
+ id: mockContextValue.eventId,
+ indexName: mockContextValue.indexName,
+ scopeId: mockContextValue.scopeId,
+ },
+ });
+ });
+
+ it('should render number of notes and plus button', () => {
+ const mockOpenLeftPanel = jest.fn();
+ (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
+
const contextValue = {
...mockContextValue,
eventId: '1',
@@ -72,10 +116,23 @@ describe('', () => {
);
- expect(getByTestId(NOTES_TITLE_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(NOTES_TITLE_TEST_ID)).toHaveTextContent('Notes');
expect(getByTestId(NOTES_COUNT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(NOTES_COUNT_TEST_ID)).toHaveTextContent('1');
+
+ const button = getByTestId(NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID);
+
+ expect(button).toBeInTheDocument();
+ button.click();
+
+ expect(mockOpenLeftPanel).toHaveBeenCalledWith({
+ id: DocumentDetailsLeftPanelKey,
+ path: { tab: LeftPanelNotesTab },
+ params: {
+ id: contextValue.eventId,
+ indexName: mockContextValue.indexName,
+ scopeId: mockContextValue.scopeId,
+ },
+ });
});
it('should render number of notes in scientific notation for big numbers', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx
index 41f96c90f89ec4..a981a16117a680 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx
@@ -5,14 +5,30 @@
* 2.0.
*/
-import React, { memo, useEffect } from 'react';
+import React, { memo, useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
-import { EuiLoadingSpinner } from '@elastic/eui';
+import {
+ EuiButtonEmpty,
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingSpinner,
+ useEuiTheme,
+} from '@elastic/eui';
+import { css } from '@emotion/react';
+import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys';
import { FormattedCount } from '../../../../common/components/formatted_number';
import { useDocumentDetailsContext } from '../../shared/context';
-import { NOTES_COUNT_TEST_ID, NOTES_LOADING_TEST_ID, NOTES_TITLE_TEST_ID } from './test_ids';
+import {
+ NOTES_ADD_NOTE_BUTTON_TEST_ID,
+ NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID,
+ NOTES_COUNT_TEST_ID,
+ NOTES_LOADING_TEST_ID,
+ NOTES_TITLE_TEST_ID,
+} from './test_ids';
import type { State } from '../../../../common/store';
import type { Note } from '../../../../../common/api/timeline';
import {
@@ -20,26 +36,49 @@ import {
ReqStatus,
selectFetchNotesByDocumentIdsError,
selectFetchNotesByDocumentIdsStatus,
- selectSortedNotesByDocumentId,
+ selectNotesByDocumentId,
} from '../../../../notes/store/notes.slice';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { AlertHeaderBlock } from './alert_header_block';
+import { LeftPanelNotesTab } from '../../left';
export const FETCH_NOTES_ERROR = i18n.translate(
- 'xpack.securitySolution.notes.fetchNotesErrorLabel',
+ 'xpack.securitySolution.flyout.right.notes.fetchNotesErrorLabel',
{
defaultMessage: 'Error fetching notes',
}
);
+export const ADD_NOTE_BUTTON = i18n.translate(
+ 'xpack.securitySolution.flyout.right.notes.addNoteButtonLabel',
+ {
+ defaultMessage: 'Add note',
+ }
+);
/**
* Renders a block with the number of notes for the event
*/
export const Notes = memo(() => {
+ const { euiTheme } = useEuiTheme();
const dispatch = useDispatch();
- const { eventId } = useDocumentDetailsContext();
+ const { eventId, indexName, scopeId } = useDocumentDetailsContext();
const { addError: addErrorToast } = useAppToasts();
+ const { openLeftPanel } = useExpandableFlyoutApi();
+ const openExpandedFlyoutNotesTab = useCallback(
+ () =>
+ openLeftPanel({
+ id: DocumentDetailsLeftPanelKey,
+ path: { tab: LeftPanelNotesTab },
+ params: {
+ id: eventId,
+ indexName,
+ scopeId,
+ },
+ }),
+ [eventId, indexName, openLeftPanel, scopeId]
+ );
+
useEffect(() => {
dispatch(fetchNotesByDocumentIds({ documentIds: [eventId] }));
}, [dispatch, eventId]);
@@ -47,12 +86,7 @@ export const Notes = memo(() => {
const fetchStatus = useSelector((state: State) => selectFetchNotesByDocumentIdsStatus(state));
const fetchError = useSelector((state: State) => selectFetchNotesByDocumentIdsError(state));
- const notes: Note[] = useSelector((state: State) =>
- selectSortedNotesByDocumentId(state, {
- documentId: eventId,
- sort: { field: 'created', direction: 'desc' },
- })
- );
+ const notes: Note[] = useSelector((state: State) => selectNotesByDocumentId(state, eventId));
// show a toast if the fetch notes call fails
useEffect(() => {
@@ -76,9 +110,36 @@ export const Notes = memo(() => {
{fetchStatus === ReqStatus.Loading ? (
) : (
-
-
-
+ <>
+ {notes.length === 0 ? (
+
+ {ADD_NOTE_BUTTON}
+
+ ) : (
+
+
+
+
+
+
+
+
+ )}
+ >
)}
);
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts
index 83f833a4b24ace..40670ddc7110a1 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts
@@ -34,6 +34,9 @@ export const SHARE_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}ShareButton` as con
export const CHAT_BUTTON_TEST_ID = 'newChatByTitle' as const;
export const NOTES_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesTitle` as const;
+export const NOTES_ADD_NOTE_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesAddNoteButton` as const;
+export const NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID =
+ `${FLYOUT_HEADER_TEST_ID}NotesAddNoteIconButton` as const;
export const NOTES_COUNT_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesCount` as const;
export const NOTES_LOADING_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesLoading` as const;
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index a14f2ce6d398dd..0a9ddb6c6b7535 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -39698,17 +39698,9 @@
"xpack.securitySolution.noPermissionsTitle": "Privilèges requis",
"xpack.securitySolution.noPrivilegesDefaultMessage": "Pour afficher cette page, vous devez mettre à jour les privilèges. Pour en savoir plus, contactez votre administrateur Kibana.",
"xpack.securitySolution.noPrivilegesPerPageMessage": "Pour afficher {pageName}, vous devez mettre à jour les privilèges. Pour en savoir plus, contactez votre administrateur Kibana.",
- "xpack.securitySolution.notes.addedANoteLabel": "a ajouté une note",
- "xpack.securitySolution.notes.addNoteBtnLabel": "Ajouter la note",
"xpack.securitySolution.notes.addNoteButtonLabel": "Ajouter la note",
- "xpack.securitySolution.notes.attachToTimelineCheckboxLabel": "Attacher à la chronologie active",
- "xpack.securitySolution.notes.attachToTimelineInfoLabel": "La chronologie active doit être enregistrée avant qu'une note puisse lui être associée",
"xpack.securitySolution.notes.cancelButtonLabel": "Annuler",
"xpack.securitySolution.notes.createdByLabel": "Créé par",
- "xpack.securitySolution.notes.createNoteErrorLabel": "Erreur lors de la création de la note",
- "xpack.securitySolution.notes.deleteNoteErrorLabel": "Erreur lors de la suppression de la note",
- "xpack.securitySolution.notes.deleteNoteLabel": "Supprimer la note",
- "xpack.securitySolution.notes.fetchNotesErrorLabel": "Erreur lors de la récupération des notes",
"xpack.securitySolution.notes.management.batchActionsTitle": "Actions groupées",
"xpack.securitySolution.notes.management.createdByColumnTitle": "Créé par",
"xpack.securitySolution.notes.management.createdColumnTitle": "Créé",
@@ -39725,8 +39717,6 @@
"xpack.securitySolution.notes.management.tableError": "Impossible de charger les notes",
"xpack.securitySolution.notes.management.timelineColumnTitle": "Chronologie",
"xpack.securitySolution.notes.management.viewEventInTimeline": "Afficher l'événement dans la chronologie",
- "xpack.securitySolution.notes.markdownAriaLabel": "Note",
- "xpack.securitySolution.notes.noNotesLabel": "Aucune note n'a été créée pour ce document",
"xpack.securitySolution.notes.noteLabel": "Note",
"xpack.securitySolution.notes.notesTitle": "Notes",
"xpack.securitySolution.notes.search.FilterByUserOrNotePlaceholder": "Filtre par utilisateur ou note",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 5184bf3718ef6d..6e3754bb7046b4 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -39441,17 +39441,9 @@
"xpack.securitySolution.noPermissionsTitle": "権限が必要です",
"xpack.securitySolution.noPrivilegesDefaultMessage": "このページを表示するには、権限を更新する必要があります。詳細については、Kibana管理者に連絡してください。",
"xpack.securitySolution.noPrivilegesPerPageMessage": "{pageName}を表示するには、権限を更新する必要があります。詳細については、Kibana管理者に連絡してください。",
- "xpack.securitySolution.notes.addedANoteLabel": "メモを追加しました",
- "xpack.securitySolution.notes.addNoteBtnLabel": "メモを追加",
"xpack.securitySolution.notes.addNoteButtonLabel": "メモを追加",
- "xpack.securitySolution.notes.attachToTimelineCheckboxLabel": "アクティブなタイムラインに関連付ける",
- "xpack.securitySolution.notes.attachToTimelineInfoLabel": "メモを関連付けるには、アクティブなタイムラインを保存する必要があります",
"xpack.securitySolution.notes.cancelButtonLabel": "キャンセル",
"xpack.securitySolution.notes.createdByLabel": "作成者",
- "xpack.securitySolution.notes.createNoteErrorLabel": "作成エラー",
- "xpack.securitySolution.notes.deleteNoteErrorLabel": "メモの削除エラー",
- "xpack.securitySolution.notes.deleteNoteLabel": "メモを削除",
- "xpack.securitySolution.notes.fetchNotesErrorLabel": "メモの取得エラー",
"xpack.securitySolution.notes.management.batchActionsTitle": "一斉アクション",
"xpack.securitySolution.notes.management.createdByColumnTitle": "作成者",
"xpack.securitySolution.notes.management.createdColumnTitle": "作成済み",
@@ -39468,8 +39460,6 @@
"xpack.securitySolution.notes.management.tableError": "メモを読み込めません",
"xpack.securitySolution.notes.management.timelineColumnTitle": "Timeline",
"xpack.securitySolution.notes.management.viewEventInTimeline": "タイムラインでイベントを表示",
- "xpack.securitySolution.notes.markdownAriaLabel": "注",
- "xpack.securitySolution.notes.noNotesLabel": "このドキュメントに作成されたメモはありません",
"xpack.securitySolution.notes.noteLabel": "注",
"xpack.securitySolution.notes.notesTitle": "メモ",
"xpack.securitySolution.notes.search.FilterByUserOrNotePlaceholder": "ユーザーまたはメモでフィルター",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index dcc0af6a008c34..67d9fa5e067a0f 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -39487,17 +39487,9 @@
"xpack.securitySolution.noPermissionsTitle": "需要权限",
"xpack.securitySolution.noPrivilegesDefaultMessage": "要查看此页面,必须更新权限。有关详细信息,请联系您的 Kibana 管理员。",
"xpack.securitySolution.noPrivilegesPerPageMessage": "要查看 {pageName},必须更新权限。有关详细信息,请联系您的 Kibana 管理员。",
- "xpack.securitySolution.notes.addedANoteLabel": "已添加备注",
- "xpack.securitySolution.notes.addNoteBtnLabel": "添加备注",
"xpack.securitySolution.notes.addNoteButtonLabel": "添加备注",
- "xpack.securitySolution.notes.attachToTimelineCheckboxLabel": "附加到活动时间线",
- "xpack.securitySolution.notes.attachToTimelineInfoLabel": "必须先保存活动时间线,然后才能将备注与其关联起来",
"xpack.securitySolution.notes.cancelButtonLabel": "取消",
"xpack.securitySolution.notes.createdByLabel": "创建者",
- "xpack.securitySolution.notes.createNoteErrorLabel": "创建备注时出错",
- "xpack.securitySolution.notes.deleteNoteErrorLabel": "删除备注时出错",
- "xpack.securitySolution.notes.deleteNoteLabel": "删除备注",
- "xpack.securitySolution.notes.fetchNotesErrorLabel": "提取备注时出错",
"xpack.securitySolution.notes.management.batchActionsTitle": "批处理操作",
"xpack.securitySolution.notes.management.createdByColumnTitle": "创建者",
"xpack.securitySolution.notes.management.createdColumnTitle": "创建时间",
@@ -39514,8 +39506,6 @@
"xpack.securitySolution.notes.management.tableError": "无法加载备注",
"xpack.securitySolution.notes.management.timelineColumnTitle": "时间线",
"xpack.securitySolution.notes.management.viewEventInTimeline": "在时间线中查看事件",
- "xpack.securitySolution.notes.markdownAriaLabel": "备注",
- "xpack.securitySolution.notes.noNotesLabel": "尚未为此文档创建备注",
"xpack.securitySolution.notes.noteLabel": "备注",
"xpack.securitySolution.notes.notesTitle": "备注",
"xpack.securitySolution.notes.search.FilterByUserOrNotePlaceholder": "按用户或备注筛选",