Skip to content

Commit

Permalink
[8.x] feat(rca): display investigations stats (#193069) (#193346)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [feat(rca): display investigations stats
(#193069)](#193069)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Kevin
Delemme","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-16T21:22:03Z","message":"feat(rca):
display investigations stats
(#193069)","sha":"bb1898ec8f56fd77cfe0858cce243d714e788194","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","ci:project-deploy-observability","Team:obs-ux-management","v8.16.0"],"title":"feat(rca):
display investigations
stats","number":193069,"url":"https://github.com/elastic/kibana/pull/193069","mergeCommit":{"message":"feat(rca):
display investigations stats
(#193069)","sha":"bb1898ec8f56fd77cfe0858cce243d714e788194"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193069","number":193069,"mergeCommit":{"message":"feat(rca):
display investigations stats
(#193069)","sha":"bb1898ec8f56fd77cfe0858cce243d714e788194"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Kevin Delemme <[email protected]>
  • Loading branch information
kibanamachine and kdelemme authored Sep 18, 2024
1 parent 7823f1c commit d00d65b
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { z } from '@kbn/zod';
import { statusSchema } from '../schema';

const getAllInvestigationStatsParamsSchema = z.object({
query: z.object({}),
});

const getAllInvestigationStatsResponseSchema = z.object({
count: z.record(statusSchema, z.number()),
total: z.number(),
});

type GetAllInvestigationStatsResponse = z.output<typeof getAllInvestigationStatsResponseSchema>;

export { getAllInvestigationStatsParamsSchema, getAllInvestigationStatsResponseSchema };
export type { GetAllInvestigationStatsResponse };
2 changes: 2 additions & 0 deletions packages/kbn-investigation-shared/src/rest_specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type * from './find';
export type * from './get';
export type * from './get_items';
export type * from './get_notes';
export type * from './get_all_investigation_stats';
export type * from './get_all_investigation_tags';
export type * from './investigation';
export type * from './investigation_item';
Expand All @@ -35,6 +36,7 @@ export * from './find';
export * from './get';
export * from './get_items';
export * from './get_notes';
export * from './get_all_investigation_stats';
export * from './get_all_investigation_tags';
export * from './investigation';
export * from './investigation_item';
Expand Down
2 changes: 2 additions & 0 deletions packages/kbn-investigation-shared/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export * from './investigation';
export * from './investigation_item';
export * from './investigation_note';
export * from './origin';

export type * from './investigation';
3 changes: 3 additions & 0 deletions packages/kbn-investigation-shared/src/schema/investigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ const investigationSchema = z.object({
items: z.array(investigationItemSchema),
});

type Status = z.infer<typeof statusSchema>;

export type { Status };
export { investigationSchema, statusSchema };
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const investigationKeys = {
userProfiles: (profileIds: Set<string>) =>
[...investigationKeys.all, 'userProfiles', ...profileIds] as const,
tags: () => [...investigationKeys.all, 'tags'] as const,
stats: () => [...investigationKeys.all, 'stats'] as const,
lists: () => [...investigationKeys.all, 'list'] as const,
list: (params: { page: number; perPage: number; search?: string; filter?: string }) =>
[...investigationKeys.lists(), params] as const,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 type { GetAllInvestigationStatsResponse, Status } from '@kbn/investigation-shared';
import { useQuery } from '@tanstack/react-query';
import { investigationKeys } from './query_key_factory';
import { useKibana } from './use_kibana';

export interface Response {
isInitialLoading: boolean;
isLoading: boolean;
isRefetching: boolean;
isSuccess: boolean;
isError: boolean;
data: { count: Record<Status, number>; total: number } | undefined;
}

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

const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
queryKey: investigationKeys.stats(),
queryFn: async ({ signal }) => {
const response = await http.get<GetAllInvestigationStatsResponse>(
`/api/observability/investigations/_stats`,
{
version: '2023-10-31',
signal,
}
);

return {
count: {
triage: response.count.triage ?? 0,
active: response.count.active ?? 0,
mitigated: response.count.mitigated ?? 0,
resolved: response.count.resolved ?? 0,
cancelled: response.count.cancelled ?? 0,
},
total: response.total ?? 0,
};
},
retry: false,
cacheTime: 600 * 1000, // 10 minutes
staleTime: 0,
onError: (error: Error) => {
toasts.addError(error, {
title: i18n.translate('xpack.investigateApp.useFetchAllInvestigationStats.errorTitle', {
defaultMessage: 'Something went wrong while fetching the investigation stats',
}),
});
},
});

return {
data,
isInitialLoading,
isLoading,
isRefetching,
isSuccess,
isError,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
EuiBasicTable,
EuiBasicTableColumn,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiLoadingSpinner,
EuiText,
Expand All @@ -25,6 +26,7 @@ import { useFetchInvestigationList } from '../../../hooks/use_fetch_investigatio
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';

Expand Down Expand Up @@ -109,11 +111,15 @@ export function InvestigationList() {
defaultMessage: 'Tags',
}),
render: (value: InvestigationResponse['tags']) => {
return value.map((tag) => (
<EuiBadge color={'hollow'} key="tag">
{tag}
</EuiBadge>
));
return (
<EuiFlexGroup wrap gutterSize="xs">
{value.map((tag) => (
<EuiFlexItem key={tag} grow={false}>
<EuiBadge color="hollow">{tag}</EuiBadge>
</EuiFlexItem>
))}
</EuiFlexGroup>
);
},
},
{
Expand Down Expand Up @@ -180,6 +186,7 @@ export function InvestigationList() {

return (
<EuiFlexGroup direction="column" gutterSize="l">
<InvestigationStats />
<SearchBar
isLoading={isLoading}
onSearch={(value) => setSearch(value)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 numeral from '@elastic/numeral';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useFetchAllInvestigationStats } from '../../../hooks/use_fetch_all_investigation_stats';
import { useKibana } from '../../../hooks/use_kibana';

export function InvestigationStats() {
const {
core: { uiSettings },
} = useKibana();
const { data, isLoading: isStatsLoading } = useFetchAllInvestigationStats();
const numberFormat = uiSettings.get('format:number:defaultPattern');

return (
<EuiPanel hasBorder={true}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiStat
title={numeral(data?.count.triage).format(numberFormat)}
description={i18n.translate(
'xpack.investigateApp.investigationList.stats.triageLabel',
{ defaultMessage: 'Triage' }
)}
titleSize="s"
isLoading={isStatsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiStat
title={numeral(data?.count.active).format(numberFormat)}
description={i18n.translate(
'xpack.investigateApp.investigationList.stats.activeLabel',
{ defaultMessage: 'Active' }
)}
titleSize="s"
isLoading={isStatsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiStat
title={numeral(data?.count.cancelled).format(numberFormat)}
description={i18n.translate(
'xpack.investigateApp.investigationList.stats.cancelledLabel',
{ defaultMessage: 'Cancelled' }
)}
titleSize="s"
isLoading={isStatsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiStat
title={numeral(data?.count.mitigated).format(numberFormat)}
description={i18n.translate(
'xpack.investigateApp.investigationList.stats.mitigatedLabel',
{ defaultMessage: 'Mitigated' }
)}
titleSize="s"
isLoading={isStatsLoading}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiStat
title={numeral(data?.count.resolved).format(numberFormat)}
description={i18n.translate(
'xpack.investigateApp.investigationList.stats.resolvedLabel',
{ defaultMessage: 'Resolved' }
)}
titleSize="s"
isLoading={isStatsLoading}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
deleteInvestigationNoteParamsSchema,
deleteInvestigationParamsSchema,
findInvestigationsParamsSchema,
getAllInvestigationStatsParamsSchema,
getAllInvestigationTagsParamsSchema,
getInvestigationItemsParamsSchema,
getInvestigationNotesParamsSchema,
Expand All @@ -37,6 +38,7 @@ import { updateInvestigation } from '../services/update_investigation';
import { updateInvestigationItem } from '../services/update_investigation_item';
import { updateInvestigationNote } from '../services/update_investigation_note';
import { createInvestigateAppServerRoute } from './create_investigate_app_server_route';
import { getAllInvestigationStats } from '../services/get_all_investigation_stats';

const createInvestigationRoute = createInvestigateAppServerRoute({
endpoint: 'POST /api/observability/investigations 2023-10-31',
Expand Down Expand Up @@ -154,6 +156,20 @@ const getAllInvestigationTagsRoute = createInvestigateAppServerRoute({
},
});

const getAllInvestigationStatsRoute = createInvestigateAppServerRoute({
endpoint: 'GET /api/observability/investigations/_stats 2023-10-31',
options: {
tags: [],
},
params: getAllInvestigationStatsParamsSchema,
handler: async ({ params, context, request, logger }) => {
const soClient = (await context.core).savedObjects.client;
const repository = investigationRepositoryFactory({ soClient, logger });

return await getAllInvestigationStats(repository);
},
});

const getInvestigationNotesRoute = createInvestigateAppServerRoute({
endpoint: 'GET /api/observability/investigations/{investigationId}/notes 2023-10-31',
options: {
Expand Down Expand Up @@ -312,6 +328,7 @@ export function getGlobalInvestigateAppServerRouteRepository() {
...deleteInvestigationItemRoute,
...updateInvestigationItemRoute,
...getInvestigationItemsRoute,
...getAllInvestigationStatsRoute,
...getAllInvestigationTagsRoute,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 {
GetAllInvestigationStatsResponse,
getAllInvestigationStatsResponseSchema,
} from '@kbn/investigation-shared';
import { InvestigationRepository } from './investigation_repository';

export async function getAllInvestigationStats(
repository: InvestigationRepository
): Promise<GetAllInvestigationStatsResponse> {
const stats = await repository.getStats();
return getAllInvestigationStatsResponseSchema.parse(stats);
}
Loading

0 comments on commit d00d65b

Please sign in to comment.