Skip to content

Commit

Permalink
feat(rca): update items and investigation params (elastic#191766)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdelemme authored Sep 3, 2024
1 parent c2d994d commit 326b305
Show file tree
Hide file tree
Showing 12 changed files with 358 additions and 74 deletions.
24 changes: 14 additions & 10 deletions packages/kbn-investigation-shared/src/rest_specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,33 @@
*/

export type * from './create';
export type * from './create_item';
export type * from './create_note';
export type * from './delete';
export type * from './delete_item';
export type * from './delete_note';
export type * from './find';
export type * from './get';
export type * from './get_notes';
export type * from './delete_note';
export type * from './investigation_note';
export type * from './create_item';
export type * from './delete_item';
export type * from './get_items';
export type * from './get_notes';
export type * from './investigation_item';
export type * from './investigation_note';
export type * from './update';
export type * from './update_item';
export type * from './update_note';

export * from './create';
export * from './create_item';
export * from './create_note';
export * from './delete';
export * from './delete_item';
export * from './delete_note';
export * from './find';
export * from './get';
export * from './get_notes';
export * from './delete_note';
export * from './investigation_note';
export * from './create_item';
export * from './delete_item';
export * from './get_items';
export * from './get_notes';
export * from './investigation_item';
export * from './investigation_note';
export * from './update';
export * from './update_item';
export * from './update_note';
31 changes: 31 additions & 0 deletions packages/kbn-investigation-shared/src/rest_specs/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { investigationResponseSchema } from './investigation';

const updateInvestigationParamsSchema = t.type({
path: t.type({
investigationId: t.string,
}),
body: t.partial({
title: t.string,
status: t.union([t.literal('ongoing'), t.literal('closed')]),
params: t.type({
timeRange: t.type({ from: t.number, to: t.number }),
}),
}),
});

const updateInvestigationResponseSchema = investigationResponseSchema;

type UpdateInvestigationParams = t.TypeOf<typeof updateInvestigationParamsSchema.props.body>;
type UpdateInvestigationResponse = t.OutputOf<typeof updateInvestigationResponseSchema>;

export { updateInvestigationParamsSchema, updateInvestigationResponseSchema };
export type { UpdateInvestigationParams, UpdateInvestigationResponse };
29 changes: 29 additions & 0 deletions packages/kbn-investigation-shared/src/rest_specs/update_item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { investigationItemResponseSchema } from './investigation_item';
import { itemSchema } from '../schema';

const updateInvestigationItemParamsSchema = t.type({
path: t.type({
investigationId: t.string,
itemId: t.string,
}),
body: itemSchema,
});

const updateInvestigationItemResponseSchema = investigationItemResponseSchema;

type UpdateInvestigationItemParams = t.TypeOf<
typeof updateInvestigationItemParamsSchema.props.body
>;
type UpdateInvestigationItemResponse = t.OutputOf<typeof updateInvestigationItemResponseSchema>;

export { updateInvestigationItemParamsSchema, updateInvestigationItemResponseSchema };
export type { UpdateInvestigationItemParams, UpdateInvestigationItemResponse };
Original file line number Diff line number Diff line change
@@ -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 { UpdateInvestigationParams, UpdateInvestigationResponse } from '@kbn/investigation-shared';
import { useMutation } from '@tanstack/react-query';
import { useKibana } from './use_kibana';

type ServerError = IHttpFetchError<ResponseErrorBody>;

export function useUpdateInvestigation() {
const {
core: {
http,
notifications: { toasts },
},
} = useKibana();

return useMutation<
UpdateInvestigationResponse,
ServerError,
{ investigationId: string; payload: UpdateInvestigationParams },
{ investigationId: string }
>(
['updateInvestigation'],
({ investigationId, payload }) => {
const body = JSON.stringify(payload);
return http.put<UpdateInvestigationResponse>(
`/api/observability/investigations/${investigationId}`,
{ body, version: '2023-10-31' }
);
},
{
onSuccess: (response, {}) => {
toasts.addSuccess('Investigation updated');
},
onError: (error, {}, context) => {
toasts.addError(new Error(error.body?.message ?? 'An error occurred'), { title: 'Error' });
},
}
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
* 2.0.
*/

import datemath from '@elastic/datemath';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { GetInvestigationResponse, Item } from '@kbn/investigation-shared';
import { pick } from 'lodash';
import React from 'react';
import { useAddInvestigationItem } from '../../../../hooks/use_add_investigation_item';
import { useDeleteInvestigationItem } from '../../../../hooks/use_delete_investigation_item';
import { useFetchInvestigationItems } from '../../../../hooks/use_fetch_investigation_items';
import { useRenderItems } from '../../hooks/use_render_items';
import { AddInvestigationItem } from '../add_investigation_item/add_investigation_item';
import { InvestigationItemsList } from '../investigation_items_list/investigation_items_list';
Expand All @@ -22,45 +20,31 @@ export interface Props {
}

export function InvestigationItems({ investigation }: Props) {
const { data: items, refetch } = useFetchInvestigationItems({
investigationId: investigation.id,
initialItems: investigation.items,
const {
renderableItems,
globalParams,
updateInvestigationParams,
addItem,
deleteItem,
isAdding,
isDeleting,
} = useRenderItems({
investigation,
});
const renderableItems = useRenderItems({ items, params: investigation.params });

const { mutateAsync: addInvestigationItem, isLoading: isAdding } = useAddInvestigationItem();
const { mutateAsync: deleteInvestigationItem, isLoading: isDeleting } =
useDeleteInvestigationItem();

const onAddItem = async (item: Item) => {
await addInvestigationItem({ investigationId: investigation.id, item });
refetch();
};

const onDeleteItem = async (itemId: string) => {
await deleteInvestigationItem({ investigationId: investigation.id, itemId });
refetch();
};

return (
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexGroup direction="column" gutterSize="m">
<InvestigationSearchBar
dateRangeFrom={
investigation ? new Date(investigation.params.timeRange.from).toISOString() : undefined
}
dateRangeTo={
investigation ? new Date(investigation.params.timeRange.to).toISOString() : undefined
}
dateRangeFrom={globalParams.timeRange.from}
dateRangeTo={globalParams.timeRange.to}
onQuerySubmit={async ({ dateRange }) => {
// const nextDateRange = {
// from: datemath.parse(dateRange.from)!.toISOString(),
// to: datemath.parse(dateRange.to)!.toISOString(),
// };
// await setGlobalParameters({
// ...renderableInvestigation.parameters,
// timeRange: nextDateRange,
// });
const nextTimeRange = {
from: datemath.parse(dateRange.from)!.toISOString(),
to: datemath.parse(dateRange.to)!.toISOString(),
};

updateInvestigationParams({ ...globalParams, timeRange: nextTimeRange });
}}
/>

Expand All @@ -69,21 +53,18 @@ export function InvestigationItems({ investigation }: Props) {
isLoading={isAdding || isDeleting}
items={renderableItems}
onItemCopy={async (copiedItem) => {
await onAddItem(pick(copiedItem, ['title', 'type', 'params']));
await addItem(pick(copiedItem, ['title', 'type', 'params']));
}}
onItemDelete={async (deletedItem) => {
await onDeleteItem(deletedItem.id);
await deleteItem(deletedItem.id);
}}
/>
</EuiFlexItem>
</EuiFlexGroup>

<AddInvestigationItem
timeRange={{
from: new Date(investigation.params.timeRange.from).toISOString(),
to: new Date(investigation.params.timeRange.to).toISOString(),
}}
onItemAdd={onAddItem}
timeRange={globalParams.timeRange}
onItemAdd={async (item: Item) => await addItem(item)}
/>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export function InvestigationNotes({ investigation, user }: Props) {
<EuiSplitPanel.Inner className={panelClassName}>
<EuiTitle size="xs">
<h2>
{i18n.translate('xpack.investigateApp.investigationNotes.investigationTimelineHeader', {
defaultMessage: 'Investigation timeline',
{i18n.translate('xpack.investigateApp.investigationNotes.header', {
defaultMessage: 'Notes',
})}
</h2>
</EuiTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,81 @@
*/

import { i18n } from '@kbn/i18n';
import { GetInvestigationResponse, InvestigationItem } from '@kbn/investigation-shared';
import { type GlobalWidgetParameters } from '@kbn/investigate-plugin/public';
import { GetInvestigationResponse, InvestigationItem, Item } from '@kbn/investigation-shared';
import React, { useEffect, useState } from 'react';
import { useKibana } from '../../../hooks/use_kibana';
import { useFetchInvestigationItems } from '../../../hooks/use_fetch_investigation_items';
import { useAddInvestigationItem } from '../../../hooks/use_add_investigation_item';
import { useDeleteInvestigationItem } from '../../../hooks/use_delete_investigation_item';
import { useUpdateInvestigation } from '../../../hooks/use_update_investigation';

export type RenderedInvestigationItem = InvestigationItem & {
loading: boolean;
element: React.ReactNode;
};

export function useRenderItems({
items,
params,
}: {
items?: InvestigationItem[];
params: GetInvestigationResponse['params'];
}) {
interface Props {
investigation: GetInvestigationResponse;
}

interface UseRenderItemsHook {
renderableItems: RenderedInvestigationItem[];
globalParams: GlobalWidgetParameters;
updateInvestigationParams: (params: GlobalWidgetParameters) => Promise<void>;
addItem: (item: Item) => Promise<void>;
deleteItem: (itemId: string) => Promise<void>;
isAdding: boolean;
isDeleting: boolean;
}

export function useRenderItems({ investigation }: Props): UseRenderItemsHook {
const {
dependencies: {
start: { investigate },
},
} = useKibana();

const { data: items, refetch } = useFetchInvestigationItems({
investigationId: investigation.id,
initialItems: investigation.items,
});

const { mutateAsync: updateInvestigation } = useUpdateInvestigation();
const { mutateAsync: addInvestigationItem, isLoading: isAdding } = useAddInvestigationItem();
const { mutateAsync: deleteInvestigationItem, isLoading: isDeleting } =
useDeleteInvestigationItem();

const [renderableItems, setRenderableItems] = useState<RenderedInvestigationItem[]>([]);
const [globalParams, setGlobalParams] = useState<GlobalWidgetParameters>({
timeRange: {
from: new Date(investigation.params.timeRange.from).toISOString(),
to: new Date(investigation.params.timeRange.to).toISOString(),
},
});

const updateInvestigationParams = async (nextGlobalParams: GlobalWidgetParameters) => {
const timeRange = {
from: new Date(nextGlobalParams.timeRange.from).getTime(),
to: new Date(nextGlobalParams.timeRange.to).getTime(),
};

await updateInvestigation({
investigationId: investigation.id,
payload: { params: { timeRange } },
});
setGlobalParams(nextGlobalParams);
};

const addItem = async (item: Item) => {
await addInvestigationItem({ investigationId: investigation.id, item });
refetch();
};

const deleteItem = async (itemId: string) => {
await deleteInvestigationItem({ investigationId: investigation.id, itemId });
refetch();
};

useEffect(() => {
async function renderItems(currItems: InvestigationItem[]) {
Expand All @@ -50,13 +102,6 @@ export function useRenderItems({
});
}

const globalParams = {
timeRange: {
from: new Date(params.timeRange.from).toISOString(),
to: new Date(params.timeRange.to).toISOString(),
},
};

const data = await itemDefinition.generate({
itemParams: item.params,
globalParams,
Expand All @@ -78,7 +123,15 @@ export function useRenderItems({
if (items) {
renderItems(items).then((nextRenderableItems) => setRenderableItems(nextRenderableItems));
}
}, [items, investigate, params]);
}, [items, investigate, globalParams]);

return renderableItems;
return {
renderableItems,
updateInvestigationParams,
globalParams,
addItem,
deleteItem,
isAdding,
isDeleting,
};
}
Loading

0 comments on commit 326b305

Please sign in to comment.