From 030c49a10fbcebaf1b6e6df815d1219a684eab44 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 4 Sep 2024 11:53:13 +0200 Subject: [PATCH 01/42] wip: log rate analysis embeddable boilerplate --- .../ml/aiops_log_rate_analysis/constants.ts | 6 + .../embeddable_log_rate_analysis_factory.tsx | 265 ++++++++++++++++++ .../log_rate_analysis_component_wrapper.tsx | 63 +++++ ...=> pattern_analysis_component_wrapper.tsx} | 0 .../aiops/public/shared_components/index.tsx | 13 + .../shared_components/log_rate_analysis.tsx | 155 ++++++++++ 6 files changed, 502 insertions(+) create mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx create mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx rename x-pack/plugins/aiops/public/embeddables/pattern_analysis/{pattern_analysys_component_wrapper.tsx => pattern_analysis_component_wrapper.tsx} (100%) create mode 100644 x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx 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 054bb876a4f7a..a9812a7507441 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts @@ -33,3 +33,9 @@ export const RANDOM_SAMPLER_SEED = 3867412; /** Highlighting color for charts */ export const LOG_RATE_ANALYSIS_HIGHLIGHT_COLOR = 'orange'; + +/** */ +export const EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE = 'aiopsLogRateAnalysisEmbeddable' as const; + +/** */ +export const LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME = 'aiopsLogRateAnalysisEmbeddableDataViewId'; 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 new file mode 100644 index 0000000000000..908bab4f7587f --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx @@ -0,0 +1,265 @@ +/* + * 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 { + EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE, + LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME, +} from '@kbn/aiops-log-rate-analysis/constants'; +import type { Reference } from '@kbn/content-management-utils'; +import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser'; +import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; +import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { + apiHasExecutionContext, + fetch$, + initializeTimeRange, + initializeTitles, + useBatchedPublishingSubjects, +} from '@kbn/presentation-publishing'; +import fastIsEqual from 'fast-deep-equal'; +import { cloneDeep } from 'lodash'; +import React, { useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { BehaviorSubject, distinctUntilChanged, map, skipWhile } from 'rxjs'; +import { getPatternAnalysisComponent } from '../../shared_components'; +import type { AiopsPluginStart, AiopsPluginStartDeps } from '../../types'; +import { initializePatternAnalysisControls } from './initialize_pattern_analysis_controls'; +import type { + PatternAnalysisEmbeddableApi, + PatternAnalysisEmbeddableRuntimeState, + PatternAnalysisEmbeddableState, +} from './types'; + +export interface EmbeddablePatternAnalysisStartServices { + data: DataPublicPluginStart; +} + +export type EmbeddablePatternAnalysisType = typeof EMBEDDABLE_PATTERN_ANALYSIS_TYPE; + +export const getDependencies = async ( + getStartServices: StartServicesAccessor +) => { + const [ + { http, uiSettings, notifications, ...startServices }, + { lens, data, usageCollection, fieldFormats }, + ] = await getStartServices(); + + return { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + ...startServices, + }; +}; + +export const getPatternAnalysisEmbeddableFactory = ( + getStartServices: StartServicesAccessor +) => { + const factory: ReactEmbeddableFactory< + PatternAnalysisEmbeddableState, + PatternAnalysisEmbeddableRuntimeState, + PatternAnalysisEmbeddableApi + > = { + type: EMBEDDABLE_PATTERN_ANALYSIS_TYPE, + deserializeState: (state) => { + const serializedState = cloneDeep(state.rawState); + // inject the reference + const dataViewIdRef = state.references?.find( + (ref) => ref.name === PATTERN_ANALYSIS_DATA_VIEW_REF_NAME + ); + // if the serializedState already contains a dataViewId, we don't want to overwrite it. (Unsaved state can cause this) + if (dataViewIdRef && serializedState && !serializedState.dataViewId) { + serializedState.dataViewId = dataViewIdRef?.id; + } + return serializedState; + }, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const [coreStart, pluginStart] = await getStartServices(); + + const { http, uiSettings, notifications, ...startServices } = coreStart; + const { lens, data, usageCollection, fieldFormats } = pluginStart; + + const deps = { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + ...startServices, + }; + + const { + api: timeRangeApi, + comparators: timeRangeComparators, + serialize: serializeTimeRange, + } = initializeTimeRange(state); + + const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); + + const { + patternAnalysisControlsApi, + serializePatternAnalysisChartState, + patternAnalysisControlsComparators, + } = initializePatternAnalysisControls(state); + + const dataLoading = new BehaviorSubject(true); + const blockingError = new BehaviorSubject(undefined); + + const dataViews$ = new BehaviorSubject([ + await deps.data.dataViews.get( + state.dataViewId ?? (await deps.data.dataViews.getDefaultId()) + ), + ]); + + const api = buildApi( + { + ...timeRangeApi, + ...titlesApi, + ...patternAnalysisControlsApi, + getTypeDisplayName: () => + i18n.translate('xpack.aiops.patternAnalysis.typeDisplayName', { + defaultMessage: 'pattern analysis', + }), + isEditingEnabled: () => true, + onEdit: async () => { + try { + const { resolveEmbeddablePatternAnalysisUserInput } = await import( + './resolve_pattern_analysis_config_input' + ); + + const result = await resolveEmbeddablePatternAnalysisUserInput( + coreStart, + pluginStart, + parentApi, + uuid, + false, + patternAnalysisControlsApi, + undefined, + serializePatternAnalysisChartState() + ); + + patternAnalysisControlsApi.updateUserInput(result); + } catch (e) { + return Promise.reject(); + } + }, + dataLoading, + blockingError, + dataViews: dataViews$, + serializeState: () => { + const dataViewId = patternAnalysisControlsApi.dataViewId.getValue(); + const references: Reference[] = dataViewId + ? [ + { + type: DATA_VIEW_SAVED_OBJECT_TYPE, + name: PATTERN_ANALYSIS_DATA_VIEW_REF_NAME, + id: dataViewId, + }, + ] + : []; + return { + rawState: { + timeRange: undefined, + ...serializeTitles(), + ...serializeTimeRange(), + ...serializePatternAnalysisChartState(), + }, + references, + }; + }, + }, + { + ...timeRangeComparators, + ...titleComparators, + ...patternAnalysisControlsComparators, + } + ); + + const PatternAnalysisComponent = getPatternAnalysisComponent(coreStart, pluginStart); + + const onLoading = (v: boolean) => dataLoading.next(v); + const onRenderComplete = () => dataLoading.next(false); + const onError = (error: Error) => blockingError.next(error); + + return { + api, + Component: () => { + if (!apiHasExecutionContext(parentApi)) { + throw new Error('Parent API does not have execution context'); + } + + const [ + dataViewId, + fieldName, + minimumTimeRangeOption, + randomSamplerMode, + randomSamplerProbability, + ] = useBatchedPublishingSubjects( + api.dataViewId, + api.fieldName, + api.minimumTimeRangeOption, + api.randomSamplerMode, + api.randomSamplerProbability + ); + + const reload$ = useMemo( + () => + fetch$(api).pipe( + skipWhile((fetchContext) => !fetchContext.isReload), + map((fetchContext) => Date.now()) + ), + [] + ); + + const timeRange$ = useMemo( + () => + fetch$(api).pipe( + map((fetchContext) => fetchContext.timeRange), + distinctUntilChanged(fastIsEqual) + ), + [] + ); + + const lastReloadRequestTime = useObservable(reload$, Date.now()); + const timeRange = useObservable(timeRange$, undefined); + + let embeddingOrigin; + if (apiHasExecutionContext(parentApi)) { + embeddingOrigin = parentApi.executionContext.type; + } + + return ( + + ); + }, + }; + }, + }; + + return factory; +}; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx new file mode 100644 index 0000000000000..169e543747763 --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx @@ -0,0 +1,63 @@ +/* + * 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 { FC } from 'react'; +import React from 'react'; +import { useDataSource } from '../../hooks/use_data_source'; +import type { LogRateAnalysisProps } from '../../shared_components/log_rate_analysis'; +import LogRateAnalysisEmbeddable from '../../components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable'; + +/** + * Grid component wrapper for embeddable. + * + * @param lastReloadRequestTime + * @param onError + * @param onLoading + * @param onRenderComplete + * @param onChange + * @param emptyState + * @param timeRange + * @constructor + */ +export const PatternAnalysisEmbeddableWrapper: FC = ({ + dataViewId, + lastReloadRequestTime, + onError, + onLoading, + onRenderComplete, + onChange, + emptyState, + timeRange, +}) => { + const { dataView } = useDataSource(); + + if (dataView.id !== dataViewId) { + return null; + } + + return ( +
+ +
+ ); +}; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysys_component_wrapper.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_component_wrapper.tsx similarity index 100% rename from x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysys_component_wrapper.tsx rename to x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_component_wrapper.tsx diff --git a/x-pack/plugins/aiops/public/shared_components/index.tsx b/x-pack/plugins/aiops/public/shared_components/index.tsx index 1c5b85c4b79b6..1dc9abd485121 100644 --- a/x-pack/plugins/aiops/public/shared_components/index.tsx +++ b/x-pack/plugins/aiops/public/shared_components/index.tsx @@ -37,3 +37,16 @@ export const getPatternAnalysisComponent = ( }; export type { PatternAnalysisSharedComponent } from './pattern_analysis'; + +const LogRateAnalysisLazy = dynamic(async () => import('./log_rate_analysis')); + +export const getLogRateAnalysisComponent = ( + coreStart: CoreStart, + pluginStart: AiopsPluginStartDeps +) => { + return React.memo((props) => { + return ; + }); +}; + +export type { LogRateAnalysisSharedComponent } from './log_rate_analysis'; diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx new file mode 100644 index 0000000000000..787548771e158 --- /dev/null +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx @@ -0,0 +1,155 @@ +/* + * 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 { EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { UI_SETTINGS } from '@kbn/data-service'; +import type { TimeRange } from '@kbn/es-query'; +import { DatePickerContextProvider } from '@kbn/ml-date-picker'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { pick } from 'lodash'; +import React, { useEffect, useMemo, useState, type FC } from 'react'; +import type { Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; +import { LogRateAnalysisEmbeddableWrapper } from '../embeddables/log_rate_analysis/log_rate__analysis_component_wrapper'; +import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context'; +import { DataSourceContextProvider } from '../hooks/use_data_source'; +import { FilterQueryContextProvider } from '../hooks/use_filters_query'; +import { ReloadContextProvider } from '../hooks/use_reload'; +import type { AiopsPluginStartDeps } from '../types'; + +/** + * Only used to initialize internally + */ +export type LogRateAnalysisPropsWithDeps = LogRateAnalysisProps & { + coreStart: CoreStart; + pluginStart: AiopsPluginStartDeps; +}; + +export type LogRateAnalysisSharedComponent = FC; + +export interface LogRateAnalysisProps { + dataViewId: string; + timeRange: TimeRange; + /** + * Component to render if there are no patterns found + */ + emptyState?: React.ReactElement; + /** + * Outputs the most recent patterns data + */ + onChange?: (significantItems: SignificantItem[]) => void; + /** + * Last reload request time, can be used for manual reload + */ + lastReloadRequestTime?: number; + /** Origin of the embeddable instance */ + embeddingOrigin?: string; + onLoading: (isLoading: boolean) => void; + onRenderComplete: () => void; + onError: (error: Error) => void; +} + +const LogRateAnalysisWrapper: FC = ({ + // Component dependencies + coreStart, + pluginStart, + // Component props + dataViewId, + timeRange, + onLoading, + onError, + onRenderComplete, + embeddingOrigin, + lastReloadRequestTime, + onChange, +}) => { + const deps = useMemo(() => { + const { http, uiSettings, notifications, ...startServices } = coreStart; + const { lens, data, usageCollection, fieldFormats, charts } = pluginStart; + + return { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + charts, + ...startServices, + }; + }, [coreStart, pluginStart]); + + const datePickerDeps = { + ...pick(deps, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), + uiSettingsKeys: UI_SETTINGS, + }; + + const aiopsAppContextValue = useMemo(() => { + return { + embeddingOrigin: embeddingOrigin ?? EMBEDDABLE_ORIGIN, + ...deps, + } as unknown as AiopsAppDependencies; + }, [deps, embeddingOrigin]); + + const [manualReload$] = useState>( + new BehaviorSubject(lastReloadRequestTime ?? Date.now()) + ); + + useEffect( + function updateManualReloadSubject() { + if (!lastReloadRequestTime) return; + manualReload$.next(lastReloadRequestTime); + }, + [lastReloadRequestTime, manualReload$] + ); + + const resultObservable$ = useMemo>(() => { + return combineLatest([manualReload$]).pipe( + map(([manualReload]) => Math.max(manualReload)), + distinctUntilChanged() + ); + }, [manualReload$]); + + // TODO: Remove data-shared-item as part of https://github.com/elastic/kibana/issues/179376> + return ( +
+ + + + + + + + + + + + + +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default LogRateAnalysisWrapper; From ce6dc171ff04df7916945d330292e5dca8489bce Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 4 Sep 2024 14:47:54 +0200 Subject: [PATCH 02/42] wip: log rate analysis embeddable boilerplate --- .../ml/aiops_log_rate_analysis/embeddable.ts | 17 ++ .../log_rate_analysis_for_embeddable.tsx | 27 +++ .../embeddable_log_rate_analysis_factory.tsx | 82 +++---- ...ize_log_rate_analysis_analysis_controls.ts | 43 ++++ .../log_rate_analysis_initializer.tsx | 226 ++++++++++++++++++ ...resolve_log_rate_analysis_config_input.tsx | 103 ++++++++ .../embeddables/log_rate_analysis/types.ts | 45 ++++ 7 files changed, 494 insertions(+), 49 deletions(-) create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts create mode 100644 x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx create mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts create mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_initializer.tsx create mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx create mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts b/x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts new file mode 100644 index 0000000000000..604d6051898e2 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts @@ -0,0 +1,17 @@ +/* + * 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 { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; + +export interface EmbeddableLogRateAnalysisInput { + dataView: DataView; + savedSearch?: SavedSearch | null; + embeddingOrigin?: string; + switchToDocumentView?: () => Promise; + lastReloadRequestTime?: number; +} diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx new file mode 100644 index 0000000000000..2eda3d59ccde6 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx @@ -0,0 +1,27 @@ +/* + * 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 { FC } from 'react'; +import React from 'react'; + +import type { EmbeddableLogRateAnalysisInput } from '@kbn/aiops-log-rate-analysis/embeddable'; + +interface LogRateAnalysisProps { + dummy: string; +} + +export type LogRateAnalysisEmbeddableProps = Readonly< + EmbeddableLogRateAnalysisInput & LogRateAnalysisProps +>; + +const BAR_TARGET = 20; + +export const LogCategorizationEmbeddable: FC = ({ dummy }) => { + return <>here be dragons: {dummy}; +}; + +// eslint-disable-next-line import/no-default-export +export default LogCategorizationEmbeddable; 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 908bab4f7587f..11dbf3c05eb84 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 @@ -28,20 +28,20 @@ import { cloneDeep } from 'lodash'; import React, { useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject, distinctUntilChanged, map, skipWhile } from 'rxjs'; -import { getPatternAnalysisComponent } from '../../shared_components'; +import { getLogRateAnalysisComponent } from '../../shared_components'; import type { AiopsPluginStart, AiopsPluginStartDeps } from '../../types'; -import { initializePatternAnalysisControls } from './initialize_pattern_analysis_controls'; +import { initializeLogRateAnalysisControls } from './initialize_log_rate_analysis_analysis_controls'; import type { - PatternAnalysisEmbeddableApi, - PatternAnalysisEmbeddableRuntimeState, - PatternAnalysisEmbeddableState, + LogRateAnalysisEmbeddableApi, + LogRateAnalysisEmbeddableRuntimeState, + LogRateAnalysisEmbeddableState, } from './types'; -export interface EmbeddablePatternAnalysisStartServices { +export interface EmbeddableLogRateAnalysisStartServices { data: DataPublicPluginStart; } -export type EmbeddablePatternAnalysisType = typeof EMBEDDABLE_PATTERN_ANALYSIS_TYPE; +export type EmbeddableLogRateAnalysisType = typeof EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE; export const getDependencies = async ( getStartServices: StartServicesAccessor @@ -63,20 +63,20 @@ export const getDependencies = async ( }; }; -export const getPatternAnalysisEmbeddableFactory = ( +export const getLogRateAnalysisEmbeddableFactory = ( getStartServices: StartServicesAccessor ) => { const factory: ReactEmbeddableFactory< - PatternAnalysisEmbeddableState, - PatternAnalysisEmbeddableRuntimeState, - PatternAnalysisEmbeddableApi + LogRateAnalysisEmbeddableState, + LogRateAnalysisEmbeddableRuntimeState, + LogRateAnalysisEmbeddableApi > = { - type: EMBEDDABLE_PATTERN_ANALYSIS_TYPE, + type: EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE, deserializeState: (state) => { const serializedState = cloneDeep(state.rawState); // inject the reference const dataViewIdRef = state.references?.find( - (ref) => ref.name === PATTERN_ANALYSIS_DATA_VIEW_REF_NAME + (ref) => ref.name === LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME ); // if the serializedState already contains a dataViewId, we don't want to overwrite it. (Unsaved state can cause this) if (dataViewIdRef && serializedState && !serializedState.dataViewId) { @@ -110,10 +110,10 @@ export const getPatternAnalysisEmbeddableFactory = ( const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); const { - patternAnalysisControlsApi, - serializePatternAnalysisChartState, - patternAnalysisControlsComparators, - } = initializePatternAnalysisControls(state); + logRateAnalysisControlsApi, + serializeLogRateAnalysisChartState, + logRateAnalysisControlsComparators, + } = initializeLogRateAnalysisControls(state); const dataLoading = new BehaviorSubject(true); const blockingError = new BehaviorSubject(undefined); @@ -128,30 +128,30 @@ export const getPatternAnalysisEmbeddableFactory = ( { ...timeRangeApi, ...titlesApi, - ...patternAnalysisControlsApi, + ...logRateAnalysisControlsApi, getTypeDisplayName: () => - i18n.translate('xpack.aiops.patternAnalysis.typeDisplayName', { - defaultMessage: 'pattern analysis', + i18n.translate('xpack.aiops.logRateAnalysis.typeDisplayName', { + defaultMessage: 'log rate analysis', }), isEditingEnabled: () => true, onEdit: async () => { try { - const { resolveEmbeddablePatternAnalysisUserInput } = await import( - './resolve_pattern_analysis_config_input' + const { resolveEmbeddableLogRateAnalysisUserInput } = await import( + './resolve_log_rate_analysis_config_input' ); - const result = await resolveEmbeddablePatternAnalysisUserInput( + const result = await resolveEmbeddableLogRateAnalysisUserInput( coreStart, pluginStart, parentApi, uuid, false, - patternAnalysisControlsApi, + logRateAnalysisControlsApi, undefined, - serializePatternAnalysisChartState() + serializeLogRateAnalysisChartState() ); - patternAnalysisControlsApi.updateUserInput(result); + logRateAnalysisControlsApi.updateUserInput(result); } catch (e) { return Promise.reject(); } @@ -160,12 +160,12 @@ export const getPatternAnalysisEmbeddableFactory = ( blockingError, dataViews: dataViews$, serializeState: () => { - const dataViewId = patternAnalysisControlsApi.dataViewId.getValue(); + const dataViewId = logRateAnalysisControlsApi.dataViewId.getValue(); const references: Reference[] = dataViewId ? [ { type: DATA_VIEW_SAVED_OBJECT_TYPE, - name: PATTERN_ANALYSIS_DATA_VIEW_REF_NAME, + name: LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME, id: dataViewId, }, ] @@ -175,7 +175,7 @@ export const getPatternAnalysisEmbeddableFactory = ( timeRange: undefined, ...serializeTitles(), ...serializeTimeRange(), - ...serializePatternAnalysisChartState(), + ...serializeLogRateAnalysisChartState(), }, references, }; @@ -184,11 +184,11 @@ export const getPatternAnalysisEmbeddableFactory = ( { ...timeRangeComparators, ...titleComparators, - ...patternAnalysisControlsComparators, + ...logRateAnalysisControlsComparators, } ); - const PatternAnalysisComponent = getPatternAnalysisComponent(coreStart, pluginStart); + const LogRateAnalysisComponent = getLogRateAnalysisComponent(coreStart, pluginStart); const onLoading = (v: boolean) => dataLoading.next(v); const onRenderComplete = () => dataLoading.next(false); @@ -201,19 +201,7 @@ export const getPatternAnalysisEmbeddableFactory = ( throw new Error('Parent API does not have execution context'); } - const [ - dataViewId, - fieldName, - minimumTimeRangeOption, - randomSamplerMode, - randomSamplerProbability, - ] = useBatchedPublishingSubjects( - api.dataViewId, - api.fieldName, - api.minimumTimeRangeOption, - api.randomSamplerMode, - api.randomSamplerProbability - ); + const [dataViewId] = useBatchedPublishingSubjects(api.dataViewId); const reload$ = useMemo( () => @@ -242,12 +230,8 @@ export const getPatternAnalysisEmbeddableFactory = ( } return ( - ; + +export const initializeLogRateAnalysisControls = (rawState: LogRateAnalysisEmbeddableState) => { + const dataViewId = new BehaviorSubject(rawState.dataViewId); + + const updateUserInput = (update: LogRateAnalysisEmbeddableCustomState) => { + dataViewId.next(update.dataViewId); + }; + + const serializeLogRateAnalysisChartState = (): LogRateAnalysisEmbeddableCustomState => { + return { + dataViewId: dataViewId.getValue(), + }; + }; + + const logRateAnalysisControlsComparators: StateComparators = + { + dataViewId: [dataViewId, (arg) => dataViewId.next(arg)], + }; + + return { + logRateAnalysisControlsApi: { + dataViewId, + updateUserInput, + } as unknown as LogRateAnalysisComponentApi, + serializeLogRateAnalysisChartState, + logRateAnalysisControlsComparators, + }; +}; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_initializer.tsx new file mode 100644 index 0000000000000..20fa45bc1ddaa --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_initializer.tsx @@ -0,0 +1,226 @@ +/* + * 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 { + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutFooter, +} from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import type { FC } from 'react'; +import React, { useEffect, useMemo, useState, useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { pick } from 'lodash'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { DataSourceContextProvider } from '../../hooks/use_data_source'; +import type { LogRateAnalysisEmbeddableRuntimeState } from './types'; +import { LogRateAnalysisSettings } from '../../components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu'; + +export interface LogRateAnalysisInitializerProps { + initialInput?: Partial; + onCreate: (props: LogRateAnalysisEmbeddableRuntimeState) => void; + onCancel: () => void; + onPreview: (update: LogRateAnalysisEmbeddableRuntimeState) => Promise; + isNewPanel: boolean; +} + +export const LogRateAnalysisEmbeddableInitializer: FC = ({ + initialInput, + onCreate, + onCancel, + onPreview, + isNewPanel, +}) => { + const { + unifiedSearch: { + ui: { IndexPatternSelect }, + }, + } = useAiopsAppContext(); + + const [formInput, setFormInput] = useState( + pick(initialInput ?? {}, ['dataViewId']) as LogRateAnalysisEmbeddableRuntimeState + ); + const [isFormValid, setIsFormValid] = useState(true); + + const updatedProps = useMemo(() => { + return { + ...formInput, + title: isPopulatedObject(formInput) + ? i18n.translate('xpack.aiops.embeddableLogRateAnalysis.attachmentTitle', { + defaultMessage: 'Log rate analysis', + }) + : '', + }; + }, [formInput]); + + useEffect( + function previewChanges() { + if (isFormValid) { + onPreview(updatedProps); + } + }, + [isFormValid, onPreview, updatedProps] + ); + + const setDataViewId = useCallback( + (dataViewId: string | undefined) => { + setFormInput({ + ...formInput, + dataViewId: dataViewId ?? '', + }); + setIsFormValid(false); + }, + [formInput] + ); + + return ( + <> + + +

+ {isNewPanel + ? i18n.translate('xpack.aiops.embeddableLogRateAnalysis.config.title.new', { + defaultMessage: 'Create log rate analysis', + }) + : i18n.translate('xpack.aiops.embeddableLogRateAnalysis.config.title.edit', { + defaultMessage: 'Edit log rate analysis', + })} +

+
+
+ + + + + { + setDataViewId(newId ?? ''); + }} + /> + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const FormControls: FC<{ + formInput: LogRateAnalysisEmbeddableRuntimeState; + onChange: (update: LogRateAnalysisEmbeddableRuntimeState) => void; + onValidationChange: (isValid: boolean) => void; +}> = ({ formInput, onChange, onValidationChange }) => { + useEffect( + function validateForm() { + onValidationChange(formInput.dataViewId !== undefined); + }, + [formInput, onValidationChange] + ); + + useEffect( + function samplerChange() { + onChange({ + ...formInput, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [onChange] + ); + + useEffect( + function samplerChange() { + onChange({ + ...formInput, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [onChange] + ); + + return ( + <> + + + ); +}; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx new file mode 100644 index 0000000000000..293c4d526835a --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx @@ -0,0 +1,103 @@ +/* + * 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 { CoreStart } from '@kbn/core/public'; +import { tracksOverlays } from '@kbn/presentation-containers'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import React from 'react'; +import type { AiopsAppDependencies } from '../..'; +import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; +import type { AiopsPluginStartDeps } from '../../types'; +import { LogRateAnalysisEmbeddableInitializer } from './log_rate_analysis_initializer'; +import type { LogRateAnalysisComponentApi, LogRateAnalysisEmbeddableState } from './types'; + +export async function resolveEmbeddableLogRateAnalysisUserInput( + coreStart: CoreStart, + pluginStart: AiopsPluginStartDeps, + parentApi: unknown, + focusedPanelId: string, + isNewPanel: boolean, + logRateAnalysisControlsApi: LogRateAnalysisComponentApi, + deletePanel?: () => void, + initialState?: LogRateAnalysisEmbeddableState +): Promise { + const { overlays } = coreStart; + + const overlayTracker = tracksOverlays(parentApi) ? parentApi : undefined; + + let hasChanged = false; + return new Promise(async (resolve, reject) => { + try { + const cancelChanges = () => { + if (isNewPanel && deletePanel) { + deletePanel(); + } else if (hasChanged && logRateAnalysisControlsApi && initialState) { + // Reset to initialState in case user has changed the preview state + logRateAnalysisControlsApi.updateUserInput(initialState); + } + + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + + const update = async (nextUpdate: LogRateAnalysisEmbeddableState) => { + resolve(nextUpdate); + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + + const preview = async (nextUpdate: LogRateAnalysisEmbeddableState) => { + if (logRateAnalysisControlsApi) { + logRateAnalysisControlsApi.updateUserInput(nextUpdate); + hasChanged = true; + } + }; + + const flyoutSession = overlays.openFlyout( + toMountPoint( + + + , + coreStart + ), + { + ownFocus: true, + size: 's', + type: 'push', + paddingSize: 'm', + hideCloseButton: true, + 'data-test-subj': 'aiopsLogRateAnalysisEmbeddableInitializer', + 'aria-labelledby': 'logRateAnalysisConfig', + onClose: () => { + reject(); + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }, + } + ); + + if (tracksOverlays(parentApi)) { + parentApi.openOverlay(flyoutSession, { focusedPanelId }); + } + } catch (error) { + reject(error); + } + }); +} 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 new file mode 100644 index 0000000000000..9dd8bb7ee4392 --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts @@ -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 type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; +import type { + HasEditCapabilities, + PublishesDataViews, + PublishesTimeRange, + PublishingSubject, + SerializedTimeRange, + SerializedTitles, +} from '@kbn/presentation-publishing'; +import type { FC } from 'react'; + +export type ViewComponent = FC<{ + interval: string; + onRenderComplete?: () => void; +}>; + +export interface LogRateAnalysisComponentApi { + dataViewId: PublishingSubject; + updateUserInput: (update: LogRateAnalysisEmbeddableState) => void; +} + +export type LogRateAnalysisEmbeddableApi = DefaultEmbeddableApi & + HasEditCapabilities & + PublishesDataViews & + PublishesTimeRange & + LogRateAnalysisComponentApi; + +export interface LogRateAnalysisEmbeddableState extends SerializedTitles, SerializedTimeRange { + dataViewId: string; +} + +export interface LogRateAnalysisEmbeddableInitialState + extends SerializedTitles, + SerializedTimeRange { + dataViewId?: string; +} + +export type LogRateAnalysisEmbeddableRuntimeState = LogRateAnalysisEmbeddableState; From 00651f03e0039bc9b2c47bc54309cc8082efe97f Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 5 Sep 2024 08:12:36 +0200 Subject: [PATCH 03/42] ui action --- .../embeddable_menu.tsx | 87 +++++++++++++++++ .../log_rate_analysis_component_wrapper.tsx | 2 +- .../shared_components/log_rate_analysis.tsx | 2 +- .../shared_components/pattern_analysis.tsx | 2 +- .../create_log_rate_analysis_actions.tsx | 97 +++++++++++++++++++ .../plugins/aiops/public/ui_actions/index.ts | 4 + .../log_rate_analysis_action_context.ts | 24 +++++ 7 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx create mode 100644 x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx create mode 100644 x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx new file mode 100644 index 0000000000000..aa1193ebae828 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx @@ -0,0 +1,87 @@ +/* + * 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 { EuiPopoverTitle, EuiToolTip } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiPanel, + EuiPopover, + EuiSpacer, + EuiTitle, + EuiHorizontalRule, +} from '@elastic/eui'; +import type { FC } from 'react'; +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +interface Props { + reload: () => void; +} + +export const EmbeddableMenu: FC = ({ reload }) => { + const [showMenu, setShowMenu] = useState(false); + const togglePopover = () => setShowMenu(!showMenu); + + const button = ( + + togglePopover()} + // @ts-expect-error - subdued does work + color="subdued" + aria-label={i18n.translate('xpack.aiops.logCategorization.embeddableMenu.aria', { + defaultMessage: 'Log rate analysis options', + })} + /> + + ); + + return ( + togglePopover()} + panelPaddingSize="s" + anchorPosition="downRight" + > + + + + + + + + + + + + + + ); +}; + +interface LogRateAnalysisSettingsProps { + compressed?: boolean; +} + +export const LogRateAnalysisSettings: FC = ({ + compressed = false, +}) => { + return <>options dragons; +}; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx index 169e543747763..40650572f17e5 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx @@ -23,7 +23,7 @@ import LogRateAnalysisEmbeddable from '../../components/log_rate_analysis/log_ra * @param timeRange * @constructor */ -export const PatternAnalysisEmbeddableWrapper: FC = ({ +export const LogRateAnalysisEmbeddableWrapper: FC = ({ dataViewId, lastReloadRequestTime, onError, diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx index 787548771e158..2a5d7ba17b362 100644 --- a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx @@ -15,7 +15,7 @@ import React, { useEffect, useMemo, useState, type FC } from 'react'; import type { Observable } from 'rxjs'; import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; import type { SignificantItem } from '@kbn/ml-agg-utils'; -import { LogRateAnalysisEmbeddableWrapper } from '../embeddables/log_rate_analysis/log_rate__analysis_component_wrapper'; +import { LogRateAnalysisEmbeddableWrapper } from '../embeddables/log_rate_analysis/log_rate_analysis_component_wrapper'; import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context'; import { DataSourceContextProvider } from '../hooks/use_data_source'; import { FilterQueryContextProvider } from '../hooks/use_filters_query'; diff --git a/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx index 78261cd1f62f0..87a15047df79f 100644 --- a/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx @@ -20,7 +20,7 @@ import type { RandomSamplerOption, RandomSamplerProbability, } from '../components/log_categorization/sampling_menu/random_sampler'; -import { PatternAnalysisEmbeddableWrapper } from '../embeddables/pattern_analysis/pattern_analysys_component_wrapper'; +import { PatternAnalysisEmbeddableWrapper } from '../embeddables/pattern_analysis/pattern_analysis_component_wrapper'; import { AiopsAppContext, type AiopsAppContextValue } from '../hooks/use_aiops_app_context'; import { DataSourceContextProvider } from '../hooks/use_data_source'; import { FilterQueryContextProvider } from '../hooks/use_filters_query'; diff --git a/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx new file mode 100644 index 0000000000000..f823cd6900936 --- /dev/null +++ b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx @@ -0,0 +1,97 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { PresentationContainer } from '@kbn/presentation-containers'; +import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; +import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants'; +import type { AiopsPluginStartDeps } from '../types'; +import type { LogRateAnalysisActionContext } from './log_rate_analysis_action_context'; +import type { + LogRateAnalysisEmbeddableApi, + LogRateAnalysisEmbeddableInitialState, +} from '../embeddables/log_rate_analysis/types'; + +const parentApiIsCompatible = async ( + parentApi: unknown +): Promise => { + const { apiIsPresentationContainer } = await import('@kbn/presentation-containers'); + // we cannot have an async type check, so return the casted parentApi rather than a boolean + return apiIsPresentationContainer(parentApi) ? (parentApi as PresentationContainer) : undefined; +}; + +export function createAddLogRateAnalysisEmbeddableAction( + coreStart: CoreStart, + pluginStart: AiopsPluginStartDeps +): UiActionsActionDefinition { + return { + id: 'create-log-rate-analysis-embeddable', + grouping: [ + { + id: 'ml', + getDisplayName: () => + i18n.translate('xpack.aiops.navMenu.mlAppNameText', { + defaultMessage: 'Machine Learning and Analytics', + }), + getIconType: () => 'logLogRateAnalysis', + }, + ], + getIconType: () => 'logLogRateAnalysis', + getDisplayName: () => + i18n.translate('xpack.aiops.embeddableLogRateAnalysisDisplayName', { + defaultMessage: 'Log rate analysis', + }), + async isCompatible(context: EmbeddableApiContext) { + return Boolean(await parentApiIsCompatible(context.embeddable)); + }, + async execute(context) { + const presentationContainerParent = await parentApiIsCompatible(context.embeddable); + if (!presentationContainerParent) throw new IncompatibleActionError(); + + try { + const { resolveEmbeddableLogRateAnalysisUserInput } = await import( + '../embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input' + ); + + const initialState: LogRateAnalysisEmbeddableInitialState = { + dataViewId: undefined, + }; + + const embeddable = await presentationContainerParent.addNewPanel< + object, + LogRateAnalysisEmbeddableApi + >({ + panelType: EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE, + initialState, + }); + + if (!embeddable) { + return; + } + + const deletePanel = () => { + presentationContainerParent.removePanel(embeddable.uuid); + }; + + resolveEmbeddableLogRateAnalysisUserInput( + coreStart, + pluginStart, + context.embeddable, + embeddable.uuid, + true, + embeddable, + deletePanel + ); + } catch (e) { + return Promise.reject(); + } + }, + }; +} diff --git a/x-pack/plugins/aiops/public/ui_actions/index.ts b/x-pack/plugins/aiops/public/ui_actions/index.ts index d14856fd28733..6081541c448e7 100644 --- a/x-pack/plugins/aiops/public/ui_actions/index.ts +++ b/x-pack/plugins/aiops/public/ui_actions/index.ts @@ -18,6 +18,7 @@ import { createOpenChangePointInMlAppAction } from './open_change_point_ml'; import type { AiopsPluginStartDeps } from '../types'; import { createCategorizeFieldAction } from '../components/log_categorization'; import { createAddPatternAnalysisEmbeddableAction } from './create_pattern_analysis_action'; +import { createAddLogRateAnalysisEmbeddableAction } from './create_log_rate_analysis_actions'; export function registerAiopsUiActions( uiActions: UiActionsSetup, @@ -27,6 +28,7 @@ export function registerAiopsUiActions( const openChangePointInMlAppAction = createOpenChangePointInMlAppAction(coreStart, pluginStart); const addChangePointChartAction = createAddChangePointChartAction(coreStart, pluginStart); const addPatternAnalysisAction = createAddPatternAnalysisEmbeddableAction(coreStart, pluginStart); + const addLogRateAnalysisAction = createAddLogRateAnalysisEmbeddableAction(coreStart, pluginStart); uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addPatternAnalysisAction); uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addChangePointChartAction); @@ -39,4 +41,6 @@ export function registerAiopsUiActions( ); uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, openChangePointInMlAppAction); + + uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addLogRateAnalysisAction); } diff --git a/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts b/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts new file mode 100644 index 0000000000000..cf1fbe83a9dc4 --- /dev/null +++ b/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts @@ -0,0 +1,24 @@ +/* + * 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 { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { apiIsOfType, type EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants'; +import type { ChangePointEmbeddableApi } from '../embeddables/change_point_chart/types'; + +export interface LogRateAnalysisActionContext extends EmbeddableApiContext { + embeddable: ChangePointEmbeddableApi; +} + +export function isLogRateAnalysisEmbeddableContext( + arg: unknown +): arg is LogRateAnalysisActionContext { + return ( + isPopulatedObject(arg, ['embeddable']) && + apiIsOfType(arg.embeddable, EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE) + ); +} From a66710dd92d4493b66a628fa4eee0ab108ea6d84 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 5 Sep 2024 13:43:59 +0200 Subject: [PATCH 04/42] fix register embeddable factory --- x-pack/plugins/aiops/public/embeddables/index.ts | 5 +++++ .../aiops/public/embeddables/log_rate_analysis/index.ts | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/index.ts diff --git a/x-pack/plugins/aiops/public/embeddables/index.ts b/x-pack/plugins/aiops/public/embeddables/index.ts index b7d9ad25951fb..dae1f0eb3eeec 100644 --- a/x-pack/plugins/aiops/public/embeddables/index.ts +++ b/x-pack/plugins/aiops/public/embeddables/index.ts @@ -9,6 +9,7 @@ import type { CoreSetup } from '@kbn/core-lifecycle-browser'; import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants'; import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants'; +import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants'; import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types'; export const registerEmbeddables = ( @@ -23,4 +24,8 @@ export const registerEmbeddables = ( const { getPatternAnalysisEmbeddableFactory } = await import('./pattern_analysis'); return getPatternAnalysisEmbeddableFactory(core.getStartServices); }); + embeddable.registerReactEmbeddableFactory(EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE, async () => { + const { getLogRateAnalysisEmbeddableFactory } = await import('./log_rate_analysis'); + return getLogRateAnalysisEmbeddableFactory(core.getStartServices); + }); }; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/index.ts b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/index.ts new file mode 100644 index 0000000000000..2203d4c64bc8b --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { getLogRateAnalysisEmbeddableFactory } from './embeddable_log_rate_analysis_factory'; From 83b926502c6d8bdada29203c7fcf429dadb66600 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 5 Sep 2024 14:09:43 +0200 Subject: [PATCH 05/42] fix icon --- .../public/ui_actions/create_log_rate_analysis_actions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx index f823cd6900936..a73b3eb6332f2 100644 --- a/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx +++ b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx @@ -40,10 +40,10 @@ export function createAddLogRateAnalysisEmbeddableAction( i18n.translate('xpack.aiops.navMenu.mlAppNameText', { defaultMessage: 'Machine Learning and Analytics', }), - getIconType: () => 'logLogRateAnalysis', + getIconType: () => 'logRateAnalysis', }, ], - getIconType: () => 'logLogRateAnalysis', + getIconType: () => 'logRateAnalysis', getDisplayName: () => i18n.translate('xpack.aiops.embeddableLogRateAnalysisDisplayName', { defaultMessage: 'Log rate analysis', From 61893b953293d2f1c6647c77bac4bdcd1089e945 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 5 Sep 2024 14:36:25 +0200 Subject: [PATCH 06/42] replace here be dragons with actual embedded content --- .../log_rate_analysis_for_embeddable.tsx | 61 ++++++++++++++++--- .../use_view_in_discover_action.tsx | 4 +- ...se_view_in_log_pattern_analysis_action.tsx | 2 +- .../public/hooks/use_aiops_app_context.ts | 2 +- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx index 2eda3d59ccde6..b2e65f881909f 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx @@ -5,23 +5,68 @@ * 2.0. */ import type { FC } from 'react'; -import React from 'react'; +import React, { useMemo } from 'react'; +import { pick } from 'lodash'; +import { createBrowserHistory } from 'history'; +import datemath from '@elastic/datemath'; + +import { Router } from '@kbn/shared-ux-router'; import type { EmbeddableLogRateAnalysisInput } from '@kbn/aiops-log-rate-analysis/embeddable'; -interface LogRateAnalysisProps { - dummy: string; -} +import { LogRateAnalysisContent } from '../../../shared_lazy_components'; +import type { LogRateAnalysisProps } from '../../../shared_components/log_rate_analysis'; +import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; export type LogRateAnalysisEmbeddableProps = Readonly< EmbeddableLogRateAnalysisInput & LogRateAnalysisProps >; -const BAR_TARGET = 20; +export const LogRateAnalysisEmbeddable: FC = ({ + dataView, + timeRange, +}) => { + const services = useAiopsAppContext(); + + const timeRangeParsed = useMemo(() => { + if (timeRange) { + const min = datemath.parse(timeRange.from); + const max = datemath.parse(timeRange.to); + if (min && max) { + return { min, max }; + } + } + }, [timeRange]); + + const history = createBrowserHistory(); -export const LogCategorizationEmbeddable: FC = ({ dummy }) => { - return <>here be dragons: {dummy}; + return ( + + + + ); }; // eslint-disable-next-line import/no-default-export -export default LogCategorizationEmbeddable; +export default LogRateAnalysisEmbeddable; 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 ec4284d6452e5..765f1435a93ad 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 @@ -28,8 +28,8 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => const { application, share, data } = useAiopsAppContext(); const discoverLocator = useMemo( - () => share.url.locators.get('DISCOVER_APP_LOCATOR'), - [share.url.locators] + () => share?.url.locators.get('DISCOVER_APP_LOCATOR'), + [share?.url.locators] ); const discoverUrlError = useMemo(() => { 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 dbac6fbe8c9f3..ec1f6774b6b46 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 @@ -32,7 +32,7 @@ const viewInLogPatternAnalysisMessage = i18n.translate( export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableItemAction => { const { application, share, data } = useAiopsAppContext(); - const mlLocator = useMemo(() => share.url.locators.get('ML_APP_LOCATOR'), [share.url.locators]); + const mlLocator = useMemo(() => share?.url.locators.get('ML_APP_LOCATOR'), [share?.url.locators]); const generateLogPatternAnalysisUrl = async ( groupTableItem: GroupTableItem | SignificantItem diff --git a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts index 03762a7ba70ba..488c4c589399a 100644 --- a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts +++ b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts @@ -98,7 +98,7 @@ export interface AiopsAppContextValue { /** * Used to create deep links to other plugins. */ - share: SharePluginStart; + share?: SharePluginStart; /** * Used to create lens embeddables. */ From c4ab732bac98d203c05f00291d5d2fdd20c6704b Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 26 Sep 2024 14:53:08 +0200 Subject: [PATCH 07/42] fix nested embedding with multiple contexts --- .../progress_controls/progress_controls.tsx | 3 +- .../log_rate_analysis_for_embeddable.tsx | 72 --------------- .../log_rate_analysis_component_wrapper.tsx | 63 -------------- ..._rate_analysis_embeddable_initializer.tsx} | 33 +++---- ...resolve_log_rate_analysis_config_input.tsx | 28 ++---- .../aiops/public/shared_components/index.tsx | 3 +- .../shared_components/log_rate_analysis.tsx | 87 ++++++++++++------- 7 files changed, 85 insertions(+), 204 deletions(-) delete mode 100644 x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx delete mode 100644 x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx rename x-pack/plugins/aiops/public/embeddables/log_rate_analysis/{log_rate_analysis_initializer.tsx => log_rate_analysis_embeddable_initializer.tsx} (94%) 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 098f7038f82c8..e4ea9493ac274 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 @@ -26,6 +26,8 @@ import { useAnimatedProgressBarBackground } from './use_animated_progress_bar_ba // TODO Consolidate with duplicate component `CorrelationsProgressControls` in // `x-pack/plugins/observability_solution/apm/public/components/app/correlations/progress_controls.tsx` +const analysisCompleteStyle = { display: 'none' }; + /** * Props for ProgressControlProps */ @@ -70,7 +72,6 @@ export const ProgressControls: FC> = (pr const { euiTheme } = useEuiTheme(); const runningProgressBarStyles = useAnimatedProgressBarBackground(euiTheme.colors.success); - const analysisCompleteStyle = { display: 'none' }; return ( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx deleted file mode 100644 index b2e65f881909f..0000000000000 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 { FC } from 'react'; -import React, { useMemo } from 'react'; -import { pick } from 'lodash'; -import { createBrowserHistory } from 'history'; - -import datemath from '@elastic/datemath'; - -import { Router } from '@kbn/shared-ux-router'; -import type { EmbeddableLogRateAnalysisInput } from '@kbn/aiops-log-rate-analysis/embeddable'; - -import { LogRateAnalysisContent } from '../../../shared_lazy_components'; -import type { LogRateAnalysisProps } from '../../../shared_components/log_rate_analysis'; -import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; - -export type LogRateAnalysisEmbeddableProps = Readonly< - EmbeddableLogRateAnalysisInput & LogRateAnalysisProps ->; - -export const LogRateAnalysisEmbeddable: FC = ({ - dataView, - timeRange, -}) => { - const services = useAiopsAppContext(); - - const timeRangeParsed = useMemo(() => { - if (timeRange) { - const min = datemath.parse(timeRange.from); - const max = datemath.parse(timeRange.to); - if (min && max) { - return { min, max }; - } - } - }, [timeRange]); - - const history = createBrowserHistory(); - - return ( - - - - ); -}; - -// eslint-disable-next-line import/no-default-export -export default LogRateAnalysisEmbeddable; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx deleted file mode 100644 index 40650572f17e5..0000000000000 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_component_wrapper.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { FC } from 'react'; -import React from 'react'; -import { useDataSource } from '../../hooks/use_data_source'; -import type { LogRateAnalysisProps } from '../../shared_components/log_rate_analysis'; -import LogRateAnalysisEmbeddable from '../../components/log_rate_analysis/log_rate_analysis_for_embeddable/log_rate_analysis_for_embeddable'; - -/** - * Grid component wrapper for embeddable. - * - * @param lastReloadRequestTime - * @param onError - * @param onLoading - * @param onRenderComplete - * @param onChange - * @param emptyState - * @param timeRange - * @constructor - */ -export const LogRateAnalysisEmbeddableWrapper: FC = ({ - dataViewId, - lastReloadRequestTime, - onError, - onLoading, - onRenderComplete, - onChange, - emptyState, - timeRange, -}) => { - const { dataView } = useDataSource(); - - if (dataView.id !== dataViewId) { - return null; - } - - return ( -
- -
- ); -}; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx similarity index 94% rename from x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_initializer.tsx rename to x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx index 20fa45bc1ddaa..9f118070ce10e 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx @@ -5,6 +5,10 @@ * 2.0. */ +import type { FC } from 'react'; +import React, { useEffect, useMemo, useState, useCallback } from 'react'; +import { pick } from 'lodash'; + import { EuiFlyoutHeader, EuiTitle, @@ -18,19 +22,20 @@ import { EuiFlexItem, EuiFlyoutFooter, } from '@elastic/eui'; + +import type { IndexPatternSelectProps } from '@kbn/unified-search-plugin/public'; import { euiThemeVars } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; -import type { FC } from 'react'; -import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { pick } from 'lodash'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; + import { DataSourceContextProvider } from '../../hooks/use_data_source'; -import type { LogRateAnalysisEmbeddableRuntimeState } from './types'; import { LogRateAnalysisSettings } from '../../components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu'; -export interface LogRateAnalysisInitializerProps { +import type { LogRateAnalysisEmbeddableRuntimeState } from './types'; + +export interface LogRateAnalysisEmbeddableInitializerProps { + IndexPatternSelect: React.ComponentType; initialInput?: Partial; onCreate: (props: LogRateAnalysisEmbeddableRuntimeState) => void; onCancel: () => void; @@ -38,19 +43,9 @@ export interface LogRateAnalysisInitializerProps { isNewPanel: boolean; } -export const LogRateAnalysisEmbeddableInitializer: FC = ({ - initialInput, - onCreate, - onCancel, - onPreview, - isNewPanel, -}) => { - const { - unifiedSearch: { - ui: { IndexPatternSelect }, - }, - } = useAiopsAppContext(); - +export const LogRateAnalysisEmbeddableInitializer: FC< + LogRateAnalysisEmbeddableInitializerProps +> = ({ IndexPatternSelect, initialInput, onCreate, onCancel, onPreview, isNewPanel }) => { const [formInput, setFormInput] = useState( pick(initialInput ?? {}, ['dataViewId']) as LogRateAnalysisEmbeddableRuntimeState ); diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx index 293c4d526835a..f2c714792d9c8 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx @@ -9,10 +9,8 @@ import type { CoreStart } from '@kbn/core/public'; import { tracksOverlays } from '@kbn/presentation-containers'; import { toMountPoint } from '@kbn/react-kibana-mount'; import React from 'react'; -import type { AiopsAppDependencies } from '../..'; -import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; import type { AiopsPluginStartDeps } from '../../types'; -import { LogRateAnalysisEmbeddableInitializer } from './log_rate_analysis_initializer'; +import { LogRateAnalysisEmbeddableInitializer } from './log_rate_analysis_embeddable_initializer'; import type { LogRateAnalysisComponentApi, LogRateAnalysisEmbeddableState } from './types'; export async function resolveEmbeddableLogRateAnalysisUserInput( @@ -59,22 +57,14 @@ export async function resolveEmbeddableLogRateAnalysisUserInput( const flyoutSession = overlays.openFlyout( toMountPoint( - - - , + , coreStart ), { diff --git a/x-pack/plugins/aiops/public/shared_components/index.tsx b/x-pack/plugins/aiops/public/shared_components/index.tsx index 1dc9abd485121..647d804355e14 100644 --- a/x-pack/plugins/aiops/public/shared_components/index.tsx +++ b/x-pack/plugins/aiops/public/shared_components/index.tsx @@ -11,6 +11,7 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser'; import type { AiopsPluginStartDeps } from '../types'; import type { ChangePointDetectionSharedComponent } from './change_point_detection'; import type { PatternAnalysisSharedComponent } from './pattern_analysis'; +import type { LogRateAnalysisSharedComponent } from './log_rate_analysis'; const ChangePointDetectionLazy = dynamic(async () => import('./change_point_detection')); @@ -43,7 +44,7 @@ const LogRateAnalysisLazy = dynamic(async () => import('./log_rate_analysis')); export const getLogRateAnalysisComponent = ( coreStart: CoreStart, pluginStart: AiopsPluginStartDeps -) => { +): LogRateAnalysisSharedComponent => { return React.memo((props) => { return ; }); diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx index 2a5d7ba17b362..8f11a908f72c8 100644 --- a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx @@ -4,24 +4,33 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import { pick } from 'lodash'; +import React, { useEffect, useMemo, useState, type FC } from 'react'; +import type { Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; +import { createBrowserHistory } from 'history'; + +import datemath from '@elastic/datemath'; + +import { UrlStateProvider } from '@kbn/ml-url-state'; +import { Router } from '@kbn/shared-ux-router'; import { EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { UI_SETTINGS } from '@kbn/data-service'; +import { LogRateAnalysisReduxProvider } from '@kbn/aiops-log-rate-analysis/state'; import type { TimeRange } from '@kbn/es-query'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { pick } from 'lodash'; -import React, { useEffect, useMemo, useState, type FC } from 'react'; -import type { Observable } from 'rxjs'; -import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; import type { SignificantItem } from '@kbn/ml-agg-utils'; -import { LogRateAnalysisEmbeddableWrapper } from '../embeddables/log_rate_analysis/log_rate_analysis_component_wrapper'; + import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context'; import { DataSourceContextProvider } from '../hooks/use_data_source'; -import { FilterQueryContextProvider } from '../hooks/use_filters_query'; import { ReloadContextProvider } from '../hooks/use_reload'; import type { AiopsPluginStartDeps } from '../types'; +import { LogRateAnalysisDocumentCountChartData } from '../components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_document_count_chart_data'; +import { LogRateAnalysisContent } from '../components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content'; + /** * Only used to initialize internally */ @@ -36,11 +45,11 @@ export interface LogRateAnalysisProps { dataViewId: string; timeRange: TimeRange; /** - * Component to render if there are no patterns found + * Component to render if there are no significant items found */ emptyState?: React.ReactElement; /** - * Outputs the most recent patterns data + * Outputs the most recent significant items */ onChange?: (significantItems: SignificantItem[]) => void; /** @@ -116,37 +125,57 @@ const LogRateAnalysisWrapper: FC = ({ ); }, [manualReload$]); + const history = createBrowserHistory(); + + const esSearchQuery = { match_all: {} }; + + const timeRangeParsed = useMemo(() => { + if (timeRange) { + const min = datemath.parse(timeRange.from); + const max = datemath.parse(timeRange.to); + if (min && max) { + return { min, max }; + } + } + }, [timeRange]); + // TODO: Remove data-shared-item as part of https://github.com/elastic/kibana/issues/179376> return (
- - - - + + + + - - - + + + + + + - - - - + + + +
); }; From 17c03f4147930af7a2607552e2481331b4f0b760 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 26 Sep 2024 15:18:54 +0200 Subject: [PATCH 08/42] tweak progress bar layout --- .../progress_controls/progress_controls.tsx | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 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 e4ea9493ac274..173f33e08f0b4 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 @@ -26,8 +26,6 @@ import { useAnimatedProgressBarBackground } from './use_animated_progress_bar_ba // TODO Consolidate with duplicate component `CorrelationsProgressControls` in // `x-pack/plugins/observability_solution/apm/public/components/app/correlations/progress_controls.tsx` -const analysisCompleteStyle = { display: 'none' }; - /** * Props for ProgressControlProps */ @@ -145,32 +143,30 @@ export const ProgressControls: FC> = (pr
) : null} - - - - + + + + + + + - - - - - - + + + ) : null} {children} From c8689ea535c0b4c7649667c46e4e902c12bec6f3 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 26 Sep 2024 15:20:21 +0200 Subject: [PATCH 09/42] pass deps to DataSourceContextProvider instead of using context within context --- .../change_point_chart_initializer.tsx | 3 ++- .../log_rate_analysis_embeddable_initializer.tsx | 14 ++++++++++++-- .../resolve_log_rate_analysis_config_input.tsx | 1 + .../pattern_analysis_initializer.tsx | 3 ++- .../plugins/aiops/public/hooks/use_data_source.tsx | 11 +++-------- .../shared_components/change_point_detection.tsx | 5 ++++- .../public/shared_components/log_rate_analysis.tsx | 5 ++++- .../public/shared_components/pattern_analysis.tsx | 5 ++++- 8 files changed, 32 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx index 08cf6ffd7dcb1..0b451d77e6b96 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx @@ -54,6 +54,7 @@ export const ChangePointChartInitializer: FC = ({ onCancel, }) => { const { + data: { dataViews }, unifiedSearch: { ui: { IndexPatternSelect }, }, @@ -135,7 +136,7 @@ export const ChangePointChartInitializer: FC = ({ }} /> - + ; initialInput?: Partial; onCreate: (props: LogRateAnalysisEmbeddableRuntimeState) => void; @@ -45,7 +47,15 @@ export interface LogRateAnalysisEmbeddableInitializerProps { export const LogRateAnalysisEmbeddableInitializer: FC< LogRateAnalysisEmbeddableInitializerProps -> = ({ IndexPatternSelect, initialInput, onCreate, onCancel, onPreview, isNewPanel }) => { +> = ({ + dataViews, + IndexPatternSelect, + initialInput, + onCreate, + onCancel, + onPreview, + isNewPanel, +}) => { const [formInput, setFormInput] = useState( pick(initialInput ?? {}, ['dataViewId']) as LogRateAnalysisEmbeddableRuntimeState ); @@ -128,7 +138,7 @@ export const LogRateAnalysisEmbeddableInitializer: FC< }} /> - + { const { + data: { dataViews }, unifiedSearch: { ui: { IndexPatternSelect }, }, @@ -166,7 +167,7 @@ export const PatternAnalysisEmbeddableInitializer: FC - + ({ get dataView(): never { @@ -30,6 +30,7 @@ export interface DataViewAndSavedSearch { } export interface DataSourceContextProviderProps { + dataViews: DataViewsPublicPluginStart; dataViewId?: string; savedSearchId?: string; /** Output resolves data view objects */ @@ -43,20 +44,14 @@ export interface DataSourceContextProviderProps { * @constructor */ export const DataSourceContextProvider: FC> = ({ + dataViews, dataViewId, - savedSearchId, children, onChange, }) => { const [value, setValue] = useState(); const [error, setError] = useState(); - const { - data: { dataViews }, - // uiSettings, - // savedSearch: savedSearchService, - } = useAiopsAppContext(); - /** * Resolve data view or saved search if exists. */ diff --git a/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx b/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx index 9afbd9e1c4c8d..6aeeeba6be342 100644 --- a/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx +++ b/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx @@ -145,7 +145,10 @@ const ChangePointDetectionWrapper: FC = ({ - + = ({ - + = ({ - + Date: Thu, 26 Sep 2024 15:32:00 +0200 Subject: [PATCH 10/42] cleanup settings --- .../embeddable_menu.tsx | 87 ------------------- ...g_rate_analysis_embeddable_initializer.tsx | 59 ++----------- 2 files changed, 5 insertions(+), 141 deletions(-) delete mode 100644 x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx deleted file mode 100644 index aa1193ebae828..0000000000000 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { EuiPopoverTitle, EuiToolTip } from '@elastic/eui'; -import { - EuiButtonIcon, - EuiPanel, - EuiPopover, - EuiSpacer, - EuiTitle, - EuiHorizontalRule, -} from '@elastic/eui'; -import type { FC } from 'react'; -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; - -import { FormattedMessage } from '@kbn/i18n-react'; - -interface Props { - reload: () => void; -} - -export const EmbeddableMenu: FC = ({ reload }) => { - const [showMenu, setShowMenu] = useState(false); - const togglePopover = () => setShowMenu(!showMenu); - - const button = ( - - togglePopover()} - // @ts-expect-error - subdued does work - color="subdued" - aria-label={i18n.translate('xpack.aiops.logCategorization.embeddableMenu.aria', { - defaultMessage: 'Log rate analysis options', - })} - /> - - ); - - return ( - togglePopover()} - panelPaddingSize="s" - anchorPosition="downRight" - > - - - - - - - - - - - - - - ); -}; - -interface LogRateAnalysisSettingsProps { - compressed?: boolean; -} - -export const LogRateAnalysisSettings: FC = ({ - compressed = false, -}) => { - return <>options dragons; -}; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx index 785e03ef3bcee..edad6e1c73623 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx @@ -15,7 +15,6 @@ import { EuiFlyoutBody, EuiForm, EuiFormRow, - EuiSpacer, EuiButton, EuiButtonEmpty, EuiFlexGroup, @@ -30,9 +29,6 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DataSourceContextProvider } from '../../hooks/use_data_source'; -import { LogRateAnalysisSettings } from '../../components/log_rate_analysis/log_rate_analysis_for_embeddable/embeddable_menu'; - import type { LogRateAnalysisEmbeddableRuntimeState } from './types'; export interface LogRateAnalysisEmbeddableInitializerProps { @@ -59,7 +55,11 @@ export const LogRateAnalysisEmbeddableInitializer: FC< const [formInput, setFormInput] = useState( pick(initialInput ?? {}, ['dataViewId']) as LogRateAnalysisEmbeddableRuntimeState ); - const [isFormValid, setIsFormValid] = useState(true); + + const isFormValid = useMemo( + () => isPopulatedObject(formInput, ['dataViewId']) && formInput.dataViewId !== '', + [formInput] + ); const updatedProps = useMemo(() => { return { @@ -87,7 +87,6 @@ export const LogRateAnalysisEmbeddableInitializer: FC< ...formInput, dataViewId: dataViewId ?? '', }); - setIsFormValid(false); }, [formInput] ); @@ -138,15 +137,6 @@ export const LogRateAnalysisEmbeddableInitializer: FC< }} /> - - - - - @@ -190,42 +180,3 @@ export const LogRateAnalysisEmbeddableInitializer: FC< ); }; - -export const FormControls: FC<{ - formInput: LogRateAnalysisEmbeddableRuntimeState; - onChange: (update: LogRateAnalysisEmbeddableRuntimeState) => void; - onValidationChange: (isValid: boolean) => void; -}> = ({ formInput, onChange, onValidationChange }) => { - useEffect( - function validateForm() { - onValidationChange(formInput.dataViewId !== undefined); - }, - [formInput, onValidationChange] - ); - - useEffect( - function samplerChange() { - onChange({ - ...formInput, - }); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [onChange] - ); - - useEffect( - function samplerChange() { - onChange({ - ...formInput, - }); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [onChange] - ); - - return ( - <> - - - ); -}; From e5324d827beb477ee54b64c3baba13df3d026cd3 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 26 Sep 2024 15:54:59 +0200 Subject: [PATCH 11/42] cleanup --- .../ml/aiops_log_rate_analysis/embeddable.ts | 17 ------------ .../embeddable_change_point_chart_factory.tsx | 25 ----------------- .../embeddables/change_point_chart/types.ts | 8 ------ .../embeddable_log_rate_analysis_factory.tsx | 27 ------------------- .../embeddables/log_rate_analysis/types.ts | 6 ----- .../embeddable_pattern_analysis_factory.tsx | 27 ------------------- .../embeddables/pattern_analysis/types.ts | 6 ----- .../shared_components/log_rate_analysis.tsx | 11 +------- 8 files changed, 1 insertion(+), 126 deletions(-) delete mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts b/x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts deleted file mode 100644 index 604d6051898e2..0000000000000 --- a/x-pack/packages/ml/aiops_log_rate_analysis/embeddable.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; - -export interface EmbeddableLogRateAnalysisInput { - dataView: DataView; - savedSearch?: SavedSearch | null; - embeddingOrigin?: string; - switchToDocumentView?: () => Promise; - lastReloadRequestTime?: number; -} diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx index 7cf39eb1cf4ae..e6dbf2ef5ce43 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx @@ -11,7 +11,6 @@ import { } from '@kbn/aiops-change-point-detection/constants'; import type { Reference } from '@kbn/content-management-utils'; import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser'; -import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/common'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; @@ -38,32 +37,8 @@ import type { ChangePointEmbeddableState, } from './types'; -export interface EmbeddableChangePointChartStartServices { - data: DataPublicPluginStart; -} - export type EmbeddableChangePointChartType = typeof EMBEDDABLE_CHANGE_POINT_CHART_TYPE; -export const getDependencies = async ( - getStartServices: StartServicesAccessor -) => { - const [ - { http, uiSettings, notifications, ...startServices }, - { lens, data, usageCollection, fieldFormats }, - ] = await getStartServices(); - - return { - http, - uiSettings, - data, - notifications, - lens, - usageCollection, - fieldFormats, - ...startServices, - }; -}; - export const getChangePointChartEmbeddableFactory = ( getStartServices: StartServicesAccessor ) => { diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts b/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts index 4a39020a299c9..f86270bdaf19d 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts @@ -15,14 +15,6 @@ import type { SerializedTimeRange, SerializedTitles, } from '@kbn/presentation-publishing'; -import type { FC } from 'react'; -import type { SelectedChangePoint } from '../../components/change_point_detection/change_point_detection_context'; - -export type ViewComponent = FC<{ - changePoints: SelectedChangePoint[]; - interval: string; - onRenderComplete?: () => void; -}>; export interface ChangePointComponentApi { viewType: PublishingSubject; 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 11dbf3c05eb84..ec45d3faec012 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 @@ -11,7 +11,6 @@ import { } from '@kbn/aiops-log-rate-analysis/constants'; import type { Reference } from '@kbn/content-management-utils'; import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser'; -import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/common'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; @@ -37,32 +36,6 @@ import type { LogRateAnalysisEmbeddableState, } from './types'; -export interface EmbeddableLogRateAnalysisStartServices { - data: DataPublicPluginStart; -} - -export type EmbeddableLogRateAnalysisType = typeof EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE; - -export const getDependencies = async ( - getStartServices: StartServicesAccessor -) => { - const [ - { http, uiSettings, notifications, ...startServices }, - { lens, data, usageCollection, fieldFormats }, - ] = await getStartServices(); - - return { - http, - uiSettings, - data, - notifications, - lens, - usageCollection, - fieldFormats, - ...startServices, - }; -}; - 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 9dd8bb7ee4392..d2255e6dacb87 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 @@ -14,12 +14,6 @@ import type { SerializedTimeRange, SerializedTitles, } from '@kbn/presentation-publishing'; -import type { FC } from 'react'; - -export type ViewComponent = FC<{ - interval: string; - onRenderComplete?: () => void; -}>; export interface LogRateAnalysisComponentApi { dataViewId: PublishingSubject; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx index e7b1d6da3be61..7a4b802bde011 100644 --- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx @@ -11,7 +11,6 @@ import { } from '@kbn/aiops-log-pattern-analysis/constants'; import type { Reference } from '@kbn/content-management-utils'; import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser'; -import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/common'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; @@ -37,32 +36,6 @@ import type { PatternAnalysisEmbeddableState, } from './types'; -export interface EmbeddablePatternAnalysisStartServices { - data: DataPublicPluginStart; -} - -export type EmbeddablePatternAnalysisType = typeof EMBEDDABLE_PATTERN_ANALYSIS_TYPE; - -export const getDependencies = async ( - getStartServices: StartServicesAccessor -) => { - const [ - { http, uiSettings, notifications, ...startServices }, - { lens, data, usageCollection, fieldFormats }, - ] = await getStartServices(); - - return { - http, - uiSettings, - data, - notifications, - lens, - usageCollection, - fieldFormats, - ...startServices, - }; -}; - export const getPatternAnalysisEmbeddableFactory = ( getStartServices: StartServicesAccessor ) => { diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts index f78934b9075f1..710e18823a2bb 100644 --- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts @@ -14,18 +14,12 @@ import type { SerializedTimeRange, SerializedTitles, } from '@kbn/presentation-publishing'; -import type { FC } from 'react'; import type { MinimumTimeRangeOption } from '../../components/log_categorization/log_categorization_for_embeddable/minimum_time_range'; import type { RandomSamplerOption, RandomSamplerProbability, } from '../../components/log_categorization/sampling_menu/random_sampler'; -export type ViewComponent = FC<{ - interval: string; - onRenderComplete?: () => void; -}>; - export interface PatternAnalysisComponentApi { dataViewId: PublishingSubject; fieldName: PublishingSubject; diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx index a64f68db8c4c4..fd2ba5b2fd913 100644 --- a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx @@ -127,8 +127,6 @@ const LogRateAnalysisWrapper: FC = ({ const history = createBrowserHistory(); - const esSearchQuery = { match_all: {} }; - const timeRangeParsed = useMemo(() => { if (timeRange) { const min = datemath.parse(timeRange.from); @@ -161,15 +159,8 @@ const LogRateAnalysisWrapper: FC = ({ > - + From a4960563d451d9a9817c490021189245f291f12f Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 26 Sep 2024 16:08:55 +0200 Subject: [PATCH 12/42] layout tweaks --- .../document_count_content.tsx | 16 ++++++++++++++-- .../log_rate_analysis_content.tsx | 10 +++++++--- .../shared_components/log_rate_analysis.tsx | 4 ---- 3 files changed, 21 insertions(+), 9 deletions(-) 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 23caf21c39ee3..c5d4ef4edff1c 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 @@ -37,17 +37,29 @@ export const DocumentCountContent: FC = ({ barHighlightColorOverride, ...docCountChartProps }) => { - const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext(); + const { data, uiSettings, fieldFormats, charts, embeddingOrigin } = useAiopsAppContext(); const { documentStats } = useAppSelector((s) => s.logRateAnalysis); const { sampleProbability, totalCount, documentCountStats } = documentStats; if (documentCountStats === undefined) { - return totalCount !== undefined ? ( + return totalCount !== undefined && embeddingOrigin !== 'dashboard' ? ( ) : null; } + if (embeddingOrigin === 'dashboard') { + return ( + + ); + } + 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 7bf43037f45c0..b592d200e9262 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 @@ -7,7 +7,7 @@ import { isEqual } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef, type FC } from 'react'; -import { EuiButton, EuiEmptyPrompt, EuiHorizontalRule, EuiPanel } from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiSpacer, EuiPanel } from '@elastic/eui'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { BarStyleAccessor } from '@elastic/charts/dist/chart_types/xy_chart/utils/specs'; @@ -200,7 +200,11 @@ export const LogRateAnalysisContent: FC = ({ const changePointType = documentCountStats?.changePoint?.type; return ( - + {showDocumentCountContent && ( = ({ barStyleAccessor={barStyleAccessor} /> )} - + {showLogRateAnalysisResults && ( = ({ // Component props dataViewId, timeRange, - onLoading, - onError, - onRenderComplete, embeddingOrigin, lastReloadRequestTime, - onChange, }) => { const deps = useMemo(() => { const { http, uiSettings, notifications, ...startServices } = coreStart; From e3b1b64bca18aa11d9aefcd65134fd7e3ff1f401 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 26 Sep 2024 18:42:10 +0200 Subject: [PATCH 13/42] move log rate analysis results options to their own component --- .../ml/aiops_log_rate_analysis/state/index.ts | 4 +- .../state/log_rate_analysis_slice.ts | 6 + .../log_rate_analysis_table_row_slice.ts | 72 ------ .../state/log_rate_analysis_table_slice.ts | 133 +++++++++++ .../aiops_log_rate_analysis/state/store.tsx | 6 +- .../state/use_current_selected_group.ts | 4 +- .../use_current_selected_significant_item.ts | 5 +- .../log_rate_analysis_content.tsx | 2 + .../log_rate_analysis_options.tsx | 199 ++++++++++++++++ .../log_rate_analysis_results.tsx | 212 +++--------------- .../log_rate_analysis_results_table/index.ts | 1 - .../log_rate_analysis_results_table.tsx | 10 +- ...log_rate_analysis_results_table_groups.tsx | 4 +- .../use_columns.tsx | 72 ++---- 14 files changed, 400 insertions(+), 330 deletions(-) delete mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts create mode 100644 x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts index 785bb02c24f31..714738d28e8c9 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts @@ -11,6 +11,7 @@ export { setAnalysisType, setAutoRunAnalysis, setDocumentCountChartData, + setGroupResults, setInitialAnalysisStart, setIsBrushCleared, setStickyHistogram, @@ -23,7 +24,8 @@ export { setPinnedSignificantItem, setSelectedGroup, setSelectedSignificantItem, -} from './log_rate_analysis_table_row_slice'; + setSkippedColumns, +} from './log_rate_analysis_table_slice'; export { LogRateAnalysisReduxProvider } from './store'; export { useAppDispatch, useAppSelector, useAppStore } from './hooks'; export { useCurrentSelectedGroup } from './use_current_selected_group'; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts index 251f0d3263800..8399e896900c6 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts @@ -34,6 +34,7 @@ export interface LogRateAnalysisState { autoRunAnalysis: boolean; initialAnalysisStart: InitialAnalysisStart; isBrushCleared: boolean; + groupResults: boolean; stickyHistogram: boolean; chartWindowParameters?: WindowParameters; earliest?: number; @@ -48,6 +49,7 @@ function getDefaultState(): LogRateAnalysisState { autoRunAnalysis: true, initialAnalysisStart: undefined, isBrushCleared: true, + groupResults: false, documentStats: { sampleProbability: 1, totalCount: 0, @@ -98,6 +100,9 @@ export const logRateAnalysisSlice = createSlice({ state.intervalMs = action.payload.intervalMs; state.documentStats = action.payload.documentStats; }, + setGroupResults: (state: LogRateAnalysisState, action: PayloadAction) => { + state.groupResults = action.payload; + }, setInitialAnalysisStart: ( state: LogRateAnalysisState, action: PayloadAction @@ -127,6 +132,7 @@ export const { setAnalysisType, setAutoRunAnalysis, setDocumentCountChartData, + setGroupResults, setInitialAnalysisStart, setIsBrushCleared, setStickyHistogram, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts deleted file mode 100644 index 3da98e4cc80ff..0000000000000 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; - -import type { SignificantItem } from '@kbn/ml-agg-utils'; - -import type { GroupTableItem } from './types'; - -type SignificantItemOrNull = SignificantItem | null; -type GroupOrNull = GroupTableItem | null; - -export interface LogRateAnalysisTableRowState { - pinnedGroup: GroupOrNull; - pinnedSignificantItem: SignificantItemOrNull; - selectedGroup: GroupOrNull; - selectedSignificantItem: SignificantItemOrNull; -} - -function getDefaultState(): LogRateAnalysisTableRowState { - return { - pinnedGroup: null, - pinnedSignificantItem: null, - selectedGroup: null, - selectedSignificantItem: null, - }; -} - -export const logRateAnalysisTableRowSlice = createSlice({ - name: 'logRateAnalysisTableRow', - initialState: getDefaultState(), - reducers: { - clearAllRowState: (state: LogRateAnalysisTableRowState) => { - state.pinnedGroup = null; - state.pinnedSignificantItem = null; - state.selectedGroup = null; - state.selectedSignificantItem = null; - }, - setPinnedGroup: (state: LogRateAnalysisTableRowState, action: PayloadAction) => { - state.pinnedGroup = action.payload; - }, - setPinnedSignificantItem: ( - state: LogRateAnalysisTableRowState, - action: PayloadAction - ) => { - state.pinnedSignificantItem = action.payload; - }, - setSelectedGroup: (state: LogRateAnalysisTableRowState, action: PayloadAction) => { - state.selectedGroup = action.payload; - }, - setSelectedSignificantItem: ( - state: LogRateAnalysisTableRowState, - action: PayloadAction - ) => { - state.selectedSignificantItem = action.payload; - }, - }, -}); - -// Action creators are generated for each case reducer function -export const { - clearAllRowState, - setPinnedGroup, - setPinnedSignificantItem, - setSelectedGroup, - setSelectedSignificantItem, -} = logRateAnalysisTableRowSlice.actions; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts new file mode 100644 index 0000000000000..efccf2da19a30 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts @@ -0,0 +1,133 @@ +/* + * 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 { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; + +import { i18n } from '@kbn/i18n'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; + +import type { GroupTableItem } from './types'; + +export const commonColumns = { + ['Log rate']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.logRateColumnTitle', { + defaultMessage: 'Log rate', + }), + ['Doc count']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.docCountColumnTitle', { + defaultMessage: 'Doc count', + }), + ['p-value']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.pValueColumnTitle', { + defaultMessage: 'p-value', + }), + ['Impact']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.impactColumnTitle', { + defaultMessage: 'Impact', + }), + ['Baseline rate']: i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.baselineRateColumnTitle', + { + defaultMessage: 'Baseline rate', + } + ), + ['Deviation rate']: i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.deviationRateColumnTitle', + { + defaultMessage: 'Deviation rate', + } + ), + ['Log rate change']: i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.logRateChangeColumnTitle', + { + defaultMessage: 'Log rate change', + } + ), + ['Actions']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.actionsColumnTitle', { + defaultMessage: 'Actions', + }), +}; + +export const significantItemColumns = { + ['Field name']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.fieldNameColumnTitle', { + defaultMessage: 'Field name', + }), + ['Field value']: i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.fieldValueColumnTitle', + { + defaultMessage: 'Field value', + } + ), + ...commonColumns, +} as const; + +export type LogRateAnalysisResultsTableColumnNames = keyof typeof significantItemColumns | 'unique'; + +type SignificantItemOrNull = SignificantItem | null; +type GroupOrNull = GroupTableItem | null; + +export interface LogRateAnalysisTableState { + skippedColumns: LogRateAnalysisResultsTableColumnNames[]; + pinnedGroup: GroupOrNull; + pinnedSignificantItem: SignificantItemOrNull; + selectedGroup: GroupOrNull; + selectedSignificantItem: SignificantItemOrNull; +} + +function getDefaultState(): LogRateAnalysisTableState { + return { + skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'], + pinnedGroup: null, + pinnedSignificantItem: null, + selectedGroup: null, + selectedSignificantItem: null, + }; +} + +export const logRateAnalysisTableSlice = createSlice({ + name: 'logRateAnalysisTable', + initialState: getDefaultState(), + reducers: { + clearAllRowState: (state: LogRateAnalysisTableState) => { + state.pinnedGroup = null; + state.pinnedSignificantItem = null; + state.selectedGroup = null; + state.selectedSignificantItem = null; + }, + setPinnedGroup: (state: LogRateAnalysisTableState, action: PayloadAction) => { + state.pinnedGroup = action.payload; + }, + setPinnedSignificantItem: ( + state: LogRateAnalysisTableState, + action: PayloadAction + ) => { + state.pinnedSignificantItem = action.payload; + }, + setSelectedGroup: (state: LogRateAnalysisTableState, action: PayloadAction) => { + state.selectedGroup = action.payload; + }, + setSelectedSignificantItem: ( + state: LogRateAnalysisTableState, + action: PayloadAction + ) => { + state.selectedSignificantItem = action.payload; + }, + setSkippedColumns: ( + state: LogRateAnalysisTableState, + action: PayloadAction + ) => { + state.skippedColumns = action.payload; + }, + }, +}); + +// Action creators are generated for each case reducer function +export const { + clearAllRowState, + setPinnedGroup, + setPinnedSignificantItem, + setSelectedGroup, + setSelectedSignificantItem, + setSkippedColumns, +} = logRateAnalysisTableSlice.actions; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx index 1589b27348d89..097e3c3f10690 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx @@ -15,7 +15,7 @@ import { streamSlice } from '@kbn/ml-response-stream/client'; import { logRateAnalysisResultsSlice } from '../api/stream_reducer'; import { logRateAnalysisSlice } from './log_rate_analysis_slice'; -import { logRateAnalysisTableRowSlice } from './log_rate_analysis_table_row_slice'; +import { logRateAnalysisTableSlice } from './log_rate_analysis_table_slice'; import { logRateAnalysisFieldCandidatesSlice } from './log_rate_analysis_field_candidates_slice'; import type { InitialAnalysisStart } from './log_rate_analysis_slice'; @@ -30,8 +30,8 @@ const getReduxStore = () => logRateAnalysisResults: logRateAnalysisResultsSlice.reducer, // Handles running the analysis logRateAnalysisStream: streamSlice.reducer, - // Handles hovering and pinning table rows - logRateAnalysisTableRow: logRateAnalysisTableRowSlice.reducer, + // Handles hovering and pinning table rows and column selection + logRateAnalysisTable: logRateAnalysisTableSlice.reducer, }, }); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts index 9653691d3efd4..a19bd3e18a735 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts @@ -10,8 +10,8 @@ import { createSelector } from '@reduxjs/toolkit'; import type { RootState } from './store'; import { useAppSelector } from './hooks'; -const selectSelectedGroup = (s: RootState) => s.logRateAnalysisTableRow.selectedGroup; -const selectPinnedGroup = (s: RootState) => s.logRateAnalysisTableRow.pinnedGroup; +const selectSelectedGroup = (s: RootState) => s.logRateAnalysisTable.selectedGroup; +const selectPinnedGroup = (s: RootState) => s.logRateAnalysisTable.pinnedGroup; const selectCurrentSelectedGroup = createSelector( selectSelectedGroup, selectPinnedGroup, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts index d189d16fc2fa0..f7327d3033df0 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts @@ -11,9 +11,8 @@ import type { RootState } from './store'; import { useAppSelector } from './hooks'; const selectSelectedSignificantItem = (s: RootState) => - s.logRateAnalysisTableRow.selectedSignificantItem; -const selectPinnedSignificantItem = (s: RootState) => - s.logRateAnalysisTableRow.pinnedSignificantItem; + s.logRateAnalysisTable.selectedSignificantItem; +const selectPinnedSignificantItem = (s: RootState) => s.logRateAnalysisTable.pinnedSignificantItem; const selectCurrentSelectedSignificantItem = createSelector( selectSelectedSignificantItem, selectPinnedSignificantItem, 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 b592d200e9262..50d2459375f9f 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 @@ -28,6 +28,7 @@ import { setInitialAnalysisStart, useAppDispatch, useAppSelector, + setGroupResults, } from '@kbn/aiops-log-rate-analysis/state'; import { DocumentCountContent } from '../../document_count_content/document_count_content'; @@ -116,6 +117,7 @@ export const LogRateAnalysisContent: FC = ({ const { documentCountStats } = documentStats; function clearSelectionHandler() { + dispatch(setGroupResults(false)); dispatch(clearSelection()); dispatch(clearAllRowState()); } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx new file mode 100644 index 0000000000000..8028a8b7bab4d --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx @@ -0,0 +1,199 @@ +/* + * 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 { FC } from 'react'; +import React, { useEffect, useState } from 'react'; + +import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { + clearAllRowState, + setGroupResults, + useAppDispatch, + useAppSelector, +} from '@kbn/aiops-log-rate-analysis/state'; +import { + commonColumns, + significantItemColumns, + setSkippedColumns, + type LogRateAnalysisResultsTableColumnNames, +} from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice'; + +import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover'; +import { ItemFilterPopover as ColumnFilterPopover } from './item_filter_popover'; + +const groupResultsMessage = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResults', + { + defaultMessage: 'Smart grouping', + } +); +const fieldFilterHelpText = i18n.translate('xpack.aiops.logRateAnalysis.page.fieldFilterHelpText', { + defaultMessage: + 'Deselect non-relevant fields to remove them from the analysis and click the Apply button to rerun the analysis. Use the search bar to filter the list, then select/deselect multiple fields with the actions below.', +}); +const columnsFilterHelpText = i18n.translate( + 'xpack.aiops.logRateAnalysis.page.columnsFilterHelpText', + { + defaultMessage: 'Configure visible columns.', + } +); +const disabledFieldFilterApplyButtonTooltipContent = i18n.translate( + 'xpack.aiops.analysis.fieldSelectorNotEnoughFieldsSelected', + { + defaultMessage: 'Grouping requires at least 2 fields to be selected.', + } +); +const disabledColumnFilterApplyButtonTooltipContent = i18n.translate( + 'xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected', + { + defaultMessage: 'At least one column must be selected.', + } +); +const columnSearchAriaLabel = i18n.translate('xpack.aiops.analysis.columnSelectorAriaLabel', { + defaultMessage: 'Filter columns', +}); +const columnsButton = i18n.translate('xpack.aiops.logRateAnalysis.page.columnsFilterButtonLabel', { + defaultMessage: 'Columns', +}); +const fieldsButton = i18n.translate('xpack.aiops.analysis.fieldsButtonLabel', { + defaultMessage: 'Fields', +}); +const groupResultsOffMessage = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOff', + { + defaultMessage: 'Off', + } +); +const groupResultsOnMessage = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOn', + { + defaultMessage: 'On', + } +); +const resultsGroupedOffId = 'aiopsLogRateAnalysisGroupingOff'; +const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn'; + +export interface LogRateAnalysisOptionsProps { + foundGroups: boolean; + onFieldsFilterChange: (skippedFieldsUpdate: string[]) => void; +} + +export const LogRateAnalysisOptions: FC = ({ + foundGroups, + onFieldsFilterChange, +}) => { + const dispatch = useAppDispatch(); + + const { groupResults } = useAppSelector((s) => s.logRateAnalysis); + const { isRunning } = useAppSelector((s) => s.logRateAnalysisStream); + const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates); + const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable); + const { fieldFilterUniqueItems, fieldFilterSkippedItems } = fieldCandidates; + const fieldFilterButtonDisabled = + isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0; + const toggleIdSelected = groupResults ? resultsGroupedOnId : resultsGroupedOffId; + + // null is used as the uninitialized state to identify the first load. + const [skippedFields, setSkippedFields] = useState(null); + + // Set skipped fields only on first load, otherwise we'd overwrite the user's selection. + useEffect(() => { + if (skippedFields === null && fieldFilterSkippedItems.length > 0) + setSkippedFields(fieldFilterSkippedItems); + }, [fieldFilterSkippedItems, skippedFields]); + + const onGroupResultsToggle = (optionId: string) => { + dispatch(setGroupResults(optionId === resultsGroupedOnId)); + // When toggling the group switch, clear all row selections + dispatch(clearAllRowState()); + }; + + const onVisibleColumnsChange = (columns: LogRateAnalysisResultsTableColumnNames[]) => { + dispatch(setSkippedColumns(columns)); + }; + + const onFieldsFilterChangeHandler = (skippedFieldsUpdate: string[]) => { + setSkippedFields(skippedFieldsUpdate); + onFieldsFilterChange(skippedFieldsUpdate); + }; + + // Disable the grouping switch toggle only if no groups were found, + // the toggle wasn't enabled already and no fields were selected to be skipped. + const disabledGroupResultsSwitch = !foundGroups && !groupResults; + + const toggleButtons = [ + { + id: resultsGroupedOffId, + label: groupResultsOffMessage, + 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOff', + }, + { + id: resultsGroupedOnId, + label: groupResultsOnMessage, + 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOn', + }, + ]; + + return ( + <> + + + + {groupResultsMessage} + + + + + + + + + + + void} + /> + + + ); +}; 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 b97019c4b4d29..acaeaf245895d 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 @@ -10,22 +10,14 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { isEqual } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { - EuiButton, - EuiButtonGroup, - EuiCallOut, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiSpacer, EuiText } from '@elastic/eui'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { ProgressControls } from '@kbn/aiops-components'; import { cancelStream, startStream } from '@kbn/ml-response-stream/client'; import { clearAllRowState, + setGroupResults, useAppDispatch, useAppSelector, } from '@kbn/aiops-log-rate-analysis/state'; @@ -37,7 +29,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; -import { useStorage } from '@kbn/ml-local-storage'; +// import { useStorage } from '@kbn/ml-local-storage'; import { AIOPS_ANALYSIS_RUN_ORIGIN } from '@kbn/aiops-common/constants'; import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-log-rate-analysis/api/schema'; import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-log-rate-analysis/api/schema_v3'; @@ -50,15 +42,11 @@ import { fetchFieldCandidates } from '@kbn/aiops-log-rate-analysis/state/log_rat import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { useDataSource } from '../../hooks/use_data_source'; -import { - commonColumns, - significantItemColumns, -} from '../log_rate_analysis_results_table/use_columns'; -import { - AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, - type AiOpsKey, - type AiOpsStorageMapped, -} from '../../types/storage'; +// import { +// AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, +// type AiOpsKey, +// type AiOpsStorageMapped, +// } from '../../types/storage'; import { getGroupTableItems, @@ -66,68 +54,15 @@ import { LogRateAnalysisResultsGroupsTable, } from '../log_rate_analysis_results_table'; -import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover'; -import { ItemFilterPopover as ColumnFilterPopover } from './item_filter_popover'; import { LogRateAnalysisInfoPopover } from './log_rate_analysis_info_popover'; -import type { ColumnNames } from '../log_rate_analysis_results_table'; +import { LogRateAnalysisOptions } from './log_rate_analysis_options'; -const groupResultsMessage = i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResults', - { - defaultMessage: 'Smart grouping', - } -); const groupResultsHelpMessage = i18n.translate( 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsHelpMessage', { defaultMessage: 'Items which are unique to a group are marked by an asterisk (*).', } ); -const groupResultsOffMessage = i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOff', - { - defaultMessage: 'Off', - } -); -const groupResultsOnMessage = i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOn', - { - defaultMessage: 'On', - } -); -const resultsGroupedOffId = 'aiopsLogRateAnalysisGroupingOff'; -const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn'; -const fieldFilterHelpText = i18n.translate('xpack.aiops.logRateAnalysis.page.fieldFilterHelpText', { - defaultMessage: - 'Deselect non-relevant fields to remove them from the analysis and click the Apply button to rerun the analysis. Use the search bar to filter the list, then select/deselect multiple fields with the actions below.', -}); -const columnsFilterHelpText = i18n.translate( - 'xpack.aiops.logRateAnalysis.page.columnsFilterHelpText', - { - defaultMessage: 'Configure visible columns.', - } -); -const disabledFieldFilterApplyButtonTooltipContent = i18n.translate( - 'xpack.aiops.analysis.fieldSelectorNotEnoughFieldsSelected', - { - defaultMessage: 'Grouping requires at least 2 fields to be selected.', - } -); -const disabledColumnFilterApplyButtonTooltipContent = i18n.translate( - 'xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected', - { - defaultMessage: 'At least one column must be selected.', - } -); -const columnSearchAriaLabel = i18n.translate('xpack.aiops.analysis.columnSelectorAriaLabel', { - defaultMessage: 'Filter columns', -}); -const columnsButton = i18n.translate('xpack.aiops.logRateAnalysis.page.columnsFilterButtonLabel', { - defaultMessage: 'Columns', -}); -const fieldsButton = i18n.translate('xpack.aiops.analysis.fieldsButtonLabel', { - defaultMessage: 'Fields', -}); /** * Interface for log rate analysis results data. @@ -173,10 +108,12 @@ export const LogRateAnalysisResults: FC = ({ documentStats: { sampleProbability }, stickyHistogram, isBrushCleared, + groupResults, } = useAppSelector((s) => s.logRateAnalysis); const { isRunning, errors: streamErrors } = useAppSelector((s) => s.logRateAnalysisStream); const data = useAppSelector((s) => s.logRateAnalysisResults); const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates); + const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable); const { currentAnalysisWindowParameters } = data; // Store the performance metric's start time using a ref @@ -184,45 +121,18 @@ export const LogRateAnalysisResults: FC = ({ const analysisStartTime = useRef(window.performance.now()); const abortCtrl = useRef(new AbortController()); - const [groupResults, setGroupResults] = useState(false); const [overrides, setOverrides] = useState( undefined ); const [shouldStart, setShouldStart] = useState(false); - const [toggleIdSelected, setToggleIdSelected] = useState(resultsGroupedOffId); - const [skippedColumns, setSkippedColumns] = useStorage< - AiOpsKey, - AiOpsStorageMapped - >(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ['p-value', 'Baseline rate', 'Deviation rate']); - // null is used as the uninitialized state to identify the first load. - const [skippedFields, setSkippedFields] = useState(null); - - const onGroupResultsToggle = (optionId: string) => { - setToggleIdSelected(optionId); - setGroupResults(optionId === resultsGroupedOnId); - - // When toggling the group switch, clear all row selections - dispatch(clearAllRowState()); - }; - - const { - fieldFilterUniqueItems, - fieldFilterSkippedItems, - keywordFieldCandidates, - textFieldCandidates, - } = fieldCandidates; - const fieldFilterButtonDisabled = - isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0; - - // Set skipped fields only on first load, otherwise we'd overwrite the user's selection. - useEffect(() => { - if (skippedFields === null && fieldFilterSkippedItems.length > 0) - setSkippedFields(fieldFilterSkippedItems); - }, [fieldFilterSkippedItems, skippedFields]); + // TOOD: Fix to store in local storage using redux toolkit + // const [skippedColumns, setSkippedColumns] = useStorage< + // AiOpsKey, + // AiOpsStorageMapped + // >(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ['p-value', 'Baseline rate', 'Deviation rate']); const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => { dispatch(resetResults()); - setSkippedFields(skippedFieldsUpdate); setOverrides({ loaded: 0, remainingKeywordFieldCandidates: keywordFieldCandidates.filter( @@ -235,10 +145,7 @@ export const LogRateAnalysisResults: FC = ({ }); startHandler(true, false); }; - - const onVisibleColumnsChange = (columns: ColumnNames[]) => { - setSkippedColumns(columns); - }; + const { fieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates } = fieldCandidates; function cancelHandler() { abortCtrl.current.abort(); @@ -298,18 +205,18 @@ export const LogRateAnalysisResults: FC = ({ dispatch(resetResults()); setOverrides({ remainingKeywordFieldCandidates: keywordFieldCandidates.filter( - (d) => skippedFields === null || !skippedFields.includes(d) + (d) => fieldFilterSkippedItems !== null && fieldFilterSkippedItems.includes(d) ), remainingTextFieldCandidates: textFieldCandidates.filter( - (d) => skippedFields === null || !skippedFields.includes(d) + (d) => fieldFilterSkippedItems !== null && fieldFilterSkippedItems.includes(d) ), }); } // Reset grouping to false and clear all row selections when restarting the analysis. if (resetGroupButton) { - setGroupResults(false); - setToggleIdSelected(resultsGroupedOffId); + dispatch(setGroupResults(false)); + // When toggling the group switch, clear all row selections dispatch(clearAllRowState()); } @@ -399,23 +306,6 @@ export const LogRateAnalysisResults: FC = ({ }, 0); const foundGroups = groupTableItems.length > 0 && groupItemCount > 0; - // Disable the grouping switch toggle only if no groups were found, - // the toggle wasn't enabled already and no fields were selected to be skipped. - const disabledGroupResultsSwitch = !foundGroups && !groupResults; - - const toggleButtons = [ - { - id: resultsGroupedOffId, - label: groupResultsOffMessage, - 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOff', - }, - { - id: resultsGroupedOnId, - label: groupResultsOnMessage, - 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOn', - }, - ]; - return (
= ({ shouldRerunAnalysis={shouldRerunAnalysis} analysisInfo={} > - - - - {groupResultsMessage} - - - - - - - - - - - void} - /> - + <> + {embeddingOrigin !== 'dashboard' && ( + + )} + {errors.length > 0 ? ( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts index 6813e71704918..c5112723e2784 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts @@ -8,4 +8,3 @@ export { getGroupTableItems } from './get_group_table_items'; export { LogRateAnalysisResultsTable } from './log_rate_analysis_results_table'; export { LogRateAnalysisResultsGroupsTable } from './log_rate_analysis_results_table_groups'; -export type { ColumnNames } from './use_columns'; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx index 83be306e93f50..e9072c2929f14 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx @@ -83,13 +83,11 @@ export const LogRateAnalysisResultsTable: FC = }, [allSignificantItems, groupFilter]); const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback); - const pinnedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.pinnedGroup); - const selectedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.selectedGroup); - const pinnedSignificantItem = useAppSelector( - (s) => s.logRateAnalysisTableRow.pinnedSignificantItem - ); + const pinnedGroup = useAppSelector((s) => s.logRateAnalysisTable.pinnedGroup); + const selectedGroup = useAppSelector((s) => s.logRateAnalysisTable.selectedGroup); + const pinnedSignificantItem = useAppSelector((s) => s.logRateAnalysisTable.pinnedSignificantItem); const selectedSignificantItem = useAppSelector( - (s) => s.logRateAnalysisTableRow.selectedSignificantItem + (s) => s.logRateAnalysisTable.selectedSignificantItem ); const dispatch = useAppDispatch(); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx index d69a0fec7200f..6bd0a5e4ce213 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx @@ -91,8 +91,8 @@ export const LogRateAnalysisResultsGroupsTable: FC s.logRateAnalysisTableRow.pinnedGroup); - const selectedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.selectedGroup); + const pinnedGroup = useAppSelector((s) => s.logRateAnalysisTable.pinnedGroup); + const selectedGroup = useAppSelector((s) => s.logRateAnalysisTable.selectedGroup); const dispatch = useAppDispatch(); const isMounted = useMountedState(); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index f3b8195767101..3d49af25ca182 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -21,6 +21,11 @@ import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query'; import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats'; import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state'; +import { + commonColumns, + significantItemColumns, + type LogRateAnalysisResultsTableColumnNames, +} from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice'; import { getBaselineAndDeviationRates, getLogRateChange, @@ -40,55 +45,6 @@ const TRUNCATE_TEXT_LINES = 3; const UNIQUE_COLUMN_WIDTH = '40px'; const NOT_AVAILABLE = '--'; -export const commonColumns = { - ['Log rate']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.logRateColumnTitle', { - defaultMessage: 'Log rate', - }), - ['Doc count']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.docCountColumnTitle', { - defaultMessage: 'Doc count', - }), - ['p-value']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.pValueColumnTitle', { - defaultMessage: 'p-value', - }), - ['Impact']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.impactColumnTitle', { - defaultMessage: 'Impact', - }), - ['Baseline rate']: i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.baselineRateColumnTitle', - { - defaultMessage: 'Baseline rate', - } - ), - ['Deviation rate']: i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.deviationRateColumnTitle', - { - defaultMessage: 'Deviation rate', - } - ), - ['Log rate change']: i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.logRateChangeColumnTitle', - { - defaultMessage: 'Log rate change', - } - ), - ['Actions']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.actionsColumnTitle', { - defaultMessage: 'Actions', - }), -}; - -export const significantItemColumns = { - ['Field name']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.fieldNameColumnTitle', { - defaultMessage: 'Field name', - }), - ['Field value']: i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.fieldValueColumnTitle', - { - defaultMessage: 'Field value', - } - ), - ...commonColumns, -} as const; - export const LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE = { GROUPS: 'groups', SIGNIFICANT_ITEMS: 'significantItems', @@ -96,8 +52,6 @@ export const LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE = { export type LogRateAnalysisResultsTableType = (typeof LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE)[keyof typeof LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE]; -export type ColumnNames = keyof typeof significantItemColumns | 'unique'; - const logRateHelpMessage = i18n.translate( 'xpack.aiops.logRateAnalysis.resultsTable.logRateColumnTooltip', { @@ -271,7 +225,10 @@ export const useColumns = ( [currentAnalysisType, buckets] ); - const columnsMap: Record> = useMemo( + const columnsMap: Record< + LogRateAnalysisResultsTableColumnNames, + EuiBasicTableColumn + > = useMemo( () => ({ ['Field name']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnFieldName', @@ -615,20 +572,21 @@ export const useColumns = ( ); const columns = useMemo(() => { - const columnNamesToReturn: Partial> = isGroupsTable - ? commonColumns - : significantItemColumns; + const columnNamesToReturn: Partial> = + isGroupsTable ? commonColumns : significantItemColumns; const columnsToReturn = []; for (const columnName in columnNamesToReturn) { if ( Object.hasOwn(columnNamesToReturn, columnName) === false || - skippedColumns.includes(columnNamesToReturn[columnName as ColumnNames] as string) || + skippedColumns.includes( + columnNamesToReturn[columnName as LogRateAnalysisResultsTableColumnNames] as string + ) || ((columnName === 'p-value' || columnName === 'Impact') && zeroDocsFallback) ) continue; - columnsToReturn.push(columnsMap[columnName as ColumnNames]); + columnsToReturn.push(columnsMap[columnName as LogRateAnalysisResultsTableColumnNames]); } if (isExpandedRow === true) { From 508fe395fada5de170d6f667ad951a00cc432dca Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 27 Sep 2024 09:12:12 +0200 Subject: [PATCH 14/42] options toggle for embeddable --- .../log_rate_analysis_options.tsx | 4 +- .../log_rate_analysis_results.tsx | 38 ++++++++++++++++++- .../log_rate_analysis_action_context.ts | 4 +- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx index 8028a8b7bab4d..46060eec4bc6e 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx @@ -81,11 +81,13 @@ const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn'; export interface LogRateAnalysisOptionsProps { foundGroups: boolean; + growFirstItem?: boolean; onFieldsFilterChange: (skippedFieldsUpdate: string[]) => void; } export const LogRateAnalysisOptions: FC = ({ foundGroups, + growFirstItem = false, onFieldsFilterChange, }) => { const dispatch = useAppDispatch(); @@ -142,7 +144,7 @@ export const LogRateAnalysisOptions: FC = ({ return ( <> - + {groupResultsMessage} 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 acaeaf245895d..ea8a056fc363b 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 @@ -10,7 +10,16 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { isEqual } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiSpacer, EuiText } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiButton, + EuiCallOut, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { ProgressControls } from '@kbn/aiops-components'; @@ -130,6 +139,11 @@ export const LogRateAnalysisResults: FC = ({ // AiOpsKey, // AiOpsStorageMapped // >(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ['p-value', 'Baseline rate', 'Deviation rate']); + const [embeddableOptionsVisible, setEmbeddableOptionsVisible] = useState(false); + + const onEmbeddableOptionsClickHandler = () => { + setEmbeddableOptionsVisible((s) => !s); + }; const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => { dispatch(resetResults()); @@ -326,9 +340,31 @@ export const LogRateAnalysisResults: FC = ({ onFieldsFilterChange={onFieldsFilterChange} /> )} + {embeddingOrigin === 'dashboard' && ( + + + + )} + {embeddingOrigin === 'dashboard' && embeddableOptionsVisible && ( + <> + + + + + + )} + {errors.length > 0 ? ( <> diff --git a/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts b/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts index cf1fbe83a9dc4..d0c1ef715b84c 100644 --- a/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts +++ b/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts @@ -8,10 +8,10 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { apiIsOfType, type EmbeddableApiContext } from '@kbn/presentation-publishing'; import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants'; -import type { ChangePointEmbeddableApi } from '../embeddables/change_point_chart/types'; +import type { LogRateAnalysisEmbeddableApi } from '../embeddables/log_rate_analysis/types'; export interface LogRateAnalysisActionContext extends EmbeddableApiContext { - embeddable: ChangePointEmbeddableApi; + embeddable: LogRateAnalysisEmbeddableApi; } export function isLogRateAnalysisEmbeddableContext( From ec74af2c728bd5800ebfcfbb18136dd30ea74eba Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 27 Sep 2024 09:18:41 +0200 Subject: [PATCH 15/42] linting --- x-pack/plugins/aiops/tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index 188f8b275fbac..af3e627acfb0d 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -79,6 +79,8 @@ "@kbn/observability-ai-assistant-plugin", "@kbn/ui-theme", "@kbn/apm-utils", + "@kbn/ml-field-stats-flyout", + "@kbn/shared-ux-router", ], "exclude": [ "target/**/*", From 494827fea2a83f7268406ab51fb60b69bfa44261 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 30 Sep 2024 09:59:39 +0200 Subject: [PATCH 16/42] restructure add panel menu with logs/aiops section --- x-pack/packages/ml/aiops_common/constants.ts | 13 +++++++++++++ .../shared_components/log_rate_analysis.tsx | 6 +++--- .../ui_actions/create_change_point_chart.tsx | 14 ++++---------- .../create_log_rate_analysis_actions.tsx | 18 ++++++------------ .../create_pattern_analysis_action.tsx | 16 +++++----------- 5 files changed, 31 insertions(+), 36 deletions(-) diff --git a/x-pack/packages/ml/aiops_common/constants.ts b/x-pack/packages/ml/aiops_common/constants.ts index 39a0fdc5842c8..1a75e929c147a 100644 --- a/x-pack/packages/ml/aiops_common/constants.ts +++ b/x-pack/packages/ml/aiops_common/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; + /** * AIOPS_PLUGIN_ID is used as a unique identifier for the aiops plugin */ @@ -28,3 +30,14 @@ export const AIOPS_EMBEDDABLE_ORIGIN = { DISCOVER: 'discover', ML_AIOPS_LABS: 'ml_aiops_labs', } as const; + +export const AIOPS_EMBEDDABLE_GROUPING = [ + { + id: 'logs-aiops', + getDisplayName: () => + i18n.translate('xpack.aiops.embedabble.groupingDisplayName', { + defaultMessage: 'Logs AIOps', + }), + getIconType: () => 'machineLearningApp', + }, +]; diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx index f88aa9c682768..2011a1d3ea3ca 100644 --- a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx @@ -15,7 +15,7 @@ import datemath from '@elastic/datemath'; import { UrlStateProvider } from '@kbn/ml-url-state'; import { Router } from '@kbn/shared-ux-router'; -import { EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; +import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { UI_SETTINGS } from '@kbn/data-service'; import { LogRateAnalysisReduxProvider } from '@kbn/aiops-log-rate-analysis/state'; @@ -97,7 +97,7 @@ const LogRateAnalysisWrapper: FC = ({ const aiopsAppContextValue = useMemo(() => { return { - embeddingOrigin: embeddingOrigin ?? EMBEDDABLE_ORIGIN, + embeddingOrigin: embeddingOrigin ?? AIOPS_EMBEDDABLE_ORIGIN, ...deps, } as unknown as AiopsAppDependencies; }, [deps, embeddingOrigin]); @@ -157,7 +157,7 @@ const LogRateAnalysisWrapper: FC = ({ diff --git a/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx b/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx index f9078f575818a..70e0ae73a3d1f 100644 --- a/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx +++ b/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx @@ -12,7 +12,10 @@ import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { AIOPS_EMBEDDABLE_GROUPING } from '@kbn/aiops-common/constants'; + import type { AiopsPluginStartDeps } from '../types'; + import type { ChangePointChartActionContext } from './change_point_action_context'; const parentApiIsCompatible = async ( @@ -29,16 +32,7 @@ export function createAddChangePointChartAction( ): UiActionsActionDefinition { return { id: 'create-change-point-chart', - grouping: [ - { - id: 'ml', - getDisplayName: () => - i18n.translate('xpack.aiops.navMenu.mlAppNameText', { - defaultMessage: 'Machine Learning and Analytics', - }), - getIconType: () => 'machineLearningApp', - }, - ], + grouping: AIOPS_EMBEDDABLE_GROUPING, order: 10, getIconType: () => 'changePointDetection', getDisplayName: () => diff --git a/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx index a73b3eb6332f2..588903e96aa16 100644 --- a/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx +++ b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx @@ -12,12 +12,15 @@ import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants'; -import type { AiopsPluginStartDeps } from '../types'; -import type { LogRateAnalysisActionContext } from './log_rate_analysis_action_context'; +import { AIOPS_EMBEDDABLE_GROUPING } from '@kbn/aiops-common/constants'; + import type { LogRateAnalysisEmbeddableApi, LogRateAnalysisEmbeddableInitialState, } from '../embeddables/log_rate_analysis/types'; +import type { AiopsPluginStartDeps } from '../types'; + +import type { LogRateAnalysisActionContext } from './log_rate_analysis_action_context'; const parentApiIsCompatible = async ( parentApi: unknown @@ -33,16 +36,7 @@ export function createAddLogRateAnalysisEmbeddableAction( ): UiActionsActionDefinition { return { id: 'create-log-rate-analysis-embeddable', - grouping: [ - { - id: 'ml', - getDisplayName: () => - i18n.translate('xpack.aiops.navMenu.mlAppNameText', { - defaultMessage: 'Machine Learning and Analytics', - }), - getIconType: () => 'logRateAnalysis', - }, - ], + grouping: AIOPS_EMBEDDABLE_GROUPING, getIconType: () => 'logRateAnalysis', getDisplayName: () => i18n.translate('xpack.aiops.embeddableLogRateAnalysisDisplayName', { diff --git a/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx index 81127559e4e3d..f840e896abac4 100644 --- a/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx +++ b/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx @@ -12,13 +12,16 @@ import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants'; +import { AIOPS_EMBEDDABLE_GROUPING } from '@kbn/aiops-common/constants'; + import type { AiopsPluginStartDeps } from '../types'; -import type { PatternAnalysisActionContext } from './pattern_analysis_action_context'; import type { PatternAnalysisEmbeddableApi, PatternAnalysisEmbeddableInitialState, } from '../embeddables/pattern_analysis/types'; +import type { PatternAnalysisActionContext } from './pattern_analysis_action_context'; + const parentApiIsCompatible = async ( parentApi: unknown ): Promise => { @@ -33,16 +36,7 @@ export function createAddPatternAnalysisEmbeddableAction( ): UiActionsActionDefinition { return { id: 'create-pattern-analysis-embeddable', - grouping: [ - { - id: 'ml', - getDisplayName: () => - i18n.translate('xpack.aiops.navMenu.mlAppNameText', { - defaultMessage: 'Machine Learning and Analytics', - }), - getIconType: () => 'logPatternAnalysis', - }, - ], + grouping: AIOPS_EMBEDDABLE_GROUPING, getIconType: () => 'logPatternAnalysis', getDisplayName: () => i18n.translate('xpack.aiops.embeddablePatternAnalysisDisplayName', { From 7c70ccfdf558cd4e79cd2151bfd0cb1a077f2219 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 30 Sep 2024 15:14:21 +0200 Subject: [PATCH 17/42] tweak embedding constants --- x-pack/packages/ml/aiops_common/tsconfig.json | 1 + .../document_count_content/document_count_content.tsx | 5 +++-- .../log_rate_analysis_content.tsx | 3 ++- .../log_rate_analysis/log_rate_analysis_results.tsx | 8 ++++---- .../embeddable_log_rate_analysis_factory.tsx | 8 ++++---- .../aiops/public/shared_components/log_rate_analysis.tsx | 4 ++-- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/x-pack/packages/ml/aiops_common/tsconfig.json b/x-pack/packages/ml/aiops_common/tsconfig.json index 806b5b07e847e..ffd8c074a421d 100644 --- a/x-pack/packages/ml/aiops_common/tsconfig.json +++ b/x-pack/packages/ml/aiops_common/tsconfig.json @@ -15,6 +15,7 @@ ], "kbn_references": [ "@kbn/ml-is-populated-object", + "@kbn/i18n", ], "exclude": [ "target/**/*", 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 c5d4ef4edff1c..4dbf021e3b10b 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 @@ -15,6 +15,7 @@ import type { import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state'; import { DocumentCountChartRedux } from '@kbn/aiops-components'; +import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; @@ -43,12 +44,12 @@ export const DocumentCountContent: FC = ({ const { sampleProbability, totalCount, documentCountStats } = documentStats; if (documentCountStats === undefined) { - return totalCount !== undefined && embeddingOrigin !== 'dashboard' ? ( + return totalCount !== undefined && embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD ? ( ) : null; } - if (embeddingOrigin === 'dashboard') { + if (embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD) { return ( = ({ {showDocumentCountContent && ( = ({ analysisInfo={} > <> - {embeddingOrigin !== 'dashboard' && ( + {embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( )} - {embeddingOrigin === 'dashboard' && ( + {embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( = ({ - {embeddingOrigin === 'dashboard' && embeddableOptionsVisible && ( + {embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && embeddableOptionsVisible && ( <> 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 ec45d3faec012..866129a83845a 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 @@ -22,6 +22,7 @@ import { initializeTitles, useBatchedPublishingSubjects, } from '@kbn/presentation-publishing'; + import fastIsEqual from 'fast-deep-equal'; import { cloneDeep } from 'lodash'; import React, { useMemo } from 'react'; @@ -197,10 +198,9 @@ export const getLogRateAnalysisEmbeddableFactory = ( const lastReloadRequestTime = useObservable(reload$, Date.now()); const timeRange = useObservable(timeRange$, undefined); - let embeddingOrigin; - if (apiHasExecutionContext(parentApi)) { - embeddingOrigin = parentApi.executionContext.type; - } + const embeddingOrigin = apiHasExecutionContext(parentApi) + ? parentApi.executionContext.type + : undefined; return ( = ({ const aiopsAppContextValue = useMemo(() => { return { - embeddingOrigin: embeddingOrigin ?? AIOPS_EMBEDDABLE_ORIGIN, + embeddingOrigin: embeddingOrigin ?? AIOPS_EMBEDDABLE_ORIGIN.DEFAULT, ...deps, } as unknown as AiopsAppDependencies; }, [deps, embeddingOrigin]); @@ -157,7 +157,7 @@ const LogRateAnalysisWrapper: FC = ({ From 32918622623f53ef1551f4d569b9c8c00a4996bb Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 30 Sep 2024 16:12:44 +0200 Subject: [PATCH 18/42] cleanup embeddingOrigin/context --- .../log_rate_analysis_content.tsx | 3 +++ .../shared_components/log_rate_analysis.tsx | 21 ++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) 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 9351e80dfb9a3..1f1a77fd8f47e 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 @@ -37,6 +37,7 @@ import { LogRateAnalysisResults, type LogRateAnalysisResultsData, } from '../log_rate_analysis_results'; +import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; export const DEFAULT_SEARCH_QUERY: estypes.QueryDslQueryContainer = { match_all: {} }; const DEFAULT_SEARCH_BAR_QUERY: estypes.QueryDslQueryContainer = { @@ -71,6 +72,8 @@ export const LogRateAnalysisContent: FC = ({ onAnalysisCompleted, onWindowParametersChange, }) => { + const { embeddingOrigin } = useAiopsAppContext(); + const dispatch = useAppDispatch(); const isRunning = useAppSelector((s) => s.logRateAnalysisStream.isRunning); diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx index 2aa3a06ff7b2c..b3932f60e10b2 100644 --- a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx @@ -23,7 +23,7 @@ import type { TimeRange } from '@kbn/es-query'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; import type { SignificantItem } from '@kbn/ml-agg-utils'; -import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context'; +import { AiopsAppContext, type AiopsAppContextValue } from '../hooks/use_aiops_app_context'; import { DataSourceContextProvider } from '../hooks/use_data_source'; import { ReloadContextProvider } from '../hooks/use_reload'; import type { AiopsPluginStartDeps } from '../types'; @@ -74,19 +74,18 @@ const LogRateAnalysisWrapper: FC = ({ lastReloadRequestTime, }) => { const deps = useMemo(() => { - const { http, uiSettings, notifications, ...startServices } = coreStart; - const { lens, data, usageCollection, fieldFormats, charts } = pluginStart; + const { lens, data, usageCollection, fieldFormats, charts, storage, unifiedSearch } = + pluginStart; return { - http, - uiSettings, data, - notifications, lens, usageCollection, fieldFormats, charts, - ...startServices, + storage, + unifiedSearch, + ...coreStart, }; }, [coreStart, pluginStart]); @@ -95,11 +94,11 @@ const LogRateAnalysisWrapper: FC = ({ uiSettingsKeys: UI_SETTINGS, }; - const aiopsAppContextValue = useMemo(() => { + const aiopsAppContextValue = useMemo(() => { return { embeddingOrigin: embeddingOrigin ?? AIOPS_EMBEDDABLE_ORIGIN.DEFAULT, ...deps, - } as unknown as AiopsAppDependencies; + }; }, [deps, embeddingOrigin]); const [manualReload$] = useState>( @@ -156,9 +155,7 @@ const LogRateAnalysisWrapper: FC = ({ - + From e5028a90bfa523063ca8a8bda1177d5a938ba654 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 30 Sep 2024 17:35:22 +0200 Subject: [PATCH 19/42] fix i18n --- x-pack/.i18nrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 7afbc9dc704c4..e1e8478aa0517 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -3,6 +3,7 @@ "paths": { "xpack.actions": "plugins/actions", "xpack.aiops": [ + "packages/ml/aiops_common", "packages/ml/aiops_components", "packages/ml/aiops_log_pattern_analysis", "packages/ml/aiops_log_rate_analysis", From 5232171440403b01705fe702ee47575bf8b29e03 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 30 Sep 2024 17:37:04 +0200 Subject: [PATCH 20/42] fix i18n --- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 3 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index accc5951d75e0..93fa3d91406ee 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9630,7 +9630,6 @@ "xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocIncreaseLabel": "{deviationBucketRate} {deviationBucketRate, plural, one {doc} other {docs}} remontent de 0 au niveau de référence", "xpack.aiops.logRateAnalysisTimeSeriesWarning.description": "L'analyse des taux de log ne fonctionne que sur des index temporels.", "xpack.aiops.miniHistogram.noDataLabel": "S. O.", - "xpack.aiops.navMenu.mlAppNameText": "Machine Learning et Analytique", "xpack.aiops.observabilityAIAssistantContextualInsight.logRateAnalysisTitle": "Causes possibles et résolutions", "xpack.aiops.progressAriaLabel": "Progression", "xpack.aiops.progressTitle": "Progression : {progress} % — {progressMessage}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9d7c444494b6f..cc08371792103 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9384,7 +9384,6 @@ "xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocIncreaseLabel": "ベースラインの0からの{deviationBucketRate} {deviationBucketRate, plural, other {ドキュメント}}レート上昇", "xpack.aiops.logRateAnalysisTimeSeriesWarning.description": "ログレートは、時間ベースのインデックスに対してのみ実行されます。", "xpack.aiops.miniHistogram.noDataLabel": "N/A", - "xpack.aiops.navMenu.mlAppNameText": "機械学習と分析", "xpack.aiops.observabilityAIAssistantContextualInsight.logRateAnalysisTitle": "考えられる原因と修正方法", "xpack.aiops.progressAriaLabel": "進捗", "xpack.aiops.progressTitle": "進行状況:{progress}% — {progressMessage}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7c6e7b3e81487..475b7fde3e67f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9401,7 +9401,6 @@ "xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocIncreaseLabel": "基线中 {deviationBucketRate} {deviationBucketRate, plural, other {文档}}速率从 0 上升", "xpack.aiops.logRateAnalysisTimeSeriesWarning.description": "日志速率分析仅在基于时间的索引上运行。", "xpack.aiops.miniHistogram.noDataLabel": "不可用", - "xpack.aiops.navMenu.mlAppNameText": "Machine Learning 和分析", "xpack.aiops.observabilityAIAssistantContextualInsight.logRateAnalysisTitle": "可能的原因和补救措施", "xpack.aiops.progressAriaLabel": "进度", "xpack.aiops.progressTitle": "进度:{progress}% — {progressMessage}", From 8a68122370937e2622383fd6397eb006fe4a96da Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 7 Oct 2024 15:21:36 +0200 Subject: [PATCH 21/42] fix storing skipped columns in local storage via redux toolkit --- .../state/log_rate_analysis_table_slice.ts | 41 ++++++++++++++++++- .../aiops_log_rate_analysis/state/store.tsx | 11 ++++- .../log_rate_analysis_results.tsx | 11 ----- x-pack/plugins/aiops/public/types/storage.ts | 5 --- 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts index efccf2da19a30..7fdab84f7fca3 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts @@ -6,13 +6,15 @@ */ import type { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice, createListenerMiddleware } from '@reduxjs/toolkit'; import { i18n } from '@kbn/i18n'; import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { GroupTableItem } from './types'; +export const AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS = 'aiops.logRateAnalysisResultColumns'; + export const commonColumns = { ['Log rate']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.logRateColumnTitle', { defaultMessage: 'Log rate', @@ -85,6 +87,25 @@ function getDefaultState(): LogRateAnalysisTableState { }; } +export function getPreloadedState(): LogRateAnalysisTableState { + const defaultState = getDefaultState(); + + const localStorageSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS); + + if (localStorageSkippedColumns === null) { + return defaultState; + } + + try { + defaultState.skippedColumns = JSON.parse(localStorageSkippedColumns); + } catch (err) { + // eslint-disable-next-line no-console + console.warn('Failed to parse skipped columns from local storage:', err); + } + + return defaultState; +} + export const logRateAnalysisTableSlice = createSlice({ name: 'logRateAnalysisTable', initialState: getDefaultState(), @@ -131,3 +152,21 @@ export const { setSelectedSignificantItem, setSkippedColumns, } = logRateAnalysisTableSlice.actions; + +// Create listener middleware +export const localStorageListenerMiddleware = createListenerMiddleware(); + +// Add a listener to save skippedColumns to localStorage whenever it changes +localStorageListenerMiddleware.startListening({ + actionCreator: setSkippedColumns, + effect: (action, listenerApi) => { + const state = listenerApi.getState() as { logRateAnalysisTable: LogRateAnalysisTableState }; + try { + const serializedState = JSON.stringify(state.logRateAnalysisTable.skippedColumns); + localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, serializedState); + } catch (err) { + // eslint-disable-next-line no-console + console.warn('Failed to save state to localStorage:', err); + } + }, +}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx index 097e3c3f10690..680916da40542 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx @@ -15,12 +15,19 @@ import { streamSlice } from '@kbn/ml-response-stream/client'; import { logRateAnalysisResultsSlice } from '../api/stream_reducer'; import { logRateAnalysisSlice } from './log_rate_analysis_slice'; -import { logRateAnalysisTableSlice } from './log_rate_analysis_table_slice'; +import { + logRateAnalysisTableSlice, + getPreloadedState, + localStorageListenerMiddleware, +} from './log_rate_analysis_table_slice'; import { logRateAnalysisFieldCandidatesSlice } from './log_rate_analysis_field_candidates_slice'; import type { InitialAnalysisStart } from './log_rate_analysis_slice'; const getReduxStore = () => configureStore({ + preloadedState: { + logRateAnalysisTable: getPreloadedState(), + }, reducer: { // General page state logRateAnalysis: logRateAnalysisSlice.reducer, @@ -33,6 +40,8 @@ const getReduxStore = () => // Handles hovering and pinning table rows and column selection logRateAnalysisTable: logRateAnalysisTableSlice.reducer, }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().prepend(localStorageListenerMiddleware.middleware), }); interface LogRateAnalysisReduxProviderProps { 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 bacedc88bd073..ed005353f849d 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 @@ -38,7 +38,6 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; -// import { useStorage } from '@kbn/ml-local-storage'; import { AIOPS_ANALYSIS_RUN_ORIGIN, AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-log-rate-analysis/api/schema'; import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-log-rate-analysis/api/schema_v3'; @@ -51,11 +50,6 @@ import { fetchFieldCandidates } from '@kbn/aiops-log-rate-analysis/state/log_rat import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { useDataSource } from '../../hooks/use_data_source'; -// import { -// AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, -// type AiOpsKey, -// type AiOpsStorageMapped, -// } from '../../types/storage'; import { getGroupTableItems, @@ -134,11 +128,6 @@ export const LogRateAnalysisResults: FC = ({ undefined ); const [shouldStart, setShouldStart] = useState(false); - // TOOD: Fix to store in local storage using redux toolkit - // const [skippedColumns, setSkippedColumns] = useStorage< - // AiOpsKey, - // AiOpsStorageMapped - // >(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ['p-value', 'Baseline rate', 'Deviation rate']); const [embeddableOptionsVisible, setEmbeddableOptionsVisible] = useState(false); const onEmbeddableOptionsClickHandler = () => { diff --git a/x-pack/plugins/aiops/public/types/storage.ts b/x-pack/plugins/aiops/public/types/storage.ts index a4a29dda2f2c3..ea6fde6b06552 100644 --- a/x-pack/plugins/aiops/public/types/storage.ts +++ b/x-pack/plugins/aiops/public/types/storage.ts @@ -19,14 +19,12 @@ export const AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE = 'aiops.randomSamplingProbabilityPreference'; export const AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE = 'aiops.patternAnalysisMinimumTimeRangePreference'; -export const AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS = 'aiops.logRateAnalysisResultColumns'; export type AiOps = Partial<{ [AIOPS_FROZEN_TIER_PREFERENCE]: FrozenTierPreference; [AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE]: RandomSamplerOption; [AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE]: number; [AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE]: MinimumTimeRangeOption; - [AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS]: string[]; }> | null; export type AiOpsKey = keyof Exclude; @@ -39,8 +37,6 @@ export type AiOpsStorageMapped = T extends typeof AIOPS_FROZ ? RandomSamplerProbability : T extends typeof AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE ? MinimumTimeRangeOption - : T extends typeof AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS - ? string[] : null; export const AIOPS_STORAGE_KEYS = [ @@ -48,5 +44,4 @@ export const AIOPS_STORAGE_KEYS = [ AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE, AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE, AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE, - AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ] as const; From fd00b43c575fec947e9d0268ed1d3794af3cd3bc Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 7 Oct 2024 15:40:29 +0200 Subject: [PATCH 22/42] add jest tests --- .../log_rate_analysis_table_slice.test.ts | 130 ++++++++++++++++++ .../state/log_rate_analysis_table_slice.ts | 6 +- .../log_rate_analysis_options.tsx | 4 +- .../use_columns.tsx | 10 +- 4 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.test.ts diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.test.ts new file mode 100644 index 0000000000000..498ada00654f0 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.test.ts @@ -0,0 +1,130 @@ +/* + * 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 { configureStore } from '@reduxjs/toolkit'; +import { + logRateAnalysisTableSlice, + localStorageListenerMiddleware, + setSkippedColumns, + getPreloadedState, + AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, + type LogRateAnalysisResultsTableColumnName, +} from './log_rate_analysis_table_slice'; + +describe('getPreloadedState', () => { + beforeEach(() => { + localStorage.clear(); + }); + + it('should return default state when localStorage is empty', () => { + const state = getPreloadedState(); + expect(state).toEqual({ + skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'], + pinnedGroup: null, + pinnedSignificantItem: null, + selectedGroup: null, + selectedSignificantItem: null, + }); + }); + + it('should return state with skippedColumns from localStorage', () => { + const skippedColumns = ['Log rate', 'Doc count']; + localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, JSON.stringify(skippedColumns)); + + const state = getPreloadedState(); + expect(state.skippedColumns).toEqual(skippedColumns); + }); + + it('should return default state when localStorage contains invalid JSON', () => { + localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, 'invalid-json'); + + const state = getPreloadedState(); + expect(state).toEqual({ + skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'], + pinnedGroup: null, + pinnedSignificantItem: null, + selectedGroup: null, + selectedSignificantItem: null, + }); + }); + + it('should return default state when localStorage does not contain skippedColumns', () => { + localStorage.setItem('someOtherKey', JSON.stringify(['someValue'])); + + const state = getPreloadedState(); + expect(state).toEqual({ + skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'], + pinnedGroup: null, + pinnedSignificantItem: null, + selectedGroup: null, + selectedSignificantItem: null, + }); + }); +}); + +type Store = ReturnType; + +describe('localStorageListenerMiddleware', () => { + let store: Store; + + beforeEach(() => { + localStorage.clear(); + store = configureStore({ + reducer: { + logRateAnalysisTable: logRateAnalysisTableSlice.reducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().prepend(localStorageListenerMiddleware.middleware), + }) as Store; + }); + + it('should save skippedColumns to localStorage when setSkippedColumns is dispatched', () => { + const skippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate', 'Doc count']; + store.dispatch(setSkippedColumns(skippedColumns)); + + const storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS); + expect(storedSkippedColumns).toEqual(JSON.stringify(skippedColumns)); + }); + + it('should handle invalid JSON in localStorage gracefully', () => { + localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, 'invalid-json'); + const skippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate', 'Doc count']; + store.dispatch(setSkippedColumns(skippedColumns)); + + const storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS); + expect(storedSkippedColumns).toEqual(JSON.stringify(skippedColumns)); + }); + + it('should not overwrite other localStorage keys', () => { + const otherKey = 'someOtherKey'; + const otherValue = ['someValue']; + localStorage.setItem(otherKey, JSON.stringify(otherValue)); + + const skippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate', 'Doc count']; + store.dispatch(setSkippedColumns(skippedColumns)); + + const storedOtherValue = localStorage.getItem(otherKey); + expect(storedOtherValue).toEqual(JSON.stringify(otherValue)); + }); + + it('should update localStorage when skippedColumns are updated multiple times', () => { + const initialSkippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate']; + store.dispatch(setSkippedColumns(initialSkippedColumns)); + + let storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS); + expect(storedSkippedColumns).toEqual(JSON.stringify(initialSkippedColumns)); + + const updatedSkippedColumns: LogRateAnalysisResultsTableColumnName[] = [ + 'Log rate', + 'Doc count', + ]; + store.dispatch(setSkippedColumns(updatedSkippedColumns)); + + storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS); + expect(storedSkippedColumns).toEqual(JSON.stringify(updatedSkippedColumns)); + }); +}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts index 7fdab84f7fca3..1d9c83dea98a6 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts @@ -64,13 +64,13 @@ export const significantItemColumns = { ...commonColumns, } as const; -export type LogRateAnalysisResultsTableColumnNames = keyof typeof significantItemColumns | 'unique'; +export type LogRateAnalysisResultsTableColumnName = keyof typeof significantItemColumns | 'unique'; type SignificantItemOrNull = SignificantItem | null; type GroupOrNull = GroupTableItem | null; export interface LogRateAnalysisTableState { - skippedColumns: LogRateAnalysisResultsTableColumnNames[]; + skippedColumns: LogRateAnalysisResultsTableColumnName[]; pinnedGroup: GroupOrNull; pinnedSignificantItem: SignificantItemOrNull; selectedGroup: GroupOrNull; @@ -136,7 +136,7 @@ export const logRateAnalysisTableSlice = createSlice({ }, setSkippedColumns: ( state: LogRateAnalysisTableState, - action: PayloadAction + action: PayloadAction ) => { state.skippedColumns = action.payload; }, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx index 46060eec4bc6e..1e5e16e02420d 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx @@ -21,7 +21,7 @@ import { commonColumns, significantItemColumns, setSkippedColumns, - type LogRateAnalysisResultsTableColumnNames, + type LogRateAnalysisResultsTableColumnName, } from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice'; import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover'; @@ -116,7 +116,7 @@ export const LogRateAnalysisOptions: FC = ({ dispatch(clearAllRowState()); }; - const onVisibleColumnsChange = (columns: LogRateAnalysisResultsTableColumnNames[]) => { + const onVisibleColumnsChange = (columns: LogRateAnalysisResultsTableColumnName[]) => { dispatch(setSkippedColumns(columns)); }; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index 3d49af25ca182..5aad495e5bf16 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -24,7 +24,7 @@ import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state'; import { commonColumns, significantItemColumns, - type LogRateAnalysisResultsTableColumnNames, + type LogRateAnalysisResultsTableColumnName, } from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice'; import { getBaselineAndDeviationRates, @@ -226,7 +226,7 @@ export const useColumns = ( ); const columnsMap: Record< - LogRateAnalysisResultsTableColumnNames, + LogRateAnalysisResultsTableColumnName, EuiBasicTableColumn > = useMemo( () => ({ @@ -572,7 +572,7 @@ export const useColumns = ( ); const columns = useMemo(() => { - const columnNamesToReturn: Partial> = + const columnNamesToReturn: Partial> = isGroupsTable ? commonColumns : significantItemColumns; const columnsToReturn = []; @@ -580,13 +580,13 @@ export const useColumns = ( if ( Object.hasOwn(columnNamesToReturn, columnName) === false || skippedColumns.includes( - columnNamesToReturn[columnName as LogRateAnalysisResultsTableColumnNames] as string + columnNamesToReturn[columnName as LogRateAnalysisResultsTableColumnName] as string ) || ((columnName === 'p-value' || columnName === 'Impact') && zeroDocsFallback) ) continue; - columnsToReturn.push(columnsMap[columnName as LogRateAnalysisResultsTableColumnNames]); + columnsToReturn.push(columnsMap[columnName as LogRateAnalysisResultsTableColumnName]); } if (isExpandedRow === true) { From 76eb2fbc8ef20175d3a97fabeb45fa895a97356a Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 8 Oct 2024 10:21:10 +0200 Subject: [PATCH 23/42] remove unused useAppStore --- .../public/containers/app/pages/page_redux_stream/hooks.ts | 5 ++--- x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts | 5 ++--- x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/response_stream/public/containers/app/pages/page_redux_stream/hooks.ts b/examples/response_stream/public/containers/app/pages/page_redux_stream/hooks.ts index f1c8c671611a8..735e70916593f 100644 --- a/examples/response_stream/public/containers/app/pages/page_redux_stream/hooks.ts +++ b/examples/response_stream/public/containers/app/pages/page_redux_stream/hooks.ts @@ -8,10 +8,9 @@ */ import type { TypedUseSelectorHook } from 'react-redux'; -import { useDispatch, useSelector, useStore } from 'react-redux'; -import type { AppDispatch, AppStore, RootState } from './store'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AppDispatch, RootState } from './store'; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook = useSelector; -export const useAppStore: () => AppStore = useStore; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts index 4652d604c5d61..d02a3bea22bf3 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts @@ -6,10 +6,9 @@ */ import type { TypedUseSelectorHook } from 'react-redux'; -import { useDispatch, useSelector, useStore } from 'react-redux'; -import type { AppDispatch, AppStore, RootState } from './store'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AppDispatch, RootState } from './store'; // Improves TypeScript support compared to plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook = useSelector; -export const useAppStore: () => AppStore = useStore; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts index 714738d28e8c9..7f7710ec23f3b 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts @@ -27,7 +27,7 @@ export { setSkippedColumns, } from './log_rate_analysis_table_slice'; export { LogRateAnalysisReduxProvider } from './store'; -export { useAppDispatch, useAppSelector, useAppStore } from './hooks'; +export { useAppDispatch, useAppSelector } from './hooks'; export { useCurrentSelectedGroup } from './use_current_selected_group'; export { useCurrentSelectedSignificantItem } from './use_current_selected_significant_item'; export type { GroupTableItem, GroupTableItemGroup, TableItemAction } from './types'; From cbcfe518f17fcd0eea1e5fbf68a5efba9ec0ee0c Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 8 Oct 2024 11:29:06 +0200 Subject: [PATCH 24/42] remove export --- x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx index 680916da40542..130b7f3dd9357 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx @@ -63,6 +63,6 @@ export const LogRateAnalysisReduxProvider: FC< }; // Infer the `RootState` and `AppDispatch` types from the store itself -export type AppStore = ReturnType; +type AppStore = ReturnType; export type RootState = ReturnType; export type AppDispatch = AppStore['dispatch']; From d9367da173273cde42486c038ba981ee854836eb Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 9 Oct 2024 16:25:18 +0200 Subject: [PATCH 25/42] move cpd to ml&analytics --- .../public/ui_actions/create_change_point_chart.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx b/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx index 70e0ae73a3d1f..4d7ed26e295f5 100644 --- a/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx +++ b/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx @@ -12,7 +12,6 @@ import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; -import { AIOPS_EMBEDDABLE_GROUPING } from '@kbn/aiops-common/constants'; import type { AiopsPluginStartDeps } from '../types'; @@ -32,7 +31,16 @@ export function createAddChangePointChartAction( ): UiActionsActionDefinition { return { id: 'create-change-point-chart', - grouping: AIOPS_EMBEDDABLE_GROUPING, + grouping: [ + { + id: 'ml', + getDisplayName: () => + i18n.translate('xpack.aiops.navMenu.mlAppNameText', { + defaultMessage: 'Machine Learning and Analytics', + }), + getIconType: () => 'machineLearningApp', + }, + ], order: 10, getIconType: () => 'changePointDetection', getDisplayName: () => From 10cedc4f39cbc22320fefd9b63fd4754e73be2fa Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 10 Oct 2024 13:30:10 +0200 Subject: [PATCH 26/42] tweak options button --- .../log_rate_analysis_results.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 ed005353f849d..f1c6d0e60f1c8 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 @@ -17,6 +17,7 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, + EuiToolTip, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -331,11 +332,19 @@ export const LogRateAnalysisResults: FC = ({ )} {embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( - + + + )} From 5898ab6715cc129701c1fe7506a5131cab6712f4 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 10 Oct 2024 13:46:24 +0200 Subject: [PATCH 27/42] fix to make html ids unique for each component instance for DualBrush --- .../src/dual_brush/dual_brush.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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 43d498aa98905..08b9fc5628297 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 @@ -6,7 +6,9 @@ */ import { isEqual } from 'lodash'; -import React, { useEffect, useRef, type FC } from 'react'; +import React, { useEffect, useMemo, useRef, type FC } from 'react'; + +import { htmlIdGenerator } from '@elastic/eui'; import * as d3Brush from 'd3-brush'; import * as d3Scale from 'd3-scale'; @@ -100,6 +102,10 @@ export const DualBrush: FC = (props) => { const d3BrushContainer = useRef(null); const brushes = useRef([]); + // id to prefix html ids for the brushes since this component can be used + // multiple times within dashboard and embedded charts. + const htmlId = useMemo(() => htmlIdGenerator()(), []); + // We need to pass props to refs here because the d3-brush code doesn't consider // native React prop changes. The brush code does its own check whether these props changed then. // The initialized brushes might otherwise act on stale data. @@ -135,10 +141,10 @@ export const DualBrush: FC = (props) => { const xMax = x(maxRef.current) ?? 0; const minExtentPx = Math.round((xMax - xMin) / 100); - const baselineBrush = d3.select('#aiops-brush-baseline'); + const baselineBrush = d3.select(`#aiops-brush-baseline-${htmlId}`); const baselineSelection = d3.brushSelection(baselineBrush.node() as SVGGElement); - const deviationBrush = d3.select('#aiops-brush-deviation'); + const deviationBrush = d3.select(`#aiops-brush-deviation-${htmlId}`); const deviationSelection = d3.brushSelection(deviationBrush.node() as SVGGElement); if (!isBrushXSelection(deviationSelection) || !isBrushXSelection(baselineSelection)) { @@ -260,7 +266,7 @@ export const DualBrush: FC = (props) => { .insert('g', '.brush') .attr('class', 'brush') .attr('id', (b: DualBrush) => { - return 'aiops-brush-' + b.id; + return `aiops-brush-${b.id}-${htmlId}`; }) .attr('data-test-subj', (b: DualBrush) => { // Uppercase the first character of the `id` so we get aiopsBrushBaseline/aiopsBrushDeviation. @@ -339,6 +345,7 @@ export const DualBrush: FC = (props) => { drawBrushes(); } }, [ + htmlId, min, max, width, From da8c6983598a2c91f65315e5a95f66de6dca38ba Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 10 Oct 2024 14:37:30 +0200 Subject: [PATCH 28/42] fix to update panel component when data view changes --- .../embeddable_log_rate_analysis_factory.tsx | 9 ++- .../aiops/public/shared_components/index.tsx | 20 ++++-- ... log_rate_analysis_embeddable_wrapper.tsx} | 65 ++++++++++++------- 3 files changed, 61 insertions(+), 33 deletions(-) rename x-pack/plugins/aiops/public/shared_components/{log_rate_analysis.tsx => log_rate_analysis_embeddable_wrapper.tsx} (67%) 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 866129a83845a..5c00a2cd88806 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 @@ -28,7 +28,7 @@ import { cloneDeep } from 'lodash'; import React, { useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject, distinctUntilChanged, map, skipWhile } from 'rxjs'; -import { getLogRateAnalysisComponent } from '../../shared_components'; +import { getLogRateAnalysisEmbeddableWrapperComponent } from '../../shared_components'; import type { AiopsPluginStart, AiopsPluginStartDeps } from '../../types'; import { initializeLogRateAnalysisControls } from './initialize_log_rate_analysis_analysis_controls'; import type { @@ -162,7 +162,10 @@ export const getLogRateAnalysisEmbeddableFactory = ( } ); - const LogRateAnalysisComponent = getLogRateAnalysisComponent(coreStart, pluginStart); + const LogRateAnalysisEmbeddableWrapper = getLogRateAnalysisEmbeddableWrapperComponent( + coreStart, + pluginStart + ); const onLoading = (v: boolean) => dataLoading.next(v); const onRenderComplete = () => dataLoading.next(false); @@ -203,7 +206,7 @@ export const getLogRateAnalysisEmbeddableFactory = ( : undefined; return ( - import('./change_point_detection')); @@ -39,15 +39,23 @@ export const getPatternAnalysisComponent = ( export type { PatternAnalysisSharedComponent } from './pattern_analysis'; -const LogRateAnalysisLazy = dynamic(async () => import('./log_rate_analysis')); +const LogRateAnalysisEmbeddableWrapperLazy = dynamic( + async () => import('./log_rate_analysis_embeddable_wrapper') +); -export const getLogRateAnalysisComponent = ( +export const getLogRateAnalysisEmbeddableWrapperComponent = ( coreStart: CoreStart, pluginStart: AiopsPluginStartDeps -): LogRateAnalysisSharedComponent => { +): LogRateAnalysisEmbeddableWrapper => { return React.memo((props) => { - return ; + return ( + + ); }); }; -export type { LogRateAnalysisSharedComponent } from './log_rate_analysis'; +export type { LogRateAnalysisEmbeddableWrapper } from './log_rate_analysis_embeddable_wrapper'; diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx similarity index 67% rename from x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx rename to x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx index b3932f60e10b2..4b0292aa41856 100644 --- a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx @@ -7,6 +7,7 @@ import { pick } from 'lodash'; 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'; @@ -34,14 +35,14 @@ import { LogRateAnalysisContent } from '../components/log_rate_analysis/log_rate /** * Only used to initialize internally */ -export type LogRateAnalysisPropsWithDeps = LogRateAnalysisProps & { +export type LogRateAnalysisPropsWithDeps = LogRateAnalysisEmbeddableWrapperProps & { coreStart: CoreStart; pluginStart: AiopsPluginStartDeps; }; -export type LogRateAnalysisSharedComponent = FC; +export type LogRateAnalysisEmbeddableWrapper = FC; -export interface LogRateAnalysisProps { +export interface LogRateAnalysisEmbeddableWrapperProps { dataViewId: string; timeRange: TimeRange; /** @@ -63,7 +64,7 @@ export interface LogRateAnalysisProps { onError: (error: Error) => void; } -const LogRateAnalysisWrapper: FC = ({ +const LogRateAnalysisEmbeddableWrapperWithDeps: FC = ({ // Component dependencies coreStart, pluginStart, @@ -132,6 +133,20 @@ const LogRateAnalysisWrapper: FC = ({ } }, [timeRange]); + // 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 + // dashboard panel are used to change the data view. This is a bit of a + // workaround since originally log rate analysis was developed as a standalone + // page with the expectation that the data view is set once and never changes. + const prevDataViewId = usePrevious(dataViewId); + const [_, setRerenderFlag] = useState(false); + useEffect(() => { + if (prevDataViewId && prevDataViewId !== dataViewId) { + setRerenderFlag((prev) => !prev); + } + }, [dataViewId, prevDataViewId]); + // TODO: Remove data-shared-item as part of https://github.com/elastic/kibana/issues/179376> return (
= ({ padding: '10px', }} > - - - - - - - - - - - - - - - - + {prevDataViewId === dataViewId && ( + + + + + + + + + + + + + + + + + )}
); }; // eslint-disable-next-line import/no-default-export -export default LogRateAnalysisWrapper; +export default LogRateAnalysisEmbeddableWrapperWithDeps; From 382efbcd893f91f4b6f4c92c560b59f95d31b685 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 10 Oct 2024 15:11:10 +0200 Subject: [PATCH 29/42] adds a check to allow only data views with time fields --- .../public/components/time_field_warning.tsx | 38 +++++++++ ...g_rate_analysis_embeddable_initializer.tsx | 81 +++++++++++++++---- .../pattern_analysis_initializer.tsx | 29 +------ .../log_rate_analysis_embeddable_wrapper.tsx | 3 +- 4 files changed, 105 insertions(+), 46 deletions(-) create mode 100644 x-pack/plugins/aiops/public/components/time_field_warning.tsx diff --git a/x-pack/plugins/aiops/public/components/time_field_warning.tsx b/x-pack/plugins/aiops/public/components/time_field_warning.tsx new file mode 100644 index 0000000000000..25e8dd21d68f7 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/time_field_warning.tsx @@ -0,0 +1,38 @@ +/* + * 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 { EuiSpacer, EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export const TimeFieldWarning = () => { + return ( + <> + +

+ {i18n.translate( + 'xpack.aiops.logCategorization.embeddableMenu.timeFieldWarning.title.description', + { + defaultMessage: 'Pattern analysis can only be run on data views with a time field.', + } + )} +

+
+ + + ); +}; diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx index edad6e1c73623..0421add5f021e 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx @@ -8,6 +8,7 @@ import type { FC } from 'react'; import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { pick } from 'lodash'; +import useMountedState from 'react-use/lib/useMountedState'; import { EuiFlyoutHeader, @@ -20,6 +21,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFlyoutFooter, + EuiSpacer, } from '@elastic/eui'; import type { IndexPatternSelectProps } from '@kbn/unified-search-plugin/public'; @@ -29,6 +31,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { TimeFieldWarning } from '../../components/time_field_warning'; + import type { LogRateAnalysisEmbeddableRuntimeState } from './types'; export interface LogRateAnalysisEmbeddableInitializerProps { @@ -52,13 +56,22 @@ export const LogRateAnalysisEmbeddableInitializer: FC< onPreview, isNewPanel, }) => { + const isMounted = useMountedState(); + const [formInput, setFormInput] = useState( pick(initialInput ?? {}, ['dataViewId']) as LogRateAnalysisEmbeddableRuntimeState ); + // State to track if the selected data view is time based, undefined is used + // to track that the check is in progress. + const [isDataViewTimeBased, setIsDataViewTimeBased] = useState(); + const isFormValid = useMemo( - () => isPopulatedObject(formInput, ['dataViewId']) && formInput.dataViewId !== '', - [formInput] + () => + isPopulatedObject(formInput, ['dataViewId']) && + formInput.dataViewId !== '' && + isDataViewTimeBased === true, + [formInput, isDataViewTimeBased] ); const updatedProps = useMemo(() => { @@ -78,7 +91,7 @@ export const LogRateAnalysisEmbeddableInitializer: FC< onPreview(updatedProps); } }, - [isFormValid, onPreview, updatedProps] + [isFormValid, onPreview, updatedProps, isDataViewTimeBased] ); const setDataViewId = useCallback( @@ -87,10 +100,36 @@ export const LogRateAnalysisEmbeddableInitializer: FC< ...formInput, dataViewId: dataViewId ?? '', }); + setIsDataViewTimeBased(undefined); }, [formInput] ); + useEffect( + function checkIsDataViewTimeBased() { + setIsDataViewTimeBased(undefined); + + const { dataViewId } = formInput; + + if (!dataViewId) { + return; + } + + dataViews + .get(dataViewId) + .then((dataView) => { + if (!isMounted()) { + return; + } + setIsDataViewTimeBased(dataView.isTimeBased()); + }) + .catch(() => { + setIsDataViewTimeBased(undefined); + }); + }, + [dataViews, formInput, isMounted] + ); + return ( <> - + { + setDataViewId(newId ?? ''); + }} + /> + {isDataViewTimeBased === false && ( + <> + + + )} - onChange={(newId) => { - setDataViewId(newId ?? ''); - }} - /> + diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx index d240664c9ecec..ef185518638b8 100644 --- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx @@ -33,6 +33,7 @@ import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { DataSourceContextProvider } from '../../hooks/use_data_source'; import type { PatternAnalysisEmbeddableRuntimeState } from './types'; import { PatternAnalysisSettings } from '../../components/log_categorization/log_categorization_for_embeddable/embeddable_menu'; +import { TimeFieldWarning } from '../../components/time_field_warning'; import { RandomSampler } from '../../components/log_categorization/sampling_menu'; import { DEFAULT_PROBABILITY, @@ -394,31 +395,3 @@ const TextFieldWarning = () => { ); }; - -const TimeFieldWarning = () => { - return ( - <> - -

- {i18n.translate( - 'xpack.aiops.logCategorization.embeddableMenu.timeFieldWarning.title.description', - { - defaultMessage: 'Pattern analysis can only be run on data views with a time field.', - } - )} -

-
- - - ); -}; 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 4b0292aa41856..79ab75cb55b7b 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 @@ -146,6 +146,7 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC setRerenderFlag((prev) => !prev); } }, [dataViewId, prevDataViewId]); + const showComponent = prevDataViewId === undefined || prevDataViewId === dataViewId; // TODO: Remove data-shared-item as part of https://github.com/elastic/kibana/issues/179376> return ( @@ -159,7 +160,7 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC padding: '10px', }} > - {prevDataViewId === dataViewId && ( + {showComponent && ( From f8e820cb97d14593984caec98ee40eb489d39767 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 10 Oct 2024 17:16:42 +0200 Subject: [PATCH 30/42] fix url locator --- .../embeddable_change_point_chart_factory.tsx | 16 +--------------- .../embeddable_log_rate_analysis_factory.tsx | 18 ++---------------- .../embeddable_pattern_analysis_factory.tsx | 18 ++---------------- .../log_rate_analysis_embeddable_wrapper.tsx | 3 ++- 4 files changed, 7 insertions(+), 48 deletions(-) diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx index e6dbf2ef5ce43..2ce1a46780db1 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx @@ -63,20 +63,6 @@ export const getChangePointChartEmbeddableFactory = ( buildEmbeddable: async (state, buildApi, uuid, parentApi) => { const [coreStart, pluginStart] = await getStartServices(); - const { http, uiSettings, notifications, ...startServices } = coreStart; - const { lens, data, usageCollection, fieldFormats } = pluginStart; - - const deps = { - http, - uiSettings, - data, - notifications, - lens, - usageCollection, - fieldFormats, - ...startServices, - }; - const { api: timeRangeApi, comparators: timeRangeComparators, @@ -95,7 +81,7 @@ export const getChangePointChartEmbeddableFactory = ( const blockingError = new BehaviorSubject(undefined); const dataViews$ = new BehaviorSubject([ - await deps.data.dataViews.get(state.dataViewId), + await pluginStart.data.dataViews.get(state.dataViewId), ]); const api = buildApi( 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 5c00a2cd88806..592ec32cef120 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 @@ -61,20 +61,6 @@ export const getLogRateAnalysisEmbeddableFactory = ( buildEmbeddable: async (state, buildApi, uuid, parentApi) => { const [coreStart, pluginStart] = await getStartServices(); - const { http, uiSettings, notifications, ...startServices } = coreStart; - const { lens, data, usageCollection, fieldFormats } = pluginStart; - - const deps = { - http, - uiSettings, - data, - notifications, - lens, - usageCollection, - fieldFormats, - ...startServices, - }; - const { api: timeRangeApi, comparators: timeRangeComparators, @@ -93,8 +79,8 @@ export const getLogRateAnalysisEmbeddableFactory = ( const blockingError = new BehaviorSubject(undefined); const dataViews$ = new BehaviorSubject([ - await deps.data.dataViews.get( - state.dataViewId ?? (await deps.data.dataViews.getDefaultId()) + await pluginStart.data.dataViews.get( + state.dataViewId ?? (await pluginStart.data.dataViews.getDefaultId()) ), ]); diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx index 7a4b802bde011..d84043ea5f637 100644 --- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx @@ -60,20 +60,6 @@ export const getPatternAnalysisEmbeddableFactory = ( buildEmbeddable: async (state, buildApi, uuid, parentApi) => { const [coreStart, pluginStart] = await getStartServices(); - const { http, uiSettings, notifications, ...startServices } = coreStart; - const { lens, data, usageCollection, fieldFormats } = pluginStart; - - const deps = { - http, - uiSettings, - data, - notifications, - lens, - usageCollection, - fieldFormats, - ...startServices, - }; - const { api: timeRangeApi, comparators: timeRangeComparators, @@ -92,8 +78,8 @@ export const getPatternAnalysisEmbeddableFactory = ( const blockingError = new BehaviorSubject(undefined); const dataViews$ = new BehaviorSubject([ - await deps.data.dataViews.get( - state.dataViewId ?? (await deps.data.dataViews.getDefaultId()) + await pluginStart.data.dataViews.get( + state.dataViewId ?? (await pluginStart.data.dataViews.getDefaultId()) ), ]); 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 79ab75cb55b7b..2d106ef117061 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 @@ -75,7 +75,7 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC lastReloadRequestTime, }) => { const deps = useMemo(() => { - const { lens, data, usageCollection, fieldFormats, charts, storage, unifiedSearch } = + const { lens, data, usageCollection, fieldFormats, charts, share, storage, unifiedSearch } = pluginStart; return { @@ -84,6 +84,7 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC usageCollection, fieldFormats, charts, + share, storage, unifiedSearch, ...coreStart, From 7435b5fb205d1fd539a56b83dfd87f865c5d4578 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 11 Oct 2024 09:19:32 +0200 Subject: [PATCH 31/42] fix contexts --- .../field_stats_flyout_provider.tsx | 24 ++---- .../field_stats_info_button.tsx | 16 +--- .../use_field_stats_flyout_context.ts | 25 ++++++- .../change_point_detection/fields_config.tsx | 3 +- .../change_point_chart_initializer.tsx | 73 ++++++++++++++++--- .../change_point_detection.tsx | 59 +++++++-------- .../shared_components/pattern_analysis.tsx | 55 +++++++------- .../configuration_step_form.tsx | 1 + .../new_job/pages/new_job/wizard_steps.tsx | 1 + .../components/wizard/wizard.tsx | 3 +- 10 files changed, 155 insertions(+), 105 deletions(-) diff --git a/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx b/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx index 678dec7d36f42..4e7a501140b01 100644 --- a/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx +++ b/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx @@ -7,9 +7,8 @@ import type { PropsWithChildren, FC } from 'react'; import React, { useCallback, useState } from 'react'; -import type { CoreStart } from '@kbn/core/public'; +import type { ThemeServiceStart } from '@kbn/core-theme-browser'; import type { DataView } from '@kbn/data-plugin/common'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; import type { FieldStatsProps } from '@kbn/unified-field-list/src/components/field_stats'; @@ -18,32 +17,18 @@ import { getProcessedFields } from '@kbn/ml-data-grid'; import { stringHash } from '@kbn/ml-string-hash'; import { lastValueFrom } from 'rxjs'; import { useRef } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { getMergedSampleDocsForPopulatedFieldsQuery } from './populated_fields/get_merged_populated_fields_query'; import { FieldStatsFlyout } from './field_stats_flyout'; import { MLFieldStatsFlyoutContext } from './use_field_stats_flyout_context'; import { PopulatedFieldsCacheManager } from './populated_fields/populated_fields_cache_manager'; -type Services = CoreStart & { - data: DataPublicPluginStart; -}; - -function useDataSearch() { - const { data } = useKibana().services; - - if (!data) { - throw new Error('Kibana data service not available.'); - } - - return data.search; -} - /** * Props for the FieldStatsFlyoutProvider component. * * @typedef {Object} FieldStatsFlyoutProviderProps * @property dataView - The data view object. * @property fieldStatsServices - Services required for field statistics. + * @property theme - The EUI theme service. * @property [timeRangeMs] - Optional time range in milliseconds. * @property [dslQuery] - Optional DSL query for filtering field statistics. * @property [disablePopulatedFields] - Optional flag to disable populated fields. @@ -51,6 +36,7 @@ function useDataSearch() { export type FieldStatsFlyoutProviderProps = PropsWithChildren<{ dataView: DataView; fieldStatsServices: FieldStatsServices; + theme: ThemeServiceStart; timeRangeMs?: TimeRangeMs; dslQuery?: FieldStatsProps['dslQuery']; disablePopulatedFields?: boolean; @@ -79,12 +65,13 @@ export const FieldStatsFlyoutProvider: FC = (prop const { dataView, fieldStatsServices, + theme, timeRangeMs, dslQuery, disablePopulatedFields = false, children, } = props; - const search = useDataSearch(); + const { search } = fieldStatsServices.data; const [isFieldStatsFlyoutVisible, setFieldStatsIsFlyoutVisible] = useState(false); const [fieldName, setFieldName] = useState(); const [fieldValue, setFieldValue] = useState(); @@ -187,6 +174,7 @@ export const FieldStatsFlyoutProvider: FC = (prop fieldValue, timeRangeMs, populatedFields, + theme, }} > = (props) => { const { field, label, onButtonClick, disabled, isEmpty, hideTrigger } = props; - const themeVars = useThemeVars(); + const theme = useFieldStatsFlyoutThemeVars(); + const themeVars = useCurrentEuiThemeVars(theme); + const emptyFieldMessage = isEmpty ? ' ' + i18n.translate('xpack.ml.newJob.wizard.fieldContextPopover.emptyFieldInSampleDocsMsg', { diff --git a/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts b/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts index ec6c28873011c..121426352e6e4 100644 --- a/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts +++ b/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts @@ -7,6 +7,7 @@ import { createContext, useContext } from 'react'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; +import type { ThemeServiceStart } from '@kbn/core-theme-browser'; /** * Represents the properties for the MLJobWizardFieldStatsFlyout component. @@ -21,6 +22,7 @@ interface MLJobWizardFieldStatsFlyoutProps { fieldValue?: string | number; timeRangeMs?: TimeRangeMs; populatedFields?: Set; + theme?: ThemeServiceStart; } /** @@ -34,6 +36,7 @@ export const MLFieldStatsFlyoutContext = createContext {}, timeRangeMs: undefined, populatedFields: undefined, + theme: undefined, }); /** @@ -41,5 +44,25 @@ export const MLFieldStatsFlyoutContext = createContext> = ({ }) => { const { splitFieldsOptions, combinedQuery } = useChangePointDetectionContext(); const { dataView } = useDataSource(); - const { data, uiSettings, fieldFormats, charts, fieldStats } = useAiopsAppContext(); + const { data, uiSettings, fieldFormats, charts, fieldStats, theme } = useAiopsAppContext(); const timefilter = useTimefilter(); // required in order to trigger state updates useTimeRangeUpdates(); @@ -677,6 +677,7 @@ export const FieldsControls: FC> = ({ } : undefined } + theme={theme} > diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx index 0b451d77e6b96..e69511fe45f92 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx @@ -18,16 +18,23 @@ import { EuiHorizontalRule, EuiTitle, } from '@elastic/eui'; +import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats'; import { ES_FIELD_TYPES } from '@kbn/field-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { FieldStatsFlyoutProvider } from '@kbn/ml-field-stats-flyout'; +import { useTimefilter } from '@kbn/ml-date-picker'; import { pick } from 'lodash'; import type { FC } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; import { + ChangePointDetectionContextProvider, ChangePointDetectionControlsContextProvider, + useChangePointDetectionContext, useChangePointDetectionControlsContext, } from '../../components/change_point_detection/change_point_detection_context'; import { DEFAULT_AGG_FUNCTION } from '../../components/change_point_detection/constants'; @@ -38,7 +45,8 @@ import { PartitionsSelector } from '../../components/change_point_detection/part import { SplitFieldSelector } from '../../components/change_point_detection/split_field_selector'; import { ViewTypeSelector } from '../../components/change_point_detection/view_type_selector'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; -import { DataSourceContextProvider } from '../../hooks/use_data_source'; +import { useDataSource, DataSourceContextProvider } from '../../hooks/use_data_source'; +import { FilterQueryContextProvider } from '../../hooks/use_filters_query'; import { DEFAULT_SERIES } from './const'; import type { ChangePointEmbeddableRuntimeState } from './types'; @@ -53,12 +61,18 @@ export const ChangePointChartInitializer: FC = ({ onCreate, onCancel, }) => { + const appContextValue = useAiopsAppContext(); const { data: { dataViews }, unifiedSearch: { ui: { IndexPatternSelect }, }, - } = useAiopsAppContext(); + } = appContextValue; + + const datePickerDeps: DatePickerDependencies = { + ...pick(appContextValue, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), + uiSettingsKeys: UI_SETTINGS, + }; const [dataViewId, setDataViewId] = useState(initialInput?.dataViewId ?? ''); const [viewType, setViewType] = useState(initialInput?.viewType ?? 'charts'); @@ -136,15 +150,21 @@ export const ChangePointChartInitializer: FC = ({ }} /> + - - - - + + + + + + + + + @@ -191,7 +211,13 @@ export const FormControls: FC<{ onChange: (update: FormControlsProps) => void; onValidationChange: (isValid: boolean) => void; }> = ({ formInput, onChange, onValidationChange }) => { + const { charts, data, fieldFormats, theme, uiSettings } = useAiopsAppContext(); + const { dataView } = useDataSource(); + const { combinedQuery } = useChangePointDetectionContext(); const { metricFieldOptions, splitFieldsOptions } = useChangePointDetectionControlsContext(); + const timefilter = useTimefilter(); + const timefilterActiveBounds = timefilter.getActiveBounds(); + const prevMetricFieldOptions = usePrevious(metricFieldOptions); const enableSearch = useMemo(() => { @@ -239,10 +265,33 @@ export const FormControls: FC<{ [formInput, onChange] ); + const fieldStatsServices: FieldStatsServices = useMemo(() => { + return { + uiSettings, + dataViews: data.dataViews, + data, + fieldFormats, + charts, + }; + }, [uiSettings, data, fieldFormats, charts]); + if (!isPopulatedObject(formInput)) return null; return ( - <> + updateCallback({ maxSeriesToPlot: v })} onValidationChange={(result) => onValidationChange(result === null)} /> - + ); }; diff --git a/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx b/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx index 6aeeeba6be342..53997219fd639 100644 --- a/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx +++ b/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx @@ -11,7 +11,6 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { UI_SETTINGS } from '@kbn/data-service'; import type { TimeRange } from '@kbn/es-query'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { pick } from 'lodash'; import React, { useEffect, useMemo, useState, type FC } from 'react'; import type { Observable } from 'rxjs'; @@ -141,36 +140,34 @@ const ChangePointDetectionWrapper: FC = ({ width: 100%; `} > - - - - - - - - - - - - - - - + + + + + + + + + + + + +
); }; diff --git a/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx index 49d0a14895d5d..f601474a5707f 100644 --- a/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx +++ b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx @@ -10,7 +10,6 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { UI_SETTINGS } from '@kbn/data-service'; import type { TimeRange } from '@kbn/es-query'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { pick } from 'lodash'; import React, { useEffect, useMemo, useState, type FC } from 'react'; import type { Observable } from 'rxjs'; @@ -139,34 +138,32 @@ const PatternAnalysisWrapper: FC = ({ padding: '10px', }} > - - - - - - - - - - - - - + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index 3212eba8b2ddd..7fd678e98f6fd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -576,6 +576,7 @@ export const ConfigurationStepForm: FC = ({ fieldStatsServices={fieldStatsServices} timeRangeMs={indexData.timeRangeMs} dslQuery={jobConfigQuery} + theme={services.theme} > diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx index 6c4600be5d25e..b44c523bc57cf 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx @@ -123,6 +123,7 @@ export const WizardSteps: FC = ({ currentStep, setCurrentStep }) => { fieldStatsServices={fieldStatsServices} timeRangeMs={timeRangeMs} dslQuery={jobCreator.query} + theme={services.theme} > <> diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index ab2865b85eb8a..2c078f93627cc 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -111,7 +111,7 @@ export const CreateTransformWizardContext = createContext<{ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) => { const { showNodeInfo } = useEnabledFeatures(); const appDependencies = useAppDependencies(); - const { uiSettings, data, fieldFormats, charts } = appDependencies; + const { uiSettings, data, fieldFormats, charts, theme } = appDependencies; const { dataView } = searchItems; // The current WIZARD_STEP @@ -247,6 +247,7 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) fieldStatsServices={fieldStatsServices} timeRangeMs={stepDefineState.timeRangeMs} dslQuery={transformConfig.source.query} + theme={theme} > Date: Fri, 11 Oct 2024 17:59:13 +0200 Subject: [PATCH 32/42] Pass on and use global search/filters to embeddable. --- .../log_rate_analysis_for_embeddable.tsx | 59 +++++++++++++++++++ .../log_rate_analysis_embeddable_wrapper.tsx | 43 +++++--------- 2 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx new file mode 100644 index 0000000000000..7357bc1a65ee5 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx @@ -0,0 +1,59 @@ +/* + * 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, { useMemo, type FC } from 'react'; + +import datemath from '@elastic/datemath'; + +import type { TimeRange } from '@kbn/es-query'; + +import { createMergedEsQuery } from '../../application/utils/search_utils'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { useFilterQueryUpdates } from '../../hooks/use_filters_query'; +import { useSearch } from '../../hooks/use_search'; +import { useDataSource } from '../../hooks/use_data_source'; +import { getDefaultLogRateAnalysisAppState } from '../../application/url_state/log_rate_analysis'; + +import { LogRateAnalysisDocumentCountChartData } from './log_rate_analysis_content/log_rate_analysis_document_count_chart_data'; +import { LogRateAnalysisContent } from './log_rate_analysis_content/log_rate_analysis_content'; + +export interface LogRateAnalysisForEmbeddableProps { + timeRange: TimeRange; +} + +export const LogRateAnalysisForEmbeddable: FC = ({ + timeRange, +}) => { + const { uiSettings } = useAiopsAppContext(); + const { dataView } = useDataSource(); + const { filters, query } = useFilterQueryUpdates(); + const appState = getDefaultLogRateAnalysisAppState({ + searchQuery: createMergedEsQuery(query, filters, dataView, uiSettings), + filters, + }); + const { searchQuery } = useSearch({ dataView, savedSearch: null }, appState, true); + + const timeRangeParsed = useMemo(() => { + if (timeRange) { + const min = datemath.parse(timeRange.from); + const max = datemath.parse(timeRange.to); + if (min && max) { + return { min, max }; + } + } + }, [timeRange]); + + return ( + <> + + + + ); +}; 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 2d106ef117061..9f2a88e73461c 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 @@ -12,8 +12,6 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; import { createBrowserHistory } from 'history'; -import datemath from '@elastic/datemath'; - import { UrlStateProvider } from '@kbn/ml-url-state'; import { Router } from '@kbn/shared-ux-router'; import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; @@ -27,10 +25,10 @@ import type { SignificantItem } from '@kbn/ml-agg-utils'; import { AiopsAppContext, type AiopsAppContextValue } from '../hooks/use_aiops_app_context'; import { DataSourceContextProvider } from '../hooks/use_data_source'; import { ReloadContextProvider } from '../hooks/use_reload'; +import { FilterQueryContextProvider } from '../hooks/use_filters_query'; import type { AiopsPluginStartDeps } from '../types'; -import { LogRateAnalysisDocumentCountChartData } from '../components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_document_count_chart_data'; -import { LogRateAnalysisContent } from '../components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content'; +import { LogRateAnalysisForEmbeddable } from '../components/log_rate_analysis/log_rate_analysis_for_embeddable'; /** * Only used to initialize internally @@ -124,16 +122,6 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC const history = createBrowserHistory(); - const timeRangeParsed = useMemo(() => { - if (timeRange) { - const min = datemath.parse(timeRange.from); - const max = datemath.parse(timeRange.to); - if (min && max) { - return { min, max }; - } - } - }, [timeRange]); - // 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 @@ -165,19 +153,20 @@ const LogRateAnalysisEmbeddableWrapperWithDeps: FC - - - - - - - - - - + + + + + + + + + + + From a16180ddcf3556fbf0392d73d1e1b929830592d8 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 11 Oct 2024 18:37:25 +0200 Subject: [PATCH 33/42] Fix DualBrush positioning in embedded panels --- .../src/document_count_chart/document_count_chart.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 39291762b8fca..d9f68fe7ef890 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 @@ -426,7 +426,12 @@ export const DocumentCountChart: FC = (props) => { <> {isBrushVisible && (
-
+ {/** + * We need position:relative on this parent container of the BrushBadges, + * because of the absolute positioning of the BrushBadges. Without it, the + * BrushBadges would not be positioned correctly when used in embedded panels. + */} +
Date: Fri, 11 Oct 2024 20:15:37 +0200 Subject: [PATCH 34/42] fix current field candidates list --- ...te_analysis_field_candidates_slice.test.ts | 13 ++++++-- ...og_rate_analysis_field_candidates_slice.ts | 31 +++++++++++++---- .../log_rate_analysis_options.tsx | 25 ++++---------- .../log_rate_analysis_results.tsx | 33 ++++++++++--------- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts index 4f829b0e0bf5a..5b4946dc2eb2b 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts @@ -9,14 +9,16 @@ import { httpServiceMock } from '@kbn/core/public/mocks'; import type { FetchFieldCandidatesResponse } from '../queries/fetch_field_candidates'; -import { fetchFieldCandidates } from './log_rate_analysis_field_candidates_slice'; +import { fetchFieldCandidates, getDefaultState } from './log_rate_analysis_field_candidates_slice'; const mockHttp = httpServiceMock.createStartContract(); describe('fetchFieldCandidates', () => { it('dispatches field candidates', async () => { const mockDispatch = jest.fn(); - const mockGetState = jest.fn(); + const mockGetState = jest.fn().mockReturnValue({ + logRateAnalysisFieldCandidates: getDefaultState(), + }); const mockResponse: FetchFieldCandidatesResponse = { isECS: false, @@ -60,7 +62,12 @@ describe('fetchFieldCandidates', () => { payload: { fieldSelectionMessage: '2 out of 5 fields were preselected for the analysis. Use the "Fields" dropdown to adjust the selection.', - fieldFilterSkippedItems: [ + initialFieldFilterSkippedItems: [ + 'another-keyword-field', + 'another-text-field', + 'yet-another-text-field', + ], + currentFieldFilterSkippedItems: [ 'another-keyword-field', 'another-text-field', 'yet-another-text-field', diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts index aa5cb969e5401..a3ac63c5927e9 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts @@ -90,10 +90,14 @@ export const fetchFieldCandidates = createAsyncThunk( ...selectedKeywordFieldCandidates, ...selectedTextFieldCandidates, ]; - const fieldFilterSkippedItems = fieldFilterUniqueItems.filter( + const initialFieldFilterSkippedItems = fieldFilterUniqueItems.filter( (d) => !fieldFilterUniqueSelectedItems.includes(d) ); + const currentFieldFilterSkippedItems = ( + thunkApi.getState() as { logRateAnalysisFieldCandidates: FieldCandidatesState } + ).logRateAnalysisFieldCandidates.currentFieldFilterSkippedItems; + thunkApi.dispatch( setAllFieldCandidates({ fieldSelectionMessage: getFieldSelectionMessage( @@ -102,7 +106,13 @@ export const fetchFieldCandidates = createAsyncThunk( fieldFilterUniqueSelectedItems.length ), fieldFilterUniqueItems, - fieldFilterSkippedItems, + initialFieldFilterSkippedItems, + // If the currentFieldFilterSkippedItems is null, we're on the first load, + // only then we set the current skipped fields to the initial skipped fields. + currentFieldFilterSkippedItems: + currentFieldFilterSkippedItems === null && initialFieldFilterSkippedItems.length > 0 + ? initialFieldFilterSkippedItems + : currentFieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates, selectedKeywordFieldCandidates, @@ -116,18 +126,20 @@ export interface FieldCandidatesState { isLoading: boolean; fieldSelectionMessage?: string; fieldFilterUniqueItems: string[]; - fieldFilterSkippedItems: string[]; + initialFieldFilterSkippedItems: string[]; + currentFieldFilterSkippedItems: string[] | null; keywordFieldCandidates: string[]; textFieldCandidates: string[]; selectedKeywordFieldCandidates: string[]; selectedTextFieldCandidates: string[]; } -function getDefaultState(): FieldCandidatesState { +export function getDefaultState(): FieldCandidatesState { return { isLoading: false, fieldFilterUniqueItems: [], - fieldFilterSkippedItems: [], + initialFieldFilterSkippedItems: [], + currentFieldFilterSkippedItems: null, keywordFieldCandidates: [], textFieldCandidates: [], selectedKeywordFieldCandidates: [], @@ -145,6 +157,12 @@ export const logRateAnalysisFieldCandidatesSlice = createSlice({ ) => { return { ...state, ...action.payload }; }, + setCurrentFieldFilterSkippedItems: ( + state: FieldCandidatesState, + action: PayloadAction + ) => { + return { ...state, currentFieldFilterSkippedItems: action.payload }; + }, }, extraReducers: (builder) => { builder.addCase(fetchFieldCandidates.pending, (state) => { @@ -157,4 +175,5 @@ export const logRateAnalysisFieldCandidatesSlice = createSlice({ }); // Action creators are generated for each case reducer function -export const { setAllFieldCandidates } = logRateAnalysisFieldCandidatesSlice.actions; +export const { setAllFieldCandidates, setCurrentFieldFilterSkippedItems } = + logRateAnalysisFieldCandidatesSlice.actions; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx index 1e5e16e02420d..99a2829886aec 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx @@ -6,7 +6,7 @@ */ import type { FC } from 'react'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; @@ -23,6 +23,7 @@ import { setSkippedColumns, type LogRateAnalysisResultsTableColumnName, } from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice'; +import { setCurrentFieldFilterSkippedItems } from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_field_candidates_slice'; import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover'; import { ItemFilterPopover as ColumnFilterPopover } from './item_filter_popover'; @@ -82,13 +83,11 @@ const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn'; export interface LogRateAnalysisOptionsProps { foundGroups: boolean; growFirstItem?: boolean; - onFieldsFilterChange: (skippedFieldsUpdate: string[]) => void; } export const LogRateAnalysisOptions: FC = ({ foundGroups, growFirstItem = false, - onFieldsFilterChange, }) => { const dispatch = useAppDispatch(); @@ -96,20 +95,11 @@ export const LogRateAnalysisOptions: FC = ({ const { isRunning } = useAppSelector((s) => s.logRateAnalysisStream); const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates); const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable); - const { fieldFilterUniqueItems, fieldFilterSkippedItems } = fieldCandidates; + const { fieldFilterUniqueItems, initialFieldFilterSkippedItems } = fieldCandidates; const fieldFilterButtonDisabled = isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0; const toggleIdSelected = groupResults ? resultsGroupedOnId : resultsGroupedOffId; - // null is used as the uninitialized state to identify the first load. - const [skippedFields, setSkippedFields] = useState(null); - - // Set skipped fields only on first load, otherwise we'd overwrite the user's selection. - useEffect(() => { - if (skippedFields === null && fieldFilterSkippedItems.length > 0) - setSkippedFields(fieldFilterSkippedItems); - }, [fieldFilterSkippedItems, skippedFields]); - const onGroupResultsToggle = (optionId: string) => { dispatch(setGroupResults(optionId === resultsGroupedOnId)); // When toggling the group switch, clear all row selections @@ -120,9 +110,8 @@ export const LogRateAnalysisOptions: FC = ({ dispatch(setSkippedColumns(columns)); }; - const onFieldsFilterChangeHandler = (skippedFieldsUpdate: string[]) => { - setSkippedFields(skippedFieldsUpdate); - onFieldsFilterChange(skippedFieldsUpdate); + const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => { + dispatch(setCurrentFieldFilterSkippedItems(skippedFieldsUpdate)); }; // Disable the grouping switch toggle only if no groups were found, @@ -173,8 +162,8 @@ export const LogRateAnalysisOptions: FC = ({ popoverButtonTitle={fieldsButton} selectedItemLimit={1} uniqueItemNames={fieldFilterUniqueItems} - initialSkippedItems={fieldFilterSkippedItems} - onChange={onFieldsFilterChangeHandler} + initialSkippedItems={initialFieldFilterSkippedItems} + onChange={onFieldsFilterChange} /> 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 f1c6d0e60f1c8..6ab82bd16e00d 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 @@ -135,21 +135,27 @@ export const LogRateAnalysisResults: FC = ({ setEmbeddableOptionsVisible((s) => !s); }; - const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => { + const { currentFieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates } = + fieldCandidates; + + useEffect(() => { + if (currentFieldFilterSkippedItems === null) return; + dispatch(resetResults()); setOverrides({ loaded: 0, remainingKeywordFieldCandidates: keywordFieldCandidates.filter( - (d) => !skippedFieldsUpdate.includes(d) + (d) => !currentFieldFilterSkippedItems.includes(d) ), remainingTextFieldCandidates: textFieldCandidates.filter( - (d) => !skippedFieldsUpdate.includes(d) + (d) => !currentFieldFilterSkippedItems.includes(d) ), regroupOnly: false, }); startHandler(true, false); - }; - const { fieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates } = fieldCandidates; + // custom check to trigger on currentFieldFilterSkippedItems change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentFieldFilterSkippedItems]); function cancelHandler() { abortCtrl.current.abort(); @@ -209,10 +215,12 @@ export const LogRateAnalysisResults: FC = ({ dispatch(resetResults()); setOverrides({ remainingKeywordFieldCandidates: keywordFieldCandidates.filter( - (d) => fieldFilterSkippedItems !== null && fieldFilterSkippedItems.includes(d) + (d) => + currentFieldFilterSkippedItems === null || !currentFieldFilterSkippedItems.includes(d) ), remainingTextFieldCandidates: textFieldCandidates.filter( - (d) => fieldFilterSkippedItems !== null && fieldFilterSkippedItems.includes(d) + (d) => + currentFieldFilterSkippedItems === null || !currentFieldFilterSkippedItems.includes(d) ), }); } @@ -325,10 +333,7 @@ export const LogRateAnalysisResults: FC = ({ > <> {embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( - + )} {embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( @@ -354,11 +359,7 @@ export const LogRateAnalysisResults: FC = ({ <> - + )} From 03c3cdce7081f0aab8bc81b64b9b799e7ebac309 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 11 Oct 2024 20:16:04 +0200 Subject: [PATCH 35/42] linting --- x-pack/packages/ml/field_stats_flyout/tsconfig.json | 3 +-- x-pack/plugins/aiops/tsconfig.json | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/packages/ml/field_stats_flyout/tsconfig.json b/x-pack/packages/ml/field_stats_flyout/tsconfig.json index 0010d79432e34..df70aa27788b8 100644 --- a/x-pack/packages/ml/field_stats_flyout/tsconfig.json +++ b/x-pack/packages/ml/field_stats_flyout/tsconfig.json @@ -23,9 +23,7 @@ "@kbn/i18n", "@kbn/react-field", "@kbn/ml-anomaly-utils", - "@kbn/kibana-react-plugin", "@kbn/ml-kibana-theme", - "@kbn/core", "@kbn/ml-data-grid", "@kbn/ml-string-hash", "@kbn/ml-is-populated-object", @@ -33,5 +31,6 @@ "@kbn/ml-is-defined", "@kbn/field-types", "@kbn/ui-theme", + "@kbn/core-theme-browser", ] } diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index af3e627acfb0d..e8b4f4f3ed972 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -63,7 +63,6 @@ "@kbn/presentation-containers", "@kbn/presentation-publishing", "@kbn/presentation-util-plugin", - "@kbn/react-kibana-context-render", "@kbn/react-kibana-context-theme", "@kbn/react-kibana-mount", "@kbn/rison", From 699fe196074f71a3a3ba8a1d9e5f02e465711e10 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 11 Oct 2024 22:56:28 +0200 Subject: [PATCH 36/42] fix types --- .../use_field_stats_trigger.tsx | 2 ++ .../public/hooks/use_aiops_app_context.ts | 25 ++++--------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx b/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx index 546dc36ce9e4b..df9c35dc6aece 100644 --- a/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx +++ b/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx @@ -94,3 +94,5 @@ export function useFieldStatsTrigger() { populatedFields, }; } + +export type UseFieldStatsTrigger = typeof useFieldStatsTrigger; diff --git a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts index 488c4c589399a..c240ec90bc1df 100644 --- a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts +++ b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createContext, type FC, type PropsWithChildren, useContext } from 'react'; +import { createContext, type FC, useContext } from 'react'; import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; @@ -24,17 +24,12 @@ import type { ThemeServiceStart, } from '@kbn/core/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; -import { type EuiComboBoxProps } from '@elastic/eui/src/components/combo_box/combo_box'; -import { type DataView } from '@kbn/data-views-plugin/common'; -import type { - FieldStatsProps, - FieldStatsServices, -} from '@kbn/unified-field-list/src/components/field_stats'; -import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { CasesPublicStart } from '@kbn/cases-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { FieldStatsFlyoutProviderProps } from '@kbn/ml-field-stats-flyout/field_stats_flyout_provider'; +import type { UseFieldStatsTrigger } from '@kbn/ml-field-stats-flyout/use_field_stats_trigger'; /** * AIOps app context value to be provided via React context. @@ -115,18 +110,8 @@ export interface AiopsAppContextValue { * Deps for unified fields stats. */ fieldStats?: { - useFieldStatsTrigger: () => { - renderOption: EuiComboBoxProps['renderOption']; - closeFlyout: () => void; - }; - FieldStatsFlyoutProvider: FC< - PropsWithChildren<{ - dataView: DataView; - fieldStatsServices: FieldStatsServices; - timeRangeMs?: TimeRangeMs; - dslQuery?: FieldStatsProps['dslQuery']; - }> - >; + useFieldStatsTrigger: UseFieldStatsTrigger; + FieldStatsFlyoutProvider: FC; }; embeddable?: EmbeddableStart; cases?: CasesPublicStart; From 652c56380fb1ebf32fcac9d157de2665c1f6bd84 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 25 Oct 2024 17:33:29 +0200 Subject: [PATCH 37/42] fix applying the query --- .../log_rate_analysis_for_embeddable.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx index 7357bc1a65ee5..e45d4bc776aef 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_for_embeddable.tsx @@ -10,8 +10,9 @@ import React, { useMemo, type FC } from 'react'; import datemath from '@elastic/datemath'; import type { TimeRange } from '@kbn/es-query'; +import { buildEsQuery } from '@kbn/es-query'; +import { getEsQueryConfig } from '@kbn/data-service'; -import { createMergedEsQuery } from '../../application/utils/search_utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { useFilterQueryUpdates } from '../../hooks/use_filters_query'; import { useSearch } from '../../hooks/use_search'; @@ -32,7 +33,12 @@ export const LogRateAnalysisForEmbeddable: FC const { dataView } = useDataSource(); const { filters, query } = useFilterQueryUpdates(); const appState = getDefaultLogRateAnalysisAppState({ - searchQuery: createMergedEsQuery(query, filters, dataView, uiSettings), + searchQuery: buildEsQuery( + dataView, + query ?? [], + filters ?? [], + uiSettings ? getEsQueryConfig(uiSettings) : undefined + ), filters, }); const { searchQuery } = useSearch({ dataView, savedSearch: null }, appState, true); From db0dd47e445fa8bc03a08c87d3320bf452c6b4ec Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 25 Oct 2024 17:38:07 +0200 Subject: [PATCH 38/42] adds functional test for log rate analysis embeddable --- ...g_rate_analysis_embeddable_initializer.tsx | 3 +- x-pack/test/functional/apps/aiops/index.ts | 1 + .../log_rate_analysis_dashboard_embeddable.ts | 104 +++++++++++++++++ .../services/aiops/dashboard_embeddables.ts | 110 ++++++++++++++++++ .../test/functional/services/aiops/index.ts | 3 + .../services/aiops/log_rate_analysis_page.ts | 16 +++ 6 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/apps/aiops/log_rate_analysis_dashboard_embeddable.ts create mode 100644 x-pack/test/functional/services/aiops/dashboard_embeddables.ts diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx index 0421add5f021e..bf53f07677739 100644 --- a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx @@ -153,7 +153,7 @@ export const LogRateAnalysisEmbeddableInitializer: FC< - + { setDataViewId(newId ?? ''); }} + data-test-subj="aiopsLogRateAnalysisEmbeddableDataViewSelector" /> {isDataViewTimeBased === false && ( <> diff --git a/x-pack/test/functional/apps/aiops/index.ts b/x-pack/test/functional/apps/aiops/index.ts index 8706d3d242c6b..75ad41f2e21fb 100644 --- a/x-pack/test/functional/apps/aiops/index.ts +++ b/x-pack/test/functional/apps/aiops/index.ts @@ -31,6 +31,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('./change_point_detection')); loadTestFile(require.resolve('./log_pattern_analysis')); loadTestFile(require.resolve('./log_pattern_analysis_in_discover')); diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis_dashboard_embeddable.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis_dashboard_embeddable.ts new file mode 100644 index 0000000000000..331a89821335d --- /dev/null +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis_dashboard_embeddable.ts @@ -0,0 +1,104 @@ +/* + * 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 { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants'; + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { logRateAnalysisTestData } from './log_rate_analysis_test_data'; + +const testDataSetup = logRateAnalysisTestData[0]; +const testDataPanel = { + type: 'testData', + suiteSuffix: 'with multi metric job', + panelTitle: `AIOps log rate analysis for ${testDataSetup.sourceIndexOrSavedSearch}`, + dashboardTitle: `AIOps log rate analysis for ${ + testDataSetup.sourceIndexOrSavedSearch + } ${Date.now()}`, +}; +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects([ + 'common', + 'console', + 'dashboard', + 'header', + 'home', + 'security', + 'timePicker', + ]); + const aiops = getService('aiops'); + + // AIOps / Log Rate Analysis lives in the ML UI so we need some related services. + const ml = getService('ml'); + + const from = 'Apr 16, 2023 @ 00:39:02.912'; + const to = 'Jun 15, 2023 @ 21:45:26.749'; + + describe('log rate analysis in dashboard', function () { + before(async () => { + await aiops.logRateAnalysisDataGenerator.generateData(testDataSetup.dataGenerator); + + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.securityUI.loginAsMlPowerUser(); + await ml.testResources.createDataViewIfNeeded( + testDataSetup.sourceIndexOrSavedSearch, + '@timestamp' + ); + + await PageObjects.common.setTime({ from, to }); + }); + + after(async () => { + await ml.testResources.deleteDataViewByTitle(testDataSetup.sourceIndexOrSavedSearch); + await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testDataSetup.dataGenerator); + await PageObjects.common.unsetTime(); + }); + + describe(testDataPanel.suiteSuffix, function () { + before(async () => { + await PageObjects.dashboard.navigateToApp(); + }); + + after(async () => { + await ml.testResources.deleteDashboardByTitle(testDataPanel.dashboardTitle); + }); + + it('should open initializer flyout', async () => { + await PageObjects.dashboard.clickNewDashboard(); + await aiops.dashboardEmbeddables.assertDashboardIsEmpty(); + await aiops.dashboardEmbeddables.openEmbeddableInitializer( + EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE + ); + }); + + it('should select data view', async () => { + await aiops.dashboardEmbeddables.assertLogRateAnalysisEmbeddableDataViewSelectorExists(); + await aiops.dashboardEmbeddables.selectLogRateAnalysisEmbeddableDataView( + testDataSetup.sourceIndexOrSavedSearch + ); + }); + + it('should create new log rate analysis panel', async () => { + await aiops.dashboardEmbeddables.clickLogRateAnalysisInitializerConfirmButtonEnabled(); + await PageObjects.timePicker.pauseAutoRefresh(); + await aiops.dashboardEmbeddables.assertDashboardPanelExists(testDataPanel.panelTitle); + await aiops.logRateAnalysisPage.assertAutoRunButtonExists(); + await PageObjects.dashboard.saveDashboard(testDataPanel.dashboardTitle); + }); + + it('should run log rate analysis', async () => { + await aiops.dashboardEmbeddables.assertDashboardPanelExists(testDataPanel.panelTitle); + await aiops.logRateAnalysisPage.clickAutoRunButton(); + // Wait for the analysis to finish + await aiops.logRateAnalysisPage.assertAnalysisComplete( + testDataSetup.analysisType, + testDataSetup.dataGenerator + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/services/aiops/dashboard_embeddables.ts b/x-pack/test/functional/services/aiops/dashboard_embeddables.ts new file mode 100644 index 0000000000000..a24cec24734da --- /dev/null +++ b/x-pack/test/functional/services/aiops/dashboard_embeddables.ts @@ -0,0 +1,110 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderContext) { + const comboBox = getService('comboBox'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const dashboardAddPanel = getService('dashboardAddPanel'); + + return { + async assertLogRateAnalysisEmbeddableInitializerExists() { + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.existOrFail('aiopsLogRateAnalysisEmbeddableInitializer', { + timeout: 1000, + }); + }); + }, + + async assertLogRateAnalysisEmbeddableInitializerNotExists() { + await retry.tryForTime(10 * 1000, async () => { + await testSubjects.missingOrFail('aiopsLogRateAnalysisEmbeddableInitializer', { + timeout: 1000, + }); + }); + }, + + async assertInitializerConfirmButtonEnabled(subj: string) { + await retry.tryForTime(60 * 1000, async () => { + await testSubjects.existOrFail(subj); + await testSubjects.isEnabled(subj); + }); + }, + + async assertDashboardIsEmpty() { + await retry.tryForTime(60 * 1000, async () => { + await testSubjects.existOrFail('emptyDashboardWidget'); + }); + }, + + async assertDashboardPanelExists(title: string) { + await retry.tryForTime(5000, async () => { + await find.existsByLinkText(title); + }); + }, + + async assertLogsAiopsSectionExists(expectExist = true) { + await retry.tryForTime(60 * 1000, async () => { + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.verifyEmbeddableFactoryGroupExists('logs-aiops', expectExist); + }); + }, + + async clickLogRateAnalysisInitializerConfirmButtonEnabled() { + const subj = 'aiopsLogRateAnalysisConfirmButton'; + await retry.tryForTime(60 * 1000, async () => { + await this.assertInitializerConfirmButtonEnabled(subj); + await testSubjects.clickWhenNotDisabledWithoutRetry(subj); + await this.assertLogRateAnalysisEmbeddableInitializerNotExists(); + }); + }, + + async openEmbeddableInitializer(mlEmbeddableType: 'aiopsLogRateAnalysisEmbeddable') { + const name = { + aiopsLogRateAnalysisEmbeddable: 'Log rate analysis', + }; + await retry.tryForTime(60 * 1000, async () => { + await dashboardAddPanel.clickEditorMenuButton(); + await testSubjects.existOrFail('dashboardPanelSelectionFlyout', { timeout: 2000 }); + + await dashboardAddPanel.verifyEmbeddableFactoryGroupExists('logs-aiops'); + + await dashboardAddPanel.clickAddNewPanelFromUIActionLink(name[mlEmbeddableType]); + await testSubjects.existOrFail('aiopsLogRateAnalysisControls', { timeout: 2000 }); + }); + }, + + async assertLogRateAnalysisEmbeddableDataViewSelectorExists() { + await testSubjects.existOrFail( + 'aiopsLogRateAnalysisEmbeddableDataViewSelector > comboBoxInput' + ); + }, + + async assertLogRateAnalysisEmbeddableDataViewSelection(dataViewValue: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'aiopsLogRateAnalysisEmbeddableDataViewSelector > comboBoxInput' + ); + expect(comboBoxSelectedOptions).to.eql( + [dataViewValue], + `Expected data view selection to be '${dataViewValue}' (got '${comboBoxSelectedOptions}')` + ); + }, + + async selectLogRateAnalysisEmbeddableDataView(dataViewValue: string) { + await comboBox.set( + 'aiopsLogRateAnalysisEmbeddableDataViewSelector > comboBoxInput', + dataViewValue + ); + await this.assertLogRateAnalysisEmbeddableDataViewSelection(dataViewValue); + }, + }; +} diff --git a/x-pack/test/functional/services/aiops/index.ts b/x-pack/test/functional/services/aiops/index.ts index 71de8c397c073..39abf21375f86 100644 --- a/x-pack/test/functional/services/aiops/index.ts +++ b/x-pack/test/functional/services/aiops/index.ts @@ -7,6 +7,7 @@ import type { FtrProviderContext } from '../../ftr_provider_context'; +import { AiopsDashboardEmbeddablesProvider } from './dashboard_embeddables'; import { LogRateAnalysisPageProvider } from './log_rate_analysis_page'; import { LogRateAnalysisResultsTableProvider } from './log_rate_analysis_results_table'; import { LogRateAnalysisResultsGroupsTableProvider } from './log_rate_analysis_results_groups_table'; @@ -16,6 +17,7 @@ import { ChangePointDetectionPageProvider } from './change_point_detection_page' import { MlTableServiceProvider } from '../ml/common_table_service'; export function AiopsProvider(context: FtrProviderContext) { + const dashboardEmbeddables = AiopsDashboardEmbeddablesProvider(context); const logRateAnalysisPage = LogRateAnalysisPageProvider(context); const logRateAnalysisResultsTable = LogRateAnalysisResultsTableProvider(context); const logRateAnalysisResultsGroupsTable = LogRateAnalysisResultsGroupsTableProvider(context); @@ -27,6 +29,7 @@ export function AiopsProvider(context: FtrProviderContext) { const changePointDetectionPage = ChangePointDetectionPageProvider(context, tableService); return { + dashboardEmbeddables, changePointDetectionPage, logRateAnalysisPage, logRateAnalysisResultsTable, 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 66c4e64f05efe..0f7b14e3e8be7 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 @@ -266,6 +266,22 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr ); }, + async clickAutoRunButton() { + await testSubjects.clickWhenNotDisabledWithoutRetry( + 'aiopsLogRateAnalysisContentRunAnalysisButton' + ); + + await retry.tryForTime(30 * 1000, async () => { + await testSubjects.missingOrFail('aiopsLogRateAnalysisContentRunAnalysisButton'); + }); + }, + + async assertAutoRunButtonExists() { + await retry.tryForTime(5000, async () => { + await testSubjects.existOrFail('aiopsLogRateAnalysisContentRunAnalysisButton'); + }); + }, + async assertNoAutoRunButtonExists() { await testSubjects.existOrFail('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton'); }, From e799c5214e6dd44edba6c688a27e3808a1227fd3 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 30 Oct 2024 14:28:27 +0100 Subject: [PATCH 39/42] fix initial loading --- .../state/log_rate_analysis_field_candidates_slice.ts | 2 +- x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx | 4 ++-- .../log_rate_analysis_content/log_rate_analysis_content.tsx | 2 +- .../log_rate_analysis/log_rate_analysis_options.tsx | 2 +- .../log_rate_analysis/log_rate_analysis_results.tsx | 5 +++-- .../log_rate_analysis_results_table/use_columns.tsx | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts index a3ac63c5927e9..07b1cd6fee402 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts @@ -110,7 +110,7 @@ export const fetchFieldCandidates = createAsyncThunk( // If the currentFieldFilterSkippedItems is null, we're on the first load, // only then we set the current skipped fields to the initial skipped fields. currentFieldFilterSkippedItems: - currentFieldFilterSkippedItems === null && initialFieldFilterSkippedItems.length > 0 + currentFieldFilterSkippedItems === null ? initialFieldFilterSkippedItems : currentFieldFilterSkippedItems, keywordFieldCandidates, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx index 130b7f3dd9357..9fd8e8240dde3 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx @@ -35,8 +35,8 @@ const getReduxStore = () => logRateAnalysisFieldCandidates: logRateAnalysisFieldCandidatesSlice.reducer, // Analysis results logRateAnalysisResults: logRateAnalysisResultsSlice.reducer, - // Handles running the analysis - logRateAnalysisStream: streamSlice.reducer, + // Handles running the analysis, needs to be "stream" for the async thunk to work properly. + stream: streamSlice.reducer, // Handles hovering and pinning table rows and column selection logRateAnalysisTable: logRateAnalysisTableSlice.reducer, }, 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 1f1a77fd8f47e..2821b59353b52 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 @@ -76,7 +76,7 @@ export const LogRateAnalysisContent: FC = ({ const dispatch = useAppDispatch(); - const isRunning = useAppSelector((s) => s.logRateAnalysisStream.isRunning); + const isRunning = useAppSelector((s) => s.stream.isRunning); const significantItems = useAppSelector((s) => s.logRateAnalysisResults.significantItems); const significantItemsGroups = useAppSelector( (s) => s.logRateAnalysisResults.significantItemsGroups diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx index 99a2829886aec..1e26a49f9eb23 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx @@ -92,7 +92,7 @@ export const LogRateAnalysisOptions: FC = ({ const dispatch = useAppDispatch(); const { groupResults } = useAppSelector((s) => s.logRateAnalysis); - const { isRunning } = useAppSelector((s) => s.logRateAnalysisStream); + const { isRunning } = useAppSelector((s) => s.stream); const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates); const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable); const { fieldFilterUniqueItems, initialFieldFilterSkippedItems } = fieldCandidates; 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 6ab82bd16e00d..9e3fa854873f0 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 @@ -114,7 +114,7 @@ export const LogRateAnalysisResults: FC = ({ isBrushCleared, groupResults, } = useAppSelector((s) => s.logRateAnalysis); - const { isRunning, errors: streamErrors } = useAppSelector((s) => s.logRateAnalysisStream); + const { isRunning, errors: streamErrors } = useAppSelector((s) => s.stream); const data = useAppSelector((s) => s.logRateAnalysisResults); const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates); const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable); @@ -290,12 +290,13 @@ export const LogRateAnalysisResults: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [shouldStart]); + // On mount, fetch field candidates first. Once they are populated, + // the actual analysis will be triggered. useEffect(() => { if (startParams) { dispatch(fetchFieldCandidates(startParams)); dispatch(setCurrentAnalysisType(analysisType)); dispatch(setCurrentAnalysisWindowParameters(chartWindowParameters)); - dispatch(startStream(startParams)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index 5aad495e5bf16..c5b7a83e33641 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -167,7 +167,7 @@ export const useColumns = ( const { earliest, latest } = useAppSelector((s) => s.logRateAnalysis); const timeRangeMs = { from: earliest ?? 0, to: latest ?? 0 }; - const loading = useAppSelector((s) => s.logRateAnalysisStream.isRunning); + const loading = useAppSelector((s) => s.stream.isRunning); const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback); const { documentStats: { documentCountStats }, From 7816dc5e915b2f8c5b157cfa4256a131aebee003 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 4 Nov 2024 09:19:58 +0100 Subject: [PATCH 40/42] use translated string for smart grouping legend --- .../components/log_rate_analysis/log_rate_analysis_options.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx index 1e26a49f9eb23..0aba8e2d763d4 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx @@ -143,7 +143,7 @@ export const LogRateAnalysisOptions: FC = ({ data-test-subj={`aiopsLogRateAnalysisGroupSwitch${groupResults ? ' checked' : ''}`} buttonSize="s" isDisabled={disabledGroupResultsSwitch} - legend="Smart grouping" + legend={groupResultsMessage} options={toggleButtons} idSelected={toggleIdSelected} onChange={onGroupResultsToggle} From 1e82368e602d6f86b91273b609d7864c6787abbc Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 4 Nov 2024 09:24:21 +0100 Subject: [PATCH 41/42] update TimeFieldWarning component texts to be suitable for both log rate analysis and pattern analysis --- .../public/components/time_field_warning.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/aiops/public/components/time_field_warning.tsx b/x-pack/plugins/aiops/public/components/time_field_warning.tsx index 25e8dd21d68f7..beb64917af538 100644 --- a/x-pack/plugins/aiops/public/components/time_field_warning.tsx +++ b/x-pack/plugins/aiops/public/components/time_field_warning.tsx @@ -14,22 +14,16 @@ export const TimeFieldWarning = () => { <>

- {i18n.translate( - 'xpack.aiops.logCategorization.embeddableMenu.timeFieldWarning.title.description', - { - defaultMessage: 'Pattern analysis can only be run on data views with a time field.', - } - )} + {i18n.translate('xpack.aiops.embeddableMenu.timeFieldWarning.title.description', { + defaultMessage: 'The analysis can only be run on data views with a time field.', + })}

From c1fe2f72c1f21a810966bb3bd991e53702182cc9 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 5 Nov 2024 10:40:59 +0100 Subject: [PATCH 42/42] aria-label for aiopsLogRateAnalysisOptionsButton --- .../components/log_rate_analysis/log_rate_analysis_results.tsx | 3 +++ 1 file changed, 3 insertions(+) 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 d597764e5374e..1eb4f8fd0af0d 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 @@ -363,6 +363,9 @@ export const LogRateAnalysisResults: FC = ({ data-test-subj="aiopsLogRateAnalysisOptionsButton" iconType="controlsHorizontal" onClick={onEmbeddableOptionsClickHandler} + aria-label={i18n.translate('xpack.aiops.logRateAnalysis.optionsButtonAriaLabel', { + defaultMessage: 'Analysis options', + })} />