Skip to content

Commit

Permalink
[ML] AiOps: Action for adding Log Pattern embeddable to a dashboard a…
Browse files Browse the repository at this point in the history
…nd case (elastic#199478)

## Summary

Part of [elastic#197247](elastic#197247)

- Added the ability to add a Log Pattern Embeddable to a dashboard and
case.

- Fixed the Change Point Detection embeddable in cases and added a
functional test to cover this scenario.



https://github.com/user-attachments/assets/d09eccc1-6738-4c8b-9a54-7c78d9ac9017



### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
rbrtj and kibanamachine authored Nov 13, 2024
1 parent 7a61d10 commit 4ad25cf
Show file tree
Hide file tree
Showing 22 changed files with 575 additions and 34 deletions.
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pageLoadAssetSize:
actions: 20000
advancedSettings: 27596
aiAssistantManagementSelection: 19146
aiops: 16000
aiops: 16526
alerting: 106936
apm: 64385
banners: 17946
Expand Down
2 changes: 2 additions & 0 deletions x-pack/packages/ml/aiops_log_pattern_analysis/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

export const CASES_ATTACHMENT_LOG_PATTERN = 'aiopsPatternAnalysisEmbeddable';

export const EMBEDDABLE_PATTERN_ANALYSIS_TYPE = 'aiopsPatternAnalysisEmbeddable' as const;

export const PATTERN_ANALYSIS_DATA_VIEW_REF_NAME = 'aiopsPatternAnalysisEmbeddableDataViewId';
61 changes: 61 additions & 0 deletions x-pack/plugins/aiops/public/cases/log_pattern_attachment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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 type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { memoize } from 'lodash';
import React from 'react';
import type { PersistableStateAttachmentViewProps } from '@kbn/cases-plugin/public/client/attachment_framework/types';
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiDescriptionList } from '@elastic/eui';
import deepEqual from 'fast-deep-equal';
import type {
PatternAnalysisProps,
PatternAnalysisSharedComponent,
} from '../shared_components/pattern_analysis';

export const initComponent = memoize(
(fieldFormats: FieldFormatsStart, PatternAnalysisComponent: PatternAnalysisSharedComponent) => {
return React.memo(
(props: PersistableStateAttachmentViewProps) => {
const { persistableStateAttachmentState } = props;

const dataFormatter = fieldFormats.deserialize({
id: FIELD_FORMAT_IDS.DATE,
});

const inputProps = persistableStateAttachmentState as unknown as PatternAnalysisProps;

const listItems = [
{
title: (
<FormattedMessage
id="xpack.aiops.logPatternAnalysis.cases.timeRangeLabel"
defaultMessage="Time range"
/>
),
description: `${dataFormatter.convert(
inputProps.timeRange.from
)} - ${dataFormatter.convert(inputProps.timeRange.to)}`,
},
];

return (
<>
<EuiDescriptionList compressed type={'inline'} listItems={listItems} />
<PatternAnalysisComponent {...inputProps} embeddingOrigin={'cases'} />
</>
);
},
(prevProps, nextProps) =>
deepEqual(
prevProps.persistableStateAttachmentState,
nextProps.persistableStateAttachmentState
)
);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { CasesPublicSetup } from '@kbn/cases-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import { CASES_ATTACHMENT_CHANGE_POINT_CHART } from '@kbn/aiops-change-point-detection/constants';
import { getChangePointDetectionComponent } from '../shared_components';
import { CASES_ATTACHMENT_LOG_PATTERN } from '@kbn/aiops-log-pattern-analysis/constants';
import {
getChangePointDetectionComponent,
getPatternAnalysisComponent,
} from '../shared_components';
import type { AiopsPluginStartDeps } from '../types';

export function registerChangePointChartsAttachment(
export function registerCases(
cases: CasesPublicSetup,
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps
Expand Down Expand Up @@ -44,4 +48,28 @@ export function registerChangePointChartsAttachment(
}),
}),
});

const LogPatternAttachmentComponent = getPatternAnalysisComponent(coreStart, pluginStart);

cases.attachmentFramework.registerPersistableState({
id: CASES_ATTACHMENT_LOG_PATTERN,
icon: 'machineLearningApp',
displayName: i18n.translate('xpack.aiops.logPatternAnalysis.cases.attachmentName', {
defaultMessage: 'Log pattern analysis',
}),
getAttachmentViewObject: () => ({
event: (
<FormattedMessage
id="xpack.aiops.logPatternAnalysis.cases.attachmentEvent"
defaultMessage="added log pattern analysis"
/>
),
timelineAvatar: 'machineLearningApp',
children: React.lazy(async () => {
const { initComponent } = await import('./log_pattern_attachment');

return { default: initComponent(pluginStart.fieldFormats, LogPatternAttachmentComponent) };
}),
}),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ const FieldPanel: FC<FieldPanelProps> = ({
content: (
<EuiPanel paddingSize={'s'}>
<EuiSpacer size={'s'} />
<EuiForm data-test-subj="aiopsChangePointDetectionCasedAttachmentForm">
<EuiForm data-test-subj="aiopsChangePointDetectionCaseAttachmentForm">
<ViewTypeSelector
value={caseAttachment.viewType}
onChange={(v) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* 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 type { EuiContextMenuProps } from '@elastic/eui';
import {
EuiButton,
EuiButtonIcon,
EuiContextMenu,
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiPanel,
EuiPopover,
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { SaveModalDashboardProps } from '@kbn/presentation-util-plugin/public';
import {
LazySavedObjectSaveModalDashboard,
withSuspense,
} from '@kbn/presentation-util-plugin/public';
import React, { useCallback, useState } from 'react';
import { useMemo } from 'react';
import type { DataView } from '@kbn/data-views-plugin/common';
import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants';
import { useTimeRangeUpdates } from '@kbn/ml-date-picker';
import type { PatternAnalysisEmbeddableState } from '../../embeddables/pattern_analysis/types';
import type { RandomSamplerOption, RandomSamplerProbability } from './sampling_menu/random_sampler';
import { useCasesModal } from '../../hooks/use_cases_modal';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';

const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard);

interface AttachmentsMenuProps {
randomSamplerMode: RandomSamplerOption;
randomSamplerProbability: RandomSamplerProbability;
dataView: DataView;
selectedField?: string;
}

export const AttachmentsMenu = ({
randomSamplerMode,
randomSamplerProbability,
dataView,
selectedField,
}: AttachmentsMenuProps) => {
const {
application: { capabilities },
cases,
embeddable,
} = useAiopsAppContext();

const [applyTimeRange, setApplyTimeRange] = useState(false);

const [dashboardAttachmentReady, setDashboardAttachmentReady] = useState(false);
const [isActionMenuOpen, setIsActionMenuOpen] = useState(false);

const { create: canCreateCase, update: canUpdateCase } = cases?.helpers?.canUseCases() ?? {
create: false,
update: false,
};

const openCasesModalCallback = useCasesModal(EMBEDDABLE_PATTERN_ANALYSIS_TYPE);

const timeRange = useTimeRangeUpdates();

const canEditDashboards = capabilities.dashboard.createNew;

const onSave: SaveModalDashboardProps['onSave'] = useCallback(
({ dashboardId, newTitle, newDescription }) => {
const stateTransfer = embeddable!.getStateTransfer();

const embeddableInput: Partial<PatternAnalysisEmbeddableState> = {
title: newTitle,
description: newDescription,
dataViewId: dataView.id,
fieldName: selectedField,
randomSamplerMode,
randomSamplerProbability,
minimumTimeRangeOption: 'No minimum',
...(applyTimeRange && { timeRange }),
};

const state = {
input: embeddableInput,
type: EMBEDDABLE_PATTERN_ANALYSIS_TYPE,
};

const path = dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`;

stateTransfer.navigateToWithEmbeddablePackage('dashboards', {
state,
path,
});
},
[
embeddable,
dataView.id,
selectedField,
randomSamplerMode,
randomSamplerProbability,
applyTimeRange,
timeRange,
]
);

const panels = useMemo<EuiContextMenuProps['panels']>(() => {
return [
{
id: 'attachMainPanel',
size: 's',
items: [
...(canEditDashboards
? [
{
name: i18n.translate('xpack.aiops.logCategorization.addToDashboardTitle', {
defaultMessage: 'Add to dashboard',
}),
panel: 'attachToDashboardPanel',
'data-test-subj': 'aiopsLogPatternAnalysisAttachToDashboardButton',
},
]
: []),
...(canUpdateCase || canCreateCase
? [
{
name: i18n.translate('xpack.aiops.logCategorization.attachToCaseLabel', {
defaultMessage: 'Add to case',
}),
'data-test-subj': 'aiopsLogPatternAnalysisAttachToCaseButton',
onClick: () => {
setIsActionMenuOpen(false);
openCasesModalCallback({
dataViewId: dataView.id,
fieldName: selectedField,
minimumTimeRangeOption: 'No minimum',
randomSamplerMode,
randomSamplerProbability,
timeRange,
});
},
},
]
: []),
],
},
{
id: 'attachToDashboardPanel',
size: 's',
title: i18n.translate('xpack.aiops.logCategorization.addToDashboardTitle', {
defaultMessage: 'Add to dashboard',
}),
content: (
<EuiPanel paddingSize="s">
<EuiSpacer size="s" />
<EuiForm>
<EuiFormRow>
<EuiSwitch
label={i18n.translate('xpack.aiops.logCategorization.applyTimeRangeLabel', {
defaultMessage: 'Apply time range',
})}
checked={applyTimeRange}
onChange={(e) => setApplyTimeRange(e.target.checked)}
/>
</EuiFormRow>
<EuiSpacer size="m" />
<EuiButton
size="s"
data-test-subj="aiopsLogPatternAnalysisAttachToDashboardSubmitButton"
fill
fullWidth
type={'submit'}
onClick={() => {
setIsActionMenuOpen(false);
setDashboardAttachmentReady(true);
}}
>
<FormattedMessage
id="xpack.aiops.logCategorization.attachToDashboardSubmitButtonLabel"
defaultMessage="Attach"
/>
</EuiButton>
</EuiForm>
</EuiPanel>
),
},
];
}, [
canEditDashboards,
canUpdateCase,
canCreateCase,
applyTimeRange,
openCasesModalCallback,
dataView.id,
selectedField,
randomSamplerMode,
randomSamplerProbability,
timeRange,
]);

return (
<EuiFlexItem>
<EuiPopover
button={
<EuiButtonIcon
data-test-subj="aiopsLogPatternAnalysisAttachmentsMenuButton"
aria-label={i18n.translate('xpack.aiops.logCategorization.attachmentsMenuAriaLabel', {
defaultMessage: 'Attachments',
})}
iconType="boxesHorizontal"
color="text"
onClick={() => setIsActionMenuOpen(!isActionMenuOpen)}
/>
}
isOpen={isActionMenuOpen}
closePopover={() => setIsActionMenuOpen(false)}
panelPaddingSize="none"
anchorPosition="downRight"
>
<EuiContextMenu panels={panels} initialPanelId="attachMainPanel" />
</EuiPopover>
{dashboardAttachmentReady ? (
<SavedObjectSaveModalDashboard
canSaveByReference={false}
objectType={i18n.translate('xpack.aiops.logCategorization.objectTypeLabel', {
defaultMessage: 'Log pattern analysis',
})}
documentInfo={{
title: 'Log pattern analysis',
}}
onClose={() => setDashboardAttachmentReady(false)}
onSave={onSave}
/>
) : null}
</EuiFlexItem>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';

interface Props {
onCancel: () => void;
onCancel?: () => void;
}

export const LoadingCategorization: FC<Props> = ({ onCancel }) => (
Expand All @@ -46,7 +46,8 @@ export const LoadingCategorization: FC<Props> = ({ onCancel }) => (
<EuiFlexItem grow={false} css={{ textAlign: 'center' }}>
<EuiButton
data-test-subj="aiopsLoadingCategorizationCancelButton"
onClick={() => onCancel()}
onClick={onCancel}
disabled={!onCancel}
>
Cancel
</EuiButton>
Expand Down
Loading

0 comments on commit 4ad25cf

Please sign in to comment.