mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[RAC] [Observability] [Security Solution] Use correct url to management app for observability cases, use normalized ids (#108775)
* Use correct url to management app for observability cases, use normalized ids in timelines * Update failing test * Load alert details data to render flyout in case detail view
This commit is contained in:
parent
ab637303e7
commit
87c93abf1d
5 changed files with 151 additions and 51 deletions
|
@ -4,9 +4,52 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import { usePluginContext } from '../../../../hooks/use_plugin_context';
|
||||
import { parseAlert } from '../../../../pages/alerts/parse_alert';
|
||||
import { TopAlert } from '../../../../pages/alerts/';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { Ecs } from '../../../../../../cases/common';
|
||||
|
||||
// no alerts in observability so far
|
||||
// dummy hook for now as hooks cannot be called conditionally
|
||||
export const useFetchAlertData = (): [boolean, Record<string, Ecs>] => [false, {}];
|
||||
|
||||
export const useFetchAlertDetail = (alertId: string): [boolean, TopAlert | null] => {
|
||||
const { http } = useKibana().services;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { observabilityRuleTypeRegistry } = usePluginContext();
|
||||
const [alert, setAlert] = useState<TopAlert | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const abortCtrl = new AbortController();
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await http.get('/internal/rac/alerts', {
|
||||
query: {
|
||||
id: alertId,
|
||||
},
|
||||
});
|
||||
if (response) {
|
||||
const parsedAlert = parseAlert(observabilityRuleTypeRegistry)(response);
|
||||
setAlert(parsedAlert);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setAlert(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isEmpty(alertId) && loading === false && alert === null) {
|
||||
fetchData();
|
||||
}
|
||||
return () => {
|
||||
abortCtrl.abort();
|
||||
};
|
||||
}, [http, alertId, alert, loading, observabilityRuleTypeRegistry]);
|
||||
|
||||
return [loading, alert];
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useState, Suspense } from 'react';
|
||||
import {
|
||||
casesBreadcrumbs,
|
||||
getCaseDetailsUrl,
|
||||
|
@ -15,10 +15,12 @@ import {
|
|||
useFormatUrl,
|
||||
} from '../../../../pages/cases/links';
|
||||
import { Case } from '../../../../../../cases/common';
|
||||
import { useFetchAlertData } from './helpers';
|
||||
import { useFetchAlertData, useFetchAlertDetail } from './helpers';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { usePluginContext } from '../../../../hooks/use_plugin_context';
|
||||
import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs';
|
||||
import { observabilityAppId } from '../../../../../common';
|
||||
import { LazyAlertsFlyout } from '../../../..';
|
||||
|
||||
interface Props {
|
||||
caseId: string;
|
||||
|
@ -41,14 +43,17 @@ export interface CaseProps extends Props {
|
|||
|
||||
export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) => {
|
||||
const [caseTitle, setCaseTitle] = useState<string | null>(null);
|
||||
const { observabilityRuleTypeRegistry } = usePluginContext();
|
||||
|
||||
const {
|
||||
cases: casesUi,
|
||||
application: { getUrlForApp, navigateToUrl },
|
||||
application: { getUrlForApp, navigateToUrl, navigateToApp },
|
||||
} = useKibana().services;
|
||||
const allCasesLink = getCaseUrl();
|
||||
const { formatUrl } = useFormatUrl();
|
||||
const href = formatUrl(allCasesLink);
|
||||
const [selectedAlertId, setSelectedAlertId] = useState<string>('');
|
||||
|
||||
useBreadcrumbs([
|
||||
{ ...casesBreadcrumbs.cases, href },
|
||||
...(caseTitle !== null
|
||||
|
@ -80,41 +85,78 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
|
|||
}),
|
||||
[caseId, formatUrl, subCaseId]
|
||||
);
|
||||
|
||||
const casesUrl = `${getUrlForApp(observabilityAppId)}/cases`;
|
||||
return casesUi.getCaseView({
|
||||
allCasesNavigation: {
|
||||
href: allCasesHref,
|
||||
onClick: async (ev) => {
|
||||
if (ev != null) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
return navigateToUrl(casesUrl);
|
||||
},
|
||||
},
|
||||
caseDetailsNavigation: {
|
||||
href: caseDetailsHref,
|
||||
onClick: async (ev) => {
|
||||
if (ev != null) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
return navigateToUrl(`${casesUrl}${getCaseDetailsUrl({ id: caseId })}`);
|
||||
},
|
||||
},
|
||||
caseId,
|
||||
configureCasesNavigation: {
|
||||
href: configureCasesHref,
|
||||
onClick: async (ev) => {
|
||||
if (ev != null) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
return navigateToUrl(`${casesUrl}${configureCasesLink}`);
|
||||
},
|
||||
},
|
||||
getCaseDetailHrefWithCommentId,
|
||||
onCaseDataSuccess,
|
||||
subCaseId,
|
||||
useFetchAlertData,
|
||||
userCanCrud,
|
||||
});
|
||||
|
||||
const handleFlyoutClose = useCallback(() => {
|
||||
setSelectedAlertId('');
|
||||
}, []);
|
||||
|
||||
const [alertLoading, alert] = useFetchAlertDetail(selectedAlertId);
|
||||
|
||||
return (
|
||||
<>
|
||||
{alertLoading === false && alert && selectedAlertId !== '' && (
|
||||
<Suspense fallback={null}>
|
||||
<LazyAlertsFlyout
|
||||
alert={alert}
|
||||
observabilityRuleTypeRegistry={observabilityRuleTypeRegistry}
|
||||
onClose={handleFlyoutClose}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
{casesUi.getCaseView({
|
||||
allCasesNavigation: {
|
||||
href: allCasesHref,
|
||||
onClick: async (ev) => {
|
||||
if (ev != null) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
return navigateToUrl(casesUrl);
|
||||
},
|
||||
},
|
||||
caseDetailsNavigation: {
|
||||
href: caseDetailsHref,
|
||||
onClick: async (ev) => {
|
||||
if (ev != null) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
return navigateToUrl(`${casesUrl}${getCaseDetailsUrl({ id: caseId })}`);
|
||||
},
|
||||
},
|
||||
caseId,
|
||||
configureCasesNavigation: {
|
||||
href: configureCasesHref,
|
||||
onClick: async (ev) => {
|
||||
if (ev != null) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
return navigateToUrl(`${casesUrl}${configureCasesLink}`);
|
||||
},
|
||||
},
|
||||
ruleDetailsNavigation: {
|
||||
href: (ruleId) => {
|
||||
return getUrlForApp('management', {
|
||||
path: `/insightsAndAlerting/triggersActions/rule/${ruleId}`,
|
||||
});
|
||||
},
|
||||
onClick: async (ruleId, ev) => {
|
||||
if (ev != null) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
return navigateToApp('management', {
|
||||
path: `/insightsAndAlerting/triggersActions/rule/${ruleId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
getCaseDetailHrefWithCommentId,
|
||||
onCaseDataSuccess,
|
||||
subCaseId,
|
||||
useFetchAlertData,
|
||||
showAlertDetails: (alertId) => {
|
||||
setSelectedAlertId(alertId);
|
||||
},
|
||||
userCanCrud,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -100,7 +100,7 @@ describe('AddToCaseAction', () => {
|
|||
{...props}
|
||||
event={{
|
||||
_id: 'test-id',
|
||||
data: [],
|
||||
data: [{ field: 'kibana.alert.rule.id', value: ['rule-id'] }],
|
||||
ecs: {
|
||||
_id: 'test-id',
|
||||
_index: 'test-index',
|
||||
|
@ -112,7 +112,7 @@ describe('AddToCaseAction', () => {
|
|||
{...props}
|
||||
event={{
|
||||
_id: 'test-id',
|
||||
data: [],
|
||||
data: [{ field: 'kibana.alert.rule.id', value: ['rule-id'] }],
|
||||
ecs: {
|
||||
_id: 'test-id',
|
||||
_index: 'test-index',
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { memo, useMemo, useCallback } from 'react';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { CaseStatuses, StatusAll } from '../../../../../../cases/common';
|
||||
import { TimelineItem } from '../../../../../common/';
|
||||
import { useAddToCase } from '../../../../hooks/use_add_to_case';
|
||||
import { useAddToCase, normalizedEventFields } from '../../../../hooks/use_add_to_case';
|
||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { TimelinesStartServices } from '../../../../types';
|
||||
import { CreateCaseFlyout } from './create/flyout';
|
||||
|
@ -38,7 +38,6 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
}) => {
|
||||
const eventId = event?.ecs._id ?? '';
|
||||
const eventIndex = event?.ecs._index ?? '';
|
||||
const rule = event?.ecs.signal?.rule;
|
||||
const dispatch = useDispatch();
|
||||
const { cases } = useKibana<TimelinesStartServices>().services;
|
||||
const {
|
||||
|
@ -52,13 +51,14 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
} = useAddToCase({ event, useInsertTimeline, casePermissions, appId, onClose });
|
||||
|
||||
const getAllCasesSelectorModalProps = useMemo(() => {
|
||||
const { ruleId, ruleName } = normalizedEventFields(event);
|
||||
return {
|
||||
alertData: {
|
||||
alertId: eventId,
|
||||
index: eventIndex ?? '',
|
||||
rule: {
|
||||
id: rule?.id != null ? rule.id[0] : null,
|
||||
name: rule?.name != null ? rule.name[0] : null,
|
||||
id: ruleId,
|
||||
name: ruleName,
|
||||
},
|
||||
owner: appId,
|
||||
},
|
||||
|
@ -85,11 +85,10 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
goToCreateCase,
|
||||
eventId,
|
||||
eventIndex,
|
||||
rule?.id,
|
||||
rule?.name,
|
||||
appId,
|
||||
dispatch,
|
||||
useInsertTimeline,
|
||||
event,
|
||||
]);
|
||||
|
||||
const closeCaseFlyoutOpen = useCallback(() => {
|
||||
|
|
|
@ -8,9 +8,11 @@ import { isEmpty } from 'lodash';
|
|||
import { useState, useCallback, useMemo, SyntheticEvent } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { ALERT_RULE_ID, ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { Case, SubCase } from '../../../cases/common';
|
||||
import { TimelinesStartServices } from '../types';
|
||||
import { TimelineItem } from '../../common/';
|
||||
import { tGridActions } from '../store/t_grid';
|
||||
import { useDeepEqualSelector } from './use_selector';
|
||||
import { createUpdateSuccessToaster } from '../components/actions/timeline/cases/helpers';
|
||||
|
@ -83,7 +85,6 @@ export const useAddToCase = ({
|
|||
}: AddToCaseActionProps): UseAddToCase => {
|
||||
const eventId = event?.ecs._id ?? '';
|
||||
const eventIndex = event?.ecs._index ?? '';
|
||||
const rule = event?.ecs.signal?.rule;
|
||||
const dispatch = useDispatch();
|
||||
// TODO: use correct value in standalone or integrated.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -154,6 +155,7 @@ export const useAddToCase = ({
|
|||
updateCase?: (newCase: Case) => void
|
||||
) => {
|
||||
dispatch(tGridActions.setOpenAddToNewCase({ id: eventId, isOpen: false }));
|
||||
const { ruleId, ruleName } = normalizedEventFields(event);
|
||||
if (postComment) {
|
||||
await postComment({
|
||||
caseId: theCase.id,
|
||||
|
@ -162,8 +164,8 @@ export const useAddToCase = ({
|
|||
alertId: eventId,
|
||||
index: eventIndex ?? '',
|
||||
rule: {
|
||||
id: rule?.id != null ? rule.id[0] : null,
|
||||
name: rule?.name != null ? rule.name[0] : null,
|
||||
id: ruleId,
|
||||
name: ruleName,
|
||||
},
|
||||
owner: appId,
|
||||
},
|
||||
|
@ -171,7 +173,7 @@ export const useAddToCase = ({
|
|||
});
|
||||
}
|
||||
},
|
||||
[eventId, eventIndex, rule, appId, dispatch]
|
||||
[eventId, eventIndex, appId, dispatch, event]
|
||||
);
|
||||
const onCaseSuccess = useCallback(
|
||||
async (theCase: Case) => {
|
||||
|
@ -239,3 +241,17 @@ export const useAddToCase = ({
|
|||
isCreateCaseFlyoutOpen,
|
||||
};
|
||||
};
|
||||
|
||||
export function normalizedEventFields(event?: TimelineItem) {
|
||||
const ruleId = event && event.data.find(({ field }) => field === ALERT_RULE_ID);
|
||||
const ruleUuid = event && event.data.find(({ field }) => field === ALERT_RULE_UUID);
|
||||
const ruleName = event && event.data.find(({ field }) => field === ALERT_RULE_NAME);
|
||||
const ruleIdValue = ruleId && ruleId.value && ruleId.value[0];
|
||||
const ruleUuidValue = ruleUuid && ruleUuid.value && ruleUuid.value[0];
|
||||
const ruleNameValue = ruleName && ruleName.value && ruleName.value[0];
|
||||
const idToUse = ruleIdValue ? ruleIdValue : ruleUuidValue;
|
||||
return {
|
||||
ruleId: idToUse ?? null,
|
||||
ruleName: ruleNameValue ?? null,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue