From 5cdbd667ee321bd23ae46fc2926dfd269e1673f7 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:59:37 +1000 Subject: [PATCH] [8.x] chore(rca): show full name in notes and store profile id in model (#193211) (#193330) # Backport This will backport the following commits from `main` to `8.x`: - [chore(rca): show full name in notes and store profile id in model (#193211)](https://github.com/elastic/kibana/pull/193211) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Kevin Delemme --- .../public/hooks/query_key_factory.ts | 2 + .../public/hooks/use_fetch_user_profiles.tsx | 63 ++++++++++++ .../investigation_notes.tsx | 10 +- .../components/investigation_notes/note.tsx | 95 +++++++++++-------- .../list/components/investigation_list.tsx | 27 ++++++ .../server/services/create_investigation.ts | 2 +- .../services/create_investigation_item.ts | 2 +- .../services/create_investigation_note.ts | 2 +- .../services/delete_investigation_item.ts | 2 +- .../services/delete_investigation_note.ts | 2 +- .../services/update_investigation_item.ts | 2 +- .../services/update_investigation_note.ts | 2 +- 12 files changed, 163 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_user_profiles.tsx diff --git a/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts b/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts index 454c77ddf56e0..4fa97e3eda1dd 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts +++ b/x-pack/plugins/observability_solution/investigate_app/public/hooks/query_key_factory.ts @@ -9,6 +9,8 @@ export const investigationKeys = { all: ['investigations'] as const, + userProfiles: (profileIds: Set) => + [...investigationKeys.all, 'userProfiles', ...profileIds] as const, tags: () => [...investigationKeys.all, 'tags'] as const, lists: () => [...investigationKeys.all, 'list'] as const, list: (params: { page: number; perPage: number; search?: string; filter?: string }) => diff --git a/x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_user_profiles.tsx b/x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_user_profiles.tsx new file mode 100644 index 0000000000000..80a5017fe479d --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/public/hooks/use_fetch_user_profiles.tsx @@ -0,0 +1,63 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { UserProfile } from '@kbn/security-plugin/common'; +import { useQuery } from '@tanstack/react-query'; +import { Dictionary, keyBy } from 'lodash'; +import { investigationKeys } from './query_key_factory'; +import { useKibana } from './use_kibana'; + +export interface Params { + profileIds: Set; +} + +export interface Response { + isInitialLoading: boolean; + isLoading: boolean; + isRefetching: boolean; + isSuccess: boolean; + isError: boolean; + data: Dictionary | undefined; +} + +export function useFetchUserProfiles({ profileIds }: Params) { + const { + core: { + notifications: { toasts }, + userProfile, + }, + } = useKibana(); + + const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ + queryKey: investigationKeys.userProfiles(profileIds), + queryFn: async () => { + const userProfiles = await userProfile.bulkGet({ uids: profileIds }); + return keyBy(userProfiles, 'uid'); + }, + enabled: profileIds.size > 0, + retry: false, + cacheTime: Infinity, + staleTime: Infinity, + onError: (error: Error) => { + toasts.addError(error, { + title: i18n.translate('xpack.investigateApp.useFetchUserProfiles.errorTitle', { + defaultMessage: 'Something went wrong while fetching user profiles', + }), + }); + }, + }); + + return { + data, + isInitialLoading, + isLoading, + isRefetching, + isSuccess, + isError, + }; +} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/investigation_notes.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/investigation_notes.tsx index 9e1741c1afd12..ec63b09358159 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/investigation_notes.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/investigation_notes.tsx @@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n'; import { InvestigationNoteResponse } from '@kbn/investigation-shared'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; import React, { useState } from 'react'; +import { useFetchUserProfiles } from '../../../../hooks/use_fetch_user_profiles'; import { useTheme } from '../../../../hooks/use_theme'; import { useInvestigation } from '../../contexts/investigation_context'; import { Note } from './note'; @@ -30,8 +31,11 @@ export interface Props { export function InvestigationNotes({ user }: Props) { const theme = useTheme(); const { investigation, addNote, isAddingNote } = useInvestigation(); - const [noteInput, setNoteInput] = useState(''); + const { data: userProfiles, isLoading: isLoadingUserProfiles } = useFetchUserProfiles({ + profileIds: new Set(investigation?.notes.map((note) => note.createdBy)), + }); + const [noteInput, setNoteInput] = useState(''); const onAddNote = async (content: string) => { await addNote(content); setNoteInput(''); @@ -59,7 +63,9 @@ export function InvestigationNotes({ user }: Props) { ); })} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/note.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/note.tsx index 4db3237b9a90a..8bd0f14c9d892 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/note.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_notes/note.tsx @@ -5,15 +5,16 @@ * 2.0. */ import { - EuiAvatar, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, + EuiLoadingSpinner, EuiMarkdownFormat, EuiText, } from '@elastic/eui'; import { css } from '@emotion/css'; import { InvestigationNoteResponse } from '@kbn/investigation-shared'; +import { UserProfile } from '@kbn/security-plugin/common'; // eslint-disable-next-line import/no-extraneous-dependencies import { formatDistance } from 'date-fns'; import React, { useState } from 'react'; @@ -27,14 +28,16 @@ const textContainerClassName = css` interface Props { note: InvestigationNoteResponse; - disabled: boolean; + isOwner: boolean; + userProfile?: UserProfile; + userProfileLoading: boolean; } -export function Note({ note, disabled }: Props) { +export function Note({ note, isOwner, userProfile, userProfileLoading }: Props) { + const theme = useTheme(); const [isEditing, setIsEditing] = useState(false); const { deleteNote, isDeletingNote } = useInvestigation(); - const theme = useTheme(); const timelineContainerClassName = css` padding-bottom: 16px; border-bottom: 1px solid ${theme.colors.lightShade}; @@ -43,51 +46,65 @@ export function Note({ note, disabled }: Props) { } `; + const actionButtonClassname = css` + color: ${theme.colors.mediumShade}; + :hover { + color: ${theme.colors.darkShade}; + } + `; + + const timestampClassName = css` + color: ${theme.colors.darkShade}; + `; + return ( - - + + - + {userProfileLoading ? ( + + ) : ( + + {userProfile?.user.full_name ?? userProfile?.user.username ?? note?.createdBy} + + )} - + {formatDistance(new Date(note.createdAt), new Date(), { addSuffix: true })} - - - { - setIsEditing(!isEditing); - }} - /> - - - await deleteNote(note.id)} - data-test-subj="deleteInvestigationNoteButton" - /> - - + {isOwner && ( + + + { + setIsEditing(!isEditing); + }} + className={actionButtonClassname} + /> + + + await deleteNote(note.id)} + data-test-subj="deleteInvestigationNoteButton" + className={actionButtonClassname} + /> + + + )} {isEditing ? ( diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx index 8e1bc793b545e..d4aa75fc2bdf2 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/list/components/investigation_list.tsx @@ -6,6 +6,7 @@ */ import { Criteria, + EuiAvatar, EuiBadge, EuiBasicTable, EuiBasicTableColumn, @@ -21,6 +22,7 @@ import React, { useState } from 'react'; import { paths } from '../../../../common/paths'; import { InvestigationStatusBadge } from '../../../components/investigation_status_badge/investigation_status_badge'; import { useFetchInvestigationList } from '../../../hooks/use_fetch_investigation_list'; +import { useFetchUserProfiles } from '../../../hooks/use_fetch_user_profiles'; import { useKibana } from '../../../hooks/use_kibana'; import { InvestigationListActions } from './investigation_list_actions'; import { InvestigationsError } from './investigations_error'; @@ -49,6 +51,10 @@ export function InvestigationList() { filter: toFilter(status, tags), }); + const { data: userProfiles, isLoading: isUserProfilesLoading } = useFetchUserProfiles({ + profileIds: new Set(data?.results.map((i) => i.createdBy)), + }); + const investigations = data?.results ?? []; const totalItemCount = data?.total ?? 0; @@ -75,6 +81,27 @@ export function InvestigationList() { defaultMessage: 'Created by', }), truncateText: true, + render: (value: InvestigationResponse['createdBy']) => { + return isUserProfilesLoading ? ( + + ) : ( + + + + {userProfiles?.[value]?.user.full_name ?? + userProfiles?.[value]?.user.username ?? + value} + + + ); + }, }, { field: 'tags', diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts index eb8277d7d6f83..6a7355c0ef875 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts @@ -23,7 +23,7 @@ export async function createInvestigation( ...params, updatedAt: now, createdAt: now, - createdBy: user.username, + createdBy: user.profile_uid!, status: 'triage', notes: [], items: [], diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts index cf77887aab0a3..548912b576618 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_item.ts @@ -23,7 +23,7 @@ export async function createInvestigationItem( const now = Date.now(); const investigationItem = { id: v4(), - createdBy: user.username, + createdBy: user.profile_uid!, createdAt: now, updatedAt: now, ...params, diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts index 2f74123b6f269..f2fe766cdf52d 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation_note.ts @@ -24,7 +24,7 @@ export async function createInvestigationNote( const investigationNote = { id: v4(), content: params.content, - createdBy: user.username, + createdBy: user.profile_uid!, updatedAt: now, createdAt: now, }; diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_item.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_item.ts index d40938804badc..a9856cc0eaa99 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_item.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_item.ts @@ -19,7 +19,7 @@ export async function deleteInvestigationItem( throw new Error('Note not found'); } - if (item.createdBy !== user.username) { + if (item.createdBy !== user.profile_uid) { throw new Error('User does not have permission to delete note'); } diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_note.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_note.ts index b0a9e7adf8492..fabbd6f0cdb9c 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_note.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/delete_investigation_note.ts @@ -19,7 +19,7 @@ export async function deleteInvestigationNote( throw new Error('Note not found'); } - if (note.createdBy !== user.username) { + if (note.createdBy !== user.profile_uid) { throw new Error('User does not have permission to delete note'); } diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_item.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_item.ts index 7e7f03bc7f12c..f95950560ca08 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_item.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_item.ts @@ -25,7 +25,7 @@ export async function updateInvestigationItem( throw new Error('Cannot change item type'); } - if (item.createdBy !== user.username) { + if (item.createdBy !== user.profile_uid) { throw new Error('User does not have permission to update item'); } diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts index fc4c5a2c0b1fc..9113be5fedf4d 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/update_investigation_note.ts @@ -21,7 +21,7 @@ export async function updateInvestigationNote( throw new Error('Note not found'); } - if (note.createdBy !== user.username) { + if (note.createdBy !== user.profile_uid) { throw new Error('User does not have permission to update note'); }