mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
feat(rca): edit notes (#191546)
This commit is contained in:
parent
4c77c7a57c
commit
d06b063eb5
15 changed files with 380 additions and 102 deletions
|
@ -18,6 +18,7 @@ export type * from './create_item';
|
|||
export type * from './delete_item';
|
||||
export type * from './get_items';
|
||||
export type * from './investigation_item';
|
||||
export type * from './update_note';
|
||||
|
||||
export * from './create';
|
||||
export * from './create_note';
|
||||
|
@ -31,3 +32,4 @@ export * from './create_item';
|
|||
export * from './delete_item';
|
||||
export * from './get_items';
|
||||
export * from './investigation_item';
|
||||
export * from './update_note';
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { investigationNoteResponseSchema } from './investigation_note';
|
||||
|
||||
const updateInvestigationNoteParamsSchema = t.type({
|
||||
path: t.type({
|
||||
investigationId: t.string,
|
||||
noteId: t.string,
|
||||
}),
|
||||
body: t.type({
|
||||
content: t.string,
|
||||
}),
|
||||
});
|
||||
|
||||
const updateInvestigationNoteResponseSchema = investigationNoteResponseSchema;
|
||||
|
||||
type UpdateInvestigationNoteParams = t.TypeOf<
|
||||
typeof updateInvestigationNoteParamsSchema.props.body
|
||||
>;
|
||||
type UpdateInvestigationNoteResponse = t.OutputOf<typeof updateInvestigationNoteResponseSchema>;
|
||||
|
||||
export { updateInvestigationNoteParamsSchema, updateInvestigationNoteResponseSchema };
|
||||
export type { UpdateInvestigationNoteParams, UpdateInvestigationNoteResponse };
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
|
||||
import { UpdateInvestigationNoteParams } from '@kbn/investigation-shared';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useKibana } from './use_kibana';
|
||||
|
||||
type ServerError = IHttpFetchError<ResponseErrorBody>;
|
||||
|
||||
export function useUpdateInvestigationNote() {
|
||||
const {
|
||||
core: {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
},
|
||||
} = useKibana();
|
||||
|
||||
return useMutation<
|
||||
void,
|
||||
ServerError,
|
||||
{ investigationId: string; noteId: string; note: UpdateInvestigationNoteParams },
|
||||
{ investigationId: string }
|
||||
>(
|
||||
['deleteInvestigationNote'],
|
||||
({ investigationId, noteId, note }) => {
|
||||
const body = JSON.stringify(note);
|
||||
return http.put<void>(
|
||||
`/api/observability/investigations/${investigationId}/notes/${noteId}`,
|
||||
{ body, version: '2023-10-31' }
|
||||
);
|
||||
},
|
||||
{
|
||||
onSuccess: (response, {}) => {
|
||||
toasts.addSuccess('Note updated');
|
||||
},
|
||||
onError: (error, {}, context) => {
|
||||
toasts.addError(new Error(error.body?.message ?? 'An error occurred'), { title: 'Error' });
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -166,7 +166,7 @@ export function registerEmbeddableItem({
|
|||
services,
|
||||
}: Options) {
|
||||
investigate.registerItemDefinition<EmbeddableItemParams, {}>({
|
||||
type: 'esql',
|
||||
type: 'embeddable',
|
||||
generate: async (option: {
|
||||
itemParams: EmbeddableItemParams;
|
||||
globalParams: GlobalWidgetParameters;
|
||||
|
|
|
@ -59,6 +59,8 @@ interface EsqlItemData {
|
|||
};
|
||||
}
|
||||
|
||||
export const ESQL_ITEM_TYPE = 'esql';
|
||||
|
||||
export function EsqlWidget({
|
||||
suggestion,
|
||||
dataView,
|
||||
|
@ -228,7 +230,7 @@ export function registerEsqlItem({
|
|||
services,
|
||||
}: Options) {
|
||||
investigate.registerItemDefinition<EsqlItemParams, EsqlItemData>({
|
||||
type: 'esql',
|
||||
type: ESQL_ITEM_TYPE,
|
||||
generate: async (option: {
|
||||
itemParams: EsqlItemParams;
|
||||
globalParams: GlobalWidgetParameters;
|
||||
|
|
|
@ -17,7 +17,7 @@ import { ErrorMessage } from '../../../../components/error_message';
|
|||
import { SuggestVisualizationList } from '../../../../components/suggest_visualization_list';
|
||||
import { useKibana } from '../../../../hooks/use_kibana';
|
||||
import { getDateHistogramResults } from '../../../../items/esql_item/get_date_histogram_results';
|
||||
import { EsqlWidget } from '../../../../items/esql_item/register_esql_item';
|
||||
import { ESQL_ITEM_TYPE, EsqlWidget } from '../../../../items/esql_item/register_esql_item';
|
||||
import { getEsFilterFromOverrides } from '../../../../utils/get_es_filter_from_overrides';
|
||||
|
||||
function getItemFromSuggestion({
|
||||
|
@ -29,7 +29,7 @@ function getItemFromSuggestion({
|
|||
}): Item {
|
||||
return {
|
||||
title: suggestion.title,
|
||||
type: 'esql',
|
||||
type: ESQL_ITEM_TYPE,
|
||||
params: {
|
||||
esql: query,
|
||||
suggestion,
|
||||
|
|
|
@ -27,11 +27,11 @@ export function InvestigationDetails({ user, investigationId }: Props) {
|
|||
return (
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={8}>
|
||||
<InvestigationItems investigationId={investigationId} investigation={investigation} />
|
||||
<InvestigationItems investigation={investigation} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={2}>
|
||||
<InvestigationNotes investigationId={investigationId} investigation={investigation} />
|
||||
<InvestigationNotes investigation={investigation} user={user} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -18,13 +18,12 @@ import { InvestigationItemsList } from '../investigation_items_list/investigatio
|
|||
import { InvestigationSearchBar } from '../investigation_search_bar/investigation_search_bar';
|
||||
|
||||
export interface Props {
|
||||
investigationId: string;
|
||||
investigation: GetInvestigationResponse;
|
||||
}
|
||||
|
||||
export function InvestigationItems({ investigationId, investigation }: Props) {
|
||||
export function InvestigationItems({ investigation }: Props) {
|
||||
const { data: items, refetch } = useFetchInvestigationItems({
|
||||
investigationId,
|
||||
investigationId: investigation.id,
|
||||
initialItems: investigation.items,
|
||||
});
|
||||
const renderableItems = useRenderItems({ items, params: investigation.params });
|
||||
|
@ -34,12 +33,12 @@ export function InvestigationItems({ investigationId, investigation }: Props) {
|
|||
useDeleteInvestigationItem();
|
||||
|
||||
const onAddItem = async (item: Item) => {
|
||||
await addInvestigationItem({ investigationId, item });
|
||||
await addInvestigationItem({ investigationId: investigation.id, item });
|
||||
refetch();
|
||||
};
|
||||
|
||||
const onDeleteItem = async (itemId: string) => {
|
||||
await deleteInvestigationItem({ investigationId, itemId });
|
||||
await deleteInvestigationItem({ investigationId: investigation.id, itemId });
|
||||
refetch();
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { InvestigationNoteResponse } from '@kbn/investigation-shared';
|
||||
import React, { useState } from 'react';
|
||||
import { ResizableTextInput } from './resizable_text_input';
|
||||
import { useUpdateInvestigationNote } from '../../../../hooks/use_update_investigation_note';
|
||||
|
||||
interface Props {
|
||||
investigationId: string;
|
||||
note: InvestigationNoteResponse;
|
||||
onCancel: () => void;
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
export function EditNoteForm({ investigationId, note, onCancel, onUpdate }: Props) {
|
||||
const [noteInput, setNoteInput] = useState(note.content);
|
||||
const { mutateAsync: updateNote, isLoading: isUpdating } = useUpdateInvestigationNote();
|
||||
|
||||
const handleUpdateNote = async () => {
|
||||
await updateNote({ investigationId, noteId: note.id, note: { content: noteInput.trim() } });
|
||||
onUpdate();
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<ResizableTextInput
|
||||
disabled={isUpdating}
|
||||
value={noteInput}
|
||||
onChange={(value) => {
|
||||
setNoteInput(value);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
handleUpdateNote();
|
||||
}}
|
||||
placeholder={note.content}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexGroup direction="row" gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
disabled={isUpdating}
|
||||
data-test-subj="cancelEditNoteButton"
|
||||
color="text"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.investigateApp.investigationNotes.cancelEditButtonLabel',
|
||||
{ defaultMessage: 'Cancel' }
|
||||
)}
|
||||
size="m"
|
||||
onClick={() => onCancel()}
|
||||
>
|
||||
{i18n.translate('xpack.investigateApp.investigationNotes.cancelEditButtonLabel', {
|
||||
defaultMessage: 'Cancel',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="updateNoteButton"
|
||||
color="primary"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.investigateApp.investigationNotes.updateNoteButtonLabel',
|
||||
{ defaultMessage: 'Update note' }
|
||||
)}
|
||||
disabled={isUpdating || noteInput.trim() === ''}
|
||||
isLoading={isUpdating}
|
||||
size="m"
|
||||
onClick={() => {
|
||||
handleUpdateNote();
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.investigateApp.investigationNotes.updateNoteButtonLabel', {
|
||||
defaultMessage: 'Update note',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiAvatar,
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -16,43 +15,36 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { InvestigationNoteResponse, GetInvestigationResponse } from '@kbn/investigation-shared';
|
||||
import { GetInvestigationResponse, InvestigationNoteResponse } from '@kbn/investigation-shared';
|
||||
import { AuthenticatedUser } from '@kbn/security-plugin/common';
|
||||
import React, { useState } from 'react';
|
||||
import { useAddInvestigationNote } from '../../../../hooks/use_add_investigation_note';
|
||||
import { useDeleteInvestigationNote } from '../../../../hooks/use_delete_investigation_note';
|
||||
import { useFetchInvestigationNotes } from '../../../../hooks/use_fetch_investigation_notes';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { Note } from './note';
|
||||
import { ResizableTextInput } from './resizable_text_input';
|
||||
import { TimelineMessage } from './timeline_message';
|
||||
|
||||
export interface Props {
|
||||
investigationId: string;
|
||||
investigation: GetInvestigationResponse;
|
||||
user: AuthenticatedUser;
|
||||
}
|
||||
|
||||
export function InvestigationNotes({ investigationId, investigation }: Props) {
|
||||
export function InvestigationNotes({ investigation, user }: Props) {
|
||||
const theme = useTheme();
|
||||
const [noteInput, setNoteInput] = useState('');
|
||||
|
||||
const { data: notes, refetch } = useFetchInvestigationNotes({
|
||||
investigationId,
|
||||
investigationId: investigation.id,
|
||||
initialNotes: investigation.notes,
|
||||
});
|
||||
const { mutateAsync: addInvestigationNote, isLoading: isAdding } = useAddInvestigationNote();
|
||||
const { mutateAsync: deleteInvestigationNote, isLoading: isDeleting } =
|
||||
useDeleteInvestigationNote();
|
||||
|
||||
const onAddNote = async (content: string) => {
|
||||
await addInvestigationNote({ investigationId, note: { content } });
|
||||
await addInvestigationNote({ investigationId: investigation.id, note: { content } });
|
||||
refetch();
|
||||
setNoteInput('');
|
||||
};
|
||||
|
||||
const onDeleteNote = async (noteId: string) => {
|
||||
await deleteInvestigationNote({ investigationId, noteId });
|
||||
refetch();
|
||||
};
|
||||
|
||||
const panelClassName = css`
|
||||
background-color: ${theme.colors.lightShade};
|
||||
`;
|
||||
|
@ -72,12 +64,12 @@ export function InvestigationNotes({ investigationId, investigation }: Props) {
|
|||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{notes?.map((currNote: InvestigationNoteResponse) => {
|
||||
return (
|
||||
<TimelineMessage
|
||||
<Note
|
||||
key={currNote.id}
|
||||
icon={<EuiAvatar name={currNote.createdBy} size="s" />}
|
||||
investigationId={investigation.id}
|
||||
note={currNote}
|
||||
onDelete={() => onDeleteNote(currNote.id)}
|
||||
isDeleting={isDeleting}
|
||||
disabled={currNote.createdBy !== user.username}
|
||||
onUpdateOrDeleteCompleted={() => refetch()}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -110,7 +102,7 @@ export function InvestigationNotes({ investigationId, investigation }: Props) {
|
|||
<EuiButton
|
||||
data-test-subj="investigateAppInvestigationNotesAddButton"
|
||||
fullWidth
|
||||
color="text"
|
||||
color="primary"
|
||||
aria-label={i18n.translate('xpack.investigateApp.investigationNotes.addButtonLabel', {
|
||||
defaultMessage: 'Add',
|
||||
})}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiAvatar,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiMarkdownFormat,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import { InvestigationNoteResponse } from '@kbn/investigation-shared';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { formatDistance } from 'date-fns';
|
||||
import React, { useState } from 'react';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { EditNoteForm } from './edit_note_form';
|
||||
import { useDeleteInvestigationNote } from '../../../../hooks/use_delete_investigation_note';
|
||||
|
||||
const textContainerClassName = css`
|
||||
padding-top: 2px;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
note: InvestigationNoteResponse;
|
||||
investigationId: string;
|
||||
disabled: boolean;
|
||||
onUpdateOrDeleteCompleted: () => void;
|
||||
}
|
||||
|
||||
export function Note({ note, investigationId, disabled, onUpdateOrDeleteCompleted }: Props) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const { mutateAsync: deleteInvestigationNote, isLoading: isDeleting } =
|
||||
useDeleteInvestigationNote();
|
||||
|
||||
const theme = useTheme();
|
||||
const timelineContainerClassName = css`
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid ${theme.colors.lightShade};
|
||||
:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
const deleteNote = async () => {
|
||||
await deleteInvestigationNote({ investigationId, noteId: note.id });
|
||||
onUpdateOrDeleteCompleted();
|
||||
};
|
||||
|
||||
const handleUpdateCompleted = async () => {
|
||||
setIsEditing(false);
|
||||
onUpdateOrDeleteCompleted();
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s" className={timelineContainerClassName}>
|
||||
<EuiFlexGroup direction="row" alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexGroup direction="row" alignItems="center" justifyContent="flexStart" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiAvatar name={note.createdBy} size="s" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs">
|
||||
{formatDistance(new Date(note.createdAt), new Date(), { addSuffix: true })}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="editInvestigationNoteButton"
|
||||
size="s"
|
||||
iconSize="s"
|
||||
color="text"
|
||||
iconType="pencil"
|
||||
disabled={disabled || isDeleting}
|
||||
onClick={() => {
|
||||
setIsEditing(!isEditing);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
iconSize="s"
|
||||
color="text"
|
||||
iconType="trash"
|
||||
disabled={disabled || isDeleting}
|
||||
onClick={() => deleteNote()}
|
||||
data-test-subj="deleteInvestigationNoteButton"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem className={textContainerClassName}>
|
||||
{isEditing ? (
|
||||
<EditNoteForm
|
||||
investigationId={investigationId}
|
||||
note={note}
|
||||
onCancel={() => setIsEditing(false)}
|
||||
onUpdate={() => handleUpdateCompleted()}
|
||||
/>
|
||||
) : (
|
||||
<EuiText size="s">
|
||||
<EuiMarkdownFormat textSize="s">{note.content}</EuiMarkdownFormat>
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiMarkdownFormat, EuiText } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import { InvestigationNoteResponse } from '@kbn/investigation-shared';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { formatDistance } from 'date-fns';
|
||||
import React from 'react';
|
||||
import { InvestigateTextButton } from '../../../../components/investigate_text_button';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
|
||||
const textContainerClassName = css`
|
||||
padding-top: 2px;
|
||||
`;
|
||||
|
||||
export function TimelineMessage({
|
||||
icon,
|
||||
note,
|
||||
onDelete,
|
||||
isDeleting,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
note: InvestigationNoteResponse;
|
||||
onDelete: () => void;
|
||||
isDeleting: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const timelineContainerClassName = css`
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid ${theme.colors.lightShade};
|
||||
:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
`;
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s" className={timelineContainerClassName}>
|
||||
<EuiFlexGroup direction="row" alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexGroup direction="row" alignItems="center" justifyContent="flexStart" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>{icon}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs">
|
||||
{formatDistance(new Date(note.createdAt), new Date(), { addSuffix: true })}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<InvestigateTextButton
|
||||
data-test-subj="investigateAppTimelineMessageButton"
|
||||
iconType="trash"
|
||||
disabled={isDeleting}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem className={textContainerClassName}>
|
||||
<EuiText size="s">
|
||||
<EuiMarkdownFormat textSize="s">{note.content}</EuiMarkdownFormat>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -125,7 +125,6 @@ export class InvestigateAppPlugin
|
|||
.getStartServices()
|
||||
.then(([, pluginsStart]) => pluginsStart);
|
||||
|
||||
// new
|
||||
Promise.all([
|
||||
pluginsStartPromise,
|
||||
import('./items/register_items').then((m) => m.registerItems),
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
getInvestigationItemsParamsSchema,
|
||||
getInvestigationNotesParamsSchema,
|
||||
getInvestigationParamsSchema,
|
||||
updateInvestigationNoteParamsSchema,
|
||||
} from '@kbn/investigation-shared';
|
||||
import { createInvestigation } from '../services/create_investigation';
|
||||
import { createInvestigationItem } from '../services/create_investigation_item';
|
||||
|
@ -29,6 +30,7 @@ import { getInvestigationNotes } from '../services/get_investigation_notes';
|
|||
import { investigationRepositoryFactory } from '../services/investigation_repository';
|
||||
import { createInvestigateAppServerRoute } from './create_investigate_app_server_route';
|
||||
import { getInvestigationItems } from '../services/get_investigation_items';
|
||||
import { updateInvestigationNote } from '../services/update_investigation_note';
|
||||
|
||||
const createInvestigationRoute = createInvestigateAppServerRoute({
|
||||
endpoint: 'POST /api/observability/investigations 2023-10-31',
|
||||
|
@ -125,7 +127,33 @@ const getInvestigationNotesRoute = createInvestigateAppServerRoute({
|
|||
},
|
||||
});
|
||||
|
||||
const deleteInvestigationNotesRoute = createInvestigateAppServerRoute({
|
||||
const updateInvestigationNoteRoute = createInvestigateAppServerRoute({
|
||||
endpoint: 'PUT /api/observability/investigations/{investigationId}/notes/{noteId} 2023-10-31',
|
||||
options: {
|
||||
tags: [],
|
||||
},
|
||||
params: updateInvestigationNoteParamsSchema,
|
||||
handler: async ({ params, context, request, logger }) => {
|
||||
const user = (await context.core).coreStart.security.authc.getCurrentUser(request);
|
||||
if (!user) {
|
||||
throw new Error('User is not authenticated');
|
||||
}
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const repository = investigationRepositoryFactory({ soClient, logger });
|
||||
|
||||
return await updateInvestigationNote(
|
||||
params.path.investigationId,
|
||||
params.path.noteId,
|
||||
params.body,
|
||||
{
|
||||
repository,
|
||||
user,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteInvestigationNoteRoute = createInvestigateAppServerRoute({
|
||||
endpoint: 'DELETE /api/observability/investigations/{investigationId}/notes/{noteId} 2023-10-31',
|
||||
options: {
|
||||
tags: [],
|
||||
|
@ -207,10 +235,11 @@ export function getGlobalInvestigateAppServerRouteRepository() {
|
|||
...createInvestigationRoute,
|
||||
...findInvestigationsRoute,
|
||||
...getInvestigationRoute,
|
||||
...deleteInvestigationRoute,
|
||||
...createInvestigationNoteRoute,
|
||||
...getInvestigationNotesRoute,
|
||||
...deleteInvestigationNotesRoute,
|
||||
...updateInvestigationNoteRoute,
|
||||
...deleteInvestigationNoteRoute,
|
||||
...deleteInvestigationRoute,
|
||||
...createInvestigationItemRoute,
|
||||
...deleteInvestigationItemRoute,
|
||||
...getInvestigationItemsRoute,
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { AuthenticatedUser } from '@kbn/core-security-common';
|
||||
import { UpdateInvestigationNoteParams } from '@kbn/investigation-shared';
|
||||
import { InvestigationRepository } from './investigation_repository';
|
||||
|
||||
export async function updateInvestigationNote(
|
||||
investigationId: string,
|
||||
noteId: string,
|
||||
params: UpdateInvestigationNoteParams,
|
||||
{ repository, user }: { repository: InvestigationRepository; user: AuthenticatedUser }
|
||||
): Promise<void> {
|
||||
const investigation = await repository.findById(investigationId);
|
||||
const note = investigation.notes.find((currNote) => currNote.id === noteId);
|
||||
if (!note) {
|
||||
throw new Error('Note not found');
|
||||
}
|
||||
|
||||
if (note.createdBy !== user.username) {
|
||||
throw new Error('User does not have permission to delete note');
|
||||
}
|
||||
|
||||
investigation.notes = investigation.notes.filter((currNote) => {
|
||||
if (currNote.id === noteId) {
|
||||
currNote.content = params.content;
|
||||
}
|
||||
|
||||
return currNote;
|
||||
});
|
||||
|
||||
await repository.save(investigation);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue