mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [feat(rca): add external incident url (#193919)](https://github.com/elastic/kibana/pull/193919) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Kevin Delemme","email":"kevin.delemme@elastic.co"},"sourceCommit":{"committedDate":"2024-09-25T15:19:11Z","message":"feat(rca): add external incident url (#193919)","sha":"5d51da90f626606ef6ea217e4b0be68e85aaa86c","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management","v8.16.0"],"title":"feat(rca): add external incident url","number":193919,"url":"https://github.com/elastic/kibana/pull/193919","mergeCommit":{"message":"feat(rca): add external incident url (#193919)","sha":"5d51da90f626606ef6ea217e4b0be68e85aaa86c"}},"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/193919","number":193919,"mergeCommit":{"message":"feat(rca): add external incident url (#193919)","sha":"5d51da90f626606ef6ea217e4b0be68e85aaa86c"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Kevin Delemme <kevin.delemme@elastic.co>
This commit is contained in:
parent
462a1c2102
commit
44e1ca26f9
12 changed files with 161 additions and 28 deletions
|
@ -20,6 +20,7 @@ const createInvestigationParamsSchema = z.object({
|
|||
}),
|
||||
origin: z.union([alertOriginSchema, blankOriginSchema]),
|
||||
tags: z.array(z.string()),
|
||||
externalIncidentUrl: z.string().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
@ -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<typeof statusSchema>;
|
||||
|
|
|
@ -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<InvestigationForm>();
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
isInvalid={getFieldState('externalIncidentUrl').invalid}
|
||||
label={I18N_LABEL}
|
||||
>
|
||||
<Controller
|
||||
name="externalIncidentUrl"
|
||||
control={control}
|
||||
rules={{ required: false }}
|
||||
render={({ field: { ref, ...field }, fieldState }) => (
|
||||
<EuiFieldText
|
||||
{...field}
|
||||
value={field.value || ''}
|
||||
data-test-subj="investigateAppExternalIncidentFieldFieldText"
|
||||
fullWidth
|
||||
isInvalid={fieldState.invalid}
|
||||
placeholder={I18N_LABEL}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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<InvestigationForm>({
|
||||
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) {
|
|||
<EuiFlexItem grow>
|
||||
<TagsField />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow>
|
||||
<ExternalIncidentField />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -70,7 +70,7 @@ export function InvestigationDetails({ user }: Props) {
|
|||
],
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexGroup direction="row" responsive>
|
||||
<EuiFlexItem grow={8}>
|
||||
<InvestigationItems />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -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 (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="externalIncidentHeaderButton"
|
||||
iconType="link"
|
||||
size="xs"
|
||||
href={investigation.externalIncidentUrl}
|
||||
target="_blank"
|
||||
>
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.investigateApp.investigationHeader.externalIncidentTextLabel', {
|
||||
defaultMessage: 'External incident',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
|
@ -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() {
|
|||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{!!investigation.externalIncidentUrl && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExternalIncidentButton />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ export function InvestigationItems() {
|
|||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<InvestigationSearchBar
|
||||
dateRangeFrom={globalParams.timeRange.from}
|
||||
dateRangeTo={globalParams.timeRange.to}
|
||||
|
@ -31,11 +31,11 @@ export function InvestigationItems() {
|
|||
updateInvestigationParams({ timeRange: nextTimeRange });
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<InvestigationItemsList />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<InvestigationItemsList />
|
||||
</EuiFlexItem>
|
||||
|
||||
<AddInvestigationItem />
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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}]`);
|
||||
|
|
|
@ -183,6 +183,7 @@ export function HeaderActions({
|
|||
type: 'alert',
|
||||
id: alert.fields[ALERT_UUID],
|
||||
},
|
||||
externalIncidentUrl: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue