From d98e6aefbd84b8fb6e46337cee99dc894117cc10 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Mon, 25 Nov 2024 12:01:21 +0100 Subject: [PATCH 01/14] add attachment to a case --- .../document_count_chart.tsx | 4 ++ .../src/dual_brush/dual_brush.tsx | 21 +++++++- .../progress_controls/progress_controls.tsx | 3 ++ .../ml/aiops_log_rate_analysis/constants.ts | 2 + .../cases/log_rate_analysis_attachment.tsx | 52 +++++++++++++++++++ .../aiops/public/cases/register_cases.tsx | 34 ++++++++++++ .../document_count_content.tsx | 8 ++- .../log_rate_analysis_app_state.tsx | 26 ++++++---- .../log_rate_analysis_attachments_menu.tsx | 46 +++++++++++++++- .../log_rate_analysis_content.tsx | 2 +- .../log_rate_analysis_results.tsx | 4 ++ .../embeddable_log_rate_analysis_factory.tsx | 2 + .../embeddables/log_rate_analysis/types.ts | 2 + .../aiops/public/hooks/use_cases_modal.ts | 9 +++- .../log_rate_analysis_embeddable_wrapper.tsx | 44 +++++++--------- x-pack/plugins/aiops/server/register_cases.ts | 4 ++ .../application/aiops/log_rate_analysis.tsx | 1 + 17 files changed, 222 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/aiops/public/cases/log_rate_analysis_attachment.tsx diff --git a/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx index d9f68fe7ef890..6e44f01cbbd69 100644 --- a/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx +++ b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx @@ -136,6 +136,8 @@ export interface DocumentCountChartProps { dataTestSubj?: string; /** Optional change point metadata */ changePoint?: DocumentCountStatsChangePoint; + /** Whether the brush should be non-interactive */ + nonInteractive?: boolean; } const SPEC_ID = 'document_count'; @@ -190,6 +192,7 @@ export const DocumentCountChart: FC = (props) => { barHighlightColorOverride, deviationBrush = {}, baselineBrush = {}, + nonInteractive, } = props; const { data, uiSettings, fieldFormats, charts } = dependencies; @@ -470,6 +473,7 @@ export const DocumentCountChart: FC = (props) => { marginLeft={mlBrushMarginLeft} snapTimestamps={snapTimestamps} width={mlBrushWidth} + nonInteractive={nonInteractive} /> diff --git a/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx index 08b9fc5628297..932f509e164de 100644 --- a/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx +++ b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx @@ -88,6 +88,11 @@ interface DualBrushProps { * Width */ width: number; + /** + * Whether the brush should be non-interactive. When true, the brush is still visible + * but cannot be moved or resized by the user. + */ + nonInteractive?: boolean; } /** @@ -98,7 +103,16 @@ interface DualBrushProps { * @returns The DualBrush component. */ export const DualBrush: FC = (props) => { - const { windowParameters, min, max, onChange, marginLeft, snapTimestamps, width } = props; + const { + windowParameters, + min, + max, + onChange, + marginLeft, + snapTimestamps, + width, + nonInteractive, + } = props; const d3BrushContainer = useRef(null); const brushes = useRef([]); @@ -301,6 +315,10 @@ export const DualBrush: FC = (props) => { .attr('rx', BRUSH_HANDLE_ROUNDED_CORNER) .attr('ry', BRUSH_HANDLE_ROUNDED_CORNER); + if (nonInteractive) { + mlBrushSelection.merge(mlBrushSelection).attr('pointer-events', 'none'); + } + mlBrushSelection.exit().remove(); } @@ -355,6 +373,7 @@ export const DualBrush: FC = (props) => { deviationMax, snapTimestamps, onChange, + nonInteractive, ]); return ( diff --git a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx index 173f33e08f0b4..d53a8c91352ed 100644 --- a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx +++ b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx @@ -40,6 +40,7 @@ interface ProgressControlProps { shouldRerunAnalysis: boolean; runAnalysisDisabled?: boolean; analysisInfo?: React.ReactNode; + resetDisabled?: boolean; } /** @@ -63,6 +64,7 @@ export const ProgressControls: FC> = (pr shouldRerunAnalysis, runAnalysisDisabled = false, analysisInfo = null, + resetDisabled = false, } = props; const progressOutput = Math.round(progress * 100); @@ -120,6 +122,7 @@ export const ProgressControls: FC> = (pr size="s" onClick={onReset} color="text" + disabled={resetDisabled} > diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts b/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts index a9812a7507441..2d915870ca129 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts @@ -39,3 +39,5 @@ export const EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE = 'aiopsLogRateAnalysisEmbeddable /** */ export const LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME = 'aiopsLogRateAnalysisEmbeddableDataViewId'; + +export const CASES_ATTACHMENT_LOG_RATE_ANALYSIS = 'aiopsLogRateAnalysisEmbeddable'; diff --git a/x-pack/plugins/aiops/public/cases/log_rate_analysis_attachment.tsx b/x-pack/plugins/aiops/public/cases/log_rate_analysis_attachment.tsx new file mode 100644 index 0000000000000..d3d74d3913084 --- /dev/null +++ b/x-pack/plugins/aiops/public/cases/log_rate_analysis_attachment.tsx @@ -0,0 +1,52 @@ +/* + * 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 React from 'react'; +import { memoize } from 'lodash'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +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 type { + LogRateAnalysisEmbeddableWrapper, + LogRateAnalysisEmbeddableWrapperProps, +} from '../shared_components/log_rate_analysis_embeddable_wrapper'; + +export const initComponent = memoize( + (fieldFormats: FieldFormatsStart, LogRateAnalysisComponent: LogRateAnalysisEmbeddableWrapper) => { + return React.memo((props: PersistableStateAttachmentViewProps) => { + const { persistableStateAttachmentState } = props; + const dataFormatter = fieldFormats.deserialize({ + id: FIELD_FORMAT_IDS.DATE, + }); + const inputProps = + persistableStateAttachmentState as unknown as LogRateAnalysisEmbeddableWrapperProps; + + const listItems = [ + { + title: ( + + ), + description: `${dataFormatter.convert( + inputProps.timeRange.from + )} - ${dataFormatter.convert(inputProps.timeRange.to)}`, + }, + ]; + + return ( + <> + + + + ); + }); + } +); diff --git a/x-pack/plugins/aiops/public/cases/register_cases.tsx b/x-pack/plugins/aiops/public/cases/register_cases.tsx index b3b6efaf16d28..7198a99238b19 100644 --- a/x-pack/plugins/aiops/public/cases/register_cases.tsx +++ b/x-pack/plugins/aiops/public/cases/register_cases.tsx @@ -12,8 +12,10 @@ 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 { CASES_ATTACHMENT_LOG_PATTERN } from '@kbn/aiops-log-pattern-analysis/constants'; +import { CASES_ATTACHMENT_LOG_RATE_ANALYSIS } from '@kbn/aiops-log-rate-analysis/constants'; import { getChangePointDetectionComponent, + getLogRateAnalysisEmbeddableWrapperComponent, getPatternAnalysisComponent, } from '../shared_components'; import type { AiopsPluginStartDeps } from '../types'; @@ -72,4 +74,36 @@ export function registerCases( }), }), }); + + const LogRateAnalysisEmbeddableWrapperComponent = getLogRateAnalysisEmbeddableWrapperComponent( + coreStart, + pluginStart + ); + + cases.attachmentFramework.registerPersistableState({ + id: CASES_ATTACHMENT_LOG_RATE_ANALYSIS, + icon: 'machineLearningApp', + displayName: i18n.translate('xpack.aiops.logRateAnalysis.cases.attachmentName', { + defaultMessage: 'Log rate analysis', + }), + getAttachmentViewObject: () => ({ + event: ( + + ), + timelineAvatar: 'machineLearningApp', + children: React.lazy(async () => { + const { initComponent } = await import('./log_rate_analysis_attachment'); + + return { + default: initComponent( + pluginStart.fieldFormats, + LogRateAnalysisEmbeddableWrapperComponent + ), + }; + }), + }), + }); } diff --git a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx index 9f4f6f1deb5bd..f7b84c9460a1d 100644 --- a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx +++ b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx @@ -45,19 +45,25 @@ export const DocumentCountContent: FC = ({ const { documentStats } = useAppSelector((s) => s.logRateAnalysis); const { sampleProbability, totalCount, documentCountStats } = documentStats; + const isCasesEmbedding = embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.CASES; + + const isEmbeddedInDashboardOrCases = + embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD || isCasesEmbedding; + if (documentCountStats === undefined) { return totalCount !== undefined && embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD ? ( ) : null; } - if (embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD) { + if (isEmbeddedInDashboardOrCases) { return ( ); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index 89a8c9aee19ae..3c37ea6e484a8 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -58,6 +58,8 @@ export const LogRateAnalysisAppState: FC = ({ if (warning !== null) { return <>{warning}; } + const CasesContext = appContextValue.cases?.ui.getCasesContext() ?? React.Fragment; + const casesPermissions = appContextValue.cases?.helpers.canUseCases(); const datePickerDeps: DatePickerDependencies = { ...pick(appContextValue, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), @@ -67,17 +69,19 @@ export const LogRateAnalysisAppState: FC = ({ return ( - - - - - - - - - - - + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx index c8c9bad1568a4..e7aa0931ff5cc 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx @@ -26,15 +26,22 @@ import { EuiSpacer, EuiSwitch, } from '@elastic/eui'; +import type { WindowParameters } from '@kbn/aiops-log-rate-analysis/window_parameters'; +import { useCasesModal } from '../../../hooks/use_cases_modal'; import { useDataSource } from '../../../hooks/use_data_source'; import type { LogRateAnalysisEmbeddableState } from '../../../embeddables/log_rate_analysis/types'; import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); -export const LogRateAnalysisAttachmentsMenu = () => { +export const LogRateAnalysisAttachmentsMenu = ({ + windowParameters, +}: { + windowParameters?: WindowParameters; +}) => { const { application: { capabilities }, + cases, embeddable, } = useAiopsAppContext(); const { dataView } = useDataSource(); @@ -44,9 +51,17 @@ export const LogRateAnalysisAttachmentsMenu = () => { const [dashboardAttachmentReady, setDashboardAttachmentReady] = useState(false); const timeRange = useTimeRangeUpdates(); + const absoluteTimeRange = useTimeRangeUpdates(true); + + const openCasesModalCallback = useCasesModal(EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE); const canEditDashboards = capabilities.dashboard.createNew; + const { create: canCreateCase, update: canUpdateCase } = cases?.helpers?.canUseCases() ?? { + create: false, + update: false, + }; + const onSave: SaveModalDashboardProps['onSave'] = useCallback( ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); @@ -88,6 +103,24 @@ export const LogRateAnalysisAttachmentsMenu = () => { }, ] : []), + ...(canUpdateCase || canCreateCase + ? [ + { + name: i18n.translate('xpack.aiops.logRateAnalysis.attachToCaseLabel', { + defaultMessage: 'Add to case', + }), + 'data-test-subj': 'aiopsLogRateAnalysisAttachToCaseButton', + onClick: () => { + setIsActionMenuOpen(false); + openCasesModalCallback({ + dataViewId: dataView.id, + timeRange: absoluteTimeRange, + ...(windowParameters && { windowParameters }), + }); + }, + }, + ] + : []), ], }, { @@ -131,7 +164,16 @@ export const LogRateAnalysisAttachmentsMenu = () => { ), }, ]; - }, [canEditDashboards, applyTimeRange]); + }, [ + canEditDashboards, + canUpdateCase, + canCreateCase, + applyTimeRange, + openCasesModalCallback, + dataView.id, + absoluteTimeRange, + windowParameters, + ]); return ( <> diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx index 29ac8d0efffc8..3039116b84cd2 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx @@ -217,7 +217,7 @@ export const LogRateAnalysisContent: FC = ({ barColorOverride={barColorOverride} barHighlightColorOverride={barHighlightColorOverride} barStyleAccessor={barStyleAccessor} - attachmentsMenu={} + attachmentsMenu={} /> )} diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index 1eb4f8fd0af0d..f0e149c2b570b 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -333,6 +333,8 @@ export const LogRateAnalysisResults: FC = ({ }, 0); const foundGroups = groupTableItems.length > 0 && groupItemCount > 0; + const isAnalysisControlsDisabled = embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.CASES; + return (
= ({ onReset={onReset} shouldRerunAnalysis={shouldRerunAnalysis || searchQueryUpdated} analysisInfo={} + runAnalysisDisabled={isAnalysisControlsDisabled} + resetDisabled={isAnalysisControlsDisabled} > <> {embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx index 592ec32cef120..6d1ea7d2e03c1 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx @@ -37,6 +37,8 @@ import type { LogRateAnalysisEmbeddableState, } from './types'; +export type EmbeddableLogRateAnalysisType = typeof EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE; + export const getLogRateAnalysisEmbeddableFactory = ( getStartServices: StartServicesAccessor ) => { diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts index d2255e6dacb87..3d4fe1e05ea35 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { WindowParameters } from '@kbn/aiops-log-rate-analysis'; import type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; import type { HasEditCapabilities, @@ -28,6 +29,7 @@ export type LogRateAnalysisEmbeddableApi = DefaultEmbeddableApi = T extends EmbeddableChangePointChartType ? ChangePointEmbeddableRuntimeState : T extends EmbeddablePatternAnalysisType ? PatternAnalysisEmbeddableRuntimeState + : T extends EmbeddableLogRateAnalysisType + ? LogRateAnalysisEmbeddableRuntimeState : never; /** diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx index 9f2a88e73461c..f08f4f0e68bd9 100644 --- a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx @@ -10,10 +10,7 @@ import React, { useEffect, useMemo, useState, type FC } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; import type { Observable } from 'rxjs'; import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; -import { createBrowserHistory } from 'history'; -import { UrlStateProvider } from '@kbn/ml-url-state'; -import { Router } from '@kbn/shared-ux-router'; import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { UI_SETTINGS } from '@kbn/data-service'; @@ -22,6 +19,7 @@ import type { TimeRange } from '@kbn/es-query'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; import type { SignificantItem } from '@kbn/ml-agg-utils'; +import type { WindowParameters } from '@kbn/aiops-log-rate-analysis'; import { AiopsAppContext, type AiopsAppContextValue } from '../hooks/use_aiops_app_context'; import { DataSourceContextProvider } from '../hooks/use_data_source'; import { ReloadContextProvider } from '../hooks/use_reload'; @@ -60,6 +58,7 @@ export interface LogRateAnalysisEmbeddableWrapperProps { onLoading: (isLoading: boolean) => void; onRenderComplete: () => void; onError: (error: Error) => void; + windowParameters?: WindowParameters; } const LogRateAnalysisEmbeddableWrapperWithDeps: FC = ({ @@ -71,6 +70,7 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC timeRange, embeddingOrigin, lastReloadRequestTime, + windowParameters, }) => { const deps = useMemo(() => { const { lens, data, usageCollection, fieldFormats, charts, share, storage, unifiedSearch } = @@ -120,8 +120,6 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC ); }, [manualReload$]); - const history = createBrowserHistory(); - // We use the following pattern to track changes of dataViewId, and if there's // a change, we unmount and remount the complete inner component. This makes // sure the component is reinitialized correctly when the options of the @@ -150,26 +148,22 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC }} > {showComponent && ( - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + )}
); diff --git a/x-pack/plugins/aiops/server/register_cases.ts b/x-pack/plugins/aiops/server/register_cases.ts index 5649c88ca6327..ac126a74a5a21 100644 --- a/x-pack/plugins/aiops/server/register_cases.ts +++ b/x-pack/plugins/aiops/server/register_cases.ts @@ -9,6 +9,7 @@ import type { Logger } from '@kbn/core/server'; import type { CasesServerSetup } from '@kbn/cases-plugin/server'; import { CASES_ATTACHMENT_CHANGE_POINT_CHART } from '@kbn/aiops-change-point-detection/constants'; import { CASES_ATTACHMENT_LOG_PATTERN } from '@kbn/aiops-log-pattern-analysis/constants'; +import { CASES_ATTACHMENT_LOG_RATE_ANALYSIS } from '@kbn/aiops-log-rate-analysis/constants'; export function registerCasesPersistableState(cases: CasesServerSetup | undefined, logger: Logger) { if (cases) { @@ -19,6 +20,9 @@ export function registerCasesPersistableState(cases: CasesServerSetup | undefine cases.attachmentFramework.registerPersistableState({ id: CASES_ATTACHMENT_LOG_PATTERN, }); + cases.attachmentFramework.registerPersistableState({ + id: CASES_ATTACHMENT_LOG_RATE_ANALYSIS, + }); } catch (error) { logger.warn(`AIOPs failed to register cases persistable state`); } diff --git a/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx index d06c46cc6f71e..01f4d2d7be74f 100644 --- a/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx @@ -60,6 +60,7 @@ export const LogRateAnalysisPage: FC = () => { 'unifiedSearch', 'observabilityAIAssistant', 'embeddable', + 'cases', ]), }} /> From 6983e2e091c509694c17ecc906527b00cae39b48 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Tue, 26 Nov 2024 14:02:21 +0100 Subject: [PATCH 02/14] disable cases action when no results && fix open in log pattern action --- .../log_rate_analysis_app_state.tsx | 5 +++- .../log_rate_analysis_attachments_menu.tsx | 24 ++++++++++++++++--- .../log_rate_analysis_content.tsx | 9 ++++++- .../use_view_in_discover_action.tsx | 7 +++++- ...se_view_in_log_pattern_analysis_action.tsx | 9 ++++++- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index 3c37ea6e484a8..0c0880ec3987e 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -25,6 +25,7 @@ import { AIOPS_STORAGE_KEYS } from '../../types/storage'; import { LogRateAnalysisPage } from './log_rate_analysis_page'; import { timeSeriesDataViewWarning } from '../../application/utils/time_series_dataview_check'; +import { FilterQueryContextProvider } from '../../hooks/use_filters_query'; const localStorage = new Storage(window.localStorage); @@ -75,7 +76,9 @@ export const LogRateAnalysisAppState: FC = ({ - + + + diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx index e7aa0931ff5cc..4258b62d6e5ac 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx @@ -34,11 +34,15 @@ import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); +interface LogRateAnalysisAttachmentsMenuProps { + windowParameters?: WindowParameters; + hasSignificantItemsToAttach: boolean; +} + export const LogRateAnalysisAttachmentsMenu = ({ windowParameters, -}: { - windowParameters?: WindowParameters; -}) => { + hasSignificantItemsToAttach, +}: LogRateAnalysisAttachmentsMenuProps) => { const { application: { capabilities }, cases, @@ -110,6 +114,19 @@ export const LogRateAnalysisAttachmentsMenu = ({ defaultMessage: 'Add to case', }), 'data-test-subj': 'aiopsLogRateAnalysisAttachToCaseButton', + disabled: !hasSignificantItemsToAttach, + ...(!hasSignificantItemsToAttach + ? { + toolTipProps: { position: 'left' as const }, + toolTipContent: i18n.translate( + 'xpack.aiops.logRateAnalysis.attachToCaseTooltipContent', + { + defaultMessage: + 'Cannot add to case because the analysis did not produce any results', + } + ), + } + : {}), onClick: () => { setIsActionMenuOpen(false); openCasesModalCallback({ @@ -168,6 +185,7 @@ export const LogRateAnalysisAttachmentsMenu = ({ canEditDashboards, canUpdateCase, canCreateCase, + hasSignificantItemsToAttach, applyTimeRange, openCasesModalCallback, dataView.id, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx index 3039116b84cd2..5d3c53b06ef45 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx @@ -206,6 +206,8 @@ export const LogRateAnalysisContent: FC = ({ const changePointType = documentCountStats?.changePoint?.type; + const hasSignificantItemsToAttach = significantItems.length > 0; + return ( = ({ barColorOverride={barColorOverride} barHighlightColorOverride={barHighlightColorOverride} barStyleAccessor={barStyleAccessor} - attachmentsMenu={} + attachmentsMenu={ + + } /> )} diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx index 765f1435a93ad..a9a030ecd16b8 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx @@ -16,6 +16,7 @@ import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; +import { useFilterQueryUpdates } from '../../hooks/use_filters_query'; const viewInDiscoverMessage = i18n.translate( 'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover', @@ -32,6 +33,10 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => [share?.url.locators] ); + // We cannot rely on the time range from AiOps App context because it is not always in sync with the time range used for analysis. + // E.g. In the case of an embeddable inside cases, the time range is fixed and not coming from the time picker. + const { timeRange } = useFilterQueryUpdates(); + const discoverUrlError = useMemo(() => { if (!application.capabilities.discover?.show) { const discoverNotEnabled = i18n.translate( @@ -69,7 +74,7 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => if (discoverLocator !== undefined) { const url = await discoverLocator.getRedirectUrl({ indexPatternId: dataViewId, - timeRange: data.query.timefilter.timefilter.getTime(), + timeRange, filters: data.query.filterManager.getFilters(), query: { language: SEARCH_QUERY_LANGUAGE.KUERY, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx index d92ce014d68fb..e96df8d140a14 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx @@ -18,6 +18,7 @@ import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; +import { useFilterQueryUpdates } from '../../hooks/use_filters_query'; const isLogPattern = (tableItem: SignificantItem | GroupTableItem) => isSignificantItem(tableItem) && tableItem.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN; @@ -34,6 +35,10 @@ export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableIte const mlLocator = useMemo(() => share?.url.locators.get('ML_APP_LOCATOR'), [share?.url.locators]); + // We cannot rely on the time range from AiOps App context because it is not always in sync with the time range used for analysis. + // E.g. In the case of an embeddable inside cases, the time range is fixed and not coming from the time picker. + const { timeRange } = useFilterQueryUpdates(); + const generateLogPatternAnalysisUrl = async ( groupTableItem: GroupTableItem | SignificantItem ) => { @@ -57,7 +62,9 @@ export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableIte page: 'aiops/log_categorization', pageState: { index: dataViewId, - timeRange: data.query.timefilter.timefilter.getTime(), + globalState: { + time: timeRange, + }, appState, }, }); From 7c159cb6ad455c534b9f92b3cfd6d7233cc3adc9 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Tue, 26 Nov 2024 14:55:02 +0100 Subject: [PATCH 03/14] update tests --- .../apps/aiops/log_rate_analysis.ts | 49 +++++++++++++++++++ .../services/aiops/log_rate_analysis_page.ts | 19 +++++++ x-pack/test/functional/services/ml/cases.ts | 8 +++ 3 files changed, 76 insertions(+) diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts index e0178cb13fe9f..fec1156b76d43 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts @@ -12,6 +12,7 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; import { isTestDataExpectedWithSampleProbability, type TestData } from './types'; import { logRateAnalysisTestData } from './log_rate_analysis_test_data'; +import { USER } from '../../services/ml/security_common'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'console', 'header', 'home', 'security']); @@ -366,6 +367,54 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await ml.navigation.navigateToMl(); }); + it(`${testData.suiteTitle} attaches log rate analysis to a case`, async () => { + await aiops.logRateAnalysisPage.navigateToDataViewSelection(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the log rate analysis page with selected data source` + ); + await ml.jobSourceSelection.selectSourceForLogRateAnalysis( + testData.sourceIndexOrSavedSearch + ); + + await ml.testExecution.logTestStep('asserts the attach to case button is disabled'); + await aiops.logRateAnalysisPage.openAttachmentsMenu(); + await aiops.logRateAnalysisPage.assertAttachToCaseButtonDisabled(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads data for full time range` + ); + + await aiops.logRateAnalysisPage.clickUseFullDataButton( + testData.expected.totalDocCountFormatted + ); + + await ml.testExecution.logTestStep('clicks the document count chart to start analysis'); + await aiops.logRateAnalysisPage.clickDocumentCountChart(testData.chartClickCoordinates); + + if (!testData.autoRun) { + await aiops.logRateAnalysisPage.assertNoAutoRunButtonExists(); + await aiops.logRateAnalysisPage.clickNoAutoRunButton(); + } + + await aiops.logRateAnalysisPage.assertAnalysisSectionExists(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} starting dashboard attachment process` + ); + const caseParams = { + title: testData.suiteTitle, + description: `Description for ${testData.suiteTitle}`, + tag: 'ml_log_rate_analysis', + reporter: USER.ML_POWERUSER, + }; + await aiops.logRateAnalysisPage.attachToCase(caseParams); + + await ml.cases.assertCaseWithLogRateAnalysisAttachment(caseParams); + + await ml.navigation.navigateToMl(); + }); + runTests(testData); }); } diff --git a/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts b/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts index 3da7659419f0f..a0a243dad7d83 100644 --- a/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts +++ b/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts @@ -13,6 +13,7 @@ import type { LogRateAnalysisType } from '@kbn/aiops-log-rate-analysis'; import type { FtrProviderContext } from '../../ftr_provider_context'; import type { LogRateAnalysisDataGenerator } from './log_rate_analysis_data_generator'; +import { CreateCaseParams } from '../cases/create'; export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrProviderContext) { const browser = getService('browser'); @@ -22,6 +23,7 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr const retry = getService('retry'); const header = getPageObject('header'); const dashboardPage = getPageObject('dashboard'); + const cases = getService('cases'); return { async assertTimeRangeSelectorSectionExists() { @@ -435,5 +437,22 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr await this.confirmAttachToDashboard(); await this.completeSaveToDashboardForm(true); }, + + async assertAttachToCaseButtonDisabled() { + const button = await testSubjects.find('aiopsLogRateAnalysisAttachToCaseButton'); + const isEnabled = await button.isEnabled(); + expect(isEnabled).to.be(false); + }, + + async clickAttachToCase() { + await testSubjects.click('aiopsLogRateAnalysisAttachToCaseButton'); + }, + + async attachToCase(params: CreateCaseParams) { + await this.openAttachmentsMenu(); + await this.clickAttachToCase(); + + await cases.create.createCaseFromModal(params); + }, }; } diff --git a/x-pack/test/functional/services/ml/cases.ts b/x-pack/test/functional/services/ml/cases.ts index 2245410985592..3f31e6da65dff 100644 --- a/x-pack/test/functional/services/ml/cases.ts +++ b/x-pack/test/functional/services/ml/cases.ts @@ -94,5 +94,13 @@ export function MachineLearningCasesProvider( await testSubjects.existOrFail('comment-persistableState-aiopsChangePointChart'); await testSubjects.existOrFail('aiopsEmbeddableChangePointChart'); }, + + async assertCaseWithLogRateAnalysisAttachment(params: CaseParams) { + await this.assertBasicCaseProps(params); + await testSubjects.existOrFail('comment-persistableState-aiopsLogRateAnalysisEmbeddable'); + await testSubjects.existOrFail('aiopsEmbeddableLogRateAnalysis'); + await testSubjects.existOrFail('aiopsDocumentCountChart'); + await testSubjects.existOrFail('aiopsLogRateAnalysisResults'); + }, }; } From 5094b7cb3878d26bf313d8dcb3f2ba98f335302d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:08:49 +0000 Subject: [PATCH 04/14] [CI] Auto-commit changed files from 'node scripts/notice' --- x-pack/plugins/aiops/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index 234420f01c52f..7b4b8493e1ab8 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -78,7 +78,6 @@ "@kbn/ui-theme", "@kbn/apm-utils", "@kbn/ml-field-stats-flyout", - "@kbn/shared-ux-router", ], "exclude": [ "target/**/*", From 6596fce7ece914daf6fc124ee81f5523c1c3b797 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Tue, 26 Nov 2024 15:38:39 +0100 Subject: [PATCH 05/14] fix types --- .../initialize_log_rate_analysis_analysis_controls.ts | 2 +- .../aiops/public/embeddables/log_rate_analysis/types.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts index 9d8a49b8f0e9c..d155546e28bf6 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts @@ -11,7 +11,7 @@ import type { LogRateAnalysisComponentApi, LogRateAnalysisEmbeddableState } from type LogRateAnalysisEmbeddableCustomState = Omit< LogRateAnalysisEmbeddableState, - 'timeRange' | 'title' | 'description' | 'hidePanelTitles' + 'timeRange' | 'title' | 'description' | 'hidePanelTitles' | 'windowParameters' >; export const initializeLogRateAnalysisControls = (rawState: LogRateAnalysisEmbeddableState) => { diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts index 3d4fe1e05ea35..28a68cecd36f3 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts @@ -38,4 +38,7 @@ export interface LogRateAnalysisEmbeddableInitialState dataViewId?: string; } -export type LogRateAnalysisEmbeddableRuntimeState = LogRateAnalysisEmbeddableState; +export type LogRateAnalysisEmbeddableRuntimeState = Omit< + LogRateAnalysisEmbeddableState, + 'windowParameters' +>; From 31a3be7d463fffb4aa1acfa49e0c240994be21b2 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Tue, 26 Nov 2024 16:19:51 +0100 Subject: [PATCH 06/14] update aiops bundle size limit --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index caa5c3b53d193..d562bdd6d580a 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -2,7 +2,7 @@ pageLoadAssetSize: actions: 20000 advancedSettings: 27596 aiAssistantManagementSelection: 19146 - aiops: 16670 + aiops: 17122 alerting: 106936 apm: 64385 banners: 17946 From d4c36b36722e62cbd431e5fe04dfb5e077a73bca Mon Sep 17 00:00:00 2001 From: rbrtj Date: Wed, 27 Nov 2024 11:24:21 +0100 Subject: [PATCH 07/14] make attachments tests more data agnostic --- .../apps/aiops/log_rate_analysis.ts | 153 ++++++++++-------- 1 file changed, 87 insertions(+), 66 deletions(-) diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts index fec1156b76d43..f13ac5133325d 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts @@ -20,6 +20,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const aiops = getService('aiops'); const retry = getService('retry'); + const cases = getService('cases'); // AIOps / Log Rate Analysis lives in the ML UI so we need some related services. const ml = getService('ml'); @@ -323,6 +324,92 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { } describe('log rate analysis', function () { + describe('attachments', function () { + const testData = logRateAnalysisTestData[0]; + + before(async () => { + await aiops.logRateAnalysisDataGenerator.generateData(testData.dataGenerator); + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + await ml.testResources.createDataViewIfNeeded( + testData.sourceIndexOrSavedSearch, + '@timestamp' + ); + await ml.navigation.navigateToMl(); + }); + + after(async () => { + await elasticChart.setNewChartUiDebugFlag(false); + await ml.testResources.deleteDataViewByTitle(testData.sourceIndexOrSavedSearch); + await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testData.dataGenerator); + await cases.api.deleteAllCases(); + }); + + it('attaches log rate analysis to a dashboard', async () => { + await aiops.logRateAnalysisPage.navigateToDataViewSelection(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the log rate analysis page with selected data source` + ); + await ml.jobSourceSelection.selectSourceForLogRateAnalysis( + testData.sourceIndexOrSavedSearch + ); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} starting dashboard attachment process` + ); + await aiops.logRateAnalysisPage.attachToDashboard(); + }); + + it('attaches log rate analysis to a case', async () => { + await ml.navigation.navigateToMl(); + + await aiops.logRateAnalysisPage.navigateToDataViewSelection(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the log rate analysis page with selected data source` + ); + await ml.jobSourceSelection.selectSourceForLogRateAnalysis( + testData.sourceIndexOrSavedSearch + ); + + await ml.testExecution.logTestStep('asserts the attach to case button is disabled'); + await aiops.logRateAnalysisPage.openAttachmentsMenu(); + await aiops.logRateAnalysisPage.assertAttachToCaseButtonDisabled(); + + await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); + + await aiops.logRateAnalysisPage.clickUseFullDataButton( + testData.expected.totalDocCountFormatted + ); + + await ml.testExecution.logTestStep('clicks the document count chart to start analysis'); + await aiops.logRateAnalysisPage.clickDocumentCountChart(testData.chartClickCoordinates); + + if (!testData.autoRun) { + await aiops.logRateAnalysisPage.assertNoAutoRunButtonExists(); + await aiops.logRateAnalysisPage.clickNoAutoRunButton(); + } + + await aiops.logRateAnalysisPage.assertAnalysisSectionExists(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} starting cases attachment process` + ); + + const caseParams = { + title: testData.suiteTitle, + description: `Description for ${testData.suiteTitle}`, + tag: 'ml_log_rate_analysis', + reporter: USER.ML_POWERUSER, + }; + + await aiops.logRateAnalysisPage.attachToCase(caseParams); + + await ml.cases.assertCaseWithLogRateAnalysisAttachment(caseParams); + }); + }); + for (const testData of logRateAnalysisTestData) { describe(`with '${testData.sourceIndexOrSavedSearch}'`, function () { before(async () => { @@ -349,72 +436,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await elasticChart.setNewChartUiDebugFlag(true); }); - it(`${testData.suiteTitle} attaches log rate analysis to a dashboard`, async () => { - await aiops.logRateAnalysisPage.navigateToDataViewSelection(); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} loads the log rate analysis page with selected data source` - ); - await ml.jobSourceSelection.selectSourceForLogRateAnalysis( - testData.sourceIndexOrSavedSearch - ); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} starting dashboard attachment process` - ); - await aiops.logRateAnalysisPage.attachToDashboard(); - - await ml.navigation.navigateToMl(); - }); - - it(`${testData.suiteTitle} attaches log rate analysis to a case`, async () => { - await aiops.logRateAnalysisPage.navigateToDataViewSelection(); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} loads the log rate analysis page with selected data source` - ); - await ml.jobSourceSelection.selectSourceForLogRateAnalysis( - testData.sourceIndexOrSavedSearch - ); - - await ml.testExecution.logTestStep('asserts the attach to case button is disabled'); - await aiops.logRateAnalysisPage.openAttachmentsMenu(); - await aiops.logRateAnalysisPage.assertAttachToCaseButtonDisabled(); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} loads data for full time range` - ); - - await aiops.logRateAnalysisPage.clickUseFullDataButton( - testData.expected.totalDocCountFormatted - ); - - await ml.testExecution.logTestStep('clicks the document count chart to start analysis'); - await aiops.logRateAnalysisPage.clickDocumentCountChart(testData.chartClickCoordinates); - - if (!testData.autoRun) { - await aiops.logRateAnalysisPage.assertNoAutoRunButtonExists(); - await aiops.logRateAnalysisPage.clickNoAutoRunButton(); - } - - await aiops.logRateAnalysisPage.assertAnalysisSectionExists(); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} starting dashboard attachment process` - ); - const caseParams = { - title: testData.suiteTitle, - description: `Description for ${testData.suiteTitle}`, - tag: 'ml_log_rate_analysis', - reporter: USER.ML_POWERUSER, - }; - await aiops.logRateAnalysisPage.attachToCase(caseParams); - - await ml.cases.assertCaseWithLogRateAnalysisAttachment(caseParams); - - await ml.navigation.navigateToMl(); - }); - runTests(testData); }); } From 114c9ce128cb659e305d96420f325a89b8b5dce4 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Wed, 27 Nov 2024 11:26:41 +0100 Subject: [PATCH 08/14] update registered persistable state attachment types --- .../attachments_framework/registered_persistable_state_trial.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/attachments_framework/registered_persistable_state_trial.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/attachments_framework/registered_persistable_state_trial.ts index 0969a9261df26..bc618b1beab42 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/attachments_framework/registered_persistable_state_trial.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/attachments_framework/registered_persistable_state_trial.ts @@ -40,6 +40,7 @@ export default ({ getService }: FtrProviderContext): void => { ml_anomaly_swimlane: 'a3517f3e53fb041e9cbb150477fb6ef0f731bd5f', ml_single_metric_viewer: '8b9532b0a40dfdfa282e262949b82cc1a643147c', aiopsPatternAnalysisEmbeddable: '6c2809a0c51e668d11794de0815b293fdb3a9060', + aiopsLogRateAnalysisEmbeddable: '6a2e9953140fa8e89f82c394d8bff3c1a5aefe66', }); }); }); From f126a0048d879a5513ebd3472978b44ffcd918e0 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Wed, 27 Nov 2024 11:31:48 +0100 Subject: [PATCH 09/14] update cases attachment removal message --- .../aiops/public/cases/register_cases.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/x-pack/plugins/aiops/public/cases/register_cases.tsx b/x-pack/plugins/aiops/public/cases/register_cases.tsx index 7198a99238b19..b98aca16fee7d 100644 --- a/x-pack/plugins/aiops/public/cases/register_cases.tsx +++ b/x-pack/plugins/aiops/public/cases/register_cases.tsx @@ -49,6 +49,14 @@ export function registerCases( }; }), }), + getAttachmentRemovalObject: () => ({ + event: ( + + ), + }), }); const LogPatternAttachmentComponent = getPatternAnalysisComponent(coreStart, pluginStart); @@ -73,6 +81,14 @@ export function registerCases( return { default: initComponent(pluginStart.fieldFormats, LogPatternAttachmentComponent) }; }), }), + getAttachmentRemovalObject: () => ({ + event: ( + + ), + }), }); const LogRateAnalysisEmbeddableWrapperComponent = getLogRateAnalysisEmbeddableWrapperComponent( @@ -105,5 +121,13 @@ export function registerCases( }; }), }), + getAttachmentRemovalObject: () => ({ + event: ( + + ), + }), }); } From 99cca0b1bf263abead23374f59945559d1f46155 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Wed, 27 Nov 2024 13:17:11 +0100 Subject: [PATCH 10/14] disable action after hitting reset fix && different messages for disabled action --- .../log_rate_analysis_attachments_menu.tsx | 37 +++++++++++++------ .../log_rate_analysis_content.tsx | 5 +-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx index 4258b62d6e5ac..2d65ceb8ed370 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx @@ -27,6 +27,7 @@ import { EuiSwitch, } from '@elastic/eui'; import type { WindowParameters } from '@kbn/aiops-log-rate-analysis/window_parameters'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import { useCasesModal } from '../../../hooks/use_cases_modal'; import { useDataSource } from '../../../hooks/use_data_source'; import type { LogRateAnalysisEmbeddableState } from '../../../embeddables/log_rate_analysis/types'; @@ -36,12 +37,14 @@ const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashb interface LogRateAnalysisAttachmentsMenuProps { windowParameters?: WindowParameters; - hasSignificantItemsToAttach: boolean; + showLogRateAnalysisResults: boolean; + significantItems: SignificantItem[]; } export const LogRateAnalysisAttachmentsMenu = ({ windowParameters, - hasSignificantItemsToAttach, + showLogRateAnalysisResults, + significantItems, }: LogRateAnalysisAttachmentsMenuProps) => { const { application: { capabilities }, @@ -66,6 +69,8 @@ export const LogRateAnalysisAttachmentsMenu = ({ update: false, }; + const isCasesAttachmentEnabled = showLogRateAnalysisResults && significantItems.length > 0; + const onSave: SaveModalDashboardProps['onSave'] = useCallback( ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); @@ -90,6 +95,19 @@ export const LogRateAnalysisAttachmentsMenu = ({ [dataView.id, embeddable, applyTimeRange, timeRange] ); + const caseAttachmentTooltipContent = useMemo(() => { + if (!showLogRateAnalysisResults) { + return i18n.translate('xpack.aiops.logRateAnalysis.attachToCaseTooltipNoAnalysis', { + defaultMessage: 'Run the analysis first to add results to a case', + }); + } + if (significantItems.length === 0) { + return i18n.translate('xpack.aiops.logRateAnalysis.attachToCaseTooltipNoResults', { + defaultMessage: 'Cannot add to case because the analysis did not produce any results', + }); + } + }, [showLogRateAnalysisResults, significantItems.length]); + const panels = useMemo>(() => { return [ { @@ -114,17 +132,11 @@ export const LogRateAnalysisAttachmentsMenu = ({ defaultMessage: 'Add to case', }), 'data-test-subj': 'aiopsLogRateAnalysisAttachToCaseButton', - disabled: !hasSignificantItemsToAttach, - ...(!hasSignificantItemsToAttach + disabled: !isCasesAttachmentEnabled, + ...(!isCasesAttachmentEnabled ? { toolTipProps: { position: 'left' as const }, - toolTipContent: i18n.translate( - 'xpack.aiops.logRateAnalysis.attachToCaseTooltipContent', - { - defaultMessage: - 'Cannot add to case because the analysis did not produce any results', - } - ), + toolTipContent: caseAttachmentTooltipContent, } : {}), onClick: () => { @@ -185,7 +197,8 @@ export const LogRateAnalysisAttachmentsMenu = ({ canEditDashboards, canUpdateCase, canCreateCase, - hasSignificantItemsToAttach, + isCasesAttachmentEnabled, + caseAttachmentTooltipContent, applyTimeRange, openCasesModalCallback, dataView.id, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx index 5d3c53b06ef45..fb3c17634fd31 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx @@ -206,8 +206,6 @@ export const LogRateAnalysisContent: FC = ({ const changePointType = documentCountStats?.changePoint?.type; - const hasSignificantItemsToAttach = significantItems.length > 0; - return ( = ({ attachmentsMenu={ } /> From 071382510445641e894c88fb2a3b9ea9e596cfa4 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Wed, 27 Nov 2024 13:32:07 +0100 Subject: [PATCH 11/14] hide analysis control buttons in cases embeddable --- .../progress_controls/progress_controls.tsx | 95 ++++++++++--------- .../document_count_content.tsx | 2 +- .../log_rate_analysis_results.tsx | 3 +- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx index d53a8c91352ed..7308185f99b65 100644 --- a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx +++ b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx @@ -40,7 +40,7 @@ interface ProgressControlProps { shouldRerunAnalysis: boolean; runAnalysisDisabled?: boolean; analysisInfo?: React.ReactNode; - resetDisabled?: boolean; + isAnalysisControlsDisabled?: boolean; } /** @@ -64,7 +64,7 @@ export const ProgressControls: FC> = (pr shouldRerunAnalysis, runAnalysisDisabled = false, analysisInfo = null, - resetDisabled = false, + isAnalysisControlsDisabled = false, } = props; const progressOutput = Math.round(progress * 100); @@ -75,54 +75,61 @@ export const ProgressControls: FC> = (pr return ( - - {!isRunning && ( - - - - - - {shouldRerunAnalysis && ( - <> - - - - - )} - - - )} - {isRunning && ( - - - - )} - - {(progress === 1 || isRunning === false) && !isBrushCleared ? ( + {!isAnalysisControlsDisabled && ( + + {!isRunning && ( + + + + + + {shouldRerunAnalysis && ( + <> + + + + + )} + + + )} + {isRunning && ( + + + + )} + + )} + + {(progress === 1 || isRunning === false) && !isBrushCleared && !isAnalysisControlsDisabled ? ( diff --git a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx index f7b84c9460a1d..b56493cf03bdb 100644 --- a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx +++ b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx @@ -51,7 +51,7 @@ export const DocumentCountContent: FC = ({ embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD || isCasesEmbedding; if (documentCountStats === undefined) { - return totalCount !== undefined && embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD ? ( + return totalCount !== undefined && !isEmbeddedInDashboardOrCases ? ( ) : null; } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index f0e149c2b570b..42fa509e6ce40 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -347,8 +347,7 @@ export const LogRateAnalysisResults: FC = ({ onReset={onReset} shouldRerunAnalysis={shouldRerunAnalysis || searchQueryUpdated} analysisInfo={} - runAnalysisDisabled={isAnalysisControlsDisabled} - resetDisabled={isAnalysisControlsDisabled} + isAnalysisControlsDisabled={isAnalysisControlsDisabled} > <> {embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( From 7fec42eb88b5893563120a11410f22d93a15390c Mon Sep 17 00:00:00 2001 From: rbrtj Date: Thu, 28 Nov 2024 16:15:52 +0100 Subject: [PATCH 12/14] update aiops bundle size limit --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index d562bdd6d580a..6f4f35cd72600 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -2,7 +2,7 @@ pageLoadAssetSize: actions: 20000 advancedSettings: 27596 aiAssistantManagementSelection: 19146 - aiops: 17122 + aiops: 17680 alerting: 106936 apm: 64385 banners: 17946 From a16acf29a744ecf2657265a724dd401257faf5c5 Mon Sep 17 00:00:00 2001 From: rbrtj Date: Fri, 29 Nov 2024 10:51:00 +0100 Subject: [PATCH 13/14] move log rate cases functional test to a separate file --- x-pack/test/functional/apps/aiops/index.ts | 1 + .../apps/aiops/log_rate_analysis.ts | 106 +++--------------- .../apps/aiops/log_rate_analysis_cases.ts | 67 +++++++++++ 3 files changed, 86 insertions(+), 88 deletions(-) create mode 100644 x-pack/test/functional/apps/aiops/log_rate_analysis_cases.ts diff --git a/x-pack/test/functional/apps/aiops/index.ts b/x-pack/test/functional/apps/aiops/index.ts index 5397a80c85bd0..981ae301a6dc5 100644 --- a/x-pack/test/functional/apps/aiops/index.ts +++ b/x-pack/test/functional/apps/aiops/index.ts @@ -32,6 +32,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./log_rate_analysis')); loadTestFile(require.resolve('./log_rate_analysis_anomaly_table')); loadTestFile(require.resolve('./log_rate_analysis_dashboard_embeddable')); + loadTestFile(require.resolve('./log_rate_analysis_cases')); loadTestFile(require.resolve('./change_point_detection')); loadTestFile(require.resolve('./change_point_detection_dashboard')); loadTestFile(require.resolve('./change_point_detection_cases')); diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts index f13ac5133325d..e0178cb13fe9f 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts @@ -12,7 +12,6 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; import { isTestDataExpectedWithSampleProbability, type TestData } from './types'; import { logRateAnalysisTestData } from './log_rate_analysis_test_data'; -import { USER } from '../../services/ml/security_common'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'console', 'header', 'home', 'security']); @@ -20,7 +19,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const aiops = getService('aiops'); const retry = getService('retry'); - const cases = getService('cases'); // AIOps / Log Rate Analysis lives in the ML UI so we need some related services. const ml = getService('ml'); @@ -324,92 +322,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { } describe('log rate analysis', function () { - describe('attachments', function () { - const testData = logRateAnalysisTestData[0]; - - before(async () => { - await aiops.logRateAnalysisDataGenerator.generateData(testData.dataGenerator); - await ml.testResources.setKibanaTimeZoneToUTC(); - await ml.securityUI.loginAsMlPowerUser(); - await ml.testResources.createDataViewIfNeeded( - testData.sourceIndexOrSavedSearch, - '@timestamp' - ); - await ml.navigation.navigateToMl(); - }); - - after(async () => { - await elasticChart.setNewChartUiDebugFlag(false); - await ml.testResources.deleteDataViewByTitle(testData.sourceIndexOrSavedSearch); - await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testData.dataGenerator); - await cases.api.deleteAllCases(); - }); - - it('attaches log rate analysis to a dashboard', async () => { - await aiops.logRateAnalysisPage.navigateToDataViewSelection(); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} loads the log rate analysis page with selected data source` - ); - await ml.jobSourceSelection.selectSourceForLogRateAnalysis( - testData.sourceIndexOrSavedSearch - ); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} starting dashboard attachment process` - ); - await aiops.logRateAnalysisPage.attachToDashboard(); - }); - - it('attaches log rate analysis to a case', async () => { - await ml.navigation.navigateToMl(); - - await aiops.logRateAnalysisPage.navigateToDataViewSelection(); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} loads the log rate analysis page with selected data source` - ); - await ml.jobSourceSelection.selectSourceForLogRateAnalysis( - testData.sourceIndexOrSavedSearch - ); - - await ml.testExecution.logTestStep('asserts the attach to case button is disabled'); - await aiops.logRateAnalysisPage.openAttachmentsMenu(); - await aiops.logRateAnalysisPage.assertAttachToCaseButtonDisabled(); - - await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); - - await aiops.logRateAnalysisPage.clickUseFullDataButton( - testData.expected.totalDocCountFormatted - ); - - await ml.testExecution.logTestStep('clicks the document count chart to start analysis'); - await aiops.logRateAnalysisPage.clickDocumentCountChart(testData.chartClickCoordinates); - - if (!testData.autoRun) { - await aiops.logRateAnalysisPage.assertNoAutoRunButtonExists(); - await aiops.logRateAnalysisPage.clickNoAutoRunButton(); - } - - await aiops.logRateAnalysisPage.assertAnalysisSectionExists(); - - await ml.testExecution.logTestStep( - `${testData.suiteTitle} starting cases attachment process` - ); - - const caseParams = { - title: testData.suiteTitle, - description: `Description for ${testData.suiteTitle}`, - tag: 'ml_log_rate_analysis', - reporter: USER.ML_POWERUSER, - }; - - await aiops.logRateAnalysisPage.attachToCase(caseParams); - - await ml.cases.assertCaseWithLogRateAnalysisAttachment(caseParams); - }); - }); - for (const testData of logRateAnalysisTestData) { describe(`with '${testData.sourceIndexOrSavedSearch}'`, function () { before(async () => { @@ -436,6 +348,24 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await elasticChart.setNewChartUiDebugFlag(true); }); + it(`${testData.suiteTitle} attaches log rate analysis to a dashboard`, async () => { + await aiops.logRateAnalysisPage.navigateToDataViewSelection(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the log rate analysis page with selected data source` + ); + await ml.jobSourceSelection.selectSourceForLogRateAnalysis( + testData.sourceIndexOrSavedSearch + ); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} starting dashboard attachment process` + ); + await aiops.logRateAnalysisPage.attachToDashboard(); + + await ml.navigation.navigateToMl(); + }); + runTests(testData); }); } diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis_cases.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis_cases.ts new file mode 100644 index 0000000000000..07c632ea7df0d --- /dev/null +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis_cases.ts @@ -0,0 +1,67 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; +import { USER } from '../../services/ml/security_common'; +import { logRateAnalysisTestData } from './log_rate_analysis_test_data'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const elasticChart = getService('elasticChart'); + const aiops = getService('aiops'); + const cases = getService('cases'); + + const testData = logRateAnalysisTestData[0]; + + // aiops lives in the ML UI so we need some related services. + const ml = getService('ml'); + + describe('log rate analysis in cases', function () { + before(async () => { + await aiops.logRateAnalysisDataGenerator.generateData(testData.dataGenerator); + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + await ml.testResources.createDataViewIfNeeded( + testData.sourceIndexOrSavedSearch, + '@timestamp' + ); + await elasticChart.setNewChartUiDebugFlag(true); + await ml.navigation.navigateToMl(); + }); + + after(async () => { + await ml.testResources.deleteDataViewByTitle(testData.sourceIndexOrSavedSearch); + await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testData.dataGenerator); + await elasticChart.setNewChartUiDebugFlag(false); + await cases.api.deleteAllCases(); + }); + + it('attaches log rate analysis to a case', async () => { + await aiops.logRateAnalysisPage.navigateToDataViewSelection(); + await ml.jobSourceSelection.selectSourceForLogRateAnalysis(testData.sourceIndexOrSavedSearch); + + await aiops.logRateAnalysisPage.openAttachmentsMenu(); + await aiops.logRateAnalysisPage.assertAttachToCaseButtonDisabled(); + + await aiops.logRateAnalysisPage.clickUseFullDataButton( + testData.expected.totalDocCountFormatted + ); + + await aiops.logRateAnalysisPage.clickDocumentCountChart(testData.chartClickCoordinates); + + const caseParams = { + title: 'Log rate analysis case', + description: 'Case with log rate analysis attachment', + tag: 'ml_log_rate_analysis', + reporter: USER.ML_POWERUSER, + }; + + await aiops.logRateAnalysisPage.attachToCase(caseParams); + + await ml.cases.assertCaseWithLogRateAnalysisAttachment(caseParams); + }); + }); +} From 33d5e29150c5734b147d73622a8010190c41aa3e Mon Sep 17 00:00:00 2001 From: rbrtj Date: Fri, 29 Nov 2024 10:53:30 +0100 Subject: [PATCH 14/14] add dots to disabled button messages --- .../log_rate_analysis_attachments_menu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx index 2d65ceb8ed370..d7e68ae42799c 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx @@ -98,12 +98,12 @@ export const LogRateAnalysisAttachmentsMenu = ({ const caseAttachmentTooltipContent = useMemo(() => { if (!showLogRateAnalysisResults) { return i18n.translate('xpack.aiops.logRateAnalysis.attachToCaseTooltipNoAnalysis', { - defaultMessage: 'Run the analysis first to add results to a case', + defaultMessage: 'Run the analysis first to add results to a case.', }); } if (significantItems.length === 0) { return i18n.translate('xpack.aiops.logRateAnalysis.attachToCaseTooltipNoResults', { - defaultMessage: 'Cannot add to case because the analysis did not produce any results', + defaultMessage: 'Cannot add to case because the analysis did not produce any results.', }); } }, [showLogRateAnalysisResults, significantItems.length]);