From f17bad80a4ceb1252a3e3b176081e5390b2581fd Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Tue, 17 Sep 2024 11:26:32 -0400 Subject: [PATCH 1/3] Use profile id on note --- .../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 +++++++++++-------- .../services/create_investigation_note.ts | 2 +- .../services/delete_investigation_note.ts | 2 +- .../services/update_investigation_note.ts | 2 +- 7 files changed, 132 insertions(+), 44 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 85a8c35b63a5e..44352e46997ea 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, stats: () => [...investigationKeys.all, 'stats'] as const, lists: () => [...investigationKeys.all, 'list'] as const, 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..7cbc07968f2ec 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/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_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_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'); } From 32c647a1127c5bec79391770fff6963144dcd196 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Tue, 17 Sep 2024 13:12:03 -0400 Subject: [PATCH 2/3] use profile_id instead of username --- .../investigation_notes.tsx | 2 +- .../list/components/investigation_list.tsx | 28 +++++++++++++++++++ .../server/services/create_investigation.ts | 2 +- .../services/create_investigation_item.ts | 2 +- .../services/delete_investigation_item.ts | 2 +- .../services/update_investigation_item.ts | 2 +- 6 files changed, 33 insertions(+), 5 deletions(-) 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 7cbc07968f2ec..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 @@ -32,7 +32,7 @@ export function InvestigationNotes({ user }: Props) { const theme = useTheme(); const { investigation, addNote, isAddingNote } = useInvestigation(); const { data: userProfiles, isLoading: isLoadingUserProfiles } = useFetchUserProfiles({ - profileIds: new Set(investigation?.notes.map((note) => note.createdBy) ?? []), + profileIds: new Set(investigation?.notes.map((note) => note.createdBy)), }); const [noteInput, setNoteInput] = useState(''); 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 d75710f817703..52b0105e1d910 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, @@ -14,6 +15,7 @@ import { EuiLink, EuiLoadingSpinner, EuiText, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { InvestigationResponse } from '@kbn/investigation-shared/src/rest_specs/investigation'; @@ -27,6 +29,7 @@ import { InvestigationListActions } from './investigation_list_actions'; import { InvestigationStats } from './investigation_stats'; import { InvestigationsError } from './investigations_error'; import { SearchBar } from './search_bar/search_bar'; +import { useFetchUserProfiles } from '../../../hooks/use_fetch_user_profiles'; export function InvestigationList() { const { @@ -51,6 +54,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; @@ -77,6 +84,27 @@ export function InvestigationList() { defaultMessage: 'Created by', }), truncateText: true, + render: (value: InvestigationResponse['createdBy']) => { + return isUserProfilesLoading ? ( + + ) : ( + + + + ); + }, }, { 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/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/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'); } From fb7dd45c6894b747f97a0c0609581677ade9e063 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Wed, 18 Sep 2024 09:35:32 -0400 Subject: [PATCH 3/3] Use avatar and fullname --- .../list/components/investigation_list.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 52b0105e1d910..a65eb12001342 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 @@ -15,7 +15,6 @@ import { EuiLink, EuiLoadingSpinner, EuiText, - EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { InvestigationResponse } from '@kbn/investigation-shared/src/rest_specs/investigation'; @@ -24,12 +23,12 @@ 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 { InvestigationStats } from './investigation_stats'; import { InvestigationsError } from './investigations_error'; import { SearchBar } from './search_bar/search_bar'; -import { useFetchUserProfiles } from '../../../hooks/use_fetch_user_profiles'; export function InvestigationList() { const { @@ -88,12 +87,7 @@ export function InvestigationList() { return isUserProfilesLoading ? ( ) : ( - + - + + {userProfiles?.[value]?.user.full_name ?? + userProfiles?.[value]?.user.username ?? + value} + + ); }, },