From 5d51da90f626606ef6ea217e4b0be68e85aaa86c Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Wed, 25 Sep 2024 11:19:11 -0400 Subject: [PATCH] feat(rca): add external incident url (#193919) --- .../src/rest_specs/create.ts | 1 + .../src/rest_specs/update.ts | 1 + .../src/schema/investigation.ts | 1 + .../fields/external_incident_field.tsx | 45 +++++++++++++++++++ .../investigation_edit_form/form_helper.ts | 43 ++++++++++++++++++ .../investigation_edit_form.tsx | 37 +++++++-------- .../investigation_details.tsx | 2 +- .../external_incident_button.tsx | 35 +++++++++++++++ .../investigation_header.tsx | 9 +++- .../investigation_items.tsx | 10 ++--- .../services/investigation_repository.ts | 4 +- .../components/header_actions.tsx | 1 + 12 files changed, 161 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/fields/external_incident_field.tsx create mode 100644 x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/form_helper.ts create mode 100644 x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/external_incident_button.tsx diff --git a/packages/kbn-investigation-shared/src/rest_specs/create.ts b/packages/kbn-investigation-shared/src/rest_specs/create.ts index afdffc2f524a4..8a8e4d62a37ee 100644 --- a/packages/kbn-investigation-shared/src/rest_specs/create.ts +++ b/packages/kbn-investigation-shared/src/rest_specs/create.ts @@ -20,6 +20,7 @@ const createInvestigationParamsSchema = z.object({ }), origin: z.union([alertOriginSchema, blankOriginSchema]), tags: z.array(z.string()), + externalIncidentUrl: z.string().nullable(), }), }); diff --git a/packages/kbn-investigation-shared/src/rest_specs/update.ts b/packages/kbn-investigation-shared/src/rest_specs/update.ts index da0bdcf4f057e..cab773d9549a2 100644 --- a/packages/kbn-investigation-shared/src/rest_specs/update.ts +++ b/packages/kbn-investigation-shared/src/rest_specs/update.ts @@ -23,6 +23,7 @@ const updateInvestigationParamsSchema = z.object({ timeRange: z.object({ from: z.number(), to: z.number() }), }), tags: z.array(z.string()), + externalIncidentUrl: z.string().nullable(), }) .partial(), }); diff --git a/packages/kbn-investigation-shared/src/schema/investigation.ts b/packages/kbn-investigation-shared/src/schema/investigation.ts index 9be39b5b2a7b3..751f1a20048a5 100644 --- a/packages/kbn-investigation-shared/src/schema/investigation.ts +++ b/packages/kbn-investigation-shared/src/schema/investigation.ts @@ -34,6 +34,7 @@ const investigationSchema = z.object({ tags: z.array(z.string()), notes: z.array(investigationNoteSchema), items: z.array(investigationItemSchema), + externalIncidentUrl: z.string().nullable(), }); type Status = z.infer; diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/fields/external_incident_field.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/fields/external_incident_field.tsx new file mode 100644 index 0000000000000..faeebb34ccb9c --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/fields/external_incident_field.tsx @@ -0,0 +1,45 @@ +/* + * 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 { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { InvestigationForm } from '../investigation_edit_form'; + +const I18N_LABEL = i18n.translate( + 'xpack.investigateApp.investigationEditForm.externalIncidentUrlLabel', + { defaultMessage: 'External incident URL' } +); + +export function ExternalIncidentField() { + const { control, getFieldState } = useFormContext(); + + return ( + + ( + + )} + /> + + ); +} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/form_helper.ts b/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/form_helper.ts new file mode 100644 index 0000000000000..102b6d81d67b6 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/form_helper.ts @@ -0,0 +1,43 @@ +/* + * 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 { CreateInvestigationParams, UpdateInvestigationParams } from '@kbn/investigation-shared'; +import { v4 as uuidv4 } from 'uuid'; +import type { InvestigationForm } from './investigation_edit_form'; + +export function toCreateInvestigationParams(data: InvestigationForm): CreateInvestigationParams { + return { + id: uuidv4(), + title: data.title, + params: { + timeRange: { + from: new Date(new Date().getTime() - 30 * 60 * 1000).getTime(), + to: new Date().getTime(), + }, + }, + tags: data.tags, + origin: { + type: 'blank', + }, + externalIncidentUrl: + data.externalIncidentUrl && data.externalIncidentUrl.trim().length > 0 + ? data.externalIncidentUrl + : null, + }; +} + +export function toUpdateInvestigationParams(data: InvestigationForm): UpdateInvestigationParams { + return { + title: data.title, + status: data.status, + tags: data.tags, + externalIncidentUrl: + data.externalIncidentUrl && data.externalIncidentUrl.trim().length > 0 + ? data.externalIncidentUrl + : null, + }; +} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/investigation_edit_form.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/investigation_edit_form.tsx index 40d845533fe0a..968e5e85320a0 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/investigation_edit_form.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigation_edit_form/investigation_edit_form.tsx @@ -24,20 +24,22 @@ import { InvestigationResponse } from '@kbn/investigation-shared'; import { pick } from 'lodash'; import React from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; -import { v4 as uuidv4 } from 'uuid'; import { paths } from '../../../common/paths'; import { useCreateInvestigation } from '../../hooks/use_create_investigation'; import { useFetchInvestigation } from '../../hooks/use_fetch_investigation'; import { useKibana } from '../../hooks/use_kibana'; import { useUpdateInvestigation } from '../../hooks/use_update_investigation'; import { InvestigationNotFound } from '../investigation_not_found/investigation_not_found'; +import { ExternalIncidentField } from './fields/external_incident_field'; import { StatusField } from './fields/status_field'; import { TagsField } from './fields/tags_field'; +import { toCreateInvestigationParams, toUpdateInvestigationParams } from './form_helper'; export interface InvestigationForm { title: string; status: InvestigationResponse['status']; tags: string[]; + externalIncidentUrl: string | null; } interface Props { @@ -64,8 +66,17 @@ export function InvestigationEditForm({ investigationId, onClose }: Props) { const { mutateAsync: createInvestigation } = useCreateInvestigation(); const methods = useForm({ - defaultValues: { title: 'New investigation', status: 'triage', tags: [] }, - values: investigation ? pick(investigation, ['title', 'status', 'tags']) : undefined, + defaultValues: { + title: i18n.translate('xpack.investigateApp.investigationDetailsPage.newInvestigationLabel', { + defaultMessage: 'New investigation', + }), + status: 'triage', + tags: [], + externalIncidentUrl: null, + }, + values: investigation + ? pick(investigation, ['title', 'status', 'tags', 'externalIncidentUrl']) + : undefined, mode: 'all', }); @@ -81,24 +92,11 @@ export function InvestigationEditForm({ investigationId, onClose }: Props) { if (isEditing) { await updateInvestigation({ investigationId: investigationId!, - payload: { title: data.title, status: data.status, tags: data.tags }, + payload: toUpdateInvestigationParams(data), }); onClose(); } else { - const resp = await createInvestigation({ - id: uuidv4(), - title: data.title, - params: { - timeRange: { - from: new Date(new Date().getTime() - 30 * 60 * 1000).getTime(), - to: new Date().getTime(), - }, - }, - tags: data.tags, - origin: { - type: 'blank', - }, - }); + const resp = await createInvestigation(toCreateInvestigationParams(data)); navigateToUrl(basePath.prepend(paths.investigationDetails(resp.id))); } }; @@ -157,6 +155,9 @@ export function InvestigationEditForm({ investigationId, onClose }: Props) { + + + diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_details/investigation_details.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_details/investigation_details.tsx index 0a2952f09e1ee..c5f30795535b4 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_details/investigation_details.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_details/investigation_details.tsx @@ -70,7 +70,7 @@ export function InvestigationDetails({ user }: Props) { ], }} > - + diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/external_incident_button.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/external_incident_button.tsx new file mode 100644 index 0000000000000..911f4e0661fe7 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/external_incident_button.tsx @@ -0,0 +1,35 @@ +/* + * 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 { EuiButtonEmpty, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useInvestigation } from '../../contexts/investigation_context'; + +export function ExternalIncidentButton() { + const { investigation } = useInvestigation(); + + if (!investigation?.externalIncidentUrl) { + return null; + } + + return ( + + + {i18n.translate('xpack.investigateApp.investigationHeader.externalIncidentTextLabel', { + defaultMessage: 'External incident', + })} + + + ); +} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/investigation_header.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/investigation_header.tsx index 339f2ce1be8e7..d6bfb1c496793 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/investigation_header.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_header/investigation_header.tsx @@ -6,14 +6,15 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; // eslint-disable-next-line import/no-extraneous-dependencies import { formatDistance } from 'date-fns'; -import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; import { InvestigationStatusBadge } from '../../../../components/investigation_status_badge/investigation_status_badge'; import { InvestigationTag } from '../../../../components/investigation_tag/investigation_tag'; import { useInvestigation } from '../../contexts/investigation_context'; import { AlertDetailsButton } from './alert_details_button'; +import { ExternalIncidentButton } from './external_incident_button'; export function InvestigationHeader() { const { investigation } = useInvestigation(); @@ -62,6 +63,12 @@ export function InvestigationHeader() { /> + + {!!investigation.externalIncidentUrl && ( + + + + )} ); diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_items/investigation_items.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_items/investigation_items.tsx index c15cdf3d3f7f3..f9398038173cd 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_items/investigation_items.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/investigation_items/investigation_items.tsx @@ -18,7 +18,7 @@ export function InvestigationItems() { return ( - + + - - - - + + + diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts index ffefe757c7c72..3e0b5e8fe8b87 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts @@ -45,9 +45,7 @@ export function investigationRepositoryFactory({ logger: Logger; }): InvestigationRepository { function toInvestigation(stored: StoredInvestigation): Investigation | undefined { - const result = investigationSchema.safeParse({ - ...stored, - }); + const result = investigationSchema.safeParse(stored); if (!result.success) { logger.error(`Invalid stored Investigation with id [${stored.id}]`); diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.tsx index dc94dab6194a0..dac2fe6af63e8 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.tsx @@ -183,6 +183,7 @@ export function HeaderActions({ type: 'alert', id: alert.fields[ALERT_UUID], }, + externalIncidentUrl: null, }, });